diff --git a/docs/03-Line-Chart.md b/docs/03-Line-Chart.md
index 45b95d231..34346bc08 100644
--- a/docs/03-Line-Chart.md
+++ b/docs/03-Line-Chart.md
@@ -96,7 +96,7 @@ The label key on each dataset is optional, and can be used when generating a sca
### Data Points
-The data passed to the chart can be passed in two formats. The most common method is to pass the data array as an array of numbers. In this case, the `data.labels` array must be specified and must contain a label for each point.
+The data passed to the chart can be passed in two formats. The most common method is to pass the data array as an array of numbers. In this case, the `data.labels` array must be specified and must contain a label for each point or, in the case of labels to be displayed over multiple lines an array of labels (one for each line) i.e `[["June","2015"], "July"]`.
The alternate is used for sparse datasets. Data is specified using an object containing `x` and `y` properties. This is used for scatter charts as documented below.
@@ -177,4 +177,4 @@ var stackedLine = new Chart(ctx, {
}
}
});
-```
+```
\ No newline at end of file
diff --git a/samples/line-multiline-labels.html b/samples/line-multiline-labels.html
new file mode 100644
index 000000000..3fd0de5e1
--- /dev/null
+++ b/samples/line-multiline-labels.html
@@ -0,0 +1,218 @@
+
+
+
+
+ Line Chart
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/core/core.helpers.js b/src/core/core.helpers.js
index 958b0bd10..4a487b7b8 100644
--- a/src/core/core.helpers.js
+++ b/src/core/core.helpers.js
@@ -817,7 +817,7 @@ module.exports = function(Chart) {
helpers.fontString = function(pixelSize, fontStyle, fontFamily) {
return fontStyle + " " + pixelSize + "px " + fontFamily;
};
- helpers.longestText = function(ctx, font, arrayOfStrings, cache) {
+ helpers.longestText = function(ctx, font, arrayOfThings, cache) {
cache = cache || {};
var data = cache.data = cache.data || {};
var gc = cache.garbageCollect = cache.garbageCollect || [];
@@ -830,31 +830,53 @@ module.exports = function(Chart) {
ctx.font = font;
var longest = 0;
- helpers.each(arrayOfStrings, function(string) {
- // Undefined strings should not be measured
- if (string !== undefined && string !== null) {
- var textWidth = data[string];
- if (!textWidth) {
- textWidth = data[string] = ctx.measureText(string).width;
- gc.push(string);
- }
-
- if (textWidth > longest) {
- longest = textWidth;
- }
+ helpers.each(arrayOfThings, function(thing) {
+ // Undefined strings and arrays should not be measured
+ if (thing !== undefined && thing !== null && helpers.isArray(thing) !== true) {
+ longest = helpers.measureText(ctx, data, gc, longest, thing);
+ } else if (helpers.isArray(thing)) {
+ // if it is an array lets measure each element
+ // to do maybe simplify this function a bit so we can do this more recursively?
+ helpers.each(thing, function(nestedThing) {
+ // Undefined strings and arrays should not be measured
+ if (nestedThing !== undefined && nestedThing !== null && helpers.isArray(nestedThing)) {
+ longest = helpers.measureText(ctx, data, gc, longest, nestedThing);
+ }
+ });
}
});
var gcLen = gc.length / 2;
- if (gcLen > arrayOfStrings.length) {
+ if (gcLen > arrayOfThings.length) {
for (var i = 0; i < gcLen; i++) {
delete data[gc[i]];
}
gc.splice(0, gcLen);
}
-
return longest;
};
+ helpers.measureText = function (ctx, data, gc, longest, string) {
+ var textWidth = data[string];
+ if (!textWidth) {
+ textWidth = data[string] = ctx.measureText(string).width;
+ gc.push(string);
+ }
+ if (textWidth > longest) {
+ longest = textWidth;
+ }
+ return longest;
+ };
+ helpers.numberOfLabelLines = function(arrayOfThings) {
+ var numberOfLines = 1;
+ helpers.each(arrayOfThings, function(thing) {
+ if (helpers.isArray(thing)) {
+ if (thing.length > numberOfLines) {
+ numberOfLines = thing.length;
+ }
+ }
+ });
+ return numberOfLines;
+ };
helpers.drawRoundedRectangle = function(ctx, x, y, width, height, radius) {
ctx.beginPath();
ctx.moveTo(x + radius, y);
diff --git a/src/core/core.scale.js b/src/core/core.scale.js
index cf6afb0f3..e692ebc95 100644
--- a/src/core/core.scale.js
+++ b/src/core/core.scale.js
@@ -44,7 +44,7 @@ module.exports = function(Chart) {
autoSkipPadding: 0,
labelOffset: 0,
callback: function(value) {
- return '' + value;
+ return helpers.isArray(value) ? value : '' + value;
}
}
};
@@ -315,13 +315,15 @@ module.exports = function(Chart) {
}
var largestTextWidth = helpers.longestText(this.ctx, tickLabelFont, this.ticks, this.longestTextCache);
+ var tallestLabelHeightInLines = helpers.numberOfLabelLines(this.ticks);
+ var lineSpace = tickFontSize * 0.5;
if (isHorizontal) {
// A horizontal axis is more constrained by the height.
this.longestLabelWidth = largestTextWidth;
// TODO - improve this calculation
- var labelHeight = (Math.sin(helpers.toRadians(this.labelRotation)) * this.longestLabelWidth) + 1.5 * tickFontSize;
+ var labelHeight = (Math.sin(helpers.toRadians(this.labelRotation)) * this.longestLabelWidth) + (tickFontSize * tallestLabelHeightInLines) + (lineSpace * tallestLabelHeightInLines);
minSize.height = Math.min(this.maxHeight, minSize.height + labelHeight);
this.ctx.font = tickLabelFont;
@@ -546,6 +548,7 @@ module.exports = function(Chart) {
helpers.each(this.ticks, function (label, index) {
// Blank optionTicks
var isLastTick = this.ticks.length === index + 1;
+ var lineHeight;
// Since we always show the last tick,we need may need to hide the last shown one before
var shouldSkip = (skipRatio > 1 && index % skipRatio > 0) || (index % skipRatio === 0 && index + skipRatio >= this.ticks.length);
@@ -594,7 +597,18 @@ module.exports = function(Chart) {
context.font = tickLabelFont;
context.textAlign = (isRotated) ? "right" : "center";
context.textBaseline = (isRotated) ? "middle" : options.position === "top" ? "bottom" : "top";
- context.fillText(label, 0, 0);
+
+ lineHeight = context.measureText("M").width * 1.2;
+
+ if (helpers.isArray(label)) {
+ for (var i = 0, y = 0; i < label.length; ++i) {
+ context.fillText(label[i], 0, y);
+ y += lineHeight;
+ }
+ } else {
+ context.fillText(label, 0, 0);
+ }
+
context.restore();
}
}, this);