diff --git a/viz-lib/src/visualizations/chart/Editor/GeneralSettings.jsx b/viz-lib/src/visualizations/chart/Editor/GeneralSettings.jsx
index 1fef28572c..f34464be70 100644
--- a/viz-lib/src/visualizations/chart/Editor/GeneralSettings.jsx
+++ b/viz-lib/src/visualizations/chart/Editor/GeneralSettings.jsx
@@ -165,14 +165,35 @@ export default function GeneralSettings({ options, data, onOptionsChange }) {
)}
{!includes(["custom", "heatmap"], options.globalSeriesType) && (
-
- onOptionsChange({ legend: { enabled: event.target.checked } })}>
- Show Legend
-
-
+
+
+ onOptionsChange({ legend: { enabled: event.target.checked } })}>
+ Show Legend
+
+
+ {options.legend.enabled && (
+
+
+
+ )}
+
)}
{includes(["box"], options.globalSeriesType) && (
diff --git a/viz-lib/src/visualizations/chart/Renderer/PlotlyChart.jsx b/viz-lib/src/visualizations/chart/Renderer/PlotlyChart.jsx
index 6d1cc9e7ef..c2968cef94 100644
--- a/viz-lib/src/visualizations/chart/Renderer/PlotlyChart.jsx
+++ b/viz-lib/src/visualizations/chart/Renderer/PlotlyChart.jsx
@@ -39,7 +39,7 @@ export default function PlotlyChart({ options, data }) {
// It will auto-purge previous graph
Plotly.newPlot(container, plotlyData, plotlyLayout, plotlyOptions).then(
catchErrors(() => {
- applyLayoutFixes(container, plotlyLayout, (e, u) => Plotly.relayout(e, u));
+ applyLayoutFixes(container, plotlyLayout, options, (e, u) => Plotly.relayout(e, u));
}, errorHandler)
);
@@ -58,7 +58,7 @@ export default function PlotlyChart({ options, data }) {
const unwatch = resizeObserver(
container,
catchErrors(() => {
- applyLayoutFixes(container, plotlyLayout, (e, u) => Plotly.relayout(e, u));
+ applyLayoutFixes(container, plotlyLayout, options, (e, u) => Plotly.relayout(e, u));
}, errorHandler)
);
return unwatch;
diff --git a/viz-lib/src/visualizations/chart/getOptions.js b/viz-lib/src/visualizations/chart/getOptions.js
index cbd5cee406..b36dd82586 100644
--- a/viz-lib/src/visualizations/chart/getOptions.js
+++ b/viz-lib/src/visualizations/chart/getOptions.js
@@ -4,7 +4,7 @@ import { visualizationsSettings } from "@/visualizations/visualizationsSettings"
const DEFAULT_OPTIONS = {
globalSeriesType: "column",
sortX: true,
- legend: { enabled: true },
+ legend: { enabled: true, placement: "auto" },
yAxis: [{ type: "linear" }, { type: "linear", opposite: true }],
xAxis: { type: "-", labels: { enabled: true } },
error_y: { type: "data", visible: true },
diff --git a/viz-lib/src/visualizations/chart/plotly/applyLayoutFixes.js b/viz-lib/src/visualizations/chart/plotly/applyLayoutFixes.js
index 0cdcecec2d..5a45be029a 100644
--- a/viz-lib/src/visualizations/chart/plotly/applyLayoutFixes.js
+++ b/viz-lib/src/visualizations/chart/plotly/applyLayoutFixes.js
@@ -14,90 +14,120 @@ function fixLegendContainer(plotlyElement) {
}
}
-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);
-
+function placeLegendNextToPlot(plotlyElement, layout, updatePlot) {
const transformName = find(
["transform", "WebkitTransform", "MozTransform", "MsTransform", "OTransform"],
prop => prop in plotlyElement.style
);
- if (layout.width <= 600) {
- // Save current `layout.height` value because `updatePlot().then(...)` handler may be called multiple
- // times within single update, and since the handler mutates `layout` object - it may lead to bugs
- const layoutHeight = layout.height;
+ 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;
+ }
- // 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.
- // Related issue: https://github.com/plotly/plotly.js/issues/1199
- 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",
- };
+ updatePlot(plotlyElement, pick(layout, ["width", "height", "legend"]));
+}
- // set `overflow: visible` to svg containing legend because later we will
- // position legend outside of it
- fixLegendContainer(plotlyElement);
+function placeLegendBelowPlot(plotlyElement, layout, updatePlot) {
+ const transformName = find(
+ ["transform", "WebkitTransform", "MozTransform", "MsTransform", "OTransform"],
+ prop => prop in plotlyElement.style
+ );
- 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(layoutHeight / 2, layoutHeight - (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",
- };
+ // Save current `layout.height` value because `updatePlot().then(...)` handler may be called multiple
+ // times within single update, and since the handler mutates `layout` object - it may lead to bugs
+ const layoutHeight = layout.height;
- const legend = plotlyElement.querySelector(".legend");
+ // 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.
+ // Related issue: https://github.com/plotly/plotly.js/issues/1199
+ 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) {
- legend.style[transformName] = null;
+ // 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(layoutHeight / 2, layoutHeight - (bounds.bottom - bounds.top)));
+ // offset the legend
+ legend.style[transformName] = "translate(0, " + layout.height + "px)";
+ updatePlot(plotlyElement, pick(layout, ["height"]));
}
+ });
+}
+
+function placeLegendAuto(plotlyElement, layout, updatePlot) {
+ if (layout.width <= 600) {
+ placeLegendBelowPlot(plotlyElement, layout, updatePlot);
+ } else {
+ placeLegendNextToPlot(plotlyElement, layout, updatePlot);
+ }
+}
- updatePlot(plotlyElement, pick(layout, ["width", "height", "legend"]));
+export default function applyLayoutFixes(plotlyElement, layout, options, updatePlot) {
+ // update layout size to plot container
+ layout.width = Math.floor(plotlyElement.offsetWidth);
+ layout.height = Math.floor(plotlyElement.offsetHeight);
+
+ if (options.legend.enabled) {
+ switch (options.legend.placement) {
+ case "auto":
+ placeLegendAuto(plotlyElement, layout, updatePlot);
+ break;
+ case "right":
+ placeLegendNextToPlot(plotlyElement, layout, updatePlot);
+ break;
+ case "below":
+ placeLegendBelowPlot(plotlyElement, layout, updatePlot);
+ break;
+ // no default
+ }
}
}
diff --git a/viz-lib/src/visualizations/chart/plotly/prepareLayout.js b/viz-lib/src/visualizations/chart/plotly/prepareLayout.js
index 78a7f0756d..f5b05d508e 100644
--- a/viz-lib/src/visualizations/chart/plotly/prepareLayout.js
+++ b/viz-lib/src/visualizations/chart/plotly/prepareLayout.js
@@ -1,4 +1,4 @@
-import { filter, has, isNumber, isObject, isUndefined, map, max, min } from "lodash";
+import { filter, isNumber, isObject, isUndefined, map, max, min } from "lodash";
import { getPieDimensions } from "./preparePieData";
function getAxisTitle(axis) {
@@ -122,7 +122,7 @@ export default function prepareLayout(element, options, data) {
width: Math.floor(element.offsetWidth),
height: Math.floor(element.offsetHeight),
autosize: false,
- showlegend: has(options, "legend") ? options.legend.enabled : true,
+ showlegend: options.legend.enabled,
};
switch (options.globalSeriesType) {