Skip to content
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

add getAnnotations function to plugin and remove _getState private one #892

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions docs/.vuepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ export default defineConfig({
'types/polygon'
]
},
'developers',
{
title: 'Migration',
collapsable: true,
Expand Down
33 changes: 33 additions & 0 deletions docs/guide/developers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Developers

## Access to the annotation elements

The annotation plugin uses Chart.js elements to draw the annotation requested by the user. The following APIs allows the user to get the created annotation elements to use in callbacks or for other purposes.
The APIs are available in the annotation plugin instance.

#### Script Tag

```html
<script>
// get annotation plugin instance
const annotationPlugin = window['chartjs-plugin-annotation'];
</script>
```

#### Bundlers (Webpack, Rollup, etc.)

```javascript
// get annotation plugin instance
import annotationPlugin from 'chartjs-plugin-annotation';
```

### `.getAnnotations(chart: Chart): AnnotationElement[]`

It provides all annotation elements configured by the plugin options, even if the annotations are not visible.

```javascript
const myLineChart = new Chart(ctx, config);
// get all annotation elements
const elements = annotationPlugin.getAnnotations(myLineChart);
```

2 changes: 1 addition & 1 deletion docs/guide/integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ title: Integration
<script src="path/to/chartjs/dist/chart.min.js"></script>
<script src="path/to/chartjs-plugin-annotation/dist/chartjs-plugin-annotation.min.js"></script>
<script>
var myChart = new Chart(ctx, {...});
const myChart = new Chart(ctx, {...});
</script>
```

Expand Down
11 changes: 9 additions & 2 deletions src/annotation.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {handleEvent, eventHooks, updateListeners} from './events';
import {invokeHook, elementHooks, updateHooks} from './hooks';
import {adjustScaleRange, verifyScaleOptions} from './scale';
import {updateElements, resolveType, isIndexable} from './elements';
import {getElements} from './interaction';
import {annotationTypes} from './types';
import {requireVersion} from './helpers';
import {version} from '../package.json';
Expand Down Expand Up @@ -101,8 +102,14 @@ export default {
chartStates.delete(chart);
},

_getState(chart) {
return chartStates.get(chart);
getAnnotations(chart) {
const state = chartStates.get(chart);
return state ? state.elements : [];
},

// only for testing
_getAnnotationElementsAtEventForMode(visibleElements, event, options) {
return getElements(visibleElements, event, options);
},

defaults: {
Expand Down
5 changes: 2 additions & 3 deletions src/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ export const eventHooks = moveHooks.concat('click');
export function updateListeners(chart, state, options) {
state.listened = loadHooks(options, eventHooks, state.listeners);
state.moveListened = false;
state._getElements = getElements; // for testing

moveHooks.forEach(hook => {
if (isFunction(options[hook])) {
Expand Down Expand Up @@ -71,7 +70,7 @@ function handleMoveEvents(state, event, options) {
let elements;

if (event.type === 'mousemove') {
elements = getElements(state, event, options.interaction);
elements = getElements(state.visibleElements, event, options.interaction);
} else {
elements = [];
}
Expand All @@ -96,7 +95,7 @@ function dispatchMoveEvents({state, event}, hook, elements, checkElements) {

function handleClickEvents(state, event, options) {
const listeners = state.listeners;
const elements = getElements(state, event, options.interaction);
const elements = getElements(state.visibleElements, event, options.interaction);
let changed;
for (const element of elements) {
changed = dispatchEvent(element.options.click || listeners.click, element, event) || changed;
Expand Down
38 changes: 19 additions & 19 deletions src/interaction.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,58 +9,58 @@ const interaction = {
modes: {
/**
* Point mode returns all elements that hit test based on the event position
* @param {Object} state - the state of the plugin
* @param {AnnotationElement[]} visibleElements - annotation elements which are visible
* @param {ChartEvent} event - the event we are find things at
* @return {AnnotationElement[]} - elements that are found
*/
point(state, event) {
return filterElements(state, event, {intersect: true});
point(visibleElements, event) {
return filterElements(visibleElements, event, {intersect: true});
},

/**
* Nearest mode returns the element closest to the event position
* @param {Object} state - the state of the plugin
* @param {AnnotationElement[]} visibleElements - annotation elements which are visible
* @param {ChartEvent} event - the event we are find things at
* @param {Object} options - interaction options to use
* @return {AnnotationElement[]} - elements that are found (only 1 element)
*/
nearest(state, event, options) {
return getNearestItem(state, event, options);
nearest(visibleElements, event, options) {
return getNearestItem(visibleElements, event, options);
},
/**
* x mode returns the elements that hit-test at the current x coordinate
* @param {Object} state - the state of the plugin
* @param {AnnotationElement[]} visibleElements - annotation elements which are visible
* @param {ChartEvent} event - the event we are find things at
* @param {Object} options - interaction options to use
* @return {AnnotationElement[]} - elements that are found
*/
x(state, event, options) {
return filterElements(state, event, {intersect: options.intersect, axis: 'x'});
x(visibleElements, event, options) {
return filterElements(visibleElements, event, {intersect: options.intersect, axis: 'x'});
},

/**
* y mode returns the elements that hit-test at the current y coordinate
* @param {Object} state - the state of the plugin
* @param {AnnotationElement[]} visibleElements - annotation elements which are visible
* @param {ChartEvent} event - the event we are find things at
* @param {Object} options - interaction options to use
* @return {AnnotationElement[]} - elements that are found
*/
y(state, event, options) {
return filterElements(state, event, {intersect: options.intersect, axis: 'y'});
y(visibleElements, event, options) {
return filterElements(visibleElements, event, {intersect: options.intersect, axis: 'y'});
}
}
};

/**
* Returns all elements that hit test based on the event position
* @param {Object} state - the state of the plugin
* @param {AnnotationElement[]} visibleElements - annotation elements which are visible
* @param {ChartEvent} event - the event we are find things at
* @param {Object} options - interaction options to use
* @return {AnnotationElement[]} - elements that are found
*/
export function getElements(state, event, options) {
export function getElements(visibleElements, event, options) {
const mode = interaction.modes[options.mode] || interaction.modes.nearest;
return mode(state, event, options);
return mode(visibleElements, event, options);
}

function inRangeByAxis(element, event, axis) {
Expand All @@ -79,14 +79,14 @@ function getPointByAxis(event, center, axis) {
return center;
}

function filterElements(state, event, options) {
return state.visibleElements.filter((element) => options.intersect ? element.inRange(event.x, event.y) : inRangeByAxis(element, event, options.axis));
function filterElements(visibleElements, event, options) {
return visibleElements.filter((element) => options.intersect ? element.inRange(event.x, event.y) : inRangeByAxis(element, event, options.axis));
}

function getNearestItem(state, event, options) {
function getNearestItem(visibleElements, event, options) {
let minDistance = Number.POSITIVE_INFINITY;

return filterElements(state, event, options)
return filterElements(visibleElements, event, options)
.reduce((nearestItems, element) => {
const center = element.getCenterPoint();
const evenPoint = getPointByAxis(event, center, options.axis);
Expand Down
3 changes: 2 additions & 1 deletion test/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {acquireChart, addMatchers, releaseCharts, specsFromFixtures, triggerMouseEvent, afterEvent} from 'chartjs-test-utils';
import {testEvents, eventPoint0, getCenterPoint} from './events';
import {createCanvas, getAnnotationElements, scatterChart, stringifyObject, interactionData, getQuadraticXY, getQuadraticAngle, drawStar} from './utils';
import {createCanvas, getAnnotationElements, getAnnotationInteractedElements, scatterChart, stringifyObject, interactionData, getQuadraticXY, getQuadraticAngle, drawStar} from './utils';
import * as helpers from '../src/helpers';

window.helpers = helpers;
Expand All @@ -14,6 +14,7 @@ window.getCenterPoint = getCenterPoint;
window.createCanvas = createCanvas;
window.drawStar = drawStar;
window.getAnnotationElements = getAnnotationElements;
window.getAnnotationInteractedElements = getAnnotationInteractedElements;
window.scatterChart = scatterChart;
window.stringifyObject = stringifyObject;
window.interactionData = interactionData;
Expand Down
2 changes: 2 additions & 0 deletions test/integration/ts/basic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,5 @@ const chart = new Chart('id', {
},
plugins: [Annotation]
});

const elements = Annotation.getAnnotations(chart);
28 changes: 28 additions & 0 deletions test/specs/annotation.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,34 @@ describe('Annotation plugin', function() {
console.warn = origWarn;
});

it('should return the right amount of annotations elements', function() {
const types = ['box', 'ellipse', 'label', 'line', 'point', 'polygon'];
const annotations = types.map(function(type) {
return {
type,
display: () => type.startsWith('l'),
xMin: 2,
yMin: 2,
xMax: 8,
yMax: 8
};
});

const chart = acquireChart({
type: 'line',
options: {
plugins: {
annotation: {
annotations
}
}
}
});

expect(window.getAnnotationElements(chart).length).toBe(types.length);
expect(window.getAnnotationElements(undefined).length).toBe(0);
});

describe('Annotation option resolution', function() {
it('should resolve from plugin common options', function() {
const chart = acquireChart({
Expand Down
11 changes: 6 additions & 5 deletions test/specs/box.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,11 @@ describe('Box annotation', function() {
};

const chart = window.scatterChart(10, 10, {outer, inner});
const state = window['chartjs-plugin-annotation']._getState(chart);
const elements = window.getAnnotationElements(chart);
const visible = elements.filter(el => !el.skip && el.options.display);
const interactionOpts = {};
const outerEl = window.getAnnotationElements(chart)[0];
const innerEl = window.getAnnotationElements(chart)[1];
const outerEl = elements[0];
const innerEl = elements[1];

it('should return the right amount of annotation elements', function() {
for (const interaction of window.interactionData) {
Expand All @@ -120,8 +121,8 @@ describe('Box annotation', function() {
for (let i = 0; i < points.length; i++) {
const point = points[i];
const elementsCount = elementsCounts[i];
const elements = state._getElements(state, point, interactionOpts);
expect(elements.length).withContext(`with interaction mode ${mode}, axis ${axis}, intersect ${intersect}, {x: ${point.x.toFixed(1)}, y: ${point.y.toFixed(1)}}`).toEqual(elementsCount);
const els = window.getAnnotationInteractedElements(visible, point, interactionOpts);
expect(els.length).withContext(`with interaction mode ${mode}, axis ${axis}, intersect ${intersect}, {x: ${point.x.toFixed(1)}, y: ${point.y.toFixed(1)}}`).toEqual(elementsCount);
}
});
}
Expand Down
11 changes: 6 additions & 5 deletions test/specs/ellipse.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,11 @@ describe('Ellipse annotation', function() {
};

const chart = window.scatterChart(10, 10, {outer, inner});
const state = window['chartjs-plugin-annotation']._getState(chart);
const elements = window.getAnnotationElements(chart);
const visible = elements.filter(el => !el.skip && el.options.display);
const interactionOpts = {};
const outerEl = window.getAnnotationElements(chart)[0];
const innerEl = window.getAnnotationElements(chart)[1];
const outerEl = elements[0];
const innerEl = elements[1];

it('should return the right amount of annotation elements', function() {
for (const interaction of window.interactionData) {
Expand All @@ -120,8 +121,8 @@ describe('Ellipse annotation', function() {
const point = points[i];
const elementsCount = elementsCounts[i];
const {x, y} = rotated(point, point.el.getCenterPoint(), rotation / 180 * Math.PI);
const elements = state._getElements(state, {x, y}, interactionOpts);
expect(elements.length).withContext(`with rotation ${rotation}, interaction mode ${mode}, axis ${axis}, intersect ${intersect}, {x: ${x.toFixed(1)}, y: ${y.toFixed(1)}`).toEqual(elementsCount);
const els = window.getAnnotationInteractedElements(visible, {x, y}, interactionOpts, true);
expect(els.length).withContext(`with rotation ${rotation}, interaction mode ${mode}, axis ${axis}, intersect ${intersect}, {x: ${x.toFixed(1)}, y: ${y.toFixed(1)}`).toEqual(elementsCount);
}
});
}
Expand Down
11 changes: 6 additions & 5 deletions test/specs/label.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,11 @@ describe('Label annotation', function() {
};

const chart = window.scatterChart(10, 10, {outer, inner});
const state = window['chartjs-plugin-annotation']._getState(chart);
const elements = window.getAnnotationElements(chart);
const visible = elements.filter(el => !el.skip && el.options.display);
const interactionOpts = {};
const outerEl = window.getAnnotationElements(chart)[0];
const innerEl = window.getAnnotationElements(chart)[1];
const outerEl = elements[0];
const innerEl = elements[1];

it('should return the right amount of annotation elements', function() {
for (const interaction of window.interactionData) {
Expand All @@ -101,8 +102,8 @@ describe('Label annotation', function() {
for (let i = 0; i < points.length; i++) {
const point = points[i];
const elementsCount = elementsCounts[i];
const elements = state._getElements(state, point, interactionOpts);
expect(elements.length).withContext(`with interaction mode ${mode}, axis ${axis}, intersect ${intersect}, {x: ${point.x.toFixed(1)}, y: ${point.y.toFixed(1)}`).toEqual(elementsCount);
const els = window.getAnnotationInteractedElements(visible, point, interactionOpts);
expect(els.length).withContext(`with interaction mode ${mode}, axis ${axis}, intersect ${intersect}, {x: ${point.x.toFixed(1)}, y: ${point.y.toFixed(1)}`).toEqual(elementsCount);
}
});
}
Expand Down
22 changes: 12 additions & 10 deletions test/specs/line.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,12 +119,13 @@ describe('Line annotation', function() {
};

const chart = window.scatterChart(10, 10, {outer, inner});
const state = window['chartjs-plugin-annotation']._getState(chart);
const elements = window.getAnnotationElements(chart);
const visible = elements.filter(el => !el.skip && el.options.display);
const interactionOpts = {};
const outerEl = window.getAnnotationElements(chart)[0];
const outerEl = elements[0];
const outCenter = outerEl.getCenterPoint();
const outHBordeWidth = outerEl.options.borderWidth / 2;
const innerEl = window.getAnnotationElements(chart)[1];
const innerEl = elements[1];
const inCenter = outerEl.getCenterPoint();
const inHBordeWidth = innerEl.options.borderWidth / 2;

Expand All @@ -147,8 +148,8 @@ describe('Line annotation', function() {
for (let i = 0; i < points.length; i++) {
const point = points[i];
const elementsCount = elementsCounts[i];
const elements = state._getElements(state, point, interactionOpts);
expect(elements.length).withContext(`with interaction mode ${mode}, axis ${axis}, intersect ${intersect}, {x: ${point.x.toFixed(1)}, y: ${point.y.toFixed(1)}`).toEqual(elementsCount);
const els = window.getAnnotationInteractedElements(visible, point, interactionOpts);
expect(els.length).withContext(`with interaction mode ${mode}, axis ${axis}, intersect ${intersect}, {x: ${point.x.toFixed(1)}, y: ${point.y.toFixed(1)}`).toEqual(elementsCount);
}
});
}
Expand Down Expand Up @@ -186,11 +187,12 @@ describe('Line annotation', function() {
};

const chart = window.scatterChart(10, 10, {outer, inner});
const state = window['chartjs-plugin-annotation']._getState(chart);
const elements = window.getAnnotationElements(chart);
const visible = elements.filter(el => !el.skip && el.options.display);
const interactionOpts = {};
const outerEl = window.getAnnotationElements(chart)[0];
const outerEl = elements[0];
const outCenter = outerEl.getCenterPoint();
const innerEl = window.getAnnotationElements(chart)[1];
const innerEl = elements[1];

it('should return the right amount of annotation elements', function() {
for (const interaction of window.interactionData) {
Expand All @@ -211,8 +213,8 @@ describe('Line annotation', function() {
for (let i = 0; i < points.length; i++) {
const point = points[i];
const elementsCount = elementsCounts[i];
const elements = state._getElements(state, point, interactionOpts);
expect(elements.length).withContext(`with interaction mode ${mode}, axis ${axis}, intersect ${intersect}, {x: ${point.x.toFixed(1)}, y: ${point.y.toFixed(1)}`).toEqual(elementsCount);
const els = window.getAnnotationInteractedElements(visible, point, interactionOpts);
expect(els.length).withContext(`with interaction mode ${mode}, axis ${axis}, intersect ${intersect}, {x: ${point.x.toFixed(1)}, y: ${point.y.toFixed(1)}`).toEqual(elementsCount);
}
});
}
Expand Down
Loading
Loading