Files
redash/client/app/visualizations/chart/plotly/applyLayoutFixes.js
Levko Kravets 2c98f0425d Allow the user to decide how to handle null values in charts (#4071)
* getredash/redash#2629 Refactor Chart visualization, add option for handling NULL values (keep/convert to 0.0)

* Handle null values in line/area stacking code; some cleanup

* Handle edge case: line/area stacking when last value of one of series is missing

* Mjnor update to line/area stacking code

* Fix line/area normalize to percents feature

* Unit tests

* Refine tests; add tests for prepareLayout function

* Tests for prepareData (heatmap) function

* Tests for prepareData (pie) function

* Tests for prepareData (bar, line, area) function

* Tests for prepareData (scatter, bubble) function

* Tests for prepareData (box) function

* Remove unused file
2019-09-09 13:00:26 +03:00

101 lines
3.6 KiB
JavaScript

import { find, pick, reduce } from 'lodash';
function fixLegendContainer(plotlyElement) {
const legend = plotlyElement.querySelector('.legend');
if (legend) {
let node = legend.parentNode;
while (node) {
if (node.tagName.toLowerCase() === 'svg') {
node.style.overflow = 'visible';
break;
}
node = node.parentNode;
}
}
}
export default function applyLayoutFixes(plotlyElement, layout, updatePlot) {
// update layout size to plot container
layout.width = Math.floor(plotlyElement.offsetWidth);
layout.height = Math.floor(plotlyElement.offsetHeight);
const transformName = find([
'transform',
'WebkitTransform',
'MozTransform',
'MsTransform',
'OTransform',
], prop => prop in plotlyElement.style);
if (layout.width <= 600) {
// change legend orientation to horizontal; plotly has a bug with this
// legend alignment - it does not preserve enough space under the plot;
// so we'll hack this: update plot (it will re-render legend), compute
// legend height, reduce plot size by legend height (but not less than
// half of plot container's height - legend will have max height equal to
// plot height), re-render plot again and offset legend to the space under
// the plot.
layout.legend = {
orientation: 'h',
// locate legend inside of plot area - otherwise plotly will preserve
// some amount of space under the plot; also this will limit legend height
// to plot's height
y: 0,
x: 0,
xanchor: 'left',
yanchor: 'bottom',
};
// set `overflow: visible` to svg containing legend because later we will
// position legend outside of it
fixLegendContainer(plotlyElement);
updatePlot(plotlyElement, pick(layout, ['width', 'height', 'legend'])).then(() => {
const legend = plotlyElement.querySelector('.legend'); // eslint-disable-line no-shadow
if (legend) {
// compute real height of legend - items may be split into few columnns,
// also scrollbar may be shown
const bounds = reduce(legend.querySelectorAll('.traces'), (result, node) => {
const b = node.getBoundingClientRect();
result = result || b;
return {
top: Math.min(result.top, b.top),
bottom: Math.max(result.bottom, b.bottom),
};
}, null);
// here we have two values:
// 1. height of plot container excluding height of legend items;
// it may be any value between 0 and plot container's height;
// 2. half of plot containers height. Legend cannot be larger than
// plot; if legend is too large, plotly will reduce it's height and
// show a scrollbar; in this case, height of plot === height of legend,
// so we can split container's height half by half between them.
layout.height = Math.floor(Math.max(
layout.height / 2,
layout.height - (bounds.bottom - bounds.top),
));
// offset the legend
legend.style[transformName] = 'translate(0, ' + layout.height + 'px)';
updatePlot(plotlyElement, pick(layout, ['height']));
}
});
} else {
layout.legend = {
orientation: 'v',
// vertical legend will be rendered properly, so just place it to the right
// side of plot
y: 1,
x: 1,
xanchor: 'left',
yanchor: 'top',
};
const legend = plotlyElement.querySelector('.legend');
if (legend) {
legend.style[transformName] = null;
}
updatePlot(plotlyElement, pick(layout, ['width', 'height', 'legend']));
}
}