Skip to content

Joyplots2 #3234

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 62 commits into from
Jan 21, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
8f04d13
added vwidth param - not working
Coding-with-Adam Oct 12, 2018
f76ca97
update dPos with vwidth//remove most console.logs
Coding-with-Adam Oct 15, 2018
81cdbe9
some revision based on review comments
Coding-with-Adam Oct 17, 2018
5ef2eeb
change dflt vwidth to 0;assign dPos for each trace
Coding-with-Adam Oct 18, 2018
1c6b26b
vwidth is now the half width // padding now uses trace.width values i…
Coding-with-Adam Oct 19, 2018
2d93a0e
vwidth to width
Coding-with-Adam Oct 19, 2018
1f9ced8
compact the t.dPos assignment with trace.width
Coding-with-Adam Oct 19, 2018
26f44c4
change location of width attribute description to be consistent with …
Coding-with-Adam Oct 19, 2018
77b9c9d
more complete descriptions
Coding-with-Adam Oct 19, 2018
243a369
add 1 image test
Coding-with-Adam Oct 22, 2018
eb5e9da
add new line to joyplot mock file
Coding-with-Adam Oct 22, 2018
eea273b
camelCase
Coding-with-Adam Oct 24, 2018
408aacb
zapped trailing ',' in box/violin attributes
Coding-with-Adam Oct 24, 2018
0555283
do not coerce scale{mode, group} if no width; otherwise set to dflt v…
Coding-with-Adam Oct 24, 2018
4eb8b44
first pass at calculating extreames for each trace
Coding-with-Adam Oct 24, 2018
44e0a88
fix up the syntax of violin/defaults
Coding-with-Adam Oct 24, 2018
f8b963b
remove old baseline png
Coding-with-Adam Oct 24, 2018
54aa355
generated updated joyplot baseline image
Coding-with-Adam Oct 24, 2018
0d4239b
remove padding depending on trace.side//regen baseline
Coding-with-Adam Oct 24, 2018
fa3a314
fix space syntax error
Coding-with-Adam Oct 24, 2018
c63780f
...
Coding-with-Adam Oct 31, 2018
c24330d
update algo for padding
Coding-with-Adam Nov 1, 2018
6e7883d
fix test syntax
Coding-with-Adam Nov 1, 2018
37786d6
remove consolve logs
Coding-with-Adam Nov 1, 2018
45ae47d
update kde line test
Coding-with-Adam Nov 1, 2018
8366c88
more testing with jasmine
Coding-with-Adam Nov 1, 2018
ce81377
remove commented lines
Coding-with-Adam Nov 1, 2018
8885b5e
remove another commented line and a useless loop
Coding-with-Adam Nov 1, 2018
57cc05a
remove maxHalfWidth var
Coding-with-Adam Nov 1, 2018
fe3d7ed
cleaner code for vpad assignment
Coding-with-Adam Nov 1, 2018
44fa269
inherit violin 'width' from box.width
etpinard Nov 6, 2018
f5ba38c
rm useless attrs in test mock
etpinard Nov 6, 2018
34722d3
mention that 'width' is in data coordinates
etpinard Nov 6, 2018
d1aed48
ignore box/violin *gap and *groupgap when 'width' is set
etpinard Nov 6, 2018
c70f7d5
do not coerce scale* attrs when violin 'width' is set
etpinard Nov 6, 2018
0dea0e9
Merge pull request #3222 from plotly/joyplots-etpinard
Kully Nov 7, 2018
fee1ea7
when width is 0, vpads correctly include points for pos/neg sided vio…
Coding-with-Adam Nov 9, 2018
a3e0a6a
extra padding to fit marker sizes
Coding-with-Adam Nov 12, 2018
88cfde7
fix jasmine tests
Coding-with-Adam Nov 12, 2018
6115138
syntax improv
Coding-with-Adam Nov 12, 2018
ef72982
added 3 new mocks
Coding-with-Adam Nov 12, 2018
7541ad4
syntax on JSON mock file
Coding-with-Adam Nov 12, 2018
ace30bd
findExtreames uses padded: true
Coding-with-Adam Nov 14, 2018
faaf894
mv bPos, bdPos computation to Box.crossTraceCalc
etpinard Nov 23, 2018
db5c055
make violin 'side' an editType:'calc'
etpinard Nov 23, 2018
9962b97
update box and violin baselines
etpinard Nov 23, 2018
c026fc5
include marker.size px padding even at pointpos=0
etpinard Nov 26, 2018
b36cd18
Merge branch 'master' into joyplots2
etpinard Dec 17, 2018
401bcc8
Merge branch 'master' into joyplots2
etpinard Dec 19, 2018
d88ee97
Merge branch 'master' into joyplots2
etpinard Dec 27, 2018
2aec059
mv box/violin pt filter to calc step
etpinard Jan 8, 2019
bf6cfa2
new attempt for Box.crossTraceCalc
etpinard Jan 8, 2019
f239889
update box/violin baseline (more precise pts beyond box/violin vals)
etpinard Jan 8, 2019
ca69b62
update box/violin baselines (back or almost back to master)
etpinard Jan 8, 2019
7c98ae0
update box/violin baselines (improve new joyplot mocks)
etpinard Jan 8, 2019
12ff323
adapt jasmine test to new box/violin autorange vals
etpinard Jan 8, 2019
18ba8de
Merge branch 'master' into joyplots2
etpinard Jan 8, 2019
c1d6e76
:hocho: "joyplot" from mock titles
etpinard Jan 15, 2019
a6398f4
rm disable box/violin grouping logic
etpinard Jan 17, 2019
a237252
fix one-sided + set width violin axis-expand case
etpinard Jan 21, 2019
ef52b8c
add box/violin ax-rng test from "width" changes on visible restyle
etpinard Jan 21, 2019
2517ed0
Merge pull request #3445 from plotly/rm-box-ungrouping-magic
etpinard Jan 21, 2019
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
15 changes: 14 additions & 1 deletion src/traces/box/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,20 @@ module.exports = {
'the vertical (horizontal).'
].join(' ')
},

