Skip to content

Commit

Permalink
Merge pull request #2261 from TomDemulierChevret/localise-auto-format…
Browse files Browse the repository at this point in the history
…ted-x-axis-date-ticks

Localize auto-formatted x-axis date ticks
  • Loading branch information
alexcjohnson authored Jan 30, 2018
2 parents f04b081 + 58acd27 commit 7de0c7f
Show file tree
Hide file tree
Showing 9 changed files with 111 additions and 68 deletions.
6 changes: 5 additions & 1 deletion lib/locales/fr.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ module.exports = {
],
date: '%d/%m/%Y',
decimal: ',',
thousands: ' '
thousands: ' ',
year: '%Y',
month: '%b %Y',
dayMonth: '%-d %b',
dayMonthYear: '%-d %b %Y'
}
};
54 changes: 7 additions & 47 deletions src/lib/dates.js
Original file line number Diff line number Diff line change
Expand Up @@ -433,18 +433,6 @@ function formatTime(x, tr) {
return timeStr;
}

// TODO: do these strings need to be localized?
// ie this gives "Dec 13, 2017" but some languages may want eg "13-Dec 2017"
var yearFormatD3 = '%Y';
var monthFormatD3 = '%b %Y';
var dayFormatD3 = '%b %-d';
var yearMonthDayFormatD3 = '%b %-d, %Y';

function yearFormatWorld(cDate) { return cDate.formatDate('yyyy'); }
function monthFormatWorld(cDate) { return cDate.formatDate('M yyyy'); }
function dayFormatWorld(cDate) { return cDate.formatDate('M d'); }
function yearMonthDayFormatWorld(cDate) { return cDate.formatDate('M d, yyyy'); }

