diff --git a/package-lock.json b/package-lock.json index 511c9189238..360829aaf99 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2090,6 +2090,52 @@ "randomfill": "1.0.4" } }, + "css-font": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/css-font/-/css-font-1.2.0.tgz", + "integrity": "sha512-V4U4Wps4dPDACJ4WpgofJ2RT5Yqwe1lEH6wlOOaIxMi0gTjdIijsc5FmxQlZ7ZZyKQkkutqqvULOp07l9c7ssA==", + "requires": { + "css-font-size-keywords": "1.0.0", + "css-font-stretch-keywords": "1.0.1", + "css-font-style-keywords": "1.0.1", + "css-font-weight-keywords": "1.0.0", + "css-global-keywords": "1.0.1", + "css-system-font-keywords": "1.0.0", + "pick-by-alias": "1.2.0", + "string-split-by": "1.0.0", + "unquote": "1.1.1" + } + }, + "css-font-size-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-font-size-keywords/-/css-font-size-keywords-1.0.0.tgz", + "integrity": "sha1-hUh1rOmspqjS7g00WkSq6btttss=" + }, + "css-font-stretch-keywords": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/css-font-stretch-keywords/-/css-font-stretch-keywords-1.0.1.tgz", + "integrity": "sha1-UM7puboDH7XJUtRyMTnx4Qe1SxA=" + }, + "css-font-style-keywords": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/css-font-style-keywords/-/css-font-style-keywords-1.0.1.tgz", + "integrity": "sha1-XDUygT9jtKHelU0TzqhqtDM0CeQ=" + }, + "css-font-weight-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-font-weight-keywords/-/css-font-weight-keywords-1.0.0.tgz", + "integrity": "sha1-m8BGcayFvHJLV07106yWsNYE/Zc=" + }, + "css-global-keywords": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/css-global-keywords/-/css-global-keywords-1.0.1.tgz", + "integrity": "sha1-cqmupyeW0Bmx0qMlLeTlqqN+Smk=" + }, + "css-system-font-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-system-font-keywords/-/css-system-font-keywords-1.0.0.tgz", + "integrity": "sha1-hcbwhquk6zLFcaMIav/ENLhII+0=" + }, "csscolorparser": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/csscolorparser/-/csscolorparser-1.0.3.tgz", @@ -2406,6 +2452,11 @@ "minimalistic-assert": "1.0.0" } }, + "detect-kerning": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-kerning/-/detect-kerning-2.1.2.tgz", + "integrity": "sha512-I3JIbrnKPAntNLl1I6TpSQQdQ4AutYzv/sKMFKbepawV/hlH0GmYKhUoOEMd4xqaUHT+Bm0f4127lh5qs1m1tw==" + }, "detective": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/detective/-/detective-5.0.2.tgz", @@ -3452,6 +3503,14 @@ "debug": "3.1.0" } }, + "font-atlas": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/font-atlas/-/font-atlas-2.1.0.tgz", + "integrity": "sha512-kP3AmvX+HJpW4w3d+PiPR2X6E1yvsBXt2yhuCw+yReO9F1WYhvZwx3c95DGZGwg9xYzDGrgJYa885xmVA+28Cg==", + "requires": { + "css-font": "1.2.0" + } + }, "font-atlas-sdf": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/font-atlas-sdf/-/font-atlas-sdf-1.3.3.tgz", @@ -3461,6 +3520,14 @@ "tiny-sdf": "1.0.2" } }, + "font-measure": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/font-measure/-/font-measure-1.2.2.tgz", + "integrity": "sha512-mRLEpdrWzKe9hbfaF3Qpr06TAjquuBVP5cHy4b3hyeNdjc9i0PO6HniGsX5vjL5OWv7+Bd++NiooNpT/s8BvIA==", + "requires": { + "css-font": "1.2.0" + } + }, "for-each": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.2.tgz", @@ -5050,6 +5117,70 @@ "typedarray-pool": "1.1.0" } }, + "gl-text": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/gl-text/-/gl-text-1.1.5.tgz", + "integrity": "sha512-vb6WKUUT+90Zy0SeqI1dNcOYw7kMIBCwE5d+8qjDoKWrwHapR3ADbZQ/ohHKEMum1h8jZxoypgE1BCBPOX3x8g==", + "requires": { + "bit-twiddle": "1.0.2", + "color-normalize": "1.1.0", + "css-font": "1.2.0", + "detect-kerning": "2.1.2", + "es6-weak-map": "2.0.2", + "flatten-vertex-data": "1.0.2", + "font-atlas": "2.1.0", + "font-measure": "1.2.2", + "gl-util": "3.0.8", + "is-plain-obj": "1.1.0", + "object-assign": "4.1.1", + "parse-rect": "1.2.0", + "parse-unit": "1.0.1", + "pick-by-alias": "1.2.0", + "regl": "1.3.6", + "to-px": "1.0.1", + "typedarray-pool": "1.1.0" + }, + "dependencies": { + "color-normalize": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/color-normalize/-/color-normalize-1.1.0.tgz", + "integrity": "sha512-OY+unS2qneabd/72V0VLfwxQHvJ1t3JxM8d7LBPBwaVeda4vbrKuKRgtR1ieuIUdnXN7mWTg8FrrQMmsG7xd3w==", + "requires": { + "clamp": "1.0.1", + "color-rgba": "2.1.0", + "dtype": "2.0.0" + } + }, + "color-parse": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/color-parse/-/color-parse-1.3.7.tgz", + "integrity": "sha512-8G6rPfyTZhWYKU7D2hwywTjA4YlqX/Z7ClqTEzh5ENc5QkLOff0u8EuyNZR6xScEBhWpAyiDrrVGNUE/Btg2LA==", + "requires": { + "color-name": "1.1.3", + "defined": "1.0.0", + "is-plain-obj": "1.1.0" + } + }, + "color-rgba": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-rgba/-/color-rgba-2.1.0.tgz", + "integrity": "sha512-yAmMouVOLRAtYJwP52qymiscIMpw2g7VO82pkW+a88BpW1AZ+O6JDxAAojLljGO0pQkkvZLLN9oQNTEgT+RFiw==", + "requires": { + "clamp": "1.0.1", + "color-parse": "1.3.7", + "color-space": "1.15.0" + } + }, + "flatten-vertex-data": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/flatten-vertex-data/-/flatten-vertex-data-1.0.2.tgz", + "integrity": "sha512-BvCBFK2NZqerFTdMDgqfHBwxYWnxeCkwONsw6PvBMcUXqo8U/KDWwmXhqx1x2kLIg7DqIsJfOaJFOmlua3Lxuw==", + "requires": { + "dtype": "2.0.0" + } + } + } + }, "gl-texture2d": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/gl-texture2d/-/gl-texture2d-2.1.0.tgz", @@ -5060,6 +5191,20 @@ "typedarray-pool": "1.1.0" } }, + "gl-util": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/gl-util/-/gl-util-3.0.8.tgz", + "integrity": "sha512-UPKIeAbWU/TNCWZNlpvR2r9TCojhSTA11cSWTqsRNkxg6V1+PXmyyYkldGJGGAkGi8kRUNRbPaoj0U12BslzdQ==", + "requires": { + "es6-weak-map": "2.0.2", + "is-browser": "2.0.1", + "is-firefox": "1.0.3", + "is-plain-obj": "1.1.0", + "number-is-integer": "1.0.1", + "object-assign": "4.1.1", + "pick-by-alias": "1.2.0" + } + }, "gl-vao": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/gl-vao/-/gl-vao-1.3.0.tgz", @@ -6009,11 +6154,15 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", - "dev": true, "requires": { "number-is-nan": "1.0.1" } }, + "is-firefox": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-firefox/-/is-firefox-1.0.3.tgz", + "integrity": "sha1-KioVZ3g6QX9uFYMjEI84YbCRhWI=" + }, "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", @@ -8075,11 +8224,18 @@ "integrity": "sha1-l33/1xdgErnsMNKjnbXPcqBDnt0=", "dev": true }, + "number-is-integer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-integer/-/number-is-integer-1.0.1.tgz", + "integrity": "sha1-5ZvKFy/+0nMY55x862y3LAlbIVI=", + "requires": { + "is-finite": "1.0.2" + } + }, "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" }, "numeric": { "version": "1.2.6", @@ -8344,6 +8500,11 @@ "integrity": "sha512-lQe48YPsMJAig+yngZ87Lus+NF+3mtu7DVOBu6b/gHO1YpKwIj5AWjZ/TOS7i46HD/UixzWb1zeWDZfGZ3iYcg==", "dev": true }, + "parenthesis": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/parenthesis/-/parenthesis-3.1.5.tgz", + "integrity": "sha512-9KbfUp3+gD0MIl4AGfLBwVNvcPf1fokUJtYxql511chVNnS8DrYFazqBfZDqD4GV76XUhQbbxmZJPPOsV4GIbw==" + }, "parents": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parents/-/parents-1.0.1.tgz", @@ -10905,6 +11066,14 @@ } } }, + "string-split-by": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string-split-by/-/string-split-by-1.0.0.tgz", + "integrity": "sha512-KaJKY+hfpzNyet/emP81PJA9hTVSfxNLS9SFTWxdCnnW1/zOOwiV248+EfoX7IQFcBaOp4G5YE6xTJMF+pLg6A==", + "requires": { + "parenthesis": "3.1.5" + } + }, "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", @@ -11582,6 +11751,11 @@ "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", "dev": true }, + "unquote": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz", + "integrity": "sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ=" + }, "update-diff": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/update-diff/-/update-diff-1.1.0.tgz", diff --git a/package.json b/package.json index 7ff361e57c1..afeac9bd93f 100644 --- a/package.json +++ b/package.json @@ -83,6 +83,7 @@ "gl-select-box": "^1.0.2", "gl-spikes2d": "^1.0.1", "gl-surface3d": "^1.3.5", + "gl-text": "^1.1.5", "glslify": "^6.1.1", "has-hover": "^1.0.1", "has-passive-events": "^1.0.0", diff --git a/src/components/drawing/index.js b/src/components/drawing/index.js index ae9c8120e16..15457ad7aa8 100644 --- a/src/components/drawing/index.js +++ b/src/components/drawing/index.js @@ -603,7 +603,9 @@ drawing.tryColorscale = function(marker, prefix) { else return Lib.identity; }; -var TEXTOFFSETSIGN = {start: 1, end: -1, middle: 0, bottom: 1, top: -1}; +var TEXTOFFSETSIGN = { + start: 1, end: -1, middle: 0, bottom: 1, top: -1 +}; function textPointPosition(s, textPosition, fontSize, markerRadius) { var group = d3.select(s.node().parentNode); @@ -623,7 +625,7 @@ function textPointPosition(s, textPosition, fontSize, markerRadius) { var numLines = (svgTextUtils.lineCount(s) - 1) * LINE_SPACING + 1; var dx = TEXTOFFSETSIGN[h] * r; var dy = fontSize * 0.75 + TEXTOFFSETSIGN[v] * r + - (TEXTOFFSETSIGN[v] - 1) * numLines * fontSize / 2; + (TEXTOFFSETSIGN[v] - 1) * numLines * fontSize / 2; // fix the overall text group position s.attr('text-anchor', h); diff --git a/src/traces/scattergl/attributes.js b/src/traces/scattergl/attributes.js index 4b930117d37..8078d22bb69 100644 --- a/src/traces/scattergl/attributes.js +++ b/src/traces/scattergl/attributes.js @@ -37,9 +37,14 @@ var attrs = module.exports = overrideAll({ 'this trace\'s (x,y) coordinates.' ].join(' ') }), + hovertext: scatterAttrs.hovertext, + + textposition: scatterAttrs.textposition, + textfont: scatterAttrs.textfont, + mode: { valType: 'flaglist', - flags: ['lines', 'markers'], + flags: ['lines', 'markers', 'text'], extras: ['none'], role: 'info', description: [ @@ -77,10 +82,12 @@ var attrs = module.exports = overrideAll({ hoveron: scatterAttrs.hoveron, selected: { - marker: scatterAttrs.selected.marker + marker: scatterAttrs.selected.marker, + textfont: scatterAttrs.selected.textfont }, unselected: { - marker: scatterAttrs.unselected.marker + marker: scatterAttrs.unselected.marker, + textfont: scatterAttrs.unselected.textfont }, opacity: plotAttrs.opacity diff --git a/src/traces/scattergl/convert.js b/src/traces/scattergl/convert.js index ba3878f1833..dd0b3366c14 100644 --- a/src/traces/scattergl/convert.js +++ b/src/traces/scattergl/convert.js @@ -25,31 +25,44 @@ var makeBubbleSizeFn = require('../scatter/make_bubble_size_func'); var constants = require('./constants'); var DESELECTDIM = require('../../constants/interactions').DESELECTDIM; +var TEXTOFFSETSIGN = { + start: 1, left: 1, end: -1, right: -1, middle: 0, center: 0, bottom: 1, top: -1 +}; + function convertStyle(gd, trace) { var i; var opts = { marker: undefined, + markerSel: undefined, + markerUnsel: undefined, line: undefined, fill: undefined, errorX: undefined, errorY: undefined, - selected: undefined, - unselected: undefined + text: undefined, + textSel: undefined, + textUnsel: undefined }; if(trace.visible !== true) return opts; + if(subTypes.hasText(trace)) { + opts.text = convertTextStyle(trace); + opts.textSel = convertTextSelection(trace, trace.selected); + opts.textUnsel = convertTextSelection(trace, trace.unselected); + } + if(subTypes.hasMarkers(trace)) { opts.marker = convertMarkerStyle(trace); - opts.selected = convertMarkerSelection(trace, trace.selected); - opts.unselected = convertMarkerSelection(trace, trace.unselected); + opts.markerSel = convertMarkerSelection(trace, trace.selected); + opts.markerUnsel = convertMarkerSelection(trace, trace.unselected); if(!trace.unselected && Array.isArray(trace.marker.opacity)) { var mo = trace.marker.opacity; - opts.unselected.opacity = new Array(mo.length); + opts.markerUnsel.opacity = new Array(mo.length); for(i = 0; i < mo.length; i++) { - opts.unselected.opacity[i] = DESELECTDIM * mo[i]; + opts.markerUnsel.opacity[i] = DESELECTDIM * mo[i]; } } } @@ -88,6 +101,78 @@ function convertStyle(gd, trace) { return opts; } +function convertTextStyle(trace) { + var count = trace._length; + var textfontIn = trace.textfont; + var textpositionIn = trace.textposition; + var textPos = Array.isArray(textpositionIn) ? textpositionIn : [textpositionIn]; + var tfc = textfontIn.color; + var tfs = textfontIn.size; + var tff = textfontIn.family; + var optsOut = {}; + var i; + + optsOut.text = trace.text; + optsOut.opacity = trace.opacity; + optsOut.font = {}; + optsOut.align = []; + optsOut.baseline = []; + + for(i = 0; i < textPos.length; i++) { + var tp = textPos[i].split(/\s+/); + + switch(tp[1]) { + case 'left': + optsOut.align.push('right'); + break; + case 'right': + optsOut.align.push('left'); + break; + default: + optsOut.align.push(tp[1]); + } + switch(tp[0]) { + case 'top': + optsOut.baseline.push('bottom'); + break; + case 'bottom': + optsOut.baseline.push('top'); + break; + default: + optsOut.baseline.push(tp[0]); + } + } + + if(Array.isArray(tfc)) { + optsOut.color = new Array(count); + for(i = 0; i < count; i++) { + optsOut.color[i] = tfc[i]; + } + } else { + optsOut.color = tfc; + } + + if(Array.isArray(tfs) || Array.isArray(tff)) { + // if any textfont param is array - make render a batch + optsOut.font = new Array(count); + for(i = 0; i < count; i++) { + var fonti = optsOut.font[i] = {}; + + fonti.size = Array.isArray(tfs) ? + (isNumeric(tfs[i]) ? tfs[i] : 0) : + tfs; + + fonti.family = Array.isArray(tff) ? tff[i] : tff; + } + } else { + // if both are single values, make render fast single-value + optsOut.font = {size: tfs, family: tff}; + } + + return optsOut; +} + + function convertMarkerStyle(trace) { var count = trace._length; var optsIn = trace.marker; @@ -219,7 +304,7 @@ function convertMarkerSelection(trace, target) { if(target.marker && target.marker.symbol) { optsOut = convertMarkerStyle(Lib.extendFlat({}, optsIn, target.marker)); } else if(target.marker) { - if(target.marker.size) optsOut.sizes = target.marker.size / 2; + if(target.marker.size) optsOut.size = target.marker.size / 2; if(target.marker.color) optsOut.colors = target.marker.color; if(target.marker.opacity !== undefined) optsOut.opacity = target.marker.opacity; } @@ -227,6 +312,27 @@ function convertMarkerSelection(trace, target) { return optsOut; } +function convertTextSelection(trace, target) { + var optsOut = {}; + + if(!target) return optsOut; + + if(target.textfont) { + var optsIn = { + opacity: 1, + text: trace.text, + textposition: trace.textposition, + textfont: Lib.extendFlat({}, trace.textfont) + }; + if(target.textfont) { + Lib.extendFlat(optsIn.textfont, target.textfont); + } + optsOut = convertTextStyle(optsIn); + } + + return optsOut; +} + function convertErrorBarStyle(trace, target) { var optsOut = { capSize: target.width * 2, @@ -419,10 +525,47 @@ function convertErrorBarPositions(gd, trace, positions, x, y) { return out; } +function convertTextPosition(gd, trace, textOpts, markerOpts) { + var count = trace._length; + var out = {}; + var i; + + // corresponds to textPointPosition from component.drawing + if(subTypes.hasMarkers(trace)) { + var fontOpts = textOpts.font; + var align = textOpts.align; + var baseline = textOpts.baseline; + out.offset = new Array(count); + + for(i = 0; i < count; i++) { + var ms = markerOpts.sizes ? markerOpts.sizes[i] : markerOpts.size; + var fs = Array.isArray(fontOpts) ? fontOpts[i].size : fontOpts.size; + + var a = Array.isArray(align) ? + (align.length > 1 ? align[i] : align[0]) : + align; + var b = Array.isArray(baseline) ? + (baseline.length > 1 ? baseline[i] : baseline[0]) : + baseline; + + var hSign = TEXTOFFSETSIGN[a]; + var vSign = TEXTOFFSETSIGN[b]; + var xPad = ms ? ms / 0.8 + 1 : 0; + var yPad = -vSign * xPad - vSign * 0.5; + out.offset[i] = [hSign * xPad / fs, yPad / fs]; + } + } + + return out; +} + module.exports = { - convertStyle: convertStyle, - convertMarkerStyle: convertMarkerStyle, - convertMarkerSelection: convertMarkerSelection, - convertLinePositions: convertLinePositions, - convertErrorBarPositions: convertErrorBarPositions + style: convertStyle, + + markerStyle: convertMarkerStyle, + markerSelection: convertMarkerSelection, + + linePositions: convertLinePositions, + errorBarPositions: convertErrorBarPositions, + textPosition: convertTextPosition }; diff --git a/src/traces/scattergl/defaults.js b/src/traces/scattergl/defaults.js index 1f55a192881..328c50e8709 100644 --- a/src/traces/scattergl/defaults.js +++ b/src/traces/scattergl/defaults.js @@ -18,6 +18,7 @@ var handleXYDefaults = require('../scatter/xy_defaults'); var handleMarkerDefaults = require('../scatter/marker_defaults'); var handleLineDefaults = require('../scatter/line_defaults'); var handleFillColorDefaults = require('../scatter/fillcolor_defaults'); +var handleTextDefaults = require('../scatter/text_defaults'); module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) { function coerce(attr, dflt) { @@ -32,9 +33,11 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout traceOut.visible = false; return; } + var defaultMode = len < constants.PTS_LINESONLY ? 'lines+markers' : 'lines'; coerce('text'); - coerce('mode', len < constants.PTS_LINESONLY ? 'lines+markers' : 'lines'); + coerce('hovertext'); + coerce('mode', defaultMode); if(subTypes.hasLines(traceOut)) { coerce('connectgaps'); @@ -49,6 +52,10 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout dfltHoverOn.push('points'); } + if(subTypes.hasText(traceOut)) { + handleTextDefaults(traceIn, traceOut, layout, coerce); + } + coerce('fill'); if(traceOut.fill !== 'none') { handleFillColorDefaults(traceIn, traceOut, defaultColor, coerce); diff --git a/src/traces/scattergl/index.js b/src/traces/scattergl/index.js index 950a163fbd1..58f4e3d7211 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -13,11 +13,13 @@ var createLine = require('regl-line2d'); var createError = require('regl-error2d'); var cluster = require('point-cluster'); var arrayRange = require('array-range'); +var Text = require('gl-text'); var Registry = require('../../registry'); var Lib = require('../../lib'); var prepareRegl = require('../../lib/prepare_regl'); var AxisIDs = require('../../plots/cartesian/axis_ids'); +var Color = require('../../components/color'); var subTypes = require('../scatter/subtypes'); var calcMarkerSize = require('../scatter/calc').calcMarkerSize; @@ -26,13 +28,11 @@ var calcColorscales = require('../scatter/colorscale_calc'); var linkTraces = require('../scatter/link_traces'); var getTraceColor = require('../scatter/get_trace_color'); var fillHoverText = require('../scatter/fill_hover_text'); - -var convertStyle = require('./convert').convertStyle; -var convertLinePositions = require('./convert').convertLinePositions; -var convertErrorBarPositions = require('./convert').convertErrorBarPositions; +var convert = require('./convert'); var BADNUM = require('../../constants/numerical').BADNUM; var TOO_MANY_POINTS = require('./constants').TOO_MANY_POINTS; +var DESELECTDIM = require('../../constants/interactions').DESELECTDIM; function calc(gd, trace) { var fullLayout = gd._fullLayout; @@ -103,6 +103,7 @@ function calc(gd, trace) { if(opts.marker && !scene.scatter2d) scene.scatter2d = true; if(opts.line && !scene.line2d) scene.line2d = true; if((opts.errorX || opts.errorY) && !scene.error2d) scene.error2d = true; + if(opts.text && !scene.glText) scene.glText = true; // FIXME: organize it in a more appropriate manner, probably in sceneOptions // put point-cluster instance for optimized regl calc @@ -116,8 +117,11 @@ function calc(gd, trace) { scene.errorYOptions.push(opts.errorY); scene.fillOptions.push(opts.fill); scene.markerOptions.push(opts.marker); - scene.selectedOptions.push(opts.selected); - scene.unselectedOptions.push(opts.unselected); + scene.markerSelectedOptions.push(opts.markerSel); + scene.markerUnselectedOptions.push(opts.markerUnsel); + scene.textOptions.push(opts.text); + scene.textSelectedOptions.push(opts.textSel); + scene.textUnselectedOptions.push(opts.textUnsel); scene.count++; // stash scene ref @@ -132,9 +136,10 @@ function calc(gd, trace) { return [{x: false, y: false, t: stash, trace: trace}]; } + // create scene options function sceneOptions(gd, subplot, trace, positions, x, y) { - var opts = convertStyle(gd, trace); + var opts = convert.style(gd, trace); if(opts.marker) { opts.marker.positions = positions; @@ -143,12 +148,12 @@ function sceneOptions(gd, subplot, trace, positions, x, y) { if(opts.line && positions.length > 1) { Lib.extendFlat( opts.line, - convertLinePositions(gd, trace, positions) + convert.linePositions(gd, trace, positions) ); } if(opts.errorX || opts.errorY) { - var errors = convertErrorBarPositions(gd, trace, positions, x, y); + var errors = convert.errorBarPositions(gd, trace, positions, x, y); if(opts.errorX) { Lib.extendFlat(opts.errorX, errors.x); @@ -158,9 +163,28 @@ function sceneOptions(gd, subplot, trace, positions, x, y) { } } + if(opts.text) { + Lib.extendFlat( + opts.text, + {positions: positions}, + convert.textPosition(gd, trace, opts.text, opts.marker) + ); + Lib.extendFlat( + opts.textSel, + {positions: positions}, + convert.textPosition(gd, trace, opts.text, opts.markerSel) + ); + Lib.extendFlat( + opts.textUnsel, + {positions: positions}, + convert.textPosition(gd, trace, opts.text, opts.markerUnsel) + ); + } + return opts; } + // make sure scene exists on subplot, return it function sceneUpdate(gd, subplot) { var scene = subplot._scene; @@ -175,10 +199,13 @@ function sceneUpdate(gd, subplot) { lineOptions: [], fillOptions: [], markerOptions: [], - selectedOptions: [], - unselectedOptions: [], + markerSelectedOptions: [], + markerUnselectedOptions: [], errorXOptions: [], - errorYOptions: [] + errorYOptions: [], + textOptions: [], + textSelectedOptions: [], + textUnselectedOptions: [] }; var initOpts = { @@ -189,6 +216,7 @@ function sceneUpdate(gd, subplot) { scatter2d: false, error2d: false, line2d: false, + glText: false, select2d: null }; @@ -203,16 +231,21 @@ function sceneUpdate(gd, subplot) { // apply new option to all regl components (used on drag) scene.update = function update(opt) { + var i; var opts = new Array(scene.count); - for(var i = 0; i < scene.count; i++) { + for(i = 0; i < scene.count; i++) { opts[i] = opt; } - if(scene.fill2d) scene.fill2d.update(opts); if(scene.scatter2d) scene.scatter2d.update(opts); if(scene.line2d) scene.line2d.update(opts); if(scene.error2d) scene.error2d.update(opts.concat(opts)); if(scene.select2d) scene.select2d.update(opts); + if(scene.glText) { + for(i = 0; i < scene.count; i++) { + scene.glText[i].update(opts[i]); + } + } scene.draw(); }; @@ -248,6 +281,12 @@ function sceneUpdate(gd, subplot) { scene.scatter2d.draw(scene.unselectBatch); } + for(i = 0; i < scene.count; i++) { + if(scene.glText[i] && scene.textOptions[i]) { + scene.glText[i].render(); + } + } + scene.dirty = false; }; @@ -265,24 +304,13 @@ function sceneUpdate(gd, subplot) { (height - vpSize.t) - (1 - yaxis.domain[1]) * vpSize.h ]; - var gl, regl; - if(scene.select2d) { - regl = scene.select2d.regl; - gl = regl._gl; - gl.enable(gl.SCISSOR_TEST); - gl.scissor(vp[0], vp[1], vp[2] - vp[0], vp[3] - vp[1]); - gl.clearColor(0, 0, 0, 0); - gl.clear(gl.COLOR_BUFFER_BIT); + clearViewport(scene.select2d, vp); } - if(scene.scatter2d) { - regl = scene.scatter2d.regl; - gl = regl._gl; - gl.enable(gl.SCISSOR_TEST); - gl.scissor(vp[0], vp[1], vp[2] - vp[0], vp[3] - vp[1]); - gl.clearColor(0, 0, 0, 0); - gl.clear(gl.COLOR_BUFFER_BIT); + clearViewport(scene.scatter2d, vp); + } else if(scene.glText) { + clearViewport(scene.glText[0], vp); } }; @@ -293,14 +321,21 @@ function sceneUpdate(gd, subplot) { if(scene.error2d) scene.error2d.destroy(); if(scene.line2d) scene.line2d.destroy(); if(scene.select2d) scene.select2d.destroy(); + if(scene.glText) { + scene.glText.forEach(function(text) { text.destroy(); }); + } scene.lineOptions = null; scene.fillOptions = null; scene.markerOptions = null; - scene.selectedOptions = null; - scene.unselectedOptions = null; + scene.markerSelectedOptions = null; + scene.markerUnselectedOptions = null; scene.errorXOptions = null; scene.errorYOptions = null; + scene.textOptions = null; + scene.textSelectedOptions = null; + scene.textUnselectedOptions = null; + scene.selectBatch = null; scene.unselectBatch = null; @@ -318,9 +353,19 @@ function sceneUpdate(gd, subplot) { return scene; } +function clearViewport(comp, vp) { + var gl = comp.regl._gl; + gl.enable(gl.SCISSOR_TEST); + gl.scissor(vp[0], vp[1], vp[2] - vp[0], vp[3] - vp[1]); + gl.clearColor(0, 0, 0, 0); + gl.clear(gl.COLOR_BUFFER_BIT); +} + function plot(gd, subplot, cdata) { if(!cdata.length) return; + var i; + var fullLayout = gd._fullLayout; var scene = cdata[0][0].t._scene; var dragmode = fullLayout.dragmode; @@ -357,8 +402,19 @@ function plot(gd, subplot, cdata) { if(scene.fill2d === true) { scene.fill2d = createLine(regl); } + if(scene.glText === true) { + scene.glText = new Array(scene.count); + for(i = 0; i < scene.count; i++) { + scene.glText[i] = new Text(regl); + } + } // update main marker options + if(scene.glText) { + for(i = 0; i < scene.count; i++) { + scene.glText[i].update(scene.textOptions[i]); + } + } if(scene.line2d) { scene.line2d.update(scene.lineOptions); } @@ -530,10 +586,9 @@ function plot(gd, subplot, cdata) { stash.xpx = stash.ypx = null; } - return trace.visible ? { - viewport: viewport, - range: range - } : null; + return trace.visible ? + {viewport: viewport, range: range} : + null; }); if(selectMode) { @@ -545,14 +600,22 @@ function plot(gd, subplot, cdata) { if(scene.scatter2d && scene.selectBatch && scene.selectBatch.length) { // update only traces with selection - scene.scatter2d.update(scene.unselectedOptions.map(function(opts, i) { + scene.scatter2d.update(scene.markerUnselectedOptions.map(function(opts, i) { return scene.selectBatch[i] ? opts : null; })); } if(scene.select2d) { scene.select2d.update(scene.markerOptions); - scene.select2d.update(scene.selectedOptions); + scene.select2d.update(scene.markerSelectedOptions); + } + + if(scene.glText) { + cdata.forEach(function(cdscatter) { + if(cdscatter && cdscatter[0] && cdscatter[0].trace) { + styleTextSelection(cdscatter); + } + }); } } @@ -572,12 +635,18 @@ function plot(gd, subplot, cdata) { if(scene.select2d) { scene.select2d.update(vpRange); } + if(scene.glText) { + scene.glText.forEach(function(text, i) { + text.update(vpRange[i]); + }); + } scene.draw(); return; } + function hoverPoints(pointData, xval, yval, hovermode) { var cd = pointData.cd; var stash = cd[0].t; @@ -767,13 +836,16 @@ function selectPoints(searchInfo, polygon) { if(!scene) return selection; - var hasOnlyLines = (!subTypes.hasMarkers(trace) && !subTypes.hasText(trace)); + var hasText = subTypes.hasText(trace); + var hasMarkers = subTypes.hasMarkers(trace); + var hasOnlyLines = !hasMarkers && !hasText; if(trace.visible !== true || hasOnlyLines) return selection; // degenerate polygon does not enable selection // filter out points by visible scatter ones var els = null; var unels = null; + // FIXME: clearing selection does not work here var i; if(polygon !== false && !polygon.degenerate) { els = [], unels = []; @@ -807,12 +879,19 @@ function selectPoints(searchInfo, polygon) { scene.unselectBatch[i] = []; } // we should turn scatter2d into unselected once we have any points selected - scene.scatter2d.update(scene.unselectedOptions); + if(hasMarkers) { + scene.scatter2d.update(scene.markerUnselectedOptions); + } } scene.selectBatch[stash.index] = els; scene.unselectBatch[stash.index] = unels; + // update text options + if(hasText) { + styleTextSelection(cd); + } + return selection; } @@ -831,6 +910,42 @@ function style(gd, cds) { scene.draw(); } +function styleTextSelection(cd) { + var cd0 = cd[0]; + var stash = cd0.t; + var scene = stash._scene; + var index = stash.index; + var els = scene.selectBatch[index]; + var unels = scene.unselectBatch[index]; + var baseOpts = scene.textOptions[index]; + var selOpts = scene.textSelectedOptions[index] || {}; + var unselOpts = scene.textUnselectedOptions[index] || {}; + var opts = Lib.extendFlat({}, baseOpts); + var i, j; + + if(els && unels) { + var stc = selOpts.color; + var utc = unselOpts.color; + var base = baseOpts.color; + var hasArrayBase = Array.isArray(base); + opts.color = new Array(stash.count); + + + for(i = 0; i < els.length; i++) { + j = els[i]; + opts.color[j] = stc || (hasArrayBase ? base[j] : base); + } + for(i = 0; i < unels.length; i++) { + j = unels[i]; + var basej = hasArrayBase ? base[j] : base; + opts.color[j] = utc ? utc : + stc ? basej : Color.addOpacity(basej, DESELECTDIM); + } + } + + scene.glText[index].update(opts); +} + module.exports = { moduleType: 'trace', name: 'scattergl', diff --git a/src/traces/scatterpolargl/index.js b/src/traces/scatterpolargl/index.js index 058767fd875..963f9d98657 100644 --- a/src/traces/scatterpolargl/index.js +++ b/src/traces/scatterpolargl/index.js @@ -117,7 +117,7 @@ function plot(container, subplot, cdata) { // bring positions to selected/unselected options if(subTypes.hasMarkers(trace)) { - options.selected.positions = options.unselected.positions = options.marker.positions; + options.markerSel.positions = options.markerUnsel.positions = options.marker.positions; } // save scene options batch @@ -126,8 +126,8 @@ function plot(container, subplot, cdata) { scene.errorYOptions.push(options.errorY); scene.fillOptions.push(options.fill); scene.markerOptions.push(options.marker); - scene.selectedOptions.push(options.selected); - scene.unselectedOptions.push(options.unselected); + scene.markerSelectedOptions.push(options.markerSel); + scene.markerUnselectedOptions.push(options.markerUnsel); scene.count = cdata.length; // stash scene ref diff --git a/src/traces/splom/index.js b/src/traces/splom/index.js index efcd775b117..6905ef6b247 100644 --- a/src/traces/splom/index.js +++ b/src/traces/splom/index.js @@ -20,8 +20,8 @@ var subTypes = require('../scatter/subtypes'); var calcMarkerSize = require('../scatter/calc').calcMarkerSize; var calcAxisExpansion = require('../scatter/calc').calcAxisExpansion; var calcColorscales = require('../scatter/colorscale_calc'); -var convertMarkerSelection = require('../scattergl/convert').convertMarkerSelection; -var convertMarkerStyle = require('../scattergl/convert').convertMarkerStyle; +var convertMarkerSelection = require('../scattergl/convert').markerSelection; +var convertMarkerStyle = require('../scattergl/convert').markerStyle; var calcHover = require('../scattergl').calcHover; var BADNUM = require('../../constants/numerical').BADNUM; diff --git a/tasks/header.js b/tasks/header.js index 67b26491d17..ccb9b41e065 100644 --- a/tasks/header.js +++ b/tasks/header.js @@ -87,7 +87,10 @@ function updateHeadersInSrcFiles() { }); function isCorrect(header) { - return (header.value === licenseStr); + return ( + header.value.replace(/\s+$/gm, '') === + licenseStr.replace(/\s+$/gm, '') + ); } function hasWrongDate(header) { diff --git a/test/image/baselines/gl2d_point-selection.png b/test/image/baselines/gl2d_point-selection.png index f4e67323d98..7826e6b0655 100644 Binary files a/test/image/baselines/gl2d_point-selection.png and b/test/image/baselines/gl2d_point-selection.png differ diff --git a/test/image/baselines/gl2d_text_chart_arrays.png b/test/image/baselines/gl2d_text_chart_arrays.png new file mode 100644 index 00000000000..cd67ed53dd1 Binary files /dev/null and b/test/image/baselines/gl2d_text_chart_arrays.png differ diff --git a/test/image/baselines/gl2d_text_chart_basic.png b/test/image/baselines/gl2d_text_chart_basic.png new file mode 100644 index 00000000000..93488811117 Binary files /dev/null and b/test/image/baselines/gl2d_text_chart_basic.png differ diff --git a/test/image/baselines/gl2d_text_chart_invalid-arrays.png b/test/image/baselines/gl2d_text_chart_invalid-arrays.png new file mode 100644 index 00000000000..20220a8e3ad Binary files /dev/null and b/test/image/baselines/gl2d_text_chart_invalid-arrays.png differ diff --git a/test/image/baselines/gl2d_text_chart_single-string.png b/test/image/baselines/gl2d_text_chart_single-string.png new file mode 100644 index 00000000000..733e3584811 Binary files /dev/null and b/test/image/baselines/gl2d_text_chart_single-string.png differ diff --git a/test/image/baselines/gl2d_text_chart_styling.png b/test/image/baselines/gl2d_text_chart_styling.png new file mode 100644 index 00000000000..1b1b10918cd Binary files /dev/null and b/test/image/baselines/gl2d_text_chart_styling.png differ diff --git a/test/image/mocks/gl2d_point-selection.json b/test/image/mocks/gl2d_point-selection.json index a7427865108..55d6265b908 100644 --- a/test/image/mocks/gl2d_point-selection.json +++ b/test/image/mocks/gl2d_point-selection.json @@ -16,6 +16,9 @@ "marker": { "color": "blue", "size": 20 + }, + "textfont": { + "color": "red" } }, "unselected": { diff --git a/test/image/mocks/gl2d_text_chart_arrays.json b/test/image/mocks/gl2d_text_chart_arrays.json new file mode 100644 index 00000000000..d9832c496dc --- /dev/null +++ b/test/image/mocks/gl2d_text_chart_arrays.json @@ -0,0 +1,144 @@ +{ + "data": [ + { + "x": [ + 0, + 1, + 2 + ], + "y": [ + 1, + 1, + 1 + ], + "mode": "lines+markers+text", + "name": "Lines,\nMarkers\rand\r\nText", + "text": [ + "Text\nA", + "Text\rB", + "Text\r\nC" + ], + "textfont": { + "family": [ + "Droid Sans, sans-serif", + "Old Standard TT, serif", + "PT Sans Narrow, sans-serif" + ], + "size": [ + 20, + 10, + 25 + ], + "color": [ + "red", + "blue", + "green" + ] + }, + "textposition": [ + "top right", + "top left", + "top left" + ], + "hovertext": [ + "Hover text\nA", + "Hover text\rB", + "Hover text\r\nC" + ], + "type": "scattergl" + }, + { + "x": [ + 0, + 1, + 2 + ], + "y": [ + 2, + 2, + 2 + ], + "mode": "lines+markers+text", + "name": "Lines and Text", + "text": [ + "Text G", + "Text H", + "Text I" + ], + "textfont": { + "family": [ + "Droid Sans Mono, sans-serif", + "Raleway, sans-serif", + "Times New Roman, Times, serif" + ], + "size": [ + 18, + 20, + 40 + ], + "color": [ + "black", + "#d3d3d2", + "#ed6100" + ] + }, + "textposition": [ + "bottom right", + "bottom left", + "bottom left" + ], + "hovertext": [ + "Hover text G", + "Hover text H", + "Hover text I" + ], + "type": "scattergl" + }, + { + "x": [ + 0, + 1, + 2 + ], + "y": [ + 1.5, + 1.5, + 1.5 + ], + "mode": "text", + "name": "missing text", + "text": [ + "a", + "", + "b" + ], + "hovertext": [ + "a (hover)", + "", + "b (hover)" + ] + } + ], + "layout": { + "showlegend": true, + "xaxis": { + "type": "linear", + "range": [ + -0.1465172137710168, + 2.1465172137710167 + ], + "autorange": true + }, + "yaxis": { + "type": "linear", + "range": [ + 0.9267515923566879, + 2.073248407643312 + ], + "autorange": true + }, + "height": 450, + "width": 1000, + "autosize": true + } +} diff --git a/test/image/mocks/gl2d_text_chart_basic.json b/test/image/mocks/gl2d_text_chart_basic.json new file mode 100644 index 00000000000..271afa73b43 --- /dev/null +++ b/test/image/mocks/gl2d_text_chart_basic.json @@ -0,0 +1,70 @@ +{ + "data": [ + { + "x": [ + 0, + 1, + 2 + ], + "y": [ + 1, + 1, + 1 + ], + "mode": "lines+markers+text", + "name": "Lines, Markers and Text", + "text": [ + "Text A", + "Text B", + "Text C" + ], + "textposition": "top", + "type": "scattergl" + }, + { + "x": [ + 0, + 1, + 2 + ], + "y": [ + 2, + 2, + 2 + ], + "mode": "markers+text", + "name": "Markers and Text", + "text": [ + "Text D", + "Text E", + "Text F" + ], + "textposition": "bottom", + "type": "scattergl" + }, + { + "x": [ + 0, + 1, + 2 + ], + "y": [ + 3, + 3, + 3 + ], + "mode": "lines+text", + "name": "Lines and Text", + "text": [ + "Text G", + "Text H", + "Text I" + ], + "textposition": "bottom", + "type": "scattergl" + } + ], + "layout": { + "showlegend": false + } +} diff --git a/test/image/mocks/gl2d_text_chart_invalid-arrays.json b/test/image/mocks/gl2d_text_chart_invalid-arrays.json new file mode 100644 index 00000000000..2a342faaad5 --- /dev/null +++ b/test/image/mocks/gl2d_text_chart_invalid-arrays.json @@ -0,0 +1,110 @@ +{ + "data": [ + { + "x": [ + 0, + 1, + 2 + ], + "y": [ + 1, + 1, + 1 + ], + "mode": "lines+markers+text", + "name": "Lines, Markers and Text", + "text": [ + "Text A", + "Text B", + "Text C" + ], + "textfont": { + "family": [ + "Droid Sans, sans-serif", + "Old Standard TT, serif", + "PT Sans Narrow, sans-serif" + ], + "size": [ + 20, + "not-a-number", + null + ], + "color": [ + "red", + "blue", + "green" + ] + }, + "textposition": [ + "top right", + "top left", + "top left" + ], + "type": "scattergl" + }, + { + "x": [ + 0, + 1, + 2 + ], + "y": [ + 2, + 2, + 2 + ], + "mode": "lines+markers+text", + "name": "Lines and Text", + "text": [ + "Text G", + "Text H", + "Text I" + ], + "textfont": { + "family": [ + "Droid Sans Mono, sans-serif", + "Raleway, sans-serif", + "Times New Roman, Times, serif" + ], + "size": [ + 18, + 20, + 40 + ], + "color": [ + null, + "not-a-color", + "#ed6100" + ] + }, + "textposition": [ + "bottom right", + "bottom left", + "bottom left" + ], + "type": "scattergl" + } + ], + "layout": { + "showlegend": true, + "xaxis": { + "type": "linear", + "range": [ + -0.1465172137710168, + 2.1465172137710167 + ], + "autorange": true + }, + "yaxis": { + "type": "linear", + "range": [ + 0.9267515923566879, + 2.073248407643312 + ], + "autorange": true + }, + "height": 450, + "width": 1000, + "autosize": true + } +} diff --git a/test/image/mocks/gl2d_text_chart_single-string.json b/test/image/mocks/gl2d_text_chart_single-string.json new file mode 100644 index 00000000000..a7b5770060b --- /dev/null +++ b/test/image/mocks/gl2d_text_chart_single-string.json @@ -0,0 +1,58 @@ +{ + "data": [ + { + "x": [ + 0, + 1, + 2 + ], + "y": [ + 1, + 1, + 1 + ], + "mode": "lines+markers+text", + "name": "Lines, Markers and (same) Text", + "text": "(same) text", + "textposition": "top", + "type": "scattergl" + }, + { + "x": [ + 0, + 1, + 2 + ], + "y": [ + 2, + 2, + 2 + ], + "mode": "markers+text", + "name": "Markers and Text (same)", + "text": "text (same)", + "textposition": "bottom", + "type": "scattergl" + }, + { + "x": [ + 0, + 1, + 2 + ], + "y": [ + 3, + 3, + 3 + ], + "mode": "lines+text", + "name": "Lines and Text", + "text": "text", + "textposition": "bottom", + "type": "scattergl" + } + ], + "layout": { + "showlegend": false + } +} diff --git a/test/image/mocks/gl2d_text_chart_styling.json b/test/image/mocks/gl2d_text_chart_styling.json new file mode 100644 index 00000000000..019094da2ed --- /dev/null +++ b/test/image/mocks/gl2d_text_chart_styling.json @@ -0,0 +1,59 @@ +{ + "data": [ + { + "x": [ + 0, + 1, + 2 + ], + "y": [ + 1, + 1, + 1 + ], + "mode": "lines+markers+text", + "name": "Lines, Markers and Text", + "text": [ + "Text A", + "Text B", + "Text C" + ], + "textfont": { + "family": "Arial, sans-serif", + "size": 18, + "color": "#1f77b4" + }, + "textposition": "top right", + "type": "scattergl" + }, + { + "x": [ + 0, + 1, + 2 + ], + "y": [ + 2, + 2, + 2 + ], + "mode": "lines+markers+text", + "name": "Lines and Text", + "text": [ + "Text G", + "Text H", + "Text I" + ], + "textfont": { + "family": "Arial, sans-serif", + "size": 18, + "color": "#ff7f0e" + }, + "textposition": "bottom", + "type": "scattergl" + } + ], + "layout": { + "showlegend": false + } +} diff --git a/test/jasmine/tests/gl2d_click_test.js b/test/jasmine/tests/gl2d_click_test.js index 512409242d7..59e5d55f4bc 100644 --- a/test/jasmine/tests/gl2d_click_test.js +++ b/test/jasmine/tests/gl2d_click_test.js @@ -318,6 +318,33 @@ describe('@gl @flaky Test hover and click interactions', function() { .then(done); }); + it('should show correct label for scattergl when hovertext is set', function(done) { + var _mock = Lib.extendDeep({}, mock1); + _mock.data[0].hovertext = 'text'; + _mock.data[0].hovertext = 'HoVeRtExT'; + _mock.layout.hovermode = 'closest'; + + var run = makeRunner([634, 321], { + x: 15.772, + y: 0.387, + label: ['(15.772, 0.387)\nHoVeRtExT', null], + curveNumber: 0, + pointNumber: 33, + bgcolor: 'rgb(0, 0, 238)', + bordercolor: 'rgb(255, 255, 255)', + fontSize: 13, + fontFamily: 'Arial', + fontColor: 'rgb(255, 255, 255)' + }, { + msg: 'scattergl with hovertext' + }); + + Plotly.plot(gd, _mock) + .then(run) + .catch(failTest) + .then(done); + }); + it('should output correct event data for pointcloud', function(done) { var _mock = Lib.extendDeep({}, mock2); @@ -711,4 +738,167 @@ describe('@noCI @gl Test gl2d lasso/select:', function() { .catch(failTest) .then(done); }); + + it('should work on gl text charts', function(done) { + var fig = Lib.extendDeep({}, require('@mocks/gl2d_text_chart_basic.json')); + fig.layout.dragmode = 'select'; + fig.layout.margin = {t: 0, b: 0, l: 0, r: 0}; + fig.layout.height = 500; + fig.layout.width = 500; + gd = createGraphDiv(); + + function _assertGlTextOpts(msg, exp) { + var scene = gd.calcdata[0][0].t._scene; + scene.glText.forEach(function(opts, i) { + expect(Array.from(opts.color)) + .toBeCloseToArray(exp.rgba[i], 2, 'item ' + i + ' - ' + msg); + }); + } + + Plotly.plot(gd, fig) + .then(delay(100)) + .then(function() { + _assertGlTextOpts('base', { + rgba: [ + [68, 68, 68, 255], + [68, 68, 68, 255], + [68, 68, 68, 255] + ] + }); + }) + .then(function() { return select([[100, 100], [250, 250]]); }) + .then(function(eventData) { + assertEventData(eventData, { + points: [{x: 1, y: 2}] + }); + _assertGlTextOpts('after selection', { + rgba: [ + [ + 68, 68, 68, 51, + 68, 68, 68, 51, + 68, 68, 68, 51, + ], + [ + 68, 68, 68, 51, + // this is the selected pt! + 68, 68, 68, 255, + 68, 68, 68, 51 + ], + [ + 68, 68, 68, 51, + 68, 68, 68, 51, + 68, 68, 68, 51 + ] + ] + }); + }) + .then(function() { + return Plotly.restyle(gd, 'selected.textfont.color', 'red'); + }) + .then(function() { return select([[100, 100], [250, 250]]); }) + .then(function() { + _assertGlTextOpts('after selection - with set selected.textfont.color', { + rgba: [ + [ + 68, 68, 68, 255, + 68, 68, 68, 255, + 68, 68, 68, 255, + ], + [ + 68, 68, 68, 255, + // this is the selected pt! + 255, 0, 0, 255, + 68, 68, 68, 255 + ], + [ + 68, 68, 68, 255, + 68, 68, 68, 255, + 68, 68, 68, 255 + ] + ] + }); + }) + .catch(failTest) + .then(done); + }); + + it('should work on gl text charts with array textfont.color', function(done) { + var fig = Lib.extendDeep({}, require('@mocks/gl2d_text_chart_arrays.json')); + fig.layout.dragmode = 'select'; + fig.layout.margin = {t: 0, b: 0, l: 0, r: 0}; + fig.layout.height = 500; + fig.layout.width = 500; + gd = createGraphDiv(); + + function _assertGlTextOpts(msg, exp) { + var scene = gd.calcdata[0][0].t._scene; + scene.glText.forEach(function(opts, i) { + expect(Array.from(opts.color)) + .toBeCloseToArray(exp.rgba[i], 2, 'item ' + i + ' - ' + msg); + }); + } + + Plotly.plot(gd, fig) + .then(delay(100)) + .then(function() { + _assertGlTextOpts('base', { + rgba: [ + [ + 255, 0, 0, 255, + 0, 0, 255, 255, + 0, 128, 0, 255 + ], + [ + 0, 0, 0, 255, + 211, 211, 210, 255, + 237, 97, 0, 255 + ] + ] + }); + }) + .then(function() { return select([[100, 10], [250, 100]]); }) + .then(function(eventData) { + assertEventData(eventData, { + points: [{x: 1, y: 2}] + }); + _assertGlTextOpts('after selection', { + rgba: [ + [ + 255, 0, 0, 51, + 0, 0, 255, 51, + 0, 128, 0, 51 + ], + [ + 0, 0, 0, 51, + // this is the selected pt! + 211, 211, 210, 255, + 237, 97, 0, 51 + ] + ] + }); + }) + .then(function() { + return Plotly.restyle(gd, 'selected.textfont.color', 'red'); + }) + .then(function() { return select([[100, 10], [250, 100]]); }) + .then(function() { + _assertGlTextOpts('after selection - with set selected.textfont.color', { + rgba: [ + [ + 255, 0, 0, 255, + 0, 0, 255, 255, + 0, 128, 0, 255 + ], + [ + 0, 0, 0, 255, + // this is the selected pt! + 255, 0, 0, 255, + 237, 97, 0, 255 + ] + ] + }); + }) + .catch(failTest) + .then(done); + }); }); diff --git a/test/jasmine/tests/gl2d_plot_interact_test.js b/test/jasmine/tests/gl2d_plot_interact_test.js index f2be65b1fa9..7107fe0b70c 100644 --- a/test/jasmine/tests/gl2d_plot_interact_test.js +++ b/test/jasmine/tests/gl2d_plot_interact_test.js @@ -389,7 +389,9 @@ describe('@gl Test gl2d plots', function() { Plotly.newPlot(gd, [{ // a trace with all regl2d objects type: 'scattergl', + mode: 'lines+markers+text', y: [1, 2, 1], + text: ['a', 'b', 'c'], error_x: {value: 10}, error_y: {value: 10}, fill: 'tozeroy' @@ -404,6 +406,7 @@ describe('@gl Test gl2d plots', function() { spyOn(scene.line2d, 'draw'); spyOn(scene.error2d, 'draw'); spyOn(scene.scatter2d, 'draw'); + spyOn(scene.glText[0], 'render'); return Plotly.restyle(gd, 'visible', 'legendonly', [0]); }) @@ -412,6 +415,7 @@ describe('@gl Test gl2d plots', function() { expect(scene.fill2d.draw).toHaveBeenCalledTimes(0); expect(scene.line2d.draw).toHaveBeenCalledTimes(0); expect(scene.error2d.draw).toHaveBeenCalledTimes(0); + expect(scene.glText[0].render).toHaveBeenCalledTimes(0); expect(scene.scatter2d.draw).toHaveBeenCalledTimes(1); return Plotly.restyle(gd, 'visible', true, [0]); @@ -421,6 +425,7 @@ describe('@gl Test gl2d plots', function() { expect(scene.fill2d.draw).toHaveBeenCalledTimes(1); expect(scene.line2d.draw).toHaveBeenCalledTimes(1); expect(scene.error2d.draw).toHaveBeenCalledTimes(2, 'twice for x AND y'); + expect(scene.glText[0].render).toHaveBeenCalledTimes(1); expect(scene.scatter2d.draw).toHaveBeenCalledTimes(3, 'both traces have markers'); }) .catch(failTest)