Align Y axes at zero (#5053)

* Align Y axes as zero

* Fix typo (with @deecay)

* Add alignYAxesAtZero function

* Avoid 0 division

Co-authored-by: Gabriel Dutra <nesk.frz@gmail.com>
This commit is contained in:
Levko Kravets 2020-09-28 13:12:40 +03:00 committed by GitHub
parent a473611cb0
commit 4fb77867b0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 52 additions and 1 deletions

View File

@ -32,6 +32,16 @@ export default function YAxisSettings({ options, onOptionsChange }) {
onChange={axis => onOptionsChange({ yAxis: [leftYAxis, axis] })}
/>
</Section>
<Section>
<Switch
id="chart-editor-y-axis-align-at-zero"
data-test="Chart.YAxis.AlignAtZero"
defaultChecked={options.alignYAxesAtZero}
onChange={alignYAxesAtZero => onOptionsChange({ alignYAxesAtZero })}>
Align Y Axes at Zero
</Switch>
</Section>
</React.Fragment>
)}

View File

@ -5,8 +5,9 @@ const DEFAULT_OPTIONS = {
globalSeriesType: "column",
sortX: true,
legend: { enabled: true, placement: "auto", traceorder: "normal" },
yAxis: [{ type: "linear" }, { type: "linear", opposite: true }],
xAxis: { type: "-", labels: { enabled: true } },
yAxis: [{ type: "linear" }, { type: "linear", opposite: true }],
alignYAxesAtZero: false,
error_y: { type: "data", visible: true },
series: { stacking: null, error_y: { type: "data", visible: true } },
seriesOptions: {},

View File

@ -4,6 +4,42 @@ function calculateAxisRange(range, min, max) {
return [isNumber(min) ? min : range[0], isNumber(max) ? max : range[1]];
}
function calculateAbsoluteDiff(value, totalRange, percentageDiff) {
return (percentageDiff * totalRange) / (1 - Math.abs(value) / totalRange - percentageDiff);
}
function alignYAxesAtZero(axisA, axisB) {
// Make sure the origin is included in both axes
axisA.range[1] = Math.max(0, axisA.range[1]);
axisB.range[1] = Math.max(0, axisB.range[1]);
axisA.range[0] = Math.min(0, axisA.range[0]);
axisB.range[0] = Math.min(0, axisB.range[0]);
const totalRangeA = axisA.range[1] - axisA.range[0];
const proportionA = axisA.range[1] / totalRangeA;
const totalRangeB = axisB.range[1] - axisB.range[0];
const proportionB = axisB.range[1] / totalRangeB;
// Calculate the difference between the proportions and distribute them within the two axes
const diff = Math.abs(proportionB - proportionA) / 2;
// Don't do anything if the difference is too low
if (diff < 0.01) {
return;
}
// Select the two that will correct the proportion by always augmenting, so the chart is not cut
if (proportionA < proportionB) {
// increase axisA max and axisB min
axisA.range[1] += calculateAbsoluteDiff(axisA.range[1], totalRangeA, diff);
axisB.range[0] -= calculateAbsoluteDiff(axisA.range[0], totalRangeB, diff);
} else {
// increase axisB max and axisA min
axisB.range[1] += calculateAbsoluteDiff(axisB.range[1], totalRangeB, diff);
axisA.range[0] -= calculateAbsoluteDiff(axisA.range[0], totalRangeA, diff);
}
}
export default function updateYRanges(plotlyElement, layout, options) {
const updates = {};
if (isObject(layout.yaxis)) {
@ -38,6 +74,10 @@ export default function updateYRanges(plotlyElement, layout, options) {
updates.yaxis2.range = calculateAxisRange(defaultRange, axisOptions.rangeMin, axisOptions.rangeMax);
}
if (options.alignYAxesAtZero && isObject(layout.yaxis) && isObject(layout.yaxis2)) {
alignYAxesAtZero(updates.yaxis, updates.yaxis2);
}
return [updates, null]; // no further updates
},
];