import { ScatterData, PointType } from '../types'; import { ScatterSeries } from './scatter_series'; import { Delaunay } from 'd3-delaunay'; import * as _ from 'lodash'; import * as d3 from 'd3'; // only types // return type row: [ 0:x, 1:y, 2:(custom value | null), 3:pointIdx, 4:serie.target ] type Value = number; type PointIdx = number; type Target = string; export type DelaunayDataRow = [Value, Value, Value | null, PointIdx, Target]; export class DelaunayDiagram { private _delaunayData: DelaunayDataRow[]; private _delaunayDiagram: any; constructor( protected series: ScatterSeries, xScale: d3.ScaleLinear, yScale: (string) => d3.ScaleLinear, // TODO: bad, but idk how to do it better ) { this._delaunayData = this.getDatapointsForDelaunay(); this.setDelaunayDiagram(xScale, yScale); } public get data(): DelaunayDataRow[] | undefined { if(!this._delaunayData || this._delaunayData.length === 0) { return undefined; } return this._delaunayData; } public setDelaunayDiagram(xScale: d3.ScaleLinear, yScale: (string) => d3.ScaleLinear) { if(!this._delaunayData) { console.warn('No data for delaunay initialization'); return; } console.time('delaunay-init'); this._delaunayDiagram = Delaunay.from( this._delaunayData, (d: DelaunayDataRow) => xScale(d[0]), (d: DelaunayDataRow) => yScale(this.series.getSerieByTarget(d[4])?.yOrientation)(d[1]), ); console.timeEnd('delaunay-init'); } public findPointIndex(eventX: number, eventY: number): number | undefined { if(!this._delaunayDiagram) { return undefined; } let pointIndex = this._delaunayDiagram.find(eventX, eventY); if(pointIndex === -1) { return undefined; } // TODO: add search radius via https://github.com/d3/d3-delaunay/issues/45 return pointIndex; } public getDataRowByIndex(index: number): DelaunayDataRow | undefined { if(!this.data) { return undefined; } return this.data[index]; } private getDatapointsForDelaunay(): DelaunayDataRow[] | undefined { // here we union all datapoints with point render type(circle or rectangle) // it means that circles and rectangles will be highlighted(not lines) // TODO: set Defaults (if pointType === undefined, Circle type will be used futher) const seriesForPointType = this.series.visibleSeries.filter((serie: ScatterData) => serie.pointType !== PointType.NONE); if(seriesForPointType.length === 0) { return undefined; // to avoid ts error } return this.concatSeriesDatapoints(seriesForPointType); } private concatSeriesDatapoints(series: ScatterData[]): DelaunayDataRow[] { const datapointsList = _.map(series, serie => { const datapointsWithOptions = _.map(serie.datapoints, (row, rowIdx) => [row[0], row[1], row[2] || null, rowIdx, serie.target]); return datapointsWithOptions; }); return _.union(...datapointsList); } }