From 135a286bf302e839f9046ad21fc39a28e664a157 Mon Sep 17 00:00:00 2001 From: vargburz Date: Fri, 16 Sep 2022 17:25:38 +0300 Subject: [PATCH] move bar annotations to a separate model --- src/index.ts | 54 +++++---------------------- src/models/bar_annotation.ts | 71 ++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 45 deletions(-) create mode 100644 src/models/bar_annotation.ts diff --git a/src/index.ts b/src/index.ts index 67cbe0a..7a75018 100755 --- a/src/index.ts +++ b/src/index.ts @@ -2,6 +2,7 @@ import { ChartwerkPod, VueChartwerkPodMixin, TimeFormat, AxisFormat } from '@cha import { BarConfig } from './models/bar_options'; import { BarSeries } from './models/bar_series'; +import { BarAnnotation } from './models/bar_annotation'; import { BarSerie, BarOptions, RowValues } from './types'; import { findClosest } from './utils'; @@ -88,27 +89,17 @@ export class ChartwerkBarPod extends ChartwerkPod { const series = _.filter(this.series.visibleSeries, serie => _.includes(d.serieTarget, serie.target)); const matchedKeys = _.map(series, serie => serie.matchedKey); // here matchedKeys should be equal const key = matchedKeys[0]; - - const lastRect = _.last(container.selectAll('rect')?.nodes()); const annotation = _.find(this.options.barOptions.annotations, a => a.key === key); - if(!lastRect || !key || !annotation) { + if(!key || !annotation) { return; } - - const rectSelection = d3.select(lastRect); - // render triangle - container.append('path') - .attr('d', () => { - const x = Math.ceil(_.toNumber(rectSelection.attr('x'))); - const y = Math.ceil(_.toNumber(rectSelection.attr('y'))); - const options = { max: this.options.barOptions.maxAnnotationSize, min: this.options.barOptions.minAnnotationSize }; - return this.getTrianglePath(x, y, this.barWidth, options); - }) - .attr('fill', annotation.color) - .on('mouseover', this.redirectEventToOverlay.bind(this)) - .on('mousemove', this.redirectEventToOverlay.bind(this)) - .on('mouseout', this.redirectEventToOverlay.bind(this)) - .on('mousedown', () => { d3.event.stopPropagation(); }); + const annotationOptions = { + size: this.barWidth, + max: this.options.barOptions.maxAnnotationSize, + min: this.options.barOptions.minAnnotationSize, + color: annotation.color, + }; + new BarAnnotation(this.overlay, container, annotationOptions); }); } @@ -116,33 +107,6 @@ export class ChartwerkBarPod extends ChartwerkPod { this.overlay?.node().dispatchEvent(new MouseEvent(d3.event.type, d3.event)); } - getTrianglePath(x: number, y: number, length: number, options?: { max: number, min: number }): string { - // (x, y) - top left corner of bar - const minTriangleSize = options?.min || 6; - const maxTriagleSize = options?.max || 10; - const yOffset = 4; // offset between triangle and bar - const centerX = x + length / 2; - const correctedLength = _.clamp(length, minTriangleSize, maxTriagleSize); - - const topY = Math.max(y - correctedLength - yOffset, 4); - const topLeftCorner = { - x: centerX - correctedLength / 2, - y: topY, - }; - const topRightCorner = { - x: centerX + correctedLength / 2, - y: topY, - }; - const bottomMiddleCorner = { - x: centerX, - y: topY + correctedLength, - }; - - return `M ${topLeftCorner.x} ${topLeftCorner.y} - L ${topRightCorner.x} ${topRightCorner.y} - L ${bottomMiddleCorner.x} ${bottomMiddleCorner.y} z`; - } - getBarOpacity(rowValues: RowValues): number { if(this.options.barOptions.opacityFormatter === undefined) { return 1; diff --git a/src/models/bar_annotation.ts b/src/models/bar_annotation.ts new file mode 100644 index 0000000..c33d317 --- /dev/null +++ b/src/models/bar_annotation.ts @@ -0,0 +1,71 @@ +import * as d3 from 'd3'; +import * as _ from 'lodash'; + + +const DEFAULT_ANNOTATION_MIN_SIZE = 6; //px +const DEFAULT_ANNOTATION_MAX_SIZE = 10; //px +const DEFAULT_ANNOTATION_OFFSET_Y = 4; //offset between triangle and bar in px +const DEFAULT_ANNOTATION_COLOR = 'red'; + +export class BarAnnotation { + position: { x: number, y: number }; + + constructor( + protected overlay: d3.Selection, // overlay from core. It should be global + protected groupContainer: d3.Selection, // group - bars as one item + protected annotationOptions: { size: number, max?: number, min?: number, offset?: number, color?: string }, + ) { + this.position = this.getGroupLastRectPosition(); + this.renderAnnotation(); + } + + protected renderAnnotation(): void { + this.groupContainer.append('path') + .attr('d', () => this.getTrianglePath()) + .attr('fill', this.annotationOptions.color || DEFAULT_ANNOTATION_COLOR) + .on('mouseover', this.redirectEventToOverlay.bind(this)) + .on('mousemove', this.redirectEventToOverlay.bind(this)) + .on('mouseout', this.redirectEventToOverlay.bind(this)) + .on('mousedown', () => { d3.event.stopPropagation(); }); + } + + getGroupLastRectPosition(): { x: number, y: number } { + const lastRect = _.last(this.groupContainer.selectAll('rect')?.nodes()); + const barSelection = d3.select(lastRect); + return { + x: Math.ceil(_.toNumber(barSelection.attr('x'))), + y: Math.ceil(_.toNumber(barSelection.attr('y'))), + } + } + + protected getTrianglePath(): string { + // (x, y) - top left corner of bar + const minTriangleSize = this.annotationOptions?.min || DEFAULT_ANNOTATION_MIN_SIZE; + const maxTriagleSize = this.annotationOptions?.max || DEFAULT_ANNOTATION_MAX_SIZE; + const yOffset = this.annotationOptions?.offset || DEFAULT_ANNOTATION_OFFSET_Y; + const centerX = this.position.x + this.annotationOptions.size / 2; + const correctedLength = _.clamp(this.annotationOptions.size, minTriangleSize, maxTriagleSize); + + const topY = Math.max(this.position.y - correctedLength - yOffset, 4); + const topLeftCorner = { + x: centerX - correctedLength / 2, + y: topY, + }; + const topRightCorner = { + x: centerX + correctedLength / 2, + y: topY, + }; + const bottomMiddleCorner = { + x: centerX, + y: topY + correctedLength, + }; + + return `M ${topLeftCorner.x} ${topLeftCorner.y} + L ${topRightCorner.x} ${topRightCorner.y} + L ${bottomMiddleCorner.x} ${bottomMiddleCorner.y} z`; + } + + redirectEventToOverlay(): void { + this.overlay?.node().dispatchEvent(new MouseEvent(d3.event.type, d3.event)); + } +}