diff --git a/src/components/drawing/index.js b/src/components/drawing/index.js index 2b1c733df98..7fee4d9c666 100644 --- a/src/components/drawing/index.js +++ b/src/components/drawing/index.js @@ -265,56 +265,120 @@ function makePointPath(symbolNumber, r) { return drawing.symbolFuncs[base](r) + (symbolNumber >= 200 ? DOTPATH : ''); } -function singlePointStyle(d, sel, trace, markerScale, lineScale, marker, markerLine, gd) { - if(Registry.traceIs(trace, 'symbols')) { - var sizeFn = makeBubbleSizeFn(trace); +var HORZGRADIENT = {x1: 1, x2: 0, y1: 0, y2: 0}; +var VERTGRADIENT = {x1: 0, x2: 0, y1: 1, y2: 0}; - sel.attr('d', function(d) { - var r; +drawing.gradient = function(sel, gd, gradientID, type, color1, color2) { + var gradient = gd._fullLayout._defs.select('.gradients') + .selectAll('#' + gradientID) + .data([type + color1 + color2], Lib.identity); - // handle multi-trace graph edit case - if(d.ms === 'various' || marker.size === 'various') { - r = 3; - } else { - r = subTypes.isBubble(trace) ? - sizeFn(d.ms) : (marker.size || 6) / 2; - } + gradient.exit().remove(); - // store the calculated size so hover can use it - d.mrc = r; + gradient.enter() + .append(type === 'radial' ? 'radialGradient' : 'linearGradient') + .each(function() { + var el = d3.select(this); + if(type === 'horizontal') el.attr(HORZGRADIENT); + else if(type === 'vertical') el.attr(VERTGRADIENT); - // turn the symbol into a sanitized number - var x = drawing.symbolNumber(d.mx || marker.symbol) || 0; + el.attr('id', gradientID); + + var tc1 = tinycolor(color1); + var tc2 = tinycolor(color2); - // save if this marker is open - // because that impacts how to handle colors - d.om = x % 200 >= 100; + el.append('stop').attr({ + offset: '0%', + 'stop-color': Color.tinyRGB(tc2), + 'stop-opacity': tc2.getAlpha() + }); - return makePointPath(x, r); + el.append('stop').attr({ + offset: '100%', + 'stop-color': Color.tinyRGB(tc1), + 'stop-opacity': tc1.getAlpha() + }); }); - } - sel.style('opacity', function(d) { - return (d.mo + 1 || marker.opacity + 1) - 1; + sel.style({ + fill: 'url(#' + gradientID + ')', + 'fill-opacity': null }); +}; + +/* + * Make the gradients container and clear out any previous gradients. + * We never collect all the gradients we need in one place, + * so we can't ever remove gradients that have stopped being useful, + * except all at once before a full redraw. + * The upside of this is arbitrary points can share gradient defs + */ +drawing.initGradients = function(gd) { + var gradientsGroup = Lib.ensureSingle(gd._fullLayout._defs, 'g', 'gradients'); + gradientsGroup.selectAll('linearGradient,radialGradient').remove(); +}; + + +drawing.pointStyle = function(s, trace, gd) { + if(!s.size()) return; + + var fns = drawing.makePointStyleFns(trace); + + s.each(function(d) { + drawing.singlePointStyle(d, d3.select(this), trace, fns, gd); + }); +}; + +drawing.singlePointStyle = function(d, sel, trace, fns, gd) { + var marker = trace.marker; + var markerLine = marker.line; + + sel.style('opacity', + fns.selectedOpacityFn ? fns.selectedOpacityFn(d) : + (d.mo === undefined ? marker.opacity : d.mo) + ); + + if(fns.ms2mrc) { + var r; + + // handle multi-trace graph edit case + if(d.ms === 'various' || marker.size === 'various') { + r = 3; + } else { + r = fns.ms2mrc(d.ms); + } + + // store the calculated size so hover can use it + d.mrc = r; + + if(fns.selectedSizeFn) { + r = d.mrc = fns.selectedSizeFn(d); + } + + // turn the symbol into a sanitized number + var x = drawing.symbolNumber(d.mx || marker.symbol) || 0; + + // save if this marker is open + // because that impacts how to handle colors + d.om = x % 200 >= 100; + + sel.attr('d', makePointPath(x, r)); + } var perPointGradient = false; + var fillColor, lineColor, lineWidth; // 'so' is suspected outliers, for box plots - var fillColor, - lineColor, - lineWidth; if(d.so) { lineWidth = markerLine.outlierwidth; lineColor = markerLine.outliercolor; fillColor = marker.outliercolor; - } - else { + } else { lineWidth = (d.mlw + 1 || markerLine.width + 1 || // TODO: we need the latter for legends... can we get rid of it? (d.trace ? d.trace.marker.line.width : 0) + 1) - 1; - if('mlc' in d) lineColor = d.mlcc = lineScale(d.mlc); + if('mlc' in d) lineColor = d.mlcc = fns.lineScale(d.mlc); // weird case: array wasn't long enough to apply to every point else if(Lib.isArrayOrTypedArray(markerLine.color)) lineColor = Color.defaultLine; else lineColor = markerLine.color; @@ -324,8 +388,15 @@ function singlePointStyle(d, sel, trace, markerScale, lineScale, marker, markerL perPointGradient = true; } - if('mc' in d) fillColor = d.mcc = markerScale(d.mc); - else fillColor = marker.color || 'rgba(0,0,0,0)'; + if('mc' in d) { + fillColor = d.mcc = fns.markerScale(d.mc); + } else { + fillColor = marker.color || 'rgba(0,0,0,0)'; + } + + if(fns.selectedColorFn) { + fillColor = fns.selectedColorFn(d); + } } if(d.om) { @@ -336,8 +407,7 @@ function singlePointStyle(d, sel, trace, markerScale, lineScale, marker, markerL 'stroke-width': (lineWidth || 1) + 'px', fill: 'none' }); - } - else { + } else { sel.style('stroke-width', lineWidth + 'px'); var markerGradient = marker.gradient; @@ -355,8 +425,7 @@ function singlePointStyle(d, sel, trace, markerScale, lineScale, marker, markerL if(perPointGradient) gradientID += '-' + d.i; sel.call(drawing.gradient, gd, gradientID, gradientType, fillColor, gradientColor); - } - else { + } else { sel.call(Color.fill, fillColor); } @@ -364,79 +433,28 @@ function singlePointStyle(d, sel, trace, markerScale, lineScale, marker, markerL sel.call(Color.stroke, lineColor); } } -} - -var HORZGRADIENT = {x1: 1, x2: 0, y1: 0, y2: 0}; -var VERTGRADIENT = {x1: 0, x2: 0, y1: 1, y2: 0}; - -drawing.gradient = function(sel, gd, gradientID, type, color1, color2) { - var gradient = gd._fullLayout._defs.select('.gradients') - .selectAll('#' + gradientID) - .data([type + color1 + color2], Lib.identity); - - gradient.exit().remove(); - - gradient.enter() - .append(type === 'radial' ? 'radialGradient' : 'linearGradient') - .each(function() { - var el = d3.select(this); - if(type === 'horizontal') el.attr(HORZGRADIENT); - else if(type === 'vertical') el.attr(VERTGRADIENT); - - el.attr('id', gradientID); - - var tc1 = tinycolor(color1); - var tc2 = tinycolor(color2); - - el.append('stop').attr({ - offset: '0%', - 'stop-color': Color.tinyRGB(tc2), - 'stop-opacity': tc2.getAlpha() - }); - - el.append('stop').attr({ - offset: '100%', - 'stop-color': Color.tinyRGB(tc1), - 'stop-opacity': tc1.getAlpha() - }); - }); - - sel.style({ - fill: 'url(#' + gradientID + ')', - 'fill-opacity': null - }); }; -/* - * Make the gradients container and clear out any previous gradients. - * We never collect all the gradients we need in one place, - * so we can't ever remove gradients that have stopped being useful, - * except all at once before a full redraw. - * The upside of this is arbitrary points can share gradient defs - */ -drawing.initGradients = function(gd) { - var gradientsGroup = Lib.ensureSingle(gd._fullLayout._defs, 'g', 'gradients'); - gradientsGroup.selectAll('linearGradient,radialGradient').remove(); -}; - -drawing.singlePointStyle = function(d, sel, trace, markerScale, lineScale, gd) { +drawing.makePointStyleFns = function(trace) { + var out = {}; var marker = trace.marker; - singlePointStyle(d, sel, trace, markerScale, lineScale, marker, marker.line, gd); -}; - -drawing.pointStyle = function(s, trace, gd) { - if(!s.size()) return; - // allow array marker and marker line colors to be // scaled by given max and min to colorscales - var marker = trace.marker; - var markerScale = drawing.tryColorscale(marker, ''); - var lineScale = drawing.tryColorscale(marker, 'line'); + out.markerScale = drawing.tryColorscale(marker, ''); + out.lineScale = drawing.tryColorscale(marker, 'line'); - s.each(function(d) { - drawing.singlePointStyle(d, d3.select(this), trace, markerScale, lineScale, gd); - }); + if(Registry.traceIs(trace, 'symbols')) { + out.ms2mrc = subTypes.isBubble(trace) ? + makeBubbleSizeFn(trace) : + function() { return (marker.size || 6) / 2; }; + } + + if(trace.selectedpoints) { + Lib.extendFlat(out, drawing.makeSelectedPointStyleFns(trace)); + } + + return out; }; drawing.makeSelectedPointStyleFns = function(trace) { @@ -455,45 +473,48 @@ drawing.makeSelectedPointStyleFns = function(trace) { var smoIsDefined = smo !== undefined; var usmoIsDefined = usmo !== undefined; - out.opacityFn = function(d) { - var dmo = d.mo; - var dmoIsDefined = dmo !== undefined; + if(Lib.isArrayOrTypedArray(mo) || smoIsDefined || usmoIsDefined) { + out.selectedOpacityFn = function(d) { + var base = d.mo === undefined ? marker.opacity : d.mo; - if(dmoIsDefined || smoIsDefined || usmoIsDefined) { if(d.selected) { - if(smoIsDefined) return smo; + return smoIsDefined ? smo : base; } else { - if(usmoIsDefined) return usmo; - return DESELECTDIM * (dmoIsDefined ? dmo : mo); + return usmoIsDefined ? usmo : DESELECTDIM * base; } - } - }; + }; + } + var mc = marker.color; var smc = selectedMarker.color; var usmc = unselectedMarker.color; if(smc || usmc) { - out.colorFn = function(d) { + out.selectedColorFn = function(d) { + var base = d.mcc || mc; + if(d.selected) { - if(smc) return smc; + return smc || base; } else { - if(usmc) return usmc; + return usmc || base; } }; } + var ms = marker.size; var sms = selectedMarker.size; var usms = unselectedMarker.size; var smsIsDefined = sms !== undefined; var usmsIsDefined = usms !== undefined; - if(smsIsDefined || usmsIsDefined) { - out.sizeFn = function(d) { - var mrc = d.mrc; + if(Registry.traceIs(trace, 'symbols') && (smsIsDefined || usmsIsDefined)) { + out.selectedSizeFn = function(d) { + var base = d.mrc || ms / 2; + if(d.selected) { - return smsIsDefined ? sms / 2 : mrc; + return smsIsDefined ? sms / 2 : base; } else { - return usmsIsDefined ? usms / 2 : mrc; + return usmsIsDefined ? usms / 2 : base; } }; } @@ -501,38 +522,73 @@ drawing.makeSelectedPointStyleFns = function(trace) { return out; }; +drawing.makeSelectedTextStyleFns = function(trace) { + var out = {}; + + var selectedAttrs = trace.selected || {}; + var unselectedAttrs = trace.unselected || {}; + + var textFont = trace.textfont || {}; + var selectedTextFont = selectedAttrs.textfont || {}; + var unselectedTextFont = unselectedAttrs.textfont || {}; + + var tc = textFont.color; + var stc = selectedTextFont.color; + var utc = unselectedTextFont.color; + + out.selectedTextColorFn = function(d) { + var base = d.tc || tc; + + if(d.selected) { + return stc || base; + } else { + if(utc) return utc; + else return stc ? base : Color.addOpacity(base, DESELECTDIM); + } + }; + + return out; +}; + drawing.selectedPointStyle = function(s, trace) { if(!s.size() || !trace.selectedpoints) return; var fns = drawing.makeSelectedPointStyleFns(trace); var marker = trace.marker || {}; + var seq = []; - s.each(function(d) { - var pt = d3.select(this); - var mo2 = fns.opacityFn(d); - if(mo2 !== undefined) pt.style('opacity', mo2); - }); + if(fns.selectedOpacityFn) { + seq.push(function(pt, d) { + pt.style('opacity', fns.selectedOpacityFn(d)); + }); + } - if(fns.colorFn) { - s.each(function(d) { - var pt = d3.select(this); - var mc2 = fns.colorFn(d); - if(mc2) Color.fill(pt, mc2); + if(fns.selectedColorFn) { + seq.push(function(pt, d) { + Color.fill(pt, fns.selectedColorFn(d)); }); } - if(Registry.traceIs(trace, 'symbols') && fns.sizeFn) { - s.each(function(d) { - var pt = d3.select(this); + if(fns.selectedSizeFn) { + seq.push(function(pt, d) { var mx = d.mx || marker.symbol || 0; - var mrc2 = fns.sizeFn(d); + var mrc2 = fns.selectedSizeFn(d); pt.attr('d', makePointPath(drawing.symbolNumber(mx), mrc2)); - // save for selectedTextStyle + // save for Drawing.selectedTextStyle d.mrc2 = mrc2; }); } + + if(seq.length) { + s.each(function(d) { + var pt = d3.select(this); + for(var i = 0; i < seq.length; i++) { + seq[i](pt, d); + } + }); + } }; drawing.tryColorscale = function(marker, prefix) { @@ -582,6 +638,15 @@ function extracTextFontSize(d, trace) { // draw text at points drawing.textPointStyle = function(s, trace, gd) { + if(!s.size()) return; + + var selectedTextColorFn; + + if(trace.selectedpoints) { + var fns = drawing.makeSelectedTextStyleFns(trace); + selectedTextColorFn = fns.selectedTextColorFn; + } + s.each(function(d) { var p = d3.select(this); var text = Lib.extractOption(d, trace, 'tx', 'text'); @@ -593,11 +658,14 @@ drawing.textPointStyle = function(s, trace, gd) { var pos = d.tp || trace.textposition; var fontSize = extracTextFontSize(d, trace); + var fontColor = selectedTextColorFn ? + selectedTextColorFn(d) : + (d.tc || trace.textfont.color); p.call(drawing.font, d.tf || trace.textfont.family, fontSize, - d.tc || trace.textfont.color) + fontColor) .text(text) .call(svgTextUtils.convertToTspans, gd) .call(textPointPosition, pos, fontSize, d.mrc); @@ -607,26 +675,15 @@ drawing.textPointStyle = function(s, trace, gd) { drawing.selectedTextStyle = function(s, trace) { if(!s.size() || !trace.selectedpoints) return; - var selectedAttrs = trace.selected || {}; - var unselectedAttrs = trace.unselected || {}; + var fns = drawing.makeSelectedTextStyleFns(trace); s.each(function(d) { var tx = d3.select(this); - var tc = d.tc || trace.textfont.color; + var tc = fns.selectedTextColorFn(d); var tp = d.tp || trace.textposition; var fontSize = extracTextFontSize(d, trace); - var stc = (selectedAttrs.textfont || {}).color; - var utc = (unselectedAttrs.textfont || {}).color; - var tc2; - - if(d.selected) { - if(stc) tc2 = stc; - } else { - if(utc) tc2 = utc; - else if(!stc) tc2 = Color.addOpacity(tc, DESELECTDIM); - } - if(tc2) Color.fill(tx, tc2); + Color.fill(tx, tc); textPointPosition(tx, tp, fontSize, d.mrc2 || d.mrc); }); }; diff --git a/src/components/legend/style.js b/src/components/legend/style.js index fd022a9eb41..3c6284d58f8 100644 --- a/src/components/legend/style.js +++ b/src/components/legend/style.js @@ -110,8 +110,8 @@ module.exports = function style(s, gd) { // constrain text, markers, etc so they'll fit on the legend if(showMarkers || showText || showLines) { - var dEdit = {}, - tEdit = {}; + var dEdit = {}; + var tEdit = {}; if(showMarkers) { dEdit.mc = boundVal('marker.color', pickFirst); @@ -142,6 +142,9 @@ module.exports = function style(s, gd) { dMod = [Lib.minExtend(d0, dEdit)]; tMod = Lib.minExtend(trace, tEdit); + + // always show legend items in base state + tMod.selectedpoints = null; } var ptgroup = d3.select(this).select('g.legendpoints'); diff --git a/src/plots/cartesian/select.js b/src/plots/cartesian/select.js index 5dd418441f8..498dfff60d2 100644 --- a/src/plots/cartesian/select.js +++ b/src/plots/cartesian/select.js @@ -404,6 +404,8 @@ function updateSelectedState(gd, searchTraces, eventData) { var len = items.length; var item0 = items[0]; var trace0 = item0.cd[0].trace; + var _module = item0._module; + var styleSelection = _module.styleOnSelect || _module.style; if(Registry.traceIs(trace0, 'regl')) { // plot regl traces per module @@ -411,11 +413,11 @@ function updateSelectedState(gd, searchTraces, eventData) { for(j = 0; j < len; j++) { cds[j] = items[j].cd; } - item0._module.style(gd, cds); + styleSelection(gd, cds); } else { // plot svg trace per trace for(j = 0; j < len; j++) { - item0._module.style(gd, items[j].cd); + styleSelection(gd, items[j].cd); } } } diff --git a/src/plots/plots.js b/src/plots/plots.js index d462c966dd0..020a6d346ac 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -819,7 +819,7 @@ plots.linkSubplots = function(newFullData, newFullLayout, oldFullData, oldFullLa } // Then look for a subplot with the counteraxis overlaying the anchor // If that fails just use the first subplot including this axis - if(!mainSubplotID || ids.indexOf(mainSubplotID) === -1) { + if(!mainSubplotID || !newSubplots[mainSubplotID]) { mainSubplotID = ''; for(j = 0; j < ids.length; j++) { id = ids[j]; diff --git a/src/traces/bar/index.js b/src/traces/bar/index.js index 1798b2a7c88..15c82c07a51 100644 --- a/src/traces/bar/index.js +++ b/src/traces/bar/index.js @@ -20,7 +20,8 @@ Bar.setPositions = require('./set_positions'); Bar.colorbar = require('../scatter/colorbar'); Bar.arraysToCalcdata = require('./arrays_to_calcdata'); Bar.plot = require('./plot'); -Bar.style = require('./style'); +Bar.style = require('./style').style; +Bar.styleOnSelect = require('./style').styleOnSelect; Bar.hoverPoints = require('./hover'); Bar.selectPoints = require('./select'); diff --git a/src/traces/bar/style.js b/src/traces/bar/style.js index 8dbb15b249c..af30dfc6c72 100644 --- a/src/traces/bar/style.js +++ b/src/traces/bar/style.js @@ -13,7 +13,7 @@ var d3 = require('d3'); var Drawing = require('../../components/drawing'); var Registry = require('../../registry'); -module.exports = function style(gd, cd) { +function style(gd, cd) { var s = cd ? cd[0].node3 : d3.select(gd).selectAll('g.trace.bars'); var barcount = s.size(); var fullLayout = gd._fullLayout; @@ -35,34 +35,52 @@ module.exports = function style(gd, cd) { s.selectAll('g.points').each(function(d) { var sel = d3.select(this); - var pts = sel.selectAll('path'); - var txs = sel.selectAll('text'); var trace = d[0].trace; + stylePoints(sel, trace, gd); + }); + + Registry.getComponentMethod('errorbars', 'style')(s); +} - Drawing.pointStyle(pts, trace, gd); - Drawing.selectedPointStyle(pts, trace); +function stylePoints(sel, trace, gd) { + var pts = sel.selectAll('path'); + var txs = sel.selectAll('text'); - txs.each(function(d) { - var tx = d3.select(this); - var textFont; + Drawing.pointStyle(pts, trace, gd); - if(tx.classed('bartext-inside')) { - textFont = trace.insidetextfont; - } else if(tx.classed('bartext-outside')) { - textFont = trace.outsidetextfont; - } - if(!textFont) textFont = trace.textfont; + txs.each(function(d) { + var tx = d3.select(this); + var textFont; - function cast(k) { - var cont = textFont[k]; - return Array.isArray(cont) ? cont[d.i] : cont; - } + if(tx.classed('bartext-inside')) { + textFont = trace.insidetextfont; + } else if(tx.classed('bartext-outside')) { + textFont = trace.outsidetextfont; + } + if(!textFont) textFont = trace.textfont; - Drawing.font(tx, cast('family'), cast('size'), cast('color')); - }); + function cast(k) { + var cont = textFont[k]; + return Array.isArray(cont) ? cont[d.i] : cont; + } - Drawing.selectedTextStyle(txs, trace); + Drawing.font(tx, cast('family'), cast('size'), cast('color')); }); +} - Registry.getComponentMethod('errorbars', 'style')(s); +function styleOnSelect(gd, cd) { + var s = cd[0].node3; + var trace = cd[0].trace; + + if(trace.selectedpoints) { + Drawing.selectedPointStyle(s.selectAll('path'), trace); + Drawing.selectedTextStyle(s.selectAll('text'), trace); + } else { + stylePoints(s, trace, gd); + } +} + +module.exports = { + style: style, + styleOnSelect: styleOnSelect }; diff --git a/src/traces/box/index.js b/src/traces/box/index.js index 621f825f813..1abe340fbd8 100644 --- a/src/traces/box/index.js +++ b/src/traces/box/index.js @@ -17,7 +17,8 @@ Box.supplyLayoutDefaults = require('./layout_defaults').supplyLayoutDefaults; Box.calc = require('./calc'); Box.setPositions = require('./set_positions').setPositions; Box.plot = require('./plot').plot; -Box.style = require('./style'); +Box.style = require('./style').style; +Box.styleOnSelect = require('./style').styleOnSelect; Box.hoverPoints = require('./hover').hoverPoints; Box.selectPoints = require('./select'); diff --git a/src/traces/box/style.js b/src/traces/box/style.js index aef404372b7..1495fb8566f 100644 --- a/src/traces/box/style.js +++ b/src/traces/box/style.js @@ -12,7 +12,7 @@ var d3 = require('d3'); var Color = require('../../components/color'); var Drawing = require('../../components/drawing'); -module.exports = function style(gd, cd) { +function style(gd, cd) { var s = cd ? cd[0].node3 : d3.select(gd).selectAll('g.trace.boxes'); s.style('opacity', function(d) { return d[0].trace.opacity; }); @@ -50,7 +50,23 @@ module.exports = function style(gd, cd) { var pts = el.selectAll('path.point'); Drawing.pointStyle(pts, trace, gd); - Drawing.selectedPointStyle(pts, trace); } }); +} + +function styleOnSelect(gd, cd) { + var s = cd[0].node3; + var trace = cd[0].trace; + var pts = s.selectAll('path.point'); + + if(trace.selectedpoints) { + Drawing.selectedPointStyle(pts, trace); + } else { + Drawing.pointStyle(pts, trace, gd); + } +} + +module.exports = { + style: style, + styleOnSelect: styleOnSelect }; diff --git a/src/traces/candlestick/index.js b/src/traces/candlestick/index.js index 12911def4df..ff568688ebb 100644 --- a/src/traces/candlestick/index.js +++ b/src/traces/candlestick/index.js @@ -37,7 +37,7 @@ module.exports = { calc: require('./calc'), plot: require('../box/plot').plot, layerName: 'boxlayer', - style: require('../box/style'), + style: require('../box/style').style, hoverPoints: require('../ohlc/hover'), selectPoints: require('../ohlc/select') }; diff --git a/src/traces/choropleth/index.js b/src/traces/choropleth/index.js index 08e1f94aa06..bf6bb963197 100644 --- a/src/traces/choropleth/index.js +++ b/src/traces/choropleth/index.js @@ -16,7 +16,8 @@ Choropleth.supplyDefaults = require('./defaults'); Choropleth.colorbar = require('../heatmap/colorbar'); Choropleth.calc = require('./calc'); Choropleth.plot = require('./plot'); -Choropleth.style = require('./style'); +Choropleth.style = require('./style').style; +Choropleth.styleOnSelect = require('./style').styleOnSelect; Choropleth.hoverPoints = require('./hover'); Choropleth.eventData = require('./event_data'); Choropleth.selectPoints = require('./select'); diff --git a/src/traces/choropleth/plot.js b/src/traces/choropleth/plot.js index e0579accf3a..fc0f0d176ac 100644 --- a/src/traces/choropleth/plot.js +++ b/src/traces/choropleth/plot.js @@ -15,7 +15,7 @@ var polygon = require('../../lib/polygon'); var getTopojsonFeatures = require('../../lib/topojson_utils').getTopojsonFeatures; var locationToFeature = require('../../lib/geo_location_utils').locationToFeature; -var style = require('./style'); +var style = require('./style').style; module.exports = function plot(gd, geo, calcData) { for(var i = 0; i < calcData.length; i++) { diff --git a/src/traces/choropleth/style.js b/src/traces/choropleth/style.js index 67ce075cb4e..6a07afa7b17 100644 --- a/src/traces/choropleth/style.js +++ b/src/traces/choropleth/style.js @@ -13,9 +13,9 @@ var Color = require('../../components/color'); var Drawing = require('../../components/drawing'); var Colorscale = require('../../components/colorscale'); -module.exports = function style(gd, calcTrace) { +function style(gd, calcTrace) { if(calcTrace) styleTrace(gd, calcTrace); -}; +} function styleTrace(gd, calcTrace) { var trace = calcTrace[0].trace; @@ -40,5 +40,21 @@ function styleTrace(gd, calcTrace) { .style('opacity', marker.opacity); }); - Drawing.selectedPointStyle(locs, trace); + Drawing.selectedPointStyle(locs, trace, gd); } + +function styleOnSelect(gd, calcTrace) { + var s = calcTrace[0].node3; + var trace = calcTrace[0].trace; + + if(trace.selectedpoints) { + Drawing.selectedPointStyle(s.selectAll('.choroplethlocation'), trace, gd); + } else { + styleTrace(gd, calcTrace); + } +} + +module.exports = { + style: style, + styleOnSelect: styleOnSelect +}; diff --git a/src/traces/histogram/index.js b/src/traces/histogram/index.js index 563689b2a4b..8ee3041badb 100644 --- a/src/traces/histogram/index.js +++ b/src/traces/histogram/index.js @@ -33,7 +33,8 @@ Histogram.calc = require('./calc'); Histogram.setPositions = require('../bar/set_positions'); Histogram.plot = require('../bar/plot'); Histogram.layerName = 'barlayer'; -Histogram.style = require('../bar/style'); +Histogram.style = require('../bar/style').style; +Histogram.styleOnSelect = require('../bar/style').styleOnSelect; Histogram.colorbar = require('../scatter/colorbar'); Histogram.hoverPoints = require('./hover'); Histogram.selectPoints = require('../bar/select'); diff --git a/src/traces/scatter/index.js b/src/traces/scatter/index.js index 3808dcf7628..fb8b6c7cf91 100644 --- a/src/traces/scatter/index.js +++ b/src/traces/scatter/index.js @@ -27,6 +27,7 @@ Scatter.arraysToCalcdata = require('./arrays_to_calcdata'); Scatter.plot = require('./plot'); Scatter.colorbar = require('./colorbar'); Scatter.style = require('./style').style; +Scatter.styleOnSelect = require('./style').styleOnSelect; Scatter.hoverPoints = require('./hover'); Scatter.selectPoints = require('./select'); Scatter.animatable = true; diff --git a/src/traces/scatter/plot.js b/src/traces/scatter/plot.js index d75719bfa98..b0e14b2964a 100644 --- a/src/traces/scatter/plot.js +++ b/src/traces/scatter/plot.js @@ -407,14 +407,14 @@ function plotOne(gd, idx, plotinfo, cdscatter, cdscatterAll, element, transition function makePoints(d) { var join, selection, hasNode; - var trace = d[0].trace, - s = d3.select(this), - showMarkers = subTypes.hasMarkers(trace), - showText = subTypes.hasText(trace); + var trace = d[0].trace; + var s = d3.select(this); + var showMarkers = subTypes.hasMarkers(trace); + var showText = subTypes.hasText(trace); - var keyFunc = getKeyFunc(trace), - markerFilter = hideFilter, - textFilter = hideFilter; + var keyFunc = getKeyFunc(trace); + var markerFilter = hideFilter; + var textFilter = hideFilter; if(showMarkers) { markerFilter = (trace.marker.maxdisplayed || trace._needsCull) ? visFilter : Lib.identity; @@ -442,18 +442,20 @@ function plotOne(gd, idx, plotinfo, cdscatter, cdscatterAll, element, transition .style('opacity', 1); } - var markerScale = showMarkers && Drawing.tryColorscale(trace.marker, ''); - var lineScale = showMarkers && Drawing.tryColorscale(trace.marker, 'line'); - join.order(); + var styleFns; + if(showMarkers) { + styleFns = Drawing.makePointStyleFns(trace); + } + join.each(function(d) { var el = d3.select(this); var sel = transition(el); hasNode = Drawing.translatePoint(d, sel, xa, ya); if(hasNode) { - Drawing.singlePointStyle(d, sel, trace, markerScale, lineScale, gd); + Drawing.singlePointStyle(d, sel, trace, styleFns, gd); if(plotinfo.layerClipId) { Drawing.hideOutsideRangePoint(d, sel, xa, ya, trace.xcalendar, trace.ycalendar); diff --git a/src/traces/scatter/style.js b/src/traces/scatter/style.js index 67becae9b91..b9090261e93 100644 --- a/src/traces/scatter/style.js +++ b/src/traces/scatter/style.js @@ -36,16 +36,24 @@ function style(gd, cd) { } function stylePoints(sel, trace, gd) { - var pts = sel.selectAll('path.point'); - var txs = sel.selectAll('text'); + Drawing.pointStyle(sel.selectAll('path.point'), trace, gd); + Drawing.textPointStyle(sel.selectAll('text'), trace, gd); +} + +function styleOnSelect(gd, cd) { + var s = cd[0].node3; + var trace = cd[0].trace; - Drawing.pointStyle(pts, trace, gd); - Drawing.textPointStyle(txs, trace, gd); - Drawing.selectedPointStyle(pts, trace); - Drawing.selectedTextStyle(txs, trace); + if(trace.selectedpoints) { + Drawing.selectedPointStyle(s.selectAll('path.point'), trace); + Drawing.selectedTextStyle(s.selectAll('text'), trace); + } else { + stylePoints(s, trace, gd); + } } module.exports = { style: style, - stylePoints: stylePoints + stylePoints: stylePoints, + styleOnSelect: styleOnSelect }; diff --git a/src/traces/scattercarpet/index.js b/src/traces/scattercarpet/index.js index c8864a6c45d..372117fd390 100644 --- a/src/traces/scattercarpet/index.js +++ b/src/traces/scattercarpet/index.js @@ -16,6 +16,7 @@ ScatterCarpet.colorbar = require('../scatter/colorbar'); ScatterCarpet.calc = require('./calc'); ScatterCarpet.plot = require('./plot'); ScatterCarpet.style = require('../scatter/style').style; +ScatterCarpet.styleOnSelect = require('../scatter/style').styleOnSelect; ScatterCarpet.hoverPoints = require('./hover'); ScatterCarpet.selectPoints = require('../scatter/select'); ScatterCarpet.eventData = require('./event_data'); diff --git a/src/traces/scattergeo/index.js b/src/traces/scattergeo/index.js index 3176f607f83..a176bc39c30 100644 --- a/src/traces/scattergeo/index.js +++ b/src/traces/scattergeo/index.js @@ -17,6 +17,7 @@ ScatterGeo.colorbar = require('../scatter/colorbar'); ScatterGeo.calc = require('./calc'); ScatterGeo.plot = require('./plot'); ScatterGeo.style = require('./style'); +ScatterGeo.styleOnSelect = require('../scatter/style').styleOnSelect; ScatterGeo.hoverPoints = require('./hover'); ScatterGeo.eventData = require('./event_data'); ScatterGeo.selectPoints = require('./select'); diff --git a/src/traces/scattermapbox/convert.js b/src/traces/scattermapbox/convert.js index 3cbe1884488..0372ecc874f 100644 --- a/src/traces/scattermapbox/convert.js +++ b/src/traces/scattermapbox/convert.js @@ -205,33 +205,27 @@ function makeCircleOpts(calcTrace) { for(i = 0; i < features.length; i++) { var d = features[i].properties; - var mo2 = fns.opacityFn(d); - if(mo2 !== undefined) d.mo = addTraceOpacity(mo2); - else if(d.mo === undefined) d.mo = addTraceOpacity(marker.opacity); - - if(fns.colorFn) { - var mc2 = fns.colorFn(d); - if(mc2) d.mcc = mc2; - else if(!d.mcc) d.mcc = marker.color; + if(fns.selectedOpacityFn) { + d.mo = addTraceOpacity(fns.selectedOpacityFn(d)); } - - if(fns.sizeFn) { - var mrc2 = fns.sizeFn(d); - if(mrc2 !== undefined) d.mrc = mrc2; - else if(d.mrc === undefined) d.mrc = size2radius(marker.size); + if(fns.selectedColorFn) { + d.mcc = fns.selectedColorFn(d); + } + if(fns.selectedSizeFn) { + d.mrc = fns.selectedSizeFn(d); } } } return { geojson: {type: 'FeatureCollection', features: features}, - mcc: arrayColor || (fns && fns.colorFn) ? + mcc: arrayColor || (fns && fns.selectedColorFn) ? {type: 'identity', property: 'mcc'} : marker.color, - mrc: arraySize || (fns && fns.sizeFn) ? + mrc: arraySize || (fns && fns.selectedSizeFn) ? {type: 'identity', property: 'mrc'} : size2radius(marker.size), - mo: arrayOpacity || selectedpoints ? + mo: arrayOpacity || (fns && fns.selectedOpacityFn) ? {type: 'identity', property: 'mo'} : addTraceOpacity(marker.opacity) }; diff --git a/src/traces/scatterternary/index.js b/src/traces/scatterternary/index.js index e6670b95a45..2b3f1fbe5bc 100644 --- a/src/traces/scatterternary/index.js +++ b/src/traces/scatterternary/index.js @@ -16,6 +16,7 @@ ScatterTernary.colorbar = require('../scatter/colorbar'); ScatterTernary.calc = require('./calc'); ScatterTernary.plot = require('./plot'); ScatterTernary.style = require('../scatter/style').style; +ScatterTernary.styleOnSelect = require('../scatter/style').styleOnSelect; ScatterTernary.hoverPoints = require('./hover'); ScatterTernary.selectPoints = require('../scatter/select'); ScatterTernary.eventData = require('./event_data'); diff --git a/src/traces/violin/index.js b/src/traces/violin/index.js index ac0d06ac031..bbab55af0da 100644 --- a/src/traces/violin/index.js +++ b/src/traces/violin/index.js @@ -17,6 +17,7 @@ module.exports = { setPositions: require('./set_positions'), plot: require('./plot'), style: require('./style'), + styleOnSelect: require('../scatter/style').styleOnSelect, hoverPoints: require('./hover'), selectPoints: require('../box/select'), diff --git a/test/jasmine/tests/scatter_test.js b/test/jasmine/tests/scatter_test.js index 4d618341bc9..d1c6c30dbff 100644 --- a/test/jasmine/tests/scatter_test.js +++ b/test/jasmine/tests/scatter_test.js @@ -909,25 +909,36 @@ describe('Test Scatter.style', function() { afterEach(destroyGraphDiv); + function assertPts(attr, getterFn, expectation, msg2) { + var selector = attr.indexOf('textfont') === 0 ? '.textpoint > text' : '.point'; + + d3.select(gd).selectAll('.trace').each(function(_, i) { + var pts = d3.select(this).selectAll(selector); + var expi = expectation[i]; + + expect(pts.size()) + .toBe(expi.length, '# of pts for trace ' + i + msg2); + + pts.each(function(_, j) { + var msg3 = ' for pt ' + j + ' in trace ' + i + msg2; + expect(getterFn(this)).toBe(expi[j], attr + msg3); + }); + }); + } + function makeCheckFn(attr, getterFn) { return function(update, expectation, msg) { - var msg2 = ' (' + msg + ')'; var promise = update ? Plotly.restyle(gd, update) : Promise.resolve(); - var selector = attr.indexOf('textfont') === 0 ? '.textpoint > text' : '.point'; return promise.then(function() { - d3.selectAll('.trace').each(function(_, i) { - var pts = d3.select(this).selectAll(selector); - var expi = expectation[i]; - - expect(pts.size()) - .toBe(expi.length, '# of pts for trace ' + i + msg2); + assertPts(attr, getterFn, expectation, ' (' + msg + ' after restyle)'); - pts.each(function(_, j) { - var msg3 = ' for pt ' + j + ' in trace ' + i + msg2; - expect(getterFn(this)).toBe(expi[j], attr + msg3); - }); + // make sure styleOnSelect (called during selection) + // gives same results as restyle + gd.calcdata.forEach(function(cd) { + Scatter.styleOnSelect(gd, cd); }); + assertPts(attr, getterFn, expectation, ' (' + msg + ' via Scatter.styleOnSelect)'); }); }; } diff --git a/test/jasmine/tests/select_test.js b/test/jasmine/tests/select_test.js index 81931219d6d..1a0e6323e97 100644 --- a/test/jasmine/tests/select_test.js +++ b/test/jasmine/tests/select_test.js @@ -1485,6 +1485,55 @@ describe('@flaky Test select box and lasso per trace:', function() { .catch(failTest) .then(done); }); + + it('should work on scatter/bar traces with text nodes', function(done) { + var assertSelectedPoints = makeAssertSelectedPoints(); + + function assertFillOpacity(exp) { + var txtPts = d3.select(gd).select('g.plot').selectAll('text'); + + expect(txtPts.size()).toBe(exp.length, '# of text nodes'); + + txtPts.each(function(_, i) { + var act = Number(this.style['fill-opacity']); + expect(act).toBe(exp[i], 'node ' + i + ' fill opacity'); + }); + } + + Plotly.plot(gd, [{ + mode: 'markers+text', + x: [1, 2, 3], + y: [1, 2, 1], + text: ['a', 'b', 'c'] + }, { + type: 'bar', + x: [1, 2, 3], + y: [1, 2, 1], + text: ['A', 'B', 'C'], + textposition: 'outside' + }], { + dragmode: 'select', + showlegend: false, + width: 400, + height: 400, + margin: {l: 0, t: 0, r: 0, b: 0} + }) + .then(function() { + return _run( + [[10, 10], [100, 300]], + function() { + assertSelectedPoints({0: [0], 1: [0]}); + assertFillOpacity([1, 0.2, 0.2, 1, 0.2, 0.2]); + }, + null, BOXEVENTS, 'selecting first scatter/bar text nodes' + ); + }) + .then(function() { + assertFillOpacity([1, 1, 1, 1, 1, 1]); + }) + .catch(failTest) + .then(done); + }); }); describe('Test that selections persist:', function() {