Skip to content

Commit 19299af

Browse files
authoredMay 8, 2019
Merge pull request #3827 from plotly/issue-1097
cartesian 2dMap: implement sorting of categorical axes
2 parents 03b7e09 + c7cd1f3 commit 19299af

16 files changed

+349
-13
lines changed
 

‎src/plots/cartesian/set_convert.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ module.exports = function setConvert(ax, fullLayout) {
133133
if(ax._categoriesMap[v] !== undefined) {
134134
return ax._categoriesMap[v];
135135
} else {
136-
ax._categories.push(v);
136+
ax._categories.push(typeof v === 'number' ? String(v) : v);
137137

138138
var curLength = ax._categories.length - 1;
139139
ax._categoriesMap[v] = curLength;

‎src/traces/heatmap/calc.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -62,16 +62,16 @@ module.exports = function calc(gd, trace) {
6262
y = trace._y;
6363
zIn = trace._z;
6464
} else {
65-
x = trace.x ? xa.makeCalcdata(trace, 'x') : [];
66-
y = trace.y ? ya.makeCalcdata(trace, 'y') : [];
65+
x = trace._x = trace.x ? xa.makeCalcdata(trace, 'x') : [];
66+
y = trace._y = trace.y ? ya.makeCalcdata(trace, 'y') : [];
6767
}
6868

6969
x0 = trace.x0;
7070
dx = trace.dx;
7171
y0 = trace.y0;
7272
dy = trace.dy;
7373

74-
z = clean2dArray(zIn, trace.transpose);
74+
z = clean2dArray(zIn, trace, xa, ya);
7575

7676
if(isContour || trace.connectgaps) {
7777
trace._emptypoints = findEmpties(z);

‎src/traces/heatmap/clean_2d_array.js

+37-4
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,18 @@
99
'use strict';
1010

1111
var isNumeric = require('fast-isnumeric');
12+
var Lib = require('../../lib');
13+
var BADNUM = require('../../constants/numerical').BADNUM;
1214

13-
module.exports = function clean2dArray(zOld, transpose) {
15+
module.exports = function clean2dArray(zOld, trace, xa, ya) {
1416
var rowlen, collen, getCollen, old2new, i, j;
1517

1618
function cleanZvalue(v) {
1719
if(!isNumeric(v)) return undefined;
1820
return +v;
1921
}
2022

21-
if(transpose) {
23+
if(trace && trace.transpose) {
2224
rowlen = 0;
2325
for(i = 0; i < zOld.length; i++) rowlen = Math.max(rowlen, zOld[i].length);
2426
if(rowlen === 0) return false;
@@ -30,12 +32,43 @@ module.exports = function clean2dArray(zOld, transpose) {
3032
old2new = function(zOld, i, j) { return zOld[i][j]; };
3133
}
3234

35+
var padOld2new = function(zOld, i, j) {
36+
if(i === BADNUM || j === BADNUM) return BADNUM;
37+
return old2new(zOld, i, j);
38+
};
39+
40+
function axisMapping(ax) {
41+
if(trace && trace.type !== 'carpet' && trace.type !== 'contourcarpet' &&
42+
ax && ax.type === 'category' && trace['_' + ax._id.charAt(0)].length) {
43+
var axLetter = ax._id.charAt(0);
44+
var axMapping = {};
45+
var traceCategories = trace['_' + axLetter + 'CategoryMap'] || trace[axLetter];
46+
for(i = 0; i < traceCategories.length; i++) {
47+
axMapping[traceCategories[i]] = i;
48+
}
49+
return function(i) {
50+
var ind = axMapping[ax._categories[i]];
51+
return ind + 1 ? ind : BADNUM;
52+
};
53+
} else {
54+
return Lib.identity;
55+
}
56+
}
57+
58+
var xMap = axisMapping(xa);
59+
var yMap = axisMapping(ya);
60+
3361
var zNew = new Array(rowlen);
3462

63+
if(ya && ya.type === 'category') rowlen = ya._categories.length;
3564
for(i = 0; i < rowlen; i++) {
36-
collen = getCollen(zOld, i);
65+
if(xa && xa.type === 'category') {
66+
collen = xa._categories.length;
67+
} else {
68+
collen = getCollen(zOld, i);
69+
}
3770
zNew[i] = new Array(collen);
38-
for(j = 0; j < collen; j++) zNew[i][j] = cleanZvalue(old2new(zOld, i, j));
71+
for(j = 0; j < collen; j++) zNew[i][j] = cleanZvalue(padOld2new(zOld, yMap(i), xMap(j)));
3972
}
4073

4174
return zNew;

‎src/traces/heatmap/convert_column_xyz.js

+8
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,12 @@ module.exports = function convertColumnData(trace, ax1, ax2, var1Name, var2Name,
6565
}
6666
if(hasColumnText) trace._text = text;
6767
if(hasColumnHoverText) trace._hovertext = hovertext;
68+
69+
if(ax1 && ax1.type === 'category') {
70+
trace['_' + var1Name + 'CategoryMap'] = col1vals.map(function(v) { return ax1._categories[v];});
71+
}
72+
73+
if(ax2 && ax2.type === 'category') {
74+
trace['_' + var2Name + 'CategoryMap'] = col2vals.map(function(v) { return ax2._categories[v];});
75+
}
6876
};

‎src/traces/heatmap/hover.js

+4
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ module.exports = function hoverPoints(pointData, xval, yval, hovermode, hoverLay
7878
} else {
7979
xl = xc ? xc[nx] : ((x[nx] + x[nx + 1]) / 2);
8080
yl = yc ? yc[ny] : ((y[ny] + y[ny + 1]) / 2);
81+
82+
if(xa && xa.type === 'category') xl = x[nx];
83+
if(ya && ya.type === 'category') yl = y[ny];
84+
8185
if(trace.zsmooth) {
8286
x0 = x1 = xa.c2p(xl);
8387
y0 = y1 = ya.c2p(yl);
13.6 KB
Loading
21.1 KB
Loading
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"layout": {
3+
"xaxis": {
4+
"type": "category",
5+
"categoryorder": "category descending"
6+
},
7+
"yaxis": {
8+
"type": "category",
9+
"categoryorder": "category descending"
10+
},
11+
"height": 400,
12+
"width": 400
13+
},
14+
"data": [{
15+
"type": "heatmap",
16+
17+
"x": ["z", "y", "x", "w"],
18+
"y": ["d", "c", "b", "a"],
19+
"z": [
20+
[100, 75, 50, 0],
21+
[90, 65, 40, 0],
22+
[80, 55, 30, 0],
23+
[0, 0, 0, 0]
24+
]
25+
}]
26+
}
+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"data": [{
3+
"type": "heatmap",
4+
"x": ["a", "a", "a", "b", "b", "b", "c", "c", "c"],
5+
"y": ["A", "B", "C", "A", "B", "C", "A", "B", "C"],
6+
"z": [0, 50, 100, 50, 0, 255, 100, 510, 1010]
7+
}],
8+
"layout": {
9+
"xaxis": {
10+
"categoryorder": "category descending"
11+
}
12+
}
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
{
2+
"data": [{
3+
"type": "heatmap",
4+
"x": ["Team A", "Team B", "Team C"],
5+
"xaxis": "x",
6+
"y": ["Game Three", "Game Two", "Game One"],
7+
"z": [
8+
[0.1, 0.3, 0.5],
9+
[1, 0.8, 0.6],
10+
[0.6, 0.4, 0.2]
11+
],
12+
"yaxis": "y",
13+
"coloraxis": "coloraxis"
14+
}, {
15+
"type": "heatmap",
16+
"x": ["Team B", "Team C"],
17+
"xaxis": "x",
18+
"y": ["Game Three", "Game Two", "Game One"],
19+
"z": [
20+
[0.3, 0.5],
21+
[0.8, 0.6],
22+
[0.4, 0.2]
23+
],
24+
"yaxis": "y2",
25+
"coloraxis": "coloraxis"
26+
}],
27+
"layout": {
28+
"xaxis": {
29+
"anchor": "y2",
30+
"domain": [0, 1],
31+
"type": "category",
32+
"range": [-0.5, 2.5],
33+
"autorange": true
34+
},
35+
"yaxis": {
36+
"anchor": "free",
37+
"domain": [0.575, 1],
38+
"position": 0,
39+
"type": "category",
40+
"range": [-0.5, 2.5],
41+
"autorange": true
42+
},
43+
"yaxis2": {
44+
"anchor": "x",
45+
"domain": [0, 0.425],
46+
"type": "category",
47+
"range": [-0.5, 2.5],
48+
"autorange": true
49+
},
50+
"coloraxis": {
51+
"colorscale": "RdBu"
52+
}
53+
}
54+
}

‎test/jasmine/tests/axes_test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -3096,7 +3096,7 @@ describe('Test axes', function() {
30963096
x: new Float32Array([3, 1, 2]),
30973097
}, 'x', 'category');
30983098
expect(out).toEqual([0, 1, 2]);
3099-
expect(ax._categories).toEqual([3, 1, 2]);
3099+
expect(ax._categories).toEqual(['3', '1', '2']);
31003100
});
31013101

31023102
it('- on a date axis', function() {

‎test/jasmine/tests/calcdata_test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -862,7 +862,7 @@ describe('calculated data and points', function() {
862862
xaxis: {type: 'category'}
863863
});
864864

865-
expect(gd._fullLayout.xaxis._categories).toEqual(['a', 'b', 1]);
865+
expect(gd._fullLayout.xaxis._categories).toEqual(['a', 'b', '1']);
866866
expect(gd._fullLayout.xaxis._categoriesMap).toEqual({
867867
'1': 2,
868868
'a': 0,

‎test/jasmine/tests/contour_test.js

+63-1
Original file line numberDiff line numberDiff line change
@@ -183,17 +183,28 @@ describe('contour makeColorMap', function() {
183183
describe('contour calc', function() {
184184
'use strict';
185185

186-
function _calc(opts) {
186+
function _calc(opts, layout) {
187187
var base = { type: 'contour' };
188188
var trace = Lib.extendFlat({}, base, opts);
189189
var gd = { data: [trace] };
190+
if(layout) gd.layout = layout;
190191

191192
supplyAllDefaults(gd);
192193
var fullTrace = gd._fullData[0];
194+
var fullLayout = gd._fullLayout;
193195
fullTrace._extremes = {};
194196

197+
// we used to call ax.setScale during supplyDefaults, and this had a
198+
// fallback to provide _categories and _categoriesMap. Now neither of
199+
// those is true... anyway the right way to do this though is
200+
// ax.clearCalc.
201+
fullLayout.xaxis.clearCalc();
202+
fullLayout.yaxis.clearCalc();
203+
195204
var out = Contour.calc(gd, fullTrace)[0];
196205
out.trace = fullTrace;
206+
out._xcategories = fullLayout.xaxis._categories;
207+
out._ycategories = fullLayout.yaxis._categories;
197208
return out;
198209
}
199210

@@ -343,6 +354,57 @@ describe('contour calc', function() {
343354
});
344355
});
345356
});
357+
358+
['contour'].forEach(function(traceType) {
359+
it('should sort z data based on axis categoryorder for ' + traceType, function() {
360+
var mock = require('@mocks/heatmap_categoryorder');
361+
var mockCopy = Lib.extendDeep({}, mock);
362+
var data = mockCopy.data[0];
363+
data.type = traceType;
364+
var layout = mockCopy.layout;
365+
366+
// sort x axis categories
367+
var mockLayout = Lib.extendDeep({}, layout);
368+
var out = _calc(data, mockLayout);
369+
mockLayout.xaxis.categoryorder = 'category ascending';
370+
var out1 = _calc(data, mockLayout);
371+
372+
expect(out._xcategories).toEqual(out1._xcategories.slice().reverse());
373+
// Check z data is also sorted
374+
for(var i = 0; i < out.z.length; i++) {
375+
expect(out1.z[i]).toEqual(out.z[i].slice().reverse());
376+
}
377+
378+
// sort y axis categories
379+
mockLayout = Lib.extendDeep({}, layout);
380+
out = _calc(data, mockLayout);
381+
mockLayout.yaxis.categoryorder = 'category ascending';
382+
out1 = _calc(data, mockLayout);
383+
384+
expect(out._ycategories).toEqual(out1._ycategories.slice().reverse());
385+
// Check z data is also sorted
386+
expect(out1.z).toEqual(out.z.slice().reverse());
387+
});
388+
389+
it('should sort z data based on axis categoryarray ' + traceType, function() {
390+
var mock = require('@mocks/heatmap_categoryorder');
391+
var mockCopy = Lib.extendDeep({}, mock);
392+
var data = mockCopy.data[0];
393+
data.type = traceType;
394+
var layout = mockCopy.layout;
395+
396+
layout.xaxis.categoryorder = 'array';
397+
layout.xaxis.categoryarray = ['x', 'z', 'y', 'w'];
398+
layout.yaxis.categoryorder = 'array';
399+
layout.yaxis.categoryarray = ['a', 'd', 'b', 'c'];
400+
401+
var out = _calc(data, layout);
402+
403+
expect(out._xcategories).toEqual(layout.xaxis.categoryarray, 'xaxis should reorder');
404+
expect(out._ycategories).toEqual(layout.yaxis.categoryarray, 'yaxis should reorder');
405+
expect(out.z[0][0]).toEqual(0);
406+
});
407+
});
346408
});
347409

348410
describe('contour plotting and editing', function() {

0 commit comments

Comments
 (0)
Please sign in to comment.