billboard.js
Version:
Re-usable easy interface JavaScript chart library, based on D3 v4+
388 lines (328 loc) • 9.56 kB
text/typescript
/**
* Copyright (c) 2017 ~ present NAVER Corp.
* billboard.js project is licensed under the MIT license
*/
import {TYPE, TYPE_BY_CATEGORY} from "../../config/const";
import {isArray, isNumber, isString} from "../../module/util";
import type {IData} from "../data/IData";
// Module-level constant: avoids re-creating the list on every isInterpolationType() call
// https://github.com/d3/d3-shape#curves
const INTERPOLATION_TYPES = new Set([
"basis",
"basis-closed",
"basis-open",
"bundle",
"cardinal",
"cardinal-closed",
"cardinal-open",
"catmull-rom",
"catmull-rom-closed",
"catmull-rom-open",
"linear",
"linear-closed",
"monotone-x",
"monotone-y",
"natural"
]);
export default {
/**
* Check if the given chart type is valid
* @param {string} type Chart type string
* @returns {boolean}
* @private
*/
isValidChartType(type: string): boolean {
return !!(type && Object.values(TYPE).indexOf(type) > -1);
},
/**
* Get the chart type to use for the subchart target.
* subchart.types > subchart.type > data.types > data.type > line
* @param {object|string} d Target data or id
* @returns {string}
* @private
*/
getSubchartTargetType(d): string {
const $$ = this;
const {config} = $$;
const id = isString(d) ? d : d?.id;
const subchartTypes = config.subchart_types || {};
const subchartType = config.subchart_type;
const dataTypes = config.data_types || {};
const targetSubchartType = id && subchartTypes[id];
if (targetSubchartType && $$.isValidChartType(targetSubchartType)) {
return targetSubchartType;
} else if (subchartType && $$.isValidChartType(subchartType)) {
return subchartType;
}
return (id && dataTypes[id]) || config.data_type || TYPE.LINE;
},
/**
* Get the regular chart type configured for the target.
* @param {object|string} d Target data or id
* @returns {string}
* @private
*/
getTargetType(d): string {
const {config} = this;
const id = isString(d) ? d : d?.id;
return (id && config.data_types?.[id]) || config.data_type || TYPE.LINE;
},
/**
* Get the target's original chart type while subchart.type context is active.
* @param {object|string} d Target data or id
* @returns {string}
* @private
*/
getSubchartSourceTargetType(d): string {
const {state} = this;
const id = isString(d) ? d : d?.id;
const sourceTypes = state.subchartSourceTypes;
return (id && sourceTypes?.[id]) || this.getTargetType(d);
},
/**
* Check whether a target had the given source type before subchart.type remapping.
* @param {object|string} d Target data or id
* @param {string|Array} type chart type
* @returns {boolean}
* @private
*/
isSubchartSourceTypeOf(d, type): boolean {
const sourceType = this.getSubchartSourceTargetType(d);
return isArray(type) ? type.indexOf(sourceType) >= 0 : sourceType === type;
},
/**
* Run a callback while regular type helpers resolve using subchart.type/types.
* @param {function(): unknown} callback Callback to run
* @returns {unknown} Callback return value
* @private
*/
withSubchartTypeContext(callback: () => unknown) {
const $$ = this;
const {config, data, state} = $$;
const dataType = config.data_type;
const dataTypes = config.data_types;
const currentTypes = state.current.types;
const sourceTypes = state.subchartSourceTypes;
const subchartTypes = {};
const subchartSourceTypes = {};
const subchartType = config.subchart_type;
$$.mapToIds(data.targets).forEach(id => {
subchartSourceTypes[id] = $$.getTargetType(id);
subchartTypes[id] = $$.getSubchartTargetType(id);
});
config.data_type = subchartType && $$.isValidChartType(subchartType) ?
subchartType :
dataType;
config.data_types = subchartTypes;
state.current.types = [];
state.subchartSourceTypes = subchartSourceTypes;
try {
return callback();
} finally {
config.data_type = dataType;
config.data_types = dataTypes;
state.current.types = currentTypes;
state.subchartSourceTypes = sourceTypes;
}
},
setTargetType(targetIds: string[], type: string): void {
const $$ = this;
const {config, state: {withoutFadeIn}} = $$;
$$.mapToTargetIds(targetIds).forEach(id => {
withoutFadeIn[id] = type === config.data_types[id];
config.data_types[id] = type;
});
if (!targetIds) {
config.data_type = type;
}
},
/**
* Updte current used chart types
* @private
*/
updateTypesElements(): void {
const $$ = this;
const {state: {current}} = $$;
Object.keys(TYPE).forEach(v => {
const t = TYPE[v];
const has = $$.hasType(t, null, true);
const idx = current.types.indexOf(t);
if (idx === -1 && has) {
current.types.push(t);
} else if (idx > -1 && !has) {
current.types.splice(idx, 1);
}
});
// Update current chart elements reference
$$.setChartElements();
},
/**
* Check if given chart types exists
* @param {string} type Chart type
* @param {Array} targetsValue Data array
* @param {boolean} checkFromData Force to check type cotains from data targets
* @returns {boolean}
* @private
*/
hasType(type: string, targetsValue?, checkFromData = false): boolean {
const $$ = this;
const {config, state: {current}} = $$;
const types = config.data_types;
const targets = targetsValue || $$.data.targets;
if (!checkFromData && current.types?.indexOf(type) > -1) {
return true;
} else if (targets?.length) {
return targets.some(target => {
const t = types[target.id];
return t === type || (!t && type === "line");
});
} else if (Object.keys(types).length) {
return Object.values(types).some(t => t === type);
}
return config.data_type === type;
},
/**
* Check if contains given chart types
* @param {string} type Type key
* @param {object} targets Target data
* @param {Array} exclude Excluded types
* @returns {boolean}
* @private
*/
hasTypeOf(type, targets, exclude: string[] = []): boolean {
if (type in TYPE_BY_CATEGORY) {
return !TYPE_BY_CATEGORY[type]
.filter((v: string) => exclude.indexOf(v) === -1)
.every((v: string) => !this.hasType(v, targets));
}
return false;
},
/**
* Check if given data is certain chart type
* @param {object} d Data object
* @param {string|Array} type chart type
* @returns {boolean}
* @private
*/
isTypeOf(d, type): boolean {
const id = isString(d) ? d : d.id;
const dataType = this.config && this.getTargetType(id);
return isArray(type) ? type.indexOf(dataType) >= 0 : dataType === type;
},
hasPointType(): boolean {
const $$ = this;
return $$.hasTypeOf("Line") || $$.hasType("bubble") || $$.hasType("scatter");
},
/**
* Check if contains arc types chart
* @param {object} targets Target data
* @param {Array} exclude Excluded types
* @returns {boolean}
* @private
*/
hasArcType(targets?: IData, exclude?: string[]): boolean {
return this.hasTypeOf("Arc", targets, exclude);
},
hasMultiArcGauge(): boolean {
return this.hasType("gauge") && this.config.gauge_type === "multi";
},
isLineType(d): boolean {
const id = isString(d) ? d : d.id;
return !this.config.data_types[id] ||
this.isTypeOf(id, TYPE_BY_CATEGORY.Line);
},
isStepType(d): boolean {
return this.isTypeOf(d, TYPE_BY_CATEGORY.Step);
},
isSplineType(d): boolean {
return this.isTypeOf(d, TYPE_BY_CATEGORY.Spline);
},
isAreaType(d): boolean {
return this.isTypeOf(d, TYPE_BY_CATEGORY.Area);
},
isAreaRangeType(d): boolean {
return this.isTypeOf(d, TYPE_BY_CATEGORY.AreaRange);
},
isBarType(d): boolean {
return this.isTypeOf(d, "bar");
},
isBubbleType(d): boolean {
return this.isTypeOf(d, "bubble");
},
isCandlestickType(d): boolean {
return this.isTypeOf(d, "candlestick");
},
isScatterType(d): boolean {
return this.isTypeOf(d, "scatter");
},
isTreemapType(d): boolean {
return this.isTypeOf(d, "treemap");
},
isPieType(d): boolean {
return this.isTypeOf(d, "pie");
},
isFunnelType(d): boolean {
return this.isTypeOf(d, "funnel");
},
isGaugeType(d): boolean {
return this.isTypeOf(d, "gauge");
},
isDonutType(d): boolean {
return this.isTypeOf(d, "donut");
},
isPolarType(d): boolean {
return this.isTypeOf(d, "polar");
},
isRadarType(d): boolean {
return this.isTypeOf(d, "radar");
},
isArcType(d): boolean {
return this.isPieType(d) ||
this.isDonutType(d) ||
this.isGaugeType(d) ||
this.isPolarType(d) ||
this.isRadarType(d);
},
// determine if is 'circle' data point
isCirclePoint(node?): boolean {
const {config} = this;
const pattern = config.point_pattern;
const isCircle = node?.tagName === "circle" || (
config.point_type === "circle" &&
(!pattern || (
isArray(pattern) && pattern.length === 0
))
);
return isCircle;
},
lineData(d) {
return this.isLineType(d) ? [d] : [];
},
arcData(d) {
return this.isArcType(d.data) ? [d] : [];
},
/**
* Get data adapt for data label showing
* @param {object} d Data object
* @returns {Array}
* @private
*/
labelishData(d) {
return this.isBarType(d) ||
this.isLineType(d) ||
this.isScatterType(d) ||
this.isBubbleType(d) ||
this.isCandlestickType(d) ||
this.isFunnelType(d) ||
this.isRadarType(d) ||
this.isTreemapType(d) ?
d.values.filter(v => isNumber(v.value) || Boolean(v.value)) :
[];
},
barLineBubbleData(d) {
return this.isBarType(d) || this.isLineType(d) || this.isBubbleType(d) ? d.values : [];
},
isInterpolationType(type: string): boolean {
return INTERPOLATION_TYPES.has(type);
}
};