Skip to content

Commit 03b1d73

Browse files
authored
Make a consistent implementations of inRange in all annotations (#547)
* Enable a consistent implementations of 'inRange' in all annotations * updates line implementation, removing labelRect property * fixes test case because the previous result was wrong (spaceAround) * fixed max number of arguments for a method * some code improvements in inRange method of line annotation * fixes polygon use final position implementation
1 parent e78e1cf commit 03b1d73

File tree

7 files changed

+115
-77
lines changed

7 files changed

+115
-77
lines changed

docs/samples/line/lowerUpper.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,23 @@ var actions = [
138138
});
139139
chart.update();
140140
}
141+
},
142+
{
143+
name: 'Cycle position',
144+
handler: function(chart) {
145+
const annotations = chart.options.plugins.annotation.annotations;
146+
if (annotations.annotation1.label.position === 'start') {
147+
annotations.annotation1.label.position = 'end';
148+
annotations.annotation2.label.position = 'end';
149+
} else if (annotations.annotation1.label.position === 'center') {
150+
annotations.annotation1.label.position = 'start';
151+
annotations.annotation2.label.position = 'start';
152+
} else {
153+
annotations.annotation1.label.position = 'center';
154+
annotations.annotation2.label.position = 'center';
155+
}
156+
chart.update();
157+
}
141158
}
142159
];
143160

src/types/ellipse.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import {getRectCenterPoint, getChartRect} from '../helpers';
44

