billboard.js
Version:
Re-usable easy interface JavaScript chart library, based on D3 v4+
220 lines (180 loc) • 5.62 kB
text/typescript
/**
* Copyright (c) 2017 ~ present NAVER Corp.
* billboard.js project is licensed under the MIT license
*/
import {
scaleTime as d3ScaleTime,
scaleLinear as d3ScaleLinear
} from "d3-scale";
import {isString, isValue, parseDate} from "../../module/util";
export default {
getScale(min: number, max: number, forTimeseries?: boolean) {
return (forTimeseries ?
d3ScaleTime() : d3ScaleLinear()
).range([min, max]);
},
/**
* Get x Axis scale function
* @param {number} min Min value
* @param {number} max Max value
* @param {Array} domain Domain value
* @param {Function} offset The offset getter to be sum
* @returns {Function} scale
* @private
*/
getXScale(min: number, max: number, domain: number[], offset: Function) {
const $$ = this;
const scale = $$.scale.zoom || $$.getScale(min, max, $$.axis.isTimeSeries());
return $$.getCustomizedScale(
domain ? scale.domain(domain) : scale,
offset
);
},
/**
* Get y Axis scale function
* @param {number} min Min value
* @param {number} max Max value
* @param {Array} domain Domain value
* @returns {Function} Scale function
* @private
*/
getYScale(min: number, max: number, domain: number[]): Function {
const $$ = this;
const scale = $$.getScale(min, max, $$.axis.isTimeSeriesY());
domain && scale.domain(domain);
return scale;
},
/**
* Get y Axis scale
* @param {string} id Axis id
* @param {boolean} isSub Weather is sub Axis
* @returns {Function} Scale function
* @private
*/
getYScaleById(id: string, isSub = false): Function {
const isY2 = this.axis.getId(id) === "y2";
const key = isSub ? (isY2 ? "subY2" : "subY") : (isY2 ? "y2" : "y");
return this.scale[key];
},
/**
* Get customized scale
* @param {d3.scaleLinear|d3.scaleTime} scaleValue Scale function
* @param {Function} offsetValue Offset getter to be sum
* @returns {Function} Scale function
* @private
*/
getCustomizedScale(scaleValue: Function | any, offsetValue): Function {
const $$ = this;
const offset = offsetValue || (() => $$.axis.x.tickOffset());
const scale = function(d, raw) {
const v = scaleValue(d) + offset();
return raw ? v : Math.ceil(v);
};
// copy original scale methods
for (const key in scaleValue) {
scale[key] = scaleValue[key];
}
scale.orgDomain = () => scaleValue.domain();
scale.orgScale = () => scaleValue;
// define custom domain() for categorized axis
if ($$.axis.isCategorized()) {
scale.domain = function(domainValue) {
let domain = domainValue;
if (!arguments.length) {
domain = this.orgDomain();
return [domain[0], domain[1] + 1];
}
scaleValue.domain(domain);
return scale;
};
}
return scale;
},
/**
* Update scale
* @param {boolean} isInit Param is given at the init rendering
* @param {boolean} updateXDomain If update x domain
* @private
*/
updateScales(isInit: boolean, updateXDomain = true): void {
const $$ = this;
const {axis, config, format, org, scale,
state: {width, height, width2, height2, hasAxis}
} = $$;
if (hasAxis) {
const isRotated = config.axis_rotated;
// update edges
const min = {
x: isRotated ? 1 : 0,
y: isRotated ? 0 : height,
subX: isRotated ? 1 : 0,
subY: isRotated ? 0 : height2
};
const max = {
x: isRotated ? height : width,
y: isRotated ? width : 1,
subX: isRotated ? height : width,
subY: isRotated ? width2 : 1
};
// update scales
// x Axis
const xDomain = updateXDomain && scale.x && scale.x.orgDomain();
const xSubDomain = updateXDomain && org.xDomain;
scale.x = $$.getXScale(min.x, max.x, xDomain, () => axis.x.tickOffset());
scale.subX = $$.getXScale(min.x, max.x, xSubDomain, d => (d % 1 ? 0 : axis.subX.tickOffset()));
format.xAxisTick = axis.getXAxisTickFormat();
axis.setAxis("x", scale.x, config.axis_x_tick_outer, isInit);
if (config.subchart_show) {
axis.setAxis("subX", scale.subX, config.axis_x_tick_outer, isInit);
}
// y Axis
scale.y = $$.getYScale(min.y, max.y, scale.y ? scale.y.domain() : config.axis_y_default);
scale.subY = $$.getYScale(
min.subY, max.subY, scale.subY ? scale.subY.domain() : config.axis_y_default);
axis.setAxis("y", scale.y, config.axis_y_tick_outer, isInit);
// y2 Axis
if (config.axis_y2_show) {
scale.y2 = $$.getYScale(min.y, max.y, scale.y2 ? scale.y2.domain() : config.axis_y2_default);
scale.subY2 = $$.getYScale(min.subY, max.subY,
scale.subY2 ? scale.subY2.domain() : config.axis_y2_default);
axis.setAxis("y2", scale.y2, config.axis_y2_tick_outer, isInit);
}
} else {
// update for arc
$$.updateArc && $$.updateArc();
}
},
/**
* Get the zoom or unzoomed scaled value
* @param {Date|number|object} d Data value
* @returns {number|null}
* @private
*/
xx(d): number | null {
const $$ = this;
const {config, scale: {x, zoom}} = $$;
const fn = config.zoom_enabled && zoom ?
zoom : x;
return d ? fn(isValue(d.x) ? d.x : d) : null;
},
xv(d): number {
const $$ = this;
const {axis, config, scale: {x}} = $$;
let value = $$.getBaseValue(d);
if (axis.isTimeSeries()) {
value = parseDate.call($$, value);
} else if (axis.isCategorized() && isString(value)) {
value = config.axis_x_categories.indexOf(value);
}
return Math.ceil(x(value));
},
yv(d): number {
const $$ = this;
const {scale: {y, y2}} = $$;
const yScale = d.axis && d.axis === "y2" ? y2 : y;
return Math.ceil(yScale($$.getBaseValue(d)));
},
subxx(d): number | null {
return d ? this.scale.subX(d.x) : null;
}
};