const Element = require('../core/core.element');
const helpers = require('../helpers/index');
-const valueOrDefault = helpers.valueOrDefault;
-
const defaultColor = defaults.global.defaultColor;
defaults._set('global', {
}
});
+function startAtGap(points, spanGaps) {
+ let closePath = true;
+ let previous = points.length && points[0]._view;
+ let index, view;
+
+ for (index = 1; index < points.length; ++index) {
+ // If there is a gap in the (looping) line, start drawing from that gap
+ view = points[index]._view;
+ if (!view.skip && previous.skip) {
+ points = points.slice(index).concat(points.slice(0, index));
+ closePath = spanGaps;
+ break;
+ }
+ previous = view;
+ }
+
+ points.closePath = closePath;
+ return points;
+}
+
+function setStyle(ctx, vm) {
+ ctx.lineCap = vm.borderCapStyle;
+ ctx.setLineDash(vm.borderDash);
+ ctx.lineDashOffset = vm.borderDashOffset;
+ ctx.lineJoin = vm.borderJoinStyle;
+ ctx.lineWidth = vm.borderWidth;
+ ctx.strokeStyle = vm.borderColor;
+}
+
+function normalPath(ctx, points, spanGaps) {
+ let move = true;
+ let index, currentVM, previousVM;
+
+ for (index = 0; index < points.length; ++index) {
+ currentVM = points[index]._view;
+
+ if (currentVM.skip) {
+ move = move || !spanGaps;
+ continue;
+ }
+ if (move) {
+ ctx.moveTo(currentVM.x, currentVM.y);
+ move = false;
+ } else {
+ helpers.canvas.lineTo(ctx, previousVM, currentVM);
+ }
+ previousVM = currentVM;
+ }
+}
+
+/**
+ * Create path from points, grouping by truncated x-coordinate
+ * Points need to be in order by x-coordinate for this to work efficiently
+ * @param {CanvasRenderingContext2D} ctx - Context
+ * @param {Point[]} points - Points defining the line
+ * @param {boolean} spanGaps - Are gaps spanned over
+ */
+function fastPath(ctx, points, spanGaps) {
+ let move = true;
+ let count = 0;
+ let avgX = 0;
+ let index, vm, truncX, x, y, prevX, minY, maxY, lastY;
+
+ for (index = 0; index < points.length; ++index) {
+ vm = points[index]._view;
+
+ // If point is skipped, we either move to next (not skipped) point
+ // or line to it if spanGaps is true. `move` can already be true.
+ if (vm.skip) {
+ move = move || !spanGaps;
+ continue;
+ }
+
+ x = vm.x;
+ y = vm.y;
+ truncX = x | 0; // truncated x-coordinate
+
+ if (move) {
+ ctx.moveTo(x, y);
+ move = false;
+ } else if (truncX === prevX) {
+ // Determine `minY` / `maxY` and `avgX` while we stay within same x-position
+ minY = Math.min(y, minY);
+ maxY = Math.max(y, maxY);
+ // For first point in group, count is `0`, so average will be `x` / 1.
+ avgX = (count * avgX + x) / ++count;
+ } else {
+ if (minY !== maxY) {
+ // Draw line to maxY and minY, using the average x-coordinate
+ ctx.lineTo(avgX, maxY);
+ ctx.lineTo(avgX, minY);
+ // Move to y-value of last point in group. So the line continues
+ // from correct position.
+ ctx.moveTo(avgX, lastY);
+ }
+ // Draw line to next x-position, using the first (or only)
+ // y-value in that group
+ ctx.lineTo(x, y);
+
+ prevX = truncX;
+ count = 0;
+ minY = maxY = y;
+ }
+ // Keep track of the last y-value in group
+ lastY = y;
+ }
+}
+
+function useFastPath(vm) {
+ return vm.tension === 0 && !vm.steppedLine && !vm.fill && !vm.borderDash.length;
+}
+
class Line extends Element {
constructor(props) {
}
draw() {
- var me = this;
- var vm = me._view;
- var ctx = me._ctx;
- var spanGaps = vm.spanGaps;
- var points = me._children;
- var globalDefaults = defaults.global;
- var globalOptionLineElements = globalDefaults.elements.line;
- var lastDrawnIndex = -1;
- var closePath = me._loop;
- var index, previous, currentVM;
+ const me = this;
+ const vm = me._view;
+ const ctx = me._ctx;
+ const spanGaps = vm.spanGaps;
+ let closePath = me._loop;
+ let points = me._children;
if (!points.length) {
return;
}
- if (me._loop) {
- points = points.slice(); // clone array
- for (index = 0; index < points.length; ++index) {
- previous = points[Math.max(0, index - 1)];
- // If the line has an open path, shift the point array
- if (!points[index]._view.skip && previous._view.skip) {
- points = points.slice(index).concat(points.slice(0, index));
- closePath = spanGaps;
- break;
- }
- }
- // If the line has a close path, add the first point again
- if (closePath) {
- points.push(points[0]);
- }
+ if (closePath) {
+ points = startAtGap(points, spanGaps);
+ closePath = points.closePath;
}
ctx.save();
- // Stroke Line Options
- ctx.lineCap = vm.borderCapStyle || globalOptionLineElements.borderCapStyle;
-
- // IE 9 and 10 do not support line dash
- if (ctx.setLineDash) {
- ctx.setLineDash(vm.borderDash || globalOptionLineElements.borderDash);
- }
-
- ctx.lineDashOffset = valueOrDefault(vm.borderDashOffset, globalOptionLineElements.borderDashOffset);
- ctx.lineJoin = vm.borderJoinStyle || globalOptionLineElements.borderJoinStyle;
- ctx.lineWidth = valueOrDefault(vm.borderWidth, globalOptionLineElements.borderWidth);
- ctx.strokeStyle = vm.borderColor || globalDefaults.defaultColor;
+ setStyle(ctx, vm);
- // Stroke Line
ctx.beginPath();
- // First point moves to its starting position no matter what
- currentVM = points[0]._view;
- if (!currentVM.skip) {
- ctx.moveTo(currentVM.x, currentVM.y);
- lastDrawnIndex = 0;
- }
-
- for (index = 1; index < points.length; ++index) {
- currentVM = points[index]._view;
- previous = lastDrawnIndex === -1 ? points[index - 1] : points[lastDrawnIndex];
-
- if (!currentVM.skip) {
- if ((lastDrawnIndex !== (index - 1) && !spanGaps) || lastDrawnIndex === -1) {
- // There was a gap and this is the first point after the gap
- ctx.moveTo(currentVM.x, currentVM.y);
- } else {
- // Line to next point
- helpers.canvas.lineTo(ctx, previous._view, currentVM);
- }
- lastDrawnIndex = index;
- }
+ if (useFastPath(vm)) {
+ fastPath(ctx, points, spanGaps);
+ } else {
+ normalPath(ctx, points, spanGaps);
}
if (closePath) {