Skip to content

Enable box annotation label as label sub-element #725

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Apr 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
}