Skip to content

Commit 0204845

Browse files
authored
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

+2-1
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

+11
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

+23
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

+43-2
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

+8-65
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

+133
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+
}

0 commit comments

Comments
 (0)