diff --git a/demo/demo.js b/demo/demo.js index 60bec6c96..85fd3b156 100644 --- a/demo/demo.js +++ b/demo/demo.js @@ -4396,36 +4396,80 @@ d3.select(".chart_area") }, Region: { - Region: { - options: { - data: { - columns: [ - ["data1", 30, 200, 100, 400, 150, 250, 400], - ["data2", 830, 1200, 1100, 1400, 1150, 1250, 1500] - ], - type: "line", - axes: { - data2: "y2" - } - }, - axis: { - y2: { - show: true - } - }, - regions: [ - {axis: "x", end: 1, class: "regionX"}, - {axis: "x", start: 2, end: 4, class: "regionX"}, - {axis: "x", start: 5, class: "regionX"}, - {axis: "y", end: 50, class: "regionY"}, - {axis: "y", start: 80, end: 140, class: "regionY"}, - {axis: "y", start: 400, class: "regionY"}, - {axis: "y2", end: 900, class: "regionY2"}, - {axis: "y2", start: 1150, end: 1250, class: "regionY2"}, - {axis: "y2", start: 1300, class: "regionY2"} - ] + Region: [ + { + options: { + data: { + columns: [ + ["data1", 30, 200, 100, 400, 150, 250, 400], + ["data2", 830, 1200, 1100, 1400, 1150, 1250, 1500] + ], + type: "line", + axes: { + data2: "y2" + } + }, + axis: { + y2: { + show: true + } + }, + regions: [ + {axis: "x", end: 1, class: "regionX"}, + {axis: "x", start: 2, end: 4, class: "regionX"}, + {axis: "x", start: 5, class: "regionX"}, + {axis: "y", end: 50, class: "regionY"}, + {axis: "y", start: 80, end: 140, class: "regionY"}, + {axis: "y", start: 400, class: "regionY"}, + {axis: "y2", end: 900, class: "regionY2"}, + {axis: "y2", start: 1150, end: 1250, class: "regionY2"}, + {axis: "y2", start: 1300, class: "regionY2"} + ] + } + }, + { + options: { + data: { + columns: [ + ["data1", 30, 200, 100, 400, 150, 250], + ["data2", 100, 150, 130, 200, 220, 190] + ], + axes: { + data2: "y2" + }, + type: "line", + colors: { + data1: "#ff0000" + } + }, + axis: { + x: { + type: "category", + categories: [ + "cat1", + "cat2", + "cat3", + "cat4", + "cat5", + "cat6" + ] + } + }, + regions: [ + { + axis: "x", + start: "cat2", + end: "cat3" + }, + { + axis: "x", + start: "cat5", + end: 5 + } + ] + } } - }, + ], RegionLabel: { options: { data: { diff --git a/src/Chart/api/regions.ts b/src/Chart/api/regions.ts index 8e80d35f9..ddaeb1b8e 100644 --- a/src/Chart/api/regions.ts +++ b/src/Chart/api/regions.ts @@ -36,7 +36,7 @@ function regionsFn(regions: RegionsParam, isAdd = false): RegionsParam { * @function regions * @instance * @memberof Chart - * @param {Array} regions Regions will be replaced with this argument. The format of this argument is the same as regions. + * @param {Array} regions Regions will be replaced with this argument. The format of this argument is the same as [regions](./Options.html#.regions) option. * @returns {Array} regions * @example * // Show 2 regions @@ -65,7 +65,7 @@ extend(regions, { * @function regions․add * @instance * @memberof Chart - * @param {Array|object} regions New region will be added. The format of this argument is the same as regions and it's possible to give an Object if only one region will be added. + * @param {Array|object} regions New region will be added. The format of this argument is the same as [regions](./Options.html#.regions) and it's possible to give an Object if only one region will be added. * @returns {Array} regions * @example * // Add a new region diff --git a/src/ChartInternal/internals/region.ts b/src/ChartInternal/internals/region.ts index bc7098258..96f5f189c 100644 --- a/src/ChartInternal/internals/region.ts +++ b/src/ChartInternal/internals/region.ts @@ -3,7 +3,8 @@ * billboard.js project is licensed under the MIT license */ import {select as d3Select} from "d3-selection"; // selection -import type {AxisType, RegionsType} from "../../../types/types"; +import type {RegionOptions} from "../../../types/options"; +import type {AxisType} from "../../../types/types"; import {$REGION} from "../../config/classes"; import {isValue, parseDate} from "../../module/util"; @@ -98,76 +99,73 @@ export default { ]; }, - getRegionXY(type: AxisType, d: RegionsType): number { - const $$ = this; - const {config, scale} = $$; - const isRotated = config.axis_rotated; - const isX = type === "x"; - let key = "start"; - let currScale; - let pos = 0; - - if (d.axis === "y" || d.axis === "y2") { - if (!isX) { - key = "end"; - } - - if ((isX ? isRotated : !isRotated) && key in d) { - currScale = scale[d.axis]; - pos = currScale(d[key]); - } - } else if ((isX ? !isRotated : isRotated) && key in d) { - currScale = scale.zoom || scale.x; - pos = currScale($$.axis.isTimeSeries() ? parseDate.call($$, d[key]) : d[key]); - } + regionX(d: RegionOptions): number { + return this.getRegionSize("x", d); + }, - return pos; + regionY(d: RegionOptions): number { + return this.getRegionSize("y", d); }, - regionX(d: RegionsType): number { - return this.getRegionXY("x", d); + regionWidth(d: RegionOptions): number { + return this.getRegionSize("width", d); }, - regionY(d: RegionsType): number { - return this.getRegionXY("y", d); + regionHeight(d: RegionOptions): number { + return this.getRegionSize("height", d); }, - getRegionSize(type: "width" | "height", d: RegionsType): number { + /** + * Get Region size according start/end position + * @param {string} type Type string + * @param {ojbect} d Data object + * @returns {number} + * @private + */ + getRegionSize(type: AxisType | "width" | "height", d: RegionOptions): number { const $$ = this; const {config, scale, state} = $$; const isRotated = config.axis_rotated; - const isWidth = type === "width"; - const start = $$[isWidth ? "regionX" : "regionY"](d); + const isAxisType = /(x|y|y2)/.test(type); + + const isType = isAxisType ? type === "x" : type === "width"; + const start = !isAxisType && $$[isType ? "regionX" : "regionY"](d); + let key = isAxisType ? "start" : "end"; + let pos = isAxisType ? 0 : state[type]; let currScale; - let key = "end"; - let end = state[type]; if (d.axis === "y" || d.axis === "y2") { - if (!isWidth) { + if (!isAxisType && !isType) { key = "start"; + } else if (isAxisType && !isType) { + key = "end"; } - if ((isWidth ? isRotated : !isRotated) && key in d) { + if ((isType ? isRotated : !isRotated) && key in d) { currScale = scale[d.axis]; - end = currScale(d[key]); } - } else if ((isWidth ? !isRotated : isRotated) && key in d) { + } else if ((isType ? !isRotated : isRotated) && key in d) { currScale = scale.zoom || scale.x; - end = currScale($$.axis.isTimeSeries() ? parseDate.call($$, d[key]) : d[key]); } - return end < start ? 0 : end - start; - }, + if (currScale) { + let offset = 0; + pos = d[key]; - regionWidth(d: RegionsType): number { - return this.getRegionSize("width", d); - }, + if ($$.axis.isTimeSeries(d.axis)) { + pos = parseDate.call($$, pos); + } else if (/(x|width)/.test(type) && $$.axis.isCategorized() && isNaN(pos)) { + pos = config.axis_x_categories.indexOf(pos); + offset = $$.axis.x.tickOffset() * (key === "start" ? -1 : 1); + } - regionHeight(d: RegionsType): number { - return this.getRegionSize("height", d); + pos = currScale(pos) + offset; + } + + return isAxisType ? pos : pos < start ? 0 : pos - start; }, - isRegionOnX(d: RegionsType): boolean { + isRegionOnX(d: RegionOptions): boolean { return !d.axis || d.axis === "x"; } }; diff --git a/src/config/Options/common/main.ts b/src/config/Options/common/main.ts index b08ea3cea..7b38a6196 100644 --- a/src/config/Options/common/main.ts +++ b/src/config/Options/common/main.ts @@ -2,7 +2,7 @@ * Copyright (c) 2017 ~ present NAVER Corp. * billboard.js project is licensed under the MIT license */ -import type {RegionsType} from "../../../../types/types"; +import type {RegionOptions} from "../../../../types/options"; /** * main config options @@ -399,17 +399,31 @@ export default { /** * Show rectangles inside the chart.

- * This option accepts array including object that has axis, start, end and class. - * The keys start, end and class are optional. - * axis must be x, y or y2. start and end should be the value where regions start and end. - * If not specified, the edge values will be used. - * If timeseries x axis, date string, Date object and unixtime integer can be used. - * If class is set, the region element will have it as class. + * - **NOTE:**
+ * - axis must be x, y or y2. start and end should be the value where regions start and end. + * - If not specified, the edge values will be used. + * - If timeseries x axis, date string, Date object and unixtime integer can be used. + * - If category x axis, category name can be used for start and end. + * - If class is set, the region element will have it as class. + * + * This option accept array of object with below values: + * - `axis {string}`: 'x', 'y', or 'y2' + * - `[start] {number|Date|string}`: Start position of the region. If not set, the start will be the edge of the chart. + * - `[end] {number|Date|string}`: End position of the region. If not set, the end will be the edge of the chart. + * - `[class] {string}`: Class value to apply to the region. + * - `[label] {object}` Lable text option. + * - `text {string}`: Text value. + * - `x {number}`: x Position. + * - `y {number}`: y Position. + * - `color {string}`: Color string. + * - `rotated (boolean)`: Whether rotate label or not. * @name regions * @memberof Options * @type {Array} * @default [] - * @see [Demo](https://naver.github.io/billboard.js/demo/#Region.RegionLabel) + * @see [Demo: Regions](https://naver.github.io/billboard.js/demo/#Region.Region) + * @see [Demo: Regions Timeseries](https://naver.github.io/billboard.js/demo/#Region.RegionWithTimeseries) + * @see [Demo: Regions Label](https://naver.github.io/billboard.js/demo/#Region.RegionLabel) * @example * regions: [ * { @@ -427,5 +441,5 @@ export default { * } * ] */ - regions: [] + regions: [] }; diff --git a/test/internals/regions-spec.ts b/test/internals/regions-spec.ts index b28b4b3ac..51a27180a 100644 --- a/test/internals/regions-spec.ts +++ b/test/internals/regions-spec.ts @@ -305,4 +305,113 @@ describe("REGIONS", function() { expect(hasOverflow).to.be.false; }); }); + + describe("category type", () => { + beforeAll(() => { + args = { + data: { + columns: [ + ["data1", 30, 200, 100, 400, 150, 250], + ["data2", 100, 150, 130, 200, 220, 190] + ], + axes: { + data2: "y2" + }, + type: "line", + colors: { + data1: "#ff0000" + } + }, + axis: { + x: { + type: "category", + categories: [ + "cat1", + "cat2", + "cat3", + "cat4", + "cat5", + "cat6" + ] + }, + y2: { + show: true + } + }, + regions: [ + { + axis: "x", + start: "cat1", + end: "cat2", + class: "regions_class1", + label: { + text: "Regions 1", + color: "red" + } + }, + { + axis: "x", + start: "cat4", + end: "cat4", + class: "regions_class4", + label: { + text: "Regions 4", + color: "blue" + } + } + ] + }; + }); + + it("should render regions correctly", () => { + const {region: {list}} = chart.internal.$el; + const {x} = chart.internal.scale; + const expected = [ + {start: -0.5, end: 1.5}, + {start: 2.5, end: 3.5} + ]; + + list.select("rect").each(function(d, i) { + const {start, end} = expected[i]; + const xPos = +this.getAttribute("x"); + const width = +this.getAttribute("width"); + + expect(x(start)).to.be.equal(xPos); + expect(x(end)).to.be.equal(xPos + width); + }); + }); + + it("set options: regions", () => { + args.regions = [ + { + axis: "x", + start: 1, + end: "cat3" + }, + { + axis: "x", + start: "cat5", + end: 4.5 + } + ]; + }); + + it("should render regions correctly", () => { + const {region: {list}} = chart.internal.$el; + const {x} = chart.internal.scale; + const expected = [ + {start: 1, end: 2.5}, + {start: 3.5, end: 4.5} + ]; + + list.select("rect").each(function(d, i) { + const {start, end} = expected[i]; + const xPos = +this.getAttribute("x"); + const width = +this.getAttribute("width"); + + expect(x(start)).to.be.closeTo(xPos, 0.1); + expect(x(end)).to.be.closeTo(xPos + width, 0.1); + }); + }); + }); }); diff --git a/types/types.d.ts b/types/types.d.ts index 191cc37e0..802d9e6a7 100644 --- a/types/types.d.ts +++ b/types/types.d.ts @@ -66,17 +66,3 @@ export interface DataRegionsType { } }; } - -export interface RegionsType { - axis?: "x" | "y" | "y2"; - start?: number; - end?: number; - class?: string; - label?: { - text: string; - x?: number; - y?: number; - color?: string; - rotated?: boolean; - }; -}