Skip to content

Commit

Permalink
Time axis tick formatting with major and minor units (#4268)
Browse files Browse the repository at this point in the history
Working towards creating the TimeSeries scale, this PR adds formatting for major and minor ticks on axes.
  • Loading branch information
hurskiy-andriy authored and etimberg committed Jun 15, 2017
1 parent 3a2884f commit 2d7c1f0
Show file tree
Hide file tree
Showing 12 changed files with 161 additions and 43 deletions.
24 changes: 24 additions & 0 deletions docs/axes/styling.md
Expand Up @@ -35,3 +35,27 @@ The tick configuration is nested under the scale configuration in the `ticks` ke
| `fontSize` | `Number` | `12` | Font size for the tick labels.
| `fontStyle` | `String` | `'normal'` | Font style for the tick labels, follows CSS font-style options (i.e. normal, italic, oblique, initial, inherit).
| `reverse` | `Boolean` | `false` | Reverses order of tick labels.
| `minor` | `object` | `{}` | Minor ticks configuration. Ommited options are inherited from options above.
| `major` | `object` | `{}` | Major ticks configuration. Ommited options are inherited from options above.

## Minor Tick Configuration
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.

| Name | Type | Default | Description
| -----| ---- | --------| -----------
| `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).
| `fontColor` | Color | `'#666'` | Font color for tick labels.
| `fontFamily` | `String` | `"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"` | Font family for the tick labels, follows CSS font-family options.
| `fontSize` | `Number` | `12` | Font size for the tick labels.
| `fontStyle` | `String` | `'normal'` | Font style for the tick labels, follows CSS font-style options (i.e. normal, italic, oblique, initial, inherit).

## Major Tick Configuration
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.

| Name | Type | Default | Description
| -----| ---- | --------| -----------
| `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).
| `fontColor` | Color | `'#666'` | Font color for tick labels.
| `fontFamily` | `String` | `"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"` | Font family for the tick labels, follows CSS font-family options.
| `fontSize` | `Number` | `12` | Font size for the tick labels.
| `fontStyle` | `String` | `'normal'` | Font style for the tick labels, follows CSS font-style options (i.e. normal, italic, oblique, initial, inherit).
9 changes: 7 additions & 2 deletions samples/scales/time/line-point-data.html
Expand Up @@ -88,7 +88,13 @@
scaleLabel: {
display: true,
labelString: 'Date'
}
},
ticks: {
major: {
fontStyle: "bold",
fontColor: "#FF0000"
}
}
}],
yAxes: [{
display: true,
Expand All @@ -104,7 +110,6 @@
window.onload = function() {
var ctx = document.getElementById("canvas").getContext("2d");
window.myLine = new Chart(ctx, config);

};

document.getElementById('randomizeData').addEventListener('click', function() {
Expand Down
1 change: 1 addition & 0 deletions src/core/core.controller.js
Expand Up @@ -265,6 +265,7 @@ module.exports = function(Chart) {
});

scales[scale.id] = scale;
scale.mergeTicksOptions();

// TODO(SB): I think we should be able to remove this custom case (options.scale)
// and consider it as a regular scale part of the "scales"" map only! This would
Expand Down
43 changes: 36 additions & 7 deletions src/core/core.scale.js
Expand Up @@ -48,7 +48,9 @@ module.exports = function(Chart) {
autoSkipPadding: 0,
labelOffset: 0,
// We pass through arrays to be rendered as multiline labels, we convert Others to strings here.
callback: Chart.Ticks.formatters.values
callback: Chart.Ticks.formatters.values,
minor: {},
major: {}
}
};

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

mergeTicksOptions: function() {
var ticks = this.options.ticks;
if (ticks.minor === false) {
ticks.minor = {
display: false
};
}
if (ticks.major === false) {
ticks.major = {
display: false
};
}
for (var key in ticks) {
if (key !== 'major' && key !== 'minor') {
if (typeof ticks.minor[key] === 'undefined') {
ticks.minor[key] = ticks[key];
}
if (typeof ticks.major[key] === 'undefined') {
ticks.major[key] = ticks[key];
}
}
}
},
beforeUpdate: function() {
helpers.callback(this.options.beforeUpdate, [this]);
},
Expand Down Expand Up @@ -486,7 +511,8 @@ module.exports = function(Chart) {

var context = me.ctx;
var globalDefaults = Chart.defaults.global;
var optionTicks = options.ticks;
var optionTicks = options.ticks.minor;
var optionMajorTicks = options.ticks.major || optionTicks;
var gridLines = options.gridLines;
var scaleLabel = options.scaleLabel;

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

var tickFontColor = helpers.getValueOrDefault(optionTicks.fontColor, globalDefaults.defaultFontColor);
var tickFont = parseFontOptions(optionTicks);
var majorTickFontColor = helpers.getValueOrDefault(optionMajorTicks.fontColor, globalDefaults.defaultFontColor);
var majorTickFont = parseFontOptions(optionMajorTicks);

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

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

// Make sure we draw text in the correct color and font
context.fillStyle = tickFontColor;

var itemsToDraw = [];

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

helpers.each(me.ticks, function(label, index) {
helpers.each(me.ticks, function(tick, index) {
var label = (tick && tick.value) || tick;
// If the callback returned a null or undefined value, do not draw this line
if (label === undefined || label === null) {
return;
Expand Down Expand Up @@ -645,6 +671,7 @@ module.exports = function(Chart) {
glBorderDashOffset: borderDashOffset,
rotation: -1 * labelRotationRadians,
label: label,
major: tick.major,
textBaseline: textBaseline,
textAlign: textAlign
});
Expand Down Expand Up @@ -678,10 +705,12 @@ module.exports = function(Chart) {
}

if (optionTicks.display) {
// Make sure we draw text in the correct color and font
context.save();
context.translate(itemToDraw.labelX, itemToDraw.labelY);
context.rotate(itemToDraw.rotation);
context.font = tickFont.font;
context.font = itemToDraw.major ? majorTickFont.font : tickFont.font;
context.fillStyle = itemToDraw.major ? majorTickFontColor : tickFontColor;
context.textBaseline = itemToDraw.textBaseline;
context.textAlign = itemToDraw.textAlign;

Expand Down
11 changes: 7 additions & 4 deletions src/helpers/helpers.time.js
Expand Up @@ -145,13 +145,16 @@ module.exports = function(Chart) {
*/
determineMajorUnit: function(unit) {
var units = Object.keys(interval);
var majorUnit = null;
var unitIndex = units.indexOf(unit);
if (unitIndex < units.length - 1) {
majorUnit = units[unitIndex + 1];
while (unitIndex < units.length) {
var majorUnit = units[++unitIndex];
// exclude 'week' and 'quarter' units
if (majorUnit !== 'week' && majorUnit !== 'quarter') {
return majorUnit;
}
}

return majorUnit;
return null;
},

/**
Expand Down
39 changes: 31 additions & 8 deletions src/scales/scale.time.js
Expand Up @@ -25,9 +25,9 @@ module.exports = function(Chart) {
displayFormats: {
millisecond: 'h:mm:ss.SSS a', // 11:20:01.123 AM,
second: 'h:mm:ss a', // 11:20:01 AM
minute: 'h:mm:ss a', // 11:20:01 AM
hour: 'MMM D, hA', // Sept 4, 5PM
day: 'll', // Sep 4 2015
minute: 'h:mm a', // 11:20 AM
hour: 'hA', // 5PM
day: 'MMM D', // Sep 4
week: 'll', // Week 46, or maybe "[W]WW - YYYY" ?
month: 'MMM YYYY', // Sept 2015
quarter: '[Q]Q - YYYY', // Q3
Expand All @@ -45,6 +45,8 @@ module.exports = function(Chart) {
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');
}

this.mergeTicksOptions();

Chart.Scale.prototype.initialize.call(this);
},
determineDataLimits: function() {
Expand Down Expand Up @@ -182,14 +184,34 @@ module.exports = function(Chart) {
},
// Function to format an individual tick mark
tickFormatFunction: function(tick, index, ticks) {
var formattedTick = tick.format(this.displayFormat);
var tickOpts = this.options.ticks;
var formattedTick;
var tickClone = tick.clone();
var tickTimestamp = tick.valueOf();
var major = false;
var tickOpts;
if (this.majorUnit && this.majorDisplayFormat && tickTimestamp === tickClone.startOf(this.majorUnit).valueOf()) {
// format as major unit
formattedTick = tick.format(this.majorDisplayFormat);
tickOpts = this.options.ticks.major;
major = true;
} else {
// format as minor (base) unit
formattedTick = tick.format(this.displayFormat);
tickOpts = this.options.ticks.minor;
}

var callback = helpers.getValueOrDefault(tickOpts.callback, tickOpts.userCallback);

if (callback) {
return callback(formattedTick, index, ticks);
return {
value: callback(formattedTick, index, ticks),
major: major
};
}
return formattedTick;
return {
value: formattedTick,
major: major
};
},
convertTicksToLabels: function() {
var me = this;
Expand Down Expand Up @@ -257,11 +279,12 @@ module.exports = function(Chart) {
var me = this;

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

var innerWidth = me.isHorizontal() ? me.width : me.height;
var labelCapacity = innerWidth / tickLabelWidth;

return labelCapacity;
}
});
Expand Down
4 changes: 4 additions & 0 deletions test/specs/core.helpers.tests.js
Expand Up @@ -215,6 +215,8 @@ describe('Core helper tests', function() {
autoSkip: true,
autoSkipPadding: 0,
labelOffset: 0,
minor: {},
major: {},
},
type: 'linear'
}, {
Expand Down Expand Up @@ -253,6 +255,8 @@ describe('Core helper tests', function() {
autoSkip: true,
autoSkipPadding: 0,
labelOffset: 0,
minor: {},
major: {},
},
type: 'linear'
}]
Expand Down
4 changes: 3 additions & 1 deletion test/specs/scale.category.tests.js
Expand Up @@ -44,7 +44,9 @@ describe('Category scale tests', function() {
callback: defaultConfig.ticks.callback, // make this nicer, then check explicitly below
autoSkip: true,
autoSkipPadding: 0,
labelOffset: 0
labelOffset: 0,
minor: {},
major: {},
}
});

Expand Down
4 changes: 3 additions & 1 deletion test/specs/scale.linear.tests.js
Expand Up @@ -42,7 +42,9 @@ describe('Linear Scale', function() {
callback: defaultConfig.ticks.callback, // make this work nicer, then check below
autoSkip: true,
autoSkipPadding: 0,
labelOffset: 0
labelOffset: 0,
minor: {},
major: {},
}
});

Expand Down
4 changes: 3 additions & 1 deletion test/specs/scale.logarithmic.tests.js
Expand Up @@ -41,7 +41,9 @@ describe('Logarithmic Scale tests', function() {
callback: defaultConfig.ticks.callback, // make this nicer, then check explicitly below
autoSkip: true,
autoSkipPadding: 0,
labelOffset: 0
labelOffset: 0,
minor: {},
major: {},
},
});

Expand Down
4 changes: 3 additions & 1 deletion test/specs/scale.radialLinear.tests.js
Expand Up @@ -58,7 +58,9 @@ describe('Test the radial linear scale', function() {
callback: defaultConfig.ticks.callback, // make this nicer, then check explicitly below
autoSkip: true,
autoSkipPadding: 0,
labelOffset: 0
labelOffset: 0,
minor: {},
major: {},
},
});

Expand Down

0 comments on commit 2d7c1f0

Please sign in to comment.