Skip to content
Merged
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
5 changes: 4 additions & 1 deletion .codeclimate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@ plugins:
fixme:
enabled: true
checks:
argument-count:
config:
threshold: 5
method-complexity:
config:
threshold: 6
threshold: 7
exclude_patterns:
- "dist/"
- "docs/"
Expand Down
92 changes: 49 additions & 43 deletions src/helpers/helpers.canvas.js
Original file line number Diff line number Diff line change
Expand Up @@ -310,28 +310,8 @@ export function renderText(ctx, text, x, y, font, opts = {}) {
let i, line;

ctx.save();

if (opts.translation) {
ctx.translate(opts.translation[0], opts.translation[1]);
}

if (!isNullOrUndef(opts.rotation)) {
ctx.rotate(opts.rotation);
}

ctx.font = font.string;

if (opts.color) {
ctx.fillStyle = opts.color;
}

if (opts.textAlign) {
ctx.textAlign = opts.textAlign;
}

if (opts.textBaseline) {
ctx.textBaseline = opts.textBaseline;
}
setRenderOpts(ctx, opts);

for (i = 0; i < lines.length; ++i) {
line = lines[i];
Expand All @@ -349,35 +329,61 @@ export function renderText(ctx, text, x, y, font, opts = {}) {
}

ctx.fillText(line, x, y, opts.maxWidth);
decorateText(ctx, x, y, line, opts);

if (opts.strikethrough || opts.underline) {
/**
* Now that IE11 support has been dropped, we can use more
* of the TextMetrics object. The actual bounding boxes
* are unflagged in Chrome, Firefox, Edge, and Safari so they
* can be safely used.
* See https://developer.mozilla.org/en-US/docs/Web/API/TextMetrics#Browser_compatibility
*/
const metrics = ctx.measureText(line);
const left = x - metrics.actualBoundingBoxLeft;
const right = x + metrics.actualBoundingBoxRight;
const top = y - metrics.actualBoundingBoxAscent;
const bottom = y + metrics.actualBoundingBoxDescent;
const yDecoration = opts.strikethrough ? (top + bottom) / 2 : bottom;

ctx.strokeStyle = ctx.fillStyle;
ctx.beginPath();
ctx.lineWidth = opts.decorationWidth || 2;
ctx.moveTo(left, yDecoration);
ctx.lineTo(right, yDecoration);
ctx.stroke();
}
y += font.lineHeight;
}

ctx.restore();
}

function setRenderOpts(ctx, opts) {
if (opts.translation) {
ctx.translate(opts.translation[0], opts.translation[1]);
}

if (!isNullOrUndef(opts.rotation)) {
ctx.rotate(opts.rotation);
}

if (opts.color) {
ctx.fillStyle = opts.color;
}

if (opts.textAlign) {
ctx.textAlign = opts.textAlign;
}

if (opts.textBaseline) {
ctx.textBaseline = opts.textBaseline;
}
}

function decorateText(ctx, x, y, line, opts) {
if (opts.strikethrough || opts.underline) {
/**
* Now that IE11 support has been dropped, we can use more
* of the TextMetrics object. The actual bounding boxes
* are unflagged in Chrome, Firefox, Edge, and Safari so they
* can be safely used.
* See https://developer.mozilla.org/en-US/docs/Web/API/TextMetrics#Browser_compatibility
*/
const metrics = ctx.measureText(line);
const left = x - metrics.actualBoundingBoxLeft;
const right = x + metrics.actualBoundingBoxRight;
const top = y - metrics.actualBoundingBoxAscent;
const bottom = y + metrics.actualBoundingBoxDescent;
const yDecoration = opts.strikethrough ? (top + bottom) / 2 : bottom;

ctx.strokeStyle = ctx.fillStyle;
ctx.beginPath();
ctx.lineWidth = opts.decorationWidth || 2;
ctx.moveTo(left, yDecoration);
ctx.lineTo(right, yDecoration);
ctx.stroke();
}
}

