Skip to content

Heatmap ordering bug #2917

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 10 commits into from
Aug 17, 2018
48 changes: 48 additions & 0 deletions src/plots/cartesian/make_trace_groups.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* Copyright 2012-2018, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/


'use strict';

var d3 = require('d3');

/**
* helper for cartesian trace types to manage trace groups and plot traces
* into them
*
* @param {div} gd: plot div
* @param {object} plotinfo: the cartesian subplot info object
* @param {array} cdModule: array of calcdata items for this
* module and subplot combination
* @param {d3.selection} traceLayer: a selection containing a single group
* to draw these traces into
* @param {string} cls: the class attribute to give each trace group
* so you can give multiple classes separated by spaces
* @param {function} plotOneFn: a function that will plot one trace
* takes arguments:
* gd
* plotinfo
* cd: calcdata array for this one trace
* plotGroup: d3 selection of the single group to draw into
*/
module.exports = function makeTraceGroups(gd, plotinfo, cdModule, traceLayer, cls, plotOneFn) {
var traces = traceLayer.selectAll('g.' + cls.replace(/\s/g, '.'))
.data(cdModule, function(cd) { return cd[0].trace.uid; });

traces.exit().remove();

traces.enter().append('g')
.attr('class', cls);

traces.each(function(cd) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Ah very nice! That cls.replace(/s/g/, '.') is a nice touch.

I think makeTraceGroups should work with all svg-based subplot types, so perhaps we could move this to src/lib/? No sure where it would belong. There's lib/gup.js which has "general-update-pattern" helpers, maybe that would be a good fit.

Personally, I would not have included plotOneFn as an argument and here just called traces.order(), return traces and make _module.plot handle the trace.each "plot-one" logic. This way we wouldn't be tied down to one plotOneFn call signature and we wouldn't have to pass gd, plotinfo to makeTraceGroups, perhaps making things more reusable. But that's really more of a personal opinion, so consider this comment non-blocking 👌

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I was trying to match the existing pattern (from contourPlot.plotWrapper) but yeah, taking plotOne out would be cleaner and even more generalizable, I'll do that. Originally I wasn't returning the final selection but now that we have that this will be easy.

plotOneFn(gd, plotinfo, cd, d3.select(this));
})
.order();

return traces;
};
17 changes: 0 additions & 17 deletions src/plots/get_data.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,20 +124,3 @@ exports.getSubplotData = function getSubplotData(data, type, subplotId) {

return subplotData;
};

/**
* Get a lookup object of trace uids corresponding in a given calcdata array.
*
* @param {array} calcdata: as in gd.calcdata (or a subset)
* @return {object} lookup object of uids (`uid: 1`)
*/
exports.getUidsFromCalcData = function(calcdata) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks!

var out = {};

for(var i = 0; i < calcdata.length; i++) {
var trace = calcdata[i][0].trace;
out[trace.uid] = 1;
}

return out;
};
207 changes: 99 additions & 108 deletions src/traces/bar/plot.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ var svgTextUtils = require('../../lib/svg_text_utils');
var Color = require('../../components/color');
var Drawing = require('../../components/drawing');
var Registry = require('../../registry');
var makeTraceGroups = require('../../plots/cartesian/make_trace_groups');

var attributes = require('./attributes'),
attributeText = attributes.text,
Expand All @@ -31,138 +32,128 @@ var attributes = require('./attributes'),
var TEXTPAD = 3;

module.exports = function plot(gd, plotinfo, cdbar, barLayer) {
var bartraces = makeTraceGroups(gd, plotinfo, cdbar, barLayer, 'trace bars', plotOne);

// error bars are on the top
Registry.getComponentMethod('errorbars', 'plot')(bartraces, plotinfo);
};

