Skip to content

Commit bb252a5

Browse files
committed
Merge branch 'master' into violins-dev
2 parents d779509 + 4b8b10d commit bb252a5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+2079
-756
lines changed

devtools/test_dashboard/perf.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ window.timeit = function(f, n, nchunk, arg) {
4242
times.sort();
4343
var min = (times[0]).toFixed(4);
4444
var max = (times[n - 1]).toFixed(4);
45-
var median = (times[Math.ceil(n / 2)]).toFixed(4);
45+
var median = (times[Math.min(Math.ceil(n / 2), n - 1)]).toFixed(4);
4646
var mean = (totalTime / n).toFixed(4);
4747
console.log((f.name || 'function') + ' timing (ms) - min: ' + min +
4848
' max: ' + max +

src/components/fx/helpers.js

+58-11
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,8 @@ exports.quadrature = function quadrature(dx, dy) {
8989
*
9090
* @param {object} pointData : point data object (gets mutated here)
9191
* @param {object} trace : full trace object
92-
* @param {number} pointNumber : point number
92+
* @param {number|Array(number)} pointNumber : point number. May be a length-2 array
93+
* [row, col] to dig into 2D arrays
9394
*/
9495
exports.appendArrayPointValue = function(pointData, trace, pointNumber) {
9596
var arrayAttrs = trace._arrayAttrs;
@@ -100,22 +101,68 @@ exports.appendArrayPointValue = function(pointData, trace, pointNumber) {
100101

101102
for(var i = 0; i < arrayAttrs.length; i++) {
102103
var astr = arrayAttrs[i];
103-
var key;
104+
var key = getPointKey(astr);
104105

105-
if(astr === 'ids') key = 'id';
106-
else if(astr === 'locations') key = 'location';
107-
else key = astr;
106+
if(pointData[key] === undefined) {
107+
var val = Lib.nestedProperty(trace, astr).get();
108+
var pointVal = getPointData(val, pointNumber);
109+
110+
if(pointVal !== undefined) pointData[key] = pointVal;
111+
}
112+
}
113+
};
114+
115+
/**
116+
* Appends values inside array attributes corresponding to given point number array
117+
* For use when pointData references a plot entity that arose (or potentially arose)
118+
* from multiple points in the input data
119+
*
120+
* @param {object} pointData : point data object (gets mutated here)
121+
* @param {object} trace : full trace object
122+
* @param {Array(number)|Array(Array(number))} pointNumbers : Array of point numbers.
123+
* Each entry in the array may itself be a length-2 array [row, col] to dig into 2D arrays
124+
*/
125+
exports.appendArrayMultiPointValues = function(pointData, trace, pointNumbers) {
126+
var arrayAttrs = trace._arrayAttrs;
127+
128+
if(!arrayAttrs) {
129+
return;
130+
}
131+
132+
for(var i = 0; i < arrayAttrs.length; i++) {
133+
var astr = arrayAttrs[i];
134+
var key = getPointKey(astr);
108135

109136
if(pointData[key] === undefined) {
110137
var val = Lib.nestedProperty(trace, astr).get();
138+
var keyVal = new Array(pointNumbers.length);
111139

112-
if(Array.isArray(pointNumber)) {
113-
if(Array.isArray(val) && Array.isArray(val[pointNumber[0]])) {
114-
pointData[key] = val[pointNumber[0]][pointNumber[1]];
115-
}
116-
} else {
117-
pointData[key] = val[pointNumber];
140+
for(var j = 0; j < pointNumbers.length; j++) {
141+
keyVal[j] = getPointData(val, pointNumbers[j]);
118142
}
143+
pointData[key] = keyVal;
119144
}
120145
}
121146
};
147+
148+
var pointKeyMap = {
149+
ids: 'id',
150+
locations: 'location',
151+
labels: 'label',
152+
values: 'value',
153+
'marker.colors': 'color'
154+
};
155+
156+
function getPointKey(astr) {
157+
return pointKeyMap[astr] || astr;
158+
}
159+
160+
function getPointData(val, pointNumber) {
161+
if(Array.isArray(pointNumber)) {
162+
if(Array.isArray(val) && Array.isArray(val[pointNumber[0]])) {
163+
return val[pointNumber[0]][pointNumber[1]];
164+
}
165+
} else {
166+
return val[pointNumber];
167+
}
168+
}

src/components/fx/hover.js

+6-11
Original file line numberDiff line numberDiff line change
@@ -155,14 +155,6 @@ exports.loneHover = function loneHover(hoverItem, opts) {
155155

156156
// The actual implementation is here:
157157
function _hover(gd, evt, subplot, noHoverEvent) {
158-
if((subplot === 'pie' || subplot === 'sankey') && !noHoverEvent) {
159-
gd.emit('plotly_hover', {
160-
event: evt.originalEvent,
161-
points: [evt]
162-
});
163-
return;
164-
}
165-
166158
if(!subplot) subplot = 'xy';
167159

168160
// if the user passed in an array of subplots,
@@ -1063,8 +1055,7 @@ function cleanPoint(d, hovermode) {
10631055
d.y0 = Lib.constrain(d.y0, 0, d.ya._length);
10641056
d.y1 = Lib.constrain(d.y1, 0, d.ya._length);
10651057

1066-
// and convert the x and y label values into objects
1067-
// formatted as text, with font info
1058+
// and convert the x and y label values into formatted text
10681059
if(d.xLabelVal !== undefined) {
10691060
d.xLabel = ('xLabel' in d) ? d.xLabel : Axes.hoverLabelText(d.xa, d.xLabelVal);
10701061
d.xVal = d.xa.c2d(d.xLabelVal);
@@ -1073,7 +1064,11 @@ function cleanPoint(d, hovermode) {
10731064
d.yLabel = ('yLabel' in d) ? d.yLabel : Axes.hoverLabelText(d.ya, d.yLabelVal);
10741065
d.yVal = d.ya.c2d(d.yLabelVal);
10751066
}
1076-
if(d.zLabelVal !== undefined) d.zLabel = String(d.zLabelVal);
1067+
1068+
// Traces like heatmaps generate the zLabel in their hoverPoints function
1069+
if(d.zLabelVal !== undefined && d.zLabel === undefined) {
1070+
d.zLabel = String(d.zLabelVal);
1071+
}
10771072

10781073
// for box means and error bars, add the range to the label
10791074
if(!isNaN(d.xerr) && !(d.xa.type === 'log' && d.xerr <= 0)) {

src/components/shapes/draw.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ function draw(gd) {
5757
function drawOne(gd, index) {
5858
// remove the existing shape if there is one.
5959
// because indices can change, we need to look in all shape layers
60-
gd._fullLayout._paper
60+
gd._fullLayout._paperdiv
6161
.selectAll('.shapelayer [data-index="' + index + '"]')
6262
.remove();
6363

src/components/updatemenus/draw.js

+10-8
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ module.exports = function draw(gd) {
5454
*/
5555

5656
// draw update menu container
57-
var menus = fullLayout._infolayer
57+
var menus = fullLayout._menulayer
5858
.selectAll('g.' + constants.containerClassName)
5959
.data(menuData.length > 0 ? [0] : []);
6060

@@ -97,6 +97,9 @@ module.exports = function draw(gd) {
9797

9898
// remove exiting header, remove dropped buttons and reset margins
9999
if(headerGroups.enter().size()) {
100+
// make sure gButton is on top of all headers
101+
gButton.node().parentNode.appendChild(gButton.node());
102+
100103
gButton
101104
.call(removeAllButtons)
102105
.attr(constants.menuIndexAttrName, '-1');
@@ -135,13 +138,12 @@ module.exports = function draw(gd) {
135138
});
136139
};
137140

141+
/**
142+
* get only visible menus for display
143+
*/
138144
function makeMenuData(fullLayout) {
139-
var contOpts = fullLayout[constants.name],
140-
menuData = [];
141-
142-
// Filter visible dropdowns and attach '_index' to each
143-
// fullLayout options object to be used for 'object constancy'
144-
// in the data join key function.
145+
var contOpts = fullLayout[constants.name];
146+
var menuData = [];
145147

146148
for(var i = 0; i < contOpts.length; i++) {
147149
var item = contOpts[i];
@@ -154,7 +156,7 @@ function makeMenuData(fullLayout) {
154156

155157
// Note that '_index' is set at the default step,
156158
// it corresponds to the menu index in the user layout update menu container.
157-
// Because a menu can b set invisible,
159+
// Because a menu can be set invisible,
158160
// this is a more 'consistent' field than the index in the menuData.
159161
function keyFunction(menuOpts) {
160162
return menuOpts._index;

src/lib/geo_location_utils.js

+2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ var locationmodeToIdFinder = {
2323
};
2424

2525
exports.locationToFeature = function(locationmode, location, features) {
26+
if(!location || typeof location !== 'string') return false;
27+
2628
var locationId = getLocationId(locationmode, location);
2729

2830
if(locationId) {

src/lib/search.js

+14-8
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@
1212
var isNumeric = require('fast-isnumeric');
1313
var loggers = require('./loggers');
1414

15+
// don't trust floating point equality - fraction of bin size to call
16+
// "on the line" and ensure that they go the right way specified by
17+
// linelow
18+
var roundingError = 1e-9;
19+
1520

1621
/**
1722
* findBin - find the bin for val - note that it can return outside the
@@ -26,20 +31,21 @@ var loggers = require('./loggers');
2631
exports.findBin = function(val, bins, linelow) {
2732
if(isNumeric(bins.start)) {
2833
return linelow ?
29-
Math.ceil((val - bins.start) / bins.size) - 1 :
30-
Math.floor((val - bins.start) / bins.size);
34+
Math.ceil((val - bins.start) / bins.size - roundingError) - 1 :
35+
Math.floor((val - bins.start) / bins.size + roundingError);
3136
}
3237
else {
33-
var n1 = 0,
34-
n2 = bins.length,
35-
c = 0,
36-
n,
37-
test;
38-
if(bins[bins.length - 1] >= bins[0]) {
38+
var n1 = 0;
39+
var n2 = bins.length;
40+
var c = 0;
41+
var binSize = (n2 > 1) ? (bins[n2 - 1] - bins[0]) / (n2 - 1) : 1;
42+
var n, test;
43+
if(binSize >= 0) {
3944
test = linelow ? lessThan : lessOrEqual;
4045
} else {
4146
test = linelow ? greaterOrEqual : greaterThan;
4247
}
48+
val += binSize * roundingError * (linelow ? -1 : 1) * (binSize >= 0 ? 1 : -1);
4349
// c is just to avoid infinite loops if there's an error
4450
while(n1 < n2 && c++ < 100) {
4551
n = Math.floor((n1 + n2) / 2);

src/plot_api/plot_api.js

+10-10
Original file line numberDiff line numberDiff line change
@@ -2830,25 +2830,25 @@ function makePlotFramework(gd) {
28302830
// single geo layer for the whole plot
28312831
fullLayout._geolayer = fullLayout._paper.append('g').classed('geolayer', true);
28322832

2833-
// upper shape layer
2834-
// (only for shapes to be drawn above the whole plot, including subplots)
2835-
var layerAbove = fullLayout._paper.append('g')
2836-
.classed('layer-above', true);
2837-
fullLayout._imageUpperLayer = layerAbove.append('g')
2838-
.classed('imagelayer', true);
2839-
fullLayout._shapeUpperLayer = layerAbove.append('g')
2840-
.classed('shapelayer', true);
2841-
28422833
// single pie layer for the whole plot
28432834
fullLayout._pielayer = fullLayout._paper.append('g').classed('pielayer', true);
28442835

28452836
// fill in image server scrape-svg
28462837
fullLayout._glimages = fullLayout._paper.append('g').classed('glimages', true);
28472838

2848-
// lastly info (legend, annotations) and hover layers go on top
2839+
// lastly upper shapes, info (legend, annotations) and hover layers go on top
28492840
// these are in a different svg element normally, but get collapsed into a single
28502841
// svg when exporting (after inserting 3D)
2842+
// upper shapes/images are only those drawn above the whole plot, including subplots
2843+
var layerAbove = fullLayout._toppaper.append('g')
2844+
.classed('layer-above', true);
2845+
fullLayout._imageUpperLayer = layerAbove.append('g')
2846+
.classed('imagelayer', true);
2847+
fullLayout._shapeUpperLayer = layerAbove.append('g')
2848+
.classed('shapelayer', true);
2849+
28512850
fullLayout._infolayer = fullLayout._toppaper.append('g').classed('infolayer', true);
2851+
fullLayout._menulayer = fullLayout._toppaper.append('g').classed('menulayer', true);
28522852
fullLayout._zoomlayer = fullLayout._toppaper.append('g').classed('zoomlayer', true);
28532853
fullLayout._hoverlayer = fullLayout._toppaper.append('g').classed('hoverlayer', true);
28542854

src/plots/cartesian/axes.js

+19-2
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ var ONEHOUR = constants.ONEHOUR;
2828
var ONEMIN = constants.ONEMIN;
2929
var ONESEC = constants.ONESEC;
3030
var MINUS_SIGN = constants.MINUS_SIGN;
31+
var BADNUM = constants.BADNUM;
3132

3233
var MID_SHIFT = require('../../constants/alignment').MID_SHIFT;
3334

@@ -1216,12 +1217,28 @@ axes.tickText = function(ax, x, hover) {
12161217
return out;
12171218
};
12181219

1219-
axes.hoverLabelText = function(ax, val) {
1220+
/**
1221+
* create text for a hover label on this axis, with special handling of
1222+
* log axes (where negative values can't be displayed but can appear in hover text)
1223+
*
1224+
* @param {object} ax: the axis to format text for
1225+
* @param {number} val: calcdata value to format
1226+
* @param {Optional(number)} val2: a second value to display
1227+
*
1228+
* @returns {string} `val` formatted as a string appropriate to this axis, or
1229+
* `val` and `val2` as a range (ie '<val> - <val2>') if `val2` is provided and
1230+
* it's different from `val`.
1231+
*/
1232+
axes.hoverLabelText = function(ax, val, val2) {
1233+
if(val2 !== BADNUM && val2 !== val) {
1234+
return axes.hoverLabelText(ax, val) + ' - ' + axes.hoverLabelText(ax, val2);
1235+
}
1236+
12201237
var logOffScale = (ax.type === 'log' && val <= 0);
12211238
var tx = axes.tickText(ax, ax.c2l(logOffScale ? -val : val), 'hover').text;
12221239

12231240
if(logOffScale) {
1224-
return val === 0 ? '0' : '-' + tx;
1241+
return val === 0 ? '0' : MINUS_SIGN + tx;
12251242
}
12261243

12271244
// TODO: should we do something special if the axis calendar and

0 commit comments

Comments
 (0)