/**
* Add a path of a rectangle with rounded corners to the current sub-path
* @param {CanvasRenderingContext2D} ctx Context
Expand Down
80 changes: 37 additions & 43 deletions src/scales/scale.radialLinear.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,11 @@ function getTickBackdropHeight(opts) {
return 0;
}

function measureLabelSize(ctx, lineHeight, label) {
if (isArray(label)) {
return {
w: _longestText(ctx, ctx.font, label),
h: label.length * lineHeight
};
}

function measureLabelSize(ctx, font, label) {
label = isArray(label) ? label : [label];
return {
w: ctx.measureText(label).width,
h: lineHeight
w: _longestText(ctx, font.string, label),
h: label.length * font.lineHeight
};
}

Expand Down Expand Up @@ -89,22 +83,18 @@ function fitWithPointLabels(scale) {
b: scale.height - scale.paddingTop
};
const furthestAngles = {};
let i, textSize, pointPosition;

const labelSizes = [];
const padding = [];

const valueCount = scale.getLabels().length;
for (i = 0; i < valueCount; i++) {
for (let i = 0; i < valueCount; i++) {
const opts = scale.options.pointLabels.setContext(scale.getContext(i));
padding[i] = opts.padding;
pointPosition = scale.getPointPosition(i, scale.drawingArea + padding[i]);
const pointPosition = scale.getPointPosition(i, scale.drawingArea + padding[i]);
const plFont = toFont(opts.font);
scale.ctx.font = plFont.string;
textSize = measureLabelSize(scale.ctx, plFont.lineHeight, scale._pointLabels[i]);
const textSize = measureLabelSize(scale.ctx, plFont, scale._pointLabels[i]);
labelSizes[i] = textSize;

// Add quarter circle to make degree 0 mean top of circle
const angleRadians = scale.getIndexAngle(i);
const angle = toDegrees(angleRadians);
const hLimits = determineLimits(angle, pointPosition.x, textSize.w, 0, 180);
Expand Down Expand Up @@ -133,50 +123,43 @@ function fitWithPointLabels(scale) {

scale._setReductions(scale.drawingArea, furthestLimits, furthestAngles);

scale._pointLabelItems = [];

// Now that text size is determined, compute the full positions
scale._pointLabelItems = buildPointLabelItems(scale, labelSizes, padding);
}

function buildPointLabelItems(scale, labelSizes, padding) {
const items = [];
const valueCount = scale.getLabels().length;
const opts = scale.options;
const tickBackdropHeight = getTickBackdropHeight(opts);
const outerDistance = scale.getDistanceFromCenterForValue(opts.ticks.reverse ? scale.min : scale.max);

for (i = 0; i < valueCount; i++) {
for (let i = 0; i < valueCount; i++) {
// Extra pixels out for some label spacing
const extra = (i === 0 ? tickBackdropHeight / 2 : 0);
const pointLabelPosition = scale.getPointPosition(i, outerDistance + extra + padding[i]);

const angle = toDegrees(scale.getIndexAngle(i));
const size = labelSizes[i];
adjustPointPositionForLabelHeight(angle, size, pointLabelPosition);

const y = yForAngle(pointLabelPosition.y, size.h, angle);
const textAlign = getTextAlignForAngle(angle);
let left;
const left = leftForTextAlign(pointLabelPosition.x, size.w, textAlign);

if (textAlign === 'left') {
left = pointLabelPosition.x;
} else if (textAlign === 'center') {
left = pointLabelPosition.x - (size.w / 2);
} else {
left = pointLabelPosition.x - size.w;
}

const right = left + size.w;

scale._pointLabelItems[i] = {
items.push({
// Text position
x: pointLabelPosition.x,
y: pointLabelPosition.y,
y,

// Text rendering data
textAlign,

// Bounding box
left,
top: pointLabelPosition.y,
right,
bottom: pointLabelPosition.y + size.h,
};
top: y,
right: left + size.w,
bottom: y + size.h
});
}
return items;
}

function getTextAlignForAngle(angle) {
Expand All @@ -189,12 +172,22 @@ function getTextAlignForAngle(angle) {
return 'right';
}

function adjustPointPositionForLabelHeight(angle, textSize, position) {
function leftForTextAlign(x, w, align) {
if (align === 'right') {
x -= w;
} else if (align === 'center') {
x -= (w / 2);
}
return x;
}

function yForAngle(y, h, angle) {
if (angle === 90 || angle === 270) {
position.y -= (textSize.h / 2);
y -= (h / 2);
} else if (angle > 270 || angle < 90) {
position.y -= textSize.h;
y -= h;
}
return y;
}

function drawPointLabels(scale, labelCount) {
Expand Down Expand Up @@ -543,6 +536,7 @@ export default class RadialLinearScale extends LinearScaleBase {
offset = me.getDistanceFromCenterForValue(me.ticks[index].value);

if (optsAtIndex.showLabelBackdrop) {
ctx.font = tickFont.string;
width = ctx.measureText(tick.label).width;
ctx.fillStyle = optsAtIndex.backdropColor;

Expand Down
6 changes: 3 additions & 3 deletions test/specs/helpers.canvas.tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -316,15 +316,15 @@ describe('Chart.helpers.canvas', function() {
expect(context.getCalls()).toEqual([{
name: 'save',
args: [],
}, {
name: 'setFont',
args: ['12px arial'],
}, {
name: 'translate',
args: [10, 20],
}, {
name: 'rotate',
args: [90],
}, {
name: 'setFont',
args: ['12px arial'],
}, {
name: 'fillText',
args: ['foo', 0, 0, undefined],
Expand Down
18 changes: 9 additions & 9 deletions test/specs/plugin.title.tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,15 +131,15 @@ describe('Plugin.title', function() {
expect(context.getCalls()).toEqual([{
name: 'save',
args: []
}, {
name: 'setFont',
args: ["normal bold 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"],
}, {
name: 'translate',
args: [300, 67.2]
}, {
name: 'rotate',
args: [0]
}, {
name: 'setFont',
args: ["normal bold 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"],
}, {
name: 'setFillStyle',
args: ['#666']
Expand Down Expand Up @@ -192,15 +192,15 @@ describe('Plugin.title', function() {
expect(context.getCalls()).toEqual([{
name: 'save',
args: []
}, {
name: 'setFont',
args: ["normal bold 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"],
}, {
name: 'translate',
args: [117.2, 250]
}, {
name: 'rotate',
args: [-0.5 * Math.PI]
}, {
name: 'setFont',
args: ["normal bold 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"],
}, {
name: 'setFillStyle',
args: ['#666']
Expand Down Expand Up @@ -234,15 +234,15 @@ describe('Plugin.title', function() {
expect(context.getCalls()).toEqual([{
name: 'save',
args: []
}, {
name: 'setFont',
args: ["normal bold 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"],
}, {
name: 'translate',
args: [117.2, 250]
}, {
name: 'rotate',
args: [0.5 * Math.PI]
}, {
name: 'setFont',
args: ["normal bold 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"],
}, {
name: 'setFillStyle',
args: ['#666']
Expand Down