Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 0204845

Browse files
authoredMay 10, 2017
Merge pull request #1609 from plotly/sort-transform
Sort transform
2 parents 28f4a05 + 4e8efe4 commit 0204845

File tree

7 files changed

+563
-68
lines changed

7 files changed

+563
-68
lines changed
 

‎lib/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ Plotly.register([
5757
//
5858
Plotly.register([
5959
require('./filter'),
60-
require('./groupby')
60+
require('./groupby'),
61+
require('./sort')
6162
]);
6263

6364
// components

‎lib/sort.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/**
2+
* Copyright 2012-2017, Plotly, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
'use strict';
10+
11+
module.exports = require('../src/transforms/sort');

‎src/lib/index.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,29 @@ lib.mergeArray = function(traceAttr, cd, cdAttr) {
344344
}
345345
};
346346

347+
/** Returns target as set by 'target' transform attribute
348+
*
349+
* @param {object} trace : full trace object
350+
* @param {object} transformOpts : transform option object
351+
* - target (string} :
352+
* either an attribute string referencing an array in the trace object, or
353+
* a set array.
354+
*
355+
* @return {array or false} : the target array (NOT a copy!!) or false if invalid
356+
*/
357+
lib.getTargetArray = function(trace, transformOpts) {
358+
var target = transformOpts.target;
359+
360+
if(typeof target === 'string' && target) {
361+
var array = lib.nestedProperty(trace, target).get();
362+
return Array.isArray(array) ? array : false;
363+
} else if(Array.isArray(target)) {
364+
return target;
365+
}
366+
367+
return false;
368+
};
369+
347370
/**
348371
* modified version of jQuery's extend to strip out private objs and functions,
349372
* and cut arrays down to first <arraylen> or 1 elements

‎src/plots/cartesian/axes.js

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,13 @@ var ONEMIN = constants.ONEMIN;
2929
var ONESEC = constants.ONESEC;
3030
var BADNUM = constants.BADNUM;
3131

32-
3332
var axes = module.exports = {};
3433

3534
axes.layoutAttributes = require('./layout_attributes');
3635
axes.supplyLayoutDefaults = require('./layout_defaults');
3736

3837
axes.setConvert = require('./set_convert');
38+
var autoType = require('./axis_autotype');
3939

4040
var axisIds = require('./axis_ids');
4141
axes.id2name = axisIds.id2name;
@@ -45,7 +45,6 @@ axes.listIds = axisIds.listIds;
4545
axes.getFromId = axisIds.getFromId;
4646
axes.getFromTrace = axisIds.getFromTrace;
4747

48-
4948
/*
5049
* find the list of possible axes to reference with an xref or yref attribute
5150
* and coerce it to that list
@@ -130,6 +129,48 @@ axes.coercePosition = function(containerOut, gd, coerce, axRef, attr, dflt) {
130129
containerOut[attr] = isNumeric(pos) ? Number(pos) : dflt;
131130
};
132131

132+
axes.getDataToCoordFunc = function(gd, trace, target, targetArray) {
133+
var ax;
134+
135+
// If target points to an axis, use the type we already have for that
136+
// axis to find the data type. Otherwise use the values to autotype.
137+
var d2cTarget = (target === 'x' || target === 'y' || target === 'z') ?
138+
target :
139+
targetArray;
140+
141+
// In the case of an array target, make a mock data array
142+
// and call supplyDefaults to the data type and
143+
// setup the data-to-calc method.
144+
if(Array.isArray(d2cTarget)) {
145+
ax = {
146+
type: autoType(targetArray),
147+
_categories: []
148+
};
149+
axes.setConvert(ax);
150+
151+
// build up ax._categories (usually done during ax.makeCalcdata()
152+
if(ax.type === 'category') {
153+
for(var i = 0; i < targetArray.length; i++) {
154+
ax.d2c(targetArray[i]);
155+
}
156+
}
157+
} else {
158+
ax = axes.getFromTrace(gd, trace, d2cTarget);
159+
}
160+
161+
// if 'target' has corresponding axis
162+
// -> use setConvert method
163+
if(ax) return ax.d2c;
164+
165+
// special case for 'ids'
166+
// -> cast to String
167+
if(d2cTarget === 'ids') return function(v) { return String(v); };
168+
169+
// otherwise (e.g. numeric-array of 'marker.color' or 'marker.size')
170+
// -> cast to Number
171+
return function(v) { return +v; };
172+
};
173+
133174
// empty out types for all axes containing these traces
134175
// so we auto-set them again
135176
axes.clearTypes = function(gd, traces) {

‎src/transforms/filter.js

Lines changed: 8 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,7 @@
1111
var Lib = require('../lib');
1212
var Registry = require('../registry');
1313
var PlotSchema = require('../plot_api/plot_schema');
14-
var axisIds = require('../plots/cartesian/axis_ids');
15-
var autoType = require('../plots/cartesian/axis_autotype');
16-
var setConvert = require('../plots/cartesian/set_convert');
14+
var Axes = require('../plots/cartesian/axes');
1715

1816
var COMPARISON_OPS = ['=', '!=', '<', '>=', '>', '<='];
1917
var INTERVAL_OPS = ['[]', '()', '[)', '(]', '][', ')(', '](', ')['];
@@ -144,12 +142,11 @@ exports.supplyDefaults = function(transformIn) {
144142
exports.calcTransform = function(gd, trace, opts) {
145143
if(!opts.enabled) return;
146144

147-
var target = opts.target,
148-
filterArray = getFilterArray(trace, target),
149-
len = filterArray.length;
150-
151-
if(!len) return;
145+
var targetArray = Lib.getTargetArray(trace, opts);
146+
if(!targetArray) return;
152147

148+
var target = opts.target;
149+
var len = targetArray.length;
153150
var targetCalendar = opts.targetcalendar;
154151

155152
// even if you provide targetcalendar, if target is a string and there
@@ -159,13 +156,8 @@ exports.calcTransform = function(gd, trace, opts) {
159156
if(attrTargetCalendar) targetCalendar = attrTargetCalendar;
160157
}
161158

162-
// if target points to an axis, use the type we already have for that
163-
// axis to find the data type. Otherwise use the values to autotype.
164-
var d2cTarget = (target === 'x' || target === 'y' || target === 'z') ?
165-
target : filterArray;
166-
167-
var dataToCoord = getDataToCoordFunc(gd, trace, d2cTarget);
168-
var filterFunc = getFilterFunc(opts, dataToCoord, targetCalendar);
159+
var d2c = Axes.getDataToCoordFunc(gd, trace, target, targetArray);
160+
var filterFunc = getFilterFunc(opts, d2c, targetCalendar);
169161
var arrayAttrs = PlotSchema.findArrayAttributes(trace);
170162
var originalArrays = {};
171163

@@ -203,60 +195,11 @@ exports.calcTransform = function(gd, trace, opts) {
203195

204196
// loop through filter array, fill trace arrays if passed
205197
for(var i = 0; i < len; i++) {
206-
var passed = filterFunc(filterArray[i]);
198+
var passed = filterFunc(targetArray[i]);
207199
if(passed) forAllAttrs(fillFn, i);
208200
}
209201
};
210202

211-
function getFilterArray(trace, target) {
212-
if(typeof target === 'string' && target) {
213-
var array = Lib.nestedProperty(trace, target).get();
214-
215-
return Array.isArray(array) ? array : [];
216-
}
217-
else if(Array.isArray(target)) return target.slice();
218-
219-
return false;
220-
}
221-
222-
function getDataToCoordFunc(gd, trace, target) {
223-
var ax;
224-
225-
// In the case of an array target, make a mock data array
226-
// and call supplyDefaults to the data type and
227-
// setup the data-to-calc method.
228-
if(Array.isArray(target)) {
229-
ax = {
230-
type: autoType(target),
231-
_categories: []
232-
};
233-
234-
setConvert(ax);
235-
236-
if(ax.type === 'category') {
237-
// build up ax._categories (usually done during ax.makeCalcdata()
238-
for(var i = 0; i < target.length; i++) {
239-
ax.d2c(target[i]);
240-
}
241-
}
242-
}
243-
else {
244-
ax = axisIds.getFromTrace(gd, trace, target);
245-
}
246-
247-
// if 'target' has corresponding axis
248-
// -> use setConvert method
249-
if(ax) return ax.d2c;
250-
251-
// special case for 'ids'
252-
// -> cast to String
253-
if(target === 'ids') return function(v) { return String(v); };
254-
255-
// otherwise (e.g. numeric-array of 'marker.color' or 'marker.size')
256-
// -> cast to Number
257-
return function(v) { return +v; };
258-
}
259-
260203
function getFilterFunc(opts, d2c, targetCalendar) {
261204
var operation = opts.operation,
262205
value = opts.value,

‎src/transforms/sort.js

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
/**
2+
* Copyright 2012-2017, Plotly, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
'use strict';
10+
11+
var Lib = require('../lib');
12+
var PlotSchema = require('../plot_api/plot_schema');
13+
var Axes = require('../plots/cartesian/axes');
14+
15+
exports.moduleType = 'transform';
16+
17+
exports.name = 'sort';
18+
19+
exports.attributes = {
20+
enabled: {
21+
valType: 'boolean',
22+
dflt: true,
23+
description: [
24+
'Determines whether this sort transform is enabled or disabled.'
25+
].join(' ')
26+
},
27+
target: {
28+
valType: 'string',
29+
strict: true,
30+
noBlank: true,
31+
arrayOk: true,
32+
dflt: 'x',
33+
description: [
34+
'Sets the target by which the sort transform is applied.',
35+
36+
'If a string, *target* is assumed to be a reference to a data array',
37+
'in the parent trace object.',
38+
'To sort about nested variables, use *.* to access them.',
39+
'For example, set `target` to *marker.size* to sort',
40+
'about the marker size array.',
41+
42+
'If an array, *target* is then the data array by which',
43+
'the sort transform is applied.'
44+
].join(' ')
45+
},
46+
order: {
47+
valType: 'enumerated',
48+
values: ['ascending', 'descending'],
49+
dflt: 'ascending',
50+
description: [
51+
'Sets the sort transform order.'
52+
].join(' ')
53+
}
54+
};
55+
56+
exports.supplyDefaults = function(transformIn) {
57+
var transformOut = {};
58+
59+
function coerce(attr, dflt) {
60+
return Lib.coerce(transformIn, transformOut, exports.attributes, attr, dflt);
61+
}
62+
63+
var enabled = coerce('enabled');
64+
65+
if(enabled) {
66+
coerce('target');
67+
coerce('order');
68+
}
69+
70+
return transformOut;
71+
};
72+
73+
exports.calcTransform = function(gd, trace, opts) {
74+
if(!opts.enabled) return;
75+
76+
var targetArray = Lib.getTargetArray(trace, opts);
77+
if(!targetArray) return;
78+
79+
var target = opts.target;
80+
var len = targetArray.length;
81+
var arrayAttrs = PlotSchema.findArrayAttributes(trace);
82+
var d2c = Axes.getDataToCoordFunc(gd, trace, target, targetArray);
83+
var indices = getIndices(opts, targetArray, d2c);
84+
85+
for(var i = 0; i < arrayAttrs.length; i++) {
86+
var np = Lib.nestedProperty(trace, arrayAttrs[i]);
87+
var arrayOld = np.get();
88+
var arrayNew = new Array(len);
89+
90+
for(var j = 0; j < len; j++) {
91+
arrayNew[j] = arrayOld[indices[j]];
92+
}
93+
94+
np.set(arrayNew);
95+
}
96+
};
97+
98+
function getIndices(opts, targetArray, d2c) {
99+
var len = targetArray.length;
100+
var indices = new Array(len);
101+
102+
var sortedArray = targetArray
103+
.slice()
104+
.sort(getSortFunc(opts, d2c));
105+
106+
for(var i = 0; i < len; i++) {
107+
var vTarget = targetArray[i];
108+
109+
for(var j = 0; j < len; j++) {
110+
var vSorted = sortedArray[j];
111+
112+
if(vTarget === vSorted) {
113+
indices[j] = i;
114+
115+
// clear sortedArray item to get correct
116+
// index of duplicate items (if any)
117+
sortedArray[j] = null;
118+
break;
119+
}
120+
}
121+
}
122+
123+
return indices;
124+
}
125+
126+
function getSortFunc(opts, d2c) {
127+
switch(opts.order) {
128+
case 'ascending':
129+
return function(a, b) { return d2c(a) - d2c(b); };
130+
case 'descending':
131+
return function(a, b) { return d2c(b) - d2c(a); };
132+
}
133+
}
Lines changed: 343 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,343 @@
1+
var Plotly = require('@lib/index');
2+
var Plots = require('@src/plots/plots');
3+
var Lib = require('@src/lib');
4+
5+
var d3 = require('d3');
6+
var createGraphDiv = require('../assets/create_graph_div');
7+
var destroyGraphDiv = require('../assets/destroy_graph_div');
8+
var fail = require('../assets/fail_test');
9+
var mouseEvent = require('../assets/mouse_event');
10+
11+
describe('Test sort transform defaults:', function() {
12+
function _supply(trace, layout) {
13+
layout = layout || {};
14+
return Plots.supplyTraceDefaults(trace, 0, layout);
15+
}
16+
17+
it('should coerce all attributes', function() {
18+
var out = _supply({
19+
x: [1, 2, 3],
20+
y: [0, 2, 1],
21+
transforms: [{
22+
type: 'sort',
23+
target: 'marker.size',
24+
order: 'descending'
25+
}]
26+
});
27+
28+
expect(out.transforms[0].type).toEqual('sort');
29+
expect(out.transforms[0].target).toEqual('marker.size');
30+
expect(out.transforms[0].order).toEqual('descending');
31+
expect(out.transforms[0].enabled).toBe(true);
32+
});
33+
34+
it('should skip unsettable attribute when `enabled: false`', function() {
35+
var out = _supply({
36+
x: [1, 2, 3],
37+
y: [0, 2, 1],
38+
transforms: [{
39+
type: 'sort',
40+
enabled: false,
41+
target: 'marker.size',
42+
order: 'descending'
43+
}]
44+
});
45+
46+
expect(out.transforms[0].type).toEqual('sort');
47+
expect(out.transforms[0].target).toBeUndefined();
48+
expect(out.transforms[0].order).toBeUndefined();
49+
expect(out.transforms[0].enabled).toBe(false);
50+
});
51+
});
52+
53+
describe('Test sort transform calc:', function() {
54+
var base = {
55+
x: [-2, -1, -2, 0, 1, 3, 1],
56+
y: [1, 2, 3, 1, 2, 3, 1],
57+
ids: ['n0', 'n1', 'n2', 'z', 'p1', 'p2', 'p3'],
58+
marker: {
59+
color: [0.1, 0.2, 0.3, 0.1, 0.2, 0.3, 0.4],
60+
size: [10, 20, 5, 1, 6, 0, 10]
61+
},
62+
transforms: [{ type: 'sort' }]
63+
};
64+
65+
function extend(update) {
66+
return Lib.extendDeep({}, base, update);
67+
}
68+
69+
function calcDatatoTrace(calcTrace) {
70+
return calcTrace[0].trace;
71+
}
72+
73+
function _transform(data, layout) {
74+
var gd = {
75+
data: data,
76+
layout: layout || {}
77+
};
78+
79+
Plots.supplyDefaults(gd);
80+
Plots.doCalcdata(gd);
81+
82+
return gd.calcdata.map(calcDatatoTrace);
83+
}
84+
85+
it('should sort all array attributes (ascending case)', function() {
86+
var out = _transform([extend({})]);
87+
88+
expect(out[0].x).toEqual([-2, -2, -1, 0, 1, 1, 3]);
89+
expect(out[0].y).toEqual([1, 3, 2, 1, 2, 1, 3]);
90+
expect(out[0].ids).toEqual(['n0', 'n2', 'n1', 'z', 'p1', 'p3', 'p2']);
91+
expect(out[0].marker.color).toEqual([0.1, 0.3, 0.2, 0.1, 0.2, 0.4, 0.3]);
92+
expect(out[0].marker.size).toEqual([10, 5, 20, 1, 6, 10, 0]);
93+
});
94+
95+
it('should sort all array attributes (descending case)', function() {
96+
var out = _transform([extend({
97+
transforms: [{
98+
order: 'descending'
99+
}]
100+
})]);
101+
102+
expect(out[0].x).toEqual([3, 1, 1, 0, -1, -2, -2]);
103+
expect(out[0].y).toEqual([3, 2, 1, 1, 2, 1, 3]);
104+
expect(out[0].ids).toEqual(['p2', 'p1', 'p3', 'z', 'n1', 'n0', 'n2']);
105+
expect(out[0].marker.color).toEqual([0.3, 0.2, 0.4, 0.1, 0.2, 0.1, 0.3]);
106+
expect(out[0].marker.size).toEqual([0, 6, 10, 1, 20, 10, 5]);
107+
});
108+
109+
it('should sort via nested targets', function() {
110+
var out = _transform([extend({
111+
transforms: [{
112+
target: 'marker.size',
113+
order: 'descending'
114+
}]
115+
})]);
116+
117+
expect(out[0].x).toEqual([-1, -2, 1, 1, -2, 0, 3]);
118+
expect(out[0].y).toEqual([2, 1, 1, 2, 3, 1, 3]);
119+
expect(out[0].ids).toEqual(['n1', 'n0', 'p3', 'p1', 'n2', 'z', 'p2']);
120+
expect(out[0].marker.color).toEqual([0.2, 0.1, 0.4, 0.2, 0.3, 0.1, 0.3]);
121+
expect(out[0].marker.size).toEqual([20, 10, 10, 6, 5, 1, 0]);
122+
});
123+
124+
it('should sort via dates targets', function() {
125+
var out = _transform([{
126+
x: ['2015-07-20', '2016-12-02', '2016-09-01', '2016-10-21', '2016-10-20'],
127+
y: [0, 1, 2, 3, 4, 5],
128+
transforms: [{ type: 'sort' }]
129+
}]);
130+
131+
expect(out[0].x).toEqual([
132+
'2015-07-20', '2016-09-01', '2016-10-20', '2016-10-21', '2016-12-02'
133+
]);
134+
expect(out[0].y).toEqual([0, 2, 4, 3, 1]);
135+
});
136+
137+
it('should sort via custom targets', function() {
138+
var out = _transform([extend({
139+
transforms: [{
140+
target: [10, 20, 30, 10, 20, 30, 0]
141+
}]
142+
})]);
143+
144+
expect(out[0].x).toEqual([1, -2, 0, -1, 1, -2, 3]);
145+
expect(out[0].y).toEqual([1, 1, 1, 2, 2, 3, 3]);
146+
expect(out[0].ids).toEqual(['p3', 'n0', 'z', 'n1', 'p1', 'n2', 'p2']);
147+
expect(out[0].marker.color).toEqual([0.4, 0.1, 0.1, 0.2, 0.2, 0.3, 0.3]);
148+
expect(out[0].marker.size).toEqual([10, 10, 1, 20, 6, 5, 0]);
149+
});
150+
151+
it('should truncate transformed arrays to target array length (short target case)', function() {
152+
var out = _transform([
153+
extend({
154+
transforms: [{
155+
order: 'descending',
156+
target: [0, 1]
157+
}]
158+
}
159+
), extend({
160+
text: ['A', 'B'],
161+
transforms: [{ target: 'text' }]
162+
})]);
163+
164+
expect(out[0].x).toEqual([-1, -2]);
165+
expect(out[0].y).toEqual([2, 1]);
166+
expect(out[0].ids).toEqual(['n1', 'n0']);
167+
expect(out[0].marker.color).toEqual([0.2, 0.1]);
168+
expect(out[0].marker.size).toEqual([20, 10]);
169+
170+
expect(out[1].x).toEqual([-2, -1]);
171+
expect(out[1].y).toEqual([1, 2]);
172+
expect(out[1].ids).toEqual(['n0', 'n1']);
173+
expect(out[1].marker.color).toEqual([0.1, 0.2]);
174+
expect(out[1].marker.size).toEqual([10, 20]);
175+
});
176+
177+
it('should truncate transformed arrays to target array length (long target case)', function() {
178+
var out = _transform([
179+
extend({
180+
transforms: [{
181+
order: 'descending',
182+
target: [0, 1, 2, 0, 1, 2, 3, 0, 1, 2, 3]
183+
}]
184+
}
185+
), extend({
186+
text: ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'],
187+
transforms: [{ target: 'text' }]
188+
})]);
189+
190+
expect(out[0].x).toEqual([1, undefined, -2, 3, undefined, -1, 1, undefined, -2, 0, undefined]);
191+
expect(out[0].y).toEqual([1, undefined, 3, 3, undefined, 2, 2, undefined, 1, 1, undefined]);
192+
expect(out[0].ids).toEqual(['p3', undefined, 'n2', 'p2', undefined, 'n1', 'p1', undefined, 'n0', 'z', undefined]);
193+
expect(out[0].marker.color).toEqual([0.4, undefined, 0.3, 0.3, undefined, 0.2, 0.2, undefined, 0.1, 0.1, undefined]);
194+
expect(out[0].marker.size).toEqual([10, undefined, 5, 0, undefined, 20, 6, undefined, 10, 1, undefined]);
195+
196+
expect(out[1].x).toEqual([-2, -1, -2, 0, 1, 3, 1, undefined, undefined]);
197+
expect(out[1].y).toEqual([1, 2, 3, 1, 2, 3, 1, undefined, undefined]);
198+
expect(out[1].ids).toEqual(['n0', 'n1', 'n2', 'z', 'p1', 'p2', 'p3', undefined, undefined]);
199+
expect(out[1].marker.color).toEqual([0.1, 0.2, 0.3, 0.1, 0.2, 0.3, 0.4, undefined, undefined]);
200+
expect(out[1].marker.size).toEqual([10, 20, 5, 1, 6, 0, 10, undefined, undefined]);
201+
});
202+
});
203+
204+
describe('Test sort transform interactions:', function() {
205+
afterEach(destroyGraphDiv);
206+
207+
function _assertFirst(p) {
208+
var parts = d3.select('.point').attr('d').split(',').slice(0, 3).join(',');
209+
expect(parts).toEqual(p);
210+
}
211+
212+
it('should respond to restyle calls', function(done) {
213+
Plotly.plot(createGraphDiv(), [{
214+
x: [-2, -1, -2, 0, 1, 3, 1],
215+
y: [1, 2, 3, 1, 2, 3, 1],
216+
marker: {
217+
size: [10, 20, 5, 1, 6, 0, 10]
218+
},
219+
transforms: [{
220+
type: 'sort',
221+
target: 'marker.size',
222+
}]
223+
}])
224+
.then(function(gd) {
225+
_assertFirst('M0,0A0,0 0 1');
226+
227+
return Plotly.restyle(gd, 'transforms[0].order', 'descending');
228+
})
229+
.then(function(gd) {
230+
_assertFirst('M10,0A10,10 0 1');
231+
232+
return Plotly.restyle(gd, 'transforms[0].enabled', false);
233+
})
234+
.then(function(gd) {
235+
_assertFirst('M5,0A5,5 0 1');
236+
237+
return Plotly.restyle(gd, 'transforms[0].enabled', true);
238+
})
239+
.then(function() {
240+
_assertFirst('M10,0A10,10 0 1');
241+
})
242+
.catch(fail)
243+
.then(done);
244+
});
245+
246+
it('does not preserve hover/click `pointNumber` value', function(done) {
247+
var gd = createGraphDiv();
248+
249+
function getPxPos(gd, id) {
250+
var trace = gd.data[0];
251+
var fullLayout = gd._fullLayout;
252+
var index = trace.ids.indexOf(id);
253+
254+
return [
255+
fullLayout.xaxis.d2p(trace.x[index]),
256+
fullLayout.yaxis.d2p(trace.y[index])
257+
];
258+
}
259+
260+
function hover(gd, id) {
261+
return new Promise(function(resolve) {
262+
gd.once('plotly_hover', function(eventData) {
263+
resolve(eventData);
264+
});
265+
266+
var pos = getPxPos(gd, id);
267+
mouseEvent('mousemove', pos[0], pos[1]);
268+
});
269+
}
270+
271+
function click(gd, id) {
272+
return new Promise(function(resolve) {
273+
gd.once('plotly_click', function(eventData) {
274+
resolve(eventData);
275+
});
276+
277+
var pos = getPxPos(gd, id);
278+
mouseEvent('mousemove', pos[0], pos[1]);
279+
mouseEvent('mousedown', pos[0], pos[1]);
280+
mouseEvent('mouseup', pos[0], pos[1]);
281+
});
282+
}
283+
284+
function wait() {
285+
return new Promise(function(resolve) {
286+
setTimeout(resolve, 60);
287+
});
288+
}
289+
290+
function assertPt(eventData, x, y, pointNumber, id) {
291+
var pt = eventData.points[0];
292+
293+
expect(pt.x).toEqual(x, 'x');
294+
expect(pt.y).toEqual(y, 'y');
295+
expect(pt.pointNumber).toEqual(pointNumber, 'pointNumber');
296+
expect(pt.fullData.ids[pt.pointNumber]).toEqual(id, 'id');
297+
}
298+
299+
Plotly.plot(gd, [{
300+
mode: 'markers',
301+
x: [-2, -1, -2, 0, 1, 3, 1],
302+
y: [1, 2, 3, 1, 2, 3, 1],
303+
ids: ['A', 'B', 'C', 'D', 'E', 'F', 'G'],
304+
marker: {
305+
size: [10, 20, 5, 1, 6, 0, 10]
306+
},
307+
transforms: [{
308+
enabled: false,
309+
type: 'sort',
310+
target: 'marker.size',
311+
}]
312+
}], {
313+
width: 500,
314+
height: 500,
315+
margin: {l: 0, t: 0, r: 0, b: 0},
316+
hovermode: 'closest'
317+
})
318+
.then(function() { return hover(gd, 'D'); })
319+
.then(function(eventData) {
320+
assertPt(eventData, 0, 1, 3, 'D');
321+
})
322+
.then(wait)
323+
.then(function() { return click(gd, 'G'); })
324+
.then(function(eventData) {
325+
assertPt(eventData, 1, 1, 6, 'G');
326+
})
327+
.then(wait)
328+
.then(function() {
329+
return Plotly.restyle(gd, 'transforms[0].enabled', true);
330+
})
331+
.then(function() { return hover(gd, 'D'); })
332+
.then(function(eventData) {
333+
assertPt(eventData, 0, 1, 1, 'D');
334+
})
335+
.then(wait)
336+
.then(function() { return click(gd, 'G'); })
337+
.then(function(eventData) {
338+
assertPt(eventData, 1, 1, 5, 'G');
339+
})
340+
.catch(fail)
341+
.then(done);
342+
});
343+
});

0 commit comments

Comments
 (0)
Please sign in to comment.