|
|
|
import { ChartwerkPod, VueChartwerkPodMixin, TickOrientation, TimeFormat, CrosshairOrientation } from '@chartwerk/core';
|
|
|
|
import { LineTimeSerie, LineOptions, Mode } from './types';
|
|
|
|
|
|
|
|
import * as d3 from 'd3';
|
|
|
|
import * as _ from 'lodash';
|
|
|
|
|
|
|
|
const METRIC_CIRCLE_RADIUS = 1.5;
|
|
|
|
const CROSSHAIR_CIRCLE_RADIUS = 3;
|
|
|
|
const CROSSHAIR_BACKGROUND_RAIDUS = 9;
|
|
|
|
const CROSSHAIR_BACKGROUND_OPACITY = 0.3;
|
|
|
|
|
|
|
|
export class LinePod extends ChartwerkPod<LineTimeSerie, LineOptions> {
|
|
|
|
lineGenerator = null;
|
|
|
|
metricContainer = null;
|
|
|
|
|
|
|
|
constructor(_el: HTMLElement, _series: LineTimeSerie[] = [], _options: LineOptions = {}) {
|
|
|
|
super(d3, _el, _series, _options);
|
|
|
|
}
|
|
|
|
|
|
|
|
renderMetrics(): void {
|
|
|
|
this.updateCrosshair();
|
|
|
|
this.initLineGenerator();
|
|
|
|
|
|
|
|
// TODO: seems that renderMetrics is not correct name
|
|
|
|
if(this.series.length === 0) {
|
|
|
|
this.renderNoDataPointsMessage();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: move to core, and create only one container
|
|
|
|
// container for clip path
|
|
|
|
const clipContatiner = this.chartContainer
|
|
|
|
.append('g')
|
|
|
|
.attr('clip-path', `url(#${this.rectClipId})`)
|
|
|
|
.attr('class', 'metrics-container');
|
|
|
|
|
|
|
|
// container for panning
|
|
|
|
this.metricContainer = clipContatiner
|
|
|
|
.append('g')
|
|
|
|
.attr('class', ' metrics-rect')
|
|
|
|
|
|
|
|
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,
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
initLineGenerator(): void {
|
|
|
|
this.lineGenerator = this.d3.line()
|
|
|
|
.x(d => this.xScale(d[1]))
|
|
|
|
.y(d => this.yScale(d[0]));
|
|
|
|
}
|
|
|
|
|
|
|
|
public appendData(data: [number, number][]): void {
|
|
|
|
this.clearScaleCache();
|
|
|
|
|
|
|
|
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.lineGenerator);
|
|
|
|
|
|
|
|
if(this.series[idx].renderDots === true) {
|
|
|
|
this.metricContainer.selectAll(`.metric-circle-${idx}`)
|
|
|
|
.data(this.series[idx].datapoints)
|
|
|
|
.attr('cx', d => this.xScale(d[1]))
|
|
|
|
.attr('cy', d => this.yScale(d[0]));
|
|
|
|
|
|
|
|
this._renderDots([data[idx]], idx);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
this.renderXAxis();
|
|
|
|
this.renderYAxis();
|
|
|
|
this.renderGrid();
|
|
|
|
}
|
|
|
|
|
|
|
|
_renderDots(datapoints: number[][], serieIdx: number): void {
|
|
|
|
this.metricContainer.selectAll(null)
|
|
|
|
.data(datapoints)
|
|
|
|
.enter()
|
|
|
|
.append('circle')
|
|
|
|
.attr('class', `metric-circle-${serieIdx} metric-el`)
|
|
|
|
.attr('fill', this.getSerieColor(serieIdx))
|
|
|
|
.attr('r', METRIC_CIRCLE_RADIUS)
|
|
|
|
.style('pointer-events', 'none')
|
|
|
|
.attr('cx', d => this.xScale(d[1]))
|
|
|
|
.attr('cy', d => this.yScale(d[0]));
|
|
|
|
}
|
|
|
|
|
|
|
|
_renderLines(datapoints: number[][], serieIdx: number): void {
|
|
|
|
this.metricContainer
|
|
|
|
.append('path')
|
|
|
|
.datum(datapoints)
|
|
|
|
.attr('class', `metric-path-${serieIdx} metric-el`)
|
|
|
|
.attr('fill', 'none')
|
|
|
|
.attr('stroke', this.getSerieColor(serieIdx))
|
|
|
|
.attr('stroke-width', 1)
|
|
|
|
.attr('stroke-opacity', 0.7)
|
|
|
|
.attr('pointer-events', 'none')
|
|
|
|
.attr('d', this.lineGenerator);
|
|
|
|
}
|
|
|
|
|
|
|
|
_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 = this.d3.pairs(datapoints);
|
|
|
|
this.metricContainer.selectAll(null)
|
|
|
|
.data(dataPairs)
|
|
|
|
.enter()
|
|
|
|
.append('line')
|
|
|
|
.attr('x1', d => this.xScale(d[0][1]))
|
|
|
|
.attr('x2', d => this.xScale(d[1][1]))
|
|
|
|
.attr('y1', d => this.yScale(d[0][0]))
|
|
|
|
.attr('y2', d => this.yScale(d[1][0]))
|
|
|
|
.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) {
|
|
|
|
this._renderDots(datapoints, metricOptions.serieIdx);
|
|
|
|
}
|
|
|
|
|
|
|
|
let upperBoundDatapoints = [];
|
|
|
|
let lowerBoundDatapoints = [];
|
|
|
|
if(
|
|
|
|
this.options.bounds !== undefined &&
|
|
|
|
this.options.bounds.upper !== undefined &&
|
|
|
|
this.options.bounds.lower !== undefined
|
|
|
|
) {
|
|
|
|
this.series.forEach(serie => {
|
|
|
|
if(serie.target === this.formatedBound(this.options.bounds.upper, metricOptions.target)) {
|
|
|
|
upperBoundDatapoints = serie.datapoints;
|
|
|
|
}
|
|
|
|
if(serie.target === this.formatedBound(this.options.bounds.lower, metricOptions.target)) {
|
|
|
|
lowerBoundDatapoints = serie.datapoints;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
if(upperBoundDatapoints.length > 0 && lowerBoundDatapoints.length > 0) {
|
|
|
|
const zip = (arr1, arr2) => arr1.map((k, i) => [k[0],k[1], arr2[i][0]]);
|
|
|
|
const data = zip(upperBoundDatapoints, lowerBoundDatapoints);
|
|
|
|
|
|
|
|
this.metricContainer.append('path')
|
|
|
|
.datum(data)
|
|
|
|
.attr('fill', metricOptions.color)
|
|
|
|
.attr('stroke', 'none')
|
|
|
|
.attr('opacity', '0.3')
|
|
|
|
.attr('d', this.d3.area()
|
|
|
|
.x((d: number[]) => this.xScale(d[1]))
|
|
|
|
.y0((d: number[]) => this.yScale(d[0]))
|
|
|
|
.y1((d: number[]) => this.yScale(d[2]))
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
if(metricOptions.confidence > 0) {
|
|
|
|
this.metricContainer.append('path')
|
|
|
|
.datum(datapoints)
|
|
|
|
.attr('fill', metricOptions.color)
|
|
|
|
.attr('stroke', 'none')
|
|
|
|
.attr('opacity', '0.3')
|
|
|
|
.attr('d', this.d3.area()
|
|
|
|
.x((d: [number, number]) => this.xScale(d[1]))
|
|
|
|
.y0((d: [number, number]) => this.yScale(d[0] + metricOptions.confidence))
|
|
|
|
.y1((d: [number, number]) => this.yScale(d[0] - metricOptions.confidence))
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
updateCrosshair(): void {
|
|
|
|
// Base don't know anything about crosshair circles, It is only for line pod
|
|
|
|
this.appendCrosshairCircles();
|
|
|
|
}
|
|
|
|
|
|
|
|
appendCrosshairCircles(): void {
|
|
|
|
// circle for each serie
|
|
|
|
this.series.forEach((serie: LineTimeSerie, serieIdx: number) => {
|
|
|
|
this.appendCrosshairCircle(serieIdx);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
appendCrosshairCircle(serieIdx: number): void {
|
|
|
|
this.crosshair.append('circle')
|
|
|
|
.attr('class', `crosshair-circle-${serieIdx} crosshair-background`)
|
|
|
|
.attr('r', CROSSHAIR_BACKGROUND_RAIDUS)
|
|
|
|
.attr('clip-path', `url(#${this.rectClipId})`)
|
|
|
|
.attr('fill', this.getSerieColor(serieIdx))
|
|
|
|
.style('opacity', CROSSHAIR_BACKGROUND_OPACITY)
|
|
|
|
.style('pointer-events', 'none');
|
|
|
|
|
|
|
|
this.crosshair
|
|
|
|
.append('circle')
|
|
|
|
.attr('class', `crosshair-circle-${serieIdx}`)
|
|
|
|
.attr('clip-path', `url(#${this.rectClipId})`)
|
|
|
|
.attr('fill', this.getSerieColor(serieIdx))
|
|
|
|
.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);
|
|
|
|
this.moveCrosshairLine(eventX, eventY);
|
|
|
|
const datapoints = this.findAndHighlightDatapoints(values.x, values.y);
|
|
|
|
|
|
|
|
if(this.options.eventsCallbacks === undefined || this.options.eventsCallbacks.sharedCrosshairMove === undefined) {
|
|
|
|
console.log('Shared crosshair move, but there is no callback');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.options.eventsCallbacks.sharedCrosshairMove({
|
|
|
|
datapoints: datapoints,
|
|
|
|
eventX, eventY
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
public hideSharedCrosshair(): void {
|
|
|
|
this.crosshair.style('display', 'none');
|
|
|
|
}
|
|
|
|
|
|
|
|
moveCrosshairLine(xPosition: number, yPosition: number): void {
|
|
|
|
switch(this.options.crosshair.orientation) {
|
|
|
|
case CrosshairOrientation.VERTICAL:
|
|
|
|
this.crosshair.select('#crosshair-line-x')
|
|
|
|
.attr('x1', xPosition)
|
|
|
|
.attr('x2', xPosition);
|
|
|
|
return;
|
|
|
|
case CrosshairOrientation.HORIZONTAL:
|
|
|
|
this.crosshair.select('#crosshair-line-y')
|
|
|
|
.attr('y1', yPosition)
|
|
|
|
.attr('y2', yPosition);
|
|
|
|
return;
|
|
|
|
case CrosshairOrientation.BOTH:
|
|
|
|
this.crosshair.select('#crosshair-line-x')
|
|
|
|
.attr('x1', xPosition)
|
|
|
|
.attr('x2', xPosition);
|
|
|
|
this.crosshair.select('#crosshair-line-y')
|
|
|
|
.attr('y1', yPosition)
|
|
|
|
.attr('y2', yPosition);
|
|
|
|
return;
|
|
|
|
default:
|
|
|
|
throw new Error(`Unknown type of crosshair orientaion: ${this.options.crosshair.orientation}`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
moveCrosshairCircle(xPosition: number, yPosition: number, serieIdx: number): void {
|
|
|
|
this.crosshair.selectAll(`.crosshair-circle-${serieIdx}`)
|
|
|
|
.attr('cx', xPosition)
|
|
|
|
.attr('cy', yPosition)
|
|
|
|
.style('display', null);
|
|
|
|
}
|
|
|
|
|
|
|
|
hideCrosshairCircle(serieIdx: number): void {
|
|
|
|
// hide circle for singe serie
|
|
|
|
this.crosshair.selectAll(`.crosshair-circle-${serieIdx}`)
|
|
|
|
.style('display', 'none');
|
|
|
|
}
|
|
|
|
|
|
|
|
getClosestDatapoint(serie: LineTimeSerie, xValue: number, yValue: number): [number, number] {
|
|
|
|
// get closest datapoint to the "xValue"/"yValue" in the "serie"
|
|
|
|
const datapoints = serie.datapoints;
|
|
|
|
const closestIdx = this.getClosestIndex(datapoints, xValue, yValue);
|
|
|
|
const datapoint = serie.datapoints[closestIdx];
|
|
|
|
return datapoint;
|
|
|
|
}
|
|
|
|
|
|
|
|
getClosestIndex(datapoints: [number, number][], xValue: number, yValue: number): number {
|
|
|
|
let columnIdx; // 0 for y value, 1 for x value
|
|
|
|
let value; // xValue ot y Value
|
|
|
|
switch(this.options.crosshair.orientation) {
|
|
|
|
case CrosshairOrientation.HORIZONTAL:
|
|
|
|
columnIdx = 0;
|
|
|
|
value = yValue;
|
|
|
|
break;
|
|
|
|
case CrosshairOrientation.VERTICAL:
|
|
|
|
columnIdx = 1;
|
|
|
|
value = xValue;
|
|
|
|
break;
|
|
|
|
case CrosshairOrientation.BOTH:
|
|
|
|
// TODO: maybe use voronoi
|
|
|
|
columnIdx = 0;
|
|
|
|
value = yValue;
|
|
|
|
default:
|
|
|
|
throw new Error(`Unknown type of crosshair orientaion: ${this.options.crosshair.orientation}`);
|
|
|
|
}
|
|
|
|
// TODO: d3.bisect is not the best way. Use binary search
|
|
|
|
const bisectIndex = this.d3.bisector((d: [number, number]) => d[columnIdx]).left;
|
|
|
|
let closestIdx = bisectIndex(datapoints, value);
|
|
|
|
// TODO: refactor corner cases
|
|
|
|
if(closestIdx < 0) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if(closestIdx >= datapoints.length) {
|
|
|
|
return datapoints.length - 1;
|
|
|
|
}
|
|
|
|
// TODO: do we realy need it? Binary search should fix it
|
|
|
|
if(
|
|
|
|
closestIdx > 0 &&
|
|
|
|
Math.abs(value - datapoints[closestIdx - 1][columnIdx]) <
|
|
|
|
Math.abs(value - datapoints[closestIdx][columnIdx])
|
|
|
|
) {
|
|
|
|
closestIdx -= 1;
|
|
|
|
}
|
|
|
|
return closestIdx;
|
|
|
|
}
|
|
|
|
|
|
|
|
getValueInterval(columnIdx: number): number | undefined {
|
|
|
|
// columnIdx: 0 for y, 1 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 => {
|
|
|
|
if(serie.datapoints.length < 2) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
const start = _.head(serie.datapoints)[columnIdx];
|
|
|
|
const end = _.last(serie.datapoints)[columnIdx];
|
|
|
|
const range = Math.abs(end - start);
|
|
|
|
const interval = range / (serie.datapoints.length - 1);
|
|
|
|
return interval;
|
|
|
|
});
|
|
|
|
return _.max(intervals);
|
|
|
|
}
|
|
|
|
|
|
|
|
onMouseMove(): void {
|
|
|
|
const eventX = this.d3.mouse(this.chartContainer.node())[0];
|
|
|
|
const eventY = this.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;
|
|
|
|
}
|
|
|
|
this.moveCrosshairLine(eventX, eventY);
|
|
|
|
|
|
|
|
const datapoints = this.findAndHighlightDatapoints(xValue, yValue);
|
|
|
|
|
|
|
|
if(this.options.eventsCallbacks === undefined || this.options.eventsCallbacks.mouseMove === undefined) {
|
|
|
|
console.log('Mouse move, but there is no callback');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// TDOO: is shift key pressed
|
|
|
|
// TODO: need to refactor this object
|
|
|
|
this.options.eventsCallbacks.mouseMove({
|
|
|
|
x: this.d3.event.pageX,
|
|
|
|
y: this.d3.event.pageY,
|
|
|
|
xVal: xValue,
|
|
|
|
yVal: yValue,
|
|
|
|
series: datapoints,
|
|
|
|
chartX: eventX,
|
|
|
|
chartWidth: this.width
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
findAndHighlightDatapoints(xValue: number, yValue: number): { value: [number, number], color: string, label: string }[] {
|
|
|
|
if(this.series === undefined || this.series.length === 0) {
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
const closestDatapoint = this.getClosestDatapoint(serie, xValue, yValue);
|
|
|
|
if(closestDatapoint === undefined || this.isOutOfRange(closestDatapoint, xValue, yValue, serie.useOutOfRange)) {
|
|
|
|
this.hideCrosshairCircle(serieIdx);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const yPosition = this.yScale(closestDatapoint[0]);
|
|
|
|
const xPosition = this.xScale(closestDatapoint[1]);
|
|
|
|
this.moveCrosshairCircle(xPosition, yPosition, serieIdx);
|
|
|
|
|
|
|
|
points.push({
|
|
|
|
value: closestDatapoint,
|
|
|
|
color: this.getSerieColor(serieIdx),
|
|
|
|
label: serie.alias || serie.target
|
|
|
|
});
|
|
|
|
});
|
|
|
|
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; // 0 for y value, 1 for x value
|
|
|
|
let value; // xValue ot y Value
|
|
|
|
switch(this.options.crosshair.orientation) {
|
|
|
|
case CrosshairOrientation.HORIZONTAL:
|
|
|
|
columnIdx = 0;
|
|
|
|
value = yValue;
|
|
|
|
break;
|
|
|
|
case CrosshairOrientation.VERTICAL:
|
|
|
|
columnIdx = 1;
|
|
|
|
value = xValue;
|
|
|
|
break;
|
|
|
|
case CrosshairOrientation.BOTH:
|
|
|
|
// TODO: maybe use voronoi
|
|
|
|
columnIdx = 0;
|
|
|
|
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 {
|
|
|
|
this.crosshair.style('display', null);
|
|
|
|
this.crosshair.selectAll('.crosshair-circle')
|
|
|
|
.style('display', null);
|
|
|
|
}
|
|
|
|
|
|
|
|
onMouseOut(): void {
|
|
|
|
if(this.options.eventsCallbacks !== undefined && this.options.eventsCallbacks.mouseOut !== undefined) {
|
|
|
|
this.options.eventsCallbacks.mouseOut();
|
|
|
|
}
|
|
|
|
this.crosshair.style('display', 'none');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// it is used with Vue.component, e.g.: Vue.component('chartwerk-line-pod', VueChartwerkLineChartObject)
|
|
|
|
export const VueChartwerkLineChartObject = {
|
|
|
|
// alternative to `template: '<div class="chartwerk-line-pod" :id="id" />'`
|
|
|
|
render(createElement) {
|
|
|
|
return createElement(
|
|
|
|
'div',
|
|
|
|
{
|
|
|
|
class: { 'chartwerk-line-chart': true },
|
|
|
|
attrs: { id: this.id }
|
|
|
|
}
|
|
|
|
);
|
|
|
|
},
|
|
|
|
mixins: [VueChartwerkPodMixin],
|
|
|
|
methods: {
|
|
|
|
render() {
|
|
|
|
if(this.pod === undefined) {
|
|
|
|
this.pod = new LinePod(document.getElementById(this.id), this.series, this.options);
|
|
|
|
this.pod.render();
|
|
|
|
} else {
|
|
|
|
this.pod.updateData(this.series, this.options);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
renderSharedCrosshair(values) {
|
|
|
|
this.pod.renderSharedCrosshair(values);
|
|
|
|
},
|
|
|
|
hideSharedCrosshair() {
|
|
|
|
this.pod.hideSharedCrosshair();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
export { LineTimeSerie, LineOptions, Mode, TickOrientation, TimeFormat };
|