|
|
@ -1,65 +1,74 @@ |
|
|
|
import { ChartwerkPod, VueChartwerkPodMixin, TimeFormat, yAxisOrientation, CrosshairOrientation } from '@chartwerk/core'; |
|
|
|
import { ChartwerkPod, VueChartwerkPodMixin, TickOrientation, TimeFormat, yAxisOrientation, CrosshairOrientation, PanOrientation } from '@chartwerk/core'; |
|
|
|
import { ScatterData, ScatterOptions, PointType, LineType, HighlightedData, MouseMoveEvent, DelaunayDataRow } from './types'; |
|
|
|
import { ScatterData, ScatterOptions, PointType, LineType, ColorFormatter } from './types'; |
|
|
|
|
|
|
|
|
|
|
|
import { DelaunayDiagram } from './models/delaunay'; |
|
|
|
import { DelaunayDiagram } from './delaunay'; |
|
|
|
import { ScatterSeries } from './models/scatter_series'; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import * as d3 from 'd3'; |
|
|
|
import * as d3 from 'd3'; |
|
|
|
import * as _ from 'lodash'; |
|
|
|
import * as _ from 'lodash'; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// TODO: use pod state with defaults
|
|
|
|
|
|
|
|
const DEFAULT_POINT_SIZE = 4; |
|
|
|
const POINT_HIGHLIGHT_DIAMETER = 4; |
|
|
|
const POINT_HIGHLIGHT_DIAMETER = 4; |
|
|
|
const CROSSHAIR_BACKGROUND_OPACITY = 0.3; |
|
|
|
const CROSSHAIR_BACKGROUND_OPACITY = 0.3; |
|
|
|
|
|
|
|
const DEFAULT_POINT_TYPE = PointType.CIRCLE; |
|
|
|
|
|
|
|
const DEFAULT_LINE_TYPE = LineType.NONE; |
|
|
|
const DEFAULT_LINE_DASHED_AMOUNT = 4; |
|
|
|
const DEFAULT_LINE_DASHED_AMOUNT = 4; |
|
|
|
|
|
|
|
|
|
|
|
export class ChartwerkScatterPod extends ChartwerkPod<ScatterData, ScatterOptions> { |
|
|
|
export class ChartwerkScatterPod extends ChartwerkPod<ScatterData, ScatterOptions> { |
|
|
|
metricContainer: any; |
|
|
|
_metricsContainer: any; |
|
|
|
_delaunayDiagram: DelaunayDiagram; |
|
|
|
_delaunayDiagram: DelaunayDiagram; |
|
|
|
series: ScatterSeries; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
constructor(el: HTMLElement, _series: ScatterData[] = [], _options: ScatterOptions = {}) { |
|
|
|
constructor(el: HTMLElement, _series: ScatterData[] = [], _options: ScatterOptions = {}) { |
|
|
|
super(el, _series, _options); |
|
|
|
super(d3, el, _series, _options); |
|
|
|
this.series = new ScatterSeries(_series); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
renderMetrics(): void { |
|
|
|
renderMetrics(): void { |
|
|
|
if(!this.series.isSeriesAvailable) { |
|
|
|
if(this.series.length === 0) { |
|
|
|
this.renderNoDataPointsMessage(); |
|
|
|
this.renderNoDataPointsMessage(); |
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
this.updateCrosshair();
|
|
|
|
this.updateCrosshair();
|
|
|
|
|
|
|
|
this.renderMetricContainer(); |
|
|
|
|
|
|
|
|
|
|
|
this._delaunayDiagram = new DelaunayDiagram(this.series, this.state.xScale, this.getYScale.bind(this)); |
|
|
|
this._delaunayDiagram = new DelaunayDiagram(this.series, this.xScale, this.getYScale.bind(this)); |
|
|
|
|
|
|
|
|
|
|
|
this.renderLines(); |
|
|
|
this.renderLines(); |
|
|
|
this.renderPoints(); |
|
|
|
this.renderPoints(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
clearAllMetrics(): void { |
|
|
|
renderMetricContainer(): void { |
|
|
|
// TODO: temporary hack before it will be implemented in core.
|
|
|
|
// container for clip path
|
|
|
|
this.chartContainer.selectAll('.metric-el').remove(); |
|
|
|
const clipContatiner = this.chartContainer |
|
|
|
|
|
|
|
.append('g') |
|
|
|
|
|
|
|
.attr('clip-path', `url(#${this.rectClipId})`) |
|
|
|
|
|
|
|
.attr('class', 'metrics-container'); |
|
|
|
|
|
|
|
// container for panning
|
|
|
|
|
|
|
|
this._metricsContainer = clipContatiner |
|
|
|
|
|
|
|
.append('g') |
|
|
|
|
|
|
|
.attr('class', ' metrics-rect'); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
protected updateCrosshair(): void { |
|
|
|
protected updateCrosshair(): void { |
|
|
|
this.crosshair.selectAll('circle').remove(); |
|
|
|
|
|
|
|
// TODO: Crosshair class, which can be used as Pod
|
|
|
|
// TODO: Crosshair class, which can be used as Pod
|
|
|
|
this.appendCrosshairPoints(); |
|
|
|
this.appendCrosshairPoints(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
appendCrosshairPoints(): void { |
|
|
|
appendCrosshairPoints(): void { |
|
|
|
this.series.visibleSeries.forEach((serie: ScatterData) => { |
|
|
|
this.series.forEach((serie: ScatterData, serieIdx: number) => { |
|
|
|
this.appendCrosshairPoint(serie.idx, serie.pointType, serie.pointSize); |
|
|
|
this.appendCrosshairPoint(serieIdx); |
|
|
|
}); |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
protected appendCrosshairPoint(serieIdx: number, pointType: PointType, size: number): void { |
|
|
|
protected appendCrosshairPoint(serieIdx: number): void { |
|
|
|
// TODO: add Crosshair type options
|
|
|
|
// TODO: add Crosshair type options
|
|
|
|
|
|
|
|
const pointType = this.series[serieIdx].pointType || DEFAULT_POINT_TYPE; |
|
|
|
switch(pointType) { |
|
|
|
switch(pointType) { |
|
|
|
case PointType.NONE: |
|
|
|
case PointType.NONE: |
|
|
|
return; |
|
|
|
return; |
|
|
|
case PointType.CIRCLE: |
|
|
|
case PointType.CIRCLE: |
|
|
|
this.crosshair.append('circle') |
|
|
|
this.crosshair.append('circle') |
|
|
|
.attr('class', `crosshair-point crosshair-point-${serieIdx} crosshair-background`) |
|
|
|
.attr('class', `crosshair-point crosshair-point-${serieIdx} crosshair-background`) |
|
|
|
.attr('r', this.getCrosshairCircleBackgroundSize(size, pointType)) |
|
|
|
.attr('r', this.getCrosshairCircleBackgroundSize(serieIdx)) |
|
|
|
.attr('clip-path', `url(#${this.rectClipId})`) |
|
|
|
.attr('clip-path', `url(#${this.rectClipId})`) |
|
|
|
.style('opacity', CROSSHAIR_BACKGROUND_OPACITY) |
|
|
|
.style('opacity', CROSSHAIR_BACKGROUND_OPACITY) |
|
|
|
.style('pointer-events', 'none') |
|
|
|
.style('pointer-events', 'none') |
|
|
@ -68,8 +77,8 @@ export class ChartwerkScatterPod extends ChartwerkPod<ScatterData, ScatterOption |
|
|
|
case PointType.RECTANGLE: |
|
|
|
case PointType.RECTANGLE: |
|
|
|
this.crosshair.append('rect') |
|
|
|
this.crosshair.append('rect') |
|
|
|
.attr('class', `crosshair-point crosshair-point-${serieIdx} crosshair-background`) |
|
|
|
.attr('class', `crosshair-point crosshair-point-${serieIdx} crosshair-background`) |
|
|
|
.attr('width', this.getCrosshairCircleBackgroundSize(size, pointType)) |
|
|
|
.attr('width', this.getCrosshairCircleBackgroundSize(serieIdx)) |
|
|
|
.attr('height', this.getCrosshairCircleBackgroundSize(size, pointType)) |
|
|
|
.attr('height', this.getCrosshairCircleBackgroundSize(serieIdx)) |
|
|
|
.attr('clip-path', `url(#${this.rectClipId})`) |
|
|
|
.attr('clip-path', `url(#${this.rectClipId})`) |
|
|
|
.style('opacity', CROSSHAIR_BACKGROUND_OPACITY) |
|
|
|
.style('opacity', CROSSHAIR_BACKGROUND_OPACITY) |
|
|
|
.style('pointer-events', 'none') |
|
|
|
.style('pointer-events', 'none') |
|
|
@ -81,97 +90,82 @@ export class ChartwerkScatterPod extends ChartwerkPod<ScatterData, ScatterOption |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
protected renderLines(): void { |
|
|
|
protected renderLines(): void { |
|
|
|
this.series.visibleSeries.forEach(serie => { |
|
|
|
this.series.forEach((serie, serieIdx) => { |
|
|
|
this.renderLine(serie); |
|
|
|
if(serie.visible === false) { |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
const lineType = serie.lineType || DEFAULT_LINE_TYPE; |
|
|
|
|
|
|
|
this.renderLine(serie.datapoints, lineType, this.getSerieColor(serieIdx), serie.yOrientation); |
|
|
|
}); |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
renderLine(serie: ScatterData): void { |
|
|
|
renderLine(datapoints: number[][], lineType: LineType, color: string, orientation: yAxisOrientation): void { |
|
|
|
if(serie.lineType === LineType.NONE) { |
|
|
|
if(lineType === LineType.NONE) { |
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
let strokeDasharray; |
|
|
|
let strokeDasharray; |
|
|
|
// TODO: move to option
|
|
|
|
// TODO: move to option
|
|
|
|
if(serie.lineType === LineType.DASHED) { |
|
|
|
if(lineType === LineType.DASHED) { |
|
|
|
strokeDasharray = DEFAULT_LINE_DASHED_AMOUNT; |
|
|
|
strokeDasharray = DEFAULT_LINE_DASHED_AMOUNT; |
|
|
|
} |
|
|
|
} |
|
|
|
const lineGenerator = d3.line() |
|
|
|
const lineGenerator = this.d3.line() |
|
|
|
.x((d: [number, number]) => this.state.xScale(d[0])) |
|
|
|
.x((d: [number, number]) => this.xScale(d[1])) |
|
|
|
.y((d: [number, number]) => this.getYScale(serie.yOrientation)(d[1])); |
|
|
|
.y((d: [number, number]) => this.getYScale(orientation)(d[0])); |
|
|
|
|
|
|
|
|
|
|
|
this.metricContainer |
|
|
|
this._metricsContainer |
|
|
|
.append('path') |
|
|
|
.append('path') |
|
|
|
.datum(serie.datapoints) |
|
|
|
.datum(datapoints) |
|
|
|
.attr('class', 'metric-path') |
|
|
|
.attr('class', 'metric-path') |
|
|
|
.attr('d', lineGenerator) |
|
|
|
|
|
|
|
.attr('fill', 'none') |
|
|
|
.attr('fill', 'none') |
|
|
|
.attr('stroke', serie.color) |
|
|
|
.style('pointer-events', 'none') |
|
|
|
|
|
|
|
.attr('stroke', color) |
|
|
|
.attr('stroke-width', 1) |
|
|
|
.attr('stroke-width', 1) |
|
|
|
.attr('stroke-opacity', 0.7) |
|
|
|
.attr('stroke-opacity', 0.7) |
|
|
|
.attr('stroke-dasharray', strokeDasharray) |
|
|
|
.attr('stroke-dasharray', strokeDasharray) |
|
|
|
.style('pointer-events', serie.clickCallback ? 'auto' : 'none') |
|
|
|
.attr('d', lineGenerator); |
|
|
|
.style('cursor', serie.clickCallback ? 'pointer' : 'crosshair') |
|
|
|
|
|
|
|
.on('click', () => { serie.clickCallback({ target: serie.target, class: serie.class, alias: serie.alias }) }); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
protected renderPoints(): void { |
|
|
|
protected renderPoints(): void { |
|
|
|
if(!this._delaunayDiagram.data) { |
|
|
|
if(!this._delaunayDiagram.data) { |
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
this.metricContainer.selectAll(null) |
|
|
|
|
|
|
|
|
|
|
|
this._metricsContainer.selectAll(null) |
|
|
|
.data(this._delaunayDiagram.data) |
|
|
|
.data(this._delaunayDiagram.data) |
|
|
|
.enter() |
|
|
|
.enter() |
|
|
|
.append('circle') |
|
|
|
.append('circle') |
|
|
|
.filter((d: DelaunayDataRow) => this.series.getSerieByTarget(d[4])?.pointType !== PointType.RECTANGLE) |
|
|
|
.filter((d: number[]) => this.series[_.last(d)].pointType !== PointType.RECTANGLE) |
|
|
|
.attr('class', (d, i: number) => `metric-element metric-circle point-${i}`) |
|
|
|
.attr('class', (d, i: number) => `metric-element metric-circle point-${i}`) |
|
|
|
.attr('r', (d: DelaunayDataRow) => this.series.getSerieByTarget(d[4])?.pointSize) |
|
|
|
.attr('r', (d: number[]) => this.series[_.last(d)].pointSize || DEFAULT_POINT_SIZE) |
|
|
|
.attr('cx', (d: DelaunayDataRow) => this.state.xScale(d[0])) |
|
|
|
.style('fill', (d: number[]) => this.getSerieColor(_.last(d))) |
|
|
|
.attr('cy', (d: DelaunayDataRow) => this.getYScale(this.series.getSerieByTarget(d[4])?.yOrientation)(d[1])) |
|
|
|
.style('pointer-events', 'none') |
|
|
|
.style('fill', (d: DelaunayDataRow, i: number) => this.getSeriesColorFromDataRow(d, i)) |
|
|
|
.attr('cx', (d: any[]) => this.xScale(d[1])) |
|
|
|
.style('pointer-events', (d: DelaunayDataRow) => this.series.getSerieByTarget(d[4])?.clickCallback ? 'auto' : 'none') |
|
|
|
.attr('cy', (d: any[]) => this.getYScale(this.series[_.last(d)].yOrientation)(d[0])); |
|
|
|
.style('cursor', (d: DelaunayDataRow) => this.series.getSerieByTarget(d[4])?.clickCallback ? 'pointer' : 'crosshair') |
|
|
|
|
|
|
|
.on('click', (d: DelaunayDataRow) => { |
|
|
|
this._metricsContainer.selectAll(null) |
|
|
|
d3.event.stopPropagation(); |
|
|
|
|
|
|
|
const serie = this.series.getSerieByTarget(d[4]); |
|
|
|
|
|
|
|
const serieData = { target: serie?.target, class: serie?.class, alias: serie?.alias }; |
|
|
|
|
|
|
|
serie?.clickCallback(serieData, d); |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.metricContainer.selectAll(null) |
|
|
|
|
|
|
|
.data(this._delaunayDiagram.data) |
|
|
|
.data(this._delaunayDiagram.data) |
|
|
|
.enter() |
|
|
|
.enter() |
|
|
|
.append('rect') |
|
|
|
.append('rect') |
|
|
|
.filter((d: DelaunayDataRow) => this.series.getSerieByTarget(d[4])?.pointType === PointType.RECTANGLE) |
|
|
|
.filter((d: number[]) => this.series[_.last(d)].pointType === PointType.RECTANGLE) |
|
|
|
.attr('class', (d, i: number) => `metric-element metric-circle point-${i}`) |
|
|
|
.attr('class', (d, i: number) => `metric-element metric-circle point-${i}`) |
|
|
|
.attr('r', (d: DelaunayDataRow) => this.series.getSerieByTarget(d[4])?.pointSize) |
|
|
|
.attr('r', (d: number[]) => this.series[_.last(d)].pointSize || DEFAULT_POINT_SIZE) |
|
|
|
.attr('x', (d: DelaunayDataRow) => this.state.xScale(d[0]) - this.series.getSerieByTarget(d[4])?.pointSize / 2) |
|
|
|
.style('fill', (d: number[]) => this.getSerieColor(_.last(d))) |
|
|
|
.attr('y', (d: DelaunayDataRow) => this.getYScale(this.series.getSerieByTarget(d[4])?.yOrientation)(d[1]) - this.series.getSerieByTarget(d[4])?.pointSize / 2) |
|
|
|
.style('pointer-events', 'none') |
|
|
|
.attr('width', (d: DelaunayDataRow) => this.series.getSerieByTarget(d[4])?.pointSize) |
|
|
|
.attr('x', (d: number[]) => this.xScale(d[1]) - (this.series[_.last(d)].pointSize || DEFAULT_POINT_SIZE) / 2) |
|
|
|
.attr('height', (d: DelaunayDataRow) => this.series.getSerieByTarget(d[4])?.pointSize) |
|
|
|
.attr('y', (d: number[]) => this.getYScale(this.series[_.last(d)].yOrientation)(d[0]) - (this.series[_.last(d)].pointSize || DEFAULT_POINT_SIZE) / 2) |
|
|
|
.style('fill', (d: DelaunayDataRow) => this.series.getSerieByTarget(d[4])?.color) |
|
|
|
.attr('width', (d: number[]) => this.series[_.last(d)].pointSize || DEFAULT_POINT_SIZE) |
|
|
|
.style('pointer-events', (d: DelaunayDataRow) => this.series.getSerieByTarget(d[4])?.clickCallback ? 'auto' : 'none') |
|
|
|
.attr('height', (d: number[]) => this.series[_.last(d)].pointSize || DEFAULT_POINT_SIZE); |
|
|
|
.style('cursor', (d: DelaunayDataRow) => this.series.getSerieByTarget(d[4])?.clickCallback ? 'pointer' : 'crosshair') |
|
|
|
|
|
|
|
.on('click', (d: DelaunayDataRow) => { |
|
|
|
|
|
|
|
d3.event.stopPropagation(); |
|
|
|
|
|
|
|
const serie = this.series.getSerieByTarget(d[4]); |
|
|
|
|
|
|
|
const serieData = { target: serie?.target, class: serie?.class, alias: serie?.alias }; |
|
|
|
|
|
|
|
serie?.clickCallback(serieData, d); |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
getSeriesColorFromDataRow(values: DelaunayDataRow, rowIdx: number): string { |
|
|
|
|
|
|
|
const serie = this.series.getSerieByTarget(values[4]); |
|
|
|
|
|
|
|
if(serie?.colorFormatter) { |
|
|
|
|
|
|
|
return serie.colorFormatter(values, rowIdx); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return serie.color; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
onPanningEnd(): void { |
|
|
|
onPanningEnd(): void { |
|
|
|
this.isPanning = false; |
|
|
|
this.isPanning = false; |
|
|
|
this.onMouseOut(); |
|
|
|
this.onMouseOut(); |
|
|
|
this._delaunayDiagram.setDelaunayDiagram(this.state.xScale, this.getYScale.bind(this)); |
|
|
|
this._delaunayDiagram.setDelaunayDiagram(this.xScale, this.getYScale.bind(this)); |
|
|
|
this.options.callbackPanningEnd([this.state.xValueRange, this.state.yValueRange, this.state.y1ValueRange]); |
|
|
|
if(this.options.eventsCallbacks !== undefined && this.options.eventsCallbacks.panningEnd !== undefined) { |
|
|
|
|
|
|
|
this.options.eventsCallbacks.panningEnd([this.state.xValueRange, this.state.yValueRange, this.state.y1ValueRange]); |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
console.log('on panning end, but there is no callback'); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
unhighlight(): void { |
|
|
|
unhighlight(): void { |
|
|
@ -181,37 +175,47 @@ export class ChartwerkScatterPod extends ChartwerkPod<ScatterData, ScatterOption |
|
|
|
highlight(pointIdx: number): void { |
|
|
|
highlight(pointIdx: number): void { |
|
|
|
this.unhighlight(); |
|
|
|
this.unhighlight(); |
|
|
|
|
|
|
|
|
|
|
|
const row = this._delaunayDiagram.getDataRowByIndex(pointIdx); |
|
|
|
const datapoint = this._delaunayDiagram.getDataRowByIndex(pointIdx); |
|
|
|
if(row === undefined || row === null) { |
|
|
|
if(datapoint === undefined || datapoint === null) { |
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
const serie = this.series.getSerieByTarget(row[4]); |
|
|
|
|
|
|
|
const size = this.getCrosshairCircleBackgroundSize(serie.pointSize, serie.pointType); |
|
|
|
const serieIdx = _.last(datapoint); |
|
|
|
this.crosshair.selectAll(`.crosshair-point-${serie.idx}`) |
|
|
|
const serieOrientation = this.series[serieIdx].yOrientation; |
|
|
|
.attr('cx', this.state.xScale(row[0])) |
|
|
|
const size = this.getCrosshairCircleBackgroundSize(serieIdx); |
|
|
|
.attr('cy', this.getYScale(serie.yOrientation)(row[1])) |
|
|
|
const colorFormatter = this.series[serieIdx].colorFormatter; |
|
|
|
.attr('x', this.state.xScale(row[0]) - size / 2) |
|
|
|
this.crosshair.selectAll(`.crosshair-point-${serieIdx}`) |
|
|
|
.attr('y', this.getYScale(serie.yOrientation)(row[1]) - size / 2) |
|
|
|
.attr('cx', this.xScale(datapoint[1])) |
|
|
|
.attr('fill', this.getSeriesColorFromDataRow(row, pointIdx)) |
|
|
|
.attr('cy', this.getYScale(serieOrientation)(datapoint[0])) |
|
|
|
|
|
|
|
.attr('x', this.xScale(datapoint[1]) - size / 2) |
|
|
|
|
|
|
|
.attr('y', this.getYScale(serieOrientation)(datapoint[0]) - size / 2) |
|
|
|
|
|
|
|
.attr('fill', colorFormatter !== undefined ? colorFormatter(datapoint) : this.series[serieIdx].color) |
|
|
|
.style('display', null); |
|
|
|
.style('display', null); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
protected getCrosshairCircleBackgroundSize(pointSize: number, pointType: PointType): number { |
|
|
|
protected getCrosshairCircleBackgroundSize(serieIdx: number): number { |
|
|
|
|
|
|
|
const seriePointSize = this.series[serieIdx].pointSize || DEFAULT_POINT_SIZE; |
|
|
|
|
|
|
|
const pointType = this.series[serieIdx].pointType || DEFAULT_POINT_TYPE; |
|
|
|
let highlightDiameter = POINT_HIGHLIGHT_DIAMETER; |
|
|
|
let highlightDiameter = POINT_HIGHLIGHT_DIAMETER; |
|
|
|
if(pointType === PointType.RECTANGLE) { |
|
|
|
if(pointType === PointType.RECTANGLE) { |
|
|
|
highlightDiameter = highlightDiameter * 2; |
|
|
|
highlightDiameter = highlightDiameter * 2; |
|
|
|
} |
|
|
|
} |
|
|
|
return pointSize + highlightDiameter; |
|
|
|
return seriePointSize + highlightDiameter; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
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.state.xScale(values.x); |
|
|
|
const eventX = this.xScale(values.x); |
|
|
|
const eventY = this.state.yScale(values.y); |
|
|
|
const eventY = this.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); |
|
|
|
|
|
|
|
|
|
|
|
this.options.callbackSharedCrosshairMove({ |
|
|
|
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 |
|
|
|
eventX, eventY |
|
|
|
}); |
|
|
|
}); |
|
|
@ -242,12 +246,8 @@ export class ChartwerkScatterPod extends ChartwerkPod<ScatterData, ScatterOption |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
findAndHighlightDatapoints(eventX: number, eventY: number): { |
|
|
|
findAndHighlightDatapoints(eventX: number, eventY: number): { values: any[], pointIdx: number } | null { |
|
|
|
xValue: number, yValue: number, customValue: number, |
|
|
|
if(this.series === undefined || this.series.length === 0) { |
|
|
|
pointIdx: number, totalPointIdx: number, |
|
|
|
|
|
|
|
serieInfo: { target: string, alias?: string, class?: string, idx?: number } |
|
|
|
|
|
|
|
} | null { |
|
|
|
|
|
|
|
if(!this.series.isSeriesAvailable) { |
|
|
|
|
|
|
|
return null; |
|
|
|
return null; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -257,29 +257,22 @@ export class ChartwerkScatterPod extends ChartwerkPod<ScatterData, ScatterOption |
|
|
|
return null; |
|
|
|
return null; |
|
|
|
} |
|
|
|
} |
|
|
|
this.highlight(pointIndex); |
|
|
|
this.highlight(pointIndex); |
|
|
|
const row = this._delaunayDiagram.data[pointIndex]; |
|
|
|
|
|
|
|
const serie = this.series.getSerieByTarget(row[4]); |
|
|
|
|
|
|
|
return { |
|
|
|
return { |
|
|
|
xValue: row[0], |
|
|
|
values: this._delaunayDiagram.data[pointIndex], |
|
|
|
yValue: row[1], |
|
|
|
pointIdx: pointIndex, |
|
|
|
customValue: row[2], |
|
|
|
|
|
|
|
pointIdx: row[3], |
|
|
|
|
|
|
|
serieInfo: { |
|
|
|
|
|
|
|
target: serie.target, |
|
|
|
|
|
|
|
alias: serie.alias, |
|
|
|
|
|
|
|
class: serie.class, |
|
|
|
|
|
|
|
idx: serie.idx, |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
totalPointIdx: pointIndex, |
|
|
|
|
|
|
|
}; |
|
|
|
}; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
protected getYScale(orientation: yAxisOrientation): d3.ScaleLinear<number, number> { |
|
|
|
protected getYScale(orientation: yAxisOrientation): d3.ScaleLinear<number, number> { |
|
|
|
|
|
|
|
if(orientation === undefined || orientation === yAxisOrientation.BOTH) { |
|
|
|
|
|
|
|
return this.yScale; |
|
|
|
|
|
|
|
} |
|
|
|
switch(orientation) { |
|
|
|
switch(orientation) { |
|
|
|
case yAxisOrientation.LEFT: |
|
|
|
case yAxisOrientation.LEFT: |
|
|
|
return this.state.yScale; |
|
|
|
return this.yScale; |
|
|
|
case yAxisOrientation.RIGHT: |
|
|
|
case yAxisOrientation.RIGHT: |
|
|
|
return this.state.y1Scale; |
|
|
|
return this.y1Scale; |
|
|
|
default: |
|
|
|
default: |
|
|
|
throw new Error(`Unknown type of y axis orientation: ${orientation}`)
|
|
|
|
throw new Error(`Unknown type of y axis orientation: ${orientation}`)
|
|
|
|
} |
|
|
|
} |
|
|
@ -290,11 +283,12 @@ export class ChartwerkScatterPod extends ChartwerkPod<ScatterData, ScatterOption |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
onMouseMove(): void { |
|
|
|
onMouseMove(): void { |
|
|
|
const mousePosition = d3.mouse(this.chartContainer.node()); |
|
|
|
const mousePosition = this.d3.mouse(this.chartContainer.node()); |
|
|
|
const eventX = mousePosition[0]; |
|
|
|
const eventX = mousePosition[0]; |
|
|
|
const eventY = mousePosition[1]; |
|
|
|
const eventY = mousePosition[1]; |
|
|
|
|
|
|
|
|
|
|
|
if(this.isPanning === true || this.isBrushing === true) { |
|
|
|
// TODO: seems isOutOfChart is deprecated (check clippath correctness)
|
|
|
|
|
|
|
|
if(this.isOutOfChart() === true || this.isPanning === true || this.isBrushing === true) { |
|
|
|
this.crosshair.style('display', 'none'); |
|
|
|
this.crosshair.style('display', 'none'); |
|
|
|
return; |
|
|
|
return; |
|
|
|
} else { |
|
|
|
} else { |
|
|
@ -305,25 +299,24 @@ export class ChartwerkScatterPod extends ChartwerkPod<ScatterData, ScatterOption |
|
|
|
|
|
|
|
|
|
|
|
// TOOD: it should be two different methods
|
|
|
|
// TOOD: it should be two different methods
|
|
|
|
const highlighted = this.findAndHighlightDatapoints(eventX, eventY); |
|
|
|
const highlighted = this.findAndHighlightDatapoints(eventX, eventY); |
|
|
|
this.options.callbackMouseMove({ |
|
|
|
if(this.options.eventsCallbacks === undefined || this.options.eventsCallbacks.mouseMove === undefined) { |
|
|
|
bbox: { |
|
|
|
console.log('Mouse move, but there is no callback'); |
|
|
|
clientX: d3.event.clientX, |
|
|
|
return; |
|
|
|
clientY: d3.event.clientY, |
|
|
|
} |
|
|
|
x: eventX, |
|
|
|
// TODO: group fields
|
|
|
|
y: eventY, |
|
|
|
this.options.eventsCallbacks.mouseMove({ |
|
|
|
chartWidth: this.width, |
|
|
|
x: this.d3.event.clientX, |
|
|
|
chartHeight: this.height, |
|
|
|
y: this.d3.event.clientY, |
|
|
|
}, |
|
|
|
xval: this.xScale.invert(eventX), |
|
|
|
data: { |
|
|
|
yval: this.xScale.invert(eventY), |
|
|
|
xval: this.state.xScale.invert(eventX), |
|
|
|
highlighted, |
|
|
|
yval: this.state.xScale.invert(eventY), |
|
|
|
chartX: eventX, |
|
|
|
highlighted, |
|
|
|
chartWidth: this.width |
|
|
|
} |
|
|
|
|
|
|
|
}); |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
onMouseOver(): void { |
|
|
|
onMouseOver(): void { |
|
|
|
if(this.isPanning === true || this.isBrushing === true) { |
|
|
|
if(this.isOutOfChart() === true || this.isPanning === true || this.isBrushing === true) { |
|
|
|
this.crosshair.style('display', 'none'); |
|
|
|
this.crosshair.style('display', 'none'); |
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
@ -331,7 +324,9 @@ export class ChartwerkScatterPod extends ChartwerkPod<ScatterData, ScatterOption |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
onMouseOut(): void { |
|
|
|
onMouseOut(): void { |
|
|
|
this.options.callbackMouseOut(); |
|
|
|
if(this.options.eventsCallbacks !== undefined && this.options.eventsCallbacks.mouseOut !== undefined) { |
|
|
|
|
|
|
|
this.options.eventsCallbacks.mouseOut(); |
|
|
|
|
|
|
|
} |
|
|
|
this.crosshair.style('display', 'none'); |
|
|
|
this.crosshair.style('display', 'none'); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
@ -361,4 +356,4 @@ export const VueChartwerkScatterPodObject = { |
|
|
|
} |
|
|
|
} |
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
export { ScatterData, ScatterOptions, TimeFormat, PointType, LineType, HighlightedData, MouseMoveEvent }; |
|
|
|
export { ScatterData, ScatterOptions, TickOrientation, TimeFormat, PointType, LineType }; |
|
|
|