Skip to content

Commit 18aeb7f

Browse files
committed
Merge pull request #4122 from plotly/subplotly
Subplotlyjs (a 🍼 first step)
2 parents 4471e15 + ec2b186 commit 18aeb7f

File tree

5 files changed

+118
-66
lines changed

5 files changed

+118
-66
lines changed

image_server/test/mocks/empty.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"data": [],
3+
"layout": {}
4+
}
7.01 KB
Loading

shelly/plotlyjs/static/plotlyjs/src/axes.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2356,7 +2356,7 @@ axes.listIds = function(td, axletter) {
23562356

23572357
// get an axis object from its id 'x','x2' etc
23582358
// optionally, id can be a subplot (ie 'x2y3') and type gets x or y from it
2359-
axes.getFromId = function(td,id,type) {
2359+
axes.getFromId = function(td, id, type) {
23602360
var fullLayout = td._fullLayout;
23612361

23622362
if(type==='x') id = id.replace(/y[0-9]*/,'');
@@ -2875,8 +2875,11 @@ axes.doTicks = function(td, axid, skipTitle) {
28752875
}
28762876
else {
28772877
var alldone = axes.getSubplots(td,ax).map(function(subplot) {
2878-
var plotinfo = fullLayout._plots[subplot],
2879-
container = plotinfo[axletter+'axislayer'],
2878+
var plotinfo = fullLayout._plots[subplot];
2879+
2880+
if(!fullLayout._hasCartesian) return;
2881+
2882+
var container = plotinfo[axletter + 'axislayer'],
28802883

28812884
// [bottom or left, top or right, free, main]
28822885
linepositions = ax._linepositions[subplot]||[],

shelly/plotlyjs/static/plotlyjs/src/graph_interact.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,11 @@ fx.init = function(gd) {
9090
return fullLayout._plots[a].mainplot ? 1 : -1;
9191
});
9292
subplots.forEach(function(subplot) {
93-
var plotinfo = fullLayout._plots[subplot],
94-
xa = plotinfo.x(),
93+
var plotinfo = fullLayout._plots[subplot];
94+
95+
if(!fullLayout._hasCartesian) return;
96+
97+
var xa = plotinfo.x(),
9598
ya = plotinfo.y(),
9699

97100
// the y position of the main x axis line

shelly/plotlyjs/static/plotlyjs/src/graph_obj.js

Lines changed: 103 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,14 @@ plots.registerSubplot = function(subplotType, attr, idRoot, attributes) {
148148
};
149149
};
150150

151+
// TODO separate the 'find subplot' step from the 'get subplot ids' step
151152
plots.getSubplotIds = function getSubplotIds(layout, type) {
153+
154+
// layout must be 'fullLayout' here
155+
if(type === 'cartesian') {
156+
return Object.keys(layout._plots);
157+
}
158+
152159
var idRegex = plots.subplotsRegistry[type].idRegex,
153160
layoutKeys = Object.keys(layout),
154161
subplotIds = [],
@@ -560,22 +567,23 @@ Plotly.plot = function(gd, data, layout, config) {
560567
}
561568

562569
function positionAndAutorange() {
563-
var i, j, subplots, subplotInfo, modules, module;
564-
565570
if(!recalc) return;
566571

572+
var subplots = plots.getSubplotIds(fullLayout, 'cartesian'),
573+
modules = gd._modules;
574+
567575
// position and range calculations for traces that
568576
// depend on each other ie bars (stacked or grouped)
569577
// and boxes (grouped) push each other out of the way
570-
subplots = Plotly.Axes.getSubplots(gd);
571-
modules = gd._modules;
572-
for (i = 0; i < subplots.length; i++) {
573-
subplotInfo = gd._fullLayout._plots[subplots[i]];
574-
for (j = 0; j < modules.length; j++) {
575-
module = modules[j];
576-
if (module.setPositions) {
577-
module.setPositions(gd, subplotInfo);
578-
}
578+
579+
var subplotInfo, _module;
580+
581+
for(var i = 0; i < subplots.length; i++) {
582+
subplotInfo = fullLayout._plots[subplots[i]];
583+
584+
for(var j = 0; j < modules.length; j++) {
585+
_module = modules[j];
586+
if(_module.setPositions) _module.setPositions(gd, subplotInfo);
579587
}
580588
}
581589

@@ -607,10 +615,10 @@ Plotly.plot = function(gd, data, layout, config) {
607615
return Plotly.Axes.doTicks(gd, 'redraw');
608616
}
609617

610-
function drawData(){
618+
function drawData() {
611619
// Now plot the data
612620
var calcdata = gd.calcdata,
613-
subplots = Plotly.Axes.getSubplots(gd),
621+
subplots = plots.getSubplotIds(fullLayout, 'cartesian'),
614622
modules = gd._modules;
615623

616624
var i, j, cd, trace, uid, subplot, subplotInfo,
@@ -666,7 +674,7 @@ Plotly.plot = function(gd, data, layout, config) {
666674
// remove old traces, then redraw everything
667675
// TODO: use enter/exit appropriately in the plot functions
668676
// so we don't need this - should sometimes be a big speedup
669-
subplotInfo.plot.selectAll('g.trace').remove();
677+
if(subplotInfo.plot) subplotInfo.plot.selectAll('g.trace').remove();
670678

671679
for(j = 0; j < modules.length; j++) {
672680
module = modules[j];
@@ -684,7 +692,7 @@ Plotly.plot = function(gd, data, layout, config) {
684692
}
685693

686694
// finally do all error bars at once
687-
if(Plotly.ErrorBars) {
695+
if(gd._fullLayout._hasCartesian && Plotly.ErrorBars) {
688696
Plotly.ErrorBars.plot(gd, subplotInfo, cdError);
689697
Plotly.Lib.markTime('done ErrorBars');
690698
}
@@ -3527,7 +3535,6 @@ function getGraphDiv(gd) {
35273535
// -------------------------------------------------------
35283536
function makePlotFramework(gd) {
35293537
var gd3 = d3.select(gd),
3530-
subplots = Plotly.Axes.getSubplots(gd),
35313538
fullLayout = gd._fullLayout;
35323539

35333540
/*
@@ -3598,6 +3605,75 @@ function makePlotFramework(gd) {
35983605
fullLayout._draggers = fullLayout._paper.append('g')
35993606
.classed('draglayer', true);
36003607

3608+
var subplots = Plotly.Axes.getSubplots(gd);
3609+
makeSubplots(gd, subplots);
3610+
3611+
if(fullLayout._hasCartesian) makeCartesianPlotFramwork(gd, subplots);
3612+
3613+
// single shape and pie layers for the whole plot
3614+
fullLayout._shapelayer = fullLayout._paper.append('g').classed('shapelayer', true);
3615+
fullLayout._pielayer = fullLayout._paper.append('g').classed('pielayer', true);
3616+
3617+
// fill in image server scrape-svg
3618+
fullLayout._glimages = fullLayout._paper.append('g').classed('glimages', true);
3619+
fullLayout._geoimages = fullLayout._paper.append('g').classed('geoimages', true);
3620+
3621+
// lastly info (legend, annotations) and hover layers go on top
3622+
// these are in a different svg element normally, but get collapsed into a single
3623+
// svg when exporting (after inserting 3D)
3624+
fullLayout._infolayer = fullLayout._toppaper.append('g').classed('infolayer', true);
3625+
fullLayout._hoverlayer = fullLayout._toppaper.append('g').classed('hoverlayer', true);
3626+
3627+
$(gd).trigger('plotly_framework');
3628+
3629+
// position and style the containers, make main title
3630+
var frameWorkDone = Plotly.Lib.syncOrAsync([
3631+
layoutStyles,
3632+
function goAxes(){ return Plotly.Axes.doTicks(gd,'redraw'); },
3633+
Plotly.Fx.init
3634+
], gd);
3635+
3636+
if(frameWorkDone && frameWorkDone.then) {
3637+
gd._promises.push(frameWorkDone);
3638+
}
3639+
3640+
return frameWorkDone;
3641+
}
3642+
3643+
// create '_plots' object grouping x/y axes into subplots
3644+
// to be better manage subplots
3645+
function makeSubplots(gd, subplots) {
3646+
var _plots = gd._fullLayout._plots = {};
3647+
3648+
var subplot, plotinfo;
3649+
3650+
function getAxisFunc(subplot, axLetter) {
3651+
return function() {
3652+
return Plotly.Axes.getFromId(gd, subplot, axLetter);
3653+
};
3654+
}
3655+
3656+
for(var i = 0; i < subplots.length; i++) {
3657+
subplot = subplots[i];
3658+
plotinfo = _plots[subplot] = {};
3659+
3660+
plotinfo.id = subplot;
3661+
3662+
// references to the axis objects controlling this subplot
3663+
plotinfo.x = getAxisFunc(subplot, 'x');
3664+
plotinfo.y = getAxisFunc(subplot, 'y');
3665+
3666+
// TODO investigate why replacing calls to .x and .y
3667+
// for .xaxis and .yaxis makes the `pseudo_html`
3668+
// test image fail
3669+
plotinfo.xaxis = plotinfo.x();
3670+
plotinfo.yaxis = plotinfo.y();
3671+
}
3672+
}
3673+
3674+
function makeCartesianPlotFramwork(gd, subplots) {
3675+
var fullLayout = gd._fullLayout;
3676+
36013677
// Layers to keep plot types in the right order.
36023678
// from back to front:
36033679
// 1. heatmaps, 2D histos and contour maps
@@ -3615,23 +3691,16 @@ function makePlotFramework(gd) {
36153691

36163692
// create all the layers in order, so we know they'll stay in order
36173693
var overlays = [];
3618-
fullLayout._plots = {};
3694+
36193695
fullLayout._paper.selectAll('g.subplot').data(subplots)
36203696
.enter().append('g')
3621-
.classed('subplot',true)
3622-
.each(function(subplot){
3623-
var plotinfo = fullLayout._plots[subplot] = {},
3624-
plotgroup = d3.select(this).classed(subplot,true);
3625-
plotinfo.id = subplot;
3626-
// references to the axis objects controlling this subplot
3627-
plotinfo.x = function() {
3628-
return Plotly.Axes.getFromId(gd,subplot,'x');
3629-
};
3630-
plotinfo.y = function() {
3631-
return Plotly.Axes.getFromId(gd,subplot,'y');
3632-
};
3633-
var xa = plotinfo.x(),
3634-
ya = plotinfo.y();
3697+
.classed('subplot', true)
3698+
.each(function(subplot) {
3699+
var plotinfo = fullLayout._plots[subplot],
3700+
plotgroup = plotinfo.plotgroup = d3.select(this).classed(subplot, true),
3701+
xa = plotinfo.xaxis,
3702+
ya = plotinfo.yaxis;
3703+
36353704
// references to any subplots overlaid on this one
36363705
plotinfo.overlays = [];
36373706

@@ -3670,7 +3739,7 @@ function makePlotFramework(gd) {
36703739
// main subplot - make the components of
36713740
// the plot and containers for overlays
36723741
plotinfo.bg = plotgroup.append('rect')
3673-
.style('stroke-width',0);
3742+
.style('stroke-width', 0);
36743743
plotinfo.gridlayer = plotgroup.append('g');
36753744
plotinfo.overgrid = plotgroup.append('g');
36763745
plotinfo.zerolinelayer = plotgroup.append('g');
@@ -3691,6 +3760,7 @@ function makePlotFramework(gd) {
36913760
plotinfo.draglayer = fullLayout._draggers.append('g');
36923761
});
36933762

3763+
36943764
// now make the components of overlaid subplots
36953765
// overlays don't have backgrounds, and append all
36963766
// their other components to the corresponding
@@ -3711,6 +3781,7 @@ function makePlotFramework(gd) {
37113781
// common attributes for all subplots, overlays or not
37123782
subplots.forEach(function(subplot) {
37133783
var plotinfo = fullLayout._plots[subplot];
3784+
37143785
plotinfo.plot
37153786
.attr('preserveAspectRatio', 'none')
37163787
.style('fill', 'none');
@@ -3721,35 +3792,6 @@ function makePlotFramework(gd) {
37213792
.style('fill', 'none')
37223793
.classed('crisp', true);
37233794
});
3724-
3725-
// single shape and pie layers for the whole plot
3726-
fullLayout._shapelayer = fullLayout._paper.append('g').classed('shapelayer', true);
3727-
fullLayout._pielayer = fullLayout._paper.append('g').classed('pielayer', true);
3728-
3729-
// fill in image server scrape-svg
3730-
fullLayout._glimages = fullLayout._paper.append('g').classed('glimages', true);
3731-
fullLayout._geoimages = fullLayout._paper.append('g').classed('geoimages', true);
3732-
3733-
// lastly info (legend, annotations) and hover layers go on top
3734-
// these are in a different svg element normally, but get collapsed into a single
3735-
// svg when exporting (after inserting 3D)
3736-
fullLayout._infolayer = fullLayout._toppaper.append('g').classed('infolayer', true);
3737-
fullLayout._hoverlayer = fullLayout._toppaper.append('g').classed('hoverlayer', true);
3738-
3739-
$(gd).trigger('plotly_framework');
3740-
3741-
// position and style the containers, make main title
3742-
var frameWorkDone = Plotly.Lib.syncOrAsync([
3743-
layoutStyles,
3744-
function goAxes(){ return Plotly.Axes.doTicks(gd,'redraw'); },
3745-
Plotly.Fx.init
3746-
], gd);
3747-
3748-
if(frameWorkDone && frameWorkDone.then) {
3749-
gd._promises.push(frameWorkDone);
3750-
}
3751-
3752-
return frameWorkDone;
37533795
}
37543796

37553797
// called by legend and colorbar routines to see if we need to

0 commit comments

Comments
 (0)