Skip to content

Seamless subplots #554

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
May 20, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 6 additions & 8 deletions src/plot_api/to_image.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@

'use strict';

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

var Plotly = require('../plotly');
var Lib = require('../lib');

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

/**
* @param {object} gd figure Object
Expand Down Expand Up @@ -61,13 +63,8 @@ function toImage(gd, opts) {
setTimeout(function() {
var svg = Snapshot.toSVG(clonedGd);

var canvasContainer = window.document.createElement('div');
var canvas = window.document.createElement('canvas');

canvasContainer.appendChild(canvas);

canvasContainer.id = Plotly.Lib.randstr();
canvas.id = Plotly.Lib.randstr();
var canvas = document.createElement('canvas');
canvas.id = Lib.randstr();

Snapshot.svgToImg({
format: opts.format,
Expand All @@ -86,6 +83,7 @@ function toImage(gd, opts) {
}).catch(function(err) {
reject(err);
});

}, delay);
});
}
Expand Down
24 changes: 24 additions & 0 deletions src/plots/geo/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,27 @@ exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout)
}
}
};

exports.toSVG = function(gd) {
var fullLayout = gd._fullLayout,
geoIds = Plots.getSubplotIds(fullLayout, 'geo'),
size = fullLayout._size;

for(var i = 0; i < geoIds.length; i++) {
var geoLayout = fullLayout[geoIds[i]],
domain = geoLayout.domain,
geoFramework = geoLayout._geo.framework;

geoFramework.attr('style', null);
geoFramework
.attr({
x: size.l + size.w * domain.x[0] + geoLayout._marginX,
y: size.t + size.h * (1 - domain.y[1]) + geoLayout._marginY,
width: geoLayout._width,
height: geoLayout._height
});

fullLayout._geoimages.node()
.appendChild(geoFramework.node());
}
};
32 changes: 28 additions & 4 deletions src/plots/gl2d/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,9 @@

'use strict';

var Plotly = require('../../plotly');

var Scene2D = require('./scene2d');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉


var Plots = Plotly.Plots;
var Plots = require('../plots');
var xmlnsNamespaces = require('../../constants/xmlns_namespaces');


exports.name = 'gl2d';
Expand Down Expand Up @@ -80,3 +78,29 @@ exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout)
}
}
};

exports.toSVG = function(gd) {
var fullLayout = gd._fullLayout,
subplotIds = Plots.getSubplotIds(fullLayout, 'gl2d'),
size = fullLayout._size;

for(var i = 0; i < subplotIds.length; i++) {
var subplot = fullLayout._plots[subplotIds[i]],
scene = subplot._scene2d;

var imageData = scene.toImage('png');
var image = fullLayout._glimages.append('svg:image');

image.attr({
xmlns: xmlnsNamespaces.svg,
'xlink:href': imageData,
x: size.l,
y: size.t,
width: size.w,
height: size.h,
preserveAspectRatio: 'none'
});

scene.destroy();
}
};
28 changes: 28 additions & 0 deletions src/plots/gl3d/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

var Scene = require('./scene');
var Plots = require('../plots');
var xmlnsNamespaces = require('../../constants/xmlns_namespaces');

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

Expand Down Expand Up @@ -83,6 +84,33 @@ exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout)
}
};

exports.toSVG = function(gd) {
var fullLayout = gd._fullLayout,
sceneIds = Plots.getSubplotIds(fullLayout, 'gl3d'),
size = fullLayout._size;

for(var i = 0; i < sceneIds.length; i++) {
var sceneLayout = fullLayout[sceneIds[i]],
domain = sceneLayout.domain,
scene = sceneLayout._scene;

var imageData = scene.toImage('png');
var image = fullLayout._glimages.append('svg:image');

image.attr({
xmlns: xmlnsNamespaces.svg,
'xlink:href': imageData,
x: size.l + size.w * domain.x[0],
y: size.t + size.h * (1 - domain.y[1]),
width: size.w * (domain.x[1] - domain.x[0]),
height: size.h * (domain.y[1] - domain.y[0]),
preserveAspectRatio: 'none'
});

scene.destroy();
}
};

// clean scene ids, 'scene1' -> 'scene'
exports.cleanId = function cleanId(id) {
if(!id.match(/^scene[0-9]*$/)) return;
Expand Down
50 changes: 27 additions & 23 deletions src/plots/plots.js
Original file line number Diff line number Diff line change
Expand Up @@ -642,50 +642,54 @@ function relinkPrivateKeys(toLayout, fromLayout) {
}
}

plots.supplyDataDefaults = function(traceIn, i, layout) {
plots.supplyDataDefaults = function(traceIn, traceIndex, layout) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awww yisssss. Love me some descriptive variable naming.

var traceOut = {},
defaultColor = Color.defaults[i % Color.defaults.length];
defaultColor = Color.defaults[traceIndex % Color.defaults.length];

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

function coerceSubplotAttr(subplotType, subplotAttr) {
if(!plots.traceIs(traceOut, subplotType)) return;

return Lib.coerce(traceIn, traceOut,
plots.subplotsRegistry[subplotType].attributes, subplotAttr);
}

// module-independent attributes
traceOut.index = i;
var visible = coerce('visible'),
scene,
_module;
traceOut.index = traceIndex;
var visible = coerce('visible');

coerce('type');
coerce('uid');

// this is necessary otherwise we lose references to scene objects when
// the traces of a scene are invisible. Also we handle visible/unvisible
// differently for 3D cases.
coerceSubplotAttr('gl3d', 'scene');
coerceSubplotAttr('geo', 'geo');
coerceSubplotAttr('ternary', 'subplot');

// module-specific attributes --- note: we need to send a trace into
// the 3D modules to have it removed from the webgl context.
if(visible || scene) {
_module = plots.getModule(traceOut);
traceOut._module = _module;
}
// coerce subplot attributes of all registered subplot types
var subplotTypes = Object.keys(subplotsRegistry);
for(var i = 0; i < subplotTypes.length; i++) {
var subplotType = subplotTypes[i];

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

if(_module && visible) _module.supplyDefaults(traceIn, traceOut, defaultColor, layout);
var attr = subplotsRegistry[subplotType].attr;

if(attr) coerceSubplotAttr(subplotType, attr);
}

if(visible) {
coerce('name', 'trace ' + i);
var _module = plots.getModule(traceOut);
traceOut._module = _module;

// gets overwritten in pie, geo and ternary modules
coerce('hoverinfo', (layout._dataLength === 1) ? 'x+y+z+text' : undefined);

// TODO add per-base-plot-module trace defaults step

if(_module) _module.supplyDefaults(traceIn, traceOut, defaultColor, layout);

coerce('name', 'trace ' + traceIndex);

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

Expand Down
15 changes: 5 additions & 10 deletions src/snapshot/toimage.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@
* LICENSE file in the root directory of this source tree.
*/