55
export default class EllipseAnnotation extends Element {
66

7-
inRange(x, y) {
8-
return pointInEllipse({x, y}, this);
7+
inRange(mouseX, mouseY, useFinalPosition) {
8+
return pointInEllipse({x: mouseX, y: mouseY}, this.getProps(['x', 'y', 'width', 'height'], useFinalPosition));
99
}
1010

1111
getCenterPoint(useFinalPosition) {

src/types/label.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export default class LabelAnnotation extends Element {
3939
}
4040

4141
resolveElementProperties(chart, options) {
42-
const visible = isLabelVisible(options);
42+
const visible = !!isLabelVisible(options);
4343
const point = !isBoundToPoint(options) ? getRectCenterPoint(getChartRect(chart, options)) : getChartPoint(chart, options);
4444
const padding = toPadding(options.padding);
4545
const labelSize = measureLabelSize(chart.ctx, options);

src/types/line.js

Lines changed: 65 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,10 @@ function limitLineToArea(p1, p2, area) {
4343
}
4444

4545
export default class LineAnnotation extends Element {
46-
intersects(x, y, epsilon = 0.001) {
46+
intersects(x, y, epsilon = 0.001, useFinalPosition) {
4747
// Adapted from https://stackoverflow.com/a/6853926/25507
4848
const sqr = v => v * v;
49-
const {x: x1, y: y1, x2, y2} = this;
49+
const {x: x1, y: y1, x2, y2} = this.getProps(['x', 'y', 'x2', 'y2'], useFinalPosition);
5050
const dx = x2 - x1;
5151
const dy = y2 - y1;
5252
const lenSq = sqr(dx) + sqr(dy);
@@ -65,29 +65,28 @@ export default class LineAnnotation extends Element {
6565
return (sqr(x - xx) + sqr(y - yy)) < epsilon;
6666
}
6767

68-
labelIsVisible(chartArea) {
69-
const label = this.options.label;
70-
71-
const inside = !chartArea || isLineInArea(this, chartArea);
72-
return inside && isLabelVisible(label);
68+
labelIsVisible(useFinalPosition, chartArea) {
69+
if (!this.labelVisible) {
70+
return false;
71+
}
72+
return !chartArea || isLineInArea(this.getProps(['x', 'y', 'x2', 'y2'], useFinalPosition), chartArea);
7373
}
7474

75-
isOnLabel(mouseX, mouseY) {
76-
const {labelRect} = this;
77-
if (!labelRect || !this.labelIsVisible()) {
75+
isOnLabel(mouseX, mouseY, useFinalPosition) {
76+
if (!this.labelIsVisible(useFinalPosition)) {
7877
return false;
7978
}
80-
81-
const {x, y} = rotated({x: mouseX, y: mouseY}, labelRect, -labelRect.rotation);
82-
const w2 = labelRect.width / 2;
83-
const h2 = labelRect.height / 2;
84-
return x >= labelRect.x - w2 && x <= labelRect.x + w2 &&
85-
y >= labelRect.y - h2 && y <= labelRect.y + h2;
79+
const {labelX, labelY, labelWidth, labelHeight, labelRotation} = this.getProps(['labelX', 'labelY', 'labelWidth', 'labelHeight', 'labelRotation'], useFinalPosition);
80+
const {x, y} = rotated({x: mouseX, y: mouseY}, {x: labelX, y: labelY}, -labelRotation);
81+
const w2 = labelWidth / 2;
82+
const h2 = labelHeight / 2;
83+
return x >= labelX - w2 && x <= labelX + w2 &&
84+
y >= labelY - h2 && y <= labelY + h2;
8685
}
8786

88-
inRange(x, y) {
87+
inRange(mouseX, mouseY, useFinalPosition) {
8988
const epsilon = this.options.borderWidth || 1;
90-
return this.intersects(x, y, epsilon) || this.isOnLabel(x, y);
89+
return this.intersects(mouseX, mouseY, epsilon, useFinalPosition) || this.isOnLabel(mouseX, mouseY, useFinalPosition);
9190
}
9291

9392
getCenterPoint() {
@@ -116,9 +115,9 @@ export default class LineAnnotation extends Element {
116115
}
117116

118117
drawLabel(ctx, chartArea) {
119-
if (this.labelIsVisible(chartArea)) {
118+
if (this.labelIsVisible(false, chartArea)) {
120119
ctx.save();
121-
applyLabel(ctx, this, chartArea);
120+
applyLabel(ctx, this);
122121
ctx.restore();
123122
}
124123
}
@@ -153,9 +152,15 @@ export default class LineAnnotation extends Element {
153152
}
154153
}
155154
const inside = isLineInArea({x, y, x2, y2}, chart.chartArea);
156-
return inside
155+
const properties = inside
157156
? limitLineToArea({x, y}, {x: x2, y: y2}, chart.chartArea)
158157
: {x, y, x2, y2, width: Math.abs(x2 - x), height: Math.abs(y2 - y)};
158+
const label = options.label;
159+
properties.labelVisible = !!isLabelVisible(label);
160+
if (properties.labelVisible) {
161+
return loadLabelRect(properties, chart, label);
162+
}
163+
return properties;
159164
}
160165
}
161166

@@ -213,39 +218,51 @@ LineAnnotation.defaultRoutes = {
213218
borderColor: 'color'
214219
};
215220

221+
function loadLabelRect(line, chart, options) {
222+
// TODO: v2 remove support for xPadding and yPadding
223+
const {padding: lblPadding, xPadding, yPadding, borderWidth} = options;
224+
const padding = getPadding(lblPadding, xPadding, yPadding);
225+
const textSize = measureLabelSize(chart.ctx, options);
226+
const width = textSize.width + padding.width + borderWidth;
227+
const height = textSize.height + padding.height + borderWidth;
228+
const labelRect = calculateLabelPosition(line, options, {width, height, padding}, chart.chartArea);
229+
line.labelX = labelRect.x;
230+
line.labelY = labelRect.y;
231+
line.labelWidth = labelRect.width;
232+
line.labelHeight = labelRect.height;
233+
line.labelRotation = labelRect.rotation;
234+
line.labelPadding = padding;
235+
line.labelTextSize = textSize;
236+
return line;
237+
}
238+
216239
function calculateAutoRotation(line) {
217240
const {x, y, x2, y2} = line;
218241
const rotation = Math.atan2(y2 - y, x2 - x);
219242
// Flip the rotation if it goes > PI/2 or < -PI/2, so label stays upright
220243
return rotation > PI / 2 ? rotation - PI : rotation < PI / -2 ? rotation + PI : rotation;
221244
}
222245

223-
function applyLabel(ctx, line, chartArea) {
224-
const label = line.options.label;
225-
// TODO: v2 remove support for xPadding and yPadding
226-
const {padding: lblPadding, xPadding, yPadding, borderWidth} = label;
227-
const padding = getPadding(lblPadding, xPadding, yPadding);
228-
const labelSize = measureLabelSize(ctx, label);
229-
const width = labelSize.width + padding.width + borderWidth;
230-
const height = labelSize.height + padding.height + borderWidth;
231-
const rect = line.labelRect = calculateLabelPosition(line, {width, height, padding}, chartArea);
246+
function applyLabel(ctx, line) {
247+
const {labelX, labelY, labelWidth, labelHeight, labelRotation, labelPadding, labelTextSize, options} = line;
248+
const label = options.label;
232249

233-
ctx.translate(rect.x, rect.y);
234-
ctx.rotate(rect.rotation);
250+
ctx.translate(labelX, labelY);
251+
ctx.rotate(labelRotation);
235252

236253
const boxRect = {
237-
x: -(width / 2),
238-
y: -(height / 2),
239-
width,
240-
height
254+
x: -(labelWidth / 2),
255+
y: -(labelHeight / 2),
256+
width: labelWidth,
257+
height: labelHeight
241258
};
242259
drawBox(ctx, boxRect, label);
243260

244261
const labelTextRect = {
245-
x: -(width / 2) + padding.left + borderWidth / 2,
246-
y: -(height / 2) + padding.top + borderWidth / 2,
247-
width: labelSize.width,
248-
height: labelSize.height
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
249266
};
250267
drawLabel(ctx, labelTextRect, label);
251268
}
@@ -259,15 +276,14 @@ function getPadding(padding, xPadding, yPadding) {
259276
return toPadding(tempPadding);
260277
}
261278

262-
function calculateLabelPosition(line, sizes, chartArea) {
279+
function calculateLabelPosition(line, label, sizes, chartArea) {
263280
const {width, height, padding} = sizes;
264-
const label = line.options.label;
265-
const {xAdjust, yAdjust, position} = label;
281+
const {xAdjust, yAdjust} = label;
266282
const p1 = {x: line.x, y: line.y};
267283
const p2 = {x: line.x2, y: line.y2};
268284
const rotation = label.rotation === 'auto' ? calculateAutoRotation(line) : toRadians(label.rotation);
269285
const size = rotatedSize(width, height, rotation);
270-
const t = calculateT(line, position, {labelSize: size, padding}, chartArea);
286+
const t = calculateT(line, label, {labelSize: size, padding}, chartArea);
271287
const pt = pointInLine(p1, p2, t);
272288
const xCoordinateSizes = {size: size.w, min: chartArea.left, max: chartArea.right, padding: padding.left};
273289
const yCoordinateSizes = {size: size.h, min: chartArea.top, max: chartArea.bottom, padding: padding.top};
@@ -290,13 +306,12 @@ function rotatedSize(width, height, rotation) {
290306
};
291307
}
292308

293-
function calculateT(line, position, sizes, chartArea) {
309+
function calculateT(line, label, sizes, chartArea) {
294310
let t = 0.5;
295311
const space = spaceAround(line, chartArea);
296-
const label = line.options.label;
297-
if (position === 'start') {
312+
if (label.position === 'start') {
298313
t = calculateTAdjust({w: line.x2 - line.x, h: line.y2 - line.y}, sizes, label, space);
299-
} else if (position === 'end') {
314+
} else if (label.position === 'end') {
300315
t = 1 - calculateTAdjust({w: line.x - line.x2, h: line.y - line.y2}, sizes, label, space);
301316
}
302317
return t;
@@ -320,27 +335,23 @@ function spaceAround(line, chartArea) {
320335
return {
321336
x: Math.min(l, r),
322337
y: Math.min(t, b),
323-
dx: l < r ? 1 : -1,
324-
dy: t < b ? 1 : -1
338+
dx: l <= r ? 1 : -1,
339+
dy: t <= b ? 1 : -1
325340
};
326341
}
327342

328343
function adjustLabelCoordinate(coordinate, labelSizes) {
329344
const {size, min, max, padding} = labelSizes;
330345
const halfSize = size / 2;
331-
332346
if (size > max - min) {
333347
// if it does not fit, display as much as possible
334348
return (max + min) / 2;
335349
}
336-
337350
if (min >= (coordinate - padding - halfSize)) {
338351
coordinate = min + padding + halfSize;
339352
}
340-
341353
if (max <= (coordinate + padding + halfSize)) {
342354
coordinate = max - padding - halfSize;
343355
}
344-
345356
return coordinate;
346357
}

src/types/point.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ import {drawPoint, inPointRange, getElementCenterPoint, resolvePointPosition} fr
33

44
export default class PointAnnotation extends Element {
55

6-
inRange(x, y, useFinalPosition) {
6+
inRange(mouseX, mouseY, useFinalPosition) {
77
const {width} = this.getProps(['width'], useFinalPosition);
8-
return inPointRange({x, y}, this.getCenterPoint(useFinalPosition), width / 2 + this.options.borderWidth);
8+
return inPointRange({x: mouseX, y: mouseY}, this.getCenterPoint(useFinalPosition), width / 2 + this.options.borderWidth);
99
}
1010

1111
getCenterPoint(useFinalPosition) {

src/types/polygon.js

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import {Element} from 'chart.js';
2-
import {PI, RAD_PER_DEG} from 'chart.js/helpers';
2+
import {PI, RAD_PER_DEG, isNumber} from 'chart.js/helpers';
33
import {setBorderStyle, resolvePointPosition, getElementCenterPoint} from '../helpers';
44

55
export default class PolygonAnnotation extends Element {
66

7-
inRange(x, y) {
8-
return this.vertices && this.vertices.length > 0 && pointIsInPolygon(this.vertices, x, y);
7+
inRange(mouseX, mouseY, useFinalPosition) {
8+
const vertices = getVertices(this.getProps(['x', 'y'], useFinalPosition), this.options);
9+
return vertices && vertices.length > 0 && pointIsInPolygon(vertices, mouseX, mouseY);
910
}
1011

1112
getCenterPoint(useFinalPosition) {
@@ -14,20 +15,15 @@ export default class PolygonAnnotation extends Element {
1415

1516
draw(ctx) {
1617
const {x, y, options} = this;
17-
const {sides, radius} = options;
18-
const point = {x, y};
19-
let angle = (2 * PI) / sides;
20-
let rad = options.rotation * RAD_PER_DEG;
21-
this.vertices = new Array();
22-
let vertex = createVertex(this.vertices, point, rad, radius);
18+
const vertices = getVertices({x, y}, options);
19+
let vertex = vertices[0];
2320
ctx.save();
2421
ctx.beginPath();
2522
ctx.fillStyle = options.backgroundColor;
2623
const stroke = setBorderStyle(ctx, options);
2724
ctx.moveTo(vertex.x, vertex.y);
28-
for (let i = 0; i < sides; i++) {
29-
rad += angle;
30-
vertex = createVertex(this.vertices, point, rad, radius);
25+
for (let i = 1; i < vertices.length; i++) {
26+
vertex = vertices[i];
3127
ctx.lineTo(vertex.x, vertex.y);
3228
}
3329
ctx.closePath();
@@ -40,7 +36,10 @@ export default class PolygonAnnotation extends Element {
4036
}
4137

4238
resolveElementProperties(chart, options) {
43-
return resolvePointPosition(chart, options);
39+
if (isNumber(options.sides) && options.sides >= 1) {
40+
return resolvePointPosition(chart, options);
41+
}
42+
return {options: {}};
4443
}
4544

4645
}
@@ -73,13 +72,24 @@ PolygonAnnotation.defaultRoutes = {
7372
backgroundColor: 'color'
7473
};
7574

76-
function createVertex(array, point, rad, radius) {
77-
const vertex = {
75+
function getVertices(point, options) {
76+
const {sides, radius} = options;
77+
let angle = (2 * PI) / sides;
78+
let rad = options.rotation * RAD_PER_DEG;
79+
const vertices = new Array();
80+
addVertex(vertices, point, rad, radius);
81+
for (let i = 0; i < sides; i++) {
82+
rad += angle;
83+
addVertex(vertices, point, rad, radius);
84+
}
85+
return vertices;
86+
}
87+
88+
function addVertex(array, point, rad, radius) {
89+
array.push({
7890
x: point.x + Math.sin(rad) * radius,
7991
y: point.y - Math.cos(rad) * radius
80-
};
81-
array.push(vertex);
82-
return vertex;
92+
});
8393
}
8494

8595
function pointIsInPolygon(vertices, x, y) {

test/fixtures/line/paddingXY.png

1 Byte
Loading

0 commit comments

Comments
 (0)