diff --git a/demo/demo.js b/demo/demo.js index 4bb027bbd..c95da5010 100644 --- a/demo/demo.js +++ b/demo/demo.js @@ -3741,6 +3741,42 @@ d3.select(".chart_area") ] }, BarChartOptions: { + BarIndices: [ + { + options: { + data: { + columns: [ + ['data1', 4, null, 4], + ['data2', null, 3, null], + ['data3', 1, 4, 4] + ], + type: "bar" + }, + bar: { + indices: { + removeNull: true + } + } + } + }, + { + options: { + data: { + columns: [ + ['data1', 4, null, 4], + ['data2', null, 3, null], + ['data3', 1, 4, 4] + ], + type: "bar" + }, + bar: { + indices: { + removeNull: false + } + } + } + }, + ], BarPadding: { options: { data: { diff --git a/src/Chart/api/data.ts b/src/Chart/api/data.ts index 48004291d..0e4a777dc 100644 --- a/src/Chart/api/data.ts +++ b/src/Chart/api/data.ts @@ -4,8 +4,7 @@ */ import {DataItem} from "../../../types/types"; import {extend, isUndefined, isArray} from "../../module/util"; - -type DataParam = {x: number, value: number, id: string, index: number}[]; +import {IDataRow} from "../../ChartInternal/data/IData"; /** * Get data loaded in the chart. @@ -173,7 +172,7 @@ extend(data, { * chart.data.min(); * // --> [{x: 0, value: 30, id: "data1", index: 0}, ...] */ - min: function(): DataParam { + min: function(): IDataRow[] { return this.internal.getMinMaxData().min; }, @@ -188,7 +187,7 @@ extend(data, { * chart.data.max(); * // --> [{x: 3, value: 400, id: "data1", index: 3}, ...] */ - max: function(): DataParam { + max: function(): IDataRow[] { return this.internal.getMinMaxData().max; } }); diff --git a/src/ChartInternal/data/IData.ts b/src/ChartInternal/data/IData.ts index aa2eb4578..e2338e385 100644 --- a/src/ChartInternal/data/IData.ts +++ b/src/ChartInternal/data/IData.ts @@ -30,3 +30,12 @@ export interface IGridData { text: string; value: number; } + +export interface IDataIndice { + [key: string]: number; + __max__: number; +} + +export type TIndices = {} | { + [key:string]: IDataIndice +}; diff --git a/src/ChartInternal/data/data.ts b/src/ChartInternal/data/data.ts index 8216beda0..d6b0386fb 100644 --- a/src/ChartInternal/data/data.ts +++ b/src/ChartInternal/data/data.ts @@ -154,7 +154,7 @@ export default { .map(t => $$.addName($$.getValueOnIndex(t.values, index))); if (filterNull) { - value = value.filter(v => isValue(v.value)); + value = value.filter(v => v && "value" in v && isValue(v.value)); } return value; diff --git a/src/ChartInternal/shape/shape.ts b/src/ChartInternal/shape/shape.ts index a385b305f..a98a302ac 100644 --- a/src/ChartInternal/shape/shape.ts +++ b/src/ChartInternal/shape/shape.ts @@ -26,6 +26,7 @@ import {select as d3Select} from "d3-selection"; import {d3Selection} from "../../../types/types"; import CLASS from "../../config/classes"; import {capitalize, getPointer, getRectSegList, getUnique, isObjectType, isNumber, isValue, isUndefined, notEmpty} from "../../module/util"; +import {IDataRow, IDataIndice, TIndices} from "../data/IData"; export default { /** @@ -88,12 +89,12 @@ export default { * @param {Function} typeFilter Chart type filter function * @returns {object} Indices object with its position */ - getShapeIndices(typeFilter): {[key: string]: number} { + getShapeIndices(typeFilter): TIndices { const $$ = this; const {config} = $$; const xs = config.data_xs; const hasXs = notEmpty(xs); - const indices = {}; + const indices: TIndices = {}; let i: any = hasXs ? {} : 0; if (hasXs) { @@ -134,12 +135,28 @@ export default { /** * Get indices value based on data ID value * @param {object} indices Indices object - * @param {string} id Data id value + * @param {object} d Data row + * @param {string} caller Caller function name (Used only for 'sparkline' plugin) * @returns {object} Indices object * @private */ - getIndices(indices, id: string) { - const xs = this.config.data_xs; + getIndices(indices: TIndices, d: IDataRow, caller?: string): IDataIndice { // eslint-disable-line + const $$ = this; + const {data_xs: xs, bar_indices_removeNull: removeNull} = $$.config; + const {id, index} = d; + + if ($$.isBarType(id) && removeNull) { + const ind = {} as IDataIndice; + + // redefine bar indices order + $$.getAllValuesOnIndex(index, true) + .forEach((v, i) => { + ind[v.id] = i; + ind.__max__ = i; + }); + + return ind; + } return notEmpty(xs) ? indices[xs[id]] : indices; @@ -151,12 +168,12 @@ export default { * @returns {number} Max number * @private */ - getIndicesMax(indices): number { + getIndicesMax(indices: TIndices | IDataIndice): number { return notEmpty(this.config.data_xs) ? // if is multiple xs, return total sum of xs' __max__ value Object.keys(indices) .map(v => indices[v].__max__ || 0) - .reduce((acc, curr) => acc + curr) : indices.__max__; + .reduce((acc, curr) => acc + curr) : (indices as IDataIndice).__max__; }, getShapeX(offset, indices, isSub?: boolean): (d) => number { @@ -170,7 +187,7 @@ export default { ); return d => { - const ind = $$.getIndices(indices, d.id, "getShapeX"); + const ind = $$.getIndices(indices, d, "getShapeX"); const index = d.id in ind ? ind[d.id] : 0; const targetsNum = (ind.__max__ || 0) + 1; let x = 0; @@ -289,7 +306,7 @@ export default { return (d, idx) => { const {id, value, x} = d; - const ind = $$.getIndices(indices, id); + const ind = $$.getIndices(indices, d); const scale = $$.getYScaleById(id, isSub); if ($$.isBarRangeType(d)) { diff --git a/src/Plugin/sparkline/index.ts b/src/Plugin/sparkline/index.ts index f165c16ae..ed7f23410 100644 --- a/src/Plugin/sparkline/index.ts +++ b/src/Plugin/sparkline/index.ts @@ -105,8 +105,8 @@ export default class Sparkline extends Plugin { const {getBarW, getIndices} = $$; // override internal methods to positioning bars - $$.getIndices = function(indices, id, caller) { - return caller === "getShapeX" ? {} : getIndices.call(this, indices, id); + $$.getIndices = function(indices, d, caller) { + return caller === "getShapeX" ? {} : getIndices.call(this, indices, d); }; $$.getBarW = function(type, axis) { diff --git a/src/config/Options/shape/bar.ts b/src/config/Options/shape/bar.ts index c372f81da..dd583e6e4 100644 --- a/src/config/Options/shape/bar.ts +++ b/src/config/Options/shape/bar.ts @@ -12,6 +12,7 @@ export default { * @memberof Options * @type {object} * @property {object} bar Bar object + * @property {number} [bar.indices.removeNull=false] Remove nullish data on bar indices positions. * @property {number} [bar.label.threshold=0] Set threshold ratio to show/hide labels. * @property {number} [bar.padding=0] The padding pixel value between each bar. * @property {number} [bar.radius] Set the radius of bar edge in pixel. @@ -27,12 +28,18 @@ export default { * @property {number} [bar.width.dataname.ratio=0.6] Change the width of bar chart by ratio. * @property {number} [bar.width.dataname.max] The maximum width value for ratio. * @property {boolean} [bar.zerobased=true] Set if min or max value will be 0 on bar chart. + * @see [Demo: bar indices](https://naver.github.io/billboard.js/demo/#BarChartOptions.BarIndices) * @see [Demo: bar padding](https://naver.github.io/billboard.js/demo/#BarChartOptions.BarPadding) * @see [Demo: bar radius](https://naver.github.io/billboard.js/demo/#BarChartOptions.BarRadius) * @see [Demo: bar width](https://naver.github.io/billboard.js/demo/#BarChartOptions.BarWidth) * @see [Demo: bar width variant](https://naver.github.io/billboard.js/demo/#BarChartOptions.BarWidthVariant) * @example * bar: { + * // remove nullish data on bar indices postions + * indices: { + * removeNull: true + * }, + * * padding: 1, * * // bar radius @@ -72,6 +79,7 @@ export default { * } */ bar_label_threshold: 0, + bar_indices_removeNull: false, bar_padding: 0, bar_radius: undefined, bar_radius_ratio: undefined, diff --git a/test/shape/bar-spec.ts b/test/shape/bar-spec.ts index d362e96f3..bc1e32518 100644 --- a/test/shape/bar-spec.ts +++ b/test/shape/bar-spec.ts @@ -723,6 +723,38 @@ describe("SHAPE BAR", () => { }); }); + describe("bar indices", () => { + before(() => { + args = { + data: { + columns: [ + ["data1", 4, null, 4], + ["data2", null, 3, null], + ["data3", 1, 4, 4] + ], + type: "bar" + }, + bar: { + indices: { + removeNull: true + } + } + } + }); + + it("should redefined bar indices removing nullish values.", () => { + const {$: {bar}, internal} = chart; + + bar.bars.each(d => { + const indices = internal.getIndices(null, d); + + expect(indices.__max__).to.be.equal(1); + expect(indices.data3).to.be.equal(1); + expect(indices["data1" in indices ? "data1" : "data2"]).to.be.equal(0); + }); + }); + }); + describe("bar radius", () => { before(() => { args = { diff --git a/types/options.d.ts b/types/options.d.ts index a2a3c5e82..150e2040c 100644 --- a/types/options.d.ts +++ b/types/options.d.ts @@ -275,31 +275,6 @@ export interface ChartOptions { }; bar?: { - /** - * Change the width of bar chart. If ratio is specified, change the width of bar chart by ratio. - */ - width?: number | { - /** - * Set the width of each bar by ratio - */ - ratio: number; - - /** - * Set max width of each bar - */ - max?: number; - } | { - /** - * Set the width option for specific dataset - */ - [key: string]: number | { - ratio: number; - max: number; - } - }; - - headers?: Array<{ [key: string]: string; }>; - /** * Set threshold ratio to show/hide labels. */ @@ -308,14 +283,11 @@ export interface ChartOptions { } /** - * Set if min or max value will be 0 on bar chart. - */ - zerobased?: boolean; - - /** - * Set space between bars in bar charts + * Remove nullish data on bar indices positions. */ - space?: number; + indices?: { + removeNull?: boolean; + } /** * The padding pixel value between each bar. @@ -337,6 +309,34 @@ export interface ChartOptions { * The senstivity offset value for interaction boundary. */ sensitivity?: number; + + /** + * Change the width of bar chart. If ratio is specified, change the width of bar chart by ratio. + */ + width?: number | { + /** + * Set the width of each bar by ratio + */ + ratio: number; + + /** + * Set max width of each bar + */ + max?: number; + } | { + /** + * Set the width option for specific dataset + */ + [key: string]: number | { + ratio: number; + max: number; + } + }; + + /** + * Set if min or max value will be 0 on bar chart. + */ + zerobased?: boolean; }; bubble?: {