Skip to content

Commit 1d765ae

Browse files
stockiNailkurkle
andauthored
Enable label position as percentage of the size (#554)
* Fix #553 * adds box implementation * fixes multiple returns * fixes test case and complexity in box position calculation * reduces duplication code * adds label implementation * changes getSize and toPercent with default * adds documentation * adds types * Update types/label.d.ts Co-authored-by: Jukka Kurkela <[email protected]> * Update src/types/box.js Co-authored-by: Jukka Kurkela <[email protected]> * Update src/types/label.js Co-authored-by: Jukka Kurkela <[email protected]> * Update src/types/label.js Co-authored-by: Jukka Kurkela <[email protected]> * applies some review updates * getRelativePosition * temporary commit having a look how to simply the position calculation * rebase to merge PR * fixes double content options in test cases and sample * fixes typo * Update test/fixtures/box/labelPosition.js Co-authored-by: Jukka Kurkela <[email protected]> * adds fixture for position label box Co-authored-by: Jukka Kurkela <[email protected]>
1 parent fd6a34c commit 1d765ae

File tree

17 files changed

+644
-46
lines changed

17 files changed

+644
-46
lines changed

docs/guide/types/box.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,11 @@ All of these options can be [Scriptable](../options#scriptable-options)
126126

127127
#### Position
128128

129-
If this value is a string (possible options are `'start'`, `'center'`, `'end'`), it is applied to vertical and horizontal position in the box.
129+
A position can be set in 2 different values types:
130130

131-
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'`. Omitted property have value of the default, `'center'`.
131+
1. `'start'`, `'center'`, `'end'` which are defining where the label will be located
132+
2. a `string`, in percentage format `'number%'`, is representing the percentage on the size where the label will be located
133+
134+
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.
135+
136+
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'`.

docs/guide/types/label.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,9 +128,14 @@ The 4 coordinates, xMin, xMax, yMin, yMax are optional. If not specified, the bo
128128

129129
### Position
130130

131-
If this value is a string (possible options are `'start'`, `'center'`, `'end'`), it is applied to vertical and horizontal position of the label, with respect to the selected point.
131+
A position can be set in 2 different values types:
132132

133-
If this value is an object, the `x` property defines the horizontal alignment of the label, with respect to the selected point. Similarly, the `y` property defines the vertical alignment of the label, with respect to the selected point. Possible options for both properties are `'start'`, `'center'`, `'end'`. Omitted property have value of the default, `'center'`.
133+
1. `'start'`, `'center'`, `'end'` which are defining where the label will be located
134+
2. a `string`, in percentage format `'number%'`, is representing the percentage on the size where the label will be located
135+
136+
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.
137+
138+
If this value is an object, the `x` property defines the horizontal alignment in the label, with respect to the selected point. Similarly, the `y` property defines the vertical alignment in the label, with respect to the selected point. Possible options for both properties are `'start'`, `'center'`, `'end'`, a string in percentage format. Omitted property have value of the default, `'center'`.
134139

135140
#### borderRadius
136141

docs/guide/types/line.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ All of these options can be [Scriptable](../options#scriptable-options)
138138
| `padding` | [`Padding`](../options#padding) | `6` | The padding to add around the text label.
139139
| `xPadding` | `number` | `6` | Padding of label to add left/right. This is **deprecated**. Use `padding`.
140140
| `yPadding` | `number` | `6` | Padding of label to add top/bottom. This is **deprecated**. Use `padding`.
141-
| `position` | `string` | `'center'` | Anchor position of label on line. Possible options are: `'start'`, `'center'`, `'end'`.
141+
| `position` | `string` | `'center'` | Anchor position of label on line. Possible options are: `'start'`, `'center'`, `'end'`. It can be set by a string in percentage format `'number%'` which are representing the percentage on the width of the line where the label will be located.
142142
| `rotation` | `number`\|`'auto'` | `0` | Rotation of label, in degrees, or 'auto' to use the degrees of the line
143143
| `textAlign` | `string` | `'center'` | Text alignment of label content when there's more than one line. Possible options are: `'start'`, `'center'`, `'end'`.
144144
| `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.

docs/samples/line/labelVisibility.md

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -80,13 +80,7 @@ function toggleLabel(ctx, event) {
8080
const chart = ctx.chart;
8181
const annotationOpts = chart.options.plugins.annotation.annotations.annotation;
8282
annotationOpts.label.enabled = !annotationOpts.label.enabled;
83-
if (event.x < oneThirdWidth) {
84-
annotationOpts.label.position = 'start';
85-
} else if (event.x > oneThirdWidth * 2) {
86-
annotationOpts.label.position = 'end';
87-
} else {
88-
annotationOpts.label.position = 'center';
89-
}
83+
annotationOpts.label.position = (event.x / ctx.chart.chartArea.width * 100) + '%';
9084
chart.update();
9185
}
9286
// </block:utils>

src/helpers/helpers.options.js

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,27 @@
11
import {isObject, valueOrDefault, defined} from 'chart.js/helpers';
2+
import {clamp} from './helpers.core';
23

34
const isEnabled = (options) => options && (options.display || options.enabled);
4-
const toPercent = (s) => typeof s === 'string' && s.endsWith('%') && parseFloat(s) / 100;
5+
const isPercentString = (s) => typeof s === 'string' && s.endsWith('%');
6+
const toPercent = (s) => clamp(parseFloat(s) / 100, 0, 1);
7+
8+
export function getRelativePosition(size, positionOption) {
9+
if (positionOption === 'start') {
10+
return 0;
11+
}
12+
if (positionOption === 'end') {
13+
return size;
14+
}
15+
if (isPercentString(positionOption)) {
16+
return toPercent(positionOption) * size;
17+
}
18+
return size / 2;
19+
}
520

621
export function getSize(size, value) {
722
if (typeof value === 'number') {
823
return value;
9-
} else if (typeof value === 'string') {
24+
} else if (isPercentString(value)) {
1025
return toPercent(value) * size;
1126
}
1227
return size;

src/types/box.js

Lines changed: 17 additions & 13 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, measureLabelSize, isLabelVisible, getRectCenterPoint, getChartRect, toPosition, inBoxRange} from '../helpers';
3+
import {drawBox, drawLabel, getRelativePosition, measureLabelSize, isLabelVisible, getRectCenterPoint, getChartRect, toPosition, inBoxRange} from '../helpers';
44

55
export default class BoxAnnotation extends Element {
66
inRange(mouseX, mouseY, useFinalPosition) {
@@ -94,24 +94,28 @@ BoxAnnotation.defaultRoutes = {
9494
function calculateX(box, labelSize, position, padding) {
9595
const {x: start, x2: end, width: size, options} = box;
9696
const {xAdjust: adjust, borderWidth} = options.label;
97-
return calculatePosition({start, end, size}, {position: position.x, padding: padding.left, adjust, borderWidth, size: labelSize.width});
97+
return calculatePosition({start, end, size}, {
98+
position: position.x,
99+
padding: {start: padding.left, end: padding.right},
100+
adjust, borderWidth,
101+
size: labelSize.width
102+
});
98103
}
99104

100105
function calculateY(box, labelSize, position, padding) {
101106
const {y: start, y2: end, height: size, options} = box;
102107
const {yAdjust: adjust, borderWidth} = options.label;
103-
return calculatePosition({start, end, size}, {position: position.y, padding: padding.top, adjust, borderWidth, size: labelSize.height});
108+
return calculatePosition({start, end, size}, {
109+
position: position.y,
110+
padding: {start: padding.top, end: padding.bottom},
111+
adjust, borderWidth,
112+
size: labelSize.height
113+
});
104114
}
105115

106116
function calculatePosition(boxOpts, labelOpts) {
107-
const {start, end, size} = boxOpts;
108-
const {position, padding, adjust, borderWidth} = labelOpts;
109-
const margin = padding + (borderWidth / 2) + adjust;
110-
if (position === 'start') {
111-
return start + margin;
112-
} else if (position === 'end') {
113-
return end - labelOpts.size - margin;
114-
}
115-
return start + (size - labelOpts.size) / 2;
117+
const {start, end} = boxOpts;
118+
const {position, padding: {start: padStart, end: padEnd}, adjust, borderWidth} = labelOpts;
119+
const availableSize = end - borderWidth - start - padStart - padEnd - labelOpts.size;
120+
return start + borderWidth / 2 + adjust + padStart + getRelativePosition(availableSize, position);
116121
}
117-

src/types/label.js

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {drawBox, drawLabel, measureLabelSize, isLabelVisible, getChartPoint, getRectCenterPoint, toPosition, setBorderStyle, getSize, inBoxRange, isBoundToPoint, getChartRect} from '../helpers';
1+
import {drawBox, drawLabel, measureLabelSize, isLabelVisible, 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

@@ -119,12 +119,7 @@ function measureRect(point, size, options, padding) {
119119
}
120120

121121
function calculatePosition(start, size, adjust, position) {
122-
if (position === 'start') {
123-
return start + adjust;
124-
} else if (position === 'end') {
125-
return start - size + adjust;
126-
}
127-
return start - size / 2 + adjust;
122+
return start - getRelativePosition(size, position) + adjust;
128123
}
129124

130125
function drawCallout(ctx, element) {
@@ -182,20 +177,15 @@ function getCalloutSideCoord(element, position, separatorStart) {
182177
const side = getCalloutSideAdjust(position, options.callout);
183178
let sideStart, sideEnd;
184179
if (position === 'left' || position === 'right') {
185-
sideStart = {x: separatorStart.x, y: y + getStartSize(height, start)};
180+
sideStart = {x: separatorStart.x, y: y + getSize(height, start)};
186181
sideEnd = {x: sideStart.x + side, y: sideStart.y};
187182
} else if (position === 'top' || position === 'bottom') {
188-
sideStart = {x: separatorStart.x + getStartSize(width, start), y: separatorStart.y};
183+
sideStart = {x: separatorStart.x + getSize(width, start), y: separatorStart.y};
189184
sideEnd = {x: sideStart.x, y: sideStart.y + side};
190185
}
191186
return {sideStart, sideEnd};
192187
}
193188

194-
function getStartSize(size, value) {
195-
const start = getSize(size, value);
196-
return Math.min(Math.max(start, 0), size);
197-
}
198-
199189
function getCalloutSideAdjust(position, options) {
200190
const side = options.side;
201191
if (position === 'left' || position === 'top') {

src/types/line.js

Lines changed: 4 additions & 2 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} from '../helpers';
3+
import {clamp, scaleValue, rotated, drawBox, drawLabel, measureLabelSize, isLabelVisible, 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)});
@@ -307,12 +307,14 @@ function rotatedSize(width, height, rotation) {
307307
}
308308

309309
function calculateT(line, label, sizes, chartArea) {
310-
let t = 0.5;
310+
let t;
311311
const space = spaceAround(line, chartArea);
312312
if (label.position === 'start') {
313313
t = calculateTAdjust({w: line.x2 - line.x, h: line.y2 - line.y}, sizes, label, space);
314314
} else if (label.position === 'end') {
315315
t = 1 - calculateTAdjust({w: line.x - line.x2, h: line.y - line.y2}, sizes, label, space);
316+
} else {
317+
t = getRelativePosition(1, label.position);
316318
}
317319
return t;
318320
}

test/fixtures/box/labelPosition.js

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
module.exports = {
2+
tolerance: 0.0085,
3+
config: {
4+
type: 'scatter',
5+
options: {
6+
scales: {
7+
x: {
8+
display: true,
9+
min: -10,
10+
max: 10
11+
},
12+
y: {
13+
display: true,
14+
min: -10,
15+
max: 10
16+
}
17+
},
18+
plugins: {
19+
legend: false,
20+
annotation: {
21+
drawTime: 'afterDraw',
22+
annotations: {
23+
box1: {
24+
type: 'box',
25+
xScaleID: 'x',
26+
yScaleID: 'y',
27+
xMin: -9,
28+
yMin: 9,
29+
xMax: -1,
30+
yMax: 1,
31+
backgroundColor: 'white',
32+
borderColor: 'red',
33+
borderWidth: 2,
34+
label: {
35+
enabled: true,
36+
content: 'p: 0%,100%',
37+
position: {
38+
x: '0%',
39+
y: '100%'
40+
}
41+
}
42+
},
43+
box2: {
44+
type: 'box',
45+
xScaleID: 'x',
46+
yScaleID: 'y',
47+
xMin: 1,
48+
yMin: 9,
49+
xMax: 9,
50+
yMax: 1,
51+
backgroundColor: 'white',
52+
borderColor: 'red',
53+
borderWidth: 2,
54+
label: {
55+
enabled: true,
56+
content: 'p: 25%,75%',
57+
position: {
58+
x: '25%',
59+
y: '75%'
60+
}
61+
}
62+
},
63+
box3: {
64+
type: 'box',
65+
xScaleID: 'x',
66+
yScaleID: 'y',
67+
xMin: -9,
68+
yMin: -1,
69+
xMax: -1,
70+
yMax: -9,
71+
backgroundColor: 'white',
72+
borderColor: 'red',
73+
borderWidth: 2,
74+
label: {
75+
enabled: true,
76+
content: 'p: 50%,50%',
77+
position: {
78+
x: '50%',
79+
y: '50%'
80+
}
81+
}
82+
},
83+
box4: {
84+
type: 'box',
85+
xScaleID: 'x',
86+
yScaleID: 'y',
87+
xMin: 1,
88+
yMin: -1,
89+
xMax: 9,
90+
yMax: -9,
91+
backgroundColor: 'white',
92+
borderColor: 'red',
93+
borderWidth: 2,
94+
label: {
95+
enabled: true,
96+
content: 'p: 100%,0%',
97+
position: {
98+
x: '100%',
99+
y: '0%'
100+
}
101+
}
102+
},
103+
}
104+
}
105+
}
106+
}
107+
},
108+
options: {
109+
spriteText: true
110+
}
111+
};
21.1 KB
Loading

0 commit comments

Comments
 (0)