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
655 changes: 0 additions & 655 deletions src/plugins/plugin.filler.js

This file was deleted.

122 changes: 122 additions & 0 deletions src/plugins/plugin.filler/filler.drawing.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import {clipArea, unclipArea} from '../../helpers';
import {_findSegmentEnd, _getBounds, _segments} from './filler.segment';
import {_getTarget} from './filler.target';

export function _drawfill(ctx, source, area) {
const target = _getTarget(source);
const {line, scale, axis} = source;
const lineOpts = line.options;
const fillOption = lineOpts.fill;
const color = lineOpts.backgroundColor;
const {above = color, below = color} = fillOption || {};
if (target && line.points.length) {
clipArea(ctx, area);
doFill(ctx, {line, target, above, below, area, scale, axis});
unclipArea(ctx);
}
}

function doFill(ctx, cfg) {
const {line, target, above, below, area, scale} = cfg;
const property = line._loop ? 'angle' : cfg.axis;

ctx.save();

if (property === 'x' && below !== above) {
clipVertical(ctx, target, area.top);
fill(ctx, {line, target, color: above, scale, property});
ctx.restore();
ctx.save();
clipVertical(ctx, target, area.bottom);
}
fill(ctx, {line, target, color: below, scale, property});

ctx.restore();
}

function clipVertical(ctx, target, clipY) {
const {segments, points} = target;
let first = true;
let lineLoop = false;

ctx.beginPath();
for (const segment of segments) {
const {start, end} = segment;
const firstPoint = points[start];
const lastPoint = points[_findSegmentEnd(start, end, points)];
if (first) {
ctx.moveTo(firstPoint.x, firstPoint.y);
first = false;
} else {
ctx.lineTo(firstPoint.x, clipY);
ctx.lineTo(firstPoint.x, firstPoint.y);
}
lineLoop = !!target.pathSegment(ctx, segment, {move: lineLoop});
if (lineLoop) {
ctx.closePath();
} else {
ctx.lineTo(lastPoint.x, clipY);
}
}

ctx.lineTo(target.first().x, clipY);
ctx.closePath();
ctx.clip();
}

function fill(ctx, cfg) {
const {line, target, property, color, scale} = cfg;
const segments = _segments(line, target, property);

for (const {source: src, target: tgt, start, end} of segments) {
const {style: {backgroundColor = color} = {}} = src;
const notShape = target !== true;

ctx.save();
ctx.fillStyle = backgroundColor;

clipBounds(ctx, scale, notShape && _getBounds(property, start, end));

ctx.beginPath();

const lineLoop = !!line.pathSegment(ctx, src);

let loop;
if (notShape) {
if (lineLoop) {
ctx.closePath();
} else {
interpolatedLineTo(ctx, target, end, property);
}

const targetLoop = !!target.pathSegment(ctx, tgt, {move: lineLoop, reverse: true});
loop = lineLoop && targetLoop;
if (!loop) {
interpolatedLineTo(ctx, target, start, property);
}
}

ctx.closePath();
ctx.fill(loop ? 'evenodd' : 'nonzero');

ctx.restore();
}
}

function clipBounds(ctx, scale, bounds) {
const {top, bottom} = scale.chart.chartArea;
const {property, start, end} = bounds || {};
if (property === 'x') {
ctx.beginPath();
ctx.rect(start, top, end - start, bottom - top);
ctx.clip();
}
}

function interpolatedLineTo(ctx, target, point, property) {
const interpolatedPoint = target.interpolate(point, property);
if (interpolatedPoint) {
ctx.lineTo(interpolatedPoint.x, interpolatedPoint.y);
}
}

136 changes: 136 additions & 0 deletions src/plugins/plugin.filler/filler.options.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import {isObject, isFinite, valueOrDefault} from '../../helpers/helpers.core';

/**
* @typedef { import('../../core/core.scale').default } Scale
* @typedef { import('../../elements/element.line').default } LineElement
* @typedef { import('../../../types/index.esm').FillTarget } FillTarget
* @typedef { import('../../../types/index.esm').ComplexFillTarget } ComplexFillTarget
*/

export function _resolveTarget(sources, index, propagate) {
const source = sources[index];
let fill = source.fill;
const visited = [index];
let target;

if (!propagate) {
return fill;
}

while (fill !== false && visited.indexOf(fill) === -1) {
if (!isFinite(fill)) {
return fill;
}

target = sources[fill];
if (!target) {
return false;
}

if (target.visible) {
return fill;
}

visited.push(fill);
fill = target.fill;
}

return false;
}

