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