Skip to content

Commit 7de0c7f

Browse files
authored
Merge pull request #2261 from TomDemulierChevret/localise-auto-formatted-x-axis-date-ticks
Localize auto-formatted x-axis date ticks
2 parents f04b081 + 58acd27 commit 7de0c7f

File tree

9 files changed

+111
-68
lines changed

9 files changed

+111
-68
lines changed

lib/locales/fr.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,10 @@ module.exports = {
8484
],
8585
date: '%d/%m/%Y',
8686
decimal: ',',
87-
thousands: ' '
87+
thousands: ' ',
88+
year: '%Y',
89+
month: '%b %Y',
90+
dayMonth: '%-d %b',
91+
dayMonthYear: '%-d %b %Y'
8892
}
8993
};

src/lib/dates.js

+7-47
Original file line numberDiff line numberDiff line change
@@ -433,18 +433,6 @@ function formatTime(x, tr) {
433433
return timeStr;
434434
}
435435

436-
// TODO: do these strings need to be localized?
437-
// ie this gives "Dec 13, 2017" but some languages may want eg "13-Dec 2017"
438-
var yearFormatD3 = '%Y';
439-
var monthFormatD3 = '%b %Y';
440-
var dayFormatD3 = '%b %-d';
441-
var yearMonthDayFormatD3 = '%b %-d, %Y';
442-
443-
function yearFormatWorld(cDate) { return cDate.formatDate('yyyy'); }
444-
function monthFormatWorld(cDate) { return cDate.formatDate('M yyyy'); }
445-
function dayFormatWorld(cDate) { return cDate.formatDate('M d'); }
446-
function yearMonthDayFormatWorld(cDate) { return cDate.formatDate('M d, yyyy'); }
447-
448436
/*
449437
* formatDate: turn a date into tick or hover label text.
450438
*
@@ -462,49 +450,21 @@ function yearMonthDayFormatWorld(cDate) { return cDate.formatDate('M d, yyyy');
462450
* the axis may choose to strip things after it when they don't change from
463451
* one tick to the next (as it does with automatic formatting)
464452
*/
465-
exports.formatDate = function(x, fmt, tr, formatter, calendar) {
466-
var headStr,
467-
dateStr;
468-
453+
exports.formatDate = function(x, fmt, tr, formatter, calendar, extraFormat) {
469454
calendar = isWorldCalendar(calendar) && calendar;
470455

471-
if(fmt) return modDateFormat(fmt, x, formatter, calendar);
472-
473-
if(calendar) {
474-
try {
475-
var dateJD = Math.floor((x + 0.05) / ONEDAY) + EPOCHJD,
476-
cDate = Registry.getComponentMethod('calendars', 'getCal')(calendar)
477-
.fromJD(dateJD);
478-
479-
if(tr === 'y') dateStr = yearFormatWorld(cDate);
480-
else if(tr === 'm') dateStr = monthFormatWorld(cDate);
481-
else if(tr === 'd') {
482-
headStr = yearFormatWorld(cDate);
483-
dateStr = dayFormatWorld(cDate);
484-
}
485-
else {
486-
headStr = yearMonthDayFormatWorld(cDate);
487-
dateStr = formatTime(x, tr);
488-
}
489-
}
490-
catch(e) { return 'Invalid'; }
491-
}
492-
else {
493-
var d = new Date(Math.floor(x + 0.05));
494-
495-
if(tr === 'y') dateStr = formatter(yearFormatD3)(d);
496-
else if(tr === 'm') dateStr = formatter(monthFormatD3)(d);
456+
if(!fmt) {
457+
if(tr === 'y') fmt = extraFormat.year;
458+
else if(tr === 'm') fmt = extraFormat.month;
497459
else if(tr === 'd') {
498-
headStr = formatter(yearFormatD3)(d);
499-
dateStr = formatter(dayFormatD3)(d);
460+
fmt = extraFormat.dayMonth + '\n' + extraFormat.year;
500461
}
501462
else {
502-
headStr = formatter(yearMonthDayFormatD3)(d);
503-
dateStr = formatTime(x, tr);
463+
return formatTime(x, tr) + '\n' + modDateFormat(extraFormat.dayMonthYear, x, formatter, calendar);
504464
}
505465
}
506466

507-
return dateStr + (headStr ? '\n' + headStr : '');
467+
return modDateFormat(fmt, x, formatter, calendar);
508468
};
509469

510470
/*

src/locale-en.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ module.exports = {
3232
decimal: '.',
3333
thousands: ',',
3434
grouping: [3],
35-
currency: ['$', '']
35+
currency: ['$', ''],
36+
year: '%Y',
37+
month: '%b %Y',
38+
dayMonth: '%b %-d',
39+
dayMonthYear: '%b %-d, %Y'
3640
}
3741
};

src/plots/cartesian/axes.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1292,7 +1292,7 @@ function formatDate(ax, out, hover, extraPrecision) {
12921292
else tr = {y: 'm', m: 'd', d: 'M', M: 'S', S: 4}[tr];
12931293
}
12941294

1295-
var dateStr = Lib.formatDate(out.x, fmt, tr, ax._dateFormat, ax.calendar),
1295+
var dateStr = Lib.formatDate(out.x, fmt, tr, ax._dateFormat, ax.calendar, ax._extraFormat),
12961296
headStr;
12971297

12981298
var splitIndex = dateStr.indexOf('\n');

src/plots/cartesian/set_convert.js

+1
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,7 @@ module.exports = function setConvert(ax, fullLayout) {
457457
var locale = fullLayout._d3locale;
458458
if(ax.type === 'date') {
459459
ax._dateFormat = locale ? locale.timeFormat.utc : d3.time.format.utc;
460+
ax._extraFormat = fullLayout._extraFormat;
460461
}
461462
// occasionally we need _numFormat to pass through
462463
// even though it won't be needed by this axis

src/plots/plots.js

+17-9
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,16 @@ plots.sendDataToCloud = function(gd) {
250250
return false;
251251
};
252252

253+
var d3FormatKeys = [
254+
'days', 'shortDays', 'months', 'shortMonths', 'periods',
255+
'dateTime', 'date', 'time',
256+
'decimal', 'thousands', 'grouping', 'currency'
257+
];
258+
259+
var extraFormatKeys = [
260+
'year', 'month', 'dayMonth', 'dayMonthYear'
261+
];
262+
253263
// Fill in default values:
254264
//
255265
// gd.data, gd.layout:
@@ -304,7 +314,7 @@ plots.supplyDefaults = function(gd) {
304314
};
305315
newFullLayout._traceWord = _(gd, 'trace');
306316

307-
var formatObj = getD3FormatObj(gd);
317+
var formatObj = getFormatObj(gd, d3FormatKeys);
308318

309319
// first fill in what we can of layout without looking at data
310320
// because fullData needs a few things from layout
@@ -341,6 +351,7 @@ plots.supplyDefaults = function(gd) {
341351
}
342352

343353
newFullLayout._d3locale = getFormatter(formatObj, newFullLayout.separators);
354+
newFullLayout._extraFormat = getFormatObj(gd, extraFormatKeys);
344355

345356
newFullLayout._initialAutoSizeIsDone = true;
346357

@@ -480,21 +491,18 @@ function remapTransformedArrays(cd0, newTrace) {
480491
}
481492
}
482493

483-
var formatKeys = [
484-
'days', 'shortDays', 'months', 'shortMonths', 'periods',
485-
'dateTime', 'date', 'time',
486-
'decimal', 'thousands', 'grouping', 'currency'
487-
];
488-
489494
/**
490-
* getD3FormatObj: use _context to get the d3.locale argument object.
495+
* getFormatObj: use _context to get the format object from locale.
496+
* Used to get d3.locale argument object and extraFormat argument object
497+
*
498+
* Regarding d3.locale argument :
491499
* decimal and thousands can be overridden later by layout.separators
492500
* grouping and currency are not presently used by our automatic number
493501
* formatting system but can be used by custom formats.
494502
*
495503
* @returns {object} d3.locale format object
496504
*/
497-
function getD3FormatObj(gd) {
505+
function getFormatObj(gd, formatKeys) {
498506
var locale = gd._context.locale;
499507
if(!locale) locale === 'en-US';
500508

test/jasmine/tests/axes_test.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -1860,7 +1860,12 @@ describe('Test axes', function() {
18601860
describe('calcTicks and tickText', function() {
18611861
function mockCalc(ax) {
18621862
ax.tickfont = {};
1863-
Axes.setConvert(ax, {separators: '.,'});
1863+
Axes.setConvert(ax, {separators: '.,', _extraFormat: {
1864+
year: '%Y',
1865+
month: '%b %Y',
1866+
dayMonth: '%b %-d',
1867+
dayMonthYear: '%b %-d, %Y'
1868+
}});
18641869
return Axes.calcTicks(ax).map(function(v) { return v.text; });
18651870
}
18661871

test/jasmine/tests/lib_date_test.js

+19-8
Original file line numberDiff line numberDiff line change
@@ -482,7 +482,12 @@ describe('dates', function() {
482482
describe('formatDate', function() {
483483
function assertFormatRounds(ms, calendar, results) {
484484
['y', 'm', 'd', 'M', 'S', 1, 2, 3, 4].forEach(function(tr, i) {
485-
expect(Lib.formatDate(ms, '', tr, utcFormat, calendar))
485+
expect(Lib.formatDate(ms, '', tr, utcFormat, calendar, {
486+
year: '%Y',
487+
month: '%b %Y',
488+
dayMonth: '%b %-d',
489+
dayMonthYear: '%b %-d, %Y'
490+
}))
486491
.toBe(results[i], calendar);
487492
});
488493
}
@@ -598,17 +603,23 @@ describe('dates', function() {
598603
});
599604

600605
it('should remove extra fractional second zeros', function() {
601-
expect(Lib.formatDate(0.1, '', 4, utcFormat)).toBe('00:00:00.0001\nJan 1, 1970');
602-
expect(Lib.formatDate(0.1, '', 3, utcFormat)).toBe('00:00:00\nJan 1, 1970');
603-
expect(Lib.formatDate(0.1, '', 0, utcFormat)).toBe('00:00:00\nJan 1, 1970');
604-
expect(Lib.formatDate(0.1, '', 'S', utcFormat)).toBe('00:00:00\nJan 1, 1970');
605-
expect(Lib.formatDate(0.1, '', 3, utcFormat, 'coptic'))
606+
var extraFormat = {
607+
year: '%Y',
608+
month: '%b %Y',
609+
dayMonth: '%b %-d',
610+
dayMonthYear: '%b %-d, %Y'
611+
};
612+
expect(Lib.formatDate(0.1, '', 4, utcFormat, null, extraFormat)).toBe('00:00:00.0001\nJan 1, 1970');
613+
expect(Lib.formatDate(0.1, '', 3, utcFormat, null, extraFormat)).toBe('00:00:00\nJan 1, 1970');
614+
expect(Lib.formatDate(0.1, '', 0, utcFormat, null, extraFormat)).toBe('00:00:00\nJan 1, 1970');
615+
expect(Lib.formatDate(0.1, '', 'S', utcFormat, null, extraFormat)).toBe('00:00:00\nJan 1, 1970');
616+
expect(Lib.formatDate(0.1, '', 3, utcFormat, 'coptic', extraFormat))
606617
.toBe('00:00:00\nKoi 23, 1686');
607618

608619
// because the decimal point is explicitly part of the format
609620
// string here, we can't remove it OR the very first zero after it.
610-
expect(Lib.formatDate(0.1, '%S.%f', null, utcFormat)).toBe('00.0001');
611-
expect(Lib.formatDate(0.1, '%S.%3f', null, utcFormat)).toBe('00.0');
621+
expect(Lib.formatDate(0.1, '%S.%f', null, utcFormat, null, extraFormat)).toBe('00.0001');
622+
expect(Lib.formatDate(0.1, '%S.%3f', null, utcFormat, null, extraFormat)).toBe('00.0');
612623
});
613624

614625
});

test/jasmine/tests/localize_test.js

+50
Original file line numberDiff line numberDiff line change
@@ -233,4 +233,54 @@ describe('localization', function() {
233233
.catch(failTest)
234234
.then(done);
235235
});
236+
237+
it('uses extraFormat to localize the autoFormatted x-axis date tick', function(done) {
238+
plot('test')
239+
.then(function() {
240+
// test format.month
241+
expect(firstXLabel()).toBe('Jan 2001');
242+
return Plotly.update(gd, {x: [['2001-01-01', '2001-02-01']]});
243+
})
244+
.then(function() {
245+
// test format.dayMonth & format.year
246+
expect(firstXLabel()).toBe('Dec 312000');
247+
248+
return Plotly.update(gd, {x: [['2001-01-01', '2001-01-02']]});
249+
})
250+
.then(function() {
251+
// test format.dayMonthYear
252+
expect(firstXLabel()).toBe('00:00Jan 1, 2001');
253+
254+
Plotly.register({
255+
moduleType: 'locale',
256+
name: 'test',
257+
format: {
258+
year: 'Y%Y',
259+
month: '%Y %b',
260+
dayMonth: '%-d %b',
261+
dayMonthYear: '%-d %b %Y'
262+
}
263+
});
264+
265+
return Plotly.update(gd, {x: [['2001-01-01', '2002-01-01']]});
266+
})
267+
.then(function() {
268+
// test format.month
269+
expect(firstXLabel()).toBe('2001 Jan');
270+
271+
return Plotly.update(gd, {x: [['2001-01-01', '2001-02-01']]});
272+
})
273+
.then(function() {
274+
// test format.dayMonth & format.year
275+
expect(firstXLabel()).toBe('31 DecY2000');
276+
277+
return Plotly.update(gd, {x: [['2001-01-01', '2001-01-02']]});
278+
})
279+
.then(function() {
280+
// test format.dayMonthYear
281+
expect(firstXLabel()).toBe('00:001 Jan 2001');
282+
})
283+
.catch(failTest)
284+
.then(done);
285+
});
236286
});

0 commit comments

Comments
 (0)