Skip to content

Ternary (and scatter) fill #462

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 11 commits into from
Apr 22, 2016
36 changes: 12 additions & 24 deletions src/plots/ternary/layout/axis_defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,6 @@ module.exports = function supplyLayoutDefaults(containerIn, containerOut, option
return Lib.coerce(containerIn, containerOut, layoutAttributes, attr, dflt);
}

function coerce2(attr, dflt) {
return Lib.coerce2(containerIn, containerOut, layoutAttributes, attr, dflt);
}

containerOut.type = 'linear'; // no other types allowed for ternary

var dfltColor = coerce('color');
Expand All @@ -54,10 +50,8 @@ module.exports = function supplyLayoutDefaults(containerIn, containerOut, option
handleTickValueDefaults(containerIn, containerOut, coerce, 'linear');
handleTickLabelDefaults(containerIn, containerOut, coerce, 'linear',
{ noHover: false });
handleTickMarkDefaults(containerIn, containerOut, coerce, 'linear',
{ outerticks: false });

// TODO - below is a bit repetitious from cartesian still...
handleTickMarkDefaults(containerIn, containerOut, coerce,
{ outerTicks: true });
Copy link
Contributor

Choose a reason for hiding this comment

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

yes.


var showTickLabels = coerce('showticklabels');
if(showTickLabels) {
Expand All @@ -72,23 +66,17 @@ module.exports = function supplyLayoutDefaults(containerIn, containerOut, option

coerce('hoverformat');

var lineColor = coerce2('linecolor', dfltColor),
lineWidth = coerce2('linewidth'),
showLine = coerce('showline', !!lineColor || !!lineWidth);

if(!showLine) {
delete containerOut.linecolor;
delete containerOut.linewidth;
var showLine = coerce('showline');
if(showLine) {
coerce('linecolor', dfltColor);
coerce('linewidth');
}

// default grid color is darker here (60%, vs cartesian default ~91%)
// because the grid is not square so the eye needs heavier cues to follow
var gridColor = coerce2('gridcolor', colorMix(dfltColor, options.bgColor, 60).toRgbString()),
gridWidth = coerce2('gridwidth'),
showGridLines = coerce('showgrid', !!gridColor || !!gridWidth);

if(!showGridLines) {
delete containerOut.gridcolor;
delete containerOut.gridwidth;
var showGridLines = coerce('showgrid');
if(showGridLines) {
// default grid color is darker here (60%, vs cartesian default ~91%)
// because the grid is not square so the eye needs heavier cues to follow
coerce('gridcolor', colorMix(dfltColor, options.bgColor, 60).toRgbString());
coerce('gridwidth');
}
};
22 changes: 19 additions & 3 deletions src/traces/scatter/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,18 +152,34 @@ module.exports = {
},
fill: {
valType: 'enumerated',
values: ['none', 'tozeroy', 'tozerox', 'tonexty', 'tonextx'],
values: ['none', 'tozeroy', 'tozerox', 'tonexty', 'tonextx', 'toself', 'tonext'],
dflt: 'none',
role: 'style',
description: [
Copy link
Contributor

Choose a reason for hiding this comment

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

would be a good time to add more info the description field.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

👍 added. It takes a lot to describe this behavior...

'Sets the area to fill with a solid color.',
'Use with `fillcolor`.'
'Use with `fillcolor` if not *none*.',
'*tozerox* and *tozeroy* fill to x=0 and y=0 respectively.',
'*tonextx* and *tonexty* fill between the endpoints of this',
'trace and the endpoints of the trace before it, connecting those',
'endpoints with straight lines (to make a stacked area graph);',
'if there is no trace before it, they behave like *tozerox* and',
'*tozeroy*.',
'*toself* connects the endpoints of the trace (or each segment',
'of the trace if it has gaps) into a closed shape.',
'*tonext* fills the space between two traces if one completely',
'encloses the other (eg consecutive contour lines), and behaves like',
'*toself* if there is no trace before it. *tonext* should not be',
'used if one trace does not enclose the other.'
Copy link
Contributor

Choose a reason for hiding this comment

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

fantastic.

].join(' ')
},
fillcolor: {
valType: 'color',
role: 'style',
description: 'Sets the fill color.'
description: [
'Sets the fill color.',
'Defaults to a half-transparent variant of the line color,',
'marker color, or marker line color, whichever is available.'
].join(' ')
},
marker: {
symbol: {
Expand Down
77 changes: 60 additions & 17 deletions src/traces/scatter/plot.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,21 +41,25 @@ module.exports = function plot(gd, plotinfo, cdscatter) {

// BUILD LINES AND FILLS
var prevpath = '',
tozero, tonext, nexttonext;
ownFillEl3, ownFillDir, tonext, nexttonext;

scattertraces.each(function(d) {
var trace = d[0].trace,
line = trace.line,
tr = d3.select(this);
if(trace.visible !== true) return;

ownFillDir = trace.fill.charAt(trace.fill.length - 1);
if(ownFillDir !== 'x' && ownFillDir !== 'y') ownFillDir = '';

d[0].node3 = tr; // store node for tweaking by selectPoints

arraysToCalcdata(d);

if(!subTypes.hasLines(trace) && trace.fill === 'none') return;

var thispath,
thisrevpath,
// fullpath is all paths for this curve, joined together straight
// across gaps, for filling
fullpath = '',
Expand All @@ -67,12 +71,12 @@ module.exports = function plot(gd, plotinfo, cdscatter) {
// make the fill-to-zero path now, so it shows behind the line
// fill to next puts the fill associated with one trace
// grouped with the previous
if(trace.fill.substr(0, 6) === 'tozero' ||
if(trace.fill.substr(0, 6) === 'tozero' || trace.fill === 'toself' ||
(trace.fill.substr(0, 2) === 'to' && !prevpath)) {
tozero = tr.append('path')
ownFillEl3 = tr.append('path')
.classed('js-fill', true);
}
else tozero = null;
else ownFillEl3 = null;

// make the fill-to-next path now for the NEXT trace, so it shows
// behind both lines.
Expand All @@ -91,7 +95,15 @@ module.exports = function plot(gd, plotinfo, cdscatter) {
}
else if(line.shape === 'spline') {
pathfn = revpathbase = function(pts) {
return Drawing.smoothopen(pts, line.smoothing);
var pLast = pts[pts.length - 1];
if(pts[0][0] === pLast[0] && pts[0][1] === pLast[1]) {
// identical start and end points: treat it as a
// closed curve so we don't get a kink
return Drawing.smoothclosed(pts.slice(1), line.smoothing);
}
else {
return Drawing.smoothopen(pts, line.smoothing);
}
};
}
else {
Expand All @@ -102,7 +114,7 @@ module.exports = function plot(gd, plotinfo, cdscatter) {

revpathfn = function(pts) {
// note: this is destructive (reverses pts in place) so can't use pts after this
return 'L' + revpathbase(pts.reverse()).substr(1);
return revpathbase(pts.reverse());
};

var segments = linePoints(d, {
Expand All @@ -121,27 +133,58 @@ module.exports = function plot(gd, plotinfo, cdscatter) {
for(var i = 0; i < segments.length; i++) {
var pts = segments[i];
thispath = pathfn(pts);
fullpath += fullpath ? ('L' + thispath.substr(1)) : thispath;
revpath = revpathfn(pts) + revpath;
thisrevpath = revpathfn(pts);
if(!fullpath) {
fullpath = thispath;
revpath = thisrevpath;
}
else if(ownFillDir) {
fullpath += 'L' + thispath.substr(1);
revpath = thisrevpath + ('L' + revpath.substr(1));
}
else {
fullpath += 'Z' + thispath;
revpath = thisrevpath + 'Z' + revpath;
}
if(subTypes.hasLines(trace) && pts.length > 1) {
tr.append('path').classed('js-line', true).attr('d', thispath);
}
}
if(tozero) {
if(ownFillEl3) {
if(pt0 && pt1) {
if(trace.fill.charAt(trace.fill.length - 1) === 'y') {
pt0[1] = pt1[1] = ya.c2p(0, true);
if(ownFillDir) {
if(ownFillDir === 'y') {
pt0[1] = pt1[1] = ya.c2p(0, true);
}
else if(ownFillDir === 'x') {
pt0[0] = pt1[0] = xa.c2p(0, true);
}

// fill to zero: full trace path, plus extension of
// the endpoints to the appropriate axis
ownFillEl3.attr('d', fullpath + 'L' + pt1 + 'L' + pt0 + 'Z');
}
else pt0[0] = pt1[0] = xa.c2p(0, true);

// fill to zero: full trace path, plus extension of
// the endpoints to the appropriate axis
tozero.attr('d', fullpath + 'L' + pt1 + 'L' + pt0 + 'Z');
// fill to self: just join the path to itself
else ownFillEl3.attr('d', fullpath + 'Z');
}
}
else if(trace.fill.substr(0, 6) === 'tonext' && fullpath && prevpath) {
// fill to next: full trace path, plus the previous path reversed
tonext.attr('d', fullpath + prevpath + 'Z');
if(trace.fill === 'tonext') {
// tonext: for use by concentric shapes, like manually constructed
// contours, we just add the two paths closed on themselves.
// This makes strange results if one path is *not* entirely
// inside the other, but then that is a strange usage.
tonext.attr('d', fullpath + 'Z' + prevpath + 'Z');
}
else {
// tonextx/y: for now just connect endpoints with lines. This is
// the correct behavior if the endpoints are at the same value of
// y/x, but if they *aren't*, we should ideally do more complicated
// things depending on whether the new endpoint projects onto the
// existing curve or off the end of it
tonext.attr('d', fullpath + 'L' + prevpath.substr(1) + 'Z');
}
}
prevpath = revpath;
}
Expand Down
15 changes: 15 additions & 0 deletions src/traces/scatterternary/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,21 @@ module.exports = {
smoothing: scatterLineAttrs.smoothing
},
connectgaps: scatterAttrs.connectgaps,
fill: extendFlat({}, scatterAttrs.fill, {
values: ['none', 'toself', 'tonext'],
description: [
'Sets the area to fill with a solid color.',
'Use with `fillcolor` if not *none*.',
'scatterternary has a subset of the options available to scatter.',
'*toself* connects the endpoints of the trace (or each segment',
'of the trace if it has gaps) into a closed shape.',
'*tonext* fills the space between two traces if one completely',
'encloses the other (eg consecutive contour lines), and behaves like',
'*toself* if there is no trace before it. *tonext* should not be',
'used if one trace does not enclose the other.'
].join(' ')
}),
fillcolor: scatterAttrs.fillcolor,
marker: {
symbol: scatterMarkerAttrs.symbol,
opacity: scatterMarkerAttrs.opacity,
Expand Down
10 changes: 7 additions & 3 deletions src/traces/scatterternary/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ var handleMarkerDefaults = require('../scatter/marker_defaults');
var handleLineDefaults = require('../scatter/line_defaults');
var handleLineShapeDefaults = require('../scatter/line_shape_defaults');
var handleTextDefaults = require('../scatter/text_defaults');
var handleFillColorDefaults = require('../scatter/fillcolor_defaults');

var attributes = require('./attributes');

Expand Down Expand Up @@ -84,8 +85,11 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
coerce('marker.maxdisplayed');
}

coerce('hoverinfo', (layout._dataLength === 1) ? 'a+b+c+text' : undefined);
coerce('fill');
if(traceOut.fill !== 'none') {
handleFillColorDefaults(traceIn, traceOut, defaultColor, coerce);
if(!subTypes.hasLines(traceOut)) handleLineShapeDefaults(traceIn, traceOut, coerce);
}

// until 'fill' and 'fillcolor' are supported
traceOut.fill = 'none';
coerce('hoverinfo', (layout._dataLength === 1) ? 'a+b+c+text' : undefined);
};
Binary file modified test/image/baselines/plot_types.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/image/baselines/scatter_fill_self_next.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/image/baselines/ternary_fill.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified test/image/baselines/ternary_multiple.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
25 changes: 25 additions & 0 deletions test/image/mocks/scatter_fill_self_next.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"data":[
{
"x": [1, 2, 3, 1, null, 4, 5, 6],
"y": [2, 3, 2, 2, null, 3, 4, 3],
Copy link
Contributor

Choose a reason for hiding this comment

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

great test mock.

"fill": "tonext",
"line":{"shape": "spline"}
},
{
"x": [-1, 4, 9, null, 0, 1, 2],
"y": [1, 6, 1, null, 5, 6, 5],
"fill": "tonext"
},
{
"x": [6, 7, 8],
"y": [5, 6, 5],
"fill": "toself"
}
],
"layout":{
"title": "Fill toself and tonext",
"width": 400,
"height": 400
}
}
26 changes: 26 additions & 0 deletions test/image/mocks/ternary_fill.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"data": [
{
"a": [0.4, 0.4, 0.2, 0.4],
"b":[0.2, 0.4, 0.4, 0.2],
"type": "scatterternary",
"fill": "tonext"
},
{
"a":[0.5, 0.5, 0, 0.5],
"b":[0, 0.5, 0.5, 0],
"type": "scatterternary",
"fill": "tonext"
},
{
"a": [0.8, 0.6, 0.6, 0.8, null, 0.1, 0.1, 0.3, 0.1, null, 0.1, 0.1, 0.3, 0.1],
"b": [0.1, 0.1, 0.3, 0.1, null, 0.8, 0.6, 0.6, 0.8, null, 0.3, 0.1, 0.1, 0.3],
"type": "scatterternary",
"fill": "toself"
}
],
"layout": {
"height": 400,
"width": 500
}
}