Scatter Pod
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.
 
 

89 lines
3.0 KiB

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;
}
}