Skip to content

Commit

Permalink
Enable box annotation label as label sub-element (#725)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
stockiNail authored Apr 25, 2022
1 parent 5d9042c commit b877dc6
Show file tree
Hide file tree
Showing 7 changed files with 110 additions and 60 deletions.
14 changes: 14 additions & 0 deletions docs/guide/migrationV2.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
20 changes: 10 additions & 10 deletions docs/samples/interaction.md
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down Expand Up @@ -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: {
Expand Down
23 changes: 12 additions & 11 deletions src/annotation.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
}
}
}
}
}
97 changes: 62 additions & 35 deletions src/types/box.js
Original file line number Diff line number Diff line change
@@ -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 {

Expand All @@ -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;
}
}

Expand All @@ -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,
Expand Down Expand Up @@ -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
};
}
13 changes: 10 additions & 3 deletions src/types/label.js
Original file line number Diff line number Diff line change
@@ -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'];

Expand All @@ -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();
}
Expand Down
2 changes: 1 addition & 1 deletion test/fixtures/box/label-dynamic.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ module.exports = {
content: 'This is dynamic!',
},
enter({element}) {
element.options.label.display = true;
element.label.options.display = true;
return true;
}
},
Expand Down
1 change: 1 addition & 0 deletions types/element.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ export interface AnnotationBoxModel {
}

export interface AnnotationElement extends AnnotationBoxModel {
label?: AnnotationElement,
options: AnnotationOptions
}

0 comments on commit b877dc6

Please sign in to comment.