Chartwerk Bar 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.
 
 

69 lines
2.5 KiB

import * as d3 from 'd3';
import * as _ from 'lodash';
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<SVGRectElement, unknown, null, undefined>, // overlay from core. It should be global
protected groupContainer: d3.Selection<SVGGElement, unknown, null, undefined>, // 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;
const maxTriagleSize = this.annotationOptions?.max;
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));
}
}