Skip to content

Commit 4f7a513

Browse files
authored
Merge pull request #7475 from plotly/react-to-config-changes
fix: React to config changes
2 parents a7eac49 + 6133268 commit 4f7a513

File tree

3 files changed

+123
-109
lines changed

3 files changed

+123
-109
lines changed

draftlogs/7475_fix.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Update plot with all config changes during call to `Plotly.react` [#7475](https://github.com/plotly/plotly.js/pull/7475)

src/plot_api/plot_api.js

Lines changed: 120 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -2615,129 +2615,140 @@ function react(gd, data, layout, config) {
26152615
configChanged = diffConfig(oldConfig, gd._context);
26162616
}
26172617

2618-
gd.data = data || [];
2619-
helpers.cleanData(gd.data);
2620-
gd.layout = layout || {};
2621-
helpers.cleanLayout(gd.layout);
2622-
2623-
applyUIRevisions(gd.data, gd.layout, oldFullData, oldFullLayout);
2624-
2625-
// "true" skips updating calcdata and remapping arrays from calcTransforms,
2626-
// which supplyDefaults usually does at the end, but we may need to NOT do
2627-
// if the diff (which we haven't determined yet) says we'll recalc
2628-
Plots.supplyDefaults(gd, {skipUpdateCalc: true});
2629-
2630-
var newFullData = gd._fullData;
2631-
var newFullLayout = gd._fullLayout;
2632-
var immutable = newFullLayout.datarevision === undefined;
2633-
var transition = newFullLayout.transition;
2634-
2635-
var relayoutFlags = diffLayout(gd, oldFullLayout, newFullLayout, immutable, transition);
2636-
var newDataRevision = relayoutFlags.newDataRevision;
2637-
var restyleFlags = diffData(gd, oldFullData, newFullData, immutable, transition, newDataRevision);
2638-
2639-
// TODO: how to translate this part of relayout to Plotly.react?
2640-
// // Setting width or height to null must reset the graph's width / height
2641-
// // back to its initial value as computed during the first pass in Plots.plotAutoSize.
2642-
// //
2643-
// // To do so, we must manually set them back here using the _initialAutoSize cache.
2644-
// if(['width', 'height'].indexOf(ai) !== -1 && vi === null) {
2645-
// fullLayout[ai] = gd._initialAutoSize[ai];
2646-
// }
2647-
2648-
if(updateAutosize(gd)) relayoutFlags.layoutReplot = true;
2649-
2650-
// clear calcdata and empty categories if required
2651-
if(restyleFlags.calc || relayoutFlags.calc) {
2652-
gd.calcdata = undefined;
2653-
var allNames = Object.getOwnPropertyNames(newFullLayout);
2654-
for(var q = 0; q < allNames.length; q++) {
2655-
var name = allNames[q];
2656-
var start = name.substring(0, 5);
2657-
if(start === 'xaxis' || start === 'yaxis') {
2658-
var emptyCategories = newFullLayout[name]._emptyCategories;
2659-
if(emptyCategories) emptyCategories();
2618+
if(configChanged) {
2619+
// Save event listeners as newPlot will remove them
2620+
const eventListeners = gd._ev.eventNames().map(name => [name, gd._ev.listeners(name)]);
2621+
plotDone = exports.newPlot(gd, data, layout, config)
2622+
.then(() => {
2623+
for (const [name, callbacks] of eventListeners) {
2624+
callbacks.forEach((cb) => gd.on(name, cb));
2625+
}
2626+
2627+
// Call react in case transition should have occurred along with config change
2628+
return exports.react(gd, data, layout, config)
2629+
});
2630+
} else {
2631+
gd.data = data || [];
2632+
helpers.cleanData(gd.data);
2633+
gd.layout = layout || {};
2634+
helpers.cleanLayout(gd.layout);
2635+
2636+
applyUIRevisions(gd.data, gd.layout, oldFullData, oldFullLayout);
2637+
2638+
// "true" skips updating calcdata and remapping arrays from calcTransforms,
2639+
// which supplyDefaults usually does at the end, but we may need to NOT do
2640+
// if the diff (which we haven't determined yet) says we'll recalc
2641+
Plots.supplyDefaults(gd, {skipUpdateCalc: true});
2642+
2643+
var newFullData = gd._fullData;
2644+
var newFullLayout = gd._fullLayout;
2645+
var immutable = newFullLayout.datarevision === undefined;
2646+
var transition = newFullLayout.transition;
2647+
2648+
var relayoutFlags = diffLayout(gd, oldFullLayout, newFullLayout, immutable, transition);
2649+
var newDataRevision = relayoutFlags.newDataRevision;
2650+
var restyleFlags = diffData(gd, oldFullData, newFullData, immutable, transition, newDataRevision);
2651+
2652+
// TODO: how to translate this part of relayout to Plotly.react?
2653+
// // Setting width or height to null must reset the graph's width / height
2654+
// // back to its initial value as computed during the first pass in Plots.plotAutoSize.
2655+
// //
2656+
// // To do so, we must manually set them back here using the _initialAutoSize cache.
2657+
// if(['width', 'height'].indexOf(ai) !== -1 && vi === null) {
2658+
// fullLayout[ai] = gd._initialAutoSize[ai];
2659+
// }
2660+
2661+
if(updateAutosize(gd)) relayoutFlags.layoutReplot = true;
2662+
2663+
// clear calcdata and empty categories if required
2664+
if(restyleFlags.calc || relayoutFlags.calc) {
2665+
gd.calcdata = undefined;
2666+
var allNames = Object.getOwnPropertyNames(newFullLayout);
2667+
for(var q = 0; q < allNames.length; q++) {
2668+
var name = allNames[q];
2669+
var start = name.substring(0, 5);
2670+
if(start === 'xaxis' || start === 'yaxis') {
2671+
var emptyCategories = newFullLayout[name]._emptyCategories;
2672+
if(emptyCategories) emptyCategories();
2673+
}
26602674
}
2675+
// otherwise do the calcdata updates and calcTransform array remaps that we skipped earlier
2676+
} else {
2677+
Plots.supplyDefaultsUpdateCalc(gd.calcdata, newFullData);
26612678
}
2662-
// otherwise do the calcdata updates and calcTransform array remaps that we skipped earlier
2663-
} else {
2664-
Plots.supplyDefaultsUpdateCalc(gd.calcdata, newFullData);
2665-
}
26662679

2667-
// Note: what restyle/relayout use impliedEdits and clearAxisTypes for
2668-
// must be handled by the user when using Plotly.react.
2680+
// Note: what restyle/relayout use impliedEdits and clearAxisTypes for
2681+
// must be handled by the user when using Plotly.react.
26692682

2670-
// fill in redraw sequence
2671-
var seq = [];
2683+
// fill in redraw sequence
2684+
var seq = [];
26722685

2673-
if(frames) {
2674-
gd._transitionData = {};
2675-
Plots.createTransitionData(gd);
2676-
seq.push(addFrames);
2677-
}
2686+
if(frames) {
2687+
gd._transitionData = {};
2688+
Plots.createTransitionData(gd);
2689+
seq.push(addFrames);
2690+
}
26782691

2679-
// Transition pathway,
2680-
// only used when 'transition' is set by user and
2681-
// when at least one animatable attribute has changed,
2682-
// N.B. config changed aren't animatable
2683-
if(newFullLayout.transition && !configChanged && (restyleFlags.anim || relayoutFlags.anim)) {
2684-
if(relayoutFlags.ticks) seq.push(subroutines.doTicksRelayout);
2692+
// Transition pathway,
2693+
// only used when 'transition' is set by user and
2694+
// when at least one animatable attribute has changed,
2695+
// N.B. config changed aren't animatable
2696+
if(newFullLayout.transition && (restyleFlags.anim || relayoutFlags.anim)) {
2697+
if(relayoutFlags.ticks) seq.push(subroutines.doTicksRelayout);
26852698

2686-
Plots.doCalcdata(gd);
2687-
subroutines.doAutoRangeAndConstraints(gd);
2699+
Plots.doCalcdata(gd);
2700+
subroutines.doAutoRangeAndConstraints(gd);
26882701

2689-
seq.push(function() {
2690-
return Plots.transitionFromReact(gd, restyleFlags, relayoutFlags, oldFullLayout);
2691-
});
2692-
} else if(restyleFlags.fullReplot || relayoutFlags.layoutReplot || configChanged) {
2693-
gd._fullLayout._skipDefaults = true;
2694-
seq.push(exports._doPlot);
2695-
} else {
2696-
for(var componentType in relayoutFlags.arrays) {
2697-
var indices = relayoutFlags.arrays[componentType];
2698-
if(indices.length) {
2699-
var drawOne = Registry.getComponentMethod(componentType, 'drawOne');
2700-
if(drawOne !== Lib.noop) {
2701-
for(var i = 0; i < indices.length; i++) {
2702-
drawOne(gd, indices[i]);
2703-
}
2704-
} else {
2705-
var draw = Registry.getComponentMethod(componentType, 'draw');
2706-
if(draw === Lib.noop) {
2707-
throw new Error('cannot draw components: ' + componentType);
2702+
seq.push(function() {
2703+
return Plots.transitionFromReact(gd, restyleFlags, relayoutFlags, oldFullLayout);
2704+
});
2705+
} else if(restyleFlags.fullReplot || relayoutFlags.layoutReplot) {
2706+
gd._fullLayout._skipDefaults = true;
2707+
seq.push(exports._doPlot);
2708+
} else {
2709+
for(var componentType in relayoutFlags.arrays) {
2710+
var indices = relayoutFlags.arrays[componentType];
2711+
if(indices.length) {
2712+
var drawOne = Registry.getComponentMethod(componentType, 'drawOne');
2713+
if(drawOne !== Lib.noop) {
2714+
for(var i = 0; i < indices.length; i++) {
2715+
drawOne(gd, indices[i]);
2716+
}
2717+
} else {
2718+
var draw = Registry.getComponentMethod(componentType, 'draw');
2719+
if(draw === Lib.noop) {
2720+
throw new Error('cannot draw components: ' + componentType);
2721+
}
2722+
draw(gd);
27082723
}
2709-
draw(gd);
27102724
}
27112725
}
2712-
}
27132726

2714-
seq.push(Plots.previousPromises);
2715-
if(restyleFlags.style) seq.push(subroutines.doTraceStyle);
2716-
if(restyleFlags.colorbars || relayoutFlags.colorbars) seq.push(subroutines.doColorBars);
2717-
if(relayoutFlags.legend) seq.push(subroutines.doLegend);
2718-
if(relayoutFlags.layoutstyle) seq.push(subroutines.layoutStyles);
2719-
if(relayoutFlags.axrange) addAxRangeSequence(seq);
2720-
if(relayoutFlags.ticks) seq.push(subroutines.doTicksRelayout);
2721-
if(relayoutFlags.modebar) seq.push(subroutines.doModeBar);
2722-
if(relayoutFlags.camera) seq.push(subroutines.doCamera);
2723-
seq.push(emitAfterPlot);
2724-
}
2727+
seq.push(Plots.previousPromises);
2728+
if(restyleFlags.style) seq.push(subroutines.doTraceStyle);
2729+
if(restyleFlags.colorbars || relayoutFlags.colorbars) seq.push(subroutines.doColorBars);
2730+
if(relayoutFlags.legend) seq.push(subroutines.doLegend);
2731+
if(relayoutFlags.layoutstyle) seq.push(subroutines.layoutStyles);
2732+
if(relayoutFlags.axrange) addAxRangeSequence(seq);
2733+
if(relayoutFlags.ticks) seq.push(subroutines.doTicksRelayout);
2734+
if(relayoutFlags.modebar) seq.push(subroutines.doModeBar);
2735+
if(relayoutFlags.camera) seq.push(subroutines.doCamera);
2736+
seq.push(emitAfterPlot);
2737+
}
27252738

2726-
seq.push(
2727-
Plots.rehover,
2728-
Plots.redrag,
2729-
Plots.reselect
2730-
);
2739+
seq.push(
2740+
Plots.rehover,
2741+
Plots.redrag,
2742+
Plots.reselect
2743+
);
27312744

2732-
plotDone = Lib.syncOrAsync(seq, gd);
2733-
if(!plotDone || !plotDone.then) plotDone = Promise.resolve(gd);
2745+
plotDone = Lib.syncOrAsync(seq, gd);
2746+
if(!plotDone || !plotDone.then) plotDone = Promise.resolve(gd);
2747+
}
27342748
}
27352749

2736-
return plotDone.then(function() {
2737-
gd.emit('plotly_react', {
2738-
data: data,
2739-
layout: layout
2740-
});
2750+
return plotDone.then(() => {
2751+
if (!configChanged) gd.emit('plotly_react', { config, data, layout });
27412752

27422753
return gd;
27432754
});
@@ -3675,11 +3686,11 @@ function makePlotFramework(gd) {
36753686
// The plot container should always take the full with the height of its
36763687
// parent (the graph div). This ensures that for responsive plots
36773688
// without a height or width set, the paper div will take up the full
3678-
// height & width of the graph div.
3689+
// height & width of the graph div.
36793690
// So, for responsive plots without a height or width set, if the plot
36803691
// container's height is left to 'auto', its height will be dictated by
36813692
// its childrens' height. (The plot container's only child is the paper
3682-
// div.)
3693+
// div.)
36833694
// In this scenario, the paper div's height will be set to 100%,
36843695
// which will be 100% of the plot container's auto height. That is
36853696
// meaninglesss, so the browser will use the paper div's children to set

test/jasmine/tests/plot_api_react_test.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,13 +425,15 @@ describe('@noCIdep Plotly.react', function() {
425425
.then(function() {
426426
expect(d3SelectAll('.drag').size()).toBe(0);
427427
expect(d3SelectAll('.gtitle').size()).toBe(0);
428+
expect(d3SelectAll('.gtitle-subtitle').size()).toBe(0);
428429
countCalls({plot: 1});
429430

430431
return Plotly.react(gd, data, layout, {});
431432
})
432433
.then(function() {
433434
expect(d3SelectAll('.drag').size()).toBe(11);
434435
expect(d3SelectAll('.gtitle').size()).toBe(0);
436+
expect(d3SelectAll('.gtitle-subtitle').size()).toBe(0);
435437
countCalls({plot: 1});
436438
})
437439
.then(done, done.fail);

0 commit comments

Comments
 (0)