|
|
|
@ -1,7 +1,7 @@
|
|
|
|
|
import { ChartwerkPod, VueChartwerkPodMixin, TimeFormat, yAxisOrientation, CrosshairOrientation } from '@chartwerk/core'; |
|
|
|
|
import { ScatterData, ScatterOptions, PointType, LineType } from './types'; |
|
|
|
|
import { ScatterData, ScatterOptions, PointType, LineType, HighlightedData, MouseMoveEvent } from './types'; |
|
|
|
|
|
|
|
|
|
import { DelaunayDiagram } from './models/delaunay'; |
|
|
|
|
import { DelaunayDiagram, DelaunayDataRow } from './models/delaunay'; |
|
|
|
|
import { ScatterSeries } from './models/scatter_series'; |
|
|
|
|
|
|
|
|
|
import * as d3 from 'd3'; |
|
|
|
@ -14,6 +14,7 @@ const DEFAULT_LINE_DASHED_AMOUNT = 4;
|
|
|
|
|
export class ChartwerkScatterPod extends ChartwerkPod<ScatterData, ScatterOptions> { |
|
|
|
|
metricContainer: any; |
|
|
|
|
_delaunayDiagram: DelaunayDiagram; |
|
|
|
|
series: ScatterSeries; |
|
|
|
|
|
|
|
|
|
constructor(el: HTMLElement, _series: ScatterData[] = [], _options: ScatterOptions = {}) { |
|
|
|
|
super(el, _series, _options); |
|
|
|
@ -27,7 +28,7 @@ export class ChartwerkScatterPod extends ChartwerkPod<ScatterData, ScatterOption
|
|
|
|
|
} |
|
|
|
|
this.updateCrosshair(); |
|
|
|
|
|
|
|
|
|
this._delaunayDiagram = new DelaunayDiagram(this.series.visibleSeries, this.state.xScale, this.getYScale.bind(this)); |
|
|
|
|
this._delaunayDiagram = new DelaunayDiagram(this.series, this.state.xScale, this.getYScale.bind(this)); |
|
|
|
|
|
|
|
|
|
this.renderLines(); |
|
|
|
|
this.renderPoints(); |
|
|
|
@ -115,41 +116,35 @@ export class ChartwerkScatterPod extends ChartwerkPod<ScatterData, ScatterOption
|
|
|
|
|
if(!this._delaunayDiagram.data) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
this.metricContainer.selectAll(null) |
|
|
|
|
.data(this._delaunayDiagram.data) |
|
|
|
|
.enter() |
|
|
|
|
.append('circle') |
|
|
|
|
.filter((d: number[]) => this.getSerieByIdx(_.last(d))?.pointType !== PointType.RECTANGLE) |
|
|
|
|
.filter((d: DelaunayDataRow) => this.series.getSerieByTarget(d[4])?.pointType !== PointType.RECTANGLE) |
|
|
|
|
.attr('class', (d, i: number) => `metric-element metric-circle point-${i}`) |
|
|
|
|
.attr('r', (d: number[]) => this.getSerieByIdx(_.last(d))?.pointSize) |
|
|
|
|
.style('fill', (d: number[], i: number) => this.getSeriesColorFromDataRow(d, i)) |
|
|
|
|
.attr('r', (d: DelaunayDataRow) => this.series.getSerieByTarget(d[4])?.pointSize) |
|
|
|
|
.style('fill', (d: DelaunayDataRow, i: number) => this.getSeriesColorFromDataRow(d, i)) |
|
|
|
|
.style('pointer-events', 'none') |
|
|
|
|
.attr('cx', (d: any[]) => this.state.xScale(d[0])) |
|
|
|
|
.attr('cy', (d: any[]) => this.getYScale(this.getSerieByIdx(_.last(d))?.yOrientation)(d[1])); |
|
|
|
|
.attr('cx', (d: DelaunayDataRow) => this.state.xScale(d[0])) |
|
|
|
|
.attr('cy', (d: DelaunayDataRow) => this.getYScale(this.series.getSerieByTarget(d[4])?.yOrientation)(d[1])); |
|
|
|
|
|
|
|
|
|
this.metricContainer.selectAll(null) |
|
|
|
|
.data(this._delaunayDiagram.data) |
|
|
|
|
.enter() |
|
|
|
|
.append('rect') |
|
|
|
|
.filter((d: number[]) => this.getSerieByIdx(_.last(d))?.pointType === PointType.RECTANGLE) |
|
|
|
|
.filter((d: DelaunayDataRow) => this.series.getSerieByTarget(d[4])?.pointType === PointType.RECTANGLE) |
|
|
|
|
.attr('class', (d, i: number) => `metric-element metric-circle point-${i}`) |
|
|
|
|
.attr('r', (d: number[]) => this.getSerieByIdx(_.last(d))?.pointSize) |
|
|
|
|
.style('fill', (d: number[]) => this.getSerieByIdx(_.last(d))?.color) |
|
|
|
|
.attr('r', (d: DelaunayDataRow) => this.series.getSerieByTarget(d[4])?.pointSize) |
|
|
|
|
.style('fill', (d: DelaunayDataRow) => this.series.getSerieByTarget(d[4])?.color) |
|
|
|
|
.style('pointer-events', 'none') |
|
|
|
|
.attr('x', (d: number[]) => this.state.xScale(d[0]) - this.getSerieByIdx(_.last(d))?.pointSize / 2) |
|
|
|
|
.attr('y', (d: number[]) => this.getYScale(this.getSerieByIdx(_.last(d)).yOrientation)(d[1]) - this.getSerieByIdx(_.last(d))?.pointSize / 2) |
|
|
|
|
.attr('width', (d: number[]) => this.getSerieByIdx(_.last(d))?.pointSize) |
|
|
|
|
.attr('height', (d: number[]) => this.getSerieByIdx(_.last(d))?.pointSize); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
getSerieByIdx(idx: number): ScatterData | undefined { |
|
|
|
|
return _.find(this.series.visibleSeries, serie => serie.idx === idx); |
|
|
|
|
.attr('x', (d: DelaunayDataRow) => this.state.xScale(d[0]) - this.series.getSerieByTarget(d[4])?.pointSize / 2) |
|
|
|
|
.attr('y', (d: DelaunayDataRow) => this.getYScale(this.series.getSerieByTarget(d[4])?.yOrientation)(d[1]) - this.series.getSerieByTarget(d[4])?.pointSize / 2) |
|
|
|
|
.attr('width', (d: DelaunayDataRow) => this.series.getSerieByTarget(d[4])?.pointSize) |
|
|
|
|
.attr('height', (d: DelaunayDataRow) => this.series.getSerieByTarget(d[4])?.pointSize); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
getSeriesColorFromDataRow(values: number[], rowIdx: number): string { |
|
|
|
|
const seriesIdx = _.last(values); |
|
|
|
|
const serie = this.getSerieByIdx(seriesIdx); |
|
|
|
|
getSeriesColorFromDataRow(values: DelaunayDataRow, rowIdx: number): string { |
|
|
|
|
const serie = this.series.getSerieByTarget(values[4]); |
|
|
|
|
if(serie?.colorFormatter) { |
|
|
|
|
return serie.colorFormatter(values, rowIdx); |
|
|
|
|
} |
|
|
|
@ -170,20 +165,18 @@ export class ChartwerkScatterPod extends ChartwerkPod<ScatterData, ScatterOption
|
|
|
|
|
highlight(pointIdx: number): void { |
|
|
|
|
this.unhighlight(); |
|
|
|
|
|
|
|
|
|
const datapoint = this._delaunayDiagram.getDataRowByIndex(pointIdx); |
|
|
|
|
if(datapoint === undefined || datapoint === null) { |
|
|
|
|
const row = this._delaunayDiagram.getDataRowByIndex(pointIdx); |
|
|
|
|
if(row === undefined || row === null) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const serieIdx = _.last(datapoint); |
|
|
|
|
const serie = this.getSerieByIdx(serieIdx); |
|
|
|
|
const serie = this.series.getSerieByTarget(row[4]); |
|
|
|
|
const size = this.getCrosshairCircleBackgroundSize(serie.pointSize, serie.pointType); |
|
|
|
|
this.crosshair.selectAll(`.crosshair-point-${serieIdx}`) |
|
|
|
|
.attr('cx', this.state.xScale(datapoint[0])) |
|
|
|
|
.attr('cy', this.getYScale(serie.yOrientation)(datapoint[1])) |
|
|
|
|
.attr('x', this.state.xScale(datapoint[0]) - size / 2) |
|
|
|
|
.attr('y', this.getYScale(serie.yOrientation)(datapoint[1]) - size / 2) |
|
|
|
|
.attr('fill', this.getSeriesColorFromDataRow(datapoint, pointIdx)) |
|
|
|
|
this.crosshair.selectAll(`.crosshair-point-${serie.idx}`) |
|
|
|
|
.attr('cx', this.state.xScale(row[0])) |
|
|
|
|
.attr('cy', this.getYScale(serie.yOrientation)(row[1])) |
|
|
|
|
.attr('x', this.state.xScale(row[0]) - size / 2) |
|
|
|
|
.attr('y', this.getYScale(serie.yOrientation)(row[1]) - size / 2) |
|
|
|
|
.attr('fill', this.getSeriesColorFromDataRow(row, pointIdx)) |
|
|
|
|
.style('display', null); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -233,21 +226,35 @@ export class ChartwerkScatterPod extends ChartwerkPod<ScatterData, ScatterOption
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
findAndHighlightDatapoints(eventX: number, eventY: number): { values: any[], pointIdx: number } | null { |
|
|
|
|
findAndHighlightDatapoints(eventX: number, eventY: number): { |
|
|
|
|
xValue: number, yValue: number, customValue: number, |
|
|
|
|
pointIdx: number, totalPointIdx: number, |
|
|
|
|
serieInfo: { target: string, alias?: string, class?: string, idx?: number } |
|
|
|
|
} | null { |
|
|
|
|
if(!this.series.isSeriesAvailable) { |
|
|
|
|
return null; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const pointIndex = this._delaunayDiagram.findPointIndex(eventX, eventY); |
|
|
|
|
if(pointIndex === undefined) { |
|
|
|
|
this.unhighlight(); |
|
|
|
|
return null; |
|
|
|
|
} |
|
|
|
|
this.highlight(pointIndex); |
|
|
|
|
|
|
|
|
|
const row = this._delaunayDiagram.data[pointIndex]; |
|
|
|
|
const serie = this.series.getSerieByTarget(row[4]); |
|
|
|
|
return { |
|
|
|
|
values: this._delaunayDiagram.data[pointIndex], |
|
|
|
|
pointIdx: pointIndex, |
|
|
|
|
xValue: row[0], |
|
|
|
|
yValue: row[1], |
|
|
|
|
customValue: row[2], |
|
|
|
|
pointIdx: row[3], |
|
|
|
|
serieInfo: { |
|
|
|
|
target: serie.target, |
|
|
|
|
alias: serie.alias, |
|
|
|
|
class: serie.class, |
|
|
|
|
idx: serie.idx, |
|
|
|
|
}, |
|
|
|
|
totalPointIdx: pointIndex, |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -282,16 +289,20 @@ export class ChartwerkScatterPod extends ChartwerkPod<ScatterData, ScatterOption
|
|
|
|
|
|
|
|
|
|
// TOOD: it should be two different methods
|
|
|
|
|
const highlighted = this.findAndHighlightDatapoints(eventX, eventY); |
|
|
|
|
|
|
|
|
|
// TODO: group fields
|
|
|
|
|
this.options.callbackMouseMove({ |
|
|
|
|
x: d3.event.clientX, |
|
|
|
|
y: d3.event.clientY, |
|
|
|
|
xval: this.state.xScale.invert(eventX), |
|
|
|
|
yval: this.state.xScale.invert(eventY), |
|
|
|
|
highlighted, |
|
|
|
|
chartX: eventX, |
|
|
|
|
chartWidth: this.width |
|
|
|
|
bbox: { |
|
|
|
|
clientX: d3.event.clientX, |
|
|
|
|
clientY: d3.event.clientY, |
|
|
|
|
x: eventX, |
|
|
|
|
y: eventY, |
|
|
|
|
chartWidth: this.width, |
|
|
|
|
chartHeight: this.height, |
|
|
|
|
}, |
|
|
|
|
data: { |
|
|
|
|
xval: this.state.xScale.invert(eventX), |
|
|
|
|
yval: this.state.xScale.invert(eventY), |
|
|
|
|
highlighted, |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -334,4 +345,4 @@ export const VueChartwerkScatterPodObject = {
|
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
export { ScatterData, ScatterOptions, TimeFormat, PointType, LineType }; |
|
|
|
|
export { ScatterData, ScatterOptions, TimeFormat, PointType, LineType, HighlightedData, MouseMoveEvent }; |
|
|
|
|