function plotOne(gd, plotinfo, cd, plotGroup) {
var xa = plotinfo.xaxis;
var ya = plotinfo.yaxis;
var fullLayout = gd._fullLayout;
var cd0 = cd[0];
var t = cd0.t;
var trace = cd0.trace;

var bartraces = barLayer.selectAll('g.trace.bars')
.data(cdbar, function(d) { return d[0].trace.uid; });

bartraces.enter().append('g')
.attr('class', 'trace bars')
.append('g')
.attr('class', 'points');

bartraces.exit().remove();

bartraces.order();
if(!plotinfo.isRangePlot) cd0.node3 = plotGroup;

bartraces.each(function(d) {
var cd0 = d[0];
var t = cd0.t;
var trace = cd0.trace;
var sel = d3.select(this);
var poffset = t.poffset;
var poffsetIsArray = Array.isArray(poffset);

if(!plotinfo.isRangePlot) cd0.node3 = sel;
var pointGroup = Lib.ensureSingle(plotGroup, 'g', 'points');

var poffset = t.poffset;
var poffsetIsArray = Array.isArray(poffset);
var bars = pointGroup.selectAll('g.point').data(Lib.identity);

var bars = sel.select('g.points').selectAll('g.point').data(Lib.identity);
bars.enter().append('g')
.classed('point', true);

bars.enter().append('g')
.classed('point', true);
bars.exit().remove();

bars.exit().remove();
bars.each(function(di, i) {
var bar = d3.select(this);

bars.each(function(di, i) {
var bar = d3.select(this);
// now display the bar
// clipped xf/yf (2nd arg true): non-positive
// log values go off-screen by plotwidth
// so you see them continue if you drag the plot
var p0 = di.p + ((poffsetIsArray) ? poffset[i] : poffset),
p1 = p0 + di.w,
s0 = di.b,
s1 = s0 + di.s;

// now display the bar
// clipped xf/yf (2nd arg true): non-positive
// log values go off-screen by plotwidth
// so you see them continue if you drag the plot
var p0 = di.p + ((poffsetIsArray) ? poffset[i] : poffset),
p1 = p0 + di.w,
s0 = di.b,
s1 = s0 + di.s;
var x0, x1, y0, y1;
if(trace.orientation === 'h') {
y0 = ya.c2p(p0, true);
y1 = ya.c2p(p1, true);
x0 = xa.c2p(s0, true);
x1 = xa.c2p(s1, true);

var x0, x1, y0, y1;
if(trace.orientation === 'h') {
y0 = ya.c2p(p0, true);
y1 = ya.c2p(p1, true);
x0 = xa.c2p(s0, true);
x1 = xa.c2p(s1, true);

// for selections
di.ct = [x1, (y0 + y1) / 2];
}
else {
x0 = xa.c2p(p0, true);
x1 = xa.c2p(p1, true);
y0 = ya.c2p(s0, true);
y1 = ya.c2p(s1, true);

// for selections
di.ct = [(x0 + x1) / 2, y1];
}
// for selections
di.ct = [x1, (y0 + y1) / 2];
}
else {
x0 = xa.c2p(p0, true);
x1 = xa.c2p(p1, true);
y0 = ya.c2p(s0, true);
y1 = ya.c2p(s1, true);

if(!isNumeric(x0) || !isNumeric(x1) ||
!isNumeric(y0) || !isNumeric(y1) ||
x0 === x1 || y0 === y1) {
bar.remove();
return;
}
// for selections
di.ct = [(x0 + x1) / 2, y1];
}

var lw = (di.mlw + 1 || trace.marker.line.width + 1 ||
(di.trace ? di.trace.marker.line.width : 0) + 1) - 1,
offset = d3.round((lw / 2) % 1, 2);
if(!isNumeric(x0) || !isNumeric(x1) ||
!isNumeric(y0) || !isNumeric(y1) ||
x0 === x1 || y0 === y1) {
bar.remove();
return;
}

function roundWithLine(v) {
// if there are explicit gaps, don't round,
// it can make the gaps look crappy
return (fullLayout.bargap === 0 && fullLayout.bargroupgap === 0) ?
d3.round(Math.round(v) - offset, 2) : v;
}
var lw = (di.mlw + 1 || trace.marker.line.width + 1 ||
(di.trace ? di.trace.marker.line.width : 0) + 1) - 1,
offset = d3.round((lw / 2) % 1, 2);

function expandToVisible(v, vc) {
// if it's not in danger of disappearing entirely,
// round more precisely
return Math.abs(v - vc) >= 2 ? roundWithLine(v) :
// but if it's very thin, expand it so it's
// necessarily visible, even if it might overlap
// its neighbor
(v > vc ? Math.ceil(v) : Math.floor(v));
}
function roundWithLine(v) {
// if there are explicit gaps, don't round,
// it can make the gaps look crappy
return (fullLayout.bargap === 0 && fullLayout.bargroupgap === 0) ?
d3.round(Math.round(v) - offset, 2) : v;
}

if(!gd._context.staticPlot) {
// if bars are not fully opaque or they have a line
// around them, round to integer pixels, mainly for
// safari so we prevent overlaps from its expansive
// pixelation. if the bars ARE fully opaque and have
// no line, expand to a full pixel to make sure we
// can see them
var op = Color.opacity(di.mc || trace.marker.color),
fixpx = (op < 1 || lw > 0.01) ?
roundWithLine : expandToVisible;
x0 = fixpx(x0, x1);
x1 = fixpx(x1, x0);
y0 = fixpx(y0, y1);
y1 = fixpx(y1, y0);
}
function expandToVisible(v, vc) {
// if it's not in danger of disappearing entirely,
// round more precisely
return Math.abs(v - vc) >= 2 ? roundWithLine(v) :
// but if it's very thin, expand it so it's
// necessarily visible, even if it might overlap
// its neighbor
(v > vc ? Math.ceil(v) : Math.floor(v));
}

Lib.ensureSingle(bar, 'path')
.style('vector-effect', 'non-scaling-stroke')
.attr('d',
'M' + x0 + ',' + y0 + 'V' + y1 + 'H' + x1 + 'V' + y0 + 'Z')
.call(Drawing.setClipUrl, plotinfo.layerClipId);
if(!gd._context.staticPlot) {
// if bars are not fully opaque or they have a line
// around them, round to integer pixels, mainly for
// safari so we prevent overlaps from its expansive
// pixelation. if the bars ARE fully opaque and have
// no line, expand to a full pixel to make sure we
// can see them
var op = Color.opacity(di.mc || trace.marker.color),
fixpx = (op < 1 || lw > 0.01) ?
roundWithLine : expandToVisible;
x0 = fixpx(x0, x1);
x1 = fixpx(x1, x0);
y0 = fixpx(y0, y1);
y1 = fixpx(y1, y0);
}

appendBarText(gd, bar, d, i, x0, x1, y0, y1);
Lib.ensureSingle(bar, 'path')
.style('vector-effect', 'non-scaling-stroke')
.attr('d',
'M' + x0 + ',' + y0 + 'V' + y1 + 'H' + x1 + 'V' + y0 + 'Z')
.call(Drawing.setClipUrl, plotinfo.layerClipId);

if(plotinfo.layerClipId) {
Drawing.hideOutsideRangePoint(d[i], bar.select('text'), xa, ya, trace.xcalendar, trace.ycalendar);
}
});
appendBarText(gd, bar, cd, i, x0, x1, y0, y1);

// lastly, clip points groups of `cliponaxis !== false` traces
// on `plotinfo._hasClipOnAxisFalse === true` subplots
var hasClipOnAxisFalse = d[0].trace.cliponaxis === false;
Drawing.setClipUrl(sel, hasClipOnAxisFalse ? null : plotinfo.layerClipId);
if(plotinfo.layerClipId) {
Drawing.hideOutsideRangePoint(di, bar.select('text'), xa, ya, trace.xcalendar, trace.ycalendar);
}
});

// error bars are on the top
Registry.getComponentMethod('errorbars', 'plot')(bartraces, plotinfo);
};
// lastly, clip points groups of `cliponaxis !== false` traces
// on `plotinfo._hasClipOnAxisFalse === true` subplots
var hasClipOnAxisFalse = cd0.trace.cliponaxis === false;
Drawing.setClipUrl(plotGroup, hasClipOnAxisFalse ? null : plotinfo.layerClipId);
}

function appendBarText(gd, bar, calcTrace, i, x0, x1, y0, y1) {
var textPosition;
Expand Down
Loading