Skip to content

Commit f85f494

Browse files
authored
Enable label on ellipse annotation (#749)
* Enable label on ellipse annotation * adds label reference in ellipse element * adds test cases * adds types * adds documentation * adds samples * Add element diagrams to the annotation types guide * fixes lint * fixes CC similar code * back to similar code
1 parent 1cfbfc6 commit f85f494

28 files changed

+1158
-101
lines changed

docs/.vuepress/config.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,8 @@ module.exports = {
140140
children: [
141141
'ellipse/basic',
142142
'ellipse/rotation',
143+
'ellipse/label',
144+
'ellipse/image',
143145
]
144146
},
145147
{
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
## Label
2+
3+
Namespace: `options.annotations[annotationID].label`, it defines options for the the label of annotation.
4+
5+
All of these options can be [Scriptable](../options#scriptable-options)
6+
7+
| Name | Type | Default | Notes
8+
| ---- | ---- | :----: | ----
9+
| `color` | [`Color`](../options#color) | `'black'` | Text color.
10+
| `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.
11+
| `display` | `boolean` | `false` | Whether or not the label is shown.
12+
| `drawTime` | `string` | `options.drawTime` | See [drawTime](../options#draw-time). Defaults to the annotation draw time if unset
13+
| `font` | [`Font`](../options#font) | `{ weight: 'bold' }` | Label font
14+
| `height` | `number`\|`string` | `undefined` | Overrides the height of the image or canvas element. Could be set in pixel by a number, or in percentage of current height of image or canvas element by a string. If undefined, uses the height of the image or canvas element. It is used only when the content is an image or canvas element.
15+
| `padding` | [`Padding`](../options#padding) | `6` | The padding to add around the text label.
16+
| [`position`](#position) | `string`\|`{x: string, y: string}` | `'center'` | Anchor position of label in the annotation.
17+
| `rotation` | `number` | `undefined` | Rotation of label, in degrees. If `undefined`, the annotation rotation is used.
18+
| `textAlign` | `string` | `'start'` | Text alignment of label content when there's more than one line. Possible options are: `'left'`, `'start'`, `'center'`, `'end'`, `'right'`.
19+
| `textStrokeColor` | [`Color`](../options#color) | `undefined` | The color of the stroke around the text.
20+
| `textStrokeWidth` | `number` | `0` | Stroke width around the text.
21+
| `width` | `number`\|`string` | `undefined` | Overrides the width of the image or canvas element. Could be set in pixel by a number, or in percentage of current width of image or canvas element by a string. If undefined, uses the width of the image or canvas element. It is used only when the content is an image or canvas element.
22+
| `xAdjust` | `number` | `0` | Adjustment along x-axis (left-right) of label relative to computed position. Negative values move the label left, positive right.
23+
| `yAdjust` | `number` | `0` | Adjustment along y-axis (top-bottom) of label relative to computed position. Negative values move the label up, positive down.
24+
| `z` | `number` | `0` | It determines the drawing stack level of the label element, with same `drawTime`.
25+
26+
### Position
27+
28+
A position can be set in 2 different values types:
29+
30+
1. `'start'`, `'center'`, `'end'` which are defining where the label will be located
31+
2. a `string`, in percentage format `'number%'`, is representing the percentage on the size where the label will be located
32+
33+
If this value is a string (possible options are `'start'`, `'center'`, `'end'` or a string in percentage format), it is applied to vertical and horizontal position in the annotation.
34+
35+
If this value is an object, the `x` property defines the horizontal alignment in the annotation. Similarly, the `y` property defines the vertical alignment in the annotation. Possible options for both properties are `'start'`, `'center'`, `'end'`, a string in percentage format. Omitted property have value of the default, `'center'`.

docs/guide/types/box.md

Lines changed: 1 addition & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -104,41 +104,7 @@ If one of the axes does not match an axis in the chart, the box will take the en
104104

105105
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.
106106

107-
## Label
108-
109-
Namespace: `options.annotations[annotationID].label`, it defines options for the box annotation label.
110-
111-
All of these options can be [Scriptable](../options#scriptable-options)
112-
113-
| Name | Type | Default | Notes
114-
| ---- | ---- | :----: | ----
115-
| `color` | [`Color`](../options#color) | `'black'` | Text color.
116-
| `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.
117-
| `display` | `boolean` | `false` | Whether or not the label is shown.
118-
| `drawTime` | `string` | `options.drawTime` | See [drawTime](../options#draw-time). Defaults to the box annotation draw time if unset
119-
| `font` | [`Font`](../options#font) | `{ weight: 'bold' }` | Label font
120-
| `height` | `number`\|`string` | `undefined` | Overrides the height of the image or canvas element. Could be set in pixel by a number, or in percentage of current height of image or canvas element by a string. If undefined, uses the height of the image or canvas element. It is used only when the content is an image or canvas element.
121-
| `padding` | [`Padding`](../options#padding) | `6` | The padding to add around the text label.
122-
| [`position`](#position) | `string`\|`{x: string, y: string}` | `'center'` | Anchor position of label in the box.
123-
| `rotation` | `number` | `undefined` | Rotation of label, in degrees. If `undefined`, the box rotation is used.
124-
| `textAlign` | `string` | `'start'` | Text alignment of label content when there's more than one line. Possible options are: `'left'`, `'start'`, `'center'`, `'end'`, `'right'`.
125-
| `textStrokeColor` | [`Color`](../options#color) | `undefined` | The color of the stroke around the text.
126-
| `textStrokeWidth` | `number` | `0` | Stroke width around the text.
127-
| `width` | `number`\|`string` | `undefined` | Overrides the width of the image or canvas element. Could be set in pixel by a number, or in percentage of current width of image or canvas element by a string. If undefined, uses the width of the image or canvas element. It is used only when the content is an image or canvas element.
128-
| `xAdjust` | `number` | `0` | Adjustment along x-axis (left-right) of label relative to computed position. Negative values move the label left, positive right.
129-
| `yAdjust` | `number` | `0` | Adjustment along y-axis (top-bottom) of label relative to computed position. Negative values move the label up, positive down.
130-
| `z` | `number` | `0` | It determines the drawing stack level of the label element, with same `drawTime`.
131-
132-
### Position
133-
134-
A position can be set in 2 different values types:
135-
136-
1. `'start'`, `'center'`, `'end'` which are defining where the label will be located
137-
2. a `string`, in percentage format `'number%'`, is representing the percentage on the size where the label will be located
138-
139-
If this value is a string (possible options are `'start'`, `'center'`, `'end'` or a string in percentage format), it is applied to vertical and horizontal position in the box.
140-
141-
If this value is an object, the `x` property defines the horizontal alignment in the box. Similarly, the `y` property defines the vertical alignment in the box. Possible options for both properties are `'start'`, `'center'`, `'end'`, a string in percentage format. Omitted property have value of the default, `'center'`.
107+
!!!include(./guide/types/_commonInnerLabel.md)!!!
142108

143109
## Element
144110

docs/guide/types/ellipse.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ The following options are available for ellipse annotations.
5555
| ---- | ---- | :----: | ----
5656
| [`backgroundShadowColor`](#styling) | [`Color`](../options#color) | Yes | `'transparent'`
5757
| [`borderWidth`](#styling) | `number`| Yes | `1`
58+
| [`label`](#label) | `object` | Yes |
5859
| [`rotation`](#general) | `number`| Yes | `0`
5960

6061
!!!include(./guide/types/_commonOptions.md)!!!
@@ -93,6 +94,8 @@ If one of the axes does not match an axis in the chart, the ellipse will take th
9394
| `shadowOffsetX` | The distance that shadow will be offset horizontally. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/shadowOffsetX).
9495
| `shadowOffsetY` | The distance that shadow will be offset vertically. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/shadowOffsetY).
9596

97+
!!!include(./guide/types/_commonInnerLabel.md)!!!
98+
9699
## Element
97100

98101
The following diagram is showing the element properties about a `'ellipse'` annotation:

docs/samples/ellipse/image.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# Using images as labels
2+
3+
```js chart-editor
4+
// <block:setup:2>
5+
const DATA_COUNT = 12;
6+
const MIN = 10;
7+
const MAX = 100;
8+
9+
const numberCfg = {count: DATA_COUNT, min: MIN, max: MAX};
10+
11+
const data = {
12+
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
13+
datasets: [{
14+
data: Utils.numbers(numberCfg)
15+
}]
16+
};
17+
// </block:setup>
18+
19+
// <block:annotation:1>
20+
const annotation = {
21+
type: 'ellipse',
22+
backgroundColor: 'rgba(0, 0, 0, 0.2)',
23+
borderWidth: 1,
24+
borderColor: '#F27173',
25+
yMin: 30,
26+
yMax: 80,
27+
xMax: 2,
28+
xMin: 5,
29+
label: {
30+
display: true,
31+
content: Utils.getImage(),
32+
width: 150,
33+
height: 150,
34+
position: 'center'
35+
}
36+
};
37+
// </block:annotation>
38+
39+
/* <block:config:0> */
40+
const config = {
41+
type: 'line',
42+
data,
43+
options: {
44+
scales: {
45+
y: {
46+
beginAtZero: true
47+
}
48+
},
49+
plugins: {
50+
annotation: {
51+
annotations: {
52+
annotation
53+
}
54+
}
55+
}
56+
}
57+
};
58+
/* </block:config> */
59+
60+
const actions = [
61+
{
62+
name: 'Randomize',
63+
handler: function(chart) {
64+
chart.data.datasets.forEach(function(dataset, i) {
65+
dataset.data = dataset.data.map(() => Utils.rand(MIN, MAX));
66+
});
67+
chart.update();
68+
}
69+
}
70+
];
71+
72+
module.exports = {
73+
actions: actions,
74+
config: config
75+
};
76+
```

docs/samples/ellipse/label.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# Labeling
2+
3+
```js chart-editor
4+
// <block:setup:2>
5+
const DATA_COUNT = 12;
6+
const MIN = 10;
7+
const MAX = 100;
8+
9+
const numberCfg = {count: DATA_COUNT, min: MIN, max: MAX};
10+
11+
const data = {
12+
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
13+
datasets: [{
14+
data: Utils.numbers(numberCfg)
15+
}]
16+
};
17+
// </block:setup>
18+
19+
// <block:annotation:1>
20+
const annotation = {
21+
type: 'ellipse',
22+
backgroundColor: 'rgba(208, 208, 208, 0.2)',
23+
borderWidth: 0,
24+
label: {
25+
drawTime: 'afterDatasetsDraw',
26+
display: true,
27+
color: 'rgba(208, 208, 208, 0.6)',
28+
content: 'whole year',
29+
font: {
30+
size: (ctx) => ctx.chart.chartArea.height / 4
31+
},
32+
position: {
33+
x: 'center',
34+
y: 'end'
35+
}
36+
}
37+
};
38+
// </block:annotation>
39+
40+
/* <block:config:0> */
41+
const config = {
42+
type: 'line',
43+
data,
44+
options: {
45+
plugins: {
46+
annotation: {
47+
annotations: {
48+
annotation
49+
}
50+
}
51+
}
52+
}
53+
};
54+
/* </block:config> */
55+
56+
const actions = [
57+
{
58+
name: 'Randomize',
59+
handler: function(chart) {
60+
chart.data.datasets.forEach(function(dataset, i) {
61+
dataset.data = dataset.data.map(() => Utils.rand(MIN, MAX));
62+
});
63+
chart.update();
64+
}
65+
}
66+
];
67+
68+
module.exports = {
69+
actions: actions,
70+
config: config,
71+
};
72+
```

src/helpers/helpers.chart.js

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import {isFinite} from 'chart.js/helpers';
2-
import {isBoundToPoint} from './helpers.options';
1+
import {isFinite, toPadding} from 'chart.js/helpers';
2+
import {measureLabelSize} from './helpers.canvas';
3+
import {isBoundToPoint, getRelativePosition, toPosition} from './helpers.options';
34

45
/**
56
* @typedef { import("chart.js").Chart } Chart
@@ -144,6 +145,23 @@ export function resolvePointProperties(chart, options) {
144145
return getChartCircle(chart, options);
145146
}
146147

148+
/**
149+
* @param {Chart} chart
150+
* @param {CoreAnnotationOptions} options
151+
* @returns {AnnotationBoxModel}
152+
*/
153+
export function resolveBoxAndLabelProperties(chart, options) {
154+
const properties = resolveBoxProperties(chart, options);
155+
const {x, y} = properties;
156+
properties.elements = [{
157+
type: 'label',
158+
optionScope: 'label',
159+
properties: resolveLabelElementProperties(chart, properties, options)
160+
}];
161+
properties.initProperties = {x, y};
162+
return properties;
163+
}
164+
147165
function getChartCircle(chart, options) {
148166
const point = getChartPoint(chart, options);
149167
const size = options.radius * 2;
@@ -166,3 +184,54 @@ function getChartDimensionByScale(scale, options) {
166184
end: Math.max(result.start, result.end)
167185
};
168186
}
187+
188+
function calculateX({properties, options}, labelSize, position, padding) {
189+
const {x: start, x2: end, width: size} = properties;
190+
return calculatePosition({start, end, size, borderWidth: options.borderWidth}, {
191+
position: position.x,
192+
padding: {start: padding.left, end: padding.right},
193+
adjust: options.label.xAdjust,
194+
size: labelSize.width
195+
});
196+
}
197+
198+
function calculateY({properties, options}, labelSize, position, padding) {
199+
const {y: start, y2: end, height: size} = properties;
200+
return calculatePosition({start, end, size, borderWidth: options.borderWidth}, {
201+
position: position.y,
202+
padding: {start: padding.top, end: padding.bottom},
203+
adjust: options.label.yAdjust,
204+
size: labelSize.height
205+
});
206+
}
207+
208+
function calculatePosition(boxOpts, labelOpts) {
209+
const {start, end, borderWidth} = boxOpts;
210+
const {position, padding: {start: padStart, end: padEnd}, adjust} = labelOpts;
211+
const availableSize = end - borderWidth - start - padStart - padEnd - labelOpts.size;
212+
return start + borderWidth / 2 + adjust + getRelativePosition(availableSize, position);
213+
}
214+
215+
function resolveLabelElementProperties(chart, properties, options) {
216+
const label = options.label;
217+
label.backgroundColor = 'transparent';
218+
label.callout.display = false;
219+
const position = toPosition(label.position);
220+
const padding = toPadding(label.padding);
221+
const labelSize = measureLabelSize(chart.ctx, label);
222+
const x = calculateX({properties, options}, labelSize, position, padding);
223+
const y = calculateY({properties, options}, labelSize, position, padding);
224+
const width = labelSize.width + padding.width;
225+
const height = labelSize.height + padding.height;
226+
return {
227+
x,
228+
y,
229+
x2: x + width,
230+
y2: y + height,
231+
width,
232+
height,
233+
centerX: x + width / 2,
234+
centerY: y + height / 2,
235+
rotation: label.rotation
236+
};
237+
}

0 commit comments

Comments
 (0)