Skip to content

Commit

Permalink
Enable stacked scales and with reverse option support (#655)
Browse files Browse the repository at this point in the history
* Change defaults of yMin/Max when missing

* adds test case with category scale

* adds scale.options.reverse checking

* apply review

* applies same config between reverse and no reverse scale

* applies scale dimensions as default for annotation located by a point

* applies reverse logic to line annotation

* fixes cc

* fixes cc2

* fixes lint

* changes documentation

* adds note about the breaking changes to migration guide

* apply review

* changes link to stacked scales

* Update docs/guide/migrationV2.md

Co-authored-by: Jukka Kurkela <jukka.kurkela@gmail.com>

Co-authored-by: Jukka Kurkela <jukka.kurkela@gmail.com>
  • Loading branch information
stockiNail and kurkle committed Apr 4, 2022
1 parent 6bf1a72 commit 17352cd
Show file tree
Hide file tree
Showing 30 changed files with 761 additions and 28 deletions.
1 change: 1 addition & 0 deletions docs/guide/migrationV2.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ A number of changes were made to the configuration options passed to the plugin

* `xScaleID` option default has been changed, now set to `undefined`. If the option is missing, the plugin will try to use the first scale of the chart, configured as `'x'` axis. If more than one scale has been defined in the chart as `'x'` axis, the option is mandatory to select the right scale.
* `yScaleID` option default has been changed, now set to `undefined`. If the option is missing, the plugin will try to use the first scale of the chart, configured as `'y'` axis. If more than one scale has been defined in the chart as `'y'` axis, the option is mandatory to select the right scale.
* When [stacked scales](https://www.chartjs.org/docs/latest/axes/cartesian/#common-options-to-all-cartesian-axes) are used, instead of the whole chart area, the designated scale area is used as fallback for `xMin`, `xMax`, `yMin`, `yMax`, `xValue` or `yValue` options.
2 changes: 1 addition & 1 deletion docs/guide/types/label.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ The following options are available for label annotations.

### General

If one of the axes does not match an axis in the chart, the content will be rendered in the center of the chart. The 2 coordinates, xValue, yValue are optional. If not specified, the content will be rendered in the center of the chart.
If one of the axes does not match an axis in the chart, the content will be rendered in the center of the chart. The 2 coordinates, xValue, yValue are optional. If not specified, the content will be rendered in the center of the scale dimension.

The 4 coordinates, xMin, xMax, yMin, yMax are optional. If not specified, the box is expanded out to the edges in the respective direction and the box size is used to calculated the center of the point. To enable to use the box positioning, the `radius` must be set to `0` or `NaN`.

Expand Down
2 changes: 1 addition & 1 deletion docs/guide/types/point.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ The following options are available for point annotations.

### General

If one of the axes does not match an axis in the chart, the point annotation will take the center of the chart as point. The 2 coordinates, xValue, yValue are optional. If not specified, the point annotation will take the center of the chart as point.
If one of the axes does not match an axis in the chart, the point annotation will take the center of the chart as point. The 2 coordinates, xValue, yValue are optional. If not specified, the point annotation will take the center of the scale dimension as point.

The 4 coordinates, xMin, xMax, yMin, yMax are optional. If not specified, the box is expanded out to the edges in the respective direction and the box size is used to calculated the center of the point. To enable to use the box positioning, the `radius` must be set to `0` or `NaN`.

Expand Down
2 changes: 1 addition & 1 deletion docs/guide/types/polygon.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ The following options are available for polygon annotations.

### General

If one of the axes does not match an axis in the chart, the polygon annotation will take the center of the chart as point. The 2 coordinates, xValue, yValue are optional. If not specified, the polygon annotation will take the center of the chart.
If one of the axes does not match an axis in the chart, the polygon annotation will take the center of the chart as point. The 2 coordinates, xValue, yValue are optional. If not specified, the polygon annotation will take the center of the scale dimension.

The 4 coordinates, xMin, xMax, yMin, yMax are optional. If not specified, the box is expanded out to the edges in the respective direction and the box size is used to calculated the center of the point. To enable to use the box positioning, the `radius` must be set to `0` or `NaN`.

Expand Down
37 changes: 24 additions & 13 deletions src/helpers/helpers.chart.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,21 +42,31 @@ export function retrieveScaleID(scales, options, key) {

/**
* @param {Scale} scale
* @param {{start: number, end: number}} options
* @returns {{start: number, end: number}}
* @param {{min: number, max: number, start: number, end: number}} options
* @returns {{start: number, end: number}|undefined}
*/
function getChartDimensionByScale(scale, options) {
export function getDimensionByScale(scale, options) {
if (scale) {
const min = scaleValue(scale, options.min, options.start);
const max = scaleValue(scale, options.max, options.end);
const reverse = scale.options.reverse;
const start = scaleValue(scale, options.min, reverse ? options.end : options.start);
const end = scaleValue(scale, options.max, reverse ? options.start : options.end);
return {
start: Math.min(min, max),
end: Math.max(min, max)
start,
end
};
}
}

/**
* @param {Scale} scale
* @param {{min: number, max: number, start: number, end: number}} options
* @returns {{start: number, end: number}}
*/
function getChartDimensionByScale(scale, options) {
const result = getDimensionByScale(scale, options) || options;
return {
start: options.start,
end: options.end
start: Math.min(result.start, result.end),
end: Math.max(result.start, result.end)
};
}

Expand All @@ -73,11 +83,11 @@ export function getChartPoint(chart, options) {
let y = chartArea.height / 2;

if (xScale) {
x = scaleValue(xScale, options.xValue, x);
x = scaleValue(xScale, options.xValue, xScale.left + xScale.width / 2);
}

if (yScale) {
y = scaleValue(yScale, options.yValue, y);
y = scaleValue(yScale, options.yValue, yScale.top + yScale.height / 2);
}
return {x, y};
}
Expand All @@ -91,16 +101,17 @@ export function getChartRect(chart, options) {
const scales = chart.scales;
const xScale = scales[retrieveScaleID(scales, options, 'xScaleID')];
const yScale = scales[retrieveScaleID(scales, options, 'yScaleID')];
let {top: y, left: x, bottom: y2, right: x2} = chart.chartArea;

if (!xScale && !yScale) {
return {};
}

let {left: x, right: x2} = xScale || chart.chartArea;
let {top: y, bottom: y2} = yScale || chart.chartArea;
const xDim = getChartDimensionByScale(xScale, {min: options.xMin, max: options.xMax, start: x, end: x2});
x = xDim.start;
x2 = xDim.end;
const yDim = getChartDimensionByScale(yScale, {min: options.yMin, max: options.yMax, start: y, end: y2});
const yDim = getChartDimensionByScale(yScale, {min: options.yMin, max: options.yMax, start: y2, end: y});
y = yDim.start;
y2 = yDim.end;

Expand Down
39 changes: 27 additions & 12 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, toPadding} from 'chart.js/helpers';
import {clamp, scaleValue, rotated, drawBox, drawLabel, measureLabelSize, getRelativePosition, setBorderStyle, setShadowStyle, retrieveScaleID} from '../helpers';
import {PI, toRadians, toPadding, valueOrDefault} from 'chart.js/helpers';
import {clamp, scaleValue, rotated, drawBox, drawLabel, measureLabelSize, getRelativePosition, setBorderStyle, setShadowStyle, 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 @@ -162,34 +162,33 @@ export default class LineAnnotation extends Element {

resolveElementProperties(chart, options) {
const scale = chart.scales[options.scaleID];
let {top: y, left: x, bottom: y2, right: x2} = chart.chartArea;
const area = translate(chart.chartArea, {y: 'top', x: 'left', y2: 'bottom', x2: 'right'});
let min, max;

if (scale) {
min = scaleValue(scale, options.value, NaN);
max = scaleValue(scale, options.endValue, min);
if (scale.isHorizontal()) {
x = min;
x2 = max;
area.x = min;
area.x2 = max;
} else {
y = min;
y2 = max;
area.y = min;
area.y2 = max;
}
} else {
const xScale = chart.scales[retrieveScaleID(chart.scales, options, 'xScaleID')];
const yScale = chart.scales[retrieveScaleID(chart.scales, options, 'yScaleID')];

if (xScale) {
x = scaleValue(xScale, options.xMin, x);
x2 = scaleValue(xScale, options.xMax, x2);
applyScaleValueToDimension(area, xScale, {min: options.xMin, max: options.xMax, start: xScale.left, end: xScale.right, startProp: 'x', endProp: 'x2'});
}

if (yScale) {
y = scaleValue(yScale, options.yMin, y);
y2 = scaleValue(yScale, options.yMax, y2);
applyScaleValueToDimension(area, yScale, {min: options.yMin, max: options.yMax, start: yScale.bottom, end: yScale.top, startProp: 'y', endProp: 'y2'});
}
}
const inside = isLineInArea({x, y, x2, y2}, chart.chartArea);
const {x, y, x2, y2} = area;
const inside = isLineInArea(area, chart.chartArea);
const properties = inside
? limitLineToArea({x, y}, {x: x2, y: y2}, chart.chartArea)
: {x, y, x2, y2, width: Math.abs(x2 - x), height: Math.abs(y2 - y)};
Expand Down Expand Up @@ -305,6 +304,22 @@ LineAnnotation.defaultRoutes = {
borderColor: 'color'
};

function translate(source, mapping) {
const ret = {};
const keys = Object.keys(mapping);
const read = prop => valueOrDefault(source[prop], source[mapping[prop]]);
for (const prop of keys) {
ret[prop] = read(prop);
}
return ret;
}

function applyScaleValueToDimension(area, scale, options) {
const dim = getDimensionByScale(scale, options);
area[options.startProp] = dim.start;
area[options.endProp] = dim.end;
}

function loadLabelRect(line, chart, options) {
// TODO: v2 remove support for xPadding and yPadding
const {padding: lblPadding, xPadding, yPadding, borderWidth} = options;
Expand Down
45 changes: 45 additions & 0 deletions test/fixtures/box/missingXMinMax.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
module.exports = {
tolerance: 0.0055,
config: {
type: 'scatter',
options: {
scales: {
x: {
display: true,
min: -10,
max: 50
},
y: {
display: false,
min: -10,
max: 50
}
},
plugins: {
annotation: {
annotations: {
first: {
type: 'box',
xMax: 1,
backgroundColor: 'rgba(159, 226, 191, 0.5)',
},
second: {
type: 'box',
xMax: 30,
xMin: 1,
backgroundColor: 'rgba(255, 191, 0, 0.5)',
},
third: {
type: 'box',
xMin: 30,
backgroundColor: 'rgba(222, 49, 99, 0.5)',
}
}
}
}
}
},
options: {
spriteText: true
}
};
Binary file added test/fixtures/box/missingXMinMax.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
47 changes: 47 additions & 0 deletions test/fixtures/box/missingXMinMaxCategory.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
module.exports = {
tolerance: 0.0055,
config: {
type: 'bar',
data: {
datasets: [{
data: [0, 0, 0, 0, 0, 0]
}]
},
options: {
scales: {
x: {
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
},
y: {
display: false
}
},
plugins: {
legend: false,
annotation: {
annotations: {
first: {
type: 'box',
xMax: 'February',
backgroundColor: 'rgba(159, 226, 191, 0.5)',
},
second: {
type: 'box',
xMax: 'April',
xMin: 'February',
backgroundColor: 'rgba(255, 191, 0, 0.5)',
},
third: {
type: 'box',
xMin: 'April',
backgroundColor: 'rgba(222, 49, 99, 0.5)',
}
}
}
}
}
},
options: {
spriteText: true
}
};
Binary file added test/fixtures/box/missingXMinMaxCategory.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
48 changes: 48 additions & 0 deletions test/fixtures/box/missingXMinMaxCategoryReverse.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
module.exports = {
tolerance: 0.0055,
config: {
type: 'bar',
data: {
datasets: [{
data: [0, 0, 0, 0, 0, 0]
}]
},
options: {
scales: {
x: {
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
reverse: true
},
y: {
display: false
}
},
plugins: {
legend: false,
annotation: {
annotations: {
first: {
type: 'box',
xMax: 'February',
backgroundColor: 'rgba(159, 226, 191, 0.5)',
},
second: {
type: 'box',
xMax: 'April',
xMin: 'February',
backgroundColor: 'rgba(255, 191, 0, 0.5)',
},
third: {
type: 'box',
xMin: 'April',
backgroundColor: 'rgba(222, 49, 99, 0.5)',
}
}
}
}
}
},
options: {
spriteText: true
}
};
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
57 changes: 57 additions & 0 deletions test/fixtures/box/missingY2MinMaxStacked.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
module.exports = {
tolerance: 0.0055,
config: {
type: 'bar',
data: {
datasets: [{
data: [0, 0, 0, 0, 0, 0]
}]
},
options: {
scales: {
x: {
display: false,
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
},
y: {
display: true,
stack: 'demo',
stackWeight: 2,
min: -10,
max: 50
},
y2: {
type: 'category',
display: true,
labels: ['ON', 'OFF'],
offset: true,
position: 'left',
stack: 'demo',
stackWeight: 1,
}
},
plugins: {
legend: false,
annotation: {
annotations: {
first: {
type: 'box',
yScaleID: 'y2',
yMax: 'OFF',
backgroundColor: 'rgba(159, 226, 191, 0.5)',
},
second: {
type: 'box',
yScaleID: 'y2',
yMin: 'OFF',
backgroundColor: 'rgba(255, 191, 0, 0.5)',
},
}
}
}
}
},
options: {
spriteText: true
}
};
Binary file added test/fixtures/box/missingY2MinMaxStacked.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 17352cd

Please sign in to comment.