Skip to content

Commit

Permalink
Merge pull request #462 from plotly/ternary-fill
Browse files Browse the repository at this point in the history
Ternary (and scatter) fill
  • Loading branch information
alexcjohnson committed Apr 22, 2016
2 parents 2fdfc17 + aca5423 commit b0e7756
Show file tree
Hide file tree
Showing 11 changed files with 164 additions and 47 deletions.
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 });

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: [
'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.'
].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],
"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
}
}

0 comments on commit b0e7756

Please sign in to comment.