billboard.js
Version:
Re-usable easy interface JavaScript chart library, based on D3 v4+
168 lines (139 loc) • 4.67 kB
text/typescript
/**
* Copyright (c) 2017 ~ present NAVER Corp.
* billboard.js project is licensed under the MIT license
*/
import {area as d3Area, line as d3Line} from "d3-shape";
type PathContext = CanvasRenderingContext2D | null | undefined;
type PathResult = string | void;
/**
* Get values with step handling for line-like paths.
* @param {object} $$ ChartInternal instance
* @param {object} d Data target (needs `.id` for per-series step type detection)
* @param {Array} values Null-filtered values to draw
* @returns {Array} Drawable values
* @private
*/
function getLineValues($$, d, values): any[] {
return $$.isStepType(d) ? $$.convertValuesToStep(values) : values;
}
/**
* Generate line path drawing function shared by SVG and canvas renderers.
* @param {object} $$ ChartInternal instance
* @param {object} lineIndices Shape indices
* @param {boolean} isSub Whether to use subchart scales
* @param {object} context Optional canvas path context
* @returns {function} Line path drawing function
* @private
*/
export function generateDrawLinePath(
$$,
lineIndices,
isSub?: boolean,
context?: PathContext
): (d) => PathResult {
const {config, scale} = $$;
const lineConnectNull = config.line_connectNull;
const isRotated = config.axis_rotated;
const getPoints = $$.generateGetLinePoints(lineIndices, isSub);
const yScale = $$.getYScaleById.bind($$);
const xValue = d => (isSub ? $$.subxx : $$.xx).call($$, d);
const yValue = (d, i) => (
$$.isGrouped(d.id) ? getPoints(d, i)[0][1] : yScale(d.id, isSub)($$.getBaseValue(d))
);
let line = d3Line<any>();
line = isRotated ? line.x(yValue).y(xValue) : line.x(xValue).y(yValue);
context && (line = line.context(context));
if (!lineConnectNull) {
line = line.defined(d => $$.getBaseValue(d) !== null);
}
const x = isSub ? scale.subX : scale.x;
return d => {
const y = yScale(d.id, isSub);
let values = lineConnectNull ? $$.filterRemoveNull(d.values) : d.values;
let x0 = 0;
let y0 = 0;
let path;
if ($$.isLineType(d)) {
const regions = config.data_regions[d.id];
if (regions && !context && $$.lineWithRegions) {
if ($$.isAreaRangeType(d)) {
values = values.map(dv => ({...dv, value: $$.getRangedData(dv, "mid")}));
}
if ($$.isStepType(d)) {
values = $$.convertValuesToStep(values);
}
path = $$.lineWithRegions(values, scale.zoom || x, y, regions);
} else {
path = line.curve($$.getCurve(d))(getLineValues($$, d, values));
}
} else {
if (values[0]) {
x0 = x(values[0].x);
y0 = y(values[0].value);
}
path = isRotated ? `M ${y0} ${x0}` : `M ${x0} ${y0}`;
}
return path || (context ? undefined : "M 0 0");
};
}
/**
* Generate area path drawing function shared by SVG and canvas renderers.
* @param {object} $$ ChartInternal instance
* @param {object} areaIndices Shape indices
* @param {boolean} isSub Whether to use subchart scales
* @param {object} context Optional canvas path context
* @returns {function} Area path drawing function
* @private
*/
export function generateDrawAreaPath(
$$,
areaIndices,
isSub?: boolean,
context?: PathContext
): (d) => PathResult {
const {config} = $$;
const lineConnectNull = config.line_connectNull;
const isRotated = config.axis_rotated;
const getPoints = $$.generateGetAreaPoints(areaIndices, isSub);
const yScale = $$.getYScaleById.bind($$);
const xValue = d => (isSub ? $$.subxx : $$.xx).call($$, d);
const value0 = (d, i) => ($$.isGrouped(d.id) ? getPoints(d, i)[0][1] : yScale(d.id, isSub)(
$$.isAreaRangeType(d) ? $$.getRangedData(d, "high") : $$.getShapeYMin(d.id)
));
const value1 = (d, i) => ($$.isGrouped(d.id) ? getPoints(d, i)[1][1] : yScale(d.id, isSub)(
$$.isAreaRangeType(d) ? $$.getRangedData(d, "low") : d.value
));
return d => {
let values = lineConnectNull ? $$.filterRemoveNull(d.values) : d.values;
let x0 = 0;
let y0 = 0;
let path;
if ($$.isAreaType(d)) {
let area = d3Area<any>();
area = isRotated ?
area.y(xValue)
.x0(value0)
.x1(value1) :
area.x(xValue)
.y0(config.area_above ? 0 : (
config.area_below ? $$.state.height : value0
))
.y1(value1);
context && (area = area.context(context));
if (!lineConnectNull) {
area = area.defined(d => $$.getBaseValue(d) !== null);
}
if ($$.isStepType(d)) {
values = $$.convertValuesToStep(values);
}
path = area.curve($$.getCurve(d))(values);
} else {
if (values[0]) {
x0 = $$.scale.x(values[0].x);
y0 = $$.getYScaleById(d.id)(values[0].value);
}
path = isRotated ? `M ${y0} ${x0}` : `M ${x0} ${y0}`;
}
return path || (context ? undefined : "M 0 0");
};
}