From c93bde1ad33c5f70b5a9690a71b9a68f37c9bf61 Mon Sep 17 00:00:00 2001 From: vargburz Date: Fri, 27 May 2022 17:15:04 +0300 Subject: [PATCH] update delaunay data with serie target && update mouse move callback type --- src/index.ts | 110 +++++++++++++++++++---------------- src/models/delaunay.ts | 27 +++++---- src/models/scatter_series.ts | 6 ++ src/types.ts | 24 +++++++- 4 files changed, 106 insertions(+), 61 deletions(-) diff --git a/src/index.ts b/src/index.ts index 2d5683b..29aa469 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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 { 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 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 +166,18 @@ export class ChartwerkScatterPod extends ChartwerkPod, yScale: (string) => d3.ScaleLinear, // TODO: bad, but idk how to do it better ) { this._delaunayData = this.getDatapointsForDelaunay(); this.setDelaunayDiagram(xScale, yScale); } - public get data(): number[][] | undefined { + public get data(): DelaunayDataRow[] | undefined { if(!this._delaunayData || this._delaunayData.length === 0) { return undefined; } @@ -33,8 +39,8 @@ export class DelaunayDiagram { console.time('delaunay-init'); this._delaunayDiagram = Delaunay.from( this._delaunayData, - (d: number[]) => xScale(d[0]), - (d: number[]) => yScale(this.series[_.last(d)].yOrientation)(d[1]), + (d: DelaunayDataRow) => xScale(d[0]), + (d: DelaunayDataRow) => yScale(this.series.getSerieByTarget(d[4])?.yOrientation)(d[1]), ); console.timeEnd('delaunay-init'); } @@ -51,28 +57,27 @@ export class DelaunayDiagram { return pointIndex; } - public getDataRowByIndex(index: number): number[] | undefined { + public getDataRowByIndex(index: number): DelaunayDataRow | undefined { if(!this.data) { return undefined; } return this.data[index]; } - private getDatapointsForDelaunay(): number[][] | undefined { + 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.filter((serie: ScatterData) => serie.pointType !== PointType.NONE); + 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[]): number[][] { - // return type row: [ 0:x, 1:y, 2?:custom value, last:serieIdx ] + private concatSeriesDatapoints(series: ScatterData[]): DelaunayDataRow[] { const datapointsList = _.map(series, serie => { - const datapointsWithOptions = _.map(serie.datapoints, row => _.concat(row, serie.idx)); + const datapointsWithOptions = _.map(serie.datapoints, (row, rowIdx) => [row[0], row[1], row[2] || null, rowIdx, serie.target]); return datapointsWithOptions; }); return _.union(...datapointsList); diff --git a/src/models/scatter_series.ts b/src/models/scatter_series.ts index b173e22..93f6269 100644 --- a/src/models/scatter_series.ts +++ b/src/models/scatter_series.ts @@ -1,6 +1,7 @@ import { CoreSeries } from '@chartwerk/core'; import { ScatterData, PointType, LineType } from '../types'; +import * as _ from 'lodash'; const DEFAULT_POINT_SIZE = 4; @@ -20,4 +21,9 @@ export class ScatterSeries extends CoreSeries { protected get defaults(): ScatterData { return { ...this._coreDefaults, ...SCATTER_DATA_DEFAULTS }; } + + // move to parent + public getSerieByTarget(target: string): ScatterData | undefined { + return _.find(this.visibleSeries, serie => serie.target === target); + } } diff --git a/src/types.ts b/src/types.ts index 22f3db9..8ce8cd2 100644 --- a/src/types.ts +++ b/src/types.ts @@ -26,4 +26,26 @@ export enum LineType { DASHED = 'dashed' } -export type ColorFormatter = (datapoint: number[], pointIdx) => string; +export type ColorFormatter = (datapointsRow: any[], pointIdx) => string; + +export type MouseMoveEvent = { + bbox: { + clientX: number, + clientY: number, + x: number, + y: number, + chartWidth: number, + chartHeight: number, + }, + data: { + xval: number, + yval: number, + highlighted?: HighlightedData, + } +} + +export type HighlightedData = { + xValue: number, yValue: number, customValue: number, + pointIdx: number, totalPointIdx: number, + serieInfo: { target: string, alias?: string, class?: string, idx?: number } +}