From b877dc696c7e244df6a6952a4c3124b3b22ba252 Mon Sep 17 00:00:00 2001 From: stockiNail Date: Mon, 25 Apr 2022 22:34:10 +0200 Subject: [PATCH] Enable box annotation label as label sub-element (#725) * Enable box annotation label as label sub-element * overrides label default because box label can have different behaviors * adds bounding box to clip the box label * fixes call to getProps * fixes check if display is disabled * creates always the label sub element * adds note about breaking change to migration guide * adds elements to definition type * apply review about fast path to the label sub element * apply review * fixes CC * fixes CC 2 * changes the elements draw * apply review --- docs/guide/migrationV2.md | 14 +++++ docs/samples/interaction.md | 20 +++--- src/annotation.js | 23 +++---- src/types/box.js | 97 +++++++++++++++++++----------- src/types/label.js | 13 +++- test/fixtures/box/label-dynamic.js | 2 +- types/element.d.ts | 1 + 7 files changed, 110 insertions(+), 60 deletions(-) diff --git a/docs/guide/migrationV2.md b/docs/guide/migrationV2.md index c28a5c0e9..7ebdc5b5f 100644 --- a/docs/guide/migrationV2.md +++ b/docs/guide/migrationV2.md @@ -20,6 +20,20 @@ A number of changes were made to the configuration options passed to the plugin ## Elements +In `chartjs-plugin-annotation` plugin version 2 the label of box annotation is a sub-element. This has changed how to access to the label options. Now the label options are at `element.label.options`. The following example shows how to show and hide the label when the mouse is hovering the box: + +```javascript +type: 'box', +enter: function({element}) { + element.label.options.display = true; + return true; +}, +leave: function({element}) { + element.label.options.display = false; + return true; +}, +``` + `chartjs-plugin-annotation` plugin version 2 hides the following methods in the `line` annotation element because they should be used only internally: * `intersects` diff --git a/docs/samples/interaction.md b/docs/samples/interaction.md index bb86a323d..15ef272d9 100644 --- a/docs/samples/interaction.md +++ b/docs/samples/interaction.md @@ -30,16 +30,16 @@ const annotation1 = { borderColor: 'rgb(255, 245, 157)', borderWidth: 2, enter: function({element}) { - console.log(element.options.label.content + ' entered'); - element.options.label.font.size = 14; + console.log(element.label.options.content + ' entered'); + element.label.options.font.size = 14; return true; }, click: function({element}) { - console.log(element.options.label.content + ' clicked'); + console.log(element.label.options.content + ' clicked'); }, leave: function({element}) { - console.log(element.options.label.content + ' left'); - element.options.label.font.size = 12; + console.log(element.label.options.content + ' left'); + element.label.options.font.size = 12; return true; }, label: { @@ -68,16 +68,16 @@ const annotation2 = { borderColor: 'rgb(165, 214, 167)', borderWidth: 2, enter: function({element}) { - console.log(element.options.label.content + ' entered'); - element.options.label.font.size = 14; + console.log(element.label.options.content + ' entered'); + element.label.options.font.size = 14; return true; }, click: function({element}) { - console.log(element.options.label.content + ' clicked'); + console.log(element.label.options.content + ' clicked'); }, leave: function({element}) { - console.log(element.options.label.content + ' left'); - element.options.label.font.size = 12; + console.log(element.label.options.content + ' left'); + element.label.options.font.size = 12; return true; }, label: { diff --git a/src/annotation.js b/src/annotation.js index 45d25ab69..7568c06d4 100644 --- a/src/annotation.js +++ b/src/annotation.js @@ -135,15 +135,16 @@ export default { }; function draw(chart, caller, clip) { - const {ctx, chartArea} = chart; + const {ctx, canvas, chartArea} = chart; const {visibleElements} = chartStates.get(chart); + let box = {x: 0, y: 0, width: canvas.width, height: canvas.height}; if (clip) { clipArea(ctx, chartArea); + box = {x: chartArea.left, y: chartArea.top, width: chartArea.width, height: chartArea.height}; } - drawElements(ctx, visibleElements, caller); - drawSubElements(ctx, visibleElements, caller); + drawElements(ctx, visibleElements, caller, box); if (clip) { unclipArea(ctx); @@ -160,18 +161,18 @@ function draw(chart, caller, clip) { }); } -function drawElements(ctx, elements, caller) { +function drawElements(ctx, elements, caller, area) { for (const el of elements) { if (el.options.drawTime === caller) { el.draw(ctx); } - } -} - -function drawSubElements(ctx, elements, caller) { - for (const el of elements) { - if (isArray(el.elements)) { - drawElements(ctx, el.elements, caller); + if (el.elements && el.elements.length) { + const box = 'getBoundingBox' in el ? el.getBoundingBox() : area; + for (const sub of el.elements) { + if (sub.options.drawTime === caller) { + sub.draw(ctx, box); + } + } } } } diff --git a/src/types/box.js b/src/types/box.js index af77f9ed3..5739bc5ce 100644 --- a/src/types/box.js +++ b/src/types/box.js @@ -1,6 +1,6 @@ import {Element} from 'chart.js'; import {toPadding, toRadians} from 'chart.js/helpers'; -import {drawBox, drawLabel, getRelativePosition, measureLabelSize, resolveBoxProperties, toPosition, inBoxRange, rotated, translate, getElementCenterPoint} from '../helpers'; +import {drawBox, getRelativePosition, measureLabelSize, resolveBoxProperties, toPosition, inBoxRange, rotated, translate, getElementCenterPoint} from '../helpers'; export default class BoxAnnotation extends Element { @@ -20,32 +20,34 @@ export default class BoxAnnotation extends Element { ctx.restore(); } - drawLabel(ctx) { - const {x, y, width, height, options} = this; - const {label, borderWidth} = options; - const halfBorder = borderWidth / 2; - const position = toPosition(label.position); + getBoundingBox() { + const {x, y, width, height} = this.getProps(['x', 'y', 'width', 'height']); + const label = this.options.label; const padding = toPadding(label.padding); - const labelSize = measureLabelSize(ctx, label); - const labelRect = { - x: calculateX(this, labelSize, position, padding), - y: calculateY(this, labelSize, position, padding), - width: labelSize.width, - height: labelSize.height + const borderWidth = this.options.borderWidth; + const halfBorder = borderWidth / 2; + return { + x: x + halfBorder + padding.left, + y: y + halfBorder + padding.top, + width: width - borderWidth - padding.width, + height: height - borderWidth - padding.height }; + } - ctx.save(); - translate(ctx, this.getCenterPoint(), label.rotation); - ctx.beginPath(); - ctx.rect(x + halfBorder + padding.left, y + halfBorder + padding.top, - width - borderWidth - padding.width, height - borderWidth - padding.height); - ctx.clip(); - drawLabel(ctx, labelRect, label); - ctx.restore(); + get label() { + return this.elements && this.elements[0]; } resolveElementProperties(chart, options) { - return resolveBoxProperties(chart, options); + const properties = resolveBoxProperties(chart, options); + const {x, y} = properties; + properties.elements = [{ + type: 'label', + optionScope: 'label', + properties: resolveLabelElementProperties(chart, properties, options) + }]; + properties.initProperties = {x, y}; + return properties; } } @@ -63,7 +65,11 @@ BoxAnnotation.defaults = { borderWidth: 1, display: true, label: { - borderWidth: undefined, + backgroundColor: 'transparent', + borderWidth: 0, + callout: { + display: false + }, color: 'black', content: null, display: false, @@ -109,31 +115,52 @@ BoxAnnotation.descriptors = { } }; -function calculateX(box, labelSize, position, padding) { - const {x: start, x2: end, width: size, options} = box; - const {xAdjust: adjust, borderWidth} = options.label; - return calculatePosition({start, end, size}, { +function calculateX({properties, options}, labelSize, position, padding) { + const {x: start, x2: end, width: size} = properties; + return calculatePosition({start, end, size, borderWidth: options.borderWidth}, { position: position.x, padding: {start: padding.left, end: padding.right}, - adjust, borderWidth, + adjust: options.label.xAdjust, size: labelSize.width }); } -function calculateY(box, labelSize, position, padding) { - const {y: start, y2: end, height: size, options} = box; - const {yAdjust: adjust, borderWidth} = options.label; - return calculatePosition({start, end, size}, { +function calculateY({properties, options}, labelSize, position, padding) { + const {y: start, y2: end, height: size} = properties; + return calculatePosition({start, end, size, borderWidth: options.borderWidth}, { position: position.y, padding: {start: padding.top, end: padding.bottom}, - adjust, borderWidth, + adjust: options.label.yAdjust, size: labelSize.height }); } function calculatePosition(boxOpts, labelOpts) { - const {start, end} = boxOpts; - const {position, padding: {start: padStart, end: padEnd}, adjust, borderWidth} = labelOpts; + const {start, end, borderWidth} = boxOpts; + const {position, padding: {start: padStart, end: padEnd}, adjust} = labelOpts; const availableSize = end - borderWidth - start - padStart - padEnd - labelOpts.size; - return start + borderWidth / 2 + adjust + padStart + getRelativePosition(availableSize, position); + return start + borderWidth / 2 + adjust + getRelativePosition(availableSize, position); +} + +function resolveLabelElementProperties(chart, properties, options) { + const label = options.label; + label.backgroundColor = 'transparent'; + label.callout.display = false; + const position = toPosition(label.position); + const padding = toPadding(label.padding); + const labelSize = measureLabelSize(chart.ctx, label); + const x = calculateX({properties, options}, labelSize, position, padding); + const y = calculateY({properties, options}, labelSize, position, padding); + const width = labelSize.width + padding.width; + const height = labelSize.height + padding.height; + return { + x, + y, + x2: x + width, + y2: y + height, + width, + height, + centerX: x + width / 2, + centerY: y + height / 2 + }; } diff --git a/src/types/label.js b/src/types/label.js index abdf792ce..240ad30b4 100644 --- a/src/types/label.js +++ b/src/types/label.js @@ -1,6 +1,6 @@ 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'; +import {toPadding, toRadians, distanceBetweenPoints, isObject} from 'chart.js/helpers'; const positions = ['left', 'bottom', 'top', 'right']; @@ -15,15 +15,22 @@ export default class LabelAnnotation extends Element { return getElementCenterPoint(this, useFinalPosition); } - draw(ctx) { + draw(ctx, box) { const options = this.options; - if (!options.content) { + if (!options.display || !options.content) { return; } ctx.save(); translate(ctx, this.getCenterPoint(), options.rotation); drawCallout(ctx, this); drawBox(ctx, this, options); + if (isObject(box)) { + const {x, y, width, height} = box; + // clip + ctx.beginPath(); + ctx.rect(x, y, width, height); + ctx.clip(); + } drawLabel(ctx, getLabelSize(this), options); ctx.restore(); } diff --git a/test/fixtures/box/label-dynamic.js b/test/fixtures/box/label-dynamic.js index d99e14c5d..8125c5832 100644 --- a/test/fixtures/box/label-dynamic.js +++ b/test/fixtures/box/label-dynamic.js @@ -31,7 +31,7 @@ module.exports = { content: 'This is dynamic!', }, enter({element}) { - element.options.label.display = true; + element.label.options.display = true; return true; } }, diff --git a/types/element.d.ts b/types/element.d.ts index 62cc04116..557aa4aa4 100644 --- a/types/element.d.ts +++ b/types/element.d.ts @@ -12,5 +12,6 @@ export interface AnnotationBoxModel { } export interface AnnotationElement extends AnnotationBoxModel { + label?: AnnotationElement, options: AnnotationOptions }