diff --git a/src/components/drawing/index.js b/src/components/drawing/index.js index eafda1a4331..b94498b7923 100644 --- a/src/components/drawing/index.js +++ b/src/components/drawing/index.js @@ -26,9 +26,9 @@ var DESELECTDIM = require('../../constants/interactions').DESELECTDIM; var subTypes = require('../../traces/scatter/subtypes'); var makeBubbleSizeFn = require('../../traces/scatter/make_bubble_size_func'); +var appendArrayPointValue = require('../../components/fx/helpers').appendArrayPointValue; var drawing = module.exports = {}; -var appendArrayPointValue = require('../fx/helpers').appendArrayPointValue; // ----------------------------------------------------- // styling functions for plot elements @@ -680,32 +680,36 @@ function extracTextFontSize(d, trace) { } // draw text at points -drawing.textPointStyle = function(s, trace, gd, inLegend) { +drawing.textPointStyle = function(s, trace, gd) { if(!s.size()) return; var selectedTextColorFn; - if(trace.selectedpoints) { var fns = drawing.makeSelectedTextStyleFns(trace); selectedTextColorFn = fns.selectedTextColorFn; } - var template = trace.texttemplate; - // If styling text in legends, do not use texttemplate - if(inLegend) template = false; + var texttemplate = trace.texttemplate; + var fullLayout = gd._fullLayout; + s.each(function(d) { var p = d3.select(this); - var text = Lib.extractOption(d, trace, template ? 'txt' : 'tx', template ? 'texttemplate' : 'text'); + + var text = texttemplate ? + Lib.extractOption(d, trace, 'txt', 'texttemplate') : + Lib.extractOption(d, trace, 'tx', 'text'); if(!text && text !== 0) { p.remove(); return; } - if(template) { - var pt = {}; - appendArrayPointValue(pt, trace, d.i); - text = Lib.texttemplateString(text, {}, gd._fullLayout._d3locale, pt, d, trace._meta || {}); + if(texttemplate) { + var labels = trace._module.formatLabels ? trace._module.formatLabels(d, trace, fullLayout) : {}; + var pointValues = {}; + appendArrayPointValue(pointValues, trace, d.i); + var meta = trace._meta || {}; + text = Lib.texttemplateString(text, labels, fullLayout._d3locale, pointValues, d, meta); } var pos = d.tp || trace.textposition; diff --git a/src/components/legend/style.js b/src/components/legend/style.js index 6a8e8c650d3..6cacadb8ed7 100644 --- a/src/components/legend/style.js +++ b/src/components/legend/style.js @@ -255,6 +255,9 @@ module.exports = function style(s, gd) { // always show legend items in base state tMod.selectedpoints = null; + + // never show texttemplate + tMod.texttemplate = null; } var ptgroup = d3.select(this).select('g.legendpoints'); @@ -278,7 +281,7 @@ module.exports = function style(s, gd) { .append('g').classed('pointtext', true) .append('text').attr('transform', 'translate(20,0)'); txt.exit().remove(); - txt.selectAll('text').call(Drawing.textPointStyle, tMod, gd, true); + txt.selectAll('text').call(Drawing.textPointStyle, tMod, gd); } function styleWaterfalls(d) { diff --git a/src/traces/bar/constants.js b/src/traces/bar/constants.js index 608bcc22041..041fc57a8aa 100644 --- a/src/traces/bar/constants.js +++ b/src/traces/bar/constants.js @@ -10,6 +10,11 @@ 'use strict'; module.exports = { - TEXTPAD: 3, // padding in pixels around text - eventDataKeys: [] + // padding in pixels around text + TEXTPAD: 3, + // 'value' and 'label' are not really necessary for bar traces, + // but they were made available to `texttemplate` (maybe by accident) + // via tokens `%{value}` and `%{label}` starting in 1.50.0, + // so let's include them in the event data also. + eventDataKeys: ['value', 'label'] }; diff --git a/src/traces/bar/event_data.js b/src/traces/bar/event_data.js new file mode 100644 index 00000000000..6c7df2ee9aa --- /dev/null +++ b/src/traces/bar/event_data.js @@ -0,0 +1,27 @@ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +module.exports = function eventData(out, pt, trace) { + // standard cartesian event data + out.x = 'xVal' in pt ? pt.xVal : pt.x; + out.y = 'yVal' in pt ? pt.yVal : pt.y; + if(pt.xa) out.xaxis = pt.xa; + if(pt.ya) out.yaxis = pt.ya; + + if(trace.orientation === 'h') { + out.label = out.y; + out.value = out.x; + } else { + out.label = out.x; + out.value = out.y; + } + + return out; +}; diff --git a/src/traces/bar/hover.js b/src/traces/bar/hover.js index 61832ac48e6..76cc8b142ab 100644 --- a/src/traces/bar/hover.js +++ b/src/traces/bar/hover.js @@ -15,6 +15,7 @@ var Color = require('../../components/color'); var fillText = require('../../lib').fillText; var getLineWidth = require('./helpers').getLineWidth; +var hoverLabelText = require('../../plots/cartesian/axes').hoverLabelText; function hoverPoints(pointData, xval, yval, hovermode) { var barPointData = hoverOnBars(pointData, xval, yval, hovermode); @@ -151,6 +152,9 @@ function hoverOnBars(pointData, xval, yval, hovermode) { pointData[posLetter + '1'] = pa.c2p(isClosest ? maxPos(di) : extent[1], true); pointData[posLetter + 'LabelVal'] = di.p; + pointData.labelLabel = hoverLabelText(pa, pointData[posLetter + 'LabelVal']); + pointData.valueLabel = hoverLabelText(sa, pointData[sizeLetter + 'LabelVal']); + // spikelines always want "closest" distance regardless of hovermode pointData.spikeDistance = (sizeFn(di) + thisBarPositionFn(di)) / 2 + maxSpikeDistance - maxHoverDistance; // they also want to point to the data value, regardless of where the label goes diff --git a/src/traces/bar/index.js b/src/traces/bar/index.js index aa1a7a8feec..7bddd744c53 100644 --- a/src/traces/bar/index.js +++ b/src/traces/bar/index.js @@ -22,6 +22,7 @@ module.exports = { style: require('./style').style, styleOnSelect: require('./style').styleOnSelect, hoverPoints: require('./hover').hoverPoints, + eventData: require('./event_data'), selectPoints: require('./select'), moduleType: 'trace', diff --git a/src/traces/bar/plot.js b/src/traces/bar/plot.js index 5863cc0f79c..d67a5abb677 100644 --- a/src/traces/bar/plot.js +++ b/src/traces/bar/plot.js @@ -574,31 +574,42 @@ function calcTexttemplate(fullLayout, calcTrace, index, xa, ya) { var trace = calcTrace[0].trace; var texttemplate = Lib.castOption(trace, index, 'texttemplate'); if(!texttemplate) return ''; - var isHorizontal = (trace.orientation === 'h'); var isWaterfall = (trace.type === 'waterfall'); var isFunnel = (trace.type === 'funnel'); + var pLetter, pAxis; + var vLetter, vAxis; + if(trace.orientation === 'h') { + pLetter = 'y'; + pAxis = ya; + vLetter = 'x'; + vAxis = xa; + } else { + pLetter = 'x'; + pAxis = xa; + vLetter = 'y'; + vAxis = ya; + } + function formatLabel(u) { - var pAxis = isHorizontal ? ya : xa; return tickText(pAxis, u, true).text; } function formatNumber(v) { - var sAxis = isHorizontal ? xa : ya; - return tickText(sAxis, +v, true).text; + return tickText(vAxis, +v, true).text; } var cdi = calcTrace[index]; var obj = {}; obj.label = cdi.p; - obj.labelLabel = formatLabel(cdi.p); + obj.labelLabel = obj[pLetter + 'Label'] = formatLabel(cdi.p); var tx = Lib.castOption(trace, cdi.i, 'text'); if(tx === 0 || tx) obj.text = tx; obj.value = cdi.s; - obj.valueLabel = formatNumber(cdi.s); + obj.valueLabel = obj[vLetter + 'Label'] = formatNumber(cdi.s); var pt = {}; appendArrayPointValue(pt, trace, cdi.i); diff --git a/src/traces/barpolar/index.js b/src/traces/barpolar/index.js index e3f300f5ab9..b67be4a9823 100644 --- a/src/traces/barpolar/index.js +++ b/src/traces/barpolar/index.js @@ -24,6 +24,7 @@ module.exports = { plot: require('./plot'), colorbar: require('../scatter/marker_colorbar'), + formatLabels: require('../scatterpolar/format_labels'), style: require('../bar/style').style, styleOnSelect: require('../bar/style').styleOnSelect, diff --git a/src/traces/scatter/format_labels.js b/src/traces/scatter/format_labels.js new file mode 100644 index 00000000000..ff90b03fad3 --- /dev/null +++ b/src/traces/scatter/format_labels.js @@ -0,0 +1,24 @@ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Axes = require('../../plots/cartesian/axes'); + +module.exports = function formatLabels(cdi, trace, fullLayout) { + var labels = {}; + + var mockGd = {_fullLayout: fullLayout}; + var xa = Axes.getFromTrace(mockGd, trace, 'x'); + var ya = Axes.getFromTrace(mockGd, trace, 'y'); + + labels.xLabel = Axes.tickText(xa, cdi.x, true).text; + labels.yLabel = Axes.tickText(ya, cdi.y, true).text; + + return labels; +}; diff --git a/src/traces/scatter/index.js b/src/traces/scatter/index.js index b86d16c981f..0a6fc00852a 100644 --- a/src/traces/scatter/index.js +++ b/src/traces/scatter/index.js @@ -24,6 +24,7 @@ module.exports = { arraysToCalcdata: require('./arrays_to_calcdata'), plot: require('./plot'), colorbar: require('./marker_colorbar'), + formatLabels: require('./format_labels'), style: require('./style').style, styleOnSelect: require('./style').styleOnSelect, hoverPoints: require('./hover'), diff --git a/src/traces/scatter3d/convert.js b/src/traces/scatter3d/convert.js index 79d48bed66f..d1abf0e6fdb 100644 --- a/src/traces/scatter3d/convert.js +++ b/src/traces/scatter3d/convert.js @@ -22,6 +22,7 @@ var makeBubbleSizeFn = require('../scatter/make_bubble_size_func'); var DASH_PATTERNS = require('../../constants/gl3d_dashes'); var MARKER_SYMBOLS = require('../../constants/gl3d_markers'); +var Axes = require('../../plots/cartesian/axes'); var appendArrayPointValue = require('../../components/fx/helpers').appendArrayPointValue; var calculateError = require('./calc_errors'); @@ -241,19 +242,35 @@ function convertPlotlyOptions(scene, data) { for(i = 0; i < len; i++) text[i] = data.text; } + function formatter(axName, val) { + var ax = sceneLayout[axName]; + return Axes.tickText(ax, ax.d2l(val), true).text; + } + // check texttemplate var texttemplate = data.texttemplate; if(texttemplate) { + var fullLayout = scene.fullLayout; + var d3locale = fullLayout._d3locale; var isArray = Array.isArray(texttemplate); var N = isArray ? Math.min(texttemplate.length, len) : len; - var txt = isArray ? function(i) {return texttemplate[i];} : function() { return texttemplate;}; - var d3locale = scene.fullLayout._d3locale; + var txt = isArray ? + function(i) { return texttemplate[i]; } : + function() { return texttemplate; }; + text = new Array(N); + for(i = 0; i < N; i++) { - var pt = {}; - pt.text = text[i]; - appendArrayPointValue(pt, data, i); - text[i] = Lib.texttemplateString(txt(i), pt, d3locale, pt, data._meta || {}); + var d = {x: x[i], y: y[i], z: z[i]}; + var labels = { + xLabel: formatter('xaxis', x[i]), + yLabel: formatter('yaxis', y[i]), + zLabel: formatter('zaxis', z[i]) + }; + var pointValues = {}; + appendArrayPointValue(pointValues, data, i); + var meta = data._meta || {}; + text[i] = Lib.texttemplateString(txt(i), labels, d3locale, pointValues, d, meta); } } diff --git a/src/traces/scattercarpet/format_labels.js b/src/traces/scattercarpet/format_labels.js new file mode 100644 index 00000000000..59357c0b85a --- /dev/null +++ b/src/traces/scattercarpet/format_labels.js @@ -0,0 +1,25 @@ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +module.exports = function formatLabels(cdi, trace) { + var labels = {}; + + var carpet = trace._carpet; + var ij = carpet.ab2ij([cdi.a, cdi.b]); + var i0 = Math.floor(ij[0]); + var ti = ij[0] - i0; + var j0 = Math.floor(ij[1]); + var tj = ij[1] - j0; + var xy = carpet.evalxy([], i0, j0, ti, tj); + + labels.yLabel = xy[1].toFixed(3); + + return labels; +}; diff --git a/src/traces/scattercarpet/hover.js b/src/traces/scattercarpet/hover.js index 74de3b8c581..f96432455e5 100644 --- a/src/traces/scattercarpet/hover.js +++ b/src/traces/scattercarpet/hover.js @@ -49,13 +49,8 @@ module.exports = function hoverPoints(pointData, xval, yval, hovermode) { var trace = newPointData.trace; var carpet = trace._carpet; - var ij = carpet.ab2ij([cdi.a, cdi.b]); - var i0 = Math.floor(ij[0]); - var ti = ij[0] - i0; - var j0 = Math.floor(ij[1]); - var tj = ij[1] - j0; - var xy = carpet.evalxy([], i0, j0, ti, tj); - newPointData.yLabel = xy[1].toFixed(3); + var labels = trace._module.formatLabels(cdi, trace); + newPointData.yLabel = labels.yLabel; delete newPointData.text; var text = []; diff --git a/src/traces/scattercarpet/index.js b/src/traces/scattercarpet/index.js index b18dc5b603b..cdfc7cc3f5b 100644 --- a/src/traces/scattercarpet/index.js +++ b/src/traces/scattercarpet/index.js @@ -12,6 +12,7 @@ module.exports = { attributes: require('./attributes'), supplyDefaults: require('./defaults'), colorbar: require('../scatter/marker_colorbar'), + formatLabels: require('./format_labels'), calc: require('./calc'), plot: require('./plot'), style: require('../scatter/style').style, diff --git a/src/traces/scattergeo/format_labels.js b/src/traces/scattergeo/format_labels.js new file mode 100644 index 00000000000..cccdab5c46a --- /dev/null +++ b/src/traces/scattergeo/format_labels.js @@ -0,0 +1,23 @@ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Axes = require('../../plots/cartesian/axes'); + +module.exports = function formatLabels(cdi, trace, fullLayout) { + var labels = {}; + + var geo = fullLayout[trace.geo]._subplot; + var ax = geo.mockAxis; + var lonlat = cdi.lonlat; + labels.lonLabel = Axes.tickText(ax, ax.c2l(lonlat[0]), true).text; + labels.latLabel = Axes.tickText(ax, ax.c2l(lonlat[1]), true).text; + + return labels; +}; diff --git a/src/traces/scattergeo/hover.js b/src/traces/scattergeo/hover.js index e925d4734f6..21ce51935f9 100644 --- a/src/traces/scattergeo/hover.js +++ b/src/traces/scattergeo/hover.js @@ -9,7 +9,6 @@ 'use strict'; var Fx = require('../../components/fx'); -var Axes = require('../../plots/cartesian/axes'); var BADNUM = require('../../constants/numerical').BADNUM; var getTraceColor = require('../scatter/get_trace_color'); @@ -63,9 +62,11 @@ module.exports = function hoverPoints(pointData, xval, yval) { pointData.lon = lonlat[0]; pointData.lat = lonlat[1]; - var ax = geo.mockAxis; - pointData.lonLabel = Axes.tickText(ax, ax.c2l(pointData.lon), 'hover').text; - pointData.latLabel = Axes.tickText(ax, ax.c2l(pointData.lat), 'hover').text; + var fullLayout = {}; + fullLayout[trace.geo] = {_subplot: geo}; + var labels = trace._module.formatLabels(di, trace, fullLayout); + pointData.lonLabel = labels.lonLabel; + pointData.latLabel = labels.latLabel; pointData.color = getTraceColor(trace, di); pointData.extraText = getExtraText(trace, di, pointData, cd[0].t.labels); diff --git a/src/traces/scattergeo/index.js b/src/traces/scattergeo/index.js index bb47c95f94d..27a6fe010e2 100644 --- a/src/traces/scattergeo/index.js +++ b/src/traces/scattergeo/index.js @@ -12,6 +12,7 @@ module.exports = { attributes: require('./attributes'), supplyDefaults: require('./defaults'), colorbar: require('../scatter/marker_colorbar'), + formatLabels: require('./format_labels'), calc: require('./calc'), plot: require('./plot'), style: require('./style'), diff --git a/src/traces/scattergl/calc.js b/src/traces/scattergl/calc.js index 59288ebdfdc..ce4bfa58c35 100644 --- a/src/traces/scattergl/calc.js +++ b/src/traces/scattergl/calc.js @@ -36,8 +36,8 @@ module.exports = function calc(gd, trace) { var stash = {}; var i, xx, yy; - var x = xa.makeCalcdata(trace, 'x'); - var y = ya.makeCalcdata(trace, 'y'); + var x = trace._x = xa.makeCalcdata(trace, 'x'); + var y = trace._y = ya.makeCalcdata(trace, 'y'); // we need hi-precision for scatter2d, // regl-scatter2d uses NaNs for bad/missing values diff --git a/src/traces/scattergl/convert.js b/src/traces/scattergl/convert.js index 92536d4ddce..3f6fa04fb45 100644 --- a/src/traces/scattergl/convert.js +++ b/src/traces/scattergl/convert.js @@ -49,9 +49,9 @@ function convertStyle(gd, trace) { if(trace.visible !== true) return opts; if(subTypes.hasText(trace)) { - opts.text = convertTextStyle(trace, gd); - opts.textSel = convertTextSelection(trace, trace.selected); - opts.textUnsel = convertTextSelection(trace, trace.unselected); + opts.text = convertTextStyle(gd, trace); + opts.textSel = convertTextSelection(gd, trace, trace.selected); + opts.textUnsel = convertTextSelection(gd, trace, trace.unselected); } if(subTypes.hasMarkers(trace)) { @@ -102,7 +102,8 @@ function convertStyle(gd, trace) { return opts; } -function convertTextStyle(trace, gd) { +function convertTextStyle(gd, trace) { + var fullLayout = gd._fullLayout; var count = trace._length; var textfontIn = trace.textfont; var textpositionIn = trace.textposition; @@ -116,14 +117,21 @@ function convertTextStyle(trace, gd) { var texttemplate = trace.texttemplate; if(texttemplate) { optsOut.text = []; + + var d3locale = fullLayout._d3locale; var isArray = Array.isArray(texttemplate); var N = isArray ? Math.min(texttemplate.length, count) : count; - var txt = isArray ? function(i) {return texttemplate[i];} : function() { return texttemplate;}; - var d3locale = gd._fullLayout._d3locale; + var txt = isArray ? + function(i) { return texttemplate[i]; } : + function() { return texttemplate; }; + for(i = 0; i < N; i++) { - var pt = {}; - appendArrayPointValue(pt, trace, i); - optsOut.text.push(Lib.texttemplateString(txt(i), pt, d3locale, pt, trace._meta || {})); + var d = {i: i}; + var labels = trace._module.formatLabels(d, trace, fullLayout); + var pointValues = {}; + appendArrayPointValue(pointValues, trace, i); + var meta = trace._meta || {}; + optsOut.text.push(Lib.texttemplateString(txt(i), labels, d3locale, pointValues, d, meta)); } } else { if(Array.isArray(trace.text) && trace.text.length < count) { @@ -339,7 +347,7 @@ function convertMarkerSelection(trace, target) { return optsOut; } -function convertTextSelection(trace, target) { +function convertTextSelection(gd, trace, target) { var optsOut = {}; if(!target) return optsOut; @@ -348,13 +356,14 @@ function convertTextSelection(trace, target) { var optsIn = { opacity: 1, text: trace.text, + texttemplate: trace.texttemplate, textposition: trace.textposition, textfont: Lib.extendFlat({}, trace.textfont) }; if(target.textfont) { Lib.extendFlat(optsIn.textfont, target.textfont); } - optsOut = convertTextStyle(optsIn); + optsOut = convertTextStyle(gd, optsIn); } return optsOut; diff --git a/src/traces/scattergl/format_labels.js b/src/traces/scattergl/format_labels.js new file mode 100644 index 00000000000..e0c7183b0cd --- /dev/null +++ b/src/traces/scattergl/format_labels.js @@ -0,0 +1,18 @@ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var scatterFormatLabels = require('../scatter/format_labels'); + +module.exports = function formatLabels(cdi, trace, fullLayout) { + var i = cdi.i; + if(!('x' in cdi)) cdi.x = trace._x[i]; + if(!('y' in cdi)) cdi.y = trace._y[i]; + return scatterFormatLabels(cdi, trace, fullLayout); +}; diff --git a/src/traces/scattergl/index.js b/src/traces/scattergl/index.js index b47ad9513fb..468af931697 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -20,6 +20,7 @@ module.exports = { supplyDefaults: require('./defaults'), crossTraceDefaults: require('../scatter/cross_trace_defaults'), colorbar: require('../scatter/marker_colorbar'), + formatLabels: require('./format_labels'), calc: require('./calc'), plot: require('./plot'), hoverPoints: hover.hoverPoints, diff --git a/src/traces/scattermapbox/convert.js b/src/traces/scattermapbox/convert.js index 564ac69a71c..f4fac7d64bb 100644 --- a/src/traces/scattermapbox/convert.js +++ b/src/traces/scattermapbox/convert.js @@ -19,6 +19,7 @@ var Drawing = require('../../components/drawing'); var makeBubbleSizeFn = require('../scatter/make_bubble_size_func'); var subTypes = require('../scatter/subtypes'); var convertTextOpts = require('../../plots/mapbox/convert_text_opts'); +var appendArrayPointValue = require('../../components/fx/helpers').appendArrayPointValue; var NEWLINES = require('../../lib/svg_text_utils').NEWLINES; var BR_TAG_ALL = require('../../lib/svg_text_utils').BR_TAG_ALL; @@ -233,6 +234,7 @@ function makeCircleOpts(calcTrace) { } function makeSymbolGeoJSON(calcTrace, gd) { + var fullLayout = gd._fullLayout; var trace = calcTrace[0].trace; var marker = trace.marker || {}; @@ -253,18 +255,23 @@ function makeSymbolGeoJSON(calcTrace, gd) { if(isBADNUM(calcPt.lonlat)) continue; - var txt = trace.texttemplate; - if(txt) { - var txti = Array.isArray(txt) ? (txt[i] || '') : txt; - calcPt.text = calcPt.tx; - calcPt.lon = calcPt.lonlat[0]; - calcPt.lat = calcPt.lonlat[1]; - calcPt.customdata = calcPt.data; - calcPt.txt = Lib.texttemplateString(txti, {}, gd._fullLayout._d3locale, calcPt, trace._meta || {}); + var texttemplate = trace.texttemplate; + var text; + + if(texttemplate) { + var tt = Array.isArray(texttemplate) ? (texttemplate[i] || '') : texttemplate; + var labels = trace._module.formatLabels(calcPt, trace, fullLayout); + var pointValues = {}; + appendArrayPointValue(pointValues, trace, calcPt.i); + var meta = trace._meta || {}; + text = Lib.texttemplateString(tt, labels, fullLayout._d3locale, pointValues, calcPt, meta); + } else { + text = fillText(calcPt.tx); } - var text = txt ? calcPt.txt : fillText(calcPt.tx); - if(text) text = text.replace(NEWLINES, '').replace(BR_TAG_ALL, '\n'); + if(text) { + text = text.replace(NEWLINES, '').replace(BR_TAG_ALL, '\n'); + } features.push({ type: 'Feature', diff --git a/src/traces/scattermapbox/format_labels.js b/src/traces/scattermapbox/format_labels.js new file mode 100644 index 00000000000..56dbb56f455 --- /dev/null +++ b/src/traces/scattermapbox/format_labels.js @@ -0,0 +1,24 @@ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Axes = require('../../plots/cartesian/axes'); + +module.exports = function formatLabels(cdi, trace, fullLayout) { + var labels = {}; + + var subplot = fullLayout[trace.subplot]._subplot; + var ax = subplot.mockAxis; + + var lonlat = cdi.lonlat; + labels.lonLabel = Axes.tickText(ax, ax.c2l(lonlat[0]), true).text; + labels.latLabel = Axes.tickText(ax, ax.c2l(lonlat[1]), true).text; + + return labels; +}; diff --git a/src/traces/scattermapbox/hover.js b/src/traces/scattermapbox/hover.js index 9dbe52f9775..5819e04e866 100644 --- a/src/traces/scattermapbox/hover.js +++ b/src/traces/scattermapbox/hover.js @@ -64,6 +64,12 @@ module.exports = function hoverPoints(pointData, xval, yval) { pointData.y0 = yc - rad; pointData.y1 = yc + rad; + var fullLayout = {}; + fullLayout[trace.subplot] = {_subplot: subplot}; + var labels = trace._module.formatLabels(di, trace, fullLayout); + pointData.lonLabel = labels.lonLabel; + pointData.latLabel = labels.latLabel; + pointData.color = getTraceColor(trace, di); pointData.extraText = getExtraText(trace, di, cd[0].t.labels); pointData.hovertemplate = trace.hovertemplate; @@ -72,9 +78,7 @@ module.exports = function hoverPoints(pointData, xval, yval) { }; function getExtraText(trace, di, labels) { - if(trace.hovertemplate) { - return; - } + if(trace.hovertemplate) return; var hoverinfo = di.hi || trace.hoverinfo; var parts = hoverinfo.split('+'); diff --git a/src/traces/scattermapbox/index.js b/src/traces/scattermapbox/index.js index ebe19400d3a..c3fc5f0cf9b 100644 --- a/src/traces/scattermapbox/index.js +++ b/src/traces/scattermapbox/index.js @@ -12,6 +12,7 @@ module.exports = { attributes: require('./attributes'), supplyDefaults: require('./defaults'), colorbar: require('../scatter/marker_colorbar'), + formatLabels: require('./format_labels'), calc: require('../scattergeo/calc'), plot: require('./plot'), hoverPoints: require('./hover'), diff --git a/src/traces/scatterpolar/format_labels.js b/src/traces/scatterpolar/format_labels.js new file mode 100644 index 00000000000..f361e233e3a --- /dev/null +++ b/src/traces/scatterpolar/format_labels.js @@ -0,0 +1,40 @@ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Lib = require('../../lib'); +var Axes = require('../../plots/cartesian/axes'); + +module.exports = function formatLabels(cdi, trace, fullLayout) { + var labels = {}; + + var subplot = fullLayout[trace.subplot]._subplot; + var radialAxis; + var angularAxis; + + // for scatterpolargl texttemplate, _subplot is NOT defined, this takes part during the convert step + // TODO we should consider moving the texttemplate formatting logic to the plot step + if(!subplot) { + subplot = fullLayout[trace.subplot]; + radialAxis = subplot.radialaxis; + angularAxis = subplot.angularaxis; + } else { + radialAxis = subplot.radialAxis; + angularAxis = subplot.angularAxis; + } + + var rVal = radialAxis.c2l(cdi.r); + labels.rLabel = Axes.tickText(radialAxis, rVal, true).text; + + // N.B here the ° sign is part of the formatted value for thetaunit:'degrees' + var thetaVal = angularAxis.thetaunit === 'degrees' ? Lib.rad2deg(cdi.theta) : cdi.theta; + labels.thetaLabel = Axes.tickText(angularAxis, thetaVal, true).text; + + return labels; +}; diff --git a/src/traces/scatterpolar/hover.js b/src/traces/scatterpolar/hover.js index fbe073e7f26..52854707549 100644 --- a/src/traces/scatterpolar/hover.js +++ b/src/traces/scatterpolar/hover.js @@ -9,8 +9,6 @@ 'use strict'; var scatterHover = require('../scatter/hover'); -var Axes = require('../../plots/cartesian/axes'); -var Lib = require('../../lib'); function hoverPoints(pointData, xval, yval, hovermode) { var scatterPointData = scatterHover(pointData, xval, yval, hovermode); @@ -42,12 +40,11 @@ function makeHoverPointText(cdi, trace, subplot, pointData) { radialAxis._hovertitle = 'r'; angularAxis._hovertitle = 'θ'; - var rVal = radialAxis.c2l(cdi.r); - pointData.rLabel = Axes.tickText(radialAxis, rVal, 'hover').text; - - // N.B here the ° sign is part of the formatted value for thetaunit:'degrees' - var thetaVal = angularAxis.thetaunit === 'degrees' ? Lib.rad2deg(cdi.theta) : cdi.theta; - pointData.thetaLabel = Axes.tickText(angularAxis, thetaVal, 'hover').text; + var fullLayout = {}; + fullLayout[trace.subplot] = {_subplot: subplot}; + var labels = trace._module.formatLabels(cdi, trace, fullLayout); + pointData.rLabel = labels.rLabel; + pointData.thetaLabel = labels.thetaLabel; var hoverinfo = cdi.hi || trace.hoverinfo; var text = []; diff --git a/src/traces/scatterpolar/index.js b/src/traces/scatterpolar/index.js index 856f76d63b6..e125d865580 100644 --- a/src/traces/scatterpolar/index.js +++ b/src/traces/scatterpolar/index.js @@ -17,6 +17,7 @@ module.exports = { attributes: require('./attributes'), supplyDefaults: require('./defaults').supplyDefaults, colorbar: require('../scatter/marker_colorbar'), + formatLabels: require('./format_labels'), calc: require('./calc'), plot: require('./plot'), style: require('../scatter/style').style, diff --git a/src/traces/scatterpolargl/calc.js b/src/traces/scatterpolargl/calc.js index 27ebb9fd71c..e2d89585f68 100644 --- a/src/traces/scatterpolargl/calc.js +++ b/src/traces/scatterpolargl/calc.js @@ -19,8 +19,8 @@ module.exports = function calc(gd, trace) { var subplotId = trace.subplot; var radialAxis = fullLayout[subplotId].radialaxis; var angularAxis = fullLayout[subplotId].angularaxis; - var rArray = radialAxis.makeCalcdata(trace, 'r'); - var thetaArray = angularAxis.makeCalcdata(trace, 'theta'); + var rArray = trace._r = radialAxis.makeCalcdata(trace, 'r'); + var thetaArray = trace._theta = angularAxis.makeCalcdata(trace, 'theta'); var len = trace._length; var stash = {}; diff --git a/src/traces/scatterpolargl/format_labels.js b/src/traces/scatterpolargl/format_labels.js new file mode 100644 index 00000000000..210db2975b3 --- /dev/null +++ b/src/traces/scatterpolargl/format_labels.js @@ -0,0 +1,18 @@ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var scatterPolarFormatLabels = require('../scatterpolar/format_labels'); + +module.exports = function formatLabels(cdi, trace, fullLayout) { + var i = cdi.i; + if(!('r' in cdi)) cdi.r = trace._r[i]; + if(!('theta' in cdi)) cdi.theta = trace._theta[i]; + return scatterPolarFormatLabels(cdi, trace, fullLayout); +}; diff --git a/src/traces/scatterpolargl/index.js b/src/traces/scatterpolargl/index.js index a9c17087949..ee3dabbf55e 100644 --- a/src/traces/scatterpolargl/index.js +++ b/src/traces/scatterpolargl/index.js @@ -17,6 +17,7 @@ module.exports = { attributes: require('./attributes'), supplyDefaults: require('./defaults'), colorbar: require('../scatter/marker_colorbar'), + formatLabels: require('./format_labels'), calc: require('./calc'), plot: require('./plot'), diff --git a/src/traces/scatterternary/format_labels.js b/src/traces/scatterternary/format_labels.js new file mode 100644 index 00000000000..4d01d0fceb2 --- /dev/null +++ b/src/traces/scatterternary/format_labels.js @@ -0,0 +1,22 @@ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Axes = require('../../plots/cartesian/axes'); + +module.exports = function formatLabels(cdi, trace, fullLayout) { + var labels = {}; + + var subplot = fullLayout[trace.subplot]._subplot; + labels.aLabel = Axes.tickText(subplot.aaxis, cdi.a, true).text; + labels.bLabel = Axes.tickText(subplot.baxis, cdi.b, true).text; + labels.cLabel = Axes.tickText(subplot.caxis, cdi.c, true).text; + + return labels; +}; diff --git a/src/traces/scatterternary/hover.js b/src/traces/scatterternary/hover.js index 49079833597..4777e905d4b 100644 --- a/src/traces/scatterternary/hover.js +++ b/src/traces/scatterternary/hover.js @@ -6,12 +6,9 @@ * LICENSE file in the root directory of this source tree. */ - 'use strict'; var scatterHover = require('../scatter/hover'); -var Axes = require('../../plots/cartesian/axes'); - module.exports = function hoverPoints(pointData, xval, yval, hovermode) { var scatterPointData = scatterHover(pointData, xval, yval, hovermode); @@ -40,6 +37,8 @@ module.exports = function hoverPoints(pointData, xval, yval, hovermode) { } var cdi = newPointData.cd[newPointData.index]; + var trace = newPointData.trace; + var subplot = newPointData.subplot; newPointData.a = cdi.a; newPointData.b = cdi.b; @@ -48,12 +47,13 @@ module.exports = function hoverPoints(pointData, xval, yval, hovermode) { newPointData.xLabelVal = undefined; newPointData.yLabelVal = undefined; - var ternary = newPointData.subplot; - newPointData.aLabel = Axes.tickText(ternary.aaxis, cdi.a, 'hover').text; - newPointData.bLabel = Axes.tickText(ternary.baxis, cdi.b, 'hover').text; - newPointData.cLabel = Axes.tickText(ternary.caxis, cdi.c, 'hover').text; + var fullLayout = {}; + fullLayout[trace.subplot] = {_subplot: subplot}; + var labels = trace._module.formatLabels(cdi, trace, fullLayout); + newPointData.aLabel = labels.aLabel; + newPointData.bLabel = labels.bLabel; + newPointData.cLabel = labels.cLabel; - var trace = newPointData.trace; var hoverinfo = cdi.hi || trace.hoverinfo; var text = []; function textPart(ax, val) { @@ -62,9 +62,9 @@ module.exports = function hoverPoints(pointData, xval, yval, hovermode) { if(!trace.hovertemplate) { var parts = hoverinfo.split('+'); if(parts.indexOf('all') !== -1) parts = ['a', 'b', 'c']; - if(parts.indexOf('a') !== -1) textPart(ternary.aaxis, newPointData.aLabel); - if(parts.indexOf('b') !== -1) textPart(ternary.baxis, newPointData.bLabel); - if(parts.indexOf('c') !== -1) textPart(ternary.caxis, newPointData.cLabel); + if(parts.indexOf('a') !== -1) textPart(subplot.aaxis, newPointData.aLabel); + if(parts.indexOf('b') !== -1) textPart(subplot.baxis, newPointData.bLabel); + if(parts.indexOf('c') !== -1) textPart(subplot.caxis, newPointData.cLabel); } newPointData.extraText = text.join('
'); newPointData.hovertemplate = trace.hovertemplate; diff --git a/src/traces/scatterternary/index.js b/src/traces/scatterternary/index.js index 2e06b157032..2b42c88711b 100644 --- a/src/traces/scatterternary/index.js +++ b/src/traces/scatterternary/index.js @@ -12,6 +12,7 @@ module.exports = { attributes: require('./attributes'), supplyDefaults: require('./defaults'), colorbar: require('../scatter/marker_colorbar'), + formatLabels: require('./format_labels'), calc: require('./calc'), plot: require('./plot'), style: require('../scatter/style').style, diff --git a/test/image/baselines/gl3d_scatter3d-texttemplate.png b/test/image/baselines/gl3d_scatter3d-texttemplate.png index 46897a1e65d..5c28dd3eedb 100644 Binary files a/test/image/baselines/gl3d_scatter3d-texttemplate.png and b/test/image/baselines/gl3d_scatter3d-texttemplate.png differ diff --git a/test/jasmine/assets/check_texttemplate.js b/test/jasmine/assets/check_texttemplate.js index 109100a9130..1fbe202b9a8 100644 --- a/test/jasmine/assets/check_texttemplate.js +++ b/test/jasmine/assets/check_texttemplate.js @@ -1,3 +1,5 @@ +'use strict'; + var Plotly = require('@lib/index'); var Registry = require('@src/registry'); @@ -7,17 +9,25 @@ var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); var supplyAllDefaults = require('../assets/supply_defaults'); -'use strict'; - module.exports = function checkTextTemplate(mock, selector, tests, skipExtra) { - var isGL = Registry.traceIs(mock[0].type, 'gl'); - var isPolar = Registry.traceIs(mock[0].type, 'polar'); - var isScatterLike = Registry.traceIs(mock[0].type, 'scatter-like'); - var isBarLike = Registry.traceIs(mock[0].type, 'bar-like'); + var data; + var layout; + if(Array.isArray(mock)) { + data = mock; + layout = {}; + } else { + data = mock.data; + layout = mock.layout || {}; + } + + var isGL = Registry.traceIs(data[0].type, 'gl'); + var isPolar = Registry.traceIs(data[0].type, 'polar'); + var isScatterLike = Registry.traceIs(data[0].type, 'scatter-like'); + var isBarLike = Registry.traceIs(data[0].type, 'bar-like'); it('should not coerce textinfo when texttemplate is defined', function() { var gd = {}; - gd.data = Lib.extendDeep([], mock); + gd.data = Lib.extendDeep([], data); gd.data[0].textinfo = 'text'; gd.data[0].texttemplate = 'texttemplate'; supplyAllDefaults(gd); @@ -27,7 +37,7 @@ module.exports = function checkTextTemplate(mock, selector, tests, skipExtra) { if(isScatterLike) { it('should not coerce texttemplate when mode has no `text` flag', function() { var gd = {}; - gd.data = Lib.extendDeep([], mock); + gd.data = Lib.extendDeep([], data); gd.data[0].mode = 'markers'; gd.data[0].texttemplate = 'texttemplate'; supplyAllDefaults(gd); @@ -38,7 +48,7 @@ module.exports = function checkTextTemplate(mock, selector, tests, skipExtra) { if(isBarLike) { it('should not coerce texttemplate when textposition is `none`', function() { var gd = {}; - gd.data = Lib.extendDeep([], mock); + gd.data = Lib.extendDeep([], data); gd.data[0].textposition = 'none'; gd.data[0].texttemplate = 'texttemplate'; supplyAllDefaults(gd); @@ -56,19 +66,19 @@ module.exports = function checkTextTemplate(mock, selector, tests, skipExtra) { for(i = 0; i < N; i++) { customdata.push(Lib.randstr({})); } - mock[0].customdata = customdata; + data[0].customdata = customdata; tests.push(['%{customdata}', customdata]); // Generate meta - mock[0].meta = {'colname': 'A'}; + data[0].meta = {'colname': 'A'}; var metaSolution = []; for(i = 0; i < N; i++) { - metaSolution.push(mock[0].meta.colname); + metaSolution.push(data[0].meta.colname); } tests.push(['%{meta.colname}', metaSolution]); // Make sure that empty text shows up as an empty string - var emptyTextMock = Lib.extendDeep([], mock); + var emptyTextMock = Lib.extendDeep([], data); var emptyTextSolution = []; emptyTextMock[0].text = []; for(i = 0; i < N; i++) { @@ -81,9 +91,9 @@ module.exports = function checkTextTemplate(mock, selector, tests, skipExtra) { tests.forEach(function(test) { it('@gl should support texttemplate', function(done) { var gd = createGraphDiv(); - var mockCopy = Lib.extendDeep([], test[2] || mock); - mockCopy[0].texttemplate = test[0]; - Plotly.newPlot(gd, mockCopy) + var dataCopy = Lib.extendDeep([], test[2] || data); + dataCopy[0].texttemplate = test[0]; + Plotly.newPlot(gd, dataCopy, Lib.extendDeep({}, layout)) .then(function() { var glText; if(isPolar) { @@ -113,9 +123,9 @@ module.exports = function checkTextTemplate(mock, selector, tests, skipExtra) { tests.forEach(function(test) { it('should support texttemplate', function(done) { var gd = createGraphDiv(); - var mockCopy = Lib.extendDeep([], test[2] || mock); - mockCopy[0].texttemplate = test[0]; - Plotly.newPlot(gd, mockCopy) + var dataCopy = Lib.extendDeep([], test[2] || data); + dataCopy[0].texttemplate = test[0]; + Plotly.newPlot(gd, dataCopy, Lib.extendDeep({}, layout)) .then(function() { var pts = Plotly.d3.selectAll(selector); expect(pts.size()).toBe(test[1].length); diff --git a/test/jasmine/tests/bar_test.js b/test/jasmine/tests/bar_test.js index bcc79108e9d..3fab8efd6cc 100644 --- a/test/jasmine/tests/bar_test.js +++ b/test/jasmine/tests/bar_test.js @@ -1112,28 +1112,6 @@ describe('A bar plot', function() { }; } - checkTextTemplate([{ - type: 'bar', - y: [1, 5, 3, 2], - text: ['A', 'B', 'C', 'D'], - textposition: 'inside', - hovertemplate: '%{text}' - }], 'text.bartext', [ - ['%{text} - %{value}', ['A - 1', 'B - 5', 'C - 3', 'D - 2']], - [['%{y}', '%{value}', '%{text}'], ['1', '5', 'C']] - ]); - - checkTextTemplate([{ - type: 'bar', - textposition: 'outside', - x: ['2019-01-01', '2019-02-01'], - y: [1, 2], - hovertemplate: '%{x}', - texttemplate: '%{x}' - }], 'text.bartext', [ - ['%{x}', ['2019-01-01', '2019-02-01']] - ]); - it('should show bar texts (inside case)', function(done) { var data = [{ y: [10, 20, 30], @@ -2347,6 +2325,50 @@ describe('bar hover', function() { .catch(failTest) .then(done); }); + + it('should allow both x/y tokens and label/value tokens', function(done) { + gd = createGraphDiv(); + + function _hover(xpx, ypx) { + return function() { + Fx.hover(gd, {xpx: xpx, ypx: ypx}, 'xy'); + Lib.clearThrottle(); + }; + } + + Plotly.plot(gd, { + data: [{ + type: 'bar', + x: ['a', 'b'], + y: ['1000', '1200'], + hovertemplate: ['%{x} is %{y}', '%{label} is %{value}'] + }], + layout: { + xaxis: { tickprefix: '*', ticksuffix: '*' }, + yaxis: { tickprefix: '$', ticksuffix: ' !', tickformat: '.2f'}, + width: 400, + height: 400, + margin: {l: 0, t: 0, r: 0, b: 0}, + hovermode: 'closest' + } + }) + .then(_hover(100, 200)) + .then(function() { + assertHoverLabelContent({ + nums: '*a* is $1000.00 !', + name: 'trace 0' + }); + }) + .then(_hover(300, 200)) + .then(function() { + assertHoverLabelContent({ + nums: '*b* is $1200.00 !', + name: 'trace 0' + }); + }) + .catch(failTest) + .then(done); + }); }); describe('with special width/offset combinations', function() { @@ -2496,6 +2518,46 @@ describe('bar hover', function() { }); }); +describe('Text templates on bar traces:', function() { + checkTextTemplate([{ + type: 'bar', + y: [1, 5, 3, 2], + text: ['A', 'B', 'C', 'D'], + textposition: 'inside', + hovertemplate: '%{text}' + }], 'text.bartext', [ + ['%{text} - %{value}', ['A - 1', 'B - 5', 'C - 3', 'D - 2']], + [['%{y}', '%{value}', '%{text}'], ['1', '5', 'C']] + ]); + + checkTextTemplate([{ + type: 'bar', + textposition: 'outside', + x: ['2019-01-01', '2019-02-01'], + y: [1, 2], + hovertemplate: '%{x}', + texttemplate: '%{x}' + }], 'text.bartext', [ + ['%{x}', ['Jan 1, 2019', 'Feb 1, 2019']] + ]); + + checkTextTemplate({ + data: [{ + type: 'bar', + textposition: 'inside', + x: ['a', 'b'], + y: ['1000', '1200'], + }], + layout: { + xaxis: { tickprefix: '*', ticksuffix: '*' }, + yaxis: { tickprefix: '$', ticksuffix: ' !', tickformat: '.2f'} + }, + }, 'text.bartext', [ + ['%{x} is %{y}', ['*a* is $1000.00 !', '*b* is $1200.00 !']], + ['%{label} is %{value}', ['*a* is $1000.00 !', '*b* is $1200.00 !']] + ]); +}); + describe('event data', function() { var mock = require('@mocks/stacked_bar'); checkEventData(mock, 216, 309, constants.eventDataKeys); diff --git a/test/jasmine/tests/carpet_test.js b/test/jasmine/tests/carpet_test.js index a71ffc3471a..8bf0aab5033 100644 --- a/test/jasmine/tests/carpet_test.js +++ b/test/jasmine/tests/carpet_test.js @@ -730,7 +730,8 @@ describe('scattercarpet texttemplates', function() { 'b': [1, 2, 3], 'y': [[1, 2.2, 3], [1.5, 2.7, 3.5], [1.7, 2.9, 3.7]] }], 'g.textpoint', [ - ['%{text}: %{a:0.1f}, %{b:0.1f}', ['A: 0.1, 1.5', 'B: 0.1, 1.5', 'C: 0.3, 1.5']] + ['%{text}: %{a:0.1f}, %{b:0.1f}', ['A: 0.1, 1.5', 'B: 0.1, 1.5', 'C: 0.3, 1.5']], + ['%{y}', ['1.000', '1.000', '2.200']] ]); }); describe('contourcarpet plotting & editing', function() { diff --git a/test/jasmine/tests/scatter_test.js b/test/jasmine/tests/scatter_test.js index 9af59f51dfa..0448f586f19 100644 --- a/test/jasmine/tests/scatter_test.js +++ b/test/jasmine/tests/scatter_test.js @@ -1239,7 +1239,9 @@ describe('end-to-end scatter tests', function() { .catch(failTest) .then(done); }); +}); +describe('Text templates on scatter traces:', function() { checkTextTemplate([{ type: 'scatter', mode: 'markers+lines+text', @@ -1249,6 +1251,21 @@ describe('end-to-end scatter tests', function() { ['%{y}', ['1', '5', '3', '2']], [['%{y}', '%{x}-%{y}'], ['1', '1-5', '', '']] ]); + + checkTextTemplate({ + data: [{ + type: 'scatter', + mode: 'text', + x: ['a', 'b'], + y: ['1000', '1200'] + }], + layout: { + xaxis: { tickprefix: '*', ticksuffix: '*' }, + yaxis: { tickprefix: '$', ticksuffix: ' !', tickformat: '.2f'} + } + }, '.textpoint', [ + ['%{x} is %{y}', ['*a* is $1000.00 !', '*b* is $1200.00 !']] + ]); }); describe('stacked area', function() { diff --git a/test/jasmine/tests/scattergeo_test.js b/test/jasmine/tests/scattergeo_test.js index 12976f642cd..6fe010fe392 100644 --- a/test/jasmine/tests/scattergeo_test.js +++ b/test/jasmine/tests/scattergeo_test.js @@ -446,7 +446,7 @@ describe('Test scattergeo texttemplate:', function() { 'lat': [45.5, 43.4, 49.13], 'text': ['Montreal', 'Toronto', 'Vancouver'] }], '.scattergeo text', [ - ['%{text}: %{lon}, %{lat}', ['Montreal: -73.57, 45.5', 'Toronto: -79.24, 43.4', 'Vancouver: -123.06, 49.13']] + ['%{text}: %{lon}, %{lat}', ['Montreal: −73.57, 45.5', 'Toronto: −79.24, 43.4', 'Vancouver: −123.06, 49.13']] ]); checkTextTemplate([{ diff --git a/test/jasmine/tests/scattergl_test.js b/test/jasmine/tests/scattergl_test.js index f8b75c05a35..6dc35e2cb36 100644 --- a/test/jasmine/tests/scattergl_test.js +++ b/test/jasmine/tests/scattergl_test.js @@ -39,17 +39,6 @@ describe('end-to-end scattergl tests', function() { }).catch(failTest).then(done); }); - checkTextTemplate([{ - type: 'scattergl', - mode: 'text+lines', - x: [1, 2, 3, 4], - y: [2, 3, 4, 5], - text: ['A', 'B', 'C', 'D'], - }], '@gl', [ - ['%{text}: %{x}, %{y}', ['A: 1, 2', 'B: 2, 3', 'C: 3, 4', 'D: 4, 5']], - [['%{x}', '%{x}', '%{text}', '%{y}'], ['1', '2', 'C', '5']] - ]); - it('@gl should update a plot with text labels', function(done) { Plotly.react(gd, [{ type: 'scattergl', @@ -761,3 +750,45 @@ describe('Test scattergl autorange:', function() { }); }); }); + +describe('Test texttemplate for scattergl', function() { + checkTextTemplate([{ + type: 'scattergl', + mode: 'text+lines', + x: [1, 2, 3, 4], + y: [2, 3, 4, 5], + text: ['A', 'B', 'C', 'D'], + }], '@gl', [ + ['%{text}: %{x}, %{y}', ['A: 1, 2', 'B: 2, 3', 'C: 3, 4', 'D: 4, 5']], + [['%{x}', '%{x}', '%{text}', '%{y}'], ['1', '2', 'C', '5']] + ]); + + checkTextTemplate({ + data: [{ + type: 'scattergl', + mode: 'text', + x: ['a', 'b'], + y: ['1000', '1200'] + }], + layout: { + xaxis: { tickprefix: '*', ticksuffix: '*' }, + yaxis: { tickprefix: '$', ticksuffix: ' !', tickformat: '.2f'} + } + }, '@gl', [ + ['%{x} is %{y}', ['*a* is $1000.00 !', '*b* is $1200.00 !']] + ]); + + checkTextTemplate({ + data: [{ + type: 'scattergl', + mode: 'text', + y: ['1000', '1200'] + }], + layout: { + xaxis: { tickprefix: '*', ticksuffix: '*' }, + yaxis: { tickprefix: '$', ticksuffix: ' !', tickformat: '.2f'} + } + }, '@gl', [ + ['%{x} is %{y}', ['*0* is $1000.00 !', '*1* is $1200.00 !']] + ]); +}); diff --git a/test/jasmine/tests/scattermapbox_test.js b/test/jasmine/tests/scattermapbox_test.js index 0b2de0ef307..35691693625 100644 --- a/test/jasmine/tests/scattermapbox_test.js +++ b/test/jasmine/tests/scattermapbox_test.js @@ -1,6 +1,7 @@ var Plotly = require('@lib'); var Plots = require('@src/plots/plots'); var Lib = require('@src/lib'); +var Axes = require('@src/plots/cartesian/axes'); var ScatterMapbox = require('@src/traces/scattermapbox'); var convert = require('@src/traces/scattermapbox/convert'); @@ -139,7 +140,15 @@ describe('scattermapbox convert', function() { Plots.doCalcdata(gd, fullTrace); var calcTrace = gd.calcdata[0]; - return convert({_fullLayout: {_d3locale: false}}, calcTrace); + + var mockAxis = {type: 'linear'}; + Axes.setConvert(mockAxis, gd._fullLayout); + + gd._fullLayout.mapbox._subplot = { + mockAxis: mockAxis + }; + + return convert(gd, calcTrace); } function assertVisibility(opts, expectations) { @@ -536,9 +545,9 @@ describe('scattermapbox convert', function() { }); expect(actualText).toEqual([ - 'Montreal (-73.57, 45.5): 1.8M', - 'Toronto (-79.24, 43.4): 2.9M', - 'Vancouver (-123.06, 49.13): 680k' + 'Montreal (−73.57, 45.5): 1.8M', + 'Toronto (−79.24, 43.4): 2.9M', + 'Vancouver (−123.06, 49.13): 680k' ]); }); diff --git a/test/jasmine/tests/scatterpolar_test.js b/test/jasmine/tests/scatterpolar_test.js index 494a893db5f..c0802a50e29 100644 --- a/test/jasmine/tests/scatterpolar_test.js +++ b/test/jasmine/tests/scatterpolar_test.js @@ -212,4 +212,21 @@ describe('Test scatterpolar texttemplate:', function() { ['%{text}: (%{r:0.2f}, %{theta:0.1f})', ['A: (1.00, 0.0)', 'B: (0.50, 90.0)', 'C: (1.00, 180.0)']], [['', 'b%{theta:0.2f}', '%{theta:0.2f}'], ['', 'b90.00', '180.00']] ]); + + checkTextTemplate({ + data: [{ + type: 'scatterpolar', + mode: 'text', + theta: ['a', 'b'], + r: ['1000', '1200'] + }], + layout: { + polar: { + radialaxis: { tickprefix: '$', ticksuffix: ' !', tickformat: '.2f'}, + angularaxis: { tickprefix: '*', ticksuffix: '*' } + } + } + }, '.textpoint', [ + ['%{theta} is %{r}', ['*a* is $1000.00 !', '*b* is $1200.00 !']] + ]); }); diff --git a/test/jasmine/tests/scatterpolargl_test.js b/test/jasmine/tests/scatterpolargl_test.js index 6afd16c50dd..10d047acea3 100644 --- a/test/jasmine/tests/scatterpolargl_test.js +++ b/test/jasmine/tests/scatterpolargl_test.js @@ -391,4 +391,21 @@ describe('Test scatterpolargl texttemplate:', function() { ['%{text}: (%{r:0.2f}, %{theta:0.1f})', ['A: (1.00, 0.0)', 'B: (0.50, 90.0)', 'C: (1.00, 180.0)']], [['', 'b%{theta:0.2f}', '%{theta:0.2f}'], ['', 'b90.00', '180.00']] ]); + + checkTextTemplate({ + data: [{ + type: 'scatterpolargl', + mode: 'text', + theta: ['a', 'b'], + r: ['1000', '1200'] + }], + layout: { + polar: { + radialaxis: { tickprefix: '$', ticksuffix: ' !', tickformat: '.2f'}, + angularaxis: { tickprefix: '*', ticksuffix: '*' } + } + } + }, '.textpoint', [ + ['%{theta} is %{r}', ['*a* is $1000.00 !', '*b* is $1200.00 !']] + ]); }); diff --git a/test/jasmine/tests/scatterternary_test.js b/test/jasmine/tests/scatterternary_test.js index 83299d8027b..cc29c0237ec 100644 --- a/test/jasmine/tests/scatterternary_test.js +++ b/test/jasmine/tests/scatterternary_test.js @@ -567,4 +567,27 @@ describe('Test scatterternary texttemplate:', function() { }], 'g.textpoint', [ ['%{text} (%{a:.1f}, %{b:.1f}, %{c:.1f})', ['A (3.0, 2.0, 5.0)', 'B (2.0, 5.0, 2.0)', 'C (5.0, 2.0, 2.0)']] ]); + + checkTextTemplate({ + data: [{ + type: 'scatterternary', + mode: 'text', + a: [3, 2, 5], + b: [2, 5, 2], + c: [5, 2, 2] + }], + layout: { + ternary: { + aaxis: { tickprefix: '*', ticksuffix: '*' }, + baxis: { tickprefix: '$', ticksuffix: ' !', tickformat: '.2f'}, + caxis: { tickprefix: '#', ticksuffix: '^'} + } + } + }, '.textpoint', [ + ['%{a} is %{b} is %{c}', [ + '*0.3* is $0.20 ! is #0.5^', + '*0.2222222* is $0.56 ! is #0.2222222^', + '*0.5555556* is $0.22 ! is #0.2222222^' + ]] + ]); });