Skip to content
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

Cartesian subplot updates using data joins #946

Merged
merged 17 commits into from
Oct 6, 2016
Merged
Show file tree
Hide file tree
Changes from 15 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
5 changes: 3 additions & 2 deletions src/components/errorbars/plot.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ var subTypes = require('../../traces/scatter/subtypes');

module.exports = function plot(traces, plotinfo, transitionOpts) {
var isNew;
var xa = plotinfo.x(),
ya = plotinfo.y();

var xa = plotinfo.xaxis,
Copy link
Contributor

Choose a reason for hiding this comment

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

Is plotinfo.x() removed here or in general? I had some subtle problems with one or the other being up to date as axis references change.

ya = plotinfo.yaxis;

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

Expand Down
242 changes: 37 additions & 205 deletions src/plot_api/plot_api.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,6 @@ var subroutines = require('./subroutines');
/**
* Main plot-creation function
*
* Note: will call makePlotFramework if necessary to create the framework
*
* @param {string id or DOM element} gd
* the id or DOM element of the graph container div
* @param {array of objects} data
Expand Down Expand Up @@ -123,28 +121,15 @@ Plotly.plot = function(gd, data, layout, config) {
// so we don't try to re-call Plotly.plot from inside
// legend and colorbar, if margins changed
gd._replotting = true;
var hasData = gd._fullData.length > 0;

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

// Make or remake the framework (ie container and axes) if we need to
// note: if they container already exists and has data,
// the new layout gets ignored (as it should)
// but if there's no data there yet, it's just a placeholder...
// then it should destroy and remake the plot
if(hasData) {
if(gd.framework !== makePlotFramework || graphWasEmpty || !hasSameSubplots) {
gd.framework = makePlotFramework;
makePlotFramework(gd);
}
}
else if(!hasSameSubplots) {
// polar need a different framework
if(gd.framework !== makePlotFramework) {
Copy link
Contributor

Choose a reason for hiding this comment

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

The naming is a bit confusing to me since framework = noun while makePlotFramework = verb, but this is probably nothing new and not a big deal.

Copy link
Contributor Author

@etpinard etpinard Oct 6, 2016

Choose a reason for hiding this comment

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

I agree this is very strange. But, like pointed out by the comment above, the only reason this line is here is because of polar.

As polar is so broken at the moment, I prefer not changing things too much until we fully refactor it.

gd.framework = makePlotFramework;
makePlotFramework(gd);
}
else if(graphWasEmpty) makePlotFramework(gd);

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

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

// draw framework first so that margin-pushing
// components can position themselves correctly
function drawFramework() {
var basePlotModules = fullLayout._basePlotModules;

for(var i = 0; i < basePlotModules.length; i++) {
if(basePlotModules[i].drawFramework) {
basePlotModules[i].drawFramework(gd);
}
}

return Lib.syncOrAsync([
subroutines.layoutStyles,
drawAxes,
Fx.init
], gd);
}

// draw anything that can affect margins.
// currently this is legend and colorbars
function marginPushers() {
Expand All @@ -195,8 +198,10 @@ Plotly.plot = function(gd, data, layout, config) {
function marginPushersAgain() {
// in case the margins changed, draw margin pushers again
var seq = JSON.stringify(fullLayout._size) === oldmargins ?
[] : [marginPushers, subroutines.layoutStyles];
return Lib.syncOrAsync(seq.concat(Fx.init), gd);
[] :
[marginPushers, subroutines.layoutStyles];

return Lib.syncOrAsync(seq, gd);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

no need to call Fx.init here too as it is called in drawFramework on every Plotly.plot.

Previously, we need a change of subplot in order to trigger Fx.init.

}

function positionAndAutorange() {
Expand All @@ -220,7 +225,6 @@ Plotly.plot = function(gd, data, layout, config) {
}
}


// calc and autorange for errorbars
ErrorBars.calc(gd);

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

function doAutoRange() {
if(gd._transitioning) return;

var axList = Plotly.Axes.list(gd, '', true);
for(var i = 0; i < axList.length; i++) {
Plotly.Axes.doAutoRange(axList[i]);
}
}

// draw ticks, titles, and calculate axis scaling (._b, ._m)
function drawAxes() {
// draw ticks, titles, and calculate axis scaling (._b, ._m)
return Plotly.Axes.doTicks(gd, 'redraw');
}

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

// keep reference to shape layers in subplots
var layerSubplot = fullLayout._paper.selectAll('.layer-subplot');
fullLayout._imageSubplotLayer = layerSubplot.selectAll('.imagelayer');
fullLayout._shapeSubplotLayer = layerSubplot.selectAll('.shapelayer');

// styling separate from drawing
Plots.style(gd);

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

Lib.syncOrAsync([
Plots.previousPromises,
drawFramework,
marginPushers,
marginPushersAgain,
positionAndAutorange,
Expand Down Expand Up @@ -2753,21 +2764,12 @@ function makePlotFramework(gd) {
fullLayout._shapeLowerLayer = layerBelow.append('g')
.classed('shapelayer', true);

var subplots = Plotly.Axes.getSubplots(gd);
if(subplots.join('') !== Object.keys(gd._fullLayout._plots || {}).join('')) {
makeSubplots(gd, subplots);
}

if(fullLayout._has('cartesian')) makeCartesianPlotFramwork(gd, subplots);
// single cartesian layer for the whole plot
fullLayout._cartesianlayer = fullLayout._paper.append('g').classed('cartesianlayer', true);

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

// shape layers in subplots
var layerSubplot = fullLayout._paper.selectAll('.layer-subplot');
fullLayout._imageSubplotLayer = layerSubplot.selectAll('.imagelayer');
fullLayout._shapeSubplotLayer = layerSubplot.selectAll('.shapelayer');

// upper shape layer
// (only for shapes to be drawn above the whole plot, including subplots)
var layerAbove = fullLayout._paper.append('g')
Expand All @@ -2793,175 +2795,5 @@ function makePlotFramework(gd) {

gd.emit('plotly_framework');

// position and style the containers, make main title
var frameWorkDone = Lib.syncOrAsync([
subroutines.layoutStyles,
function goAxes() { return Plotly.Axes.doTicks(gd, 'redraw'); },
Fx.init
], gd);

if(frameWorkDone && frameWorkDone.then) {
gd._promises.push(frameWorkDone);
}

return frameWorkDone;
}

// create '_plots' object grouping x/y axes into subplots
// to be better manage subplots
function makeSubplots(gd, subplots) {
var _plots = gd._fullLayout._plots = {};
var subplot, plotinfo;

function getAxisFunc(subplot, axLetter) {
return function() {
return Plotly.Axes.getFromId(gd, subplot, axLetter);
};
}

for(var i = 0; i < subplots.length; i++) {
subplot = subplots[i];
plotinfo = _plots[subplot] = {};

plotinfo.id = subplot;

// references to the axis objects controlling this subplot
plotinfo.x = getAxisFunc(subplot, 'x');
plotinfo.y = getAxisFunc(subplot, 'y');

// TODO investigate why replacing calls to .x and .y
// for .xaxis and .yaxis makes the `pseudo_html`
// test image fail
plotinfo.xaxis = plotinfo.x();
plotinfo.yaxis = plotinfo.y();
}
}

function makeCartesianPlotFramwork(gd, subplots) {
var fullLayout = gd._fullLayout;

// Layers to keep plot types in the right order.
// from back to front:
// 1. heatmaps, 2D histos and contour maps
// 2. bars / 1D histos
// 3. errorbars for bars and scatter
// 4. scatter
// 5. box plots
function plotLayers(svg) {
svg.append('g').classed('imagelayer', true);
svg.append('g').classed('maplayer', true);
svg.append('g').classed('barlayer', true);
svg.append('g').classed('boxlayer', true);
svg.append('g').classed('scatterlayer', true);
}

// create all the layers in order, so we know they'll stay in order
var overlays = [];

fullLayout._paper.selectAll('g.subplot').data(subplots)
.enter().append('g')
.classed('subplot', true)
.each(function(subplot) {
var plotinfo = fullLayout._plots[subplot],
plotgroup = plotinfo.plotgroup = d3.select(this).classed(subplot, true),
xa = plotinfo.xaxis,
ya = plotinfo.yaxis;

// references to any subplots overlaid on this one
plotinfo.overlays = [];

// is this subplot overlaid on another?
// ax.overlaying is the id of another axis of the same
// dimension that this one overlays to be an overlaid subplot,
// the main plot must exist make sure we're not trying to
// overlay on an axis that's already overlaying another
var xa2 = Plotly.Axes.getFromId(gd, xa.overlaying) || xa;
if(xa2 !== xa && xa2.overlaying) {
xa2 = xa;
xa.overlaying = false;
}

var ya2 = Plotly.Axes.getFromId(gd, ya.overlaying) || ya;
if(ya2 !== ya && ya2.overlaying) {
ya2 = ya;
ya.overlaying = false;
}

var mainplot = xa2._id + ya2._id;
if(mainplot !== subplot && subplots.indexOf(mainplot) !== -1) {
plotinfo.mainplot = mainplot;
overlays.push(plotinfo);

// for now force overlays to overlay completely... so they
// can drag together correctly and share backgrounds.
// Later perhaps we make separate axis domain and
// tick/line domain or something, so they can still share
// the (possibly larger) dragger and background but don't
// have to both be drawn over that whole domain
xa.domain = xa2.domain.slice();
ya.domain = ya2.domain.slice();
}
else {
// main subplot - make the components of
// the plot and containers for overlays
plotinfo.bg = plotgroup.append('rect')
.style('stroke-width', 0);

// back layer for shapes and images to
// be drawn below a subplot
var backlayer = plotgroup.append('g')
.classed('layer-subplot', true);

plotinfo.shapelayer = backlayer.append('g')
.classed('shapelayer', true);
plotinfo.imagelayer = backlayer.append('g')
.classed('imagelayer', true);
plotinfo.gridlayer = plotgroup.append('g');
plotinfo.overgrid = plotgroup.append('g');
plotinfo.zerolinelayer = plotgroup.append('g');
plotinfo.overzero = plotgroup.append('g');
plotinfo.plot = plotgroup.append('g').call(plotLayers);
plotinfo.overplot = plotgroup.append('g');
plotinfo.xlines = plotgroup.append('path');
plotinfo.ylines = plotgroup.append('path');
plotinfo.overlines = plotgroup.append('g');
plotinfo.xaxislayer = plotgroup.append('g');
plotinfo.yaxislayer = plotgroup.append('g');
plotinfo.overaxes = plotgroup.append('g');

// make separate drag layers for each subplot,
// but append them to paper rather than the plot groups,
// so they end up on top of the rest
}
plotinfo.draglayer = fullLayout._draggers.append('g');
});

// now make the components of overlaid subplots
// overlays don't have backgrounds, and append all
// their other components to the corresponding
// extra groups of their main Plots.
overlays.forEach(function(plotinfo) {
var mainplot = fullLayout._plots[plotinfo.mainplot];
mainplot.overlays.push(plotinfo);

plotinfo.gridlayer = mainplot.overgrid.append('g');
plotinfo.zerolinelayer = mainplot.overzero.append('g');
plotinfo.plot = mainplot.overplot.append('g').call(plotLayers);
plotinfo.xlines = mainplot.overlines.append('path');
plotinfo.ylines = mainplot.overlines.append('path');
plotinfo.xaxislayer = mainplot.overaxes.append('g');
plotinfo.yaxislayer = mainplot.overaxes.append('g');
});

// common attributes for all subplots, overlays or not
subplots.forEach(function(subplot) {
var plotinfo = fullLayout._plots[subplot];

plotinfo.xlines
.style('fill', 'none')
.classed('crisp', true);
plotinfo.ylines
.style('fill', 'none')
.classed('crisp', true);
});
return 'FRAMEWORK';
Copy link
Contributor

Choose a reason for hiding this comment

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

What does this return code do? It seems 10% strange, so maybe a comment would be good if it's actually needed/expected.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh good catch.

There's no need for this line anymore.

}
2 changes: 1 addition & 1 deletion src/plot_api/subroutines.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ exports.lsInner = function(gd) {
var plotinfo = fullLayout._plots[subplot],
xa = Plotly.Axes.getFromId(gd, subplot, 'x'),
ya = Plotly.Axes.getFromId(gd, subplot, 'y');

xa.setScale(); // this may already be done... not sure
ya.setScale();

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


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

Expand Down
Loading