|
|
@ -1,5 +1,7 @@ |
|
|
|
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 { LineTimeSerie, LineOptions } from './types'; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import { LineSeries } from './models/line_series'; |
|
|
|
|
|
|
|
|
|
|
|
import * as d3 from 'd3'; |
|
|
|
import * as d3 from 'd3'; |
|
|
|
import * as _ from 'lodash'; |
|
|
|
import * as _ from 'lodash'; |
|
|
@ -15,6 +17,7 @@ export class LinePod extends ChartwerkPod<LineTimeSerie, LineOptions> { |
|
|
|
|
|
|
|
|
|
|
|
constructor(_el: HTMLElement, _series: LineTimeSerie[] = [], _options: LineOptions = {}) { |
|
|
|
constructor(_el: HTMLElement, _series: LineTimeSerie[] = [], _options: LineOptions = {}) { |
|
|
|
super(_el, _series, _options); |
|
|
|
super(_el, _series, _options); |
|
|
|
|
|
|
|
this.series = new LineSeries(_series); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
renderMetrics(): void { |
|
|
|
renderMetrics(): void { |
|
|
@ -24,35 +27,13 @@ export class LinePod extends ChartwerkPod<LineTimeSerie, LineOptions> { |
|
|
|
this.initLineGenerator(); |
|
|
|
this.initLineGenerator(); |
|
|
|
this.initAreaGenerator(); |
|
|
|
this.initAreaGenerator(); |
|
|
|
|
|
|
|
|
|
|
|
// TODO: seems that renderMetrics is not correct name
|
|
|
|
if(!this.series.isSeriesAvailable) { |
|
|
|
if(this.series.length === 0) { |
|
|
|
|
|
|
|
this.renderNoDataPointsMessage(); |
|
|
|
this.renderNoDataPointsMessage(); |
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
for(let idx = 0; idx < this.series.length; ++idx) { |
|
|
|
for(const serie of this.series.visibleSeries) { |
|
|
|
if(this.series[idx].visible === false) { |
|
|
|
this._renderMetric(serie); |
|
|
|
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, |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -63,141 +44,62 @@ export class LinePod extends ChartwerkPod<LineTimeSerie, LineOptions> { |
|
|
|
|
|
|
|
|
|
|
|
initLineGenerator(): void { |
|
|
|
initLineGenerator(): void { |
|
|
|
this.lineGenerator = d3.line() |
|
|
|
this.lineGenerator = d3.line() |
|
|
|
.x(d => this.xScale(d[0])) |
|
|
|
.x(d => this.state.xScale(d[0])) |
|
|
|
.y(d => this.yScale(d[1])); |
|
|
|
.y(d => this.state.yScale(d[1])); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
initAreaGenerator(): void { |
|
|
|
initAreaGenerator(): void { |
|
|
|
this.areaGenerator = d3.area() |
|
|
|
this.areaGenerator = d3.area() |
|
|
|
.x(d => this.xScale(d[0])) |
|
|
|
.x(d => this.state.xScale(d[0])) |
|
|
|
.y1(d => this.yScale(d[1])) |
|
|
|
.y1(d => this.state.yScale(d[1])) |
|
|
|
.y0(d => this.height); |
|
|
|
.y0(d => this.height); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
getRenderGenerator(serieIdx: number): any { |
|
|
|
getRenderGenerator(renderArea: boolean): any { |
|
|
|
const renderArea = this.series[serieIdx].renderArea; |
|
|
|
|
|
|
|
if(renderArea) { |
|
|
|
if(renderArea) { |
|
|
|
return this.areaGenerator; |
|
|
|
return this.areaGenerator; |
|
|
|
} |
|
|
|
} |
|
|
|
return this.lineGenerator; |
|
|
|
return this.lineGenerator; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public appendData(data: [number, number][], shouldRerender = true): void { |
|
|
|
_renderDots(serie: LineTimeSerie): 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(let idx = 0; idx < this.series.length; ++idx) { |
|
|
|
|
|
|
|
this.metricContainer.select(`.metric-path-${idx}`) |
|
|
|
|
|
|
|
.datum(this.series[idx].datapoints) |
|
|
|
|
|
|
|
.attr('d', this.getRenderGenerator(idx)); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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])); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this._renderDots([data[idx]], idx); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if(shouldRerender) { |
|
|
|
|
|
|
|
const rightBorder = _.last(data)[0]; |
|
|
|
|
|
|
|
this.state.xValueRange = [this.state.getMinValueX(), rightBorder]; |
|
|
|
|
|
|
|
this.renderXAxis(); |
|
|
|
|
|
|
|
this.renderYAxis(); |
|
|
|
|
|
|
|
this.renderGrid(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_renderDots(datapoints: number[][], serieIdx: number): void { |
|
|
|
|
|
|
|
const customClass = this.series[serieIdx].class || ''; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.metricContainer.selectAll(null) |
|
|
|
this.metricContainer.selectAll(null) |
|
|
|
.data(datapoints) |
|
|
|
.data(serie.datapoints) |
|
|
|
.enter() |
|
|
|
.enter() |
|
|
|
.append('circle') |
|
|
|
.append('circle') |
|
|
|
.attr('class', `metric-circle-${serieIdx} metric-el ${customClass}`) |
|
|
|
.attr('class', `metric-circle-${serie.idx} metric-el ${serie.class}`) |
|
|
|
.attr('fill', this.getSerieColor(serieIdx)) |
|
|
|
.attr('fill', serie.color) |
|
|
|
.attr('r', METRIC_CIRCLE_RADIUS) |
|
|
|
.attr('r', METRIC_CIRCLE_RADIUS) |
|
|
|
.style('pointer-events', 'none') |
|
|
|
.style('pointer-events', 'none') |
|
|
|
.attr('cx', d => this.xScale(d[0])) |
|
|
|
.attr('cx', d => this.state.xScale(d[0])) |
|
|
|
.attr('cy', d => this.yScale(d[1])); |
|
|
|
.attr('cy', d => this.state.yScale(d[1])); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
_renderLines(datapoints: number[][], serieIdx: number): void { |
|
|
|
_renderLines(serie: LineTimeSerie): void { |
|
|
|
const dashArray = this.series[serieIdx].dashArray !== undefined ? this.series[serieIdx].dashArray : '0'; |
|
|
|
const fillColor = serie.renderArea ? serie.color : 'none'; |
|
|
|
const customClass = this.series[serieIdx].class || ''; |
|
|
|
const fillOpacity = serie.renderArea ? 0.5 : 'none'; |
|
|
|
const fillColor = this.series[serieIdx].renderArea ? this.getSerieColor(serieIdx) : 'none'; |
|
|
|
|
|
|
|
const fillOpacity = this.series[serieIdx].renderArea ? 0.5 : 'none'; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.metricContainer |
|
|
|
this.metricContainer |
|
|
|
.append('path') |
|
|
|
.append('path') |
|
|
|
.datum(datapoints) |
|
|
|
.datum(serie.datapoints) |
|
|
|
.attr('class', `metric-path-${serieIdx} metric-el ${customClass}`) |
|
|
|
.attr('class', `metric-path-${serie.idx} metric-el ${serie.class}`) |
|
|
|
.attr('fill', fillColor) |
|
|
|
.attr('fill', fillColor) |
|
|
|
.attr('fill-opacity', fillOpacity) |
|
|
|
.attr('fill-opacity', fillOpacity) |
|
|
|
.attr('stroke', this.getSerieColor(serieIdx)) |
|
|
|
.attr('stroke', serie.color) |
|
|
|
.attr('stroke-width', 1) |
|
|
|
.attr('stroke-width', 1) |
|
|
|
.attr('stroke-opacity', 0.7) |
|
|
|
.attr('stroke-opacity', 0.7) |
|
|
|
.attr('pointer-events', 'none') |
|
|
|
.attr('pointer-events', 'none') |
|
|
|
.style('stroke-dasharray', dashArray) |
|
|
|
.style('stroke-dasharray', serie.dashArray) |
|
|
|
.attr('d', this.getRenderGenerator(serieIdx)); |
|
|
|
.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) { |
|
|
|
_renderMetric(serie: LineTimeSerie): void { |
|
|
|
const dataPairs = d3.pairs(datapoints); |
|
|
|
if(serie.renderLines === true) { |
|
|
|
this.metricContainer.selectAll(null) |
|
|
|
this._renderLines(serie); |
|
|
|
.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('stroke-opacity', 0.7) |
|
|
|
|
|
|
|
.style('stroke-width', 1) |
|
|
|
|
|
|
|
.style('stroke', d => { |
|
|
|
|
|
|
|
if(d[1][0] > d[0][0]) { |
|
|
|
|
|
|
|
return 'green'; |
|
|
|
|
|
|
|
} else if (d[1][0] < d[0][0]) { |
|
|
|
|
|
|
|
return 'red'; |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
return 'gray'; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if(metricOptions.renderLines === true) { |
|
|
|
|
|
|
|
this._renderLines(datapoints, metricOptions.serieIdx); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if(metricOptions.renderDots === true) { |
|
|
|
if(serie.renderDots === true) { |
|
|
|
this._renderDots(datapoints, metricOptions.serieIdx); |
|
|
|
this._renderDots(serie); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -209,7 +111,7 @@ export class LinePod extends ChartwerkPod<LineTimeSerie, LineOptions> { |
|
|
|
|
|
|
|
|
|
|
|
appendCrosshairCircles(): void { |
|
|
|
appendCrosshairCircles(): void { |
|
|
|
// circle for each serie
|
|
|
|
// circle for each serie
|
|
|
|
this.series.forEach((serie: LineTimeSerie, serieIdx: number) => { |
|
|
|
this.series.visibleSeries.forEach((serie: LineTimeSerie, serieIdx: number) => { |
|
|
|
this.appendCrosshairCircle(serieIdx); |
|
|
|
this.appendCrosshairCircle(serieIdx); |
|
|
|
}); |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
@ -222,7 +124,7 @@ export class LinePod extends ChartwerkPod<LineTimeSerie, LineOptions> { |
|
|
|
.attr('cx', -CROSSHAIR_BACKGROUND_RAIDUS) |
|
|
|
.attr('cx', -CROSSHAIR_BACKGROUND_RAIDUS) |
|
|
|
.attr('cy', -CROSSHAIR_BACKGROUND_RAIDUS) |
|
|
|
.attr('cy', -CROSSHAIR_BACKGROUND_RAIDUS) |
|
|
|
.attr('clip-path', `url(#${this.rectClipId})`) |
|
|
|
.attr('clip-path', `url(#${this.rectClipId})`) |
|
|
|
.attr('fill', this.getSerieColor(serieIdx)) |
|
|
|
.attr('fill', this.series.visibleSeries[serieIdx].color) |
|
|
|
.style('opacity', CROSSHAIR_BACKGROUND_OPACITY) |
|
|
|
.style('opacity', CROSSHAIR_BACKGROUND_OPACITY) |
|
|
|
.style('pointer-events', 'none'); |
|
|
|
.style('pointer-events', 'none'); |
|
|
|
|
|
|
|
|
|
|
@ -232,23 +134,19 @@ export class LinePod extends ChartwerkPod<LineTimeSerie, LineOptions> { |
|
|
|
.attr('cy', -CROSSHAIR_CIRCLE_RADIUS) |
|
|
|
.attr('cy', -CROSSHAIR_CIRCLE_RADIUS) |
|
|
|
.attr('class', `crosshair-circle-${serieIdx}`) |
|
|
|
.attr('class', `crosshair-circle-${serieIdx}`) |
|
|
|
.attr('clip-path', `url(#${this.rectClipId})`) |
|
|
|
.attr('clip-path', `url(#${this.rectClipId})`) |
|
|
|
.attr('fill', this.getSerieColor(serieIdx)) |
|
|
|
.attr('fill', this.series.visibleSeries[serieIdx].color) |
|
|
|
.attr('r', CROSSHAIR_CIRCLE_RADIUS) |
|
|
|
.attr('r', CROSSHAIR_CIRCLE_RADIUS) |
|
|
|
.style('pointer-events', 'none'); |
|
|
|
.style('pointer-events', 'none'); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public renderSharedCrosshair(values: { x?: number, y?: number }): void { |
|
|
|
public renderSharedCrosshair(values: { x?: number, y?: number }): void { |
|
|
|
this.onMouseOver(); // TODO: refactor to use it once
|
|
|
|
this.onMouseOver(); // TODO: refactor to use it once
|
|
|
|
const eventX = this.xScale(values.x); |
|
|
|
const eventX = this.state.xScale(values.x); |
|
|
|
const eventY = this.yScale(values.y); |
|
|
|
const eventY = this.state.yScale(values.y); |
|
|
|
this.moveCrosshairLine(eventX, eventY); |
|
|
|
this.moveCrosshairLine(eventX, eventY); |
|
|
|
const datapoints = this.findAndHighlightDatapoints(values.x, values.y); |
|
|
|
const datapoints = this.findAndHighlightDatapoints(values.x, values.y); |
|
|
|
|
|
|
|
|
|
|
|
if(this.options.eventsCallbacks === undefined || this.options.eventsCallbacks.sharedCrosshairMove === undefined) { |
|
|
|
this.options.callbackSharedCrosshairMove({ |
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.options.eventsCallbacks.sharedCrosshairMove({ |
|
|
|
|
|
|
|
datapoints: datapoints, |
|
|
|
datapoints: datapoints, |
|
|
|
eventX, eventY |
|
|
|
eventX, eventY |
|
|
|
}); |
|
|
|
}); |
|
|
@ -350,7 +248,7 @@ export class LinePod extends ChartwerkPod<LineTimeSerie, LineOptions> { |
|
|
|
// columnIdx: 1 for y, 0 for x
|
|
|
|
// columnIdx: 1 for y, 0 for x
|
|
|
|
// inverval: x/y value interval between data points
|
|
|
|
// inverval: x/y value interval between data points
|
|
|
|
// TODO: move it to base/state instead of timeInterval
|
|
|
|
// TODO: move it to base/state instead of timeInterval
|
|
|
|
const intervals = _.map(this.series, serie => { |
|
|
|
const intervals = _.map(this.series.visibleSeries, serie => { |
|
|
|
if(serie.datapoints.length < 2) { |
|
|
|
if(serie.datapoints.length < 2) { |
|
|
|
return undefined; |
|
|
|
return undefined; |
|
|
|
} |
|
|
|
} |
|
|
@ -366,23 +264,15 @@ export class LinePod extends ChartwerkPod<LineTimeSerie, LineOptions> { |
|
|
|
onMouseMove(): void { |
|
|
|
onMouseMove(): void { |
|
|
|
const eventX = d3.mouse(this.chartContainer.node())[0]; |
|
|
|
const eventX = d3.mouse(this.chartContainer.node())[0]; |
|
|
|
const eventY = d3.mouse(this.chartContainer.node())[1]; |
|
|
|
const eventY = d3.mouse(this.chartContainer.node())[1]; |
|
|
|
const xValue = this.xScale.invert(eventX); // mouse x position in xScale
|
|
|
|
const xValue = this.state.xScale.invert(eventX); // mouse x position in xScale
|
|
|
|
const yValue = this.yScale.invert(eventY); |
|
|
|
const yValue = this.state.yScale.invert(eventY); |
|
|
|
// TODO: isOutOfChart is a hack, use clip path correctly
|
|
|
|
|
|
|
|
if(this.isOutOfChart() === true) { |
|
|
|
|
|
|
|
this.crosshair.style('display', 'none'); |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
this.moveCrosshairLine(eventX, eventY); |
|
|
|
this.moveCrosshairLine(eventX, eventY); |
|
|
|
|
|
|
|
|
|
|
|
const datapoints = this.findAndHighlightDatapoints(xValue, yValue); |
|
|
|
const datapoints = this.findAndHighlightDatapoints(xValue, yValue); |
|
|
|
|
|
|
|
|
|
|
|
if(this.options.eventsCallbacks === undefined || this.options.eventsCallbacks.mouseMove === undefined) { |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// TDOO: is shift key pressed
|
|
|
|
// TDOO: is shift key pressed
|
|
|
|
// TODO: need to refactor this object
|
|
|
|
// TODO: need to refactor this object
|
|
|
|
this.options.eventsCallbacks.mouseMove({ |
|
|
|
this.options.callbackMouseMove({ |
|
|
|
x: d3.event.pageX, |
|
|
|
x: d3.event.pageX, |
|
|
|
y: d3.event.pageY, |
|
|
|
y: d3.event.pageY, |
|
|
|
xVal: xValue, |
|
|
|
xVal: xValue, |
|
|
@ -394,67 +284,30 @@ export class LinePod extends ChartwerkPod<LineTimeSerie, LineOptions> { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
findAndHighlightDatapoints(xValue: number, yValue: number): { value: [number, number], color: string, label: string }[] { |
|
|
|
findAndHighlightDatapoints(xValue: number, yValue: number): { value: [number, number], color: string, label: string }[] { |
|
|
|
if(this.series === undefined || this.series.length === 0) { |
|
|
|
if(!this.series.isSeriesAvailable) { |
|
|
|
return []; |
|
|
|
return []; |
|
|
|
} |
|
|
|
} |
|
|
|
let points = []; // datapoints in each metric that is closest to xValue/yValue position
|
|
|
|
let points = []; // datapoints in each metric that is closest to xValue/yValue position
|
|
|
|
this.series.forEach((serie: LineTimeSerie, serieIdx: number) => { |
|
|
|
this.series.visibleSeries.forEach((serie: LineTimeSerie) => { |
|
|
|
if( |
|
|
|
|
|
|
|
serie.visible === false || |
|
|
|
|
|
|
|
_.includes(this.seriesTargetsWithBounds, serie.target) |
|
|
|
|
|
|
|
) { |
|
|
|
|
|
|
|
this.hideCrosshairCircle(serieIdx); |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
const closestDatapoint = this.getClosestDatapoint(serie, xValue, yValue); |
|
|
|
const closestDatapoint = this.getClosestDatapoint(serie, xValue, yValue); |
|
|
|
if(closestDatapoint === undefined || this.isOutOfRange(closestDatapoint, xValue, yValue, serie.useOutOfRange)) { |
|
|
|
if(closestDatapoint === undefined) { |
|
|
|
this.hideCrosshairCircle(serieIdx); |
|
|
|
this.hideCrosshairCircle(serie.idx); |
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const xPosition = this.xScale(closestDatapoint[0]); |
|
|
|
const xPosition = this.state.xScale(closestDatapoint[0]); |
|
|
|
const yPosition = this.yScale(closestDatapoint[1]); |
|
|
|
const yPosition = this.state.yScale(closestDatapoint[1]); |
|
|
|
this.moveCrosshairCircle(xPosition, yPosition, serieIdx); |
|
|
|
this.moveCrosshairCircle(xPosition, yPosition, serie.idx); |
|
|
|
|
|
|
|
|
|
|
|
points.push({ |
|
|
|
points.push({ |
|
|
|
value: closestDatapoint, |
|
|
|
value: closestDatapoint, |
|
|
|
color: this.getSerieColor(serieIdx), |
|
|
|
color: serie.color, |
|
|
|
label: serie.alias || serie.target |
|
|
|
label: serie.alias || serie.target |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
return points; |
|
|
|
return points; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
isOutOfRange(closestDatapoint: [number, number], xValue: number, yValue: number, useOutOfRange = true): boolean { |
|
|
|
|
|
|
|
// find is mouse position more than xRange/yRange from closest point
|
|
|
|
|
|
|
|
// TODO: refactor getValueInterval to remove this!
|
|
|
|
|
|
|
|
if(useOutOfRange === false) { |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
let columnIdx; // 1 for y value, 0 for x value
|
|
|
|
|
|
|
|
let value; // xValue ot y Value
|
|
|
|
|
|
|
|
switch(this.options.crosshair.orientation) { |
|
|
|
|
|
|
|
case CrosshairOrientation.VERTICAL: |
|
|
|
|
|
|
|
columnIdx = 0; |
|
|
|
|
|
|
|
value = xValue; |
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
case CrosshairOrientation.HORIZONTAL: |
|
|
|
|
|
|
|
columnIdx = 1; |
|
|
|
|
|
|
|
value = yValue; |
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
case CrosshairOrientation.BOTH: |
|
|
|
|
|
|
|
// TODO: maybe use voronoi
|
|
|
|
|
|
|
|
columnIdx = 1; |
|
|
|
|
|
|
|
value = yValue; |
|
|
|
|
|
|
|
default: |
|
|
|
|
|
|
|
throw new Error(`Unknown type of crosshair orientaion: ${this.options.crosshair.orientation}`); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
const range = Math.abs(closestDatapoint[columnIdx] - value); |
|
|
|
|
|
|
|
const interval = this.getValueInterval(columnIdx); // interval between points
|
|
|
|
|
|
|
|
// do not move crosshair circles, it mouse to far from closest point
|
|
|
|
|
|
|
|
return interval === undefined || range > interval / 2; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
onMouseOver(): void { |
|
|
|
onMouseOver(): void { |
|
|
|
this.crosshair.style('display', null); |
|
|
|
this.crosshair.style('display', null); |
|
|
|
this.crosshair.selectAll('.crosshair-circle') |
|
|
|
this.crosshair.selectAll('.crosshair-circle') |
|
|
@ -462,30 +315,21 @@ export class LinePod extends ChartwerkPod<LineTimeSerie, LineOptions> { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
onMouseOut(): void { |
|
|
|
onMouseOut(): void { |
|
|
|
if(this.options.eventsCallbacks !== undefined && this.options.eventsCallbacks.mouseOut !== undefined) { |
|
|
|
this.options.callbackMouseOut(); |
|
|
|
this.options.eventsCallbacks.mouseOut(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
this.crosshair.style('display', 'none'); |
|
|
|
this.crosshair.style('display', 'none'); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
isDoubleClickActive(): boolean { |
|
|
|
isDoubleClickActive(): boolean { |
|
|
|
if(this.options.zoomEvents.mouse.doubleClick === undefined) { |
|
|
|
return this.options.doubleClickEvent.isActive; |
|
|
|
return false; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return this.options.zoomEvents.mouse.doubleClick.isActive; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// methods below rewrite cores, (move more methods here)
|
|
|
|
// methods below rewrite s, (move more methods here)
|
|
|
|
protected zoomOut(): void { |
|
|
|
protected zoomOut(): void { |
|
|
|
// TODO: test to remove, seems its depricated
|
|
|
|
|
|
|
|
if(this.isOutOfChart() === true) { |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if(d3.event.type === 'dblclick' && !this.isDoubleClickActive()) { |
|
|
|
if(d3.event.type === 'dblclick' && !this.isDoubleClickActive()) { |
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
// TODO: its not clear, why we use this orientation here. Mb its better to use separate option
|
|
|
|
// 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.options.mouseZoomEvent.orientation; |
|
|
|
const xInterval = this.state.xValueRange[1] - this.state.xValueRange[0]; |
|
|
|
const xInterval = this.state.xValueRange[1] - this.state.xValueRange[0]; |
|
|
|
const yInterval = this.state.yValueRange[1] - this.state.yValueRange[0]; |
|
|
|
const yInterval = this.state.yValueRange[1] - this.state.yValueRange[0]; |
|
|
|
switch(orientation) { |
|
|
|
switch(orientation) { |
|
|
@ -514,15 +358,13 @@ export class LinePod extends ChartwerkPod<LineTimeSerie, LineOptions> { |
|
|
|
this.renderGrid(); |
|
|
|
this.renderGrid(); |
|
|
|
this.onMouseOver(); |
|
|
|
this.onMouseOver(); |
|
|
|
|
|
|
|
|
|
|
|
if(this.options.eventsCallbacks !== undefined && this.options.eventsCallbacks.zoomOut !== undefined) { |
|
|
|
let xAxisMiddleValue: number = this.state.xScale.invert(this.width / 2); |
|
|
|
let xAxisMiddleValue: number = this.xScale.invert(this.width / 2); |
|
|
|
let yAxisMiddleValue: number = this.state.yScale.invert(this.height / 2); |
|
|
|
let yAxisMiddleValue: number = this.yScale.invert(this.height / 2); |
|
|
|
|
|
|
|
const centers = { |
|
|
|
const centers = { |
|
|
|
x: xAxisMiddleValue, |
|
|
|
x: xAxisMiddleValue, |
|
|
|
y: yAxisMiddleValue |
|
|
|
y: yAxisMiddleValue |
|
|
|
} |
|
|
|
} |
|
|
|
this.options.eventsCallbacks.zoomOut(centers); |
|
|
|
this.options.callbackZoomOut(centers); |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -557,4 +399,4 @@ export const VueChartwerkLinePod = { |
|
|
|
} |
|
|
|
} |
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
export { LineTimeSerie, LineOptions, Mode, TickOrientation, TimeFormat }; |
|
|
|
export { LineTimeSerie, LineOptions, TimeFormat }; |
|
|
|