/**
* @param {LineElement} line
* @param {number} index
* @param {number} count
*/
export function _decodeFill(line, index, count) {
const fill = parseFillOption(line);

if (isObject(fill)) {
return isNaN(fill.value) ? false : fill;
}

let target = parseFloat(fill);

if (isFinite(target) && Math.floor(target) === target) {
return decodeTargetIndex(fill[0], index, target, count);
}

return ['origin', 'start', 'end', 'stack', 'shape'].indexOf(fill) >= 0 && fill;
}

function decodeTargetIndex(firstCh, index, target, count) {
if (firstCh === '-' || firstCh === '+') {
target = index + target;
}

if (target === index || target < 0 || target >= count) {
return false;
}

return target;
}

/**
* @param {FillTarget | ComplexFillTarget} fill
* @param {Scale} scale
* @returns {number | null}
*/
export function _getTargetPixel(fill, scale) {
let pixel = null;
if (fill === 'start') {
pixel = scale.bottom;
} else if (fill === 'end') {
pixel = scale.top;
} else if (isObject(fill)) {
// @ts-ignore
pixel = scale.getPixelForValue(fill.value);
} else if (scale.getBasePixel) {
pixel = scale.getBasePixel();
}
return pixel;
}

/**
* @param {FillTarget | ComplexFillTarget} fill
* @param {Scale} scale
* @param {number} startValue
* @returns {number | undefined}
*/
export function _getTargetValue(fill, scale, startValue) {
let value;

if (fill === 'start') {
value = startValue;
} else if (fill === 'end') {
value = scale.options.reverse ? scale.min : scale.max;
} else if (isObject(fill)) {
// @ts-ignore
value = fill.value;
} else {
value = scale.getBaseValue();
}
return value;
}

/**
* @param {LineElement} line
*/
function parseFillOption(line) {
const options = line.options;
const fillOption = options.fill;
let fill = valueOrDefault(fillOption && fillOption.target, fillOption);

if (fill === undefined) {
fill = !!options.backgroundColor;
}

if (fill === false || fill === null) {
return false;
}

if (fill === true) {
return 'origin';
}
return fill;
}
99 changes: 99 additions & 0 deletions src/plugins/plugin.filler/filler.segment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import {_boundSegment, _boundSegments, _normalizeAngle} from '../../helpers';

export function _segments(line, target, property) {
const segments = line.segments;
const points = line.points;
const tpoints = target.points;
const parts = [];

for (const segment of segments) {
let {start, end} = segment;
end = _findSegmentEnd(start, end, points);

const bounds = _getBounds(property, points[start], points[end], segment.loop);

if (!target.segments) {
// Special case for boundary not supporting `segments` (simpleArc)
// Bounds are provided as `target` for partial circle, or undefined for full circle
parts.push({
source: segment,
target: bounds,
start: points[start],
end: points[end]
});
continue;
}

// Get all segments from `target` that intersect the bounds of current segment of `line`
const targetSegments = _boundSegments(target, bounds);

for (const tgt of targetSegments) {
const subBounds = _getBounds(property, tpoints[tgt.start], tpoints[tgt.end], tgt.loop);
const fillSources = _boundSegment(segment, points, subBounds);

for (const fillSource of fillSources) {
parts.push({
source: fillSource,
target: tgt,
start: {
[property]: _getEdge(bounds, subBounds, 'start', Math.max)
},
end: {
[property]: _getEdge(bounds, subBounds, 'end', Math.min)
}
});
}
}
}
return parts;
}

export function _getBounds(property, first, last, loop) {
if (loop) {
return;
}
let start = first[property];
let end = last[property];

if (property === 'angle') {
start = _normalizeAngle(start);
end = _normalizeAngle(end);
}
return {property, start, end};
}

export function _pointsFromSegments(boundary, line) {
const {x = null, y = null} = boundary || {};
const linePoints = line.points;
const points = [];
line.segments.forEach(({start, end}) => {
end = _findSegmentEnd(start, end, linePoints);
const first = linePoints[start];
const last = linePoints[end];
if (y !== null) {
points.push({x: first.x, y});
points.push({x: last.x, y});
} else if (x !== null) {
points.push({x, y: first.y});
points.push({x, y: last.y});
}
});
return points;
}

export function _findSegmentEnd(start, end, points) {
for (;end > start; end--) {
const point = points[end];
if (!isNaN(point.x) && !isNaN(point.y)) {
break;
}
}
return end;
}

function _getEdge(a, b, prop, fn) {
if (a && b) {
return fn(a[prop], b[prop]);
}
return a ? a[prop] : b ? b[prop] : 0;
}
Loading