Skip to content

Commit 8d446be

Browse files
committed
count *distinct* values for category and date axis autotype
1 parent 7176e7a commit 8d446be

File tree

2 files changed

+88
-21
lines changed

2 files changed

+88
-21
lines changed

src/plots/cartesian/axis_autotype.js

+22-13
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,20 @@ function linearOK(array) {
3838
// 2- or 4-digit integers can be both, so require twice as many
3939
// dates as non-dates, to exclude cases with mostly 2 & 4 digit
4040
// numbers and a few dates
41+
// as with categories, consider DISTINCT values only.
4142
function moreDates(a, calendar) {
42-
var dcnt = 0,
43-
ncnt = 0,
44-
// test at most 1000 points, evenly spaced
45-
inc = Math.max(1, (a.length - 1) / 1000),
46-
ai;
43+
// test at most 1000 points, evenly spaced
44+
var inc = Math.max(1, (a.length - 1) / 1000);
45+
var dcnt = 0;
46+
var ncnt = 0;
47+
var seen = {};
4748

4849
for(var i = 0; i < a.length; i += inc) {
49-
ai = a[Math.round(i)];
50+
var ai = a[Math.round(i)];
51+
var stri = String(ai);
52+
if(seen[stri]) continue;
53+
seen[stri] = 1;
54+
5055
if(Lib.isDateTime(ai, calendar)) dcnt += 1;
5156
if(isNumeric(ai)) ncnt += 1;
5257
}
@@ -55,19 +60,23 @@ function moreDates(a, calendar) {
5560
}
5661

5762
// are the (x,y)-values in gd.data mostly text?
58-
// require twice as many categories as numbers
63+
// require twice as many DISTINCT categories as distinct numbers
5964
function category(a) {
6065
// test at most 1000 points
61-
var inc = Math.max(1, (a.length - 1) / 1000),
62-
curvenums = 0,
63-
curvecats = 0,
64-
ai;
66+
var inc = Math.max(1, (a.length - 1) / 1000);
67+
var curvenums = 0;
68+
var curvecats = 0;
69+
var seen = {};
6570

6671
for(var i = 0; i < a.length; i += inc) {
67-
ai = a[Math.round(i)];
72+
var ai = a[Math.round(i)];
73+
var stri = String(ai);
74+
if(seen[stri]) continue;
75+
seen[stri] = 1;
76+
6877
if(typeof ai === 'boolean') curvecats++;
6978
else if(Lib.cleanNumber(ai) !== BADNUM) curvenums++;
70-
else if(typeof ai === 'string' && ai !== '' && ai !== 'None') curvecats++;
79+
else if(typeof ai === 'string') curvecats++;
7180
}
7281

7382
return curvecats > curvenums * 2;

test/jasmine/tests/axes_test.js

+66-8
Original file line numberDiff line numberDiff line change
@@ -190,18 +190,76 @@ describe('Test axes', function() {
190190
});
191191

192192
describe('autotype', function() {
193-
function fullTrace(mods) {
194-
return Lib.extendDeep(
193+
function supplyWithTrace(trace) {
194+
var fullTrace = Lib.extendDeep(
195195
{type: 'scatter', xaxis: 'x', yaxis: 'y'},
196-
mods
196+
trace
197197
);
198+
layoutIn = {xaxis: {}, yaxis: {}};
199+
supplyLayoutDefaults(layoutIn, layoutOut, [fullTrace]);
200+
}
201+
202+
function checkTypes(xType, yType, msg) {
203+
expect(layoutOut.xaxis.type).toBe(xType, msg);
204+
expect(layoutOut.yaxis.type).toBe(yType, msg);
198205
}
206+
199207
it('treats booleans as categories', function() {
200-
fullData = [fullTrace({x: [0, 1, 2], y: [true, false, true]})];
201-
layoutIn = {xaxis: {}, yaxis: {}};
202-
supplyLayoutDefaults(layoutIn, layoutOut, fullData);
203-
expect(layoutOut.xaxis.type).toBe('linear');
204-
expect(layoutOut.yaxis.type).toBe('category');
208+
supplyWithTrace({x: [0, 1, 2], y: [true, false, true]});
209+
checkTypes('linear', 'category');
210+
});
211+
212+
it('sees a single "None" or "" as a category', function() {
213+
supplyWithTrace({x: ['None'], y: ['']});
214+
checkTypes('category', 'category');
215+
});
216+
217+
it('lets a single number beat up to two distinct categories', function() {
218+
supplyWithTrace({
219+
x: ['2.1', 'N/A', '', 'N/A', '', 'N/A', 'N/A', '', '', ''],
220+
y: [0, 'None', true, 'None', 'None', 'None', 'None', 'None']
221+
});
222+
checkTypes('linear', 'linear');
223+
});
224+
225+
it('turns back to category with >2 per distinct number', function() {
226+
supplyWithTrace({
227+
x: [4, 4, 4, 4, 4, 4, 4, 4, 'Yes', 'No', 'Maybe'],
228+
y: [1, 2, 1, 2, 1, 2, true, false, '', 'None', 'nan']
229+
});
230+
checkTypes('category', 'category');
231+
});
232+
233+
it('works with world calendars', function() {
234+
// these are only valid dates in chinese
235+
var intercalary = ['1995-08i-01', '1995-08i-29', '1984-10i-15'];
236+
supplyWithTrace({
237+
x: intercalary, xcalendar: 'chinese',
238+
y: intercalary, ycalendar: 'gregorian'
239+
});
240+
checkTypes('date', 'category');
241+
});
242+
243+
it('requires >twice as many distinct dates as numbers', function() {
244+
supplyWithTrace({
245+
x: ['2000-01-01', '2000-01-02', '2000-01-03', 1, 1.2],
246+
y: ['2000-01', '2000-02', '2000-03', '2000-04', 1.1]
247+
});
248+
checkTypes('linear', 'date');
249+
250+
supplyWithTrace({
251+
x: ['2000-01-01', '2000-01-02', '2000-01-03', 1, 1],
252+
y: ['2000-01', '2000-01', '2000-01', '2000-01', 1.1]
253+
});
254+
checkTypes('date', 'linear');
255+
});
256+
257+
it('counts ambiguous dates as both dates and numbers', function() {
258+
supplyWithTrace({
259+
x: ['2000', '2000-01', '2000-02'], // 3 dates, 1 number
260+
y: ['2000', '2001', '2000-01'] // 3 dates, 2 numbers
261+
});
262+
checkTypes('date', 'linear');
205263
});
206264
});
207265

0 commit comments

Comments
 (0)