diff --git a/src/components/fx/helpers.js b/src/components/fx/helpers.js
index 22caa8847d0..fd005f52824 100644
--- a/src/components/fx/helpers.js
+++ b/src/components/fx/helpers.js
@@ -89,7 +89,8 @@ exports.quadrature = function quadrature(dx, dy) {
*
* @param {object} pointData : point data object (gets mutated here)
* @param {object} trace : full trace object
- * @param {number} pointNumber : point number
+ * @param {number|Array(number)} pointNumber : point number. May be a length-2 array
+ * [row, col] to dig into 2D arrays
*/
exports.appendArrayPointValue = function(pointData, trace, pointNumber) {
var arrayAttrs = trace._arrayAttrs;
@@ -100,22 +101,68 @@ exports.appendArrayPointValue = function(pointData, trace, pointNumber) {
for(var i = 0; i < arrayAttrs.length; i++) {
var astr = arrayAttrs[i];
- var key;
+ var key = getPointKey(astr);
- if(astr === 'ids') key = 'id';
- else if(astr === 'locations') key = 'location';
- else key = astr;
+ if(pointData[key] === undefined) {
+ var val = Lib.nestedProperty(trace, astr).get();
+ var pointVal = getPointData(val, pointNumber);
+
+ if(pointVal !== undefined) pointData[key] = pointVal;
+ }
+ }
+};
+
+/**
+ * Appends values inside array attributes corresponding to given point number array
+ * For use when pointData references a plot entity that arose (or potentially arose)
+ * from multiple points in the input data
+ *
+ * @param {object} pointData : point data object (gets mutated here)
+ * @param {object} trace : full trace object
+ * @param {Array(number)|Array(Array(number))} pointNumbers : Array of point numbers.
+ * Each entry in the array may itself be a length-2 array [row, col] to dig into 2D arrays
+ */
+exports.appendArrayMultiPointValues = function(pointData, trace, pointNumbers) {
+ var arrayAttrs = trace._arrayAttrs;
+
+ if(!arrayAttrs) {
+ return;
+ }
+
+ for(var i = 0; i < arrayAttrs.length; i++) {
+ var astr = arrayAttrs[i];
+ var key = getPointKey(astr);
if(pointData[key] === undefined) {
var val = Lib.nestedProperty(trace, astr).get();
+ var keyVal = new Array(pointNumbers.length);
- if(Array.isArray(pointNumber)) {
- if(Array.isArray(val) && Array.isArray(val[pointNumber[0]])) {
- pointData[key] = val[pointNumber[0]][pointNumber[1]];
- }
- } else {
- pointData[key] = val[pointNumber];
+ for(var j = 0; j < pointNumbers.length; j++) {
+ keyVal[j] = getPointData(val, pointNumbers[j]);
}
+ pointData[key] = keyVal;
}
}
};
+
+var pointKeyMap = {
+ ids: 'id',
+ locations: 'location',
+ labels: 'label',
+ values: 'value',
+ 'marker.colors': 'color'
+};
+
+function getPointKey(astr) {
+ return pointKeyMap[astr] || astr;
+}
+
+function getPointData(val, pointNumber) {
+ if(Array.isArray(pointNumber)) {
+ if(Array.isArray(val) && Array.isArray(val[pointNumber[0]])) {
+ return val[pointNumber[0]][pointNumber[1]];
+ }
+ } else {
+ return val[pointNumber];
+ }
+}
diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js
index 6f32c7b3f78..78a2ebf36d5 100644
--- a/src/components/fx/hover.js
+++ b/src/components/fx/hover.js
@@ -155,14 +155,6 @@ exports.loneHover = function loneHover(hoverItem, opts) {
// The actual implementation is here:
function _hover(gd, evt, subplot, noHoverEvent) {
- if((subplot === 'pie' || subplot === 'sankey') && !noHoverEvent) {
- gd.emit('plotly_hover', {
- event: evt.originalEvent,
- points: [evt]
- });
- return;
- }
-
if(!subplot) subplot = 'xy';
// if the user passed in an array of subplots,
diff --git a/src/traces/pie/attributes.js b/src/traces/pie/attributes.js
index 5adbd886ca4..cab596b95be 100644
--- a/src/traces/pie/attributes.js
+++ b/src/traces/pie/attributes.js
@@ -24,7 +24,13 @@ module.exports = {
labels: {
valType: 'data_array',
editType: 'calc',
- description: 'Sets the sector labels.'
+ description: [
+ 'Sets the sector labels.',
+ 'If `labels` entries are duplicated, we sum associated `values`',
+ 'or simply count occurrences if `values` is not provided.',
+ 'For other array attributes (including color) we use the first',
+ 'non-empty entry among all occurrences of the label.'
+ ].join(' ')
},
// equivalent of x0 and dx, if label is missing
label0: {
@@ -50,7 +56,10 @@ module.exports = {
values: {
valType: 'data_array',
editType: 'calc',
- description: 'Sets the values of the sectors of this pie chart.'
+ description: [
+ 'Sets the values of the sectors of this pie chart.',
+ 'If omitted, we count occurrences of each label.'
+ ].join(' ')
},
marker: {
diff --git a/src/traces/pie/calc.js b/src/traces/pie/calc.js
index 7fd82028790..bb425b1a606 100644
--- a/src/traces/pie/calc.js
+++ b/src/traces/pie/calc.js
@@ -15,21 +15,18 @@ var Color = require('../../components/color');
var helpers = require('./helpers');
module.exports = function calc(gd, trace) {
- var vals = trace.values,
- labels = trace.labels,
- cd = [],
- fullLayout = gd._fullLayout,
- colorMap = fullLayout._piecolormap,
- allThisTraceLabels = {},
- needDefaults = false,
- vTotal = 0,
- hiddenLabels = fullLayout.hiddenlabels || [],
- i,
- v,
- label,
- color,
- hidden,
- pt;
+ var vals = trace.values;
+ var hasVals = Array.isArray(vals) && vals.length;
+ var labels = trace.labels;
+ var colors = trace.marker.colors;
+ var cd = [];
+ var fullLayout = gd._fullLayout;
+ var colorMap = fullLayout._piecolormap;
+ var allThisTraceLabels = {};
+ var vTotal = 0;
+ var hiddenLabels = fullLayout.hiddenlabels || [];
+
+ var i, v, label, hidden, pt;
if(trace.dlabel) {
labels = new Array(vals.length);
@@ -38,46 +35,60 @@ module.exports = function calc(gd, trace) {
}
}
- for(i = 0; i < vals.length; i++) {
- v = vals[i];
- if(!isNumeric(v)) continue;
- v = +v;
- if(v < 0) continue;
+ function pullColor(color, label) {
+ if(!color) return false;
+
+ color = tinycolor(color);
+ if(!color.isValid()) return false;
+
+ color = Color.addOpacity(color, color.getAlpha());
+ if(!colorMap[label]) colorMap[label] = color;
+
+ return color;
+ }
+
+ var seriesLen = (hasVals ? vals : labels).length;
+
+ for(i = 0; i < seriesLen; i++) {
+ if(hasVals) {
+ v = vals[i];
+ if(!isNumeric(v)) continue;
+ v = +v;
+ if(v < 0) continue;
+ }
+ else v = 1;
label = labels[i];
if(label === undefined || label === '') label = i;
label = String(label);
- // only take the first occurrence of any given label.
- // TODO: perhaps (optionally?) sum values for a repeated label?
- if(allThisTraceLabels[label] === undefined) allThisTraceLabels[label] = true;
- else continue;
-
- color = tinycolor(trace.marker.colors[i]);
- if(color.isValid()) {
- color = Color.addOpacity(color, color.getAlpha());
- if(!colorMap[label]) {
- colorMap[label] = color;
- }
- }
- // have we seen this label and assigned a color to it in a previous trace?
- else if(colorMap[label]) color = colorMap[label];
- // color needs a default - mark it false, come back after sorting
- else {
- color = false;
- needDefaults = true;
- }
- hidden = hiddenLabels.indexOf(label) !== -1;
+ var thisLabelIndex = allThisTraceLabels[label];
+ if(thisLabelIndex === undefined) {
+ allThisTraceLabels[label] = cd.length;
- if(!hidden) vTotal += v;
+ hidden = hiddenLabels.indexOf(label) !== -1;
- cd.push({
- v: v,
- label: label,
- color: color,
- i: i,
- hidden: hidden
- });
+ if(!hidden) vTotal += v;
+
+ cd.push({
+ v: v,
+ label: label,
+ color: pullColor(colors[i]),
+ i: i,
+ pts: [i],
+ hidden: hidden
+ });
+ }
+ else {
+ pt = cd[thisLabelIndex];
+ pt.v += v;
+ pt.pts.push(i);
+ if(!pt.hidden) vTotal += v;
+
+ if(pt.color === false && colors[i]) {
+ pt.color = pullColor(colors[i], label);
+ }
+ }
}
if(trace.sort) cd.sort(function(a, b) { return b.v - a.v; });
@@ -88,10 +99,14 @@ module.exports = function calc(gd, trace) {
* in the order slices will be displayed
*/
- if(needDefaults) {
- for(i = 0; i < cd.length; i++) {
- pt = cd[i];
- if(pt.color === false) {
+ for(i = 0; i < cd.length; i++) {
+ pt = cd[i];
+ if(pt.color === false) {
+ // have we seen this label and assigned a color to it in a previous trace?
+ if(colorMap[pt.label]) {
+ pt.color = colorMap[pt.label];
+ }
+ else {
colorMap[pt.label] = pt.color = nextDefaultColor(fullLayout._piedefaultcolorcount);
fullLayout._piedefaultcolorcount++;
}
@@ -103,17 +118,21 @@ module.exports = function calc(gd, trace) {
// now insert text
if(trace.textinfo && trace.textinfo !== 'none') {
- var hasLabel = trace.textinfo.indexOf('label') !== -1,
- hasText = trace.textinfo.indexOf('text') !== -1,
- hasValue = trace.textinfo.indexOf('value') !== -1,
- hasPercent = trace.textinfo.indexOf('percent') !== -1,
- separators = fullLayout.separators,
- thisText;
+ var hasLabel = trace.textinfo.indexOf('label') !== -1;
+ var hasText = trace.textinfo.indexOf('text') !== -1;
+ var hasValue = trace.textinfo.indexOf('value') !== -1;
+ var hasPercent = trace.textinfo.indexOf('percent') !== -1;
+ var separators = fullLayout.separators;
+
+ var thisText;
for(i = 0; i < cd.length; i++) {
pt = cd[i];
thisText = hasLabel ? [pt.label] : [];
- if(hasText && trace.text[pt.i]) thisText.push(trace.text[pt.i]);
+ if(hasText) {
+ var texti = helpers.getFirstFilled(trace.text, pt.pts);
+ if(texti) thisText.push(texti);
+ }
if(hasValue) thisText.push(helpers.formatPieValue(pt.v, separators));
if(hasPercent) thisText.push(helpers.formatPiePercent(pt.v / vTotal, separators));
pt.text = thisText.join('
');
diff --git a/src/traces/pie/defaults.js b/src/traces/pie/defaults.js
index 7984ed7ce73..4cd2f99eb3e 100644
--- a/src/traces/pie/defaults.js
+++ b/src/traces/pie/defaults.js
@@ -19,13 +19,14 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
var coerceFont = Lib.coerceFont;
var vals = coerce('values');
- if(!Array.isArray(vals) || !vals.length) {
- traceOut.visible = false;
- return;
- }
-
var labels = coerce('labels');
if(!Array.isArray(labels)) {
+ if(!Array.isArray(vals) || !vals.length) {
+ // must have at least one of vals or labels
+ traceOut.visible = false;
+ return;
+ }
+
coerce('label0');
coerce('dlabel');
}
@@ -34,14 +35,10 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
if(lineWidth) coerce('marker.line.color');
var colors = coerce('marker.colors');
- if(!Array.isArray(colors)) traceOut.marker.colors = []; // later this will get padded with default colors
+ if(!Array.isArray(colors)) traceOut.marker.colors = [];
coerce('scalegroup');
- // TODO: tilt, depth, and hole all need to be coerced to the same values within a scaleegroup
- // (ideally actually, depth would get set the same *after* scaling, ie the same absolute depth)
- // and if colors aren't specified we should match these up - potentially even if separate pies
- // are NOT in the same sharegroup
-
+ // TODO: hole needs to be coerced to the same value within a scaleegroup
var textData = coerce('text');
var textInfo = coerce('textinfo', Array.isArray(textData) ? 'text+percent' : 'percent');
@@ -63,14 +60,6 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
coerce('domain.x');
coerce('domain.y');
- // 3D attributes commented out until I finish them in a later PR
- // var tilt = coerce('tilt');
- // if(tilt) {
- // coerce('tiltaxis');
- // coerce('depth');
- // coerce('shading');
- // }
-
coerce('hole');
coerce('sort');
diff --git a/src/traces/pie/event_data.js b/src/traces/pie/event_data.js
new file mode 100644
index 00000000000..d987f47e8b3
--- /dev/null
+++ b/src/traces/pie/event_data.js
@@ -0,0 +1,41 @@
+/**
+* Copyright 2012-2017, 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 appendArrayMultiPointValues = require('../../components/fx/helpers').appendArrayMultiPointValues;
+
+
+// Note: like other eventData routines, this creates the data for hover/unhover/click events
+// but it has a different API and goes through a totally different pathway.
+// So to ensure it doesn't get misused, it's not attached to the Pie module.
+module.exports = function eventData(pt, trace) {
+ var out = {
+ curveNumber: trace.index,
+ pointNumbers: pt.pts,
+ data: trace._input,
+ fullData: trace,
+ label: pt.label,
+ color: pt.color,
+ value: pt.v,
+
+ // pt.v (and pt.i below) for backward compatibility
+ v: pt.v
+ };
+
+ // Only include pointNumber if it's unambiguous
+ if(pt.pts.length === 1) out.pointNumber = out.i = pt.pts[0];
+
+ // Add extra data arrays to the output
+ // notice that this is the multi-point version ('s' on the end!)
+ // so added data will be arrays matching the pointNumbers array.
+ appendArrayMultiPointValues(out, trace, pt.pts);
+
+ return out;
+};
diff --git a/src/traces/pie/helpers.js b/src/traces/pie/helpers.js
index ac19f6f6c1e..360a695786e 100644
--- a/src/traces/pie/helpers.js
+++ b/src/traces/pie/helpers.js
@@ -25,3 +25,16 @@ exports.formatPieValue = function formatPieValue(v, separators) {
}
return Lib.numSeparate(vRounded, separators);
};
+
+exports.getFirstFilled = function getFirstFilled(array, indices) {
+ if(!Array.isArray(array)) return;
+ for(var i = 0; i < indices.length; i++) {
+ var v = array[indices[i]];
+ if(v || v === 0) return v;
+ }
+};
+
+exports.castOption = function castOption(item, indices) {
+ if(Array.isArray(item)) return exports.getFirstFilled(item, indices);
+ else if(item) return item;
+};
diff --git a/src/traces/pie/plot.js b/src/traces/pie/plot.js
index 026dad443c7..b083188c283 100644
--- a/src/traces/pie/plot.js
+++ b/src/traces/pie/plot.js
@@ -16,6 +16,7 @@ var Drawing = require('../../components/drawing');
var svgTextUtils = require('../../lib/svg_text_utils');
var helpers = require('./helpers');
+var eventData = require('./event_data');
module.exports = function plot(gd, cdpie) {
var fullLayout = gd._fullLayout;
@@ -34,31 +35,13 @@ module.exports = function plot(gd, cdpie) {
pieGroups.order();
pieGroups.each(function(cd) {
- var pieGroup = d3.select(this),
- cd0 = cd[0],
- trace = cd0.trace,
- tiltRads = 0, // trace.tilt * Math.PI / 180,
- depthLength = (trace.depth||0) * cd0.r * Math.sin(tiltRads) / 2,
- tiltAxis = trace.tiltaxis || 0,
- tiltAxisRads = tiltAxis * Math.PI / 180,
- depthVector = [
- depthLength * Math.sin(tiltAxisRads),
- depthLength * Math.cos(tiltAxisRads)
- ],
- rSmall = cd0.r * Math.cos(tiltRads);
-
- var pieParts = pieGroup.selectAll('g.part')
- .data(trace.tilt ? ['top', 'sides'] : ['top']);
-
- pieParts.enter().append('g').attr('class', function(d) {
- return d + ' part';
- });
- pieParts.exit().remove();
- pieParts.order();
+ var pieGroup = d3.select(this);
+ var cd0 = cd[0];
+ var trace = cd0.trace;
setCoords(cd);
- pieGroup.selectAll('.top').each(function() {
+ pieGroup.each(function() {
var slices = d3.select(this).selectAll('g.slice').data(cd);
slices.enter().append('g')
@@ -66,10 +49,10 @@ module.exports = function plot(gd, cdpie) {
slices.exit().remove();
var quadrants = [
- [[], []], // y<0: x<0, x>=0
- [[], []] // y>=0: x<0, x>=0
- ],
- hasOutsideText = false;
+ [[], []], // y<0: x<0, x>=0
+ [[], []] // y>=0: x<0, x>=0
+ ];
+ var hasOutsideText = false;
slices.each(function(pt) {
if(pt.hidden) {
@@ -83,90 +66,116 @@ module.exports = function plot(gd, cdpie) {
quadrants[pt.pxmid[1] < 0 ? 0 : 1][pt.pxmid[0] < 0 ? 0 : 1].push(pt);
- var cx = cd0.cx + depthVector[0],
- cy = cd0.cy + depthVector[1],
- sliceTop = d3.select(this),
- slicePath = sliceTop.selectAll('path.surface').data([pt]),
- hasHoverData = false;
-
- function handleMouseOver(evt) {
- evt.originalEvent = d3.event;
-
+ var cx = cd0.cx;
+ var cy = cd0.cy;
+ var sliceTop = d3.select(this);
+ var slicePath = sliceTop.selectAll('path.surface').data([pt]);
+
+ // hover state vars
+ // have we drawn a hover label, so it should be cleared later
+ var hasHoverLabel = false;
+ // have we emitted a hover event, so later an unhover event should be emitted
+ // note that click events do not depend on this - you can still get them
+ // with hovermode: false or if you were earlier dragging, then clicked
+ // in the same slice that you moused up in
+ var hasHoverEvent = false;
+
+ function handleMouseOver() {
// in case fullLayout or fullData has changed without a replot
var fullLayout2 = gd._fullLayout;
var trace2 = gd._fullData[trace.index];
- var hoverinfo = Fx.castHoverinfo(trace2, fullLayout2, pt.i);
+
+ if(gd._dragging || fullLayout2.hovermode === false) return;
+
+ var hoverinfo = trace2.hoverinfo;
+ if(Array.isArray(hoverinfo)) {
+ // super hacky: we need to pull out the *first* hoverinfo from
+ // pt.pts, then put it back into an array in a dummy trace
+ // and call castHoverinfo on that.
+ // TODO: do we want to have Fx.castHoverinfo somehow handle this?
+ // it already takes an array for index, for 2D, so this seems tricky.
+ hoverinfo = Fx.castHoverinfo({
+ hoverinfo: [helpers.castOption(hoverinfo, pt.pts)],
+ _module: trace._module
+ }, fullLayout2, 0);
+ }
if(hoverinfo === 'all') hoverinfo = 'label+text+value+percent+name';
// in case we dragged over the pie from another subplot,
// or if hover is turned off
- if(gd._dragging || fullLayout2.hovermode === false ||
- hoverinfo === 'none' || hoverinfo === 'skip' || !hoverinfo) {
- Fx.hover(gd, evt, 'pie');
- return;
- }
-
- var rInscribed = getInscribedRadiusFraction(pt, cd0),
- hoverCenterX = cx + pt.pxmid[0] * (1 - rInscribed),
- hoverCenterY = cy + pt.pxmid[1] * (1 - rInscribed),
- separators = fullLayout.separators,
- thisText = [];
-
- if(hoverinfo.indexOf('label') !== -1) thisText.push(pt.label);
- if(hoverinfo.indexOf('text') !== -1) {
- if(trace2.hovertext) {
- thisText.push(
- Array.isArray(trace2.hovertext) ?
- trace2.hovertext[pt.i] :
- trace2.hovertext
- );
- } else if(trace2.text && trace2.text[pt.i]) {
- thisText.push(trace2.text[pt.i]);
+ if(hoverinfo !== 'none' && hoverinfo !== 'skip' && hoverinfo) {
+ var rInscribed = getInscribedRadiusFraction(pt, cd0);
+ var hoverCenterX = cx + pt.pxmid[0] * (1 - rInscribed);
+ var hoverCenterY = cy + pt.pxmid[1] * (1 - rInscribed);
+ var separators = fullLayout.separators;
+ var thisText = [];
+
+ if(hoverinfo.indexOf('label') !== -1) thisText.push(pt.label);
+ if(hoverinfo.indexOf('text') !== -1) {
+ var texti = helpers.castOption(trace2.hovertext || trace2.text, pt.pts);
+ if(texti) thisText.push(texti);
}
- }
- if(hoverinfo.indexOf('value') !== -1) thisText.push(helpers.formatPieValue(pt.v, separators));
- if(hoverinfo.indexOf('percent') !== -1) thisText.push(helpers.formatPiePercent(pt.v / cd0.vTotal, separators));
-
- Fx.loneHover({
- x0: hoverCenterX - rInscribed * cd0.r,
- x1: hoverCenterX + rInscribed * cd0.r,
- y: hoverCenterY,
- text: thisText.join('
'),
- name: hoverinfo.indexOf('name') !== -1 ? trace2.name : undefined,
- idealAlign: pt.pxmid[0] < 0 ? 'left' : 'right',
- color: Fx.castHoverOption(trace, pt.i, 'bgcolor') || pt.color,
- borderColor: Fx.castHoverOption(trace, pt.i, 'bordercolor'),
- fontFamily: Fx.castHoverOption(trace, pt.i, 'font.family'),
- fontSize: Fx.castHoverOption(trace, pt.i, 'font.size'),
- fontColor: Fx.castHoverOption(trace, pt.i, 'font.color')
- }, {
- container: fullLayout2._hoverlayer.node(),
- outerContainer: fullLayout2._paper.node(),
- gd: gd
- });
+ if(hoverinfo.indexOf('value') !== -1) thisText.push(helpers.formatPieValue(pt.v, separators));
+ if(hoverinfo.indexOf('percent') !== -1) thisText.push(helpers.formatPiePercent(pt.v / cd0.vTotal, separators));
+
+ var hoverLabel = trace.hoverlabel;
+ var hoverFont = hoverLabel.font;
+
+ Fx.loneHover({
+ x0: hoverCenterX - rInscribed * cd0.r,
+ x1: hoverCenterX + rInscribed * cd0.r,
+ y: hoverCenterY,
+ text: thisText.join('
'),
+ name: hoverinfo.indexOf('name') !== -1 ? trace2.name : undefined,
+ idealAlign: pt.pxmid[0] < 0 ? 'left' : 'right',
+ color: helpers.castOption(hoverLabel.bgcolor, pt.pts) || pt.color,
+ borderColor: helpers.castOption(hoverLabel.bordercolor, pt.pts),
+ fontFamily: helpers.castOption(hoverFont.family, pt.pts),
+ fontSize: helpers.castOption(hoverFont.size, pt.pts),
+ fontColor: helpers.castOption(hoverFont.color, pt.pts)
+ }, {
+ container: fullLayout2._hoverlayer.node(),
+ outerContainer: fullLayout2._paper.node(),
+ gd: gd
+ });
- Fx.hover(gd, evt, 'pie');
+ hasHoverLabel = true;
+ }
- hasHoverData = true;
+ gd.emit('plotly_hover', {
+ points: [eventData(pt, trace2)],
+ event: d3.event
+ });
+ hasHoverEvent = true;
}
function handleMouseOut(evt) {
- evt.originalEvent = d3.event;
- gd.emit('plotly_unhover', {
- event: d3.event,
- points: [evt]
- });
+ var fullLayout2 = gd._fullLayout;
+ var trace2 = gd._fullData[trace.index];
- if(hasHoverData) {
- Fx.loneUnhover(fullLayout._hoverlayer.node());
- hasHoverData = false;
+ if(hasHoverEvent) {
+ evt.originalEvent = d3.event;
+ gd.emit('plotly_unhover', {
+ points: [eventData(pt, trace2)],
+ event: d3.event
+ });
+ hasHoverEvent = false;
+ }
+
+ if(hasHoverLabel) {
+ Fx.loneUnhover(fullLayout2._hoverlayer.node());
+ hasHoverLabel = false;
}
}
function handleClick() {
- gd._hoverdata = [pt];
- gd._hoverdata.trace = cd0.trace;
+ var fullLayout2 = gd._fullLayout;
+ var trace2 = gd._fullData[trace.index];
+
+ if(gd._dragging || fullLayout2.hovermode === false) return;
+
+ gd._hoverdata = [eventData(pt, trace2)];
Fx.click(gd, d3.event);
}
@@ -182,7 +191,7 @@ module.exports = function plot(gd, cdpie) {
.on('click', handleClick);
if(trace.pull) {
- var pull = +(Array.isArray(trace.pull) ? trace.pull[pt.i] : trace.pull) || 0;
+ var pull = +helpers.castOption(trace.pull, pt.pts) || 0;
if(pull > 0) {
cx += pull * pt.pxmid[0];
cy += pull * pt.pxmid[1];
@@ -193,7 +202,7 @@ module.exports = function plot(gd, cdpie) {
pt.cyFinal = cy;
function arc(start, finish, cw, scale) {
- return 'a' + (scale * cd0.r) + ',' + (scale * rSmall) + ' ' + tiltAxis + ' ' +
+ return 'a' + (scale * cd0.r) + ',' + (scale * cd0.r) + ' 0 ' +
pt.largeArc + (cw ? ' 1 ' : ' 0 ') +
(scale * (finish[0] - start[0])) + ',' + (scale * (finish[1] - start[1]));
}
@@ -233,9 +242,8 @@ module.exports = function plot(gd, cdpie) {
}
// add text
- var textPosition = Array.isArray(trace.textposition) ?
- trace.textposition[pt.i] : trace.textposition,
- sliceTextGroup = sliceTop.selectAll('g.slicetext')
+ var textPosition = helpers.castOption(trace.textposition, pt.pts);
+ var sliceTextGroup = sliceTop.selectAll('g.slicetext')
.data(pt.text && (textPosition !== 'none') ? [0] : []);
sliceTextGroup.enter().append('g')
@@ -262,9 +270,8 @@ module.exports = function plot(gd, cdpie) {
.call(svgTextUtils.convertToTspans, gd);
// position the text relative to the slice
- // TODO: so far this only accounts for flat
- var textBB = Drawing.bBox(sliceText.node()),
- transform;
+ var textBB = Drawing.bBox(sliceText.node());
+ var transform;
if(textPosition === 'outside') {
transform = transformOutsideText(textBB, pt);
@@ -280,8 +287,8 @@ module.exports = function plot(gd, cdpie) {
}
}
- var translateX = cx + pt.pxmid[0] * transform.rCenter + (transform.x || 0),
- translateY = cy + pt.pxmid[1] * transform.rCenter + (transform.y || 0);
+ var translateX = cx + pt.pxmid[0] * transform.rCenter + (transform.x || 0);
+ var translateY = cy + pt.pxmid[1] * transform.rCenter + (transform.y || 0);
// save some stuff to use later ensure no labels overlap
if(transform.outside) {
@@ -309,20 +316,21 @@ module.exports = function plot(gd, cdpie) {
slices.each(function(pt) {
if(pt.labelExtraX || pt.labelExtraY) {
// first move the text to its new location
- var sliceTop = d3.select(this),
- sliceText = sliceTop.select('g.slicetext text');
+ var sliceTop = d3.select(this);
+ var sliceText = sliceTop.select('g.slicetext text');
sliceText.attr('transform', 'translate(' + pt.labelExtraX + ',' + pt.labelExtraY + ')' +
sliceText.attr('transform'));
// then add a line to the new location
- var lineStartX = pt.cxFinal + pt.pxmid[0],
- lineStartY = pt.cyFinal + pt.pxmid[1],
- textLinePath = 'M' + lineStartX + ',' + lineStartY,
- finalX = (pt.yLabelMax - pt.yLabelMin) * (pt.pxmid[0] < 0 ? -1 : 1) / 4;
+ var lineStartX = pt.cxFinal + pt.pxmid[0];
+ var lineStartY = pt.cyFinal + pt.pxmid[1];
+ var textLinePath = 'M' + lineStartX + ',' + lineStartY;
+ var finalX = (pt.yLabelMax - pt.yLabelMin) * (pt.pxmid[0] < 0 ? -1 : 1) / 4;
+
if(pt.labelExtraX) {
- var yFromX = pt.labelExtraX * pt.pxmid[1] / pt.pxmid[0],
- yNet = pt.yLabelMid + pt.labelExtraY - (pt.cyFinal + pt.pxmid[1]);
+ var yFromX = pt.labelExtraX * pt.pxmid[1] / pt.pxmid[0];
+ var yNet = pt.yLabelMid + pt.labelExtraY - (pt.cyFinal + pt.pxmid[1]);
if(Math.abs(yFromX) > Math.abs(yNet)) {
textLinePath +=
@@ -368,11 +376,11 @@ module.exports = function plot(gd, cdpie) {
function transformInsideText(textBB, pt, cd0) {
- var textDiameter = Math.sqrt(textBB.width * textBB.width + textBB.height * textBB.height),
- textAspect = textBB.width / textBB.height,
- halfAngle = Math.PI * Math.min(pt.v / cd0.vTotal, 0.5),
- ring = 1 - cd0.trace.hole,
- rInscribed = getInscribedRadiusFraction(pt, cd0),
+ var textDiameter = Math.sqrt(textBB.width * textBB.width + textBB.height * textBB.height);
+ var textAspect = textBB.width / textBB.height;
+ var halfAngle = Math.PI * Math.min(pt.v / cd0.vTotal, 0.5);
+ var ring = 1 - cd0.trace.hole;
+ var rInscribed = getInscribedRadiusFraction(pt, cd0),
// max size text can be inserted inside without rotating it
// this inscribes the text rectangle in a circle, which is then inscribed
@@ -389,34 +397,34 @@ function transformInsideText(textBB, pt, cd0) {
if(transform.scale >= 1) return transform;
// max size if text is rotated radially
- var Qr = textAspect + 1 / (2 * Math.tan(halfAngle)),
- maxHalfHeightRotRadial = cd0.r * Math.min(
- 1 / (Math.sqrt(Qr * Qr + 0.5) + Qr),
- ring / (Math.sqrt(textAspect * textAspect + ring / 2) + textAspect)
- ),
- radialTransform = {
- scale: maxHalfHeightRotRadial * 2 / textBB.height,
- rCenter: Math.cos(maxHalfHeightRotRadial / cd0.r) -
- maxHalfHeightRotRadial * textAspect / cd0.r,
- rotate: (180 / Math.PI * pt.midangle + 720) % 180 - 90
- },
+ var Qr = textAspect + 1 / (2 * Math.tan(halfAngle));
+ var maxHalfHeightRotRadial = cd0.r * Math.min(
+ 1 / (Math.sqrt(Qr * Qr + 0.5) + Qr),
+ ring / (Math.sqrt(textAspect * textAspect + ring / 2) + textAspect)
+ );
+ var radialTransform = {
+ scale: maxHalfHeightRotRadial * 2 / textBB.height,
+ rCenter: Math.cos(maxHalfHeightRotRadial / cd0.r) -
+ maxHalfHeightRotRadial * textAspect / cd0.r,
+ rotate: (180 / Math.PI * pt.midangle + 720) % 180 - 90
+ };
// max size if text is rotated tangentially
- aspectInv = 1 / textAspect,
- Qt = aspectInv + 1 / (2 * Math.tan(halfAngle)),
- maxHalfWidthTangential = cd0.r * Math.min(
- 1 / (Math.sqrt(Qt * Qt + 0.5) + Qt),
- ring / (Math.sqrt(aspectInv * aspectInv + ring / 2) + aspectInv)
- ),
- tangentialTransform = {
- scale: maxHalfWidthTangential * 2 / textBB.width,
- rCenter: Math.cos(maxHalfWidthTangential / cd0.r) -
- maxHalfWidthTangential / textAspect / cd0.r,
- rotate: (180 / Math.PI * pt.midangle + 810) % 180 - 90
- },
- // if we need a rotated transform, pick the biggest one
- // even if both are bigger than 1
- rotatedTransform = tangentialTransform.scale > radialTransform.scale ?
+ var aspectInv = 1 / textAspect;
+ var Qt = aspectInv + 1 / (2 * Math.tan(halfAngle));
+ var maxHalfWidthTangential = cd0.r * Math.min(
+ 1 / (Math.sqrt(Qt * Qt + 0.5) + Qt),
+ ring / (Math.sqrt(aspectInv * aspectInv + ring / 2) + aspectInv)
+ );
+ var tangentialTransform = {
+ scale: maxHalfWidthTangential * 2 / textBB.width,
+ rCenter: Math.cos(maxHalfWidthTangential / cd0.r) -
+ maxHalfWidthTangential / textAspect / cd0.r,
+ rotate: (180 / Math.PI * pt.midangle + 810) % 180 - 90
+ };
+ // if we need a rotated transform, pick the biggest one
+ // even if both are bigger than 1
+ var rotatedTransform = tangentialTransform.scale > radialTransform.scale ?
tangentialTransform : radialTransform;
if(transform.scale < 1 && rotatedTransform.scale > transform.scale) return rotatedTransform;
@@ -431,10 +439,10 @@ function getInscribedRadiusFraction(pt, cd0) {
}
function transformOutsideText(textBB, pt) {
- var x = pt.pxmid[0],
- y = pt.pxmid[1],
- dx = textBB.width / 2,
- dy = textBB.height / 2;
+ var x = pt.pxmid[0];
+ var y = pt.pxmid[1];
+ var dx = textBB.width / 2;
+ var dy = textBB.height / 2;
if(x < 0) dx *= -1;
if(y < 0) dy *= -1;
@@ -450,19 +458,9 @@ function transformOutsideText(textBB, pt) {
}
function scootLabels(quadrants, trace) {
- var xHalf,
- yHalf,
- equatorFirst,
- farthestX,
- farthestY,
- xDiffSign,
- yDiffSign,
- thisQuad,
- oppositeQuad,
- wholeSide,
- i,
- thisQuadOutside,
- firstOppositeOutsidePt;
+ var xHalf, yHalf, equatorFirst, farthestX, farthestY,
+ xDiffSign, yDiffSign, thisQuad, oppositeQuad,
+ wholeSide, i, thisQuadOutside, firstOppositeOutsidePt;
function topFirst(a, b) { return a.pxmid[1] - b.pxmid[1]; }
function bottomFirst(a, b) { return b.pxmid[1] - a.pxmid[1]; }
@@ -470,17 +468,14 @@ function scootLabels(quadrants, trace) {
function scootOneLabel(thisPt, prevPt) {
if(!prevPt) prevPt = {};
- var prevOuterY = prevPt.labelExtraY + (yHalf ? prevPt.yLabelMax : prevPt.yLabelMin),
- thisInnerY = yHalf ? thisPt.yLabelMin : thisPt.yLabelMax,
- thisOuterY = yHalf ? thisPt.yLabelMax : thisPt.yLabelMin,
- thisSliceOuterY = thisPt.cyFinal + farthestY(thisPt.px0[1], thisPt.px1[1]),
- newExtraY = prevOuterY - thisInnerY,
- xBuffer,
- i,
- otherPt,
- otherOuterY,
- otherOuterX,
- newExtraX;
+ var prevOuterY = prevPt.labelExtraY + (yHalf ? prevPt.yLabelMax : prevPt.yLabelMin);
+ var thisInnerY = yHalf ? thisPt.yLabelMin : thisPt.yLabelMax;
+ var thisOuterY = yHalf ? thisPt.yLabelMax : thisPt.yLabelMin;
+ var thisSliceOuterY = thisPt.cyFinal + farthestY(thisPt.px0[1], thisPt.px1[1]);
+ var newExtraY = prevOuterY - thisInnerY;
+
+ var xBuffer, i, otherPt, otherOuterY, otherOuterX, newExtraX;
+
// make sure this label doesn't overlap other labels
// this *only* has us move these labels vertically
if(newExtraY * yDiffSign > 0) thisPt.labelExtraY = newExtraY;
@@ -492,7 +487,12 @@ function scootLabels(quadrants, trace) {
otherPt = wholeSide[i];
// overlap can only happen if the other point is pulled more than this one
- if(otherPt === thisPt || ((trace.pull[thisPt.i] || 0) >= trace.pull[otherPt.i] || 0)) continue;
+ if(otherPt === thisPt || (
+ (helpers.castOption(trace.pull, thisPt.pts) || 0) >=
+ (helpers.castOption(trace.pull, otherPt.pts) || 0))
+ ) {
+ continue;
+ }
if((thisPt.pxmid[1] - otherPt.pxmid[1]) * yDiffSign > 0) {
// closer to the equator - by construction all of these happen first
@@ -564,17 +564,10 @@ function scootLabels(quadrants, trace) {
}
function scalePies(cdpie, plotSize) {
- var pieBoxWidth,
- pieBoxHeight,
- i,
- j,
- cd0,
- trace,
- tiltAxisRads,
- maxPull,
- scaleGroups = [],
- scaleGroup,
- minPxPerValUnit;
+ var scaleGroups = [];
+
+ var pieBoxWidth, pieBoxHeight, i, j, cd0, trace,
+ maxPull, scaleGroup, minPxPerValUnit;
// first figure out the center and maximum radius for each pie
for(i = 0; i < cdpie.length; i++) {
@@ -582,7 +575,6 @@ function scalePies(cdpie, plotSize) {
trace = cd0.trace;
pieBoxWidth = plotSize.w * (trace.domain.x[1] - trace.domain.x[0]);
pieBoxHeight = plotSize.h * (trace.domain.y[1] - trace.domain.y[0]);
- tiltAxisRads = trace.tiltaxis * Math.PI / 180;
maxPull = trace.pull;
if(Array.isArray(maxPull)) {
@@ -592,10 +584,7 @@ function scalePies(cdpie, plotSize) {
}
}
- cd0.r = Math.min(
- pieBoxWidth / maxExtent(trace.tilt, Math.sin(tiltAxisRads), trace.depth),
- pieBoxHeight / maxExtent(trace.tilt, Math.cos(tiltAxisRads), trace.depth)
- ) / (2 + 2 * maxPull);
+ cd0.r = Math.min(pieBoxWidth, pieBoxHeight) / (2 + 2 * maxPull);
cd0.cx = plotSize.l + plotSize.w * (trace.domain.x[1] + trace.domain.x[0]) / 2;
cd0.cy = plotSize.t + plotSize.h * (2 - trace.domain.y[1] - trace.domain.y[0]) / 2;
@@ -629,22 +618,14 @@ function scalePies(cdpie, plotSize) {
}
function setCoords(cd) {
- var cd0 = cd[0],
- trace = cd0.trace,
- tilt = trace.tilt,
- tiltAxisRads,
- tiltAxisSin,
- tiltAxisCos,
- tiltRads,
- crossTilt,
- inPlane,
- currentAngle = trace.rotation * Math.PI / 180,
- angleFactor = 2 * Math.PI / cd0.vTotal,
- firstPt = 'px0',
- lastPt = 'px1',
- i,
- cdi,
- currentCoords;
+ var cd0 = cd[0];
+ var trace = cd0.trace;
+ var currentAngle = trace.rotation * Math.PI / 180;
+ var angleFactor = 2 * Math.PI / cd0.vTotal;
+ var firstPt = 'px0';
+ var lastPt = 'px1';
+
+ var i, cdi, currentCoords;
if(trace.direction === 'counterclockwise') {
for(i = 0; i < cd.length; i++) {
@@ -658,26 +639,8 @@ function setCoords(cd) {
lastPt = 'px0';
}
- if(tilt) {
- tiltRads = tilt * Math.PI / 180;
- tiltAxisRads = trace.tiltaxis * Math.PI / 180;
- crossTilt = Math.sin(tiltAxisRads) * Math.cos(tiltAxisRads);
- inPlane = 1 - Math.cos(tiltRads);
- tiltAxisSin = Math.sin(tiltAxisRads);
- tiltAxisCos = Math.cos(tiltAxisRads);
- }
-
function getCoords(angle) {
- var xFlat = cd0.r * Math.sin(angle),
- yFlat = -cd0.r * Math.cos(angle);
-
- if(!tilt) return [xFlat, yFlat];
-
- return [
- xFlat * (1 - inPlane * tiltAxisSin * tiltAxisSin) + yFlat * crossTilt * inPlane,
- xFlat * crossTilt * inPlane + yFlat * (1 - inPlane * tiltAxisCos * tiltAxisCos),
- Math.sin(tiltRads) * (yFlat * tiltAxisCos - xFlat * tiltAxisSin)
- ];
+ return [cd0.r * Math.sin(angle), -cd0.r * Math.cos(angle)];
}
currentCoords = getCoords(currentAngle);
@@ -700,11 +663,3 @@ function setCoords(cd) {
cdi.largeArc = (cdi.v > cd0.vTotal / 2) ? 1 : 0;
}
}
-
-function maxExtent(tilt, tiltAxisFraction, depth) {
- if(!tilt) return 1;
- var sinTilt = Math.sin(tilt * Math.PI / 180);
- return Math.max(0.01, // don't let it go crazy if you tilt the pie totally on its side
- depth * sinTilt * Math.abs(tiltAxisFraction) +
- 2 * Math.sqrt(1 - sinTilt * sinTilt * tiltAxisFraction * tiltAxisFraction));
-}
diff --git a/src/traces/pie/style.js b/src/traces/pie/style.js
index fb02933eb00..35c11ab0d7a 100644
--- a/src/traces/pie/style.js
+++ b/src/traces/pie/style.js
@@ -14,13 +14,13 @@ var styleOne = require('./style_one');
module.exports = function style(gd) {
gd._fullLayout._pielayer.selectAll('.trace').each(function(cd) {
- var cd0 = cd[0],
- trace = cd0.trace,
- traceSelection = d3.select(this);
+ var cd0 = cd[0];
+ var trace = cd0.trace;
+ var traceSelection = d3.select(this);
traceSelection.style({opacity: trace.opacity});
- traceSelection.selectAll('.top path.surface').each(function(pt) {
+ traceSelection.selectAll('path.surface').each(function(pt) {
d3.select(this).call(styleOne, pt, trace);
});
});
diff --git a/src/traces/pie/style_one.js b/src/traces/pie/style_one.js
index 0b7f3e7cd60..9ed05500fcf 100644
--- a/src/traces/pie/style_one.js
+++ b/src/traces/pie/style_one.js
@@ -9,15 +9,14 @@
'use strict';
var Color = require('../../components/color');
+var castOption = require('./helpers').castOption;
module.exports = function styleOne(s, pt, trace) {
- var lineColor = trace.marker.line.color;
- if(Array.isArray(lineColor)) lineColor = lineColor[pt.i] || Color.defaultLine;
-
- var lineWidth = trace.marker.line.width || 0;
- if(Array.isArray(lineWidth)) lineWidth = lineWidth[pt.i] || 0;
+ var line = trace.marker.line;
+ var lineColor = castOption(line.color, pt.pts) || Color.defaultLine;
+ var lineWidth = castOption(line.width, pt.pts) || 0;
s.style({'stroke-width': lineWidth})
- .call(Color.fill, pt.color)
- .call(Color.stroke, lineColor);
+ .call(Color.fill, pt.color)
+ .call(Color.stroke, lineColor);
};
diff --git a/src/traces/sankey/plot.js b/src/traces/sankey/plot.js
index cf53c53857d..59d978fa4a4 100644
--- a/src/traces/sankey/plot.js
+++ b/src/traces/sankey/plot.js
@@ -128,10 +128,11 @@ module.exports = function plot(gd, calcData) {
};
var linkHover = function(element, d, sankey) {
- var evt = d.link;
- evt.originalEvent = d3.event;
d3.select(element).call(linkHoveredStyle.bind(0, d, sankey, true));
- Fx.hover(gd, evt, 'sankey');
+ gd.emit('plotly_hover', {
+ event: d3.event,
+ points: [d.link]
+ });
};
var linkHoverFollow = function(element, d) {
@@ -185,10 +186,11 @@ module.exports = function plot(gd, calcData) {
};
var nodeHover = function(element, d, sankey) {
- var evt = d.node;
- evt.originalEvent = d3.event;
d3.select(element).call(nodeHoveredStyle, d, sankey);
- Fx.hover(gd, evt, 'sankey');
+ gd.emit('plotly_hover', {
+ event: d3.event,
+ points: [d.node]
+ });
};
var nodeHoverFollow = function(element, d) {
diff --git a/test/image/baselines/pie_aggregated.png b/test/image/baselines/pie_aggregated.png
new file mode 100644
index 00000000000..576ae7dd8c6
Binary files /dev/null and b/test/image/baselines/pie_aggregated.png differ
diff --git a/test/image/mocks/pie_aggregated.json b/test/image/mocks/pie_aggregated.json
new file mode 100644
index 00000000000..9740acecae2
--- /dev/null
+++ b/test/image/mocks/pie_aggregated.json
@@ -0,0 +1,39 @@
+{
+ "data": [
+ {
+ "labels": [
+ "Alice",
+ "Bob",
+ "Charlie",
+ "Charlie",
+ "Charlie",
+ "Alice"
+ ],
+ "type": "pie",
+ "domain": {"x": [0, 0.4]}
+ },
+ {
+ "labels": [
+ "Alice",
+ "Alice",
+ "Allison",
+ "Alys",
+ "Elise",
+ "Allison",
+ "Alys",
+ "Alys"
+ ],
+ "values": [
+ 10, 20, 30, 40, 50, 60, 70, 80
+ ],
+ "marker": {"colors": ["", "", "#ccc"]},
+ "type": "pie",
+ "domain": {"x": [0.5, 1]},
+ "textinfo": "label+value+percent"
+ }
+ ],
+ "layout": {
+ "height": 300,
+ "width": 500
+ }
+}
diff --git a/test/jasmine/tests/pie_test.js b/test/jasmine/tests/pie_test.js
index 4a9f675cf72..1f4e109a18f 100644
--- a/test/jasmine/tests/pie_test.js
+++ b/test/jasmine/tests/pie_test.js
@@ -65,6 +65,33 @@ describe('Pie traces:', function() {
.catch(failTest)
.then(done);
});
+
+ it('can sum values or count labels', function(done) {
+ Plotly.newPlot(gd, [{
+ labels: ['a', 'b', 'c', 'a', 'b', 'a'],
+ values: [1, 2, 3, 4, 5, 6],
+ type: 'pie',
+ domain: {x: [0, 0.45]}
+ }, {
+ labels: ['d', 'e', 'f', 'd', 'e', 'd'],
+ type: 'pie',
+ domain: {x: [0.55, 1]}
+ }])
+ .then(function() {
+ var expected = [
+ [['a', 11], ['b', 7], ['c', 3]],
+ [['d', 3], ['e', 2], ['f', 1]]
+ ];
+ for(var i = 0; i < 2; i++) {
+ for(var j = 0; j < 3; j++) {
+ expect(gd.calcdata[i][j].label).toBe(expected[i][j][0], i + ',' + j);
+ expect(gd.calcdata[i][j].v).toBe(expected[i][j][1], i + ',' + j);
+ }
+ }
+ })
+ .catch(failTest)
+ .then(done);
+ });
});
describe('pie hovering', function() {
@@ -141,24 +168,6 @@ describe('pie hovering', function() {
it('should contain the correct fields', function() {
- /*
- * expected = [{
- * v: 4,
- * label: '3',
- * color: '#ff7f0e',
- * i: 3,
- * hidden: false,
- * text: '26.7%',
- * px1: [0,-60],
- * pxmid: [-44.588689528643656,-40.14783638153149],
- * midangle: -0.8377580409572781,
- * px0: [-59.67131372209641,6.2717077960592],
- * largeArc: 0,
- * cxFinal: 200,
- * cyFinal: 160,
- * originalEvent: MouseEvent
- * }];
- */
var hoverData,
unhoverData;
@@ -178,19 +187,17 @@ describe('pie hovering', function() {
expect(unhoverData.points.length).toEqual(1);
var fields = [
- 'v', 'label', 'color', 'i', 'hidden',
- 'text', 'px1', 'pxmid', 'midangle',
- 'px0', 'largeArc',
- 'pointNumber', 'curveNumber',
- 'cxFinal', 'cyFinal',
- 'originalEvent'
+ 'curveNumber', 'pointNumber', 'pointNumbers',
+ 'data', 'fullData',
+ 'label', 'color', 'value',
+ 'i', 'v'
];
- expect(Object.keys(hoverData.points[0])).toEqual(fields);
- expect(hoverData.points[0].i).toEqual(3);
+ expect(Object.keys(hoverData.points[0]).sort()).toEqual(fields.sort());
+ expect(hoverData.points[0].pointNumber).toEqual(3);
- expect(Object.keys(unhoverData.points[0])).toEqual(fields);
- expect(unhoverData.points[0].i).toEqual(3);
+ expect(Object.keys(unhoverData.points[0]).sort()).toEqual(fields.sort());
+ expect(unhoverData.points[0].pointNumber).toEqual(3);
});
it('should fire hover event when moving from one slice to another', function(done) {
@@ -355,7 +362,7 @@ describe('pie hovering', function() {
});
-describe('Test event property of interactions on a pie plot:', function() {
+describe('Test event data of interactions on a pie plot:', function() {
var mock = require('@mocks/pie_simple.json');
var mockCopy, gd;
@@ -376,10 +383,37 @@ describe('Test event property of interactions on a pie plot:', function() {
beforeEach(function() {
gd = createGraphDiv();
mockCopy = Lib.extendDeep({}, mock);
+ Lib.extendFlat(mockCopy.data[0], {
+ ids: ['marge', 'homer', 'bart', 'lisa', 'maggie'],
+ customdata: [{1: 2}, {3: 4}, {5: 6}, {7: 8}, {9: 10}]
+ });
});
afterEach(destroyGraphDiv);
+ function checkEventData(data) {
+ var point = data.points[0];
+
+ expect(point.curveNumber).toBe(0);
+ expect(point.pointNumber).toBe(4);
+ expect(point.pointNumbers).toEqual([4]);
+ expect(point.data).toBe(gd.data[0]);
+ expect(point.fullData).toBe(gd._fullData[0]);
+ expect(point.label).toBe('4');
+ expect(point.value).toBe(5);
+ expect(point.color).toBe('#1f77b4');
+ expect(point.id).toEqual(['maggie']);
+ expect(point.customdata).toEqual([{9: 10}]);
+
+ // for backward compat - i/v to be removed at some point?
+ expect(point.i).toBe(point.pointNumber);
+ expect(point.v).toBe(point.value);
+
+ var evt = data.event;
+ expect(evt.clientX).toEqual(pointPos[0], 'event.clientX');
+ expect(evt.clientY).toEqual(pointPos[1], 'event.clientY');
+ }
+
describe('click events', function() {
var futureData;
@@ -400,43 +434,24 @@ describe('Test event property of interactions on a pie plot:', function() {
click(pointPos[0], pointPos[1]);
expect(futureData.points.length).toEqual(1);
- var trace = futureData.points.trace;
- expect(typeof trace).toEqual(typeof {}, 'points.trace');
-
- var pt = futureData.points[0];
- expect(Object.keys(pt)).toEqual(jasmine.arrayContaining([
- 'v', 'label', 'color', 'i', 'hidden', 'vTotal', 'text', 't',
- 'trace', 'r', 'cx', 'cy', 'px1', 'pxmid', 'midangle', 'px0',
- 'largeArc', 'cxFinal', 'cyFinal',
- 'pointNumber', 'curveNumber'
- ]));
- expect(Object.keys(pt).length).toBe(21);
-
- expect(typeof pt.color).toEqual(typeof '#1f77b4', 'points[0].color');
- expect(pt.cx).toEqual(200, 'points[0].cx');
- expect(pt.cxFinal).toEqual(200, 'points[0].cxFinal');
- expect(pt.cy).toEqual(160, 'points[0].cy');
- expect(pt.cyFinal).toEqual(160, 'points[0].cyFinal');
- expect(pt.hidden).toEqual(false, 'points[0].hidden');
- expect(pt.i).toEqual(4, 'points[0].i');
- expect(pt.pointNumber).toEqual(4, 'points[0].pointNumber');
- expect(pt.label).toEqual('4', 'points[0].label');
- expect(pt.largeArc).toEqual(0, 'points[0].largeArc');
- expect(pt.midangle).toEqual(1.0471975511965976, 'points[0].midangle');
- expect(pt.px0).toEqual([0, -60], 'points[0].px0');
- expect(pt.px1).toEqual([51.96152422706632, 29.999999999999986], 'points[0].px1');
- expect(pt.pxmid).toEqual([51.96152422706631, -30.000000000000007], 'points[0].pxmid');
- expect(pt.r).toEqual(60, 'points[0].r');
- expect(typeof pt.t).toEqual(typeof {}, 'points[0].t');
- expect(pt.text).toEqual('33.3%', 'points[0].text');
- expect(typeof pt.trace).toEqual(typeof {}, 'points[0].trace');
- expect(pt.v).toEqual(5, 'points[0].v');
- expect(pt.vTotal).toEqual(15, 'points[0].vTotal');
- expect(pt.curveNumber).toEqual(0, 'points[0].curveNumber');
+ checkEventData(futureData);
+ });
- var evt = futureData.event;
- expect(evt.clientX).toEqual(pointPos[0], 'event.clientX');
- expect(evt.clientY).toEqual(pointPos[1], 'event.clientY');
+ it('should not contain pointNumber if aggregating', function() {
+ var values = gd.data[0].values;
+ var labels = [];
+ for(var i = 0; i < values.length; i++) labels.push(i);
+ Plotly.restyle(gd, {
+ labels: [labels.concat(labels)],
+ values: [values.concat(values)]
+ });
+
+ click(pointPos[0], pointPos[1]);
+ expect(futureData.points.length).toEqual(1);
+
+ expect(futureData.points[0].pointNumber).toBeUndefined();
+ expect(futureData.points[0].i).toBeUndefined();
+ expect(futureData.points[0].pointNumbers).toEqual([4, 9]);
});
});
@@ -466,42 +481,9 @@ describe('Test event property of interactions on a pie plot:', function() {
click(pointPos[0], pointPos[1], clickOpts);
expect(futureData.points.length).toEqual(1);
- var trace = futureData.points.trace;
- expect(typeof trace).toEqual(typeof {}, 'points.trace');
-
- var pt = futureData.points[0];
- expect(Object.keys(pt)).toEqual(jasmine.arrayContaining([
- 'v', 'label', 'color', 'i', 'hidden', 'vTotal', 'text', 't',
- 'trace', 'r', 'cx', 'cy', 'px1', 'pxmid', 'midangle', 'px0',
- 'largeArc', 'cxFinal', 'cyFinal',
- 'pointNumber', 'curveNumber'
- ]));
-
- expect(typeof pt.color).toEqual(typeof '#1f77b4', 'points[0].color');
- expect(pt.cx).toEqual(200, 'points[0].cx');
- expect(pt.cxFinal).toEqual(200, 'points[0].cxFinal');
- expect(pt.cy).toEqual(160, 'points[0].cy');
- expect(pt.cyFinal).toEqual(160, 'points[0].cyFinal');
- expect(pt.hidden).toEqual(false, 'points[0].hidden');
- expect(pt.i).toEqual(4, 'points[0].i');
- expect(pt.pointNumber).toEqual(4, 'points[0].pointNumber');
- expect(pt.label).toEqual('4', 'points[0].label');
- expect(pt.largeArc).toEqual(0, 'points[0].largeArc');
- expect(pt.midangle).toEqual(1.0471975511965976, 'points[0].midangle');
- expect(pt.px0).toEqual([0, -60], 'points[0].px0');
- expect(pt.px1).toEqual([51.96152422706632, 29.999999999999986], 'points[0].px1');
- expect(pt.pxmid).toEqual([51.96152422706631, -30.000000000000007], 'points[0].pxmid');
- expect(pt.r).toEqual(60, 'points[0].r');
- expect(typeof pt.t).toEqual(typeof {}, 'points[0].t');
- expect(pt.text).toEqual('33.3%', 'points[0].text');
- expect(typeof pt.trace).toEqual(typeof {}, 'points[0].trace');
- expect(pt.v).toEqual(5, 'points[0].v');
- expect(pt.vTotal).toEqual(15, 'points[0].vTotal');
- expect(pt.curveNumber).toEqual(0, 'points[0].curveNumber');
+ checkEventData(futureData);
var evt = futureData.event;
- expect(evt.clientX).toEqual(pointPos[0], 'event.clientX');
- expect(evt.clientY).toEqual(pointPos[1], 'event.clientY');
Object.getOwnPropertyNames(clickOpts).forEach(function(opt) {
expect(evt[opt]).toEqual(clickOpts[opt], 'event.' + opt);
});
@@ -512,7 +494,8 @@ describe('Test event property of interactions on a pie plot:', function() {
var futureData;
beforeEach(function(done) {
- Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(done);
+ futureData = undefined;
+ Plotly.newPlot(gd, mockCopy.data, mockCopy.layout).then(done);
gd.on('plotly_hover', function(data) {
futureData = data;
@@ -522,11 +505,19 @@ describe('Test event property of interactions on a pie plot:', function() {
it('should contain the correct fields', function() {
mouseEvent('mouseover', pointPos[0], pointPos[1]);
- var point0 = futureData.points[0],
- evt = futureData.event;
- expect(point0.originalEvent).toEqual(evt, 'points');
- expect(evt.clientX).toEqual(pointPos[0], 'event.clientX');
- expect(evt.clientY).toEqual(pointPos[1], 'event.clientY');
+ checkEventData(futureData);
+ });
+
+ it('should not emit a hover if you\'re dragging', function() {
+ gd._dragging = true;
+ mouseEvent('mouseover', pointPos[0], pointPos[1]);
+ expect(futureData).toBeUndefined();
+ });
+
+ it('should not emit a hover if hover is disabled', function() {
+ Plotly.relayout(gd, 'hovermode', false);
+ mouseEvent('mouseover', pointPos[0], pointPos[1]);
+ expect(futureData).toBeUndefined();
});
});
@@ -534,7 +525,8 @@ describe('Test event property of interactions on a pie plot:', function() {
var futureData;
beforeEach(function(done) {
- Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(done);
+ futureData = undefined;
+ Plotly.newPlot(gd, mockCopy.data, mockCopy.layout).then(done);
gd.on('plotly_unhover', function(data) {
futureData = data;
@@ -542,13 +534,15 @@ describe('Test event property of interactions on a pie plot:', function() {
});
it('should contain the correct fields', function() {
+ mouseEvent('mouseover', pointPos[0], pointPos[1]);
mouseEvent('mouseout', pointPos[0], pointPos[1]);
- var point0 = futureData.points[0],
- evt = futureData.event;
- expect(point0.originalEvent).toEqual(evt, 'points');
- expect(evt.clientX).toEqual(pointPos[0], 'event.clientX');
- expect(evt.clientY).toEqual(pointPos[1], 'event.clientY');
+ checkEventData(futureData);
+ });
+
+ it('should not emit an unhover if you didn\'t first hover', function() {
+ mouseEvent('mouseout', pointPos[0], pointPos[1]);
+ expect(futureData).toBeUndefined();
});
});
});