Skip to content

Commit 1cfbfc6

Browse files
authored
Enable callout in the label of line annotation (#740)
* Enable callout in the label of line annotation * adds test cases * adds test case with oblique labels and auto rotation * solves CC issue of duplicate code * Add element diagrams to the annotation types guide * revert wrong merging
1 parent 236111e commit 1cfbfc6

File tree

12 files changed

+560
-23
lines changed

12 files changed

+560
-23
lines changed

docs/.vuepress/config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ module.exports = {
162162
'line/limited',
163163
'line/average',
164164
'line/standardDeviation',
165+
'line/callout',
165166
'line/visibility',
166167
'line/labelVisibility',
167168
'line/canvas',

docs/guide/types/line.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ All of these options can be [Scriptable](../options#scriptable-options)
127127
| [`borderRadius`](#borderradius) | `number` \| `object` | `6` | Radius of label box corners in pixels.
128128
| `borderShadowColor` | [`Color`](../options#color) | `'transparent'` | The color of border shadow of the box where the label is located. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/shadowColor).
129129
| `borderWidth` | `number` | `0` | The border line width (in pixels).
130+
| [`callout`](#callout) | `object` | | Can connect the label to the line. See [callout](#callout).
130131
| `color` | [`Color`](../options#color) | `'#fff'` | Text color.
131132
| `content` | `string`\|`string[]`\|[`Image`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/Image)\|[`HTMLCanvasElement`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement) | `null` | The content to show in the label.
132133
| `display` | `boolean` | `false` | Whether or not the label is shown.
@@ -151,6 +152,28 @@ All of these options can be [Scriptable](../options#scriptable-options)
151152

152153
If this value is a number, it is applied to all corners of the rectangle (topLeft, topRight, bottomLeft, bottomRight). If this value is an object, the `topLeft` property defines the top-left corners border radius. Similarly, the `topRight`, `bottomLeft`, and `bottomRight` properties can also be specified. Omitted corners have radius of 0.
153154

155+
### Callout
156+
157+
A callout can connect the label to the line when the label is arbitrarily (by `xAdjust` and `yAdjust` options) moved from its original position.
158+
159+
Namespace: `options.annotations[annotationID].label.callout`, it defines options for the callout on the label of the line annotation.
160+
161+
All of these options can be [Scriptable](../options#scriptable-options).
162+
163+
| Name | Type | Default | Notes
164+
| ---- | ---- | :----: | ----
165+
| `borderCapStyle` | `string` | `'butt'` | Cap style of the border line of callout. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineCap).
166+
| `borderColor` | [`Color`](../options#color) | `undefined` | Stroke color of the pointer of the callout.
167+
| `borderDash` | `number[]` | `[]` | Length and spacing of dashes of callout. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash).
168+
| `borderDashOffset` | `number` | `0` | Offset for line dashes of callout. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineDashOffset).
169+
| `borderJoinStyle` | `string` | `'miter'` | Border line join style of the callout. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineJoin).
170+
| `borderWidth` | `number` | `1` | Stroke width of the pointer of the callout.
171+
| `display` | `boolean` | `false` | If true, the callout is drawn.
172+
| `margin` | `number` | `5` | Amount of pixels between the label and the callout separator.
173+
| `position` | `string` | `'auto'` | The position of callout, with respect to the label. Could be `left`, `top`, `right`, `bottom` or `auto`.
174+
| `side` | `number` | `5` | Width of the starter line of callout pointer.
175+
| `start` | `number`\|`string` | `'50%'` | The percentage of the separator dimension to use as starting point for callout pointer. Could be set in pixel by a number, or in percentage of the separator dimension by a string.
176+
154177
## Arrow heads
155178

156179
Namespace: `options.annotations[annotationID].arrowHeads`, it defines options for the line annotation arrow heads.

docs/samples/line/callout.md

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
# Callout
2+
3+
```js chart-editor
4+
// <block:setup:5>
5+
const DATA_COUNT = 16;
6+
const MIN = 20;
7+
const MAX = 100;
8+
9+
Utils.srand(8);
10+
11+
const labels = [];
12+
for (let i = 0; i < DATA_COUNT; ++i) {
13+
labels.push('' + i);
14+
}
15+
16+
const numberCfg = {count: DATA_COUNT, min: MIN, max: MAX};
17+
18+
const data = {
19+
labels: labels,
20+
datasets: [{
21+
data: Utils.numbers(numberCfg)
22+
}]
23+
};
24+
// </block:setup>
25+
26+
// <block:annotation1:1>
27+
const annotation1 = {
28+
type: 'line',
29+
borderColor: 'rgb(100, 149, 237)',
30+
borderDash: [6, 6],
31+
borderDashOffset: 0,
32+
borderWidth: 3,
33+
label: {
34+
display: true,
35+
backgroundColor: 'rgb(100, 149, 237)',
36+
content: (ctx) => 'Average: ' + average(ctx).toFixed(2)
37+
},
38+
scaleID: 'y',
39+
value: (ctx) => average(ctx)
40+
};
41+
// </block:annotation1>
42+
43+
// <block:annotation2:2>
44+
const annotation2 = {
45+
type: 'line',
46+
borderColor: 'rgba(102, 102, 102, 0.5)',
47+
borderDash: [6, 6],
48+
borderDashOffset: 0,
49+
borderWidth: 3,
50+
label: {
51+
display: true,
52+
backgroundColor: 'rgba(102, 102, 102, 0.5)',
53+
borderWidth: 1,
54+
borderColor: 'rgba(102, 102, 102, 0.5)',
55+
callout: {
56+
display: true,
57+
borderColor: 'rgba(102, 102, 102, 0.5)',
58+
borderDash: [6, 6],
59+
borderWidth: 2,
60+
margin: 0
61+
},
62+
color: 'black',
63+
content: (ctx) => (average(ctx) + standardDeviation(ctx)).toFixed(2),
64+
position: 'start',
65+
xAdjust: 100,
66+
yAdjust: -50
67+
},
68+
scaleID: 'y',
69+
value: (ctx) => average(ctx) + standardDeviation(ctx)
70+
};
71+
// </block:annotation2>
72+
73+
// <block:annotation3:3>
74+
const annotation3 = {
75+
type: 'line',
76+
borderColor: 'rgba(102, 102, 102, 0.5)',
77+
borderDash: [6, 6],
78+
borderDashOffset: 0,
79+
borderWidth: 3,
80+
label: {
81+
display: true,
82+
backgroundColor: 'rgba(102, 102, 102, 0.5)',
83+
borderWidth: 1,
84+
borderColor: 'rgba(102, 102, 102, 0.5)',
85+
callout: {
86+
display: true,
87+
borderColor: 'rgba(102, 102, 102, 0.5)',
88+
borderDash: [6, 6],
89+
borderWidth: 2,
90+
margin: 0
91+
},
92+
color: 'black',
93+
content: (ctx) => (average(ctx) - standardDeviation(ctx)).toFixed(2),
94+
position: 'end',
95+
xAdjust: -100,
96+
yAdjust: 50
97+
},
98+
scaleID: 'y',
99+
value: (ctx) => average(ctx) - standardDeviation(ctx)
100+
};
101+
// </block:annotation3>
102+
103+
/* <block:config:0> */
104+
const config = {
105+
type: 'line',
106+
data,
107+
options: {
108+
scale: {
109+
y: {
110+
beginAtZero: true,
111+
max: 120,
112+
min: 0
113+
}
114+
},
115+
plugins: {
116+
annotation: {
117+
annotations: {
118+
annotation1,
119+
annotation2,
120+
annotation3
121+
}
122+
}
123+
}
124+
}
125+
};
126+
/* </block:config> */
127+
128+
// <block:utils:4>
129+
function average(ctx) {
130+
const values = ctx.chart.data.datasets[0].data;
131+
return values.reduce((a, b) => a + b, 0) / values.length;
132+
}
133+
134+
function standardDeviation(ctx) {
135+
const values = ctx.chart.data.datasets[0].data;
136+
const n = values.length;
137+
const mean = average(ctx);
138+
return Math.sqrt(values.map(x => Math.pow(x - mean, 2)).reduce((a, b) => a + b) / n);
139+
}
140+
141+
// </block:utils>
142+
143+
const actions = [
144+
{
145+
name: 'Randomize',
146+
handler: function(chart) {
147+
chart.data.datasets.forEach(function(dataset, i) {
148+
dataset.data = dataset.data.map(() => Utils.rand(MIN, MAX));
149+
});
150+
chart.update();
151+
}
152+
},
153+
{
154+
name: 'Add data',
155+
handler: function(chart) {
156+
chart.data.labels.push(chart.data.labels.length);
157+
chart.data.datasets.forEach(function(dataset, i) {
158+
dataset.data.push(Utils.rand(MIN, MAX));
159+
});
160+
chart.update();
161+
}
162+
},
163+
{
164+
name: 'Remove data',
165+
handler: function(chart) {
166+
chart.data.labels.shift();
167+
chart.data.datasets.forEach(function(dataset, i) {
168+
dataset.data.shift();
169+
});
170+
chart.update();
171+
}
172+
}
173+
];
174+
175+
module.exports = {
176+
actions: actions,
177+
config: config,
178+
};
179+
```

src/types/line.js

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {Element} from 'chart.js';
22
import {PI, toRadians, toDegrees, toPadding} from 'chart.js/helpers';
33
import {EPSILON, clamp, scaleValue, measureLabelSize, getRelativePosition, setBorderStyle, setShadowStyle, getElementCenterPoint, retrieveScaleID, getDimensionByScale} from '../helpers';
4+
import LabelAnnotation from './label';
45

56
const pointInLine = (p1, p2, t) => ({x: p1.x + t * (p2.x - p1.x), y: p1.y + t * (p2.y - p1.y)});
67
const interpolateX = (y, p1, p2) => pointInLine(p1, p2, Math.abs((y - p1.y) / (p2.y - p1.y))).x;
@@ -148,9 +149,7 @@ LineAnnotation.defaults = {
148149
borderRadius: 6,
149150
borderShadowColor: 'transparent',
150151
borderWidth: 0,
151-
callout: {
152-
display: false
153-
},
152+
callout: Object.assign({}, LabelAnnotation.defaults.callout),
154153
color: '#fff',
155154
content: null,
156155
display: false,
@@ -275,8 +274,6 @@ function applyScaleValueToDimension(area, scale, options) {
275274
}
276275

277276
function resolveLabelElementProperties(chart, properties, options) {
278-
// TODO to remove by another PR to enable callout for line label
279-
options.callout.display = false;
280277
const borderWidth = options.borderWidth;
281278
const padding = toPadding(options.padding);
282279
const textSize = measureLabelSize(chart.ctx, options);
@@ -312,6 +309,8 @@ function calculateLabelPosition(properties, label, sizes, chartArea) {
312309
y2: centerY + (height / 2),
313310
centerX,
314311
centerY,
312+
pointX: pt.x,
313+
pointY: pt.y,
315314
width,
316315
height,
317316
rotation: toDegrees(rotation)

test/fixtures/line/labelCallout.js

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
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+
line1: {
21+
type: 'line',
22+
scaleID: 'y',
23+
value: 8,
24+
borderColor: 'black',
25+
borderWidth: 5,
26+
label: {
27+
display: true,
28+
backgroundColor: '#f5f5f5',
29+
borderColor: 'black',
30+
borderRadius: 0,
31+
borderWidth: 1,
32+
content: 'yAdjust: -40, callout position: auto',
33+
yAdjust: -40,
34+
callout: {
35+
display: true
36+
}
37+
}
38+
},
39+
line2: {
40+
type: 'line',
41+
scaleID: 'y',
42+
value: 6,
43+
borderColor: 'black',
44+
borderWidth: 5,
45+
label: {
46+
display: true,
47+
backgroundColor: '#f5f5f5',
48+
borderColor: 'black',
49+
borderRadius: 0,
50+
borderWidth: 1,
51+
content: ['yAdjust: -40, xAdjust: -150', 'callout position: auto'],
52+
yAdjust: -40,
53+
xAdjust: -150,
54+
callout: {
55+
display: true
56+
}
57+
}
58+
},
59+
line3: {
60+
type: 'line',
61+
scaleID: 'y',
62+
value: 4,
63+
borderColor: 'black',
64+
borderWidth: 5,
65+
label: {
66+
display: true,
67+
backgroundColor: '#f5f5f5',
68+
borderColor: 'black',
69+
borderRadius: 0,
70+
borderWidth: 1,
71+
content: ['yAdjust: 40, xAdjust: 150', 'callout position: auto'],
72+
yAdjust: 40,
73+
xAdjust: 150,
74+
callout: {
75+
display: true
76+
}
77+
}
78+
},
79+
line4: {
80+
type: 'line',
81+
scaleID: 'y',
82+
value: 2,
83+
borderColor: 'black',
84+
borderWidth: 5,
85+
label: {
86+
display: true,
87+
backgroundColor: '#f5f5f5',
88+
borderColor: 'black',
89+
borderRadius: 0,
90+
borderWidth: 1,
91+
content: 'yAdjust: 40, callout position: auto',
92+
yAdjust: 40,
93+
callout: {
94+
display: true
95+
}
96+
}
97+
}
98+
}
99+
}
100+
}
101+
}
102+
},
103+
options: {
104+
spriteText: true
105+
}
106+
};
19.3 KB
Loading

0 commit comments

Comments
 (0)