Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move drawing of gridLines into separate plugin and add border functionality #4117

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
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
7 changes: 4 additions & 3 deletions src/chart.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,10 @@ require('./charts/Chart.Scatter')(Chart);
var plugins = [];

plugins.push(
require('./plugins/plugin.filler')(Chart),
require('./plugins/plugin.legend')(Chart),
require('./plugins/plugin.title')(Chart)
require('./plugins/plugin.filler')(Chart),
require('./plugins/plugin.gridlines')(Chart),
require('./plugins/plugin.legend')(Chart),
require('./plugins/plugin.title')(Chart)
);

Chart.plugins.register(plugins);
Expand Down
98 changes: 57 additions & 41 deletions src/core/core.scale.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ defaults._set('scale', {
display: true,
position: 'left',
offset: false,
borderColor: 'rgba(0, 0, 0, 0.4)',
borderWidth: 0,
borderDash: [],
borderDashOffset: 0.0,

// grid line settings
gridLines: {
Expand Down Expand Up @@ -672,7 +676,7 @@ module.exports = function(Chart) {
},

// Actually draw the scale on the canvas
// @param {rectangle} chartArea : the area of the chart to draw full grid lines on
// @param {rectangle} chartArea : the area of the chart to draw all ticks and labels on
draw: function(chartArea) {
var me = this;
var options = me.options;
Expand Down Expand Up @@ -732,7 +736,7 @@ module.exports = function(Chart) {
}

// Common properties
var tx1, ty1, tx2, ty2, x1, y1, x2, y2, labelX, labelY;
var tx1, ty1, tx2, ty2, labelX, labelY;
var textAlign = 'middle';
var textBaseline = 'middle';
var tickPadding = optionTicks.padding;
Expand Down Expand Up @@ -760,11 +764,9 @@ module.exports = function(Chart) {

labelX = me.getPixelForTick(index) + optionTicks.labelOffset; // x values for optionTicks (need to consider offsetLabel option)

tx1 = tx2 = x1 = x2 = xLineValue;
tx1 = tx2 = xLineValue;
ty1 = yTickStart;
ty2 = yTickEnd;
y1 = chartArea.top;
y2 = chartArea.bottom;
} else {
var isLeft = options.position === 'left';
var labelXOffset;
Expand All @@ -789,43 +791,53 @@ module.exports = function(Chart) {

tx1 = xTickStart;
tx2 = xTickEnd;
x1 = chartArea.left;
x2 = chartArea.right;
ty1 = ty2 = y1 = y2 = yLineValue;
ty1 = ty2 = yLineValue;
}

itemsToDraw.push({
tx1: tx1,
ty1: ty1,
tx2: tx2,
ty2: ty2,
x1: x1,
y1: y1,
x2: x2,
y2: y2,
labelX: labelX,
labelY: labelY,
glWidth: lineWidth,
glColor: lineColor,
glBorderDash: borderDash,
glBorderDashOffset: borderDashOffset,
tmWidth: lineWidth,
tmColor: lineColor,
tmBorderDash: borderDash,
tmBorderDashOffset: borderDashOffset,
rotation: -1 * labelRotationRadians,
label: label,
label: '' + label,
major: tick.major,
textBaseline: textBaseline,
textAlign: textAlign
});
});

// Draw all of the tick labels, tick marks, and grid lines at the correct places
// When offsetGridLines is enabled, there should be one more tick mark than there
// are ticks in scale.ticks array, therefore the missing gridLine has to be manually added
if (gridLines.offsetGridLines) {
var aliasPixel = helpers.aliasPixel(gridLines.lineWidth);
itemsToDraw.push({
tx1: !isHorizontal ? xTickStart : chartArea.right + aliasPixel,
ty1: !isHorizontal ? chartArea.bottom + aliasPixel : yTickStart,
tx2: !isHorizontal ? xTickEnd : chartArea.right + aliasPixel,
ty2: !isHorizontal ? chartArea.bottom + aliasPixel : yTickEnd,
tmWidth: helpers.valueAtIndexOrDefault(gridLines.lineWidth, ticks.length),
tmColor: helpers.valueAtIndexOrDefault(gridLines.color, ticks.length),
tmBorderDash: gridLines.borderDash,
tmBorderDashOffset: gridLines.borderDashOffset
});
}

// Draw all of the tick labels and tick marks at the correct places
helpers.each(itemsToDraw, function(itemToDraw) {
if (gridLines.display) {
context.save();
context.lineWidth = itemToDraw.glWidth;
context.strokeStyle = itemToDraw.glColor;
context.lineWidth = itemToDraw.tmWidth;
context.strokeStyle = itemToDraw.tmColor;
if (context.setLineDash) {
context.setLineDash(itemToDraw.glBorderDash);
context.lineDashOffset = itemToDraw.glBorderDashOffset;
context.setLineDash(itemToDraw.tmBorderDash);
context.lineDashOffset = itemToDraw.tmBorderDashOffset;
}

context.beginPath();
Expand All @@ -835,16 +847,11 @@ module.exports = function(Chart) {
context.lineTo(itemToDraw.tx2, itemToDraw.ty2);
}

if (gridLines.drawOnChartArea) {
context.moveTo(itemToDraw.x1, itemToDraw.y1);
context.lineTo(itemToDraw.x2, itemToDraw.y2);
}

context.stroke();
context.restore();
}

if (optionTicks.display) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might make sense to do this for any value of itemToDraw.label that isn't truthy. Blank labels don't need to be drawn and same with null labels. What do you think?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

items with null or undefined label aren't pushed into the array at all, thanks to this statement.
The blank labels don't need to be drawn tho. The question is if they should be skipped the same way as when it's null(its ticks and gridLines wont be drawn) or in the statement you linked so there will be a blank tick, but the label itself wont be drawn.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm, for grid lines we have the following logic:
if label is a string, draw label, tick and grid line
if label is null or undefined hide the label, tick and gridline.

This allows users to filter out grid lines by return null from the tick callback. here is an example of filtering

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i understand, i'll add just add the statement to skip the drawing of label itself if it's empty string then

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good

if (optionTicks.display && itemToDraw.label) {
// Make sure we draw text in the correct color and font
context.save();
context.translate(itemToDraw.labelX, itemToDraw.labelY);
Expand Down Expand Up @@ -901,30 +908,39 @@ module.exports = function(Chart) {
context.restore();
}

if (gridLines.drawBorder) {
// Draw the line at the edge of the axis
context.lineWidth = helpers.valueAtIndexOrDefault(gridLines.lineWidth, 0);
context.strokeStyle = helpers.valueAtIndexOrDefault(gridLines.color, 0);
var x1 = me.left;
var x2 = me.right;
var y1 = me.top;
var y2 = me.bottom;
// gridLines.drawBorder is deprecated
if (gridLines.drawBorder && options.borderColor && options.borderWidth) {
// Draw the scale border
context.lineWidth = options.borderWidth;
context.strokeStyle = options.borderColor;
if (context.setLineDash) {
context.setLineDash(helpers.getValueOrDefault(options.borderDash, globalDefaults.borderDash));
context.lineDashOffset = helpers.getValueOrDefault(options.borderDashOffset, globalDefaults.borderDashOffset);
}

var x1 = Math.round(me.left);
var x2 = Math.round(me.right);
var y1 = Math.round(me.top);
var y2 = Math.round(me.bottom);

var aliasPixel = helpers.aliasPixel(context.lineWidth);
var borderAliasPixel = helpers.aliasPixel(context.lineWidth);
if (isHorizontal) {
y1 = y2 = options.position === 'top' ? me.bottom : me.top;
y1 += aliasPixel;
y2 += aliasPixel;
y1 += borderAliasPixel;
y2 += borderAliasPixel;
} else {
x1 = x2 = options.position === 'left' ? me.right : me.left;
x1 += aliasPixel;
x2 += aliasPixel;
x1 += borderAliasPixel;
x2 += borderAliasPixel;
}

context.beginPath();

context.moveTo(x1, y1);
context.lineTo(x2, y2);

context.stroke();
context.restore();
}
}
});
Expand Down
182 changes: 182 additions & 0 deletions src/plugins/plugin.gridlines.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
'use strict';

var defaults = require('../core/core.defaults');
var helpers = require('../helpers/index');
var MODEL_KEY = '$gridlines';

// This is copied from core.scale.js, which is also required here and im not sure where it should be placed for both of them to access it
function getLineValue(scale, index, offsetGridLines) {
var lineValue = scale.getPixelForTick(index);

if (offsetGridLines) {
if (index === 0) {
lineValue -= (scale.getPixelForTick(1) - lineValue) / 2;
} else {
lineValue -= (lineValue - scale.getPixelForTick(index - 1)) / 2;
}
}
return lineValue;
}

module.exports = function() {
function getGridLine(chart, scale, position, tickIndex) {
var chartArea = chart.chartArea;
var scaleOptions = scale.options;
var gridLines = scaleOptions.gridLines;

var lineWidth, lineColor, borderDash, borderDashOffset;

if (tickIndex !== undefined && tickIndex === scale.zeroLineIndex) {
lineWidth = gridLines.zeroLineWidth;
lineColor = gridLines.zeroLineColor;
borderDash = gridLines.zeroLineBorderDash;
borderDashOffset = gridLines.zeroLineBorderDashOffset;
} else {
lineWidth = helpers.getValueAtIndexOrDefault(gridLines.lineWidth, tickIndex);
lineColor = helpers.getValueAtIndexOrDefault(gridLines.color, tickIndex);
borderDash = helpers.getValueOrDefault(gridLines.borderDash, defaults.global.borderDash);
borderDashOffset = helpers.getValueOrDefault(gridLines.borderDashOffset, defaults.global.borderDashOffset);
}

var x1, x2, y1, y2;

if (scale.isHorizontal()) {
x1 = x2 = position;
y1 = chartArea.top;
y2 = chartArea.bottom;
} else {
x1 = chartArea.left;
x2 = chartArea.right;
y1 = y2 = position;
}

return {
x1: x1,
x2: x2,
y1: y1,
y2: y2,
width: lineWidth,
color: lineColor,
borderDash: borderDash,
borderDashOffset: borderDashOffset
};
}

function collectVisibleGridLines(chart) {
var lines = [];

// Temporary object that stores already collected gridLine positions to prevent gridLines from overlapping
var gridLinesHash = {
horizontal: {},
vertical: {}
};

// Marks scale border positions to prevent overlapping of gridLines and scale borders
helpers.each(chart.scales, function(scale) {
var scaleOptions = scale.options;
var glHashByOrientation = gridLinesHash[!scale.isHorizontal() ? 'horizontal' : 'vertical'];
var borderPosition;

// gridLines.drawBorder is deprecated
if (scaleOptions.gridLines.drawBorder && scaleOptions.borderColor !== null && scaleOptions.borderWidth !== 0 && scaleOptions.borderWidth !== null) {
if (scale.isHorizontal()) {
borderPosition = scale.position === 'top' ? scale.bottom : scale.top;
} else {
borderPosition = scale.position === 'left' ? scale.right : scale.left;
}

glHashByOrientation[Math.round(borderPosition)] = true;
}
});

// Collects gridLines
helpers.each(chart.scales, function(scale) {
var scaleOptions = scale.options;
var glHashByOrientation = gridLinesHash[scale.isHorizontal() ? 'horizontal' : 'vertical'];
var position;

if (scaleOptions.display && scaleOptions.gridLines.display && scaleOptions.gridLines.drawOnChartArea) {
for (var tickIndex = 0, ticksCount = scale.ticks.length; tickIndex < ticksCount; tickIndex++) {
if (helpers.isNullOrUndef(scale.ticks[tickIndex])) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@simonbrunel @benmccann does this check make sense in the context of how ticks changed in 2.7.0?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think so

continue;
}

position = getLineValue(scale, tickIndex, scaleOptions.gridLines.offsetGridLines && ticksCount > 1);

if (glHashByOrientation[position] === undefined) {
glHashByOrientation[position] = true;
lines.push(getGridLine(chart, scale, position, tickIndex));
}
}

// When offsetGridLines is enabled, there should be one more gridLine than there
// are ticks in scale.ticks array, therefore the missing gridLine has to be manually added
if (scaleOptions.gridLines.offsetGridLines) {
position = Math.round(!scale.isHorizontal() ? scale.bottom : scale.right);

if (glHashByOrientation[position] === undefined) {
glHashByOrientation[position] = true;
lines.push(getGridLine(chart, scale, position, undefined));
}
}
}
});

return lines;
}

function drawGridLines(ctx, lines) {
var aliasPixel;
var x1, x2, y1, y2;

for (var i = 0, ilen = lines.length; i < ilen; i++) {
var line = lines[i];

ctx.lineWidth = line.width;
ctx.strokeStyle = line.color;
if (ctx.setLineDash) {
ctx.setLineDash(line.borderDash);
ctx.lineDashOffset = line.borderDashOffset;
}

aliasPixel = helpers.aliasPixel(ctx.lineWidth);
x1 = line.x1;
x2 = line.x2;
y1 = line.y1;
y2 = line.y2;

if (y1 === y2) {
y1 += aliasPixel;
y2 += aliasPixel;
} else {
x1 += aliasPixel;
x2 += aliasPixel;
}

ctx.beginPath();

ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);

ctx.stroke();
ctx.restore();
}
}

return {
id: 'gridlines',

afterUpdate: function(chart) {
chart[MODEL_KEY] = {
lines: collectVisibleGridLines(chart)
};
},

beforeDraw: function(chart) {
var model = chart[MODEL_KEY];
if (model) {
drawGridLines(chart.ctx, model.lines);
}
}
};
};
Loading