Skip to content
This repository has been archived by the owner on Nov 7, 2019. It is now read-only.

Added axis label multiline support in canvas #18

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
209 changes: 186 additions & 23 deletions jquery.flot.axislabels.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,53 +72,209 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
plot, opts);
}

// Adjust the label width in canvas (if the option axisLabelAdjustment for
// this axis was set to true) to the max height/width of the axis
// by inserting line breaks as long as needed and possible
CanvasAxisLabel.prototype.adjustLabel = function(box) {

// we'll need those options and it's possible, that they
// haven't yet been set to their default values if not present
if (!this.opts.axisLabelFontSizePixels)
this.opts.axisLabelFontSizePixels = 14;
if (!this.opts.axisLabelFontFamily)
this.opts.axisLabelFontFamily = 'sans-serif';

var widthBox, labelMaxWidth;
var ctx;
var blMadeAdjustment = true;

// if it's x-axis, take the height of the axis as the max width
// of the label, otherwise (y-axis) take the width of the axis
// as the max width of the label
if (this.position == 'left' || this.position == 'right') {
widthBox = box.height;
} else {
widthBox = box.width;
}

// we need to calculate the label width, for that we'll need the canvas
// and set the used font size and family
ctx = this.plot.getCanvas().getContext('2d');
ctx.font = this.opts.axisLabelFontSizePixels + 'px ' + this.opts.axisLabelFontFamily;

// Now get the label width. If there is already more than one line: iterate through
// them and find the line with the widest width.
labelMaxWidth = this.getMaxWidthLabel(ctx);

// now lets do adjustments to the label (inserting line breaks) as long as
// the label width is wider than the height/width of the axis and as long
// as we were able to do adjustments (or if we're just beginning the loop).
while (labelMaxWidth.width > widthBox && blMadeAdjustment) {

// is there a space somewhere in the most broadest line
var idx = labelMaxWidth.lines[labelMaxWidth.index].lastIndexOf(' ');

// If so, insert a line break into this most broadest line and
// join the lines back together so we have our new axis label.
// If we can't break the broadest line it doesn't make sense to
// break any other line, so we would have to leave the loop
// by declaring we haven't done any adjustments.
if (idx > 0) {
// now we insert the line break to the last space(s) of the broadest line
labelMaxWidth.lines[labelMaxWidth.index] = labelMaxWidth.lines[labelMaxWidth.index].replace(/ *([^ ]*)$/, '\n$1');

// and now we join the lines back together with line breaks (since that's how
// we've splittet them into lines) and we have our new axis label
this.opts.axisLabel = labelMaxWidth.lines.join('\n');

// Now lets start all this over again and check if we have our perfect label
// now by getting once more the label width. Since there is now for sure already
// more than one line: iterate through them and find the line with the widest width.
labelMaxWidth = this.getMaxWidthLabel(ctx);
}
else
blMadeAdjustment = false;
}
}

// Lets get the width of the broadest line,
// we'll also need all the lines
// and the index of the broadest line.
// We need this for adjusting the axis label.
CanvasAxisLabel.prototype.getMaxWidthLabel = function(ctx) {
var arrAxisLabel = this.opts.axisLabel.split('\n');
var countLines = arrAxisLabel.length;
var widthLine, widthLabelMax = null, idx = 0;

for (var i = 0; i < countLines; i++) {
widthLine = ctx.measureText(arrAxisLabel[i]).width;
if (widthLabelMax === null || widthLine > widthLabelMax) {
widthLabelMax = widthLine;
idx = i;
}
}

return { width: widthLabelMax, index: idx, lines: arrAxisLabel };
}

CanvasAxisLabel.prototype.calculateSize = function() {
// padding between lines if we have more than one
if (!this.opts.axisLabelLinePadding)
this.opts.axisLabelLinePadding = 5;
if (!this.opts.axisLabelFontSizePixels)
this.opts.axisLabelFontSizePixels = 14;
if (!this.opts.axisLabelFontFamily)
this.opts.axisLabelFontFamily = 'sans-serif';

// How many lines are there in our label? Important if it's a multiline label.
var countLines = this.opts.axisLabel.split('\n').length;
var textWidth = this.opts.axisLabelFontSizePixels + this.padding;
var textHeight = this.opts.axisLabelFontSizePixels + this.padding;

// now calculate the height of line times the number of line adding the padding between lines and the padding to the axis
if (this.position == 'left' || this.position == 'right') {
this.width = this.opts.axisLabelFontSizePixels + this.padding;
this.width = this.opts.axisLabelFontSizePixels * countLines * 0.72 + this.opts.axisLabelLinePadding * (countLines - 1) + this.padding;
this.height = 0;
} else {
this.width = 0;
this.height = this.opts.axisLabelFontSizePixels + this.padding;
this.height = this.opts.axisLabelFontSizePixels * countLines * 0.72 + this.opts.axisLabelLinePadding * (countLines - 1) + this.padding;
}
};

