Skip to content

Commit 96887ac

Browse files
committed
Merge pull request #554 from plotly/seamless-subplots
Seamless subplots
2 parents d3b3513 + b0f6230 commit 96887ac

File tree

7 files changed

+131
-122
lines changed

7 files changed

+131
-122
lines changed

src/plot_api/to_image.js

+6-8
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@
88

99
'use strict';
1010

11+
var isNumeric = require('fast-isnumeric');
12+
1113
var Plotly = require('../plotly');
14+
var Lib = require('../lib');
1215

13-
var isNumeric = require('fast-isnumeric');
1416

1517
/**
1618
* @param {object} gd figure Object
@@ -61,13 +63,8 @@ function toImage(gd, opts) {
6163
setTimeout(function() {
6264
var svg = Snapshot.toSVG(clonedGd);
6365

64-
var canvasContainer = window.document.createElement('div');
65-
var canvas = window.document.createElement('canvas');
66-
67-
canvasContainer.appendChild(canvas);
68-
69-
canvasContainer.id = Plotly.Lib.randstr();
70-
canvas.id = Plotly.Lib.randstr();
66+
var canvas = document.createElement('canvas');
67+
canvas.id = Lib.randstr();
7168

7269
Snapshot.svgToImg({
7370
format: opts.format,
@@ -86,6 +83,7 @@ function toImage(gd, opts) {
8683
}).catch(function(err) {
8784
reject(err);
8885
});
86+
8987
}, delay);
9088
});
9189
}

src/plots/geo/index.js

+24
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,27 @@ exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout)
7878
}
7979
}
8080
};
81+
82+
exports.toSVG = function(gd) {
83+
var fullLayout = gd._fullLayout,
84+
geoIds = Plots.getSubplotIds(fullLayout, 'geo'),
85+
size = fullLayout._size;
86+
87+
for(var i = 0; i < geoIds.length; i++) {
88+
var geoLayout = fullLayout[geoIds[i]],
89+
domain = geoLayout.domain,
90+
geoFramework = geoLayout._geo.framework;
91+
92+
geoFramework.attr('style', null);
93+
geoFramework
94+
.attr({
95+
x: size.l + size.w * domain.x[0] + geoLayout._marginX,
96+
y: size.t + size.h * (1 - domain.y[1]) + geoLayout._marginY,
97+
width: geoLayout._width,
98+
height: geoLayout._height
99+
});
100+
101+
fullLayout._geoimages.node()
102+
.appendChild(geoFramework.node());
103+
}
104+
};

src/plots/gl2d/index.js

+28-4
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,9 @@
99

1010
'use strict';
1111

12-
var Plotly = require('../../plotly');
13-
1412
var Scene2D = require('./scene2d');
15-
16-
var Plots = Plotly.Plots;
13+
var Plots = require('../plots');
14+
var xmlnsNamespaces = require('../../constants/xmlns_namespaces');
1715

1816

1917
exports.name = 'gl2d';
@@ -80,3 +78,29 @@ exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout)
8078
}
8179
}
8280
};
81+
82+
exports.toSVG = function(gd) {
83+
var fullLayout = gd._fullLayout,
84+
subplotIds = Plots.getSubplotIds(fullLayout, 'gl2d'),
85+
size = fullLayout._size;
86+
87+
for(var i = 0; i < subplotIds.length; i++) {
88+
var subplot = fullLayout._plots[subplotIds[i]],
89+
scene = subplot._scene2d;
90+
91+
var imageData = scene.toImage('png');
92+
var image = fullLayout._glimages.append('svg:image');
93+
94+
image.attr({
95+
xmlns: xmlnsNamespaces.svg,
96+
'xlink:href': imageData,
97+
x: size.l,
98+
y: size.t,
99+
width: size.w,
100+
height: size.h,
101+
preserveAspectRatio: 'none'
102+
});
103+
104+
scene.destroy();
105+
}
106+
};

src/plots/gl3d/index.js

+28
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
var Scene = require('./scene');
1313
var Plots = require('../plots');
14+
var xmlnsNamespaces = require('../../constants/xmlns_namespaces');
1415

1516
var axesNames = ['xaxis', 'yaxis', 'zaxis'];
1617

@@ -83,6 +84,33 @@ exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout)
8384
}
8485
};
8586

87+
exports.toSVG = function(gd) {
88+
var fullLayout = gd._fullLayout,
89+
sceneIds = Plots.getSubplotIds(fullLayout, 'gl3d'),
90+
size = fullLayout._size;
91+
92+
for(var i = 0; i < sceneIds.length; i++) {
93+
var sceneLayout = fullLayout[sceneIds[i]],
94+
domain = sceneLayout.domain,
95+
scene = sceneLayout._scene;
96+
97+
var imageData = scene.toImage('png');
98+
var image = fullLayout._glimages.append('svg:image');
99+
100+
image.attr({
101+
xmlns: xmlnsNamespaces.svg,
102+
'xlink:href': imageData,
103+
x: size.l + size.w * domain.x[0],
104+
y: size.t + size.h * (1 - domain.y[1]),
105+
width: size.w * (domain.x[1] - domain.x[0]),
106+
height: size.h * (domain.y[1] - domain.y[0]),
107+
preserveAspectRatio: 'none'
108+
});
109+
110+
scene.destroy();
111+
}
112+
};
113+
86114
// clean scene ids, 'scene1' -> 'scene'
87115
exports.cleanId = function cleanId(id) {
88116
if(!id.match(/^scene[0-9]*$/)) return;

src/plots/plots.js

+27-23
Original file line numberDiff line numberDiff line change
@@ -642,50 +642,54 @@ function relinkPrivateKeys(toLayout, fromLayout) {
642642
}
643643
}
644644

645-
plots.supplyDataDefaults = function(traceIn, i, layout) {
645+
plots.supplyDataDefaults = function(traceIn, traceIndex, layout) {
646646
var traceOut = {},
647-
defaultColor = Color.defaults[i % Color.defaults.length];
647+
defaultColor = Color.defaults[traceIndex % Color.defaults.length];
648648

649649
function coerce(attr, dflt) {
650650
return Lib.coerce(traceIn, traceOut, plots.attributes, attr, dflt);
651651
}
652652

653653
function coerceSubplotAttr(subplotType, subplotAttr) {
654654
if(!plots.traceIs(traceOut, subplotType)) return;
655+
655656
return Lib.coerce(traceIn, traceOut,
656657
plots.subplotsRegistry[subplotType].attributes, subplotAttr);
657658
}
658659

659660
// module-independent attributes
660-
traceOut.index = i;
661-
var visible = coerce('visible'),
662-
scene,
663-
_module;
661+
traceOut.index = traceIndex;
662+
var visible = coerce('visible');
664663

665664
coerce('type');
666665
coerce('uid');
667666

668-
// this is necessary otherwise we lose references to scene objects when
669-
// the traces of a scene are invisible. Also we handle visible/unvisible
670-
// differently for 3D cases.
671-
coerceSubplotAttr('gl3d', 'scene');
672-
coerceSubplotAttr('geo', 'geo');
673-
coerceSubplotAttr('ternary', 'subplot');
674-
675-
// module-specific attributes --- note: we need to send a trace into
676-
// the 3D modules to have it removed from the webgl context.
677-
if(visible || scene) {
678-
_module = plots.getModule(traceOut);
679-
traceOut._module = _module;
680-
}
667+
// coerce subplot attributes of all registered subplot types
668+
var subplotTypes = Object.keys(subplotsRegistry);
669+
for(var i = 0; i < subplotTypes.length; i++) {
670+
var subplotType = subplotTypes[i];
681671

682-
// gets overwritten in pie, geo and ternary modules
683-
if(visible) coerce('hoverinfo', (layout._dataLength === 1) ? 'x+y+z+text' : undefined);
672+
// done below (only when visible is true)
673+
// TODO unified this pattern
674+
if(['cartesian', 'gl2d'].indexOf(subplotType) !== -1) continue;
684675

685-
if(_module && visible) _module.supplyDefaults(traceIn, traceOut, defaultColor, layout);
676+
var attr = subplotsRegistry[subplotType].attr;
677+
678+
if(attr) coerceSubplotAttr(subplotType, attr);
679+
}
686680

687681
if(visible) {
688-
coerce('name', 'trace ' + i);
682+
var _module = plots.getModule(traceOut);
683+
traceOut._module = _module;
684+
685+
// gets overwritten in pie, geo and ternary modules
686+
coerce('hoverinfo', (layout._dataLength === 1) ? 'x+y+z+text' : undefined);
687+
688+
// TODO add per-base-plot-module trace defaults step
689+
690+
if(_module) _module.supplyDefaults(traceIn, traceOut, defaultColor, layout);
691+
692+
coerce('name', 'trace ' + traceIndex);
689693

690694
if(!plots.traceIs(traceOut, 'noOpacity')) coerce('opacity');
691695

src/snapshot/toimage.js

+5-10
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@
66
* LICENSE file in the root directory of this source tree.
77
*/
88