width: {
valType: 'number',
min: 0,
role: 'info',
dflt: 0,
editType: 'calc',
description: [
'Sets the width of the box in data coordinate',
'If *0* (default value) the width is automatically selected based on the positions',
'of other box traces in the same subplot.'
].join(' ')
},

marker: {
outliercolor: {
valType: 'color',
Expand Down Expand Up @@ -244,7 +258,6 @@ module.exports = {
marker: scatterAttrs.unselected.marker,
editType: 'style'
},

hoveron: {
valType: 'flaglist',
flags: ['boxes', 'points'],
Expand Down
15 changes: 10 additions & 5 deletions src/traces/box/calc.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,17 +64,21 @@ module.exports = function calc(gd, trace) {
}
}

var cdi;
var ptFilterFn = (trace.boxpoints || trace.points) === 'all' ?
Lib.identity :
function(pt) { return (pt.v < cdi.lf || pt.v > cdi.uf); };

// build calcdata trace items, one item per distinct position
for(i = 0; i < pLen; i++) {
if(ptsPerBin[i].length > 0) {
var pts = ptsPerBin[i].sort(sortByVal);
var boxVals = pts.map(extractVal);
var bvLen = boxVals.length;

var cdi = {
pos: posDistinct[i],
pts: pts
};
cdi = {};
cdi.pos = posDistinct[i];
cdi.pts = pts;

cdi.min = boxVals[0];
cdi.max = boxVals[bvLen - 1];
Expand Down Expand Up @@ -110,13 +114,14 @@ module.exports = function calc(gd, trace) {
cdi.lo = 4 * cdi.q1 - 3 * cdi.q3;
cdi.uo = 4 * cdi.q3 - 3 * cdi.q1;


// lower and upper notches ~95% Confidence Intervals for median
var iqr = cdi.q3 - cdi.q1;
var mci = 1.57 * iqr / Math.sqrt(bvLen);
cdi.ln = cdi.med - mci;
cdi.un = cdi.med + mci;

cdi.pts2 = pts.filter(ptFilterFn);

cd.push(cdi);
}
}
Expand Down
167 changes: 133 additions & 34 deletions src/traces/box/cross_trace_calc.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ function crossTraceCalc(gd, plotinfo) {
var orientation = orientations[i];
var posAxis = orientation === 'h' ? ya : xa;
var boxList = [];
var minPad = 0;
var maxPad = 0;

// make list of boxes / candlesticks
// For backward compatibility, candlesticks are treated as if they *are* box traces here
Expand All @@ -40,72 +38,173 @@ function crossTraceCalc(gd, plotinfo) {
trace.yaxis === ya._id
) {
boxList.push(j);

if(trace.boxpoints) {
minPad = Math.max(minPad, trace.jitter - trace.pointpos - 1);
maxPad = Math.max(maxPad, trace.jitter + trace.pointpos - 1);
}
}
}

setPositionOffset('box', gd, boxList, posAxis, [minPad, maxPad]);
setPositionOffset('box', gd, boxList, posAxis);
}
}

function setPositionOffset(traceType, gd, boxList, posAxis, pad) {
function setPositionOffset(traceType, gd, boxList, posAxis) {
var calcdata = gd.calcdata;
var fullLayout = gd._fullLayout;
var pointList = [];
var axId = posAxis._id;
var axLetter = axId.charAt(0);

// N.B. reused in violin
var numKey = traceType === 'violin' ? '_numViolins' : '_numBoxes';

var i, j, calcTrace;
var pointList = [];
var shownPts = 0;

// make list of box points
for(i = 0; i < boxList.length; i++) {
calcTrace = calcdata[boxList[i]];
for(j = 0; j < calcTrace.length; j++) {
pointList.push(calcTrace[j].pos);
shownPts += (calcTrace[j].pts2 || []).length;
}
}

if(!pointList.length) return;

// box plots - update dPos based on multiple traces
// and then use for posAxis autorange
var boxdv = Lib.distinctVals(pointList);
var dPos = boxdv.minDiff / 2;

// if there's no duplication of x points,
// disable 'group' mode by setting counter to 1
if(pointList.length === boxdv.vals.length) {
fullLayout[numKey] = 1;
}
var dPos0 = boxdv.minDiff / 2;

// check for forced minimum dtick
Axes.minDtick(posAxis, boxdv.minDiff, boxdv.vals[0], true);

var gap = fullLayout[traceType + 'gap'];
var groupgap = fullLayout[traceType + 'groupgap'];
var padfactor = (1 - gap) * (1 - groupgap) * dPos / fullLayout[numKey];

// autoscale the x axis - including space for points if they're off the side
// TODO: this will overdo it if the outermost boxes don't have
// their points as far out as the other boxes
var extremes = Axes.findExtremes(posAxis, boxdv.vals, {
vpadminus: dPos + pad[0] * padfactor,
vpadplus: dPos + pad[1] * padfactor
});
var num = fullLayout[numKey];
var group = (fullLayout[traceType + 'mode'] === 'group' && num > 1);
var groupFraction = 1 - fullLayout[traceType + 'gap'];
var groupGapFraction = 1 - fullLayout[traceType + 'groupgap'];

for(i = 0; i < boxList.length; i++) {
calcTrace = calcdata[boxList[i]];
// set the width of all boxes
calcTrace[0].t.dPos = dPos;
// link extremes to all boxes
calcTrace[0].trace._extremes[posAxis._id] = extremes;
}

var trace = calcTrace[0].trace;
var t = calcTrace[0].t;
var width = trace.width;
var side = trace.side;

// position coordinate delta
var dPos;
// box half width;
var bdPos;
// box center offset
var bPos;
// half-width within which to accept hover for this box/violin
// always split the distance to the closest box/violin
var wHover;

if(width) {
dPos = bdPos = wHover = width / 2;
bPos = 0;
} else {
dPos = dPos0;
bdPos = dPos * groupFraction * groupGapFraction / (group ? num : 1);
bPos = group ? 2 * dPos * (-0.5 + (t.num + 0.5) / num) * groupFraction : 0;
wHover = dPos * (group ? groupFraction / num : 1);
}
t.dPos = dPos;
t.bPos = bPos;
t.bdPos = bdPos;
t.wHover = wHover;

// box/violin-only value-space push value
var pushplus;
var pushminus;
// edge of box/violin
var edge = bPos + bdPos;
var edgeplus;
var edgeminus;

if(side === 'positive') {
pushplus = dPos * (width ? 1 : 0.5);
edgeplus = edge;
pushminus = edgeplus = bPos;
} else if(side === 'negative') {
pushplus = edgeplus = bPos;
pushminus = dPos * (width ? 1 : 0.5);
edgeminus = edge;
} else {
pushplus = pushminus = dPos;
edgeplus = edgeminus = edge;
}

// value-space padding
var vpadplus;
var vpadminus;
// pixel-space padding
var ppadplus;
var ppadminus;
// do we add 5% of both sides (for points beyond box/violin)
var padded = false;
// does this trace show points?
var hasPts = (trace.boxpoints || trace.points) && (shownPts > 0);

if(hasPts) {
var pointpos = trace.pointpos;
var jitter = trace.jitter;
var ms = trace.marker.size / 2;

var pp = 0;
if((pointpos + jitter) >= 0) {
pp = edge * (pointpos + jitter);
if(pp > pushplus) {
// (++) beyond plus-value, use pp
padded = true;
ppadplus = ms;
vpadplus = pp;
} else if(pp > edgeplus) {
// (+), use push-value (it's bigger), but add px-pad
ppadplus = ms;
vpadplus = pushplus;
}
}
if(pp <= pushplus) {
// (->) fallback to push value
vpadplus = pushplus;
}

var pm = 0;
if((pointpos - jitter) <= 0) {
pm = -edge * (pointpos - jitter);
if(pm > pushminus) {
// (--) beyond plus-value, use pp
padded = true;
ppadminus = ms;
vpadminus = pm;
} else if(pm > edgeminus) {
// (-), use push-value (it's bigger), but add px-pad
ppadminus = ms;
vpadminus = pushminus;
}
}
if(pm <= pushminus) {
// (<-) fallback to push value
vpadminus = pushminus;
}
} else {
vpadplus = pushplus;
vpadminus = pushminus;
}

// calcdata[i][j] are in ascending order
var firstPos = calcTrace[0].pos;
var lastPos = calcTrace[calcTrace.length - 1].pos;

trace._extremes[axId] = Axes.findExtremes(posAxis, [firstPos, lastPos], {
padded: padded,
vpadminus: vpadminus,
vpadplus: vpadplus,
// N.B. SVG px-space positive/negative
ppadminus: {x: ppadminus, y: ppadplus}[axLetter],
ppadplus: {x: ppadplus, y: ppadminus}[axLetter],
});
}
}

module.exports = {
Expand Down
1 change: 1 addition & 0 deletions src/traces/box/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ function supplyDefaults(traceIn, traceOut, defaultColor, layout) {

coerce('whiskerwidth');
coerce('boxmean');
coerce('width');

var notched = coerce('notched', traceIn.notchwidth !== undefined);
if(notched) coerce('notchwidth');
Expand Down
9 changes: 6 additions & 3 deletions src/traces/box/layout_attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ module.exports = {
'If *group*, the boxes are plotted next to one another',
'centered around the shared location.',
'If *overlay*, the boxes are plotted over one another,',
'you might need to set *opacity* to see them multiple boxes.'
'you might need to set *opacity* to see them multiple boxes.',
'Has no effect on traces that have *width* set.'
].join(' ')
},
boxgap: {
Expand All @@ -34,7 +35,8 @@ module.exports = {
editType: 'calc',
description: [
'Sets the gap (in plot fraction) between boxes of',
'adjacent location coordinates.'
'adjacent location coordinates.',
'Has no effect on traces that have *width* set.'
].join(' ')
},
boxgroupgap: {
Expand All @@ -46,7 +48,8 @@ module.exports = {
editType: 'calc',
description: [
'Sets the gap (in plot fraction) between boxes of',
'the same location coordinate.'
'the same location coordinate.',
'Has no effect on traces that have *width* set.'
].join(' ')
}
};
24 changes: 3 additions & 21 deletions src/traces/box/plot.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,25 +18,18 @@ var JITTERCOUNT = 5; // points either side of this to include
var JITTERSPREAD = 0.01; // fraction of IQR to count as "dense"

function plot(gd, plotinfo, cdbox, boxLayer) {
var fullLayout = gd._fullLayout;
var xa = plotinfo.xaxis;
var ya = plotinfo.yaxis;
var numBoxes = fullLayout._numBoxes;
var groupFraction = (1 - fullLayout.boxgap);
var group = (fullLayout.boxmode === 'group' && numBoxes > 1);

Lib.makeTraceGroups(boxLayer, cdbox, 'trace boxes').each(function(cd) {
var plotGroup = d3.select(this);
var cd0 = cd[0];
var t = cd0.t;
var trace = cd0.trace;
if(!plotinfo.isRangePlot) cd0.node3 = plotGroup;
// box half width
var bdPos = t.dPos * groupFraction * (1 - fullLayout.boxgroupgap) / (group ? numBoxes : 1);
// box center offset
var bPos = group ? 2 * t.dPos * (-0.5 + (t.num + 0.5) / numBoxes) * groupFraction : 0;

// whisker width
var wdPos = bdPos * trace.whiskerwidth;
t.wdPos = t.bdPos * trace.whiskerwidth;

if(trace.visible !== true || t.empty) {
plotGroup.remove();
Expand All @@ -53,14 +46,6 @@ function plot(gd, plotinfo, cdbox, boxLayer) {
valAxis = ya;
}

// save the box size and box position for use by hover
t.bPos = bPos;
t.bdPos = bdPos;
t.wdPos = wdPos;
// half-width within which to accept hover for this box
// always split the distance to the closest box
t.wHover = t.dPos * (group ? groupFraction / numBoxes : 1);

plotBoxAndWhiskers(plotGroup, {pos: posAxis, val: valAxis}, trace, t);
plotPoints(plotGroup, {x: xa, y: ya}, trace, t);
plotBoxMean(plotGroup, {pos: posAxis, val: valAxis}, trace, t);
Expand Down Expand Up @@ -192,10 +177,7 @@ function plotPoints(sel, axes, trace, t) {
var paths = gPoints.selectAll('path')
.data(function(d) {
var i;

var pts = mode === 'all' ?
d.pts :
d.pts.filter(function(pt) { return (pt.v < d.lf || pt.v > d.uf); });
var pts = d.pts2;

// normally use IQR, but if this is 0 or too small, use max-min
var typicalSpread = Math.max((d.max - d.min) / 10, d.q3 - d.q1);
Expand Down
Loading