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'; + } +};