diff --git a/package.json b/package.json index 4f5d0e5..30e7724 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "scripts": { "build": "webpack --config build/webpack.prod.conf.js && webpack --config build/webpack.dev.conf.js", "dev": "webpack --watch --config build/webpack.dev.conf.js", - "test": "echo \"Error: no test specified\" && exit 1" + "test": "echo \"Error: no test specified\" && exit 1", + "update-core": "yarn up @chartwerk/core && yarn up @chartwerk/core@latest" }, "repository": { "type": "git", diff --git a/src/index.ts b/src/index.ts index 97917cf..80688df 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,8 @@ -import { ChartwerkPod, VueChartwerkPodMixin, TickOrientation, TimeFormat, CrosshairOrientation, BrushOrientation } from '@chartwerk/core'; +import { ChartwerkPod, VueChartwerkPodMixin, TimeFormat, CrosshairOrientation, BrushOrientation } from '@chartwerk/core'; import { LineTimeSerie, LineOptions, Mode } from './types'; +import { LineSeries } from './models/line-series'; + import * as d3 from 'd3'; import * as _ from 'lodash'; @@ -15,6 +17,7 @@ export class LinePod extends ChartwerkPod { constructor(_el: HTMLElement, _series: LineTimeSerie[] = [], _options: LineOptions = {}) { super(_el, _series, _options); + this.coreSeries = new LineSeries(_series); } renderMetrics(): void { @@ -24,35 +27,13 @@ export class LinePod extends ChartwerkPod { this.initLineGenerator(); this.initAreaGenerator(); - // TODO: seems that renderMetrics is not correct name - if(this.series.length === 0) { + if(!this.coreSeries.isSeriesAvailable) { this.renderNoDataPointsMessage(); return; } - for(let idx = 0; idx < this.series.length; ++idx) { - if(this.series[idx].visible === false) { - continue; - } - // TODO: use _.defaults same as in core - const confidence = this.series[idx].confidence || 0; - const mode = this.series[idx].mode || Mode.STANDARD; - const target = this.series[idx].target; - const renderDots = this.series[idx].renderDots !== undefined ? this.series[idx].renderDots : false; - const renderLines = this.series[idx].renderLines !== undefined ? this.series[idx].renderLines : true; - - this._renderMetric( - this.series[idx].datapoints, - { - color: this.getSerieColor(idx), - confidence, - target, - mode, - serieIdx: idx, - renderDots, - renderLines, - } - ); + for(const serie of this.coreSeries.visibleSeries) { + this._renderMetric(serie); } } @@ -63,19 +44,18 @@ export class LinePod extends ChartwerkPod { initLineGenerator(): void { this.lineGenerator = d3.line() - .x(d => this.xScale(d[0])) - .y(d => this.yScale(d[1])); + .x(d => this.state.xScale(d[0])) + .y(d => this.state.yScale(d[1])); } initAreaGenerator(): void { this.areaGenerator = d3.area() - .x(d => this.xScale(d[0])) - .y1(d => this.yScale(d[1])) + .x(d => this.state.xScale(d[0])) + .y1(d => this.state.yScale(d[1])) .y0(d => this.height); } - getRenderGenerator(serieIdx: number): any { - const renderArea = this.series[serieIdx].renderArea; + getRenderGenerator(renderArea: boolean): any { if(renderArea) { return this.areaGenerator; } @@ -83,29 +63,26 @@ export class LinePod extends ChartwerkPod { } public appendData(data: [number, number][], shouldRerender = true): void { - for(let idx = 0; idx < this.series.length; ++idx) { - if(this.series[idx].visible === false) { - continue; - } - this.series[idx].datapoints.push(data[idx]); - const maxLength = this.series[idx].maxLength; - if(maxLength !== undefined && this.series[idx].datapoints.length > maxLength) { - this.series[idx].datapoints.shift(); + for(const serie of this.coreSeries.visibleSeries) { + serie.datapoints.push(data[serie.idx]); + const maxLength = serie.maxLength; + if(maxLength !== undefined && serie.datapoints.length > maxLength) { + serie.datapoints.shift(); } } - for(let idx = 0; idx < this.series.length; ++idx) { - this.metricContainer.select(`.metric-path-${idx}`) - .datum(this.series[idx].datapoints) - .attr('d', this.getRenderGenerator(idx)); + for(const serie of this.coreSeries.visibleSeries) { + this.metricContainer.select(`.metric-path-${serie.idx}`) + .datum(serie.datapoints) + .attr('d', this.getRenderGenerator(serie.renderArea)); - if(this.series[idx].renderDots === true) { - this.metricContainer.selectAll(`.metric-circle-${idx}`) - .data(this.series[idx].datapoints) - .attr('cx', d => this.xScale(d[0])) - .attr('cy', d => this.yScale(d[1])); + if(serie.renderDots === true) { + this.metricContainer.selectAll(`.metric-circle-${serie.idx}`) + .data(serie.datapoints) + .attr('cx', d => this.state.xScale(d[0])) + .attr('cy', d => this.state.yScale(d[1])); - this._renderDots([data[idx]], idx); + this._renderDots(serie); } } if(shouldRerender) { @@ -117,67 +94,48 @@ export class LinePod extends ChartwerkPod { } } - _renderDots(datapoints: number[][], serieIdx: number): void { - const customClass = this.series[serieIdx].class || ''; - + _renderDots(serie: LineTimeSerie): void { this.metricContainer.selectAll(null) - .data(datapoints) + .data(serie.datapoints) .enter() .append('circle') - .attr('class', `metric-circle-${serieIdx} metric-el ${customClass}`) - .attr('fill', this.getSerieColor(serieIdx)) + .attr('class', `metric-circle-${serie.idx} metric-el ${serie.class}`) + .attr('fill', serie.color) .attr('r', METRIC_CIRCLE_RADIUS) .style('pointer-events', 'none') - .attr('cx', d => this.xScale(d[0])) - .attr('cy', d => this.yScale(d[1])); + .attr('cx', d => this.state.xScale(d[0])) + .attr('cy', d => this.state.yScale(d[1])); } - _renderLines(datapoints: number[][], serieIdx: number): void { - const dashArray = this.series[serieIdx].dashArray !== undefined ? this.series[serieIdx].dashArray : '0'; - const customClass = this.series[serieIdx].class || ''; - const fillColor = this.series[serieIdx].renderArea ? this.getSerieColor(serieIdx) : 'none'; - const fillOpacity = this.series[serieIdx].renderArea ? 0.5 : 'none'; + _renderLines(serie: LineTimeSerie): void { + const fillColor = serie.renderArea ? serie.color : 'none'; + const fillOpacity = serie.renderArea ? 0.5 : 'none'; this.metricContainer .append('path') - .datum(datapoints) - .attr('class', `metric-path-${serieIdx} metric-el ${customClass}`) + .datum(serie.datapoints) + .attr('class', `metric-path-${serie.idx} metric-el ${serie.class}`) .attr('fill', fillColor) .attr('fill-opacity', fillOpacity) - .attr('stroke', this.getSerieColor(serieIdx)) + .attr('stroke', serie.color) .attr('stroke-width', 1) .attr('stroke-opacity', 0.7) .attr('pointer-events', 'none') - .style('stroke-dasharray', dashArray) - .attr('d', this.getRenderGenerator(serieIdx)); + .style('stroke-dasharray', serie.dashArray) + .attr('d', this.getRenderGenerator(serie.renderArea)); } - _renderMetric( - datapoints: number[][], - metricOptions: { - color: string, - confidence: number, - target: string, - mode: Mode, - serieIdx: number, - renderDots: boolean, - renderLines: boolean, - } - ): void { - if(_.includes(this.seriesTargetsWithBounds, metricOptions.target)) { - return; - } - - if(metricOptions.mode === Mode.CHARGE) { - const dataPairs = d3.pairs(datapoints); + _renderMetric(serie: LineTimeSerie): void { + if(serie.mode === Mode.CHARGE) { + const dataPairs = d3.pairs(serie.datapoints); this.metricContainer.selectAll(null) .data(dataPairs) .enter() .append('line') - .attr('x1', d => this.xScale(d[0][0])) - .attr('x2', d => this.xScale(d[1][0])) - .attr('y1', d => this.yScale(d[0][1])) - .attr('y2', d => this.yScale(d[1][1])) + .attr('x1', d => this.state.xScale(d[0][0])) + .attr('x2', d => this.state.xScale(d[1][0])) + .attr('y1', d => this.state.yScale(d[0][1])) + .attr('y2', d => this.state.yScale(d[1][1])) .attr('stroke-opacity', 0.7) .style('stroke-width', 1) .style('stroke', d => { @@ -192,12 +150,12 @@ export class LinePod extends ChartwerkPod { return; } - if(metricOptions.renderLines === true) { - this._renderLines(datapoints, metricOptions.serieIdx); + if(serie.renderLines === true) { + this._renderLines(serie); } - if(metricOptions.renderDots === true) { - this._renderDots(datapoints, metricOptions.serieIdx); + if(serie.renderDots === true) { + this._renderDots(serie); } } @@ -209,7 +167,7 @@ export class LinePod extends ChartwerkPod { appendCrosshairCircles(): void { // circle for each serie - this.series.forEach((serie: LineTimeSerie, serieIdx: number) => { + this.coreSeries.visibleSeries.forEach((serie: LineTimeSerie, serieIdx: number) => { this.appendCrosshairCircle(serieIdx); }); } @@ -222,7 +180,7 @@ export class LinePod extends ChartwerkPod { .attr('cx', -CROSSHAIR_BACKGROUND_RAIDUS) .attr('cy', -CROSSHAIR_BACKGROUND_RAIDUS) .attr('clip-path', `url(#${this.rectClipId})`) - .attr('fill', this.getSerieColor(serieIdx)) + .attr('fill', this.coreSeries.visibleSeries[serieIdx].color) .style('opacity', CROSSHAIR_BACKGROUND_OPACITY) .style('pointer-events', 'none'); @@ -232,23 +190,19 @@ export class LinePod extends ChartwerkPod { .attr('cy', -CROSSHAIR_CIRCLE_RADIUS) .attr('class', `crosshair-circle-${serieIdx}`) .attr('clip-path', `url(#${this.rectClipId})`) - .attr('fill', this.getSerieColor(serieIdx)) + .attr('fill', this.coreSeries.visibleSeries[serieIdx].color) .attr('r', CROSSHAIR_CIRCLE_RADIUS) .style('pointer-events', 'none'); } public renderSharedCrosshair(values: { x?: number, y?: number }): void { this.onMouseOver(); // TODO: refactor to use it once - const eventX = this.xScale(values.x); - const eventY = this.yScale(values.y); + const eventX = this.state.xScale(values.x); + const eventY = this.state.yScale(values.y); this.moveCrosshairLine(eventX, eventY); const datapoints = this.findAndHighlightDatapoints(values.x, values.y); - if(this.options.eventsCallbacks === undefined || this.options.eventsCallbacks.sharedCrosshairMove === undefined) { - return; - } - - this.options.eventsCallbacks.sharedCrosshairMove({ + this.coreOptions.callbackSharedCrosshairMove({ datapoints: datapoints, eventX, eventY }); @@ -259,7 +213,7 @@ export class LinePod extends ChartwerkPod { } moveCrosshairLine(xPosition: number, yPosition: number): void { - switch(this.options.crosshair.orientation) { + switch(this.coreOptions.crosshair.orientation) { case CrosshairOrientation.VERTICAL: this.crosshair.select('#crosshair-line-x') .attr('x1', xPosition) @@ -279,7 +233,7 @@ export class LinePod extends ChartwerkPod { .attr('y2', yPosition); return; default: - throw new Error(`Unknown type of crosshair orientaion: ${this.options.crosshair.orientation}`); + throw new Error(`Unknown type of crosshair orientaion: ${this.coreOptions.crosshair.orientation}`); } } @@ -307,7 +261,7 @@ export class LinePod extends ChartwerkPod { getClosestIndex(datapoints: [number, number][], xValue: number, yValue: number): number { let columnIdx; // 0 for x value, 1 for y value, let value; // xValue to y Value - switch(this.options.crosshair.orientation) { + switch(this.coreOptions.crosshair.orientation) { case CrosshairOrientation.VERTICAL: columnIdx = 0; value = xValue; @@ -321,7 +275,7 @@ export class LinePod extends ChartwerkPod { columnIdx = 1; value = yValue; default: - throw new Error(`Unknown type of crosshair orientaion: ${this.options.crosshair.orientation}`); + throw new Error(`Unknown type of crosshair orientaion: ${this.coreOptions.crosshair.orientation}`); } // TODO: d3.bisect is not the best way. Use binary search @@ -350,7 +304,7 @@ export class LinePod extends ChartwerkPod { // columnIdx: 1 for y, 0 for x // inverval: x/y value interval between data points // TODO: move it to base/state instead of timeInterval - const intervals = _.map(this.series, serie => { + const intervals = _.map(this.coreSeries.visibleSeries, serie => { if(serie.datapoints.length < 2) { return undefined; } @@ -366,23 +320,15 @@ export class LinePod extends ChartwerkPod { onMouseMove(): void { const eventX = d3.mouse(this.chartContainer.node())[0]; const eventY = d3.mouse(this.chartContainer.node())[1]; - const xValue = this.xScale.invert(eventX); // mouse x position in xScale - const yValue = this.yScale.invert(eventY); - // TODO: isOutOfChart is a hack, use clip path correctly - if(this.isOutOfChart() === true) { - this.crosshair.style('display', 'none'); - return; - } + const xValue = this.state.xScale.invert(eventX); // mouse x position in xScale + const yValue = this.state.yScale.invert(eventY); this.moveCrosshairLine(eventX, eventY); const datapoints = this.findAndHighlightDatapoints(xValue, yValue); - if(this.options.eventsCallbacks === undefined || this.options.eventsCallbacks.mouseMove === undefined) { - return; - } // TDOO: is shift key pressed // TODO: need to refactor this object - this.options.eventsCallbacks.mouseMove({ + this.coreOptions.callbackMouseMove({ x: d3.event.pageX, y: d3.event.pageY, xVal: xValue, @@ -394,31 +340,24 @@ export class LinePod extends ChartwerkPod { } findAndHighlightDatapoints(xValue: number, yValue: number): { value: [number, number], color: string, label: string }[] { - if(this.series === undefined || this.series.length === 0) { + if(!this.coreSeries.isSeriesAvailable) { return []; } let points = []; // datapoints in each metric that is closest to xValue/yValue position - this.series.forEach((serie: LineTimeSerie, serieIdx: number) => { - if( - serie.visible === false || - _.includes(this.seriesTargetsWithBounds, serie.target) - ) { - this.hideCrosshairCircle(serieIdx); - return; - } + this.coreSeries.visibleSeries.forEach((serie: LineTimeSerie) => { const closestDatapoint = this.getClosestDatapoint(serie, xValue, yValue); if(closestDatapoint === undefined || this.isOutOfRange(closestDatapoint, xValue, yValue, serie.useOutOfRange)) { - this.hideCrosshairCircle(serieIdx); + this.hideCrosshairCircle(serie.idx); return; } - const xPosition = this.xScale(closestDatapoint[0]); - const yPosition = this.yScale(closestDatapoint[1]); - this.moveCrosshairCircle(xPosition, yPosition, serieIdx); + const xPosition = this.state.xScale(closestDatapoint[0]); + const yPosition = this.state.yScale(closestDatapoint[1]); + this.moveCrosshairCircle(xPosition, yPosition, serie.idx); points.push({ value: closestDatapoint, - color: this.getSerieColor(serieIdx), + color: serie.color, label: serie.alias || serie.target }); }); @@ -433,7 +372,7 @@ export class LinePod extends ChartwerkPod { } let columnIdx; // 1 for y value, 0 for x value let value; // xValue ot y Value - switch(this.options.crosshair.orientation) { + switch(this.coreOptions.crosshair.orientation) { case CrosshairOrientation.VERTICAL: columnIdx = 0; value = xValue; @@ -447,7 +386,7 @@ export class LinePod extends ChartwerkPod { columnIdx = 1; value = yValue; default: - throw new Error(`Unknown type of crosshair orientaion: ${this.options.crosshair.orientation}`); + throw new Error(`Unknown type of crosshair orientaion: ${this.coreOptions.crosshair.orientation}`); } const range = Math.abs(closestDatapoint[columnIdx] - value); const interval = this.getValueInterval(columnIdx); // interval between points @@ -462,30 +401,21 @@ export class LinePod extends ChartwerkPod { } onMouseOut(): void { - if(this.options.eventsCallbacks !== undefined && this.options.eventsCallbacks.mouseOut !== undefined) { - this.options.eventsCallbacks.mouseOut(); - } + this.coreOptions.callbackMouseOut(); this.crosshair.style('display', 'none'); } isDoubleClickActive(): boolean { - if(this.options.zoomEvents.mouse.doubleClick === undefined) { - return false; - } - return this.options.zoomEvents.mouse.doubleClick.isActive; + return this.coreOptions.doubleClickEvent.isActive; } // methods below rewrite cores, (move more methods here) protected zoomOut(): void { - // TODO: test to remove, seems its depricated - if(this.isOutOfChart() === true) { - return; - } if(d3.event.type === 'dblclick' && !this.isDoubleClickActive()) { return; } // TODO: its not clear, why we use this orientation here. Mb its better to use separate option - const orientation: BrushOrientation = this.options.zoomEvents.mouse.zoom.orientation; + const orientation: BrushOrientation = this.coreOptions.mouseZoomEvent.orientation; const xInterval = this.state.xValueRange[1] - this.state.xValueRange[0]; const yInterval = this.state.yValueRange[1] - this.state.yValueRange[0]; switch(orientation) { @@ -514,15 +444,13 @@ export class LinePod extends ChartwerkPod { this.renderGrid(); this.onMouseOver(); - if(this.options.eventsCallbacks !== undefined && this.options.eventsCallbacks.zoomOut !== undefined) { - let xAxisMiddleValue: number = this.xScale.invert(this.width / 2); - let yAxisMiddleValue: number = this.yScale.invert(this.height / 2); - const centers = { - x: xAxisMiddleValue, - y: yAxisMiddleValue - } - this.options.eventsCallbacks.zoomOut(centers); + let xAxisMiddleValue: number = this.state.xScale.invert(this.width / 2); + let yAxisMiddleValue: number = this.state.yScale.invert(this.height / 2); + const centers = { + x: xAxisMiddleValue, + y: yAxisMiddleValue } + this.coreOptions.callbackZoomOut(centers); } } @@ -557,4 +485,4 @@ export const VueChartwerkLinePod = { } }; -export { LineTimeSerie, LineOptions, Mode, TickOrientation, TimeFormat }; +export { LineTimeSerie, LineOptions, Mode, TimeFormat }; diff --git a/src/models/line-series.ts b/src/models/line-series.ts new file mode 100644 index 0000000..278498e --- /dev/null +++ b/src/models/line-series.ts @@ -0,0 +1,26 @@ +import { CoreSeries } from '@chartwerk/core'; +import { LineTimeSerie, Mode } from 'types'; + + +const LINE_SERIE_DEFAULTS = { + mode: Mode.STANDARD, + maxLength: undefined, + renderDots: false, + renderLines: true, + useOutOfRange: true, //? + dashArray: '0', + class: '', + renderArea: false, +}; + +export class LineSeries extends CoreSeries { + _lineDefaults = LINE_SERIE_DEFAULTS; + + constructor(series: LineTimeSerie[]) { + super(series); + } + + protected get defaults(): LineTimeSerie { + return { ...this._coreDefaults, ...this._lineDefaults }; + } +} diff --git a/src/types.ts b/src/types.ts index 1575cfe..44e44fe 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,4 +1,4 @@ -import { TimeSerie, Options } from '@chartwerk/core'; +import { Serie, Options } from '@chartwerk/core'; type LineTimeSerieParams = { confidence: number, @@ -15,5 +15,5 @@ export enum Mode { STANDARD = 'Standard', CHARGE = 'Charge' } -export type LineTimeSerie = TimeSerie & Partial; +export type LineTimeSerie = Serie & Partial; export type LineOptions = Options; diff --git a/yarn.lock b/yarn.lock index 167871d..edf1642 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6,12 +6,12 @@ __metadata: cacheKey: 8 "@chartwerk/core@npm:latest": - version: 0.5.4 - resolution: "@chartwerk/core@npm:0.5.4" + version: 0.5.8 + resolution: "@chartwerk/core@npm:0.5.8" dependencies: d3: ^5.7.2 lodash: ^4.14.149 - checksum: 0d686409377d6880f0012db3d9c1e1910cf3660885ac13196dabe93f57eca53984cf52dcf781b2876373fe9efc7b9d0209117cbcd811c50892b1de4f38a4a37f + checksum: 24b403af2703cc99ef3dee4793318ed71f4b02ed50bee6940774ccb392b8fbb53bb5353e91903b2c1ec815a8d2b830f2a2a2925d8e4380c64895ee2002b2baed languageName: node linkType: hard