CanvasAxisLabel.prototype.draw = function(box) {
// split our label in multiple lines if there are any line breaks inserted
var arrAxisLabel = this.opts.axisLabel.split('\n');

if (!this.opts.axisLabelColour)
this.opts.axisLabelColour = 'black';

// now set the default value of label alignment if none was given,
// or convert the value 'middle' to value 'center'. This will only
// affect multiline labels.
if (!this.opts.axisLabelAlignment || this.opts.axisLabelAlignment === 'middle')
this.opts.axisLabelAlignment = 'center';

// what is our line count?
var countLines = arrAxisLabel.length;
// array of canvases
var arrCtx = new Array(countLines);
// height of one line
var height = this.opts.axisLabelFontSizePixels;
// get the broadest line width of our multiline label,
// we might need this for alignment.
var ctx = this.plot.getCanvas().getContext('2d');
ctx.font = this.opts.axisLabelFontSizePixels + 'px ' + this.opts.axisLabelFontFamily;
ctx.save();
ctx.font = this.opts.axisLabelFontSizePixels + 'px ' +
this.opts.axisLabelFontFamily;
ctx.fillStyle = this.opts.axisLabelColour;
var width = ctx.measureText(this.opts.axisLabel).width;
var height = this.opts.axisLabelFontSizePixels;
var x, y, angle = 0;
if (this.position == 'top') {
x = box.left + box.width/2 - width/2;
y = box.top + height*0.72;
} else if (this.position == 'bottom') {
x = box.left + box.width/2 - width/2;
y = box.top + box.height - height*0.72;
} else if (this.position == 'left') {
x = box.left + height*0.72;
y = box.height/2 + box.top + width/2;
var labelMaxWidth = this.getMaxWidthLabel(ctx);
var angle = 0;

// angle if this is a y-axis
if (this.position == 'left') {
angle = -Math.PI/2;
} else if (this.position == 'right') {
x = box.left + box.width - height*0.72;
y = box.height/2 + box.top - width/2;
angle = Math.PI/2;
}
ctx.translate(x, y);
ctx.rotate(angle);
ctx.fillText(this.opts.axisLabel, 0, 0);
ctx.restore();

// now lets draw each label line on the chart
for (var i = 0; i < countLines; i++) {
// prepare the canvas for this label
arrCtx[i] = this.plot.getCanvas().getContext('2d');
arrCtx[i].save();
arrCtx[i].font = this.opts.axisLabelFontSizePixels + 'px ' + this.opts.axisLabelFontFamily;
arrCtx[i].fillStyle = this.opts.axisLabelColour;

// measure width of this line
var width = arrCtx[i].measureText(arrAxisLabel[i]).width;
var x, y;

// calculate position of this line
if (this.position == 'top') {
// align the text correct in multiline labels
if (this.opts.axisLabelAlignment === 'left')
x = box.left + box.width/2 - labelMaxWidth.width/2;
else if (this.opts.axisLabelAlignment === 'right')
x = box.left + box.width/2 + labelMaxWidth.width/2 - width;
else
x = box.left + box.width/2 - width/2;
// calculate the position of one label line
y = box.top + height * (i + 1) * 0.72 + this.opts.axisLabelLinePadding * i;
} else if (this.position == 'bottom') {
// align the text correct in multiline labels
if (this.opts.axisLabelAlignment === 'left')
x = box.left + box.width/2 - labelMaxWidth.width/2;
else if (this.opts.axisLabelAlignment === 'right')
x = box.left + box.width/2 + labelMaxWidth.width/2 - width;
else
x = box.left + box.width/2 - width/2;
// calculate the position of one label line
y = box.top + box.height - height * (countLines - i) * 0.72 - this.opts.axisLabelLinePadding * (countLines - i - 1);
} else if (this.position == 'left') {
// calculate the position of one label line
x = box.left + height * (i + 1) * 0.72 + this.opts.axisLabelLinePadding * i;
// align the text correct in multiline labels
if (this.opts.axisLabelAlignment === 'left')
y = box.top + box.height/2 + labelMaxWidth.width/2;
else if (this.opts.axisLabelAlignment === 'right')
y = box.top + box.height/2 - labelMaxWidth.width/2 + width;
else
y = box.top + box.height/2 + width/2;
} else if (this.position == 'right') {
// calculate the position of one label line
x = box.left + box.width - height * (i + 1) * 0.72 - this.opts.axisLabelLinePadding * i;
// align the text correct in multiline labels
if (this.opts.axisLabelAlignment === 'left')
y = box.top + box.height/2 - labelMaxWidth.width/2;
else if (this.opts.axisLabelAlignment === 'right')
y = box.top + box.height/2 + labelMaxWidth.width/2 - width;
else
y = box.top + box.height/2 - width/2;
}
// now draw the text of this line
arrCtx[i].translate(x, y);
arrCtx[i].rotate(angle);
arrCtx[i].fillText(arrAxisLabel[i], 0, 0);
arrCtx[i].restore();
}
};


Expand Down Expand Up @@ -403,6 +559,13 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
axis.position, padding,
plot, opts);

// if the label should be adjusted (enters line breaks if needed)
// then do this now, provided the adjustLabel function exists
// for this kind of axis (currently only in canvas).
if (axisLabels[axisName].opts.axisLabelAdjustment &&
typeof axisLabels[axisName].adjustLabel === 'function')
axisLabels[axisName].adjustLabel(axis.box);

// flot interprets axis.labelHeight and .labelWidth as
// the height and width of the tick labels. We increase
// these values to make room for the axis label and
Expand Down