/*
* formatDate: turn a date into tick or hover label text.
*
Expand All @@ -462,49 +450,21 @@ function yearMonthDayFormatWorld(cDate) { return cDate.formatDate('M d, yyyy');
* the axis may choose to strip things after it when they don't change from
* one tick to the next (as it does with automatic formatting)
*/
exports.formatDate = function(x, fmt, tr, formatter, calendar) {
var headStr,
dateStr;

exports.formatDate = function(x, fmt, tr, formatter, calendar, extraFormat) {
calendar = isWorldCalendar(calendar) && calendar;

if(fmt) return modDateFormat(fmt, x, formatter, calendar);

if(calendar) {
try {
var dateJD = Math.floor((x + 0.05) / ONEDAY) + EPOCHJD,
cDate = Registry.getComponentMethod('calendars', 'getCal')(calendar)
.fromJD(dateJD);

if(tr === 'y') dateStr = yearFormatWorld(cDate);
else if(tr === 'm') dateStr = monthFormatWorld(cDate);
else if(tr === 'd') {
headStr = yearFormatWorld(cDate);
dateStr = dayFormatWorld(cDate);
}
else {
headStr = yearMonthDayFormatWorld(cDate);
dateStr = formatTime(x, tr);
}
}
catch(e) { return 'Invalid'; }
}
else {
var d = new Date(Math.floor(x + 0.05));

if(tr === 'y') dateStr = formatter(yearFormatD3)(d);
else if(tr === 'm') dateStr = formatter(monthFormatD3)(d);
if(!fmt) {
if(tr === 'y') fmt = extraFormat.year;
else if(tr === 'm') fmt = extraFormat.month;
else if(tr === 'd') {
headStr = formatter(yearFormatD3)(d);
dateStr = formatter(dayFormatD3)(d);
fmt = extraFormat.dayMonth + '\n' + extraFormat.year;
}
else {
headStr = formatter(yearMonthDayFormatD3)(d);
dateStr = formatTime(x, tr);
return formatTime(x, tr) + '\n' + modDateFormat(extraFormat.dayMonthYear, x, formatter, calendar);
}
}

return dateStr + (headStr ? '\n' + headStr : '');
return modDateFormat(fmt, x, formatter, calendar);
};

/*
Expand Down
6 changes: 5 additions & 1 deletion src/locale-en.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ module.exports = {
decimal: '.',
thousands: ',',
grouping: [3],
currency: ['$', '']
currency: ['$', ''],
year: '%Y',
month: '%b %Y',
dayMonth: '%b %-d',
dayMonthYear: '%b %-d, %Y'
}
};
2 changes: 1 addition & 1 deletion src/plots/cartesian/axes.js
Original file line number Diff line number Diff line change
Expand Up @@ -1292,7 +1292,7 @@ function formatDate(ax, out, hover, extraPrecision) {
else tr = {y: 'm', m: 'd', d: 'M', M: 'S', S: 4}[tr];
}

var dateStr = Lib.formatDate(out.x, fmt, tr, ax._dateFormat, ax.calendar),
var dateStr = Lib.formatDate(out.x, fmt, tr, ax._dateFormat, ax.calendar, ax._extraFormat),
headStr;

var splitIndex = dateStr.indexOf('\n');
Expand Down
1 change: 1 addition & 0 deletions src/plots/cartesian/set_convert.js
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,7 @@ module.exports = function setConvert(ax, fullLayout) {
var locale = fullLayout._d3locale;
if(ax.type === 'date') {
ax._dateFormat = locale ? locale.timeFormat.utc : d3.time.format.utc;
ax._extraFormat = fullLayout._extraFormat;
}
// occasionally we need _numFormat to pass through
// even though it won't be needed by this axis
Expand Down
26 changes: 17 additions & 9 deletions src/plots/plots.js
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,16 @@ plots.sendDataToCloud = function(gd) {
return false;
};

var d3FormatKeys = [
'days', 'shortDays', 'months', 'shortMonths', 'periods',
'dateTime', 'date', 'time',
'decimal', 'thousands', 'grouping', 'currency'
];

var extraFormatKeys = [
'year', 'month', 'dayMonth', 'dayMonthYear'
];

// Fill in default values:
//
// gd.data, gd.layout:
Expand Down Expand Up @@ -304,7 +314,7 @@ plots.supplyDefaults = function(gd) {
};
newFullLayout._traceWord = _(gd, 'trace');

var formatObj = getD3FormatObj(gd);
var formatObj = getFormatObj(gd, d3FormatKeys);

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

newFullLayout._d3locale = getFormatter(formatObj, newFullLayout.separators);
newFullLayout._extraFormat = getFormatObj(gd, extraFormatKeys);

newFullLayout._initialAutoSizeIsDone = true;

Expand Down Expand Up @@ -480,21 +491,18 @@ function remapTransformedArrays(cd0, newTrace) {
}
}

var formatKeys = [
'days', 'shortDays', 'months', 'shortMonths', 'periods',
'dateTime', 'date', 'time',
'decimal', 'thousands', 'grouping', 'currency'
];

/**
* getD3FormatObj: use _context to get the d3.locale argument object.
* getFormatObj: use _context to get the format object from locale.
* Used to get d3.locale argument object and extraFormat argument object
*
* Regarding d3.locale argument :
* decimal and thousands can be overridden later by layout.separators
* grouping and currency are not presently used by our automatic number
* formatting system but can be used by custom formats.
*
* @returns {object} d3.locale format object
*/
function getD3FormatObj(gd) {
function getFormatObj(gd, formatKeys) {
var locale = gd._context.locale;
if(!locale) locale === 'en-US';

Expand Down
7 changes: 6 additions & 1 deletion test/jasmine/tests/axes_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1860,7 +1860,12 @@ describe('Test axes', function() {
describe('calcTicks and tickText', function() {
function mockCalc(ax) {
ax.tickfont = {};
Axes.setConvert(ax, {separators: '.,'});
Axes.setConvert(ax, {separators: '.,', _extraFormat: {
year: '%Y',
month: '%b %Y',
dayMonth: '%b %-d',
dayMonthYear: '%b %-d, %Y'
}});
return Axes.calcTicks(ax).map(function(v) { return v.text; });
}

Expand Down
27 changes: 19 additions & 8 deletions test/jasmine/tests/lib_date_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -482,7 +482,12 @@ describe('dates', function() {
describe('formatDate', function() {
function assertFormatRounds(ms, calendar, results) {
['y', 'm', 'd', 'M', 'S', 1, 2, 3, 4].forEach(function(tr, i) {
expect(Lib.formatDate(ms, '', tr, utcFormat, calendar))
expect(Lib.formatDate(ms, '', tr, utcFormat, calendar, {
year: '%Y',
month: '%b %Y',
dayMonth: '%b %-d',
dayMonthYear: '%b %-d, %Y'
}))
.toBe(results[i], calendar);
});
}
Expand Down Expand Up @@ -598,17 +603,23 @@ describe('dates', function() {
});

it('should remove extra fractional second zeros', function() {
expect(Lib.formatDate(0.1, '', 4, utcFormat)).toBe('00:00:00.0001\nJan 1, 1970');
expect(Lib.formatDate(0.1, '', 3, utcFormat)).toBe('00:00:00\nJan 1, 1970');
expect(Lib.formatDate(0.1, '', 0, utcFormat)).toBe('00:00:00\nJan 1, 1970');
expect(Lib.formatDate(0.1, '', 'S', utcFormat)).toBe('00:00:00\nJan 1, 1970');
expect(Lib.formatDate(0.1, '', 3, utcFormat, 'coptic'))
var extraFormat = {
year: '%Y',
month: '%b %Y',
dayMonth: '%b %-d',
dayMonthYear: '%b %-d, %Y'
};
expect(Lib.formatDate(0.1, '', 4, utcFormat, null, extraFormat)).toBe('00:00:00.0001\nJan 1, 1970');
expect(Lib.formatDate(0.1, '', 3, utcFormat, null, extraFormat)).toBe('00:00:00\nJan 1, 1970');
expect(Lib.formatDate(0.1, '', 0, utcFormat, null, extraFormat)).toBe('00:00:00\nJan 1, 1970');
expect(Lib.formatDate(0.1, '', 'S', utcFormat, null, extraFormat)).toBe('00:00:00\nJan 1, 1970');
expect(Lib.formatDate(0.1, '', 3, utcFormat, 'coptic', extraFormat))
.toBe('00:00:00\nKoi 23, 1686');

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

});
Expand Down
50 changes: 50 additions & 0 deletions test/jasmine/tests/localize_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -233,4 +233,54 @@ describe('localization', function() {
.catch(failTest)
.then(done);
});

it('uses extraFormat to localize the autoFormatted x-axis date tick', function(done) {
plot('test')
.then(function() {
// test format.month
expect(firstXLabel()).toBe('Jan 2001');
return Plotly.update(gd, {x: [['2001-01-01', '2001-02-01']]});
})
.then(function() {
// test format.dayMonth & format.year
expect(firstXLabel()).toBe('Dec 312000');

return Plotly.update(gd, {x: [['2001-01-01', '2001-01-02']]});
})
.then(function() {
// test format.dayMonthYear
expect(firstXLabel()).toBe('00:00Jan 1, 2001');

Plotly.register({
moduleType: 'locale',
name: 'test',
format: {
year: 'Y%Y',
month: '%Y %b',
dayMonth: '%-d %b',
dayMonthYear: '%-d %b %Y'
}
});

return Plotly.update(gd, {x: [['2001-01-01', '2002-01-01']]});
})
.then(function() {
// test format.month
expect(firstXLabel()).toBe('2001 Jan');

return Plotly.update(gd, {x: [['2001-01-01', '2001-02-01']]});
})
.then(function() {
// test format.dayMonth & format.year
expect(firstXLabel()).toBe('31 DecY2000');

return Plotly.update(gd, {x: [['2001-01-01', '2001-01-02']]});
})
.then(function() {
// test format.dayMonthYear
expect(firstXLabel()).toBe('00:001 Jan 2001');
})
.catch(failTest)
.then(done);
});
});

0 comments on commit 7de0c7f

Please sign in to comment.