forked from getredash/redash
-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix wrong Y-axis range for stacked bar chart (getredash#5029)
* getredash#5026 Fix wrong Y-axis range for stacked bar chart * Update tests * Use Plotly's built-in algorinthm to compute Y-axis range * Update tests * Revert previous solution (yRange-related code) * Revert other unrelated changes * Revert other unrelated changes * Move chart rendering to own file and ensure that rendering steps will occur in necessary order * Reduce amount of plot updates by mergin separate updates into a sigle cumulative update * Give better names for several functions
- Loading branch information
1 parent
75424e4
commit e992f8c
Showing
14 changed files
with
284 additions
and
164 deletions.
There are no files selected for viewing
99 changes: 30 additions & 69 deletions
99
viz-lib/src/visualizations/chart/Renderer/PlotlyChart.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
import { isArray, isObject, isString, isFunction, startsWith, reduce, merge, map, each } from "lodash"; | ||
import resizeObserver from "@/services/resizeObserver"; | ||
import { Plotly, prepareData, prepareLayout, updateData, updateYRanges, updateChartSize } from "../plotly"; | ||
|
||
function createErrorHandler(errorHandler) { | ||
return error => { | ||
// This error happens only when chart width is 20px and looks that | ||
// it's safe to just ignore it: 1px less or more and chart will get fixed. | ||
if (isString(error) && startsWith(error, "ax.dtick error")) { | ||
return; | ||
} | ||
errorHandler(error); | ||
}; | ||
} | ||
|
||
// This utility is intended to reduce amount of plot updates when multiple Plotly.relayout | ||
// calls needed in order to compute/update the plot. | ||
// `.append()` method takes an array of two element: first one is a object with updates for layout, | ||
// and second is an optional function that will be called when plot is updated. That function may | ||
// return an array with same structure if further updates needed. | ||
// `.process()` merges all updates into a single object and calls `Plotly.relayout()`. After that | ||
// it calls all callbacks, collects their return values and does another loop if needed. | ||
function initPlotUpdater() { | ||
let actions = []; | ||
|
||
const updater = { | ||
append(action) { | ||
if (isArray(action) && isObject(action[0])) { | ||
actions.push(action); | ||
} | ||
return updater; | ||
}, | ||
process(plotlyElement) { | ||
if (actions.length > 0) { | ||
const updates = reduce(actions, (updates, action) => merge(updates, action[0]), {}); | ||
const handlers = map(actions, action => (isFunction(action[1]) ? action[1] : () => null)); | ||
actions = []; | ||
return Plotly.relayout(plotlyElement, updates).then(() => { | ||
each(handlers, handler => updater.append(handler())); | ||
return updater.process(plotlyElement); | ||
}); | ||
} else { | ||
return Promise.resolve(); | ||
} | ||
}, | ||
}; | ||
|
||
return updater; | ||
} | ||
|
||
export default function initChart(container, options, data, additionalOptions, onError) { | ||
const handleError = createErrorHandler(onError); | ||
|
||
const plotlyOptions = { | ||
showLink: false, | ||
displaylogo: false, | ||
}; | ||
|
||
if (additionalOptions.hidePlotlyModeBar) { | ||
plotlyOptions.displayModeBar = false; | ||
} | ||
|
||
const plotlyData = prepareData(data, options); | ||
const plotlyLayout = prepareLayout(container, options, plotlyData); | ||
|
||
let isDestroyed = false; | ||
|
||
let updater = initPlotUpdater(); | ||
|
||
function createSafeFunction(fn) { | ||
return (...args) => { | ||
if (!isDestroyed) { | ||
try { | ||
return fn(...args); | ||
} catch (error) { | ||
handleError(error); | ||
} | ||
} | ||
}; | ||
} | ||
|
||
let unwatchResize = () => {}; | ||
|
||
const promise = Promise.resolve() | ||
.then(() => Plotly.newPlot(container, plotlyData, plotlyLayout, plotlyOptions)) | ||
.then( | ||
createSafeFunction(() => | ||
updater | ||
.append(updateYRanges(container, plotlyLayout, options)) | ||
.append(updateChartSize(container, plotlyLayout, options)) | ||
.process(container) | ||
) | ||
) | ||
.then( | ||
createSafeFunction(() => { | ||
container.on( | ||
"plotly_restyle", | ||
createSafeFunction(updates => { | ||
// This event is triggered if some plotly data/layout has changed. | ||
// We need to catch only changes of traces visibility to update stacking | ||
if (isArray(updates) && isObject(updates[0]) && updates[0].visible) { | ||
updateData(plotlyData, options); | ||
updater.append(updateYRanges(container, plotlyLayout, options)).process(container); | ||
} | ||
}) | ||
); | ||
|
||
unwatchResize = resizeObserver( | ||
container, | ||
createSafeFunction(() => { | ||
updater.append(updateChartSize(container, plotlyLayout, options)).process(container); | ||
}) | ||
); | ||
}) | ||
) | ||
.catch(handleError); | ||
|
||
const result = { | ||
initialized: promise.then(() => result), | ||
setZoomEnabled: createSafeFunction(allowZoom => { | ||
const layoutUpdates = { dragmode: allowZoom ? "zoom" : false }; | ||
return Plotly.relayout(container, layoutUpdates); | ||
}), | ||
destroy: createSafeFunction(() => { | ||
isDestroyed = true; | ||
container.removeAllListeners("plotly_restyle"); | ||
unwatchResize(); | ||
delete container.__previousSize; // added by `updateChartSize` | ||
Plotly.purge(container); | ||
}), | ||
}; | ||
|
||
return result; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.