diff --git a/draftlogs/6381_add.md b/draftlogs/6381_add.md new file mode 100644 index 00000000000..562a5f008f5 --- /dev/null +++ b/draftlogs/6381_add.md @@ -0,0 +1,2 @@ + - Introduce group attributes for `scatter` trace i.e. `alignmentgroup`, `offsetgroup`, `scattermode` and `scattergap` [[#6381](https://github.com/plotly/plotly.js/pull/6381)], + this feature was anonymously sponsored: thank you to our sponsor! diff --git a/src/plots/plots.js b/src/plots/plots.js index 29b375d62fa..3790ef8e9bc 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -14,6 +14,7 @@ var BADNUM = require('../constants/numerical').BADNUM; var axisIDs = require('./cartesian/axis_ids'); var clearOutline = require('../components/shapes/handle_outline').clearOutline; +var scatterAttrs = require('../traces/scatter/layout_attributes'); var animationAttrs = require('./animation_attributes'); var frameAttrs = require('./frame_attributes'); @@ -1566,6 +1567,8 @@ plots.supplyLayoutGlobalDefaults = function(layoutIn, layoutOut, formatObj) { 'fx', 'supplyLayoutGlobalDefaults' )(layoutIn, layoutOut, coerce); + + Lib.coerce(layoutIn, layoutOut, scatterAttrs, 'scattermode'); }; function getComputedSize(attr) { diff --git a/src/traces/bar/attributes.js b/src/traces/bar/attributes.js index de48a83c666..12d909f4c95 100644 --- a/src/traces/bar/attributes.js +++ b/src/traces/bar/attributes.js @@ -196,27 +196,8 @@ module.exports = { marker: marker, - offsetgroup: { - valType: 'string', - dflt: '', - editType: 'calc', - description: [ - 'Set several traces linked to the same position axis', - 'or matching axes to the same', - 'offsetgroup where bars of the same position coordinate will line up.' - ].join(' ') - }, - alignmentgroup: { - valType: 'string', - dflt: '', - editType: 'calc', - description: [ - 'Set several traces linked to the same position axis', - 'or matching axes to the same', - 'alignmentgroup. This controls whether bars compute their positional', - 'range dependently or independently.' - ].join(' ') - }, + offsetgroup: scatterAttrs.offsetgroup, + alignmentgroup: scatterAttrs.alignmentgroup, selected: { marker: { diff --git a/src/traces/bar/cross_trace_calc.js b/src/traces/bar/cross_trace_calc.js index f1cad80924d..a63b4cc248b 100644 --- a/src/traces/bar/cross_trace_calc.js +++ b/src/traces/bar/cross_trace_calc.js @@ -441,7 +441,14 @@ function setBarCenterAndWidth(pa, sieve) { // store the actual bar width and position, for use by hover var width = calcBar.w = barwidthIsArray ? barwidth[j] : barwidth; - calcBar[pLetter] = calcBar.p + (poffsetIsArray ? poffset[j] : poffset) + width / 2; + + if(calcBar.p === undefined) { + calcBar.p = calcBar[pLetter]; + calcBar['orig_' + pLetter] = calcBar[pLetter]; + } + + var delta = (poffsetIsArray ? poffset[j] : poffset) + width / 2; + calcBar[pLetter] = calcBar.p + delta; } } } @@ -498,13 +505,17 @@ function setBaseAndTop(sa, sieve) { for(var i = 0; i < calcTraces.length; i++) { var calcTrace = calcTraces[i]; var fullTrace = calcTrace[0].trace; + var isScatter = fullTrace.type === 'scatter'; + var isVertical = fullTrace.orientation === 'v'; var pts = []; var tozero = false; for(var j = 0; j < calcTrace.length; j++) { var bar = calcTrace[j]; - var base = bar.b; - var top = base + bar.s; + var base = isScatter ? 0 : bar.b; + var top = isScatter ? ( + isVertical ? bar.y : bar.x + ) : base + bar.s; bar[sLetter] = top; pts.push(top); diff --git a/src/traces/bar/defaults.js b/src/traces/bar/defaults.js index 419cb45c095..3ed0e57ddea 100644 --- a/src/traces/bar/defaults.js +++ b/src/traces/bar/defaults.js @@ -7,7 +7,7 @@ var Registry = require('../../registry'); var handleXYDefaults = require('../scatter/xy_defaults'); var handlePeriodDefaults = require('../scatter/period_defaults'); var handleStyleDefaults = require('./style_defaults'); -var handleGroupingDefaults = require('./grouping_defaults'); +var handleGroupingDefaults = require('../scatter/grouping_defaults'); var attributes = require('./attributes'); var coerceFont = Lib.coerceFont; diff --git a/src/traces/bar/sieve.js b/src/traces/bar/sieve.js index ad7eec482f8..54dc5a74b25 100644 --- a/src/traces/bar/sieve.js +++ b/src/traces/bar/sieve.js @@ -3,7 +3,6 @@ module.exports = Sieve; var distinctVals = require('../../lib').distinctVals; -var BADNUM = require('../../constants/numerical').BADNUM; /** * Helper class to sieve data from traces into bins @@ -27,12 +26,18 @@ function Sieve(traces, opts) { // for single-bin histograms - see histogram/calc var width1 = Infinity; + var axLetter = opts.posAxis._id.charAt(0); + var positions = []; for(var i = 0; i < traces.length; i++) { var trace = traces[i]; for(var j = 0; j < trace.length; j++) { var bar = trace[j]; - if(bar.p !== BADNUM) positions.push(bar.p); + var pos = bar.p; + if(pos === undefined) { + pos = bar[axLetter]; + } + if(pos !== undefined) positions.push(pos); } if(trace[0] && trace[0].width1) { width1 = Math.min(trace[0].width1, width1); diff --git a/src/traces/box/defaults.js b/src/traces/box/defaults.js index 5ecb56e0e02..f2f087b714a 100644 --- a/src/traces/box/defaults.js +++ b/src/traces/box/defaults.js @@ -4,7 +4,7 @@ var Lib = require('../../lib'); var Registry = require('../../registry'); var Color = require('../../components/color'); var handlePeriodDefaults = require('../scatter/period_defaults'); -var handleGroupingDefaults = require('../bar/grouping_defaults'); +var handleGroupingDefaults = require('../scatter/grouping_defaults'); var autoType = require('../../plots/cartesian/axis_autotype'); var attributes = require('./attributes'); diff --git a/src/traces/funnel/defaults.js b/src/traces/funnel/defaults.js index eaba2fb103b..f729bc21639 100644 --- a/src/traces/funnel/defaults.js +++ b/src/traces/funnel/defaults.js @@ -2,7 +2,7 @@ var Lib = require('../../lib'); -var handleGroupingDefaults = require('../bar/grouping_defaults'); +var handleGroupingDefaults = require('../scatter/grouping_defaults'); var handleText = require('../bar/defaults').handleText; var handleXYDefaults = require('../scatter/xy_defaults'); var handlePeriodDefaults = require('../scatter/period_defaults'); diff --git a/src/traces/histogram/cross_trace_defaults.js b/src/traces/histogram/cross_trace_defaults.js index 15eeb3c8eb7..5e09a9e34c6 100644 --- a/src/traces/histogram/cross_trace_defaults.js +++ b/src/traces/histogram/cross_trace_defaults.js @@ -4,7 +4,7 @@ var Lib = require('../../lib'); var axisIds = require('../../plots/cartesian/axis_ids'); var traceIs = require('../../registry').traceIs; -var handleGroupingDefaults = require('../bar/grouping_defaults'); +var handleGroupingDefaults = require('../scatter/grouping_defaults'); var nestedProperty = Lib.nestedProperty; var getAxisGroup = require('../../plots/cartesian/constraints').getAxisGroup; diff --git a/src/traces/scatter/attributes.js b/src/traces/scatter/attributes.js index cce2de0cd7d..f6beafa16a4 100644 --- a/src/traces/scatter/attributes.js +++ b/src/traces/scatter/attributes.js @@ -123,6 +123,29 @@ module.exports = { xhoverformat: axisHoverFormat('x'), yhoverformat: axisHoverFormat('y'), + offsetgroup: { + valType: 'string', + dflt: '', + editType: 'calc', + description: [ + 'Set several traces linked to the same position axis', + 'or matching axes to the same', + 'offsetgroup where bars of the same position coordinate will line up.' + ].join(' ') + }, + + alignmentgroup: { + valType: 'string', + dflt: '', + editType: 'calc', + description: [ + 'Set several traces linked to the same position axis', + 'or matching axes to the same', + 'alignmentgroup. This controls whether bars compute their positional', + 'range dependently or independently.' + ].join(' ') + }, + stackgroup: { valType: 'string', dflt: '', @@ -146,7 +169,9 @@ module.exports = { values: ['v', 'h'], editType: 'calc', description: [ - 'Only relevant when `stackgroup` is used, and only the first', + 'Only relevant in the following cases:', + '1. when `scattermode` is set to *group*.', + '2. when `stackgroup` is used, and only the first', '`orientation` found in the `stackgroup` will be used - including', 'if `visible` is *legendonly* but not if it is `false`. Sets the', 'stacking direction. With *v* (*h*), the y (x) values of subsequent', diff --git a/src/traces/scatter/cross_trace_calc.js b/src/traces/scatter/cross_trace_calc.js index 69006d8d45a..0456de332c7 100644 --- a/src/traces/scatter/cross_trace_calc.js +++ b/src/traces/scatter/cross_trace_calc.js @@ -1,6 +1,42 @@ 'use strict'; var calc = require('./calc'); +var setGroupPositions = require('../bar/cross_trace_calc').setGroupPositions; + +function groupCrossTraceCalc(gd, plotinfo) { + var xa = plotinfo.xaxis; + var ya = plotinfo.yaxis; + + var fullLayout = gd._fullLayout; + var fullTraces = gd._fullData; + var calcTraces = gd.calcdata; + var calcTracesHorz = []; + var calcTracesVert = []; + + for(var i = 0; i < fullTraces.length; i++) { + var fullTrace = fullTraces[i]; + if( + fullTrace.visible === true && + fullTrace.type === 'scatter' && + fullTrace.xaxis === xa._id && + fullTrace.yaxis === ya._id + ) { + if(fullTrace.orientation === 'h') { + calcTracesHorz.push(calcTraces[i]); + } else if(fullTrace.orientation === 'v') { // check for v since certain scatter traces may not have an orientation + calcTracesVert.push(calcTraces[i]); + } + } + } + + var opts = { + mode: fullLayout.scattermode, + gap: fullLayout.scattergap + }; + + setGroupPositions(gd, xa, ya, calcTracesVert, opts); + setGroupPositions(gd, ya, xa, calcTracesHorz, opts); +} /* * Scatter stacking & normalization calculations @@ -8,6 +44,10 @@ var calc = require('./calc'); */ module.exports = function crossTraceCalc(gd, plotinfo) { + if(gd._fullLayout.scattermode === 'group') { + groupCrossTraceCalc(gd, plotinfo); + } + var xa = plotinfo.xaxis; var ya = plotinfo.yaxis; var subplot = xa._id + ya._id; diff --git a/src/traces/scatter/cross_trace_defaults.js b/src/traces/scatter/cross_trace_defaults.js index 61b4f734b57..6505c6573d7 100644 --- a/src/traces/scatter/cross_trace_defaults.js +++ b/src/traces/scatter/cross_trace_defaults.js @@ -1,9 +1,29 @@ 'use strict'; +var Lib = require('../../lib'); +var handleGroupingDefaults = require('./grouping_defaults'); +var attributes = require('./attributes'); // remove opacity for any trace that has a fill or is filled to -module.exports = function crossTraceDefaults(fullData) { - for(var i = 0; i < fullData.length; i++) { +module.exports = function crossTraceDefaults(fullData, fullLayout) { + var traceIn, traceOut, i; + + function coerce(attr) { + return Lib.coerce(traceOut._input, traceOut, attributes, attr); + } + + if(fullLayout.scattermode === 'group') { + for(i = 0; i < fullData.length; i++) { + traceOut = fullData[i]; + + if(traceOut.type === 'scatter') { + traceIn = traceOut._input; + handleGroupingDefaults(traceIn, traceOut, fullLayout, coerce); + } + } + } + + for(i = 0; i < fullData.length; i++) { var tracei = fullData[i]; if(tracei.type !== 'scatter') continue; diff --git a/src/traces/scatter/defaults.js b/src/traces/scatter/defaults.js index 33ac037c920..9c980ca69de 100644 --- a/src/traces/scatter/defaults.js +++ b/src/traces/scatter/defaults.js @@ -31,6 +31,12 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('yhoverformat'); var stackGroupOpts = handleStackDefaults(traceIn, traceOut, layout, coerce); + if( + layout.scattermode === 'group' && + traceOut.orientation === undefined + ) { + coerce('orientation', 'v'); + } var defaultMode = !stackGroupOpts && (len < constants.PTS_LINESONLY) ? 'lines+markers' : 'lines'; diff --git a/src/traces/scatter/format_labels.js b/src/traces/scatter/format_labels.js index 5908a425466..079afd0956f 100644 --- a/src/traces/scatter/format_labels.js +++ b/src/traces/scatter/format_labels.js @@ -9,8 +9,14 @@ module.exports = function formatLabels(cdi, trace, fullLayout) { var xa = Axes.getFromTrace(mockGd, trace, 'x'); var ya = Axes.getFromTrace(mockGd, trace, 'y'); - labels.xLabel = Axes.tickText(xa, xa.c2l(cdi.x), true).text; - labels.yLabel = Axes.tickText(ya, ya.c2l(cdi.y), true).text; + var x = cdi.orig_x; + if(x === undefined) x = cdi.x; + + var y = cdi.orig_y; + if(y === undefined) y = cdi.y; + + labels.xLabel = Axes.tickText(xa, xa.c2l(x), true).text; + labels.yLabel = Axes.tickText(ya, ya.c2l(y), true).text; return labels; }; diff --git a/src/traces/bar/grouping_defaults.js b/src/traces/scatter/grouping_defaults.js similarity index 100% rename from src/traces/bar/grouping_defaults.js rename to src/traces/scatter/grouping_defaults.js diff --git a/src/traces/scatter/index.js b/src/traces/scatter/index.js index ca331864668..2de0e3ed8cd 100644 --- a/src/traces/scatter/index.js +++ b/src/traces/scatter/index.js @@ -9,8 +9,10 @@ module.exports = { isBubble: subtypes.isBubble, attributes: require('./attributes'), + layoutAttributes: require('./layout_attributes'), supplyDefaults: require('./defaults'), crossTraceDefaults: require('./cross_trace_defaults'), + supplyLayoutDefaults: require('./layout_defaults'), calc: require('./calc').calc, crossTraceCalc: require('./cross_trace_calc'), arraysToCalcdata: require('./arrays_to_calcdata'), diff --git a/src/traces/scatter/layout_attributes.js b/src/traces/scatter/layout_attributes.js new file mode 100644 index 00000000000..86c2d474f47 --- /dev/null +++ b/src/traces/scatter/layout_attributes.js @@ -0,0 +1,30 @@ +'use strict'; + + +module.exports = { + scattermode: { + valType: 'enumerated', + values: ['group', 'overlay'], + dflt: 'overlay', + editType: 'calc', + description: [ + 'Determines how scatter points at the same location coordinate', + 'are displayed on the graph.', + 'With *group*, the scatter points are plotted next to one another', + 'centered around the shared location.', + 'With *overlay*, the scatter points are plotted over one another,', + 'you might need to reduce *opacity* to see multiple scatter points.' + ].join(' ') + }, + scattergap: { + valType: 'number', + min: 0, + max: 1, + editType: 'calc', + description: [ + 'Sets the gap (in plot fraction) between scatter points of', + 'adjacent location coordinates.', + 'Defaults to `bargap`.' + ].join(' ') + } +}; diff --git a/src/traces/scatter/layout_defaults.js b/src/traces/scatter/layout_defaults.js new file mode 100644 index 00000000000..37deee850db --- /dev/null +++ b/src/traces/scatter/layout_defaults.js @@ -0,0 +1,17 @@ +'use strict'; + +var Lib = require('../../lib'); + +var layoutAttributes = require('./layout_attributes'); + +module.exports = function(layoutIn, layoutOut) { + function coerce(attr, dflt) { + return Lib.coerce(layoutIn, layoutOut, layoutAttributes, attr, dflt); + } + + var groupBarmode = layoutOut.barmode === 'group'; + + if(layoutOut.scattermode === 'group') { + coerce('scattergap', groupBarmode ? layoutOut.bargap : 0.2); + } +}; diff --git a/src/traces/waterfall/defaults.js b/src/traces/waterfall/defaults.js index 299c894e39d..e5b07290ab6 100644 --- a/src/traces/waterfall/defaults.js +++ b/src/traces/waterfall/defaults.js @@ -2,7 +2,7 @@ var Lib = require('../../lib'); -var handleGroupingDefaults = require('../bar/grouping_defaults'); +var handleGroupingDefaults = require('../scatter/grouping_defaults'); var handleText = require('../bar/defaults').handleText; var handleXYDefaults = require('../scatter/xy_defaults'); var handlePeriodDefaults = require('../scatter/period_defaults'); diff --git a/test/image/baselines/zz-grouped_scatter-linear.png b/test/image/baselines/zz-grouped_scatter-linear.png new file mode 100644 index 00000000000..55e0666acaa Binary files /dev/null and b/test/image/baselines/zz-grouped_scatter-linear.png differ diff --git a/test/image/baselines/zz-grouped_scatter.png b/test/image/baselines/zz-grouped_scatter.png new file mode 100644 index 00000000000..7d19f1a20d9 Binary files /dev/null and b/test/image/baselines/zz-grouped_scatter.png differ diff --git a/test/image/baselines/zz-scatter-grouping-vs-defaults.png b/test/image/baselines/zz-scatter-grouping-vs-defaults.png new file mode 100644 index 00000000000..42e135f6824 Binary files /dev/null and b/test/image/baselines/zz-scatter-grouping-vs-defaults.png differ diff --git a/test/image/mocks/zz-grouped_scatter-linear.json b/test/image/mocks/zz-grouped_scatter-linear.json new file mode 100644 index 00000000000..e0d0ce0da1b --- /dev/null +++ b/test/image/mocks/zz-grouped_scatter-linear.json @@ -0,0 +1,31 @@ +{ + "data": [ + { + "mode": "markers+lines+text", + "texttemplate": "x:%{x}", + "textposition": "bottom center", + "textfont": { "size": 16 }, + "y": [ + 1, + 3, + 2 + ] + }, + { + "mode": "markers+lines+text", + "texttemplate": "x:%{x}", + "textposition": "bottom center", + "textfont": { "size": 16 }, + "y": [ + 2, + 1, + 3 + ] + } + ], + "layout": { + "width": 600, + "height": 400, + "scattermode": "group" + } +} diff --git a/test/image/mocks/zz-grouped_scatter.json b/test/image/mocks/zz-grouped_scatter.json new file mode 100644 index 00000000000..2ad6f2efd01 --- /dev/null +++ b/test/image/mocks/zz-grouped_scatter.json @@ -0,0 +1,35 @@ +{ + "data": [ + { + "x": [ + "giraffes", + "orangutans", + "monkeys" + ], + "y": [ + 20, + 14, + 23 + ], + "name": "SF Zoo" + }, + { + "x": [ + "giraffes", + "orangutans", + "monkeys" + ], + "y": [ + 12, + 18, + 29 + ], + "name": "LA Zoo" + } + ], + "layout": { + "width": 600, + "height": 400, + "scattermode": "group" + } +} diff --git a/test/image/mocks/zz-scatter-grouping-vs-defaults.json b/test/image/mocks/zz-scatter-grouping-vs-defaults.json new file mode 100644 index 00000000000..6512763fdde --- /dev/null +++ b/test/image/mocks/zz-scatter-grouping-vs-defaults.json @@ -0,0 +1,99 @@ +{ + "data": [ + { + "type": "bar", + "y": [ 1, 2, 1 ], + "yaxis": "y2" + }, + { + "type": "bar", + "y": [ 2, 1, 2 ] + }, + { + "type": "bar", + "y": [ 1, 3, 0 ] + }, + { + "type": "bar", + "y": [ 1, 2, 1 ], + "alignmentgroup": "top", + "hovertext": "alignmentgroup: top", + "xaxis": "x2", + "yaxis": "y2" + }, + { + "type": "bar", + "y": [ 2, 1, 2 ], + "hovertext": "alignmentgroup: top
offsetgroup: 1", + "alignmentgroup": "bottom", + "offsetgroup": "1", + "xaxis": "x2" + }, + { + "type": "bar", + "y": [ 1, 3, 0 ], + "hovertext": "alignmentgroup: top
offsetgroup: 2", + "alignmentgroup": "bottom", + "offsetgroup": "2", + "xaxis": "x2" + }, + { + "type": "scatter", + "y": [ 1, 2, 1 ], + "yaxis": "y2" + }, + { + "type": "scatter", + "y": [ 2, 1, 2 ] + }, + { + "type": "scatter", + "y": [ 1, 3, 0 ] + }, + { + "type": "scatter", + "y": [ 1, 2, 1 ], + "alignmentgroup": "top", + "hovertext": "alignmentgroup: top", + "xaxis": "x2", + "yaxis": "y2" + }, + { + "type": "scatter", + "y": [ 2, 1, 2 ], + "hovertext": "alignmentgroup: top
offsetgroup: 1", + "alignmentgroup": "bottom", + "offsetgroup": "1", + "xaxis": "x2" + }, + { + "type": "scatter", + "y": [ 1, 3, 0 ], + "hovertext": "alignmentgroup: top
offsetgroup: 2", + "alignmentgroup": "bottom", + "offsetgroup": "2", + "xaxis": "x2" + } + ], + "layout": { + "scattermode": "group", + "showlegend": false, + "grid": { + "rows": 2, + "columns": 2, + "roworder": "bottom to top" + }, + "colorway": [ "blue", "orange", "green" ], + "margin": { "t": 20 }, + "xaxis": { + "title": { + "text": "no alignmentgroup
no offsetgroup" + } + }, + "xaxis2": { + "title": { + "text": "with alignmentgroup
with offsetgroup" + } + } + } +} diff --git a/test/plot-schema.json b/test/plot-schema.json index eed6f90148e..2f6ea1bc6a4 100644 --- a/test/plot-schema.json +++ b/test/plot-schema.json @@ -42942,6 +42942,12 @@ "scatter": { "animatable": true, "attributes": { + "alignmentgroup": { + "description": "Set several traces linked to the same position axis or matching axes to the same alignmentgroup. This controls whether bars compute their positional range dependently or independently.", + "dflt": "", + "editType": "calc", + "valType": "string" + }, "cliponaxis": { "description": "Determines whether or not markers and text nodes are clipped about the subplot axes. To show markers and text nodes above axis lines and tick labels, make sure to set `xaxis.layer` and `yaxis.layer` to *below traces*.", "dflt": true, @@ -44900,6 +44906,12 @@ "editType": "style", "valType": "string" }, + "offsetgroup": { + "description": "Set several traces linked to the same position axis or matching axes to the same offsetgroup where bars of the same position coordinate will line up.", + "dflt": "", + "editType": "calc", + "valType": "string" + }, "opacity": { "description": "Sets the opacity of the trace.", "dflt": 1, @@ -44909,7 +44921,7 @@ "valType": "number" }, "orientation": { - "description": "Only relevant when `stackgroup` is used, and only the first `orientation` found in the `stackgroup` will be used - including if `visible` is *legendonly* but not if it is `false`. Sets the stacking direction. With *v* (*h*), the y (x) values of subsequent traces are added. Also affects the default value of `fill`.", + "description": "Only relevant in the following cases: 1. when `scattermode` is set to *group*. 2. when `stackgroup` is used, and only the first `orientation` found in the `stackgroup` will be used - including if `visible` is *legendonly* but not if it is `false`. Sets the stacking direction. With *v* (*h*), the y (x) values of subsequent traces are added. Also affects the default value of `fill`.", "editType": "calc", "valType": "enumerated", "values": [ @@ -45314,6 +45326,25 @@ "scatter-like", "zoomScale" ], + "layoutAttributes": { + "scattergap": { + "description": "Sets the gap (in plot fraction) between scatter points of adjacent location coordinates. Defaults to `bargap`.", + "editType": "calc", + "max": 1, + "min": 0, + "valType": "number" + }, + "scattermode": { + "description": "Determines how scatter points at the same location coordinate are displayed on the graph. With *group*, the scatter points are plotted next to one another centered around the shared location. With *overlay*, the scatter points are plotted over one another, you might need to reduce *opacity* to see multiple scatter points.", + "dflt": "overlay", + "editType": "calc", + "valType": "enumerated", + "values": [ + "group", + "overlay" + ] + } + }, "meta": { "description": "The scatter trace type encompasses line charts, scatter charts, text charts, and bubble charts. The data visualized as scatter point or lines is set in `x` and `y`. Text (appearing either on the chart or on hover only) is via `text`. Bubble charts are achieved by setting `marker.size` and/or `marker.color` to numerical arrays." },