Skip to content

Range slider second iteration (part 2: on-par range plots !!!) #1017

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 4 commits into from
Oct 11, 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
119 changes: 109 additions & 10 deletions src/components/rangeslider/draw.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,18 @@ var d3 = require('d3');

var Plotly = require('../../plotly');
var Plots = require('../../plots/plots');
var Axes = require('../../plots/cartesian/axes');

var Lib = require('../../lib');
var Drawing = require('../drawing');
var Color = require('../color');

var Cartesian = require('../../plots/cartesian');
var Axes = require('../../plots/cartesian/axes');

var dragElement = require('../dragelement');
var setCursor = require('../../lib/setcursor');

var constants = require('./constants');
var rangePlot = require('./range_plot');


module.exports = function(gd) {
Expand Down Expand Up @@ -55,7 +59,14 @@ module.exports = function(gd) {
.classed(constants.containerClassName, true)
.attr('pointer-events', 'all');

rangeSliders.exit().remove();
// remove exiting sliders and their corresponding clip paths
rangeSliders.exit().each(function(axisOpts) {
var rangeSlider = d3.select(this),
opts = axisOpts[constants.name];

rangeSlider.remove();
fullLayout._topdefs.select('#' + opts._clipId).remove();
});

// remove push margin object(s)
if(rangeSliders.exit().size()) clearPushMargins(gd);
Expand All @@ -82,6 +93,8 @@ module.exports = function(gd) {
domain = axisOpts.domain;

opts._id = constants.name + axisOpts._id;
opts._clipId = opts._id + '-' + fullLayout._uid;

opts._width = graphSize.w * (domain[1] - domain[0]);
opts._height = (fullLayout.height - margin.b - margin.t) * opts.thickness;
opts._offsetShift = Math.floor(opts.borderwidth / 2);
Expand All @@ -95,6 +108,7 @@ module.exports = function(gd) {

rangeSlider
.call(drawBg, gd, axisOpts, opts)
.call(addClipPath, gd, axisOpts, opts)
.call(drawRangePlot, gd, axisOpts, opts)
.call(drawMasks, gd, axisOpts, opts)
.call(drawSlideBox, gd, axisOpts, opts)
Expand Down Expand Up @@ -279,17 +293,102 @@ function drawBg(rangeSlider, gd, axisOpts, opts) {
});
}

function drawRangePlot(rangeSlider, gd, axisOpts, opts) {
var rangePlots = rangePlot(gd, opts._width, opts._height);
function addClipPath(rangeSlider, gd, axisOpts, opts) {
var fullLayout = gd._fullLayout;

var gRangePlot = rangeSlider.selectAll('g.' + constants.rangePlotClassName)
var clipPath = fullLayout._topdefs.selectAll('#' + opts._clipId)
.data([0]);
Copy link
Contributor

Choose a reason for hiding this comment

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

Did you manage to factor out the .data([0]) pattern? I suppose it would be a function with a name like var clipPath = createOrAppend(fullLayout._topdefs, '#' + opts._clipId);?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not yet. I'll do that in a PR of its own. Hopefully this week.


gRangePlot.enter().append('g')
.classed(constants.rangePlotClassName, true);
clipPath.enter().append('clipPath')
.attr('id', opts._clipId)
.append('rect')
.attr({ x: 0, y: 0 });

clipPath.select('rect').attr({
width: opts._width,
height: opts._height
});
}

function drawRangePlot(rangeSlider, gd, axisOpts, opts) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

So, the algorithm is as follows:

  1. find all the subplots spanned by the axis where the range slider reside
  2. for each subplot, mock a plotinfo object (which are essentially the cartesian subplot state objects) using range slider options to find the width and height
  3. find all traces in this subplot
  4. Call Cartesian.rangePlot which creates all the required layers and plots / styles the given plotinfo subplot
  5. clip the subplot

var subplotData = Axes.getSubplots(gd, axisOpts),
calcData = gd.calcdata;

var rangePlots = rangeSlider.selectAll('g.' + constants.rangePlotClassName)
.data(subplotData, Lib.identity);

rangePlots.enter().append('g')
.attr('class', function(id) { return constants.rangePlotClassName + ' ' + id; })
.call(Drawing.setClipUrl, opts._clipId);

rangePlots.order();
Copy link
Contributor

Choose a reason for hiding this comment

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

👍 Nice. The order is important to manage (and easy to neglect) with all of these d3 joins.


rangePlots.exit().remove();

var mainplotinfo;

rangePlots.each(function(id, i) {
var plotgroup = d3.select(this),
isMainPlot = (i === 0);

var oppAxisOpts = Axes.getFromId(gd, id, 'y'),
oppAxisName = oppAxisOpts._name;

var mockFigure = {
Copy link
Contributor

@rreusser rreusser Oct 10, 2016

Choose a reason for hiding this comment

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

Interesting. There's something I always like the usage of supplyDefaults for mocking instead of the real thing. Feels robust and lightweight. I liked using it with animations since many things just worked.

data: [],
layout: {
xaxis: {
domain: [0, 1],
range: opts.range.slice()
},
width: opts._width,
height: opts._height,
margin: { t: 0, b: 0, l: 0, r: 0 }
}
};

mockFigure.layout[oppAxisName] = {
domain: [0, 1],
range: oppAxisOpts.range.slice()
};

Plots.supplyDefaults(mockFigure);

var xa = mockFigure._fullLayout.xaxis,
ya = mockFigure._fullLayout[oppAxisName];

var plotinfo = {
id: id,
plotgroup: plotgroup,
xaxis: xa,
yaxis: ya
};

if(isMainPlot) mainplotinfo = plotinfo;
else {
plotinfo.mainplot = 'xy';
plotinfo.mainplotinfo = mainplotinfo;
}

Cartesian.rangePlot(gd, plotinfo, filterRangePlotCalcData(calcData, id));

if(isMainPlot) plotinfo.bg.call(Color.fill, opts.bgcolor);
});
}

function filterRangePlotCalcData(calcData, subplotId) {
var out = [];

for(var i = 0; i < calcData.length; i++) {
var calcTrace = calcData[i],
trace = calcTrace[0].trace;

if(trace.xaxis + trace.yaxis === subplotId) {
out.push(calcTrace);
}
}

gRangePlot.html(null);
gRangePlot.node().appendChild(rangePlots);
return out;
}

function drawMasks(rangeSlider, gd, axisOpts, opts) {
Expand Down
24 changes: 0 additions & 24 deletions src/components/rangeslider/helpers.js

This file was deleted.

179 changes: 0 additions & 179 deletions src/components/rangeslider/range_plot.js

This file was deleted.

Loading