Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
5 changes: 2 additions & 3 deletions docs/guide/migrationV2.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ 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:
In `chartjs-plugin-annotation` plugin version 2 the label of box and line annotations 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',
type: 'box', // or 'line'
enter: function({element}) {
element.label.options.display = true;
return true;
Expand All @@ -37,7 +37,6 @@ leave: function({element}) {
`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.
Expand Down
4 changes: 2 additions & 2 deletions docs/samples/line/labelVisibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,11 @@ const annotation1 = {
// For simple property changes, you can directly modify the annotation
// element's properties then return true to force chart re-drawing. This is faster.
enter({element}, event) {
element.options.label.display = true;
element.label.options.display = true;
return true;
},
leave({element}, event) {
element.options.label.display = false;
element.label.options.display = false;
return true;
}
};
Expand Down
19 changes: 5 additions & 14 deletions src/annotation.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,33 +144,24 @@ function draw(chart, caller, clip) {
box = {x: chartArea.left, y: chartArea.top, width: chartArea.width, height: chartArea.height};
}

drawElements(ctx, visibleElements, caller, box);
drawElements(chart, visibleElements, caller, box);

if (clip) {
unclipArea(ctx);
}

visibleElements.forEach(el => {
if (!('drawLabel' in el)) {
return;
}
const label = el.options.label;
if (label && label.display && label.content && (label.drawTime || el.options.drawTime) === caller) {
el.drawLabel(ctx, chartArea);
}
});
}

function drawElements(ctx, elements, caller, area) {
function drawElements(chart, elements, caller, area) {
for (const el of elements) {
if (el.options.drawTime === caller) {
el.draw(ctx);
el.draw(chart.ctx);
}
if (el.elements && el.elements.length) {
const box = 'getBoundingBox' in el ? el.getBoundingBox() : area;
const labelIsVisible = 'labelIsVisible' in el ? el.labelIsVisible(false, chart.chartArea) : true;
for (const sub of el.elements) {
if (sub.options.drawTime === caller) {
sub.draw(ctx, box);
sub.draw(chart.ctx, box, labelIsVisible);
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/elements.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ function updateSubElements(mainElement, {elements, initProperties}, resolver, an
const properties = definition.properties;
const subElement = getOrCreateElement(subElements, i, definition.type, initProperties);
const subResolver = resolver[definition.optionScope].override(definition);
properties.options = resolveAnnotationOptions(subResolver);
properties.options = resolveAnnotationOptions(subResolver, resolver);
animations.update(subElement, properties);
}
}
Expand All @@ -103,12 +103,12 @@ function getOrCreateElement(elements, index, type, initProperties) {
return element;
}

function resolveAnnotationOptions(resolver) {
function resolveAnnotationOptions(resolver, mainResolver) {
const elementClass = annotationTypes[resolveType(resolver.type)];
const result = {};
result.id = resolver.id;
result.type = resolver.type;
result.drawTime = resolver.drawTime;
result.drawTime = resolver.drawTime || (mainResolver && mainResolver.drawTime);
Object.assign(result,
resolveObj(resolver, elementClass.defaults),
resolveObj(resolver, elementClass.defaultRoutes));
Expand Down
9 changes: 5 additions & 4 deletions src/types/label.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,22 @@ const positions = ['left', 'bottom', 'top', 'right'];
export default class LabelAnnotation extends Element {

inRange(mouseX, mouseY, axis, useFinalPosition) {
const {x, y} = rotated({x: mouseX, y: mouseY}, this.getCenterPoint(useFinalPosition), toRadians(-this.options.rotation));
const rotation = this.rotation || this.options.rotation;
const {x, y} = rotated({x: mouseX, y: mouseY}, this.getCenterPoint(useFinalPosition), toRadians(-rotation));
return inBoxRange({x, y}, this.getProps(['x', 'y', 'x2', 'y2'], useFinalPosition), axis, this.options.borderWidth);
}

getCenterPoint(useFinalPosition) {
return getElementCenterPoint(this, useFinalPosition);
}

draw(ctx, box) {
draw(ctx, box, labelIsVisible = true) {
const options = this.options;
if (!options.display || !options.content) {
if (!labelIsVisible || !options.display || !options.content) {
return;
}
ctx.save();
translate(ctx, this.getCenterPoint(), options.rotation);
translate(ctx, this.getCenterPoint(), this.rotation || options.rotation);
drawCallout(ctx, this);
drawBox(ctx, this, options);
if (isObject(box)) {
Expand Down
92 changes: 32 additions & 60 deletions src/types/line.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {Element} from 'chart.js';
import {PI, toRadians, toDegrees, toPadding, valueOrDefault} from 'chart.js/helpers';
import {EPSILON, clamp, scaleValue, rotated, drawBox, drawLabel, measureLabelSize, getRelativePosition, setBorderStyle, setShadowStyle, translate, getElementCenterPoint, inBoxRange, retrieveScaleID, getDimensionByScale} from '../helpers';
import {EPSILON, clamp, scaleValue, measureLabelSize, getRelativePosition, setBorderStyle, setShadowStyle, getElementCenterPoint, retrieveScaleID, getDimensionByScale} from '../helpers';

const pointInLine = (p1, p2, t) => ({x: p1.x + t * (p2.x - p1.x), y: p1.y + t * (p2.y - p1.y)});
const interpolateX = (y, p1, p2) => pointInLine(p1, p2, Math.abs((y - p1.y) / (p2.y - p1.y))).x;
Expand Down Expand Up @@ -50,31 +50,21 @@ export default class LineAnnotation extends Element {
ctx.restore();
}

drawLabel(ctx, chartArea) {
if (!labelIsVisible(this, false, chartArea)) {
return;
}
const {labelX, labelY, labelCenterX, labelCenterY, labelWidth, labelHeight, labelRotation, labelPadding, labelTextSize, options: {label}} = this;
get label() {
return this.elements && this.elements[0];
}

ctx.save();
translate(ctx, {x: labelCenterX, y: labelCenterY}, labelRotation);

const boxRect = {
x: labelX,
y: labelY,
width: labelWidth,
height: labelHeight
};
drawBox(ctx, boxRect, label);

const labelTextRect = {
x: labelX + labelPadding.left + label.borderWidth / 2,
y: labelY + labelPadding.top + label.borderWidth / 2,
width: labelTextSize.width,
height: labelTextSize.height
};
drawLabel(ctx, labelTextRect, label);
ctx.restore();
/**
* @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.label.options;
if (!labelOpts || !labelOpts.display) {
return false;
}
return !chartArea || isLineInArea(this.getProps(['x', 'y', 'x2', 'y2'], useFinalPosition), chartArea);
}

resolveElementProperties(chart, options) {
Expand Down Expand Up @@ -111,11 +101,11 @@ export default class LineAnnotation extends Element {
: {x, y, x2, y2, width: Math.abs(x2 - x), height: Math.abs(y2 - y)};
properties.centerX = (x2 + x) / 2;
properties.centerY = (y2 + y) / 2;

const label = options.label;
if (label && label.content) {
return loadLabelRect(properties, chart, label);
}
properties.elements = [{
type: 'label',
optionScope: 'label',
properties: resolveLabelElementProperties(chart, properties, options.label)
}];
return properties;
}
}
Expand Down Expand Up @@ -166,6 +156,9 @@ LineAnnotation.defaults = {
borderRadius: 6,
borderShadowColor: 'transparent',
borderWidth: 0,
callout: {
display: false
},
color: '#fff',
content: null,
display: false,
Expand Down Expand Up @@ -276,26 +269,11 @@ function intersects(element, {mouseX, mouseY}, epsilon = EPSILON, useFinalPositi
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)) {
if (!element.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);
return element.label.inRange(mouseX, mouseY, axis, useFinalPosition);
}

function translateArea(source, mapping) {
Expand All @@ -314,25 +292,19 @@ function applyScaleValueToDimension(area, scale, options) {
area[options.endProp] = dim.end;
}

function loadLabelRect(properties, chart, options) {
function resolveLabelElementProperties(chart, properties, options) {
// TODO to remove by another PR to enable callout for line label
options.callout.display = false;
const borderWidth = options.borderWidth;
const padding = toPadding(options.padding);
const textSize = measureLabelSize(chart.ctx, options);
const width = textSize.width + padding.width + borderWidth;
const height = textSize.height + padding.height + borderWidth;
const labelRect = calculateLabelPosition(properties, options, {width, height, padding}, chart.chartArea);
properties.labelX = labelRect.x;
properties.labelY = labelRect.y;
properties.labelX2 = labelRect.x2;
properties.labelY2 = labelRect.y2;
properties.labelCenterX = labelRect.centerX;
properties.labelCenterY = labelRect.centerY;
properties.labelWidth = labelRect.width;
properties.labelHeight = labelRect.height;
properties.labelRotation = toDegrees(labelRect.rotation);
properties.labelPadding = padding;
properties.labelTextSize = textSize;
return properties;
return {
...labelRect,
rotation: toDegrees(labelRect.rotation)
};
}

function calculateAutoRotation(properties) {
Expand Down
4 changes: 2 additions & 2 deletions test/fixtures/line/label-dynamic-hide.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@ module.exports = {
display: false
},
enter({element}) {
element.options.label.display = true;
element.label.options.display = true;
return true;
},
leave({element}) {
element.options.label.display = false;
element.label.options.display = false;
return true;
}
},
Expand Down
2 changes: 1 addition & 1 deletion test/fixtures/line/label-dynamic-show.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ module.exports = {
display: false
},
enter({element}) {
element.options.label.display = true;
element.label.options.display = true;
return true;
}
},
Expand Down
28 changes: 14 additions & 14 deletions test/specs/line.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,12 @@ describe('Line annotation', function() {
it('should return true inside label of element', function() {
for (const borderWidth of [0, 10]) {
const halfBorder = borderWidth / 2;
element.options.label.borderWidth = borderWidth;
element.label.options.borderWidth = borderWidth;
const rad = rotation / 180 * Math.PI;
for (const ax of [element.labelX - halfBorder, element.labelX2 + halfBorder]) {
for (const ay of [element.labelY - halfBorder, element.labelY2 + halfBorder]) {
for (const ax of [element.label.x - halfBorder, element.label.x2 + halfBorder]) {
for (const ay of [element.label.y - halfBorder, element.label.y2 + halfBorder]) {
const {x, y} = rotated({x: ax, y: ay},
{x: element.labelCenterX, y: element.labelCenterY}, rad);
{x: element.label.centerX, y: element.label.centerY}, rad);
expect(element.inRange(x, y)).withContext(`rotation: ${rotation}, borderWidth: ${borderWidth}, {x: ${x.toFixed(1)}, y: ${y.toFixed(1)}}`).toEqual(true);
}
}
Expand All @@ -86,12 +86,12 @@ describe('Line annotation', function() {
it('should return false outside label of element', function() {
for (const borderWidth of [0, 10]) {
const halfBorder = borderWidth / 2;
element.options.label.borderWidth = borderWidth;
element.label.options.borderWidth = borderWidth;
const rad = rotation / 180 * Math.PI;
for (const ax of [element.labelX - halfBorder - 1, element.labelX2 + halfBorder + 1]) {
for (const ay of [element.labelY - halfBorder - 1, element.labelY2 + halfBorder + 1]) {
for (const ax of [element.label.x - halfBorder - 1, element.label.x2 + halfBorder + 1]) {
for (const ay of [element.label.y - halfBorder - 1, element.label.y2 + halfBorder + 1]) {
const {x, y} = rotated({x: ax, y: ay},
{x: element.labelCenterX, y: element.labelCenterY}, rad);
{x: element.label.centerX, y: element.label.centerY}, rad);
expect(element.inRange(x, y)).withContext(`rotation: ${rotation}, borderWidth: ${borderWidth}, {x: ${x.toFixed(1)}, y: ${y.toFixed(1)}}`).toEqual(false);
}
}
Expand Down Expand Up @@ -201,12 +201,12 @@ describe('Line annotation', function() {
[true, false].forEach(function(intersect) {
interactionOpts.intersect = intersect;
const elementsCounts = interaction.axes[axis].intersect[intersect];
const points = [{x: outerEl.labelX, y: outerEl.labelY},
{x: innerEl.labelX, y: innerEl.labelY},
{x: innerEl.labelCenterX, y: innerEl.labelCenterY},
{x: innerEl.labelX2 + 1, y: innerEl.labelY},
{x: outerEl.labelX2 + 1, y: outerEl.labelY},
{x: outerEl.labelX + 1, y: outCenter.y - outerEl.height / 2 - 1}];
const points = [{x: outerEl.label.x, y: outerEl.label.y},
{x: innerEl.label.x, y: innerEl.label.y},
{x: innerEl.label.centerX, y: innerEl.label.centerY},
{x: innerEl.label.x2 + 1, y: innerEl.label.y},
{x: outerEl.label.x2 + 1, y: outerEl.label.y},
{x: outerEl.label.x + 1, y: outCenter.y - outerEl.height / 2 - 1}];

for (let i = 0; i < points.length; i++) {
const point = points[i];
Expand Down