You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
90 lines
3.0 KiB
90 lines
3.0 KiB
3 years ago
|
import { ScatterData, PointType } from './types';
|
||
|
|
||
|
import { Delaunay } from 'd3-delaunay';
|
||
|
|
||
|
import * as _ from 'lodash';
|
||
|
import * as d3 from 'd3'; // only types
|
||
|
|
||
|
|
||
|
export class DelaunayDiagram {
|
||
|
private _delaunayData: number[][]; // [ 0:y, 1:x, ..., last:serieIdx ][]
|
||
|
private _delaunayDiagram: any;
|
||
|
|
||
|
constructor(
|
||
|
protected series: ScatterData[],
|
||
|
xScale: d3.ScaleLinear<number, number>, yScale: (string) => d3.ScaleLinear<number, number>, // TODO: bad, but idk how to do it better
|
||
|
) {
|
||
|
this._delaunayData = this.getDatapointsForDelaunay();
|
||
|
this.setDelaunayDiagram(xScale, yScale);
|
||
|
}
|
||
|
|
||
|
public get data(): number[][] | undefined {
|
||
|
if(!this._delaunayData || this._delaunayData.length === 0) {
|
||
|
return undefined;
|
||
|
}
|
||
|
return this._delaunayData;
|
||
|
}
|
||
|
|
||
|
public setDelaunayDiagram(xScale: d3.ScaleLinear<number, number>, yScale: (string) => d3.ScaleLinear<number, number>) {
|
||
|
if(!this._delaunayData) {
|
||
|
console.warn('No data for delaunay initialization');
|
||
|
return;
|
||
|
}
|
||
|
console.time('delaunay-init');
|
||
|
this._delaunayDiagram = Delaunay.from(
|
||
|
this._delaunayData,
|
||
|
(d: number[]) => xScale(d[1]),
|
||
|
(d: number[]) => yScale(this.series[_.last(d)].yOrientation)(d[0]),
|
||
|
);
|
||
|
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): number[] | undefined {
|
||
|
if(!this.data) {
|
||
|
return undefined;
|
||
|
}
|
||
|
return this.data[index];
|
||
|
}
|
||
|
|
||
|
private getDatapointsForDelaunay(): number[][] | 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.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[]): number[][] {
|
||
|
// return type row: [ 0:y, 1:x, 2?:custom value, last:serieIdx ]
|
||
|
const datapointsList = _.map(series, serie => {
|
||
|
const serieIdx = this.getSerieIdxByTarget(serie.target);
|
||
|
const datapointsWithOptions = _.map(serie.datapoints, row => _.concat(row, serieIdx));
|
||
|
return datapointsWithOptions;
|
||
|
});
|
||
|
return _.union(...datapointsList);
|
||
|
}
|
||
|
|
||
|
private getSerieIdxByTarget(target: string): number {
|
||
|
const idx = _.findIndex(this.series, serie => serie.target === target);
|
||
|
if(idx === -1) {
|
||
|
throw new Error(`Can't find serie with target: ${target}`);
|
||
|
}
|
||
|
return idx;
|
||
|
}
|
||
|
}
|