Browse Source

move bar annotations to a separate model

pull/1/head
vargburz 2 years ago
parent
commit
135a286bf3
  1. 54
      src/index.ts
  2. 71
      src/models/bar_annotation.ts

54
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<BarSerie, BarOptions> {
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<BarSerie, BarOptions> {
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;

71
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<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…
Cancel
Save