Skip to content

Commit 79d6ef1

Browse files
authored
Merge pull request #3817 from plotly/funnel-traces
Funnel traces
2 parents 7e52977 + 6d50386 commit 79d6ef1

File tree

88 files changed

+5362
-446
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

88 files changed

+5362
-446
lines changed

Diff for: lib/funnel.js

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/**
2+
* Copyright 2012-2019, Plotly, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
'use strict';
10+
11+
module.exports = require('../src/traces/funnel');

Diff for: lib/index-finance.js

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Plotly.register([
1616
require('./pie'),
1717
require('./ohlc'),
1818
require('./candlestick'),
19+
require('./funnel'),
1920
require('./waterfall')
2021
]);
2122

Diff for: lib/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Plotly.register([
2121
require('./contour'),
2222
require('./scatterternary'),
2323
require('./violin'),
24+
require('./funnel'),
2425
require('./waterfall'),
2526

2627
require('./pie'),

Diff for: src/components/drawing/index.js

+1-3
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,7 @@ drawing.hideOutsideRangePoints = function(traceGroups, subplot) {
110110
var trace = d[0].trace;
111111
var xcalendar = trace.xcalendar;
112112
var ycalendar = trace.ycalendar;
113-
var selector = trace.type === 'bar' ? '.bartext' :
114-
trace.type === 'waterfall' ? '.bartext,.line' :
115-
'.point,.textpoint';
113+
var selector = Registry.traceIs(trace, 'bar-like') ? '.bartext' : '.point,.textpoint';
116114

117115
traceGroups.selectAll(selector).each(function(d) {
118116
drawing.hideOutsideRangePoint(d, d3.select(this), xa, ya, xcalendar, ycalendar);

Diff for: src/components/legend/style.js

+16-4
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ module.exports = function style(s, gd) {
8484
.classed('legendpoints', true);
8585
})
8686
.each(styleWaterfalls)
87+
.each(styleFunnels)
8788
.each(styleBars)
8889
.each(styleBoxes)
8990
.each(stylePies)
@@ -306,14 +307,25 @@ module.exports = function style(s, gd) {
306307
}
307308

308309
function styleBars(d) {
310+
styleBarFamily(d, this);
311+
}
312+
313+
function styleFunnels(d) {
314+
styleBarFamily(d, this, 'funnel');
315+
}
316+
317+
function styleBarFamily(d, lThis, desiredType) {
309318
var trace = d[0].trace;
310319
var marker = trace.marker || {};
311320
var markerLine = marker.line || {};
312321

313-
var barpath = d3.select(this).select('g.legendpoints')
314-
.selectAll('path.legendbar')
315-
.data(Registry.traceIs(trace, 'bar') ? [d] : []);
316-
barpath.enter().append('path').classed('legendbar', true)
322+
var isVisible = (!desiredType) ? Registry.traceIs(trace, 'bar') :
323+
(trace.type === desiredType && trace.visible);
324+
325+
var barpath = d3.select(lThis).select('g.legendpoints')
326+
.selectAll('path.legend' + desiredType)
327+
.data(isVisible ? [d] : []);
328+
barpath.enter().append('path').classed('legend' + desiredType, true)
317329
.attr('d', 'M6,6H-6V-6H6Z')
318330
.attr('transform', 'translate(20,0)');
319331
barpath.exit().remove();

Diff for: src/plot_api/helpers.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ exports.cleanData = function(data) {
328328
trace.scene = Plots.subplotsRegistry.gl3d.cleanId(trace.scene);
329329
}
330330

331-
if(!traceIs(trace, 'pie') && !traceIs(trace, 'bar') && trace.type !== 'waterfall') {
331+
if(!traceIs(trace, 'pie') && !traceIs(trace, 'bar-like')) {
332332
if(Array.isArray(trace.textposition)) {
333333
for(i = 0; i < trace.textposition.length; i++) {
334334
trace.textposition[i] = cleanTextPosition(trace.textposition[i]);

Diff for: src/plots/cartesian/axes.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -2813,7 +2813,7 @@ function hasBarsOrFill(gd, ax) {
28132813

28142814
if(trace.visible === true && (trace.xaxis + trace.yaxis) === subplot) {
28152815
if(
2816-
(Registry.traceIs(trace, 'bar') || trace.type === 'waterfall') &&
2816+
Registry.traceIs(trace, 'bar-like') &&
28172817
trace.orientation === {x: 'h', y: 'v'}[axLetter]
28182818
) return true;
28192819

Diff for: src/plots/cartesian/axis_defaults.js

+13-5
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,18 @@ var setConvert = require('./set_convert');
3131
* noTickson: boolean, this axis doesn't support 'tickson'
3232
* data: the plot data, used to manage categories
3333
* bgColor: the plot background color, to calculate default gridline colors
34+
* calendar:
35+
* splomStash:
36+
* visibleDflt: boolean
37+
* reverseDflt: boolean
38+
* automargin: boolean
3439
*/
3540
module.exports = function handleAxisDefaults(containerIn, containerOut, coerce, options, layoutOut) {
3641
var letter = options.letter;
3742
var font = options.font || {};
3843
var splomStash = options.splomStash || {};
3944

40-
var visible = coerce('visible', !options.cheateronly);
45+
var visible = coerce('visible', !options.visibleDflt);
4146

4247
var axType = containerOut.type;
4348

@@ -48,7 +53,9 @@ module.exports = function handleAxisDefaults(containerIn, containerOut, coerce,
4853

4954
setConvert(containerOut, layoutOut);
5055

51-
var autoRange = coerce('autorange', !containerOut.isValidRange(containerIn.range));
56+
var autorangeDflt = !containerOut.isValidRange(containerIn.range);
57+
if(autorangeDflt && options.reverseDflt) autorangeDflt = 'reversed';
58+
var autoRange = coerce('autorange', autorangeDflt);
5259
if(autoRange && (axType === 'linear' || axType === '-')) coerce('rangemode');
5360

5461
coerce('range');
@@ -58,8 +65,6 @@ module.exports = function handleAxisDefaults(containerIn, containerOut, coerce,
5865

5966
if(axType !== 'category' && !options.noHover) coerce('hoverformat');
6067

61-
if(!visible) return containerOut;
62-
6368
var dfltColor = coerce('color');
6469
// if axis.color was provided, use it for fonts too; otherwise,
6570
// inherit from global font color in case that was provided.
@@ -69,6 +74,9 @@ module.exports = function handleAxisDefaults(containerIn, containerOut, coerce,
6974
// try to get default title from splom trace, fallback to graph-wide value
7075
var dfltTitle = splomStash.label || layoutOut._dfltTitle[letter];
7176

77+
handleTickLabelDefaults(containerIn, containerOut, coerce, axType, options, {pass: 1});
78+
if(!visible) return containerOut;
79+
7280
coerce('title.text', dfltTitle);
7381
Lib.coerceFont(coerce, 'title.font', {
7482
family: font.family,
@@ -77,7 +85,7 @@ module.exports = function handleAxisDefaults(containerIn, containerOut, coerce,
7785
});
7886

7987
handleTickValueDefaults(containerIn, containerOut, coerce, axType);
80-
handleTickLabelDefaults(containerIn, containerOut, coerce, axType, options);
88+
handleTickLabelDefaults(containerIn, containerOut, coerce, axType, options, {pass: 2});
8189
handleTickMarkDefaults(containerIn, containerOut, coerce, options);
8290
handleLineGridDefaults(containerIn, containerOut, coerce, {
8391
dfltColor: dfltColor,

Diff for: src/plots/cartesian/constants.js

+8-1
Original file line numberDiff line numberDiff line change
@@ -65,14 +65,21 @@ module.exports = {
6565
traceLayerClasses: [
6666
'heatmaplayer',
6767
'contourcarpetlayer', 'contourlayer',
68-
'waterfalllayer', 'barlayer',
68+
'funnellayer', 'waterfalllayer', 'barlayer',
6969
'carpetlayer',
7070
'violinlayer',
7171
'boxlayer',
7272
'ohlclayer',
7373
'scattercarpetlayer', 'scatterlayer'
7474
],
7575

76+
clipOnAxisFalseQuery: [
77+
'.scatterlayer',
78+
'.barlayer',
79+
'.funnellayer',
80+
'.waterfalllayer'
81+
],
82+
7683
layerValue2layerClass: {
7784
'above traces': 'above',
7885
'below traces': 'below'

Diff for: src/plots/cartesian/index.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ function plotOne(gd, plotinfo, cdSubplot, transitionOpts, makeOnCompleteCallback
260260
);
261261

262262
// layers that allow `cliponaxis: false`
263-
if(className !== 'scatterlayer' && className !== 'barlayer' && className !== 'waterfalllayer') {
263+
if(constants.clipOnAxisFalseQuery.indexOf('.' + className) === -1) {
264264
Drawing.setClipUrl(sel, plotinfo.layerClipId, gd);
265265
}
266266
});
@@ -276,7 +276,7 @@ function plotOne(gd, plotinfo, cdSubplot, transitionOpts, makeOnCompleteCallback
276276
if(!gd._context.staticPlot) {
277277
if(plotinfo._hasClipOnAxisFalse) {
278278
plotinfo.clipOnAxisFalseTraces = plotinfo.plot
279-
.selectAll('.scatterlayer, .barlayer, .waterfalllayer')
279+
.selectAll(constants.clipOnAxisFalseQuery.join(','))
280280
.selectAll('.trace');
281281
}
282282

Diff for: src/plots/cartesian/layout_defaults.js

+38-10
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,12 @@ function appendList(cont, k, item) {
3535

3636
module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
3737
var ax2traces = {};
38-
var xaCheater = {};
39-
var xaNonCheater = {};
38+
var xaMayHide = {};
39+
var yaMayHide = {};
40+
var xaMustDisplay = {};
41+
var yaMustDisplay = {};
42+
var yaMustForward = {};
43+
var yaMayBackward = {};
4044
var outerTicks = {};
4145
var noGrids = {};
4246
var i, j;
@@ -66,30 +70,46 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
6670
}
6771
}
6872

73+
// logic for funnels
74+
if(trace.type === 'funnel') {
75+
if(trace.orientation === 'h') {
76+
if(xaName) xaMayHide[xaName] = true;
77+
if(yaName) yaMayBackward[yaName] = true;
78+
} else {
79+
if(yaName) yaMayHide[yaName] = true;
80+
}
81+
} else {
82+
if(yaName) {
83+
yaMustDisplay[yaName] = true;
84+
yaMustForward[yaName] = true;
85+
}
86+
87+
if(!traceIs(trace, 'carpet') || (trace.type === 'carpet' && !trace._cheater)) {
88+
if(xaName) xaMustDisplay[xaName] = true;
89+
}
90+
}
91+
6992
// Two things trigger axis visibility:
7093
// 1. is not carpet
7194
// 2. carpet that's not cheater
72-
if(!traceIs(trace, 'carpet') || (trace.type === 'carpet' && !trace._cheater)) {
73-
if(xaName) xaNonCheater[xaName] = 1;
74-
}
7595

7696
// The above check for definitely-not-cheater is not adequate. This
7797
// second list tracks which axes *could* be a cheater so that the
7898
// full condition triggering hiding is:
7999
// *could* be a cheater and *is not definitely visible*
80100
if(trace.type === 'carpet' && trace._cheater) {
81-
if(xaName) xaCheater[xaName] = 1;
101+
if(xaName) xaMayHide[xaName] = true;
82102
}
83103

84104
// check for default formatting tweaks
85105
if(traceIs(trace, '2dMap')) {
86-
outerTicks[xaName] = 1;
87-
outerTicks[yaName] = 1;
106+
outerTicks[xaName] = true;
107+
outerTicks[yaName] = true;
88108
}
89109

90110
if(traceIs(trace, 'oriented')) {
91111
var positionAxis = trace.orientation === 'h' ? yaName : xaName;
92-
noGrids[positionAxis] = 1;
112+
noGrids[positionAxis] = true;
93113
}
94114
}
95115

@@ -167,6 +187,13 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
167187

168188
var overlayableAxes = getOverlayableAxes(axLetter, axName);
169189

190+
var visibleDflt =
191+
(axLetter === 'x' && !xaMustDisplay[axName] && xaMayHide[axName]) ||
192+
(axLetter === 'y' && !yaMustDisplay[axName] && yaMayHide[axName]);
193+
194+
var reverseDflt =
195+
(axLetter === 'y' && !yaMustForward[axName] && yaMayBackward[axName]);
196+
170197
var defaultOptions = {
171198
letter: axLetter,
172199
font: layoutOut.font,
@@ -176,7 +203,8 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
176203
bgColor: bgColor,
177204
calendar: layoutOut.calendar,
178205
automargin: true,
179-
cheateronly: axLetter === 'x' && xaCheater[axName] && !xaNonCheater[axName],
206+
visibleDflt: visibleDflt,
207+
reverseDflt: reverseDflt,
180208
splomStash: ((layoutOut._splomAxes || {})[axLetter] || {})[id]
181209
};
182210

Diff for: src/plots/cartesian/tick_label_defaults.js

+22-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,27 @@ var Lib = require('../../lib');
1313
var layoutAttributes = require('./layout_attributes');
1414
var handleArrayContainerDefaults = require('../array_container_defaults');
1515

16-
module.exports = function handleTickLabelDefaults(containerIn, containerOut, coerce, axType, options) {
16+
module.exports = function handleTickLabelDefaults(containerIn, containerOut, coerce, axType, options, config) {
17+
if(!config || config.pass === 1) {
18+
handlePrefixSuffix(containerIn, containerOut, coerce, axType, options);
19+
}
20+
21+
if(!config || config.pass === 2) {
22+
handleOtherDefaults(containerIn, containerOut, coerce, axType, options);
23+
}
24+
};
25+
26+
function handlePrefixSuffix(containerIn, containerOut, coerce, axType, options) {
27+
var showAttrDflt = getShowAttrDflt(containerIn);
28+
29+
var tickPrefix = coerce('tickprefix');
30+
if(tickPrefix) coerce('showtickprefix', showAttrDflt);
31+
32+
var tickSuffix = coerce('ticksuffix', options.tickSuffixDflt);
33+
if(tickSuffix) coerce('showticksuffix', showAttrDflt);
34+
}
35+
36+
function handleOtherDefaults(containerIn, containerOut, coerce, axType, options) {
1737
var showAttrDflt = getShowAttrDflt(containerIn);
1838

1939
var tickPrefix = coerce('tickprefix');
@@ -54,7 +74,7 @@ module.exports = function handleTickLabelDefaults(containerIn, containerOut, coe
5474
}
5575
}
5676
}
57-
};
77+
}
5878

5979
/*
6080
* Attributes 'showexponent', 'showtickprefix' and 'showticksuffix'

Diff for: src/traces/bar/attributes.js

+24
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,30 @@ module.exports = {
8686
].join(' ')
8787
},
8888

89+
insidetextanchor: {
90+
valType: 'enumerated',
91+
values: ['end', 'middle', 'start'],
92+
dflt: 'end',
93+
role: 'info', // TODO: or style ?
94+
editType: 'plot',
95+
description: [
96+
'Determines if texts are kept at center or start/end points in `textposition` *inside* mode.'
97+
].join(' ')
98+
},
99+
100+
textangle: {
101+
valType: 'angle',
102+
dflt: 'auto',
103+
role: 'info', // TODO: or style ?
104+
editType: 'plot',
105+
description: [
106+
'Sets the angle of the tick labels with respect to the bar.',
107+
'For example, a `tickangle` of -90 draws the tick labels',
108+
'vertically. With *auto* the texts may automatically be',
109+
'rotated to fit with the maximum size in bars.'
110+
].join(' ')
111+
},
112+
89113
textfont: extendFlat({}, textFontAttrs, {
90114
description: 'Sets the font used for `text`.'
91115
}),

0 commit comments

Comments
 (0)