vargburz
2 years ago
2 changed files with 80 additions and 45 deletions
@ -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<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 || 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)); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue