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'])); } }