/*eslint dot-notation: [2, {"allowPattern": "^catch$"}]*/

'use strict';

var EventEmitter = require('events').EventEmitter;

var Plotly = require('../plotly');
var Lib = require('../lib');


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

var canvasContainer = window.document.createElement('div');
var canvas = window.document.createElement('canvas');

// window.document.body.appendChild(canvasContainer);
canvasContainer.appendChild(canvas);

canvasContainer.id = Plotly.Lib.randstr();
canvas.id = Plotly.Lib.randstr();
var canvas = document.createElement('canvas');
canvas.id = Lib.randstr();

ev = Plotly.Snapshot.svgToImg({
format: opts.format,
Expand Down
90 changes: 13 additions & 77 deletions src/snapshot/tosvg.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@

var d3 = require('d3');

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


module.exports = function toSVG(gd, format) {
var fullLayout = gd._fullLayout,
svg = fullLayout._paper,
toppaper = fullLayout._toppaper,
i;

// make background color a rect in the svg, then revert after scraping
// all other alterations have been dealt with by properly preparing the svg
// in the first place... like setting cursors with css classes so we don't
// have to remove them, and providing the right namespaces in the svg to
// begin with
var fullLayout = gd._fullLayout,
svg = fullLayout._paper,
size = fullLayout._size,
domain,
i;

svg.insert('rect', ':first-child')
.call(Drawing.setRect, 0, 0, fullLayout.width, fullLayout.height)
.call(Color.fill, fullLayout.paper_bgcolor);

/* Grab the 3d scenes and rasterize em. Calculate their positions,
* then insert them into the SVG element as images */
var sceneIds = Plots.getSubplotIds(fullLayout, 'gl3d'),
scene;

for(i = 0; i < sceneIds.length; i++) {
scene = fullLayout[sceneIds[i]];
domain = scene.domain;
insertGlImage(fullLayout, scene._scene, {
x: size.l + size.w * domain.x[0],
y: size.t + size.h * (1 - domain.y[1]),
width: size.w * (domain.x[1] - domain.x[0]),
height: size.h * (domain.y[1] - domain.y[0])
});
}

// similarly for 2d scenes
var subplotIds = Plots.getSubplotIds(fullLayout, 'gl2d'),
subplot;

for(i = 0; i < subplotIds.length; i++) {
subplot = fullLayout._plots[subplotIds[i]];
insertGlImage(fullLayout, subplot._scene2d, {
x: size.l,
y: size.t,
width: size.w,
height: size.h
});
}
// subplot-specific to-SVG methods
// which notably add the contents of the gl-container
// into the main svg node
var basePlotModules = fullLayout._basePlotModules || [];
for(i = 0; i < basePlotModules.length; i++) {
var _module = basePlotModules[i];

// Grab the geos off the geo-container and place them in geoimages
var geoIds = Plots.getSubplotIds(fullLayout, 'geo'),
geoLayout,
geoFramework;

for(i = 0; i < geoIds.length; i++) {
geoLayout = fullLayout[geoIds[i]];
domain = geoLayout.domain;
geoFramework = geoLayout._geo.framework;

geoFramework.attr('style', null);
geoFramework
.attr({
x: size.l + size.w * domain.x[0] + geoLayout._marginX,
y: size.t + size.h * (1 - domain.y[1]) + geoLayout._marginY,
width: geoLayout._width,
height: geoLayout._height
});

fullLayout._geoimages.node()
.appendChild(geoFramework.node());
if(_module.toSVG) _module.toSVG(gd);
}

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

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

return s;
};

function insertGlImage(fullLayout, scene, opts) {
var imageData = scene.toImage('png');

fullLayout._glimages.append('svg:image')
.attr({
xmlns: xmlnsNamespaces.svg,
'xlink:href': imageData,
x: opts.x,
y: opts.y,
width: opts.width,
height: opts.height,
preserveAspectRatio: 'none'
});

scene.destroy();
}