Skip to content

Commit 25a1958

Browse files
authored
Hide line-annotations completely outside chartArea (#458)
1 parent 11b7f48 commit 25a1958

File tree

3 files changed

+152
-8
lines changed

3 files changed

+152
-8
lines changed

docs/.vuepress/config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ module.exports = {
6969
collapsable: false,
7070
children: [
7171
'charts/bar',
72+
'charts/line',
7273
],
7374
},
7475
]

docs/samples/charts/line.md

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# Line Chart
2+
3+
```js chart-editor
4+
// <block:setup:5>
5+
const DATA_COUNT = 8;
6+
const MIN = 10;
7+
const MAX = 100;
8+
9+
Utils.srand(8);
10+
11+
12+
const numberCfg = {count: DATA_COUNT, min: MIN, max: MAX};
13+
14+
const data = {
15+
labels: [10, 20, 30, 40, 50, 60, 70, 80],
16+
datasets: [{
17+
data: Utils.numbers(numberCfg),
18+
}, {
19+
data: Utils.numbers(numberCfg),
20+
}, {
21+
data: Utils.numbers(numberCfg),
22+
}]
23+
};
24+
// </block:setup>
25+
26+
// <block:annotation1:1>
27+
const annotation1 = {
28+
type: 'line',
29+
scaleID: 'x',
30+
borderWidth: 3,
31+
borderColor: 'black',
32+
value: 5,
33+
label: {
34+
rotation: 'auto',
35+
content: 'Line at x=5',
36+
enabled: true
37+
},
38+
};
39+
// </block:annotation1>
40+
41+
// <block:annotation2:2>
42+
const annotation2 = {
43+
type: 'line',
44+
scaleID: 'x',
45+
borderWidth: 3,
46+
borderColor: 'black',
47+
value: 90,
48+
label: {
49+
rotation: 'auto',
50+
position: 'start',
51+
backgroundColor: 'black',
52+
content: 'Line at x=90',
53+
enabled: true
54+
}
55+
};
56+
// </block:annotation2>
57+
58+
// <block:annotation3:3>
59+
const annotation3 = {
60+
type: 'box',
61+
xMin: 75,
62+
xMax: 85,
63+
yMin: 80,
64+
yMax: 90,
65+
backgroundColor: 'rgba(250,250,0,0.4)',
66+
borderColor: 'rgba(0,150,0,0.2)',
67+
drawTime: 'beforeDatasetsDraw',
68+
borderWidth: 0,
69+
cornerRadius: 0,
70+
};
71+
// </block:annotation3>
72+
73+
74+
/* <block:config:0> */
75+
const config = {
76+
type: 'line',
77+
data,
78+
options: {
79+
scales: {
80+
x: {
81+
type: 'linear',
82+
bounds: 'data'
83+
}
84+
},
85+
plugins: {
86+
annotation: {
87+
annotations: {
88+
annotation1,
89+
annotation2,
90+
annotation3
91+
}
92+
}
93+
},
94+
}
95+
};
96+
/* </block:config> */
97+
98+
var actions = [
99+
{
100+
name: 'Zoom out',
101+
handler: function(chart) {
102+
chart.scales.x.options.min = 0;
103+
chart.scales.x.options.max = 100;
104+
chart.update();
105+
}
106+
},
107+
{
108+
name: 'Zoom in',
109+
handler: function(chart) {
110+
chart.scales.x.options.min = 10;
111+
chart.scales.x.options.max = 80;
112+
chart.update();
113+
}
114+
},
115+
{
116+
name: 'Reset zoom',
117+
handler: function(chart) {
118+
chart.scales.x.options.min = undefined;
119+
chart.scales.x.options.max = undefined;
120+
chart.update();
121+
}
122+
}
123+
];
124+
125+
module.exports = {
126+
actions: actions,
127+
config: config,
128+
};
129+
```

src/types/line.js

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,30 @@ const interpolateX = (y, p1, p2) => pointInLine(p1, p2, Math.abs((y - p1.y) / (p
99
const interpolateY = (x, p1, p2) => pointInLine(p1, p2, Math.abs((x - p1.x) / (p2.x - p1.x))).y;
1010
const toPercent = (s) => typeof s === 'string' && s.endsWith('%') && parseFloat(s) / 100;
1111

12+
function isLineInArea({x, y, x2, y2}, {top, right, bottom, left}) {
13+
return !(
14+
(x < left && x2 < left) ||
15+
(x > right && x2 > right) ||
16+
(y < top && y2 < top) ||
17+
(y > bottom && y2 > bottom)
18+
);
19+
}
20+
1221
function limitPointToArea({x, y}, p2, {top, right, bottom, left}) {
1322
if (x < left) {
14-
y = p2.x < left ? NaN : interpolateY(left, {x, y}, p2);
23+
y = interpolateY(left, {x, y}, p2);
1524
x = left;
1625
}
1726
if (x > right) {
18-
y = p2.x > right ? NaN : interpolateY(right, {x, y}, p2);
27+
y = interpolateY(right, {x, y}, p2);
1928
x = right;
2029
}
2130
if (y < top) {
22-
x = p2.y < top ? NaN : interpolateX(top, {x, y}, p2);
31+
x = interpolateX(top, {x, y}, p2);
2332
y = top;
2433
}
2534
if (y > bottom) {
26-
x = p2.y > bottom ? NaN : interpolateX(bottom, {x, y}, p2);
35+
x = interpolateX(bottom, {x, y}, p2);
2736
y = bottom;
2837
}
2938
return {x, y};
@@ -58,9 +67,11 @@ export default class LineAnnotation extends Element {
5867
return (sqr(x - xx) + sqr(y - yy)) < epsilon;
5968
}
6069

61-
labelIsVisible() {
70+
labelIsVisible(chartArea) {
6271
const label = this.options.label;
63-
return label && label.enabled && label.content;
72+
73+
const inside = !chartArea || isLineInArea(this, chartArea);
74+
return inside && label && label.enabled && label.content;
6475
}
6576

6677
isOnLabel(mouseX, mouseY) {
@@ -107,7 +118,7 @@ export default class LineAnnotation extends Element {
107118
}
108119

109120
drawLabel(ctx, chartArea) {
110-
if (this.labelIsVisible()) {
121+
if (this.labelIsVisible(chartArea)) {
111122
ctx.save();
112123
drawLabel(ctx, this, chartArea);
113124
ctx.restore();
@@ -143,7 +154,10 @@ export default class LineAnnotation extends Element {
143154
y2 = scaleValue(yScale, options.yMax, y2);
144155
}
145156
}
146-
return limitLineToArea({x, y}, {x: x2, y: y2}, chart.chartArea);
157+
const inside = isLineInArea({x, y, x2, y2}, chart.chartArea);
158+
return inside
159+
? limitLineToArea({x, y}, {x: x2, y: y2}, chart.chartArea)
160+
: {x, y, x2, y2, width: Math.abs(x2 - x), height: Math.abs(y2 - y)};
147161
}
148162
}
149163

0 commit comments

Comments
 (0)