Skip to content

Commit 5a08fbc

Browse files
authored
Merge pull request #946 from plotly/cartesian-per-subplot
Cartesian subplot updates using data joins
2 parents f058e8b + e78b099 commit 5a08fbc

File tree

22 files changed

+438
-313
lines changed

22 files changed

+438
-313
lines changed

src/components/errorbars/plot.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@ var subTypes = require('../../traces/scatter/subtypes');
1616

1717
module.exports = function plot(traces, plotinfo, transitionOpts) {
1818
var isNew;
19-
var xa = plotinfo.x(),
20-
ya = plotinfo.y();
19+
20+
var xa = plotinfo.xaxis,
21+
ya = plotinfo.yaxis;
2122

2223
var hasAnimation = transitionOpts && transitionOpts.duration > 0;
2324

src/plot_api/plot_api.js

Lines changed: 36 additions & 206 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,6 @@ var subroutines = require('./subroutines');
3535
/**
3636
* Main plot-creation function
3737
*
38-
* Note: will call makePlotFramework if necessary to create the framework
39-
*
4038
* @param {string id or DOM element} gd
4139
* the id or DOM element of the graph container div
4240
* @param {array of objects} data
@@ -123,28 +121,15 @@ Plotly.plot = function(gd, data, layout, config) {
123121
// so we don't try to re-call Plotly.plot from inside
124122
// legend and colorbar, if margins changed
125123
gd._replotting = true;
126-
var hasData = gd._fullData.length > 0;
127124

128-
var subplots = Plotly.Axes.getSubplots(gd).join(''),
129-
oldSubplots = Object.keys(gd._fullLayout._plots || {}).join(''),
130-
hasSameSubplots = (oldSubplots === subplots);
125+
// make or remake the framework if we need to
126+
if(graphWasEmpty) makePlotFramework(gd);
131127

132-
// Make or remake the framework (ie container and axes) if we need to
133-
// note: if they container already exists and has data,
134-
// the new layout gets ignored (as it should)
135-
// but if there's no data there yet, it's just a placeholder...
136-
// then it should destroy and remake the plot
137-
if(hasData) {
138-
if(gd.framework !== makePlotFramework || graphWasEmpty || !hasSameSubplots) {
139-
gd.framework = makePlotFramework;
140-
makePlotFramework(gd);
141-
}
142-
}
143-
else if(!hasSameSubplots) {
128+
// polar need a different framework
129+
if(gd.framework !== makePlotFramework) {
144130
gd.framework = makePlotFramework;
145131
makePlotFramework(gd);
146132
}
147-
else if(graphWasEmpty) makePlotFramework(gd);
148133

149134
// save initial axis range once per graph
150135
if(graphWasEmpty) Plotly.Axes.saveRangeInitial(gd);
@@ -169,6 +154,24 @@ Plotly.plot = function(gd, data, layout, config) {
169154

170155
var oldmargins = JSON.stringify(fullLayout._size);
171156

157+
// draw framework first so that margin-pushing
158+
// components can position themselves correctly
159+
function drawFramework() {
160+
var basePlotModules = fullLayout._basePlotModules;
161+
162+
for(var i = 0; i < basePlotModules.length; i++) {
163+
if(basePlotModules[i].drawFramework) {
164+
basePlotModules[i].drawFramework(gd);
165+
}
166+
}
167+
168+
return Lib.syncOrAsync([
169+
subroutines.layoutStyles,
170+
drawAxes,
171+
Fx.init
172+
], gd);
173+
}
174+
172175
// draw anything that can affect margins.
173176
// currently this is legend and colorbars
174177
function marginPushers() {
@@ -195,8 +198,10 @@ Plotly.plot = function(gd, data, layout, config) {
195198
function marginPushersAgain() {
196199
// in case the margins changed, draw margin pushers again
197200
var seq = JSON.stringify(fullLayout._size) === oldmargins ?
198-
[] : [marginPushers, subroutines.layoutStyles];
199-
return Lib.syncOrAsync(seq.concat(Fx.init), gd);
201+
[] :
202+
[marginPushers, subroutines.layoutStyles];
203+
204+
return Lib.syncOrAsync(seq, gd);
200205
}
201206

202207
function positionAndAutorange() {
@@ -220,7 +225,6 @@ Plotly.plot = function(gd, data, layout, config) {
220225
}
221226
}
222227

223-
224228
// calc and autorange for errorbars
225229
ErrorBars.calc(gd);
226230

@@ -234,14 +238,15 @@ Plotly.plot = function(gd, data, layout, config) {
234238

235239
function doAutoRange() {
236240
if(gd._transitioning) return;
241+
237242
var axList = Plotly.Axes.list(gd, '', true);
238243
for(var i = 0; i < axList.length; i++) {
239244
Plotly.Axes.doAutoRange(axList[i]);
240245
}
241246
}
242247

248+
// draw ticks, titles, and calculate axis scaling (._b, ._m)
243249
function drawAxes() {
244-
// draw ticks, titles, and calculate axis scaling (._b, ._m)
245250
return Plotly.Axes.doTicks(gd, 'redraw');
246251
}
247252

@@ -276,6 +281,11 @@ Plotly.plot = function(gd, data, layout, config) {
276281
basePlotModules[i].plot(gd);
277282
}
278283

284+
// keep reference to shape layers in subplots
285+
var layerSubplot = fullLayout._paper.selectAll('.layer-subplot');
286+
fullLayout._imageSubplotLayer = layerSubplot.selectAll('.imagelayer');
287+
fullLayout._shapeSubplotLayer = layerSubplot.selectAll('.shapelayer');
288+
279289
// styling separate from drawing
280290
Plots.style(gd);
281291

@@ -313,6 +323,7 @@ Plotly.plot = function(gd, data, layout, config) {
313323

314324
Lib.syncOrAsync([
315325
Plots.previousPromises,
326+
drawFramework,
316327
marginPushers,
317328
marginPushersAgain,
318329
positionAndAutorange,
@@ -2757,21 +2768,12 @@ function makePlotFramework(gd) {
27572768
fullLayout._shapeLowerLayer = layerBelow.append('g')
27582769
.classed('shapelayer', true);
27592770

2760-
var subplots = Plotly.Axes.getSubplots(gd);
2761-
if(subplots.join('') !== Object.keys(gd._fullLayout._plots || {}).join('')) {
2762-
makeSubplots(gd, subplots);
2763-
}
2764-
2765-
if(fullLayout._has('cartesian')) makeCartesianPlotFramwork(gd, subplots);
2771+
// single cartesian layer for the whole plot
2772+
fullLayout._cartesianlayer = fullLayout._paper.append('g').classed('cartesianlayer', true);
27662773

27672774
// single ternary layer for the whole plot
27682775
fullLayout._ternarylayer = fullLayout._paper.append('g').classed('ternarylayer', true);
27692776

2770-
// shape layers in subplots
2771-
var layerSubplot = fullLayout._paper.selectAll('.layer-subplot');
2772-
fullLayout._imageSubplotLayer = layerSubplot.selectAll('.imagelayer');
2773-
fullLayout._shapeSubplotLayer = layerSubplot.selectAll('.shapelayer');
2774-
27752777
// upper shape layer
27762778
// (only for shapes to be drawn above the whole plot, including subplots)
27772779
var layerAbove = fullLayout._paper.append('g')
@@ -2796,176 +2798,4 @@ function makePlotFramework(gd) {
27962798
fullLayout._hoverlayer = fullLayout._toppaper.append('g').classed('hoverlayer', true);
27972799

27982800
gd.emit('plotly_framework');
2799-
2800-
// position and style the containers, make main title
2801-
var frameWorkDone = Lib.syncOrAsync([
2802-
subroutines.layoutStyles,
2803-
function goAxes() { return Plotly.Axes.doTicks(gd, 'redraw'); },
2804-
Fx.init
2805-
], gd);
2806-
2807-
if(frameWorkDone && frameWorkDone.then) {
2808-
gd._promises.push(frameWorkDone);
2809-
}
2810-
2811-
return frameWorkDone;
2812-
}
2813-
2814-
// create '_plots' object grouping x/y axes into subplots
2815-
// to be better manage subplots
2816-
function makeSubplots(gd, subplots) {
2817-
var _plots = gd._fullLayout._plots = {};
2818-
var subplot, plotinfo;
2819-
2820-
function getAxisFunc(subplot, axLetter) {
2821-
return function() {
2822-
return Plotly.Axes.getFromId(gd, subplot, axLetter);
2823-
};
2824-
}
2825-
2826-
for(var i = 0; i < subplots.length; i++) {
2827-
subplot = subplots[i];
2828-
plotinfo = _plots[subplot] = {};
2829-
2830-
plotinfo.id = subplot;
2831-
2832-
// references to the axis objects controlling this subplot
2833-
plotinfo.x = getAxisFunc(subplot, 'x');
2834-
plotinfo.y = getAxisFunc(subplot, 'y');
2835-
2836-
// TODO investigate why replacing calls to .x and .y
2837-
// for .xaxis and .yaxis makes the `pseudo_html`
2838-
// test image fail
2839-
plotinfo.xaxis = plotinfo.x();
2840-
plotinfo.yaxis = plotinfo.y();
2841-
}
2842-
}
2843-
2844-
function makeCartesianPlotFramwork(gd, subplots) {
2845-
var fullLayout = gd._fullLayout;
2846-
2847-
// Layers to keep plot types in the right order.
2848-
// from back to front:
2849-
// 1. heatmaps, 2D histos and contour maps
2850-
// 2. bars / 1D histos
2851-
// 3. errorbars for bars and scatter
2852-
// 4. scatter
2853-
// 5. box plots
2854-
function plotLayers(svg) {
2855-
svg.append('g').classed('imagelayer', true);
2856-
svg.append('g').classed('maplayer', true);
2857-
svg.append('g').classed('barlayer', true);
2858-
svg.append('g').classed('boxlayer', true);
2859-
svg.append('g').classed('scatterlayer', true);
2860-
}
2861-
2862-
// create all the layers in order, so we know they'll stay in order
2863-
var overlays = [];
2864-
2865-
fullLayout._paper.selectAll('g.subplot').data(subplots)
2866-
.enter().append('g')
2867-
.classed('subplot', true)
2868-
.each(function(subplot) {
2869-
var plotinfo = fullLayout._plots[subplot],
2870-
plotgroup = plotinfo.plotgroup = d3.select(this).classed(subplot, true),
2871-
xa = plotinfo.xaxis,
2872-
ya = plotinfo.yaxis;
2873-
2874-
// references to any subplots overlaid on this one
2875-
plotinfo.overlays = [];
2876-
2877-
// is this subplot overlaid on another?
2878-
// ax.overlaying is the id of another axis of the same
2879-
// dimension that this one overlays to be an overlaid subplot,
2880-
// the main plot must exist make sure we're not trying to
2881-
// overlay on an axis that's already overlaying another
2882-
var xa2 = Plotly.Axes.getFromId(gd, xa.overlaying) || xa;
2883-
if(xa2 !== xa && xa2.overlaying) {
2884-
xa2 = xa;
2885-
xa.overlaying = false;
2886-
}
2887-
2888-
var ya2 = Plotly.Axes.getFromId(gd, ya.overlaying) || ya;
2889-
if(ya2 !== ya && ya2.overlaying) {
2890-
ya2 = ya;
2891-
ya.overlaying = false;
2892-
}
2893-
2894-
var mainplot = xa2._id + ya2._id;
2895-
if(mainplot !== subplot && subplots.indexOf(mainplot) !== -1) {
2896-
plotinfo.mainplot = mainplot;
2897-
overlays.push(plotinfo);
2898-
2899-
// for now force overlays to overlay completely... so they
2900-
// can drag together correctly and share backgrounds.
2901-
// Later perhaps we make separate axis domain and
2902-
// tick/line domain or something, so they can still share
2903-
// the (possibly larger) dragger and background but don't
2904-
// have to both be drawn over that whole domain
2905-
xa.domain = xa2.domain.slice();
2906-
ya.domain = ya2.domain.slice();
2907-
}
2908-
else {
2909-
// main subplot - make the components of
2910-
// the plot and containers for overlays
2911-
plotinfo.bg = plotgroup.append('rect')
2912-
.style('stroke-width', 0);
2913-
2914-
// back layer for shapes and images to
2915-
// be drawn below a subplot
2916-
var backlayer = plotgroup.append('g')
2917-
.classed('layer-subplot', true);
2918-
2919-
plotinfo.shapelayer = backlayer.append('g')
2920-
.classed('shapelayer', true);
2921-
plotinfo.imagelayer = backlayer.append('g')
2922-
.classed('imagelayer', true);
2923-
plotinfo.gridlayer = plotgroup.append('g');
2924-
plotinfo.overgrid = plotgroup.append('g');
2925-
plotinfo.zerolinelayer = plotgroup.append('g');
2926-
plotinfo.overzero = plotgroup.append('g');
2927-
plotinfo.plot = plotgroup.append('g').call(plotLayers);
2928-
plotinfo.overplot = plotgroup.append('g');
2929-
plotinfo.xlines = plotgroup.append('path');
2930-
plotinfo.ylines = plotgroup.append('path');
2931-
plotinfo.overlines = plotgroup.append('g');
2932-
plotinfo.xaxislayer = plotgroup.append('g');
2933-
plotinfo.yaxislayer = plotgroup.append('g');
2934-
plotinfo.overaxes = plotgroup.append('g');
2935-
2936-
// make separate drag layers for each subplot,
2937-
// but append them to paper rather than the plot groups,
2938-
// so they end up on top of the rest
2939-
}
2940-
plotinfo.draglayer = fullLayout._draggers.append('g');
2941-
});
2942-
2943-
// now make the components of overlaid subplots
2944-
// overlays don't have backgrounds, and append all
2945-
// their other components to the corresponding
2946-
// extra groups of their main Plots.
2947-
overlays.forEach(function(plotinfo) {
2948-
var mainplot = fullLayout._plots[plotinfo.mainplot];
2949-
mainplot.overlays.push(plotinfo);
2950-
2951-
plotinfo.gridlayer = mainplot.overgrid.append('g');
2952-
plotinfo.zerolinelayer = mainplot.overzero.append('g');
2953-
plotinfo.plot = mainplot.overplot.append('g').call(plotLayers);
2954-
plotinfo.xlines = mainplot.overlines.append('path');
2955-
plotinfo.ylines = mainplot.overlines.append('path');
2956-
plotinfo.xaxislayer = mainplot.overaxes.append('g');
2957-
plotinfo.yaxislayer = mainplot.overaxes.append('g');
2958-
});
2959-
2960-
// common attributes for all subplots, overlays or not
2961-
subplots.forEach(function(subplot) {
2962-
var plotinfo = fullLayout._plots[subplot];
2963-
2964-
plotinfo.xlines
2965-
.style('fill', 'none')
2966-
.classed('crisp', true);
2967-
plotinfo.ylines
2968-
.style('fill', 'none')
2969-
.classed('crisp', true);
2970-
});
29712801
}

src/plot_api/subroutines.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ exports.lsInner = function(gd) {
4848
var plotinfo = fullLayout._plots[subplot],
4949
xa = Plotly.Axes.getFromId(gd, subplot, 'x'),
5050
ya = Plotly.Axes.getFromId(gd, subplot, 'y');
51+
5152
xa.setScale(); // this may already be done... not sure
5253
ya.setScale();
5354

@@ -59,7 +60,6 @@ exports.lsInner = function(gd) {
5960
.call(Color.fill, fullLayout.plot_bgcolor);
6061
}
6162

62-
6363
// Clip so that data only shows up on the plot area.
6464
plotinfo.clipId = 'clip' + fullLayout._uid + subplot + 'plot';
6565

0 commit comments

Comments
 (0)