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