|
|
@ -1,12 +1,13 @@ |
|
|
|
import VueChartwerkPodMixin from './VueChartwerkPodMixin'; |
|
|
|
import VueChartwerkPodMixin from './VueChartwerkPodMixin'; |
|
|
|
import { PodState } from './state'; |
|
|
|
import { PodState } from './state'; |
|
|
|
import { Grid } from './components/grid'; |
|
|
|
import { Grid } from './components/grid'; |
|
|
|
|
|
|
|
import { CoreSeries } from 'models/series'; |
|
|
|
|
|
|
|
|
|
|
|
import styles from './css/style.css'; |
|
|
|
import styles from './css/style.css'; |
|
|
|
|
|
|
|
|
|
|
|
import { |
|
|
|
import { |
|
|
|
Margin, |
|
|
|
Margin, |
|
|
|
TimeSerie, |
|
|
|
CoreSerie, |
|
|
|
Options, |
|
|
|
Options, |
|
|
|
TickOrientation, |
|
|
|
TickOrientation, |
|
|
|
TimeFormat, |
|
|
|
TimeFormat, |
|
|
@ -109,7 +110,11 @@ const DEFAULT_OPTIONS: Options = { |
|
|
|
renderLegend: true, |
|
|
|
renderLegend: true, |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
abstract class ChartwerkPod<T extends TimeSerie, O extends Options> { |
|
|
|
abstract class ChartwerkPod<T extends CoreSerie, O extends Options> { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
protected coreSeries: CoreSeries<T>; |
|
|
|
|
|
|
|
protected options: O; |
|
|
|
|
|
|
|
|
|
|
|
protected d3Node?: d3.Selection<HTMLElement, unknown, null, undefined>; |
|
|
|
protected d3Node?: d3.Selection<HTMLElement, unknown, null, undefined>; |
|
|
|
protected customOverlay?: d3.Selection<SVGRectElement, unknown, null, undefined>; |
|
|
|
protected customOverlay?: d3.Selection<SVGRectElement, unknown, null, undefined>; |
|
|
|
protected crosshair?: d3.Selection<SVGGElement, unknown, null, undefined>; |
|
|
|
protected crosshair?: d3.Selection<SVGGElement, unknown, null, undefined>; |
|
|
@ -130,8 +135,7 @@ abstract class ChartwerkPod<T extends TimeSerie, O extends Options> { |
|
|
|
protected y1AxisElement?: d3.Selection<SVGGElement, unknown, null, undefined>; |
|
|
|
protected y1AxisElement?: d3.Selection<SVGGElement, unknown, null, undefined>; |
|
|
|
protected yAxisTicksColors?: string[] = []; |
|
|
|
protected yAxisTicksColors?: string[] = []; |
|
|
|
private _clipPathUID = ''; |
|
|
|
private _clipPathUID = ''; |
|
|
|
protected series: T[]; |
|
|
|
|
|
|
|
protected options: O; |
|
|
|
|
|
|
|
protected readonly d3: typeof d3; |
|
|
|
protected readonly d3: typeof d3; |
|
|
|
protected deltaYTransform = 0; |
|
|
|
protected deltaYTransform = 0; |
|
|
|
// TODO: forceRerender is a hack, it will be remove someday. But we need to update state on resize
|
|
|
|
// TODO: forceRerender is a hack, it will be remove someday. But we need to update state on resize
|
|
|
@ -154,7 +158,8 @@ abstract class ChartwerkPod<T extends TimeSerie, O extends Options> { |
|
|
|
let options = cloneDeep(_options); |
|
|
|
let options = cloneDeep(_options); |
|
|
|
defaultsDeep(options, DEFAULT_OPTIONS); |
|
|
|
defaultsDeep(options, DEFAULT_OPTIONS); |
|
|
|
this.options = options; |
|
|
|
this.options = options; |
|
|
|
this.series = cloneDeep(_series); |
|
|
|
|
|
|
|
|
|
|
|
this.coreSeries = new CoreSeries(_series); |
|
|
|
|
|
|
|
|
|
|
|
this.d3Node = d3.select(this.el); |
|
|
|
this.d3Node = d3.select(this.el); |
|
|
|
this.addEventListeners(); |
|
|
|
this.addEventListeners(); |
|
|
@ -227,8 +232,7 @@ abstract class ChartwerkPod<T extends TimeSerie, O extends Options> { |
|
|
|
if(newSeries === undefined) { |
|
|
|
if(newSeries === undefined) { |
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
let series = cloneDeep(newSeries); |
|
|
|
this.coreSeries.updateSeries(newSeries); |
|
|
|
this.series = series; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
protected abstract renderMetrics(): void; |
|
|
|
protected abstract renderMetrics(): void; |
|
|
@ -243,7 +247,7 @@ abstract class ChartwerkPod<T extends TimeSerie, O extends Options> { |
|
|
|
height: this.height, |
|
|
|
height: this.height, |
|
|
|
width: this.width, |
|
|
|
width: this.width, |
|
|
|
} |
|
|
|
} |
|
|
|
this.state = new PodState(boxPararms, this.series, this.options); |
|
|
|
this.state = new PodState(boxPararms, this.coreSeries.visibleSeries, this.options); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
protected initComponents(): void { |
|
|
|
protected initComponents(): void { |
|
|
@ -521,46 +525,48 @@ abstract class ChartwerkPod<T extends TimeSerie, O extends Options> { |
|
|
|
if(this.options.renderLegend === false) { |
|
|
|
if(this.options.renderLegend === false) { |
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
if(this.series.length > 0) { |
|
|
|
if(!this.coreSeries.isSeriesAvailable) { |
|
|
|
let legendRow = this.chartContainer |
|
|
|
return; |
|
|
|
.append('g') |
|
|
|
} |
|
|
|
.attr('class', 'legend-row'); |
|
|
|
let legendRow = this.chartContainer |
|
|
|
for(let idx = 0; idx < this.series.length; idx++) { |
|
|
|
.append('g') |
|
|
|
if(includes(this.seriesTargetsWithBounds, this.series[idx].target)) { |
|
|
|
.attr('class', 'legend-row'); |
|
|
|
continue; |
|
|
|
const series = this.coreSeries.allSeries;
|
|
|
|
} |
|
|
|
for(let idx = 0; idx < series.length; idx++) { |
|
|
|
let node = legendRow.selectAll('text').node(); |
|
|
|
if(includes(this.seriesTargetsWithBounds, series[idx].target)) { |
|
|
|
let rowWidth = 0; |
|
|
|
continue; |
|
|
|
if(node !== null) { |
|
|
|
|
|
|
|
rowWidth = legendRow.node().getBBox().width + 25; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const isChecked = this.series[idx].visible !== false; |
|
|
|
|
|
|
|
legendRow.append('foreignObject') |
|
|
|
|
|
|
|
.attr('x', rowWidth) |
|
|
|
|
|
|
|
.attr('y', this.legendRowPositionY - 12) |
|
|
|
|
|
|
|
.attr('width', 13) |
|
|
|
|
|
|
|
.attr('height', 15) |
|
|
|
|
|
|
|
.html(`<form><input type=checkbox ${isChecked? 'checked' : ''} /></form>`) |
|
|
|
|
|
|
|
.on('click', () => { |
|
|
|
|
|
|
|
if(this.options.eventsCallbacks !== undefined && this.options.eventsCallbacks.onLegendClick !== undefined) { |
|
|
|
|
|
|
|
this.options.eventsCallbacks.onLegendClick(idx); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
legendRow.append('text') |
|
|
|
|
|
|
|
.attr('x', rowWidth + 20) |
|
|
|
|
|
|
|
.attr('y', this.legendRowPositionY) |
|
|
|
|
|
|
|
.attr('class', `metric-legend-${idx}`) |
|
|
|
|
|
|
|
.style('font-size', '12px') |
|
|
|
|
|
|
|
.style('fill', this.getSerieColor(idx)) |
|
|
|
|
|
|
|
.text(this.series[idx].target) |
|
|
|
|
|
|
|
.on('click', () => { |
|
|
|
|
|
|
|
if(this.options.eventsCallbacks !== undefined && this.options.eventsCallbacks.onLegendLabelClick !== undefined) { |
|
|
|
|
|
|
|
this.options.eventsCallbacks.onLegendLabelClick(idx); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
let node = legendRow.selectAll('text').node(); |
|
|
|
|
|
|
|
let rowWidth = 0; |
|
|
|
|
|
|
|
if(node !== null) { |
|
|
|
|
|
|
|
rowWidth = legendRow.node().getBBox().width + 25; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const isChecked = series[idx].visible !== false; |
|
|
|
|
|
|
|
legendRow.append('foreignObject') |
|
|
|
|
|
|
|
.attr('x', rowWidth) |
|
|
|
|
|
|
|
.attr('y', this.legendRowPositionY - 12) |
|
|
|
|
|
|
|
.attr('width', 13) |
|
|
|
|
|
|
|
.attr('height', 15) |
|
|
|
|
|
|
|
.html(`<form><input type=checkbox ${isChecked? 'checked' : ''} /></form>`) |
|
|
|
|
|
|
|
.on('click', () => { |
|
|
|
|
|
|
|
if(this.options.eventsCallbacks !== undefined && this.options.eventsCallbacks.onLegendClick !== undefined) { |
|
|
|
|
|
|
|
this.options.eventsCallbacks.onLegendClick(idx); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
legendRow.append('text') |
|
|
|
|
|
|
|
.attr('x', rowWidth + 20) |
|
|
|
|
|
|
|
.attr('y', this.legendRowPositionY) |
|
|
|
|
|
|
|
.attr('class', `metric-legend-${idx}`) |
|
|
|
|
|
|
|
.style('font-size', '12px') |
|
|
|
|
|
|
|
.style('fill', series[idx].color) |
|
|
|
|
|
|
|
.text(series[idx].target) |
|
|
|
|
|
|
|
.on('click', () => { |
|
|
|
|
|
|
|
if(this.options.eventsCallbacks !== undefined && this.options.eventsCallbacks.onLegendLabelClick !== undefined) { |
|
|
|
|
|
|
|
this.options.eventsCallbacks.onLegendLabelClick(idx); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -585,7 +591,7 @@ abstract class ChartwerkPod<T extends TimeSerie, O extends Options> { |
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
let yPosition = this.height + this.margin.top + this.margin.bottom - 35; |
|
|
|
let yPosition = this.height + this.margin.top + this.margin.bottom - 35; |
|
|
|
if(this.series.length === 0) { |
|
|
|
if(this.coreSeries.isSeriesAvailable) { |
|
|
|
yPosition += 20; |
|
|
|
yPosition += 20; |
|
|
|
} |
|
|
|
} |
|
|
|
this.chartContainer.append('text') |
|
|
|
this.chartContainer.append('text') |
|
|
@ -943,11 +949,11 @@ abstract class ChartwerkPod<T extends TimeSerie, O extends Options> { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
get serieTimestampRange(): number | undefined { |
|
|
|
get serieTimestampRange(): number | undefined { |
|
|
|
if(this.series.length === 0) { |
|
|
|
if(!this.coreSeries.isSeriesAvailable) { |
|
|
|
return undefined; |
|
|
|
return undefined; |
|
|
|
} |
|
|
|
} |
|
|
|
const startTimestamp = first(this.series[0].datapoints)[0]; |
|
|
|
const startTimestamp = first(this.coreSeries.visibleSeries[0].datapoints)[0]; |
|
|
|
const endTimestamp = last(this.series[0].datapoints)[0]; |
|
|
|
const endTimestamp = last(this.coreSeries.visibleSeries[0].datapoints)[0]; |
|
|
|
return (endTimestamp - startTimestamp) / 1000; |
|
|
|
return (endTimestamp - startTimestamp) / 1000; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -982,8 +988,8 @@ abstract class ChartwerkPod<T extends TimeSerie, O extends Options> { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
get timeInterval(): number { |
|
|
|
get timeInterval(): number { |
|
|
|
if(this.series !== undefined && this.series.length > 0 && this.series[0].datapoints.length > 1) { |
|
|
|
if(this.coreSeries.isSeriesAvailable && this.coreSeries.visibleSeries[0].datapoints.length > 1) { |
|
|
|
const interval = this.series[0].datapoints[1][0] - this.series[0].datapoints[0][0]; |
|
|
|
const interval = this.coreSeries.visibleSeries[0].datapoints[1][0] - this.coreSeries.visibleSeries[0].datapoints[0][0]; |
|
|
|
return interval; |
|
|
|
return interval; |
|
|
|
} |
|
|
|
} |
|
|
|
if(this.options.timeInterval !== undefined && this.options.timeInterval.count !== undefined) { |
|
|
|
if(this.options.timeInterval !== undefined && this.options.timeInterval.count !== undefined) { |
|
|
@ -1033,7 +1039,7 @@ abstract class ChartwerkPod<T extends TimeSerie, O extends Options> { |
|
|
|
optionalMargin.left += 20; |
|
|
|
optionalMargin.left += 20; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
if(this.series.length > 0) { |
|
|
|
if(this.coreSeries.isSeriesAvailable) { |
|
|
|
optionalMargin.bottom += 25; |
|
|
|
optionalMargin.bottom += 25; |
|
|
|
} |
|
|
|
} |
|
|
|
return optionalMargin; |
|
|
|
return optionalMargin; |
|
|
@ -1067,19 +1073,6 @@ abstract class ChartwerkPod<T extends TimeSerie, O extends Options> { |
|
|
|
this.state.clearState(); |
|
|
|
this.state.clearState(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
protected getSerieColor(idx: number): string { |
|
|
|
|
|
|
|
if(this.series[idx] === undefined) { |
|
|
|
|
|
|
|
throw new Error( |
|
|
|
|
|
|
|
`Can't get color for unexisting serie: ${idx}, there are only ${this.series.length} series` |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
let serieColor = this.series[idx].color; |
|
|
|
|
|
|
|
if(serieColor === undefined) { |
|
|
|
|
|
|
|
serieColor = palette[idx % palette.length]; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return serieColor; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
protected get seriesTargetsWithBounds(): any[] { |
|
|
|
protected get seriesTargetsWithBounds(): any[] { |
|
|
|
if( |
|
|
|
if( |
|
|
|
this.options.bounds === undefined || |
|
|
|
this.options.bounds === undefined || |
|
|
@ -1089,17 +1082,13 @@ abstract class ChartwerkPod<T extends TimeSerie, O extends Options> { |
|
|
|
return []; |
|
|
|
return []; |
|
|
|
} |
|
|
|
} |
|
|
|
let series = []; |
|
|
|
let series = []; |
|
|
|
this.series.forEach(serie => { |
|
|
|
this.coreSeries.allSeries.forEach(serie => { |
|
|
|
series.push(this.formattedBound(this.options.bounds.upper, serie.target)); |
|
|
|
series.push(this.formattedBound(this.options.bounds.upper, serie.target)); |
|
|
|
series.push(this.formattedBound(this.options.bounds.lower, serie.target)); |
|
|
|
series.push(this.formattedBound(this.options.bounds.lower, serie.target)); |
|
|
|
}); |
|
|
|
}); |
|
|
|
return series; |
|
|
|
return series; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
protected get visibleSeries(): any[] { |
|
|
|
|
|
|
|
return this.series.filter(serie => serie.visible !== false); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
protected get rectClipId(): string { |
|
|
|
protected get rectClipId(): string { |
|
|
|
if(this._clipPathUID.length === 0) { |
|
|
|
if(this._clipPathUID.length === 0) { |
|
|
|
this._clipPathUID = uid(); |
|
|
|
this._clipPathUID = uid(); |
|
|
@ -1123,7 +1112,7 @@ abstract class ChartwerkPod<T extends TimeSerie, O extends Options> { |
|
|
|
|
|
|
|
|
|
|
|
export { |
|
|
|
export { |
|
|
|
ChartwerkPod, VueChartwerkPodMixin, |
|
|
|
ChartwerkPod, VueChartwerkPodMixin, |
|
|
|
Margin, TimeSerie, Options, TickOrientation, TimeFormat, BrushOrientation, PanOrientation, |
|
|
|
Margin, CoreSerie, Options, TickOrientation, TimeFormat, BrushOrientation, PanOrientation, |
|
|
|
AxisFormat, yAxisOrientation, CrosshairOrientation, ScrollPanOrientation, ScrollPanDirection, KeyEvent, |
|
|
|
AxisFormat, yAxisOrientation, CrosshairOrientation, ScrollPanOrientation, ScrollPanDirection, KeyEvent, |
|
|
|
palette |
|
|
|
palette |
|
|
|
}; |
|
|
|
}; |
|
|
|