From 881e02012540aa9af0edbb9557c3a1af4f4d0b98 Mon Sep 17 00:00:00 2001 From: stockiNail Date: Thu, 7 Apr 2022 16:52:16 +0200 Subject: [PATCH] Reduce visibility of some line annotation element methods --- docs/guide/migrationV2.md | 6 ++ src/types/label.js | 5 +- src/types/line.js | 166 +++++++++++++++++++------------------- 3 files changed, 90 insertions(+), 87 deletions(-) diff --git a/docs/guide/migrationV2.md b/docs/guide/migrationV2.md index e5d2a0746..c98540f30 100644 --- a/docs/guide/migrationV2.md +++ b/docs/guide/migrationV2.md @@ -16,6 +16,12 @@ A number of changes were made to the configuration options passed to the plugin ## Elements +`chartjs-plugin-annotation` plugin version 2 hides the following methods in the `line` annotation element because they should be used only internally: + + * `intersects` + * `labelIsVisible` + * `isOnLabel` + `chartjs-plugin-annotation` plugin version 2 normalizes the properties of the annotation elements in order to be based on common box model. #### Box annotation diff --git a/src/types/label.js b/src/types/label.js index 18d90fe22..abdf792ce 100644 --- a/src/types/label.js +++ b/src/types/label.js @@ -2,6 +2,8 @@ import {Element} from 'chart.js'; import {drawBox, drawLabel, measureLabelSize, getChartPoint, toPosition, setBorderStyle, getSize, inBoxRange, isBoundToPoint, resolveBoxProperties, getRelativePosition, translate, rotated, getElementCenterPoint} from '../helpers'; import {toPadding, toRadians, distanceBetweenPoints} from 'chart.js/helpers'; +const positions = ['left', 'bottom', 'top', 'right']; + export default class LabelAnnotation extends Element { inRange(mouseX, mouseY, axis, useFinalPosition) { @@ -26,7 +28,6 @@ export default class LabelAnnotation extends Element { ctx.restore(); } - // TODO: make private in v2 resolveElementProperties(chart, options) { let point; if (!isBoundToPoint(options)) { @@ -219,8 +220,6 @@ function resolveCalloutPosition(properties, options, rotation) { return resolveCalloutAutoPosition(properties, options, rotation); } -const positions = ['left', 'bottom', 'top', 'right']; - function resolveCalloutAutoPosition(properties, options, rotation) { const {x, y, x2, y2, width, height, pointX, pointY, centerX, centerY} = properties; const center = {x: centerX, y: centerY}; diff --git a/src/types/line.js b/src/types/line.js index 7e2fc927e..03648e5f6 100644 --- a/src/types/line.js +++ b/src/types/line.js @@ -8,97 +8,17 @@ const interpolateY = (x, p1, p2) => pointInLine(p1, p2, Math.abs((x - p1.x) / (p const sqr = v => v * v; const rangeLimit = (mouseX, mouseY, {x, y, x2, y2}, axis) => axis === 'y' ? {start: Math.min(y, y2), end: Math.max(y, y2), value: mouseY} : {start: Math.min(x, x2), end: Math.max(x, x2), value: mouseX}; -function isLineInArea({x, y, x2, y2}, {top, right, bottom, left}) { - return !( - (x < left && x2 < left) || - (x > right && x2 > right) || - (y < top && y2 < top) || - (y > bottom && y2 > bottom) - ); -} - -function limitPointToArea({x, y}, p2, {top, right, bottom, left}) { - if (x < left) { - y = interpolateY(left, {x, y}, p2); - x = left; - } - if (x > right) { - y = interpolateY(right, {x, y}, p2); - x = right; - } - if (y < top) { - x = interpolateX(top, {x, y}, p2); - y = top; - } - if (y > bottom) { - x = interpolateX(bottom, {x, y}, p2); - y = bottom; - } - return {x, y}; -} - -function limitLineToArea(p1, p2, area) { - const {x, y} = limitPointToArea(p1, p2, area); - const {x: x2, y: y2} = limitPointToArea(p2, p1, area); - return {x, y, x2, y2, width: Math.abs(x2 - x), height: Math.abs(y2 - y)}; -} - export default class LineAnnotation extends Element { - // TODO: make private in v2 - intersects(x, y, epsilon = EPSILON, useFinalPosition) { - // Adapted from https://stackoverflow.com/a/6853926/25507 - const {x: x1, y: y1, x2, y2} = this.getProps(['x', 'y', 'x2', 'y2'], useFinalPosition); - const dx = x2 - x1; - const dy = y2 - y1; - const lenSq = sqr(dx) + sqr(dy); - const t = lenSq === 0 ? -1 : ((x - x1) * dx + (y - y1) * dy) / lenSq; - let xx, yy; - if (t < 0) { - xx = x1; - yy = y1; - } else if (t > 1) { - xx = x2; - yy = y2; - } else { - xx = x1 + t * dx; - yy = y1 + t * dy; - } - return (sqr(x - xx) + sqr(y - yy)) <= epsilon; - } - - /** - * @todo make private in v2 - * @param {boolean} useFinalPosition - use the element's animation target instead of current position - * @param {top, right, bottom, left} [chartArea] - optional, area of the chart - * @returns {boolean} true if the label is visible - */ - labelIsVisible(useFinalPosition, chartArea) { - const labelOpts = this.options.label; - if (!labelOpts || !labelOpts.display) { - return false; - } - return !chartArea || isLineInArea(this.getProps(['x', 'y', 'x2', 'y2'], useFinalPosition), chartArea); - } - - // TODO: make private in v2 - isOnLabel(mouseX, mouseY, useFinalPosition, axis) { - if (!this.labelIsVisible(useFinalPosition)) { - return false; - } - const {labelX, labelY, labelX2, labelY2, labelCenterX, labelCenterY, labelRotation} = this.getProps(['labelX', 'labelY', 'labelX2', 'labelY2', 'labelCenterX', 'labelCenterY', 'labelRotation'], useFinalPosition); - const {x, y} = rotated({x: mouseX, y: mouseY}, {x: labelCenterX, y: labelCenterY}, -toRadians(labelRotation)); - return inBoxRange({x, y}, {x: labelX, y: labelY, x2: labelX2, y2: labelY2}, axis, this.options.label.borderWidth); - } - inRange(mouseX, mouseY, axis, useFinalPosition) { const hBorderWidth = this.options.borderWidth / 2; if (axis !== 'x' && axis !== 'y') { const epsilon = sqr(hBorderWidth); - return this.intersects(mouseX, mouseY, epsilon, useFinalPosition) || this.isOnLabel(mouseX, mouseY, useFinalPosition); + const point = {mouseX, mouseY}; + return intersects(this, point, epsilon, useFinalPosition) || isOnLabel(this, point, useFinalPosition); } const limit = rangeLimit(mouseX, mouseY, this.getProps(['x', 'y', 'x2', 'y2'], useFinalPosition), axis); - return (limit.value >= limit.start - hBorderWidth && limit.value <= limit.end + hBorderWidth) || this.isOnLabel(mouseX, mouseY, useFinalPosition, axis); + return (limit.value >= limit.start - hBorderWidth && limit.value <= limit.end + hBorderWidth) || isOnLabel(this, {mouseX, mouseY}, useFinalPosition, axis); } getCenterPoint(useFinalPosition) { @@ -131,7 +51,7 @@ export default class LineAnnotation extends Element { } drawLabel(ctx, chartArea) { - if (!this.labelIsVisible(false, chartArea)) { + if (!labelIsVisible(this, false, chartArea)) { return; } const {labelX, labelY, labelCenterX, labelCenterY, labelWidth, labelHeight, labelRotation, labelPadding, labelTextSize, options: {label}} = this; @@ -300,6 +220,84 @@ LineAnnotation.defaultRoutes = { borderColor: 'color' }; +function isLineInArea({x, y, x2, y2}, {top, right, bottom, left}) { + return !( + (x < left && x2 < left) || + (x > right && x2 > right) || + (y < top && y2 < top) || + (y > bottom && y2 > bottom) + ); +} + +function limitPointToArea({x, y}, p2, {top, right, bottom, left}) { + if (x < left) { + y = interpolateY(left, {x, y}, p2); + x = left; + } + if (x > right) { + y = interpolateY(right, {x, y}, p2); + x = right; + } + if (y < top) { + x = interpolateX(top, {x, y}, p2); + y = top; + } + if (y > bottom) { + x = interpolateX(bottom, {x, y}, p2); + y = bottom; + } + return {x, y}; +} + +function limitLineToArea(p1, p2, area) { + const {x, y} = limitPointToArea(p1, p2, area); + const {x: x2, y: y2} = limitPointToArea(p2, p1, area); + return {x, y, x2, y2, width: Math.abs(x2 - x), height: Math.abs(y2 - y)}; +} + +function intersects(element, {mouseX, mouseY}, epsilon = EPSILON, useFinalPosition) { + // Adapted from https://stackoverflow.com/a/6853926/25507 + const {x: x1, y: y1, x2, y2} = element.getProps(['x', 'y', 'x2', 'y2'], useFinalPosition); + const dx = x2 - x1; + const dy = y2 - y1; + const lenSq = sqr(dx) + sqr(dy); + const t = lenSq === 0 ? -1 : ((mouseX - x1) * dx + (mouseY - y1) * dy) / lenSq; + let xx, yy; + if (t < 0) { + xx = x1; + yy = y1; + } else if (t > 1) { + xx = x2; + yy = y2; + } else { + xx = x1 + t * dx; + yy = y1 + t * dy; + } + return (sqr(mouseX - xx) + sqr(mouseY - yy)) <= epsilon; +} + +/** + * @param {boolean} useFinalPosition - use the element's animation target instead of current position + * @param {top, right, bottom, left} [chartArea] - optional, area of the chart + * @returns {boolean} true if the label is visible + */ +function labelIsVisible(element, useFinalPosition, chartArea) { + const labelOpts = element.options.label; + if (!labelOpts || !labelOpts.display) { + return false; + } + return !chartArea || isLineInArea(element.getProps(['x', 'y', 'x2', 'y2'], useFinalPosition), chartArea); +} + +function isOnLabel(element, {mouseX, mouseY}, useFinalPosition, axis) { + if (!labelIsVisible(element, useFinalPosition)) { + return false; + } + const {labelX, labelY, labelX2, labelY2, labelCenterX, labelCenterY, labelRotation} = element.getProps(['labelX', 'labelY', 'labelX2', 'labelY2', 'labelCenterX', 'labelCenterY', 'labelRotation'], useFinalPosition); + const {x, y} = rotated({x: mouseX, y: mouseY}, {x: labelCenterX, y: labelCenterY}, -toRadians(labelRotation)); + return inBoxRange({x, y}, {x: labelX, y: labelY, x2: labelX2, y2: labelY2}, axis, element.options.label.borderWidth); +} + function translateArea(source, mapping) { const ret = {}; const keys = Object.keys(mapping);