diff --git a/dev/mocks.json b/dev/mocks.json
index c13a3f0d0..e7436f252 100644
--- a/dev/mocks.json
+++ b/dev/mocks.json
@@ -3,6 +3,8 @@
"/percy/panelTest.json",
"/percy/bar.json",
"/percy/box.json",
+ "/percy/funnel.json",
+ "/percy/funnelarea.json",
"/percy/histogram.json",
"/percy/histogram2d.json",
"/percy/pie.json",
diff --git a/dev/percy/funnel.json b/dev/percy/funnel.json
new file mode 100644
index 000000000..8d716a1f1
--- /dev/null
+++ b/dev/percy/funnel.json
@@ -0,0 +1,9 @@
+{
+ "data": [
+ {
+ "type": "funnel",
+ "y": ["Lead", "Pipeline", "Proposal", "Negotiation", "Closed (Won)"],
+ "x": [ 610, 432, 231, 103, 54 ]
+ }
+ ]
+}
diff --git a/dev/percy/funnelarea.json b/dev/percy/funnelarea.json
new file mode 100644
index 000000000..b5f5d23d1
--- /dev/null
+++ b/dev/percy/funnelarea.json
@@ -0,0 +1,9 @@
+{
+ "data": [
+ {
+ "type": "funnelarea",
+ "labels": ["Lead", "Pipeline", "Proposal", "Negotiation", "Closed (Won)"],
+ "values": [ 610, 432, 231, 103, 54 ]
+ }
+ ]
+}
diff --git a/dev/percy/index.js b/dev/percy/index.js
index 7598bd307..0c070d867 100644
--- a/dev/percy/index.js
+++ b/dev/percy/index.js
@@ -8,4 +8,6 @@ export {default as box} from './box.json';
export {default as waterfall} from './waterfall.json';
export {default as sunburst} from './sunburst.json';
export {default as sankey} from './sankey.json';
+export {default as funnel} from './funnel.json';
+export {default as funnelarea} from './funnelarea.json';
export {default as geoTest} from './geoTest.json';
diff --git a/src/EditorControls.js b/src/EditorControls.js
index 204ca4a9e..3b08f5078 100644
--- a/src/EditorControls.js
+++ b/src/EditorControls.js
@@ -11,6 +11,7 @@ import {
shamefullyAdjustSplitStyleTargetContainers,
shamefullyDeleteRelatedAnalysisTransforms,
shamefullyAdjustSizeref,
+ shamefullyAdjustAxisDirection,
} from './shame';
import {EDITOR_ACTIONS} from './lib/constants';
import isNumeric from 'fast-isnumeric';
@@ -71,7 +72,7 @@ class EditorControls extends Component {
}
shamefullyAdjustSizeref(graphDiv, payload);
-
+ shamefullyAdjustAxisDirection(graphDiv, payload);
shamefullyClearAxisTypes(graphDiv, payload);
shamefullyAdjustAxisRef(graphDiv, payload);
shamefullyAddTableColumns(graphDiv, payload);
diff --git a/src/components/containers/SubplotAccordion.js b/src/components/containers/SubplotAccordion.js
index 54b8982d8..d62772168 100644
--- a/src/components/containers/SubplotAccordion.js
+++ b/src/components/containers/SubplotAccordion.js
@@ -64,7 +64,7 @@ class SubplotAccordion extends Component {
// of right type that have attr 'subplot': 'ternary' in their data.
/**
- Example:
+ Example:
{
"data": [
{
@@ -126,7 +126,9 @@ class SubplotAccordion extends Component {
data.forEach((d, i) => {
if (
(d.type === 'pie' && d.values) ||
- ['pie', 'table', 'sunburst', 'sankey', 'parcoords', 'parcats'].includes(d.type)
+ ['pie', 'table', 'sunburst', 'sankey', 'parcoords', 'parcats', 'funnelarea'].includes(
+ d.type
+ )
) {
counter[d.type]++;
const currentCount = counter[d.type];
diff --git a/src/components/containers/TraceMarkerSection.js b/src/components/containers/TraceMarkerSection.js
index 149f57557..a798255b0 100644
--- a/src/components/containers/TraceMarkerSection.js
+++ b/src/components/containers/TraceMarkerSection.js
@@ -15,12 +15,10 @@ class TraceMarkerSection extends Component {
setLocals(context) {
const _ = this.context.localize;
const traceType = context.fullContainer.type;
- if (['bar', 'histogram'].includes(traceType)) {
+ if (['bar', 'histogram', 'funnel', 'waterfall'].includes(traceType)) {
this.name = _('Bars');
- } else if (traceType === 'pie') {
- this.name = _('Pie Segments');
- } else if (traceType === 'sunburst') {
- this.name = _('Sunburst Segments');
+ } else if (['funnelarea', 'pie', 'sunburst'].includes(traceType)) {
+ this.name = _('Segments');
} else {
this.name = _('Points');
}
diff --git a/src/components/fields/DataSelector.js b/src/components/fields/DataSelector.js
index 5cdefe92b..59a676d5a 100644
--- a/src/components/fields/DataSelector.js
+++ b/src/components/fields/DataSelector.js
@@ -40,6 +40,7 @@ export class UnconnectedDataSelector extends Component {
'scatter',
'scattergl',
'bar',
+ 'funnel',
'heatmap',
'heatmapgl',
'violin',
diff --git a/src/components/fields/TextPosition.js b/src/components/fields/TextPosition.js
index 0542c7888..d0a12efa4 100644
--- a/src/components/fields/TextPosition.js
+++ b/src/components/fields/TextPosition.js
@@ -91,7 +91,7 @@ export default connectToContainer(UnconnectedTextPosition, {
{label: _('Bottom Center'), value: 'bottom center'},
{label: _('Bottom Right'), value: 'bottom right'},
];
- if (['pie', 'bar', 'waterfall'].includes(context.container.type)) {
+ if (['pie', 'bar', 'funnel', 'waterfall'].includes(context.container.type)) {
options = [
{label: _('Inside'), value: 'inside'},
{label: _('Outside'), value: 'outside'},
@@ -99,6 +99,9 @@ export default connectToContainer(UnconnectedTextPosition, {
{label: _('None'), value: 'none'},
];
}
+ if (['funnelarea'].includes(context.container.type)) {
+ options = [{label: _('Inside'), value: 'inside'}, {label: _('None'), value: 'none'}];
+ }
plotProps.options = options;
plotProps.clearable = false;
},
diff --git a/src/components/fields/derived.js b/src/components/fields/derived.js
index 2b03f664f..569eda5fc 100644
--- a/src/components/fields/derived.js
+++ b/src/components/fields/derived.js
@@ -500,12 +500,23 @@ function computeAxesRefOptions(axes, propsAttr) {
export const TextInfo = connectToContainer(UnconnectedFlaglist, {
modifyPlotProps: (props, context, plotProps) => {
const {localize: _, container} = context;
- const options = [
+
+ let options = [
{label: _('Label'), value: 'label'},
{label: _('Value'), value: 'value'},
{label: _('%'), value: 'percent'},
];
+ if (container.type === 'funnel') {
+ options = [
+ {label: _('Label'), value: 'label'},
+ {label: _('Value'), value: 'value'},
+ {label: _('% initial'), value: 'percent initial'},
+ {label: _('% previous'), value: 'percent previous'},
+ {label: _('% total'), value: 'percent total'},
+ ];
+ }
+
if (container.text) {
options.push({label: _('Text'), value: 'text'});
}
@@ -591,11 +602,11 @@ export const HoverInfo = connectToContainer(UnconnectedFlaglist, {
options = [];
}
- if (container.labels && ['pie', 'sunburst'].includes(container.type)) {
+ if (container.labels && ['pie', 'sunburst', 'funnelarea'].includes(container.type)) {
options.push({label: _('Label'), value: 'label'});
}
- if (container.values && ['pie', 'sunburst'].includes(container.type)) {
+ if (container.values && ['pie', 'sunburst', 'funnelarea'].includes(container.type)) {
options.push({label: _('Value'), value: 'value'});
}
diff --git a/src/default_panels/StyleTracesPanel.js b/src/default_panels/StyleTracesPanel.js
index e78cb8db8..756265d9d 100644
--- a/src/default_panels/StyleTracesPanel.js
+++ b/src/default_panels/StyleTracesPanel.js
@@ -91,18 +91,11 @@ const StyleTracesPanel = (props, {localize: _}) => (
-
-
-
-
-
-
-
-
+
(
attr="extendpiecolors"
options={[{label: _('On'), value: true}, {label: _('Off'), value: false}]}
/>
-
-
-
-
+
+
-
+
+
+
+
+
(
@@ -258,6 +264,32 @@ const StyleTracesPanel = (props, {localize: _}) => (
/>
+
+
+
+
+
+
+
+
@@ -363,6 +395,60 @@ const StyleTracesPanel = (props, {localize: _}) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -597,59 +683,6 @@ const StyleTracesPanel = (props, {localize: _}) => (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
(
-
+
+
@@ -762,9 +796,6 @@ const StyleTracesPanel = (props, {localize: _}) => (
-
-
-
[
label: _('Waterfall'),
category: chartCategory(_).FINANCIAL,
},
+ {
+ value: 'funnel',
+ label: _('Funnel'),
+ category: chartCategory(_).FINANCIAL,
+ },
+ {
+ value: 'funnelarea',
+ label: _('Funnel Area'),
+ category: chartCategory(_).FINANCIAL,
+ },
{
value: 'scattergl',
icon: 'scatter',
diff --git a/src/shame.js b/src/shame.js
index 14b6fcecd..e20f1da6d 100644
--- a/src/shame.js
+++ b/src/shame.js
@@ -212,3 +212,9 @@ export const shamefullyAdjustSizeref = (gd, {update}) => {
update['marker.sizemode'] = 'area';
}
};
+
+export const shamefullyAdjustAxisDirection = (gd, {update}) => {
+ if (update.type === 'funnel' && gd.data.length === 1) {
+ gd.layout.yaxis.autorange = 'reversed';
+ }
+};