Skip to content

Commit d6beb96

Browse files
kurkleJukka Kurkela
andauthored
Fix dynamically showing/hiding line label (#595)
* Fix dynamically showing/hiding line label * chore * add test for box * remove borderdash (drawn differently by ff) * inconsistent inRange * Empty label behavior * Revert "Empty label behavior" This reverts commit 32c29c9. * Hide empty labels * cc Co-authored-by: Jukka Kurkela <[email protected]>
1 parent 22aa092 commit d6beb96

File tree

13 files changed

+239
-87
lines changed

13 files changed

+239
-87
lines changed

src/annotation.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,11 @@ function draw(chart, caller, clip) {
230230
}
231231

232232
state.visibleElements.forEach(el => {
233-
if ('drawLabel' in el && el.options.label && (el.options.label.drawTime || el.options.drawTime) === caller) {
233+
if (!('drawLabel' in el)) {
234+
return;
235+
}
236+
const label = el.options.label;
237+
if (label && label.enabled && label.content && (label.drawTime || el.options.drawTime) === caller) {
234238
el.drawLabel(ctx, chartArea);
235239
}
236240
});

src/helpers/helpers.canvas.js

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import {addRoundedRectPath, drawPoint as drawPointOnChart, isArray, toFont, toTRBLCorners, valueOrDefault} from 'chart.js/helpers';
1+
import {addRoundedRectPath, isArray, toFont, toTRBLCorners, valueOrDefault} from 'chart.js/helpers';
22
import {clampAll} from './helpers.core';
3-
import {calculateTextAlignment, getSize, isLabelVisible, isPointVisible} from './helpers.options';
3+
import {calculateTextAlignment, getSize} from './helpers.options';
44

55
const widthCache = new Map();
66

@@ -59,18 +59,6 @@ export function measureLabelSize(ctx, options) {
5959
return widthCache.get(mapKey);
6060
}
6161

62-
export function drawPoint(ctx, point, options) {
63-
if (!isPointVisible(options)) {
64-
return;
65-
}
66-
ctx.save();
67-
ctx.fillStyle = options.backgroundColor;
68-
setBorderStyle(ctx, options);
69-
drawPointOnChart(ctx, options, point.x, point.y);
70-
ctx.restore();
71-
}
72-
73-
7462
/**
7563
* Draw a box with the size and the styling options.
7664
* @param {CanvasRenderingContext2D} ctx - chart canvas context
@@ -98,9 +86,6 @@ export function drawBox(ctx, rect, options) {
9886
}
9987

10088
export function drawLabel(ctx, rect, options) {
101-
if (!isLabelVisible(options)) {
102-
return;
103-
}
10489
const content = options.content;
10590
if (isImageOrCanvas(content)) {
10691
ctx.drawImage(content, rect.x, rect.y, rect.width, rect.height);

src/helpers/helpers.options.js

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import {isObject, valueOrDefault, defined} from 'chart.js/helpers';
22
import {clamp} from './helpers.core';
33

4-
const isEnabled = (options) => options && (options.display || options.enabled);
54
const isPercentString = (s) => typeof s === 'string' && s.endsWith('%');
65
const toPercent = (s) => clamp(parseFloat(s) / 100, 0, 1);
76

@@ -27,14 +26,6 @@ export function getSize(size, value) {
2726
return size;
2827
}
2928

30-
export function isLabelVisible(options) {
31-
return isEnabled(options) && options.content;
32-
}
33-
34-
export function isPointVisible(options) {
35-
return isEnabled(options) && options.radius > 0.1;
36-
}
37-
3829
export function calculateTextAlignment(size, options) {
3930
const {x, width} = size;
4031
const textAlign = options.textAlign;

src/types/box.js

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {Element} from 'chart.js';
22
import {toPadding} from 'chart.js/helpers';
3-
import {drawBox, drawLabel, getRelativePosition, measureLabelSize, isLabelVisible, getRectCenterPoint, getChartRect, toPosition, inBoxRange} from '../helpers';
3+
import {drawBox, drawLabel, getRelativePosition, measureLabelSize, getRectCenterPoint, getChartRect, toPosition, inBoxRange} from '../helpers';
44

55
export default class BoxAnnotation extends Element {
66
inRange(mouseX, mouseY, useFinalPosition) {
@@ -20,25 +20,22 @@ export default class BoxAnnotation extends Element {
2020
drawLabel(ctx) {
2121
const {x, y, width, height, options} = this;
2222
const labelOpts = options.label;
23-
if (isLabelVisible(labelOpts)) {
24-
// copies borderWidth to label options
25-
labelOpts.borderWidth = options.borderWidth;
26-
ctx.save();
27-
ctx.beginPath();
28-
ctx.rect(x + labelOpts.borderWidth / 2, y + labelOpts.borderWidth / 2, width - labelOpts.borderWidth, height - labelOpts.borderWidth);
29-
ctx.clip();
30-
const position = toPosition(labelOpts.position);
31-
const padding = toPadding(labelOpts.padding);
32-
const labelSize = measureLabelSize(ctx, labelOpts);
33-
const labelRect = {
34-
x: calculateX(this, labelSize, position, padding),
35-
y: calculateY(this, labelSize, position, padding),
36-
width: labelSize.width,
37-
height: labelSize.height
38-
};
39-
drawLabel(ctx, labelRect, labelOpts);
40-
ctx.restore();
41-
}
23+
labelOpts.borderWidth = options.borderWidth;
24+
ctx.save();
25+
ctx.beginPath();
26+
ctx.rect(x + labelOpts.borderWidth / 2, y + labelOpts.borderWidth / 2, width - labelOpts.borderWidth, height - labelOpts.borderWidth);
27+
ctx.clip();
28+
const position = toPosition(labelOpts.position);
29+
const padding = toPadding(labelOpts.padding);
30+
const labelSize = measureLabelSize(ctx, labelOpts);
31+
const labelRect = {
32+
x: calculateX(this, labelSize, position, padding),
33+
y: calculateY(this, labelSize, position, padding),
34+
width: labelSize.width,
35+
height: labelSize.height
36+
};
37+
drawLabel(ctx, labelRect, labelOpts);
38+
ctx.restore();
4239
}
4340

4441
resolveElementProperties(chart, options) {

src/types/label.js

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
1-
import {drawBox, drawLabel, measureLabelSize, isLabelVisible, getChartPoint, getRectCenterPoint, toPosition, setBorderStyle, getSize, inBoxRange, isBoundToPoint, getChartRect, getRelativePosition} from '../helpers';
1+
import {drawBox, drawLabel, measureLabelSize, getChartPoint, getRectCenterPoint, toPosition, setBorderStyle, getSize, inBoxRange, isBoundToPoint, getChartRect, getRelativePosition} from '../helpers';
22
import {color, toPadding} from 'chart.js/helpers';
33
import {Element} from 'chart.js';
44

55
export default class LabelAnnotation extends Element {
66

77
inRange(mouseX, mouseY, useFinalPosition) {
8-
return this.visible && inBoxRange(mouseX, mouseY, this.getProps(['x', 'y', 'width', 'height'], useFinalPosition));
8+
return inBoxRange(mouseX, mouseY, this.getProps(['x', 'y', 'width', 'height'], useFinalPosition));
99
}
1010

1111
getCenterPoint(useFinalPosition) {
1212
return getRectCenterPoint(this.getProps(['x', 'y', 'width', 'height'], useFinalPosition));
1313
}
1414

1515
draw(ctx) {
16-
if (!this.visible) {
16+
if (!this.options.content) {
1717
return;
1818
}
1919
const {labelX, labelY, labelWidth, labelHeight, options} = this;
@@ -24,8 +24,8 @@ export default class LabelAnnotation extends Element {
2424
drawLabel(ctx, {x: labelX, y: labelY, width: labelWidth, height: labelHeight}, options);
2525
}
2626

27+
// TODO: make private in v2
2728
resolveElementProperties(chart, options) {
28-
const visible = !!isLabelVisible(options);
2929
const point = !isBoundToPoint(options) ? getRectCenterPoint(getChartRect(chart, options)) : getChartPoint(chart, options);
3030
const padding = toPadding(options.padding);
3131
const labelSize = measureLabelSize(chart.ctx, options);
@@ -34,7 +34,6 @@ export default class LabelAnnotation extends Element {
3434
const boxVisible = options.borderWidth > 0 || (bgColor && bgColor.valid && bgColor.rgb.a > 0);
3535

3636
const properties = {
37-
visible,
3837
boxVisible,
3938
pointX: point.x,
4039
pointY: point.y,

src/types/line.js

Lines changed: 32 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {Element} from 'chart.js';
22
import {toRadians, toPadding} from 'chart.js/helpers';
3-
import {clamp, scaleValue, rotated, drawBox, drawLabel, measureLabelSize, isLabelVisible, getRelativePosition} from '../helpers';
3+
import {clamp, scaleValue, rotated, drawBox, drawLabel, measureLabelSize, getRelativePosition} from '../helpers';
44

55
const PI = Math.PI;
66
const pointInLine = (p1, p2, t) => ({x: p1.x + t * (p2.x - p1.x), y: p1.y + t * (p2.y - p1.y)});
@@ -65,13 +65,16 @@ export default class LineAnnotation extends Element {
6565
return (sqr(x - xx) + sqr(y - yy)) < epsilon;
6666
}
6767

68+
// TODO: make private in v2
6869
labelIsVisible(useFinalPosition, chartArea) {
69-
if (!this.labelVisible) {
70+
const labelOpts = this.options.label;
71+
if (!chartArea || !labelOpts || !labelOpts.enabled) {
7072
return false;
7173
}
72-
return !chartArea || isLineInArea(this.getProps(['x', 'y', 'x2', 'y2'], useFinalPosition), chartArea);
74+
return isLineInArea(this.getProps(['x', 'y', 'x2', 'y2'], useFinalPosition), chartArea);
7375
}
7476

77+
// TODO: make private in v2
7578
isOnLabel(mouseX, mouseY, useFinalPosition) {
7679
if (!this.labelIsVisible(useFinalPosition)) {
7780
return false;
@@ -115,11 +118,31 @@ export default class LineAnnotation extends Element {
115118
}
116119

117120
drawLabel(ctx, chartArea) {
118-
if (this.labelIsVisible(false, chartArea)) {
119-
ctx.save();
120-
applyLabel(ctx, this);
121-
ctx.restore();
121+
if (!this.labelIsVisible(false, chartArea)) {
122+
return;
122123
}
124+
const {labelX, labelY, labelWidth, labelHeight, labelRotation, labelPadding, labelTextSize, options: {label}} = this;
125+
126+
ctx.save();
127+
ctx.translate(labelX, labelY);
128+
ctx.rotate(labelRotation);
129+
130+
const boxRect = {
131+
x: -(labelWidth / 2),
132+
y: -(labelHeight / 2),
133+
width: labelWidth,
134+
height: labelHeight
135+
};
136+
drawBox(ctx, boxRect, label);
137+
138+
const labelTextRect = {
139+
x: -(labelWidth / 2) + labelPadding.left + label.borderWidth / 2,
140+
y: -(labelHeight / 2) + labelPadding.top + label.borderWidth / 2,
141+
width: labelTextSize.width,
142+
height: labelTextSize.height
143+
};
144+
drawLabel(ctx, labelTextRect, label);
145+
ctx.restore();
123146
}
124147

125148
resolveElementProperties(chart, options) {
@@ -155,9 +178,9 @@ export default class LineAnnotation extends Element {
155178
const properties = inside
156179
? limitLineToArea({x, y}, {x: x2, y: y2}, chart.chartArea)
157180
: {x, y, x2, y2, width: Math.abs(x2 - x), height: Math.abs(y2 - y)};
181+
158182
const label = options.label;
159-
properties.labelVisible = !!isLabelVisible(label);
160-
if (properties.labelVisible) {
183+
if (label && label.content) {
161184
return loadLabelRect(properties, chart, label);
162185
}
163186
return properties;
@@ -243,30 +266,6 @@ function calculateAutoRotation(line) {
243266
return rotation > PI / 2 ? rotation - PI : rotation < PI / -2 ? rotation + PI : rotation;
244267
}
245268

246-
function applyLabel(ctx, line) {
247-
const {labelX, labelY, labelWidth, labelHeight, labelRotation, labelPadding, labelTextSize, options} = line;
248-
const label = options.label;
249-
250-
ctx.translate(labelX, labelY);
251-
ctx.rotate(labelRotation);
252-
253-
const boxRect = {
254-
x: -(labelWidth / 2),
255-
y: -(labelHeight / 2),
256-
width: labelWidth,
257-
height: labelHeight
258-
};
259-
drawBox(ctx, boxRect, label);
260-
261-
const labelTextRect = {
262-
x: -(labelWidth / 2) + labelPadding.left + label.borderWidth / 2,
263-
y: -(labelHeight / 2) + labelPadding.top + label.borderWidth / 2,
264-
width: labelTextSize.width,
265-
height: labelTextSize.height
266-
};
267-
drawLabel(ctx, labelTextRect, label);
268-
}
269-
270269
// TODO: v2 remove support for xPadding and yPadding
271270
function getPadding(padding, xPadding, yPadding) {
272271
let tempPadding = padding;

src/types/point.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {Element} from 'chart.js';
2-
import {drawPoint, inPointRange, getElementCenterPoint, resolvePointPosition} from '../helpers';
2+
import {drawPoint} from 'chart.js/helpers';
3+
import {inPointRange, getElementCenterPoint, resolvePointPosition, setBorderStyle} from '../helpers';
34

45
export default class PointAnnotation extends Element {
56

@@ -13,7 +14,12 @@ export default class PointAnnotation extends Element {
1314
}
1415

1516
draw(ctx) {
16-
drawPoint(ctx, this, this.options);
17+
const options = this.options;
18+
ctx.save();
19+
ctx.fillStyle = options.backgroundColor;
20+
setBorderStyle(ctx, options);
21+
drawPoint(ctx, options, this.x, this.y);
22+
ctx.restore();
1723
}
1824

1925
resolveElementProperties(chart, options) {

test/fixtures/box/label-dynamic.js

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
module.exports = {
2+
config: {
3+
type: 'scatter',
4+
options: {
5+
scales: {
6+
x: {
7+
display: false,
8+
min: 0,
9+
max: 10
10+
},
11+
y: {
12+
display: false,
13+
min: 0,
14+
max: 10
15+
}
16+
},
17+
plugins: {
18+
annotation: {
19+
annotations: {
20+
box: {
21+
type: 'box',
22+
xScaleID: 'x',
23+
yScaleID: 'y',
24+
xMin: 1,
25+
xMax: 9,
26+
yMin: 1,
27+
yMax: 9,
28+
backgroundColor: 'rgba(255, 99, 132, 0.5)',
29+
borderColor: 'rgba(255, 99, 132)',
30+
borderWidth: 5,
31+
label: {
32+
enabled: false,
33+
content: 'This is dynamic!',
34+
},
35+
enter({chart, element}) {
36+
element.options.label.enabled = true;
37+
chart.draw();
38+
}
39+
},
40+
}
41+
}
42+
}
43+
}
44+
},
45+
options: {
46+
canvas: {
47+
width: 256,
48+
height: 256
49+
},
50+
spriteText: true,
51+
async run(chart) {
52+
const el = window['chartjs-plugin-annotation']._getState(chart).elements[0];
53+
await window.triggerMouseEvent(chart, 'mousemove', el.getCenterPoint());
54+
}
55+
}
56+
};
1.48 KB
Loading

0 commit comments

Comments
 (0)