9-
/*eslint dot-notation: [2, {"allowPattern": "^catch$"}]*/
10-
119
'use strict';
1210

1311
var EventEmitter = require('events').EventEmitter;
12+
1413
var Plotly = require('../plotly');
14+
var Lib = require('../lib');
15+
1516

1617
/**
1718
* @param {object} gd figure Object
@@ -38,14 +39,8 @@ function toImage(gd, opts) {
3839
setTimeout(function() {
3940
var svg = Plotly.Snapshot.toSVG(clonedGd);
4041

41-
var canvasContainer = window.document.createElement('div');
42-
var canvas = window.document.createElement('canvas');
43-
44-
// window.document.body.appendChild(canvasContainer);
45-
canvasContainer.appendChild(canvas);
46-
47-
canvasContainer.id = Plotly.Lib.randstr();
48-
canvas.id = Plotly.Lib.randstr();
42+
var canvas = document.createElement('canvas');
43+
canvas.id = Lib.randstr();
4944

5045
ev = Plotly.Snapshot.svgToImg({
5146
format: opts.format,

src/snapshot/tosvg.js

+13-77
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111

1212
var d3 = require('d3');
1313

14-
var Plots = require('../plots/plots');
1514
var svgTextUtils = require('../lib/svg_text_utils');
1615
var Drawing = require('../components/drawing');
1716
var Color = require('../components/color');
@@ -20,80 +19,34 @@ var xmlnsNamespaces = require('../constants/xmlns_namespaces');
2019

2120

2221
module.exports = function toSVG(gd, format) {
22+
var fullLayout = gd._fullLayout,
23+
svg = fullLayout._paper,
24+
toppaper = fullLayout._toppaper,
25+
i;
2326

2427
// make background color a rect in the svg, then revert after scraping
2528
// all other alterations have been dealt with by properly preparing the svg
2629
// in the first place... like setting cursors with css classes so we don't
2730
// have to remove them, and providing the right namespaces in the svg to
2831
// begin with
29-
var fullLayout = gd._fullLayout,
30-
svg = fullLayout._paper,
31-
size = fullLayout._size,
32-
domain,
33-
i;
34-
3532
svg.insert('rect', ':first-child')
3633
.call(Drawing.setRect, 0, 0, fullLayout.width, fullLayout.height)
3734
.call(Color.fill, fullLayout.paper_bgcolor);
3835

39-
/* Grab the 3d scenes and rasterize em. Calculate their positions,
40-
* then insert them into the SVG element as images */
41-
var sceneIds = Plots.getSubplotIds(fullLayout, 'gl3d'),
42-
scene;
43-
44-
for(i = 0; i < sceneIds.length; i++) {
45-
scene = fullLayout[sceneIds[i]];
46-
domain = scene.domain;
47-
insertGlImage(fullLayout, scene._scene, {
48-
x: size.l + size.w * domain.x[0],
49-
y: size.t + size.h * (1 - domain.y[1]),
50-
width: size.w * (domain.x[1] - domain.x[0]),
51-
height: size.h * (domain.y[1] - domain.y[0])
52-
});
53-
}
54-
55-
// similarly for 2d scenes
56-
var subplotIds = Plots.getSubplotIds(fullLayout, 'gl2d'),
57-
subplot;
58-
59-
for(i = 0; i < subplotIds.length; i++) {
60-
subplot = fullLayout._plots[subplotIds[i]];
61-
insertGlImage(fullLayout, subplot._scene2d, {
62-
x: size.l,
63-
y: size.t,
64-
width: size.w,
65-
height: size.h
66-
});
67-
}
36+
// subplot-specific to-SVG methods
37+
// which notably add the contents of the gl-container
38+
// into the main svg node
39+
var basePlotModules = fullLayout._basePlotModules || [];
40+
for(i = 0; i < basePlotModules.length; i++) {
41+
var _module = basePlotModules[i];
6842

69-
// Grab the geos off the geo-container and place them in geoimages
70-
var geoIds = Plots.getSubplotIds(fullLayout, 'geo'),
71-
geoLayout,
72-
geoFramework;
73-
74-
for(i = 0; i < geoIds.length; i++) {
75-
geoLayout = fullLayout[geoIds[i]];
76-
domain = geoLayout.domain;
77-
geoFramework = geoLayout._geo.framework;
78-
79-
geoFramework.attr('style', null);
80-
geoFramework
81-
.attr({
82-
x: size.l + size.w * domain.x[0] + geoLayout._marginX,
83-
y: size.t + size.h * (1 - domain.y[1]) + geoLayout._marginY,
84-
width: geoLayout._width,
85-
height: geoLayout._height
86-
});
87-
88-
fullLayout._geoimages.node()
89-
.appendChild(geoFramework.node());
43+
if(_module.toSVG) _module.toSVG(gd);
9044
}
9145

92-
// now that we've got the 3d images in the right layer,
9346
// add top items above them assumes everything in toppaper is either
9447
// a group or a defs, and if it's empty (like hoverlayer) we can ignore it.
95-
if(fullLayout._toppaper) {
96-
var nodes = fullLayout._toppaper.node().childNodes;
48+
if(toppaper) {
49+
var nodes = toppaper.node().childNodes;
9750

9851
// make copy of nodes as childNodes prop gets mutated in loop below
9952
var topGroups = Array.prototype.slice.call(nodes);
@@ -164,20 +117,3 @@ module.exports = function toSVG(gd, format) {
164117

165118
return s;
166119
};
167-
168-
function insertGlImage(fullLayout, scene, opts) {
169-
var imageData = scene.toImage('png');
170-
171-
fullLayout._glimages.append('svg:image')
172-
.attr({
173-
xmlns: xmlnsNamespaces.svg,
174-
'xlink:href': imageData,
175-
x: opts.x,
176-
y: opts.y,
177-
width: opts.width,
178-
height: opts.height,
179-
preserveAspectRatio: 'none'
180-
});
181-
182-
scene.destroy();
183-
}

0 commit comments

Comments
 (0)