Skip to content

Commit 2d7c1f0

Browse files
hurskiy-andriyetimberg
authored andcommitted
Time axis tick formatting with major and minor units (#4268)
Working towards creating the TimeSeries scale, this PR adds formatting for major and minor ticks on axes.
1 parent 3a2884f commit 2d7c1f0

File tree

12 files changed

+161
-43
lines changed

12 files changed

+161
-43
lines changed

docs/axes/styling.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,27 @@ The tick configuration is nested under the scale configuration in the `ticks` ke
3535
| `fontSize` | `Number` | `12` | Font size for the tick labels.
3636
| `fontStyle` | `String` | `'normal'` | Font style for the tick labels, follows CSS font-style options (i.e. normal, italic, oblique, initial, inherit).
3737
| `reverse` | `Boolean` | `false` | Reverses order of tick labels.
38+
| `minor` | `object` | `{}` | Minor ticks configuration. Ommited options are inherited from options above.
39+
| `major` | `object` | `{}` | Major ticks configuration. Ommited options are inherited from options above.
40+
41+
## Minor Tick Configuration
42+
The minorTick configuration is nested under the ticks configuration in the `minor` key. It defines options for the minor tick marks that are generated by the axis. Omitted options are inherited from `ticks` configuration.
43+
44+
| Name | Type | Default | Description
45+
| -----| ---- | --------| -----------
46+
| `callback` | `Function` | | Returns the string representation of the tick value as it should be displayed on the chart. See [callback](../axes/labelling.md#creating-custom-tick-formats).
47+
| `fontColor` | Color | `'#666'` | Font color for tick labels.
48+
| `fontFamily` | `String` | `"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"` | Font family for the tick labels, follows CSS font-family options.
49+
| `fontSize` | `Number` | `12` | Font size for the tick labels.
50+
| `fontStyle` | `String` | `'normal'` | Font style for the tick labels, follows CSS font-style options (i.e. normal, italic, oblique, initial, inherit).
51+
52+
## Major Tick Configuration
53+
The majorTick configuration is nested under the ticks configuration in the `major` key. It defines options for the major tick marks that are generated by the axis. Omitted options are inherited from `ticks` configuration.
54+
55+
| Name | Type | Default | Description
56+
| -----| ---- | --------| -----------
57+
| `callback` | `Function` | | Returns the string representation of the tick value as it should be displayed on the chart. See [callback](../axes/labelling.md#creating-custom-tick-formats).
58+
| `fontColor` | Color | `'#666'` | Font color for tick labels.
59+
| `fontFamily` | `String` | `"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"` | Font family for the tick labels, follows CSS font-family options.
60+
| `fontSize` | `Number` | `12` | Font size for the tick labels.
61+
| `fontStyle` | `String` | `'normal'` | Font style for the tick labels, follows CSS font-style options (i.e. normal, italic, oblique, initial, inherit).

samples/scales/time/line-point-data.html

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,13 @@
8888
scaleLabel: {
8989
display: true,
9090
labelString: 'Date'
91-
}
91+
},
92+
ticks: {
93+
major: {
94+
fontStyle: "bold",
95+
fontColor: "#FF0000"
96+
}
97+
}
9298
}],
9399
yAxes: [{
94100
display: true,
@@ -104,7 +110,6 @@
104110
window.onload = function() {
105111
var ctx = document.getElementById("canvas").getContext("2d");
106112
window.myLine = new Chart(ctx, config);
107-
108113
};
109114

110115
document.getElementById('randomizeData').addEventListener('click', function() {

src/core/core.controller.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,7 @@ module.exports = function(Chart) {
265265
});
266266

267267
scales[scale.id] = scale;
268+
scale.mergeTicksOptions();
268269

269270
// TODO(SB): I think we should be able to remove this custom case (options.scale)
270271
// and consider it as a regular scale part of the "scales"" map only! This would

src/core/core.scale.js

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,9 @@ module.exports = function(Chart) {
4848
autoSkipPadding: 0,
4949
labelOffset: 0,
5050
// We pass through arrays to be rendered as multiline labels, we convert Others to strings here.
51-
callback: Chart.Ticks.formatters.values
51+
callback: Chart.Ticks.formatters.values,
52+
minor: {},
53+
major: {}
5254
}
5355
};
5456

@@ -94,6 +96,29 @@ module.exports = function(Chart) {
9496
// Any function defined here is inherited by all scale types.
9597
// Any function can be extended by the scale type
9698

99+
mergeTicksOptions: function() {
100+
var ticks = this.options.ticks;
101+
if (ticks.minor === false) {
102+
ticks.minor = {
103+
display: false
104+
};
105+
}
106+
if (ticks.major === false) {
107+
ticks.major = {
108+
display: false
109+
};
110+
}
111+
for (var key in ticks) {
112+
if (key !== 'major' && key !== 'minor') {
113+
if (typeof ticks.minor[key] === 'undefined') {
114+
ticks.minor[key] = ticks[key];
115+
}
116+
if (typeof ticks.major[key] === 'undefined') {
117+
ticks.major[key] = ticks[key];
118+
}
119+
}
120+
}
121+
},
97122
beforeUpdate: function() {
98123
helpers.callback(this.options.beforeUpdate, [this]);
99124
},
@@ -486,7 +511,8 @@ module.exports = function(Chart) {
486511

487512
var context = me.ctx;
488513
var globalDefaults = Chart.defaults.global;
489-
var optionTicks = options.ticks;
514+
var optionTicks = options.ticks.minor;
515+
var optionMajorTicks = options.ticks.major || optionTicks;
490516
var gridLines = options.gridLines;
491517
var scaleLabel = options.scaleLabel;
492518

@@ -503,6 +529,8 @@ module.exports = function(Chart) {
503529

504530
var tickFontColor = helpers.getValueOrDefault(optionTicks.fontColor, globalDefaults.defaultFontColor);
505531
var tickFont = parseFontOptions(optionTicks);
532+
var majorTickFontColor = helpers.getValueOrDefault(optionMajorTicks.fontColor, globalDefaults.defaultFontColor);
533+
var majorTickFont = parseFontOptions(optionMajorTicks);
506534

507535
var tl = gridLines.drawTicks ? gridLines.tickMarkLength : 0;
508536

@@ -513,9 +541,6 @@ module.exports = function(Chart) {
513541
var cosRotation = Math.cos(labelRotationRadians);
514542
var longestRotatedLabel = me.longestLabelWidth * cosRotation;
515543

516-
// Make sure we draw text in the correct color and font
517-
context.fillStyle = tickFontColor;
518-
519544
var itemsToDraw = [];
520545

521546
if (isHorizontal) {
@@ -547,7 +572,8 @@ module.exports = function(Chart) {
547572
var yTickStart = options.position === 'bottom' ? me.top : me.bottom - tl;
548573
var yTickEnd = options.position === 'bottom' ? me.top + tl : me.bottom;
549574

550-
helpers.each(me.ticks, function(label, index) {
575+
helpers.each(me.ticks, function(tick, index) {
576+
var label = (tick && tick.value) || tick;
551577
// If the callback returned a null or undefined value, do not draw this line
552578
if (label === undefined || label === null) {
553579
return;
@@ -645,6 +671,7 @@ module.exports = function(Chart) {
645671
glBorderDashOffset: borderDashOffset,
646672
rotation: -1 * labelRotationRadians,
647673
label: label,
674+
major: tick.major,
648675
textBaseline: textBaseline,
649676
textAlign: textAlign
650677
});
@@ -678,10 +705,12 @@ module.exports = function(Chart) {
678705
}
679706

680707
if (optionTicks.display) {
708+
// Make sure we draw text in the correct color and font
681709
context.save();
682710
context.translate(itemToDraw.labelX, itemToDraw.labelY);
683711
context.rotate(itemToDraw.rotation);
684-
context.font = tickFont.font;
712+
context.font = itemToDraw.major ? majorTickFont.font : tickFont.font;
713+
context.fillStyle = itemToDraw.major ? majorTickFontColor : tickFontColor;
685714
context.textBaseline = itemToDraw.textBaseline;
686715
context.textAlign = itemToDraw.textAlign;
687716

src/helpers/helpers.time.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -145,13 +145,16 @@ module.exports = function(Chart) {
145145
*/
146146
determineMajorUnit: function(unit) {
147147
var units = Object.keys(interval);
148-
var majorUnit = null;
149148
var unitIndex = units.indexOf(unit);
150-
if (unitIndex < units.length - 1) {
151-
majorUnit = units[unitIndex + 1];
149+
while (unitIndex < units.length) {
150+
var majorUnit = units[++unitIndex];
151+
// exclude 'week' and 'quarter' units
152+
if (majorUnit !== 'week' && majorUnit !== 'quarter') {
153+
return majorUnit;
154+
}
152155
}
153156

154-
return majorUnit;
157+
return null;
155158
},
156159

157160
/**

src/scales/scale.time.js

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ module.exports = function(Chart) {
2525
displayFormats: {
2626
millisecond: 'h:mm:ss.SSS a', // 11:20:01.123 AM,
2727
second: 'h:mm:ss a', // 11:20:01 AM
28-
minute: 'h:mm:ss a', // 11:20:01 AM
29-
hour: 'MMM D, hA', // Sept 4, 5PM
30-
day: 'll', // Sep 4 2015
28+
minute: 'h:mm a', // 11:20 AM
29+
hour: 'hA', // 5PM
30+
day: 'MMM D', // Sep 4
3131
week: 'll', // Week 46, or maybe "[W]WW - YYYY" ?
3232
month: 'MMM YYYY', // Sept 2015
3333
quarter: '[Q]Q - YYYY', // Q3
@@ -45,6 +45,8 @@ module.exports = function(Chart) {
4545
throw new Error('Chart.js - Moment.js could not be found! You must include it before Chart.js to use the time scale. Download at https://momentjs.com');
4646
}
4747

48+
this.mergeTicksOptions();
49+
4850
Chart.Scale.prototype.initialize.call(this);
4951
},
5052
determineDataLimits: function() {
@@ -182,14 +184,34 @@ module.exports = function(Chart) {
182184
},
183185
// Function to format an individual tick mark
184186
tickFormatFunction: function(tick, index, ticks) {
185-
var formattedTick = tick.format(this.displayFormat);
186-
var tickOpts = this.options.ticks;
187+
var formattedTick;
188+
var tickClone = tick.clone();
189+
var tickTimestamp = tick.valueOf();
190+
var major = false;
191+
var tickOpts;
192+
if (this.majorUnit && this.majorDisplayFormat && tickTimestamp === tickClone.startOf(this.majorUnit).valueOf()) {
193+
// format as major unit
194+
formattedTick = tick.format(this.majorDisplayFormat);
195+
tickOpts = this.options.ticks.major;
196+
major = true;
197+
} else {
198+
// format as minor (base) unit
199+
formattedTick = tick.format(this.displayFormat);
200+
tickOpts = this.options.ticks.minor;
201+
}
202+
187203
var callback = helpers.getValueOrDefault(tickOpts.callback, tickOpts.userCallback);
188204

189205
if (callback) {
190-
return callback(formattedTick, index, ticks);
206+
return {
207+
value: callback(formattedTick, index, ticks),
208+
major: major
209+
};
191210
}
192-
return formattedTick;
211+
return {
212+
value: formattedTick,
213+
major: major
214+
};
193215
},
194216
convertTicksToLabels: function() {
195217
var me = this;
@@ -257,11 +279,12 @@ module.exports = function(Chart) {
257279
var me = this;
258280

259281
me.displayFormat = me.options.time.displayFormats.millisecond; // Pick the longest format for guestimation
260-
var exampleLabel = me.tickFormatFunction(moment(exampleTime), 0, []);
282+
var exampleLabel = me.tickFormatFunction(moment(exampleTime), 0, []).value;
261283
var tickLabelWidth = me.getLabelWidth(exampleLabel);
262284

263285
var innerWidth = me.isHorizontal() ? me.width : me.height;
264286
var labelCapacity = innerWidth / tickLabelWidth;
287+
265288
return labelCapacity;
266289
}
267290
});

test/specs/core.helpers.tests.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,8 @@ describe('Core helper tests', function() {
215215
autoSkip: true,
216216
autoSkipPadding: 0,
217217
labelOffset: 0,
218+
minor: {},
219+
major: {},
218220
},
219221
type: 'linear'
220222
}, {
@@ -253,6 +255,8 @@ describe('Core helper tests', function() {
253255
autoSkip: true,
254256
autoSkipPadding: 0,
255257
labelOffset: 0,
258+
minor: {},
259+
major: {},
256260
},
257261
type: 'linear'
258262
}]

test/specs/scale.category.tests.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@ describe('Category scale tests', function() {
4444
callback: defaultConfig.ticks.callback, // make this nicer, then check explicitly below
4545
autoSkip: true,
4646
autoSkipPadding: 0,
47-
labelOffset: 0
47+
labelOffset: 0,
48+
minor: {},
49+
major: {},
4850
}
4951
});
5052

test/specs/scale.linear.tests.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@ describe('Linear Scale', function() {
4242
callback: defaultConfig.ticks.callback, // make this work nicer, then check below
4343
autoSkip: true,
4444
autoSkipPadding: 0,
45-
labelOffset: 0
45+
labelOffset: 0,
46+
minor: {},
47+
major: {},
4648
}
4749
});
4850

test/specs/scale.logarithmic.tests.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@ describe('Logarithmic Scale tests', function() {
4141
callback: defaultConfig.ticks.callback, // make this nicer, then check explicitly below
4242
autoSkip: true,
4343
autoSkipPadding: 0,
44-
labelOffset: 0
44+
labelOffset: 0,
45+
minor: {},
46+
major: {},
4547
},
4648
});
4749

0 commit comments

Comments
 (0)