Skip to content

Commit

Permalink
feat(options): Intent to ship data.groupsZeroAs
Browse files Browse the repository at this point in the history
Implement the way to specify how zero value is treated on groups

Fix #2813
  • Loading branch information
netil authored Aug 11, 2022
1 parent e7bdbbc commit 3de8e7a
Show file tree
Hide file tree
Showing 7 changed files with 174 additions and 9 deletions.
74 changes: 74 additions & 0 deletions demo/demo.js
Original file line number Diff line number Diff line change
Expand Up @@ -2583,6 +2583,80 @@ var demos = {
}
}
],
Groups: [
{
options: {
title: {
text: "Groups zero treated as 'positive' value"
},
data: {
columns: [
["series1", -11, -11, -11, -11],
["series2", 4, 4, 4, 4],
["series3", 0, 1, 0, -1]
],
type: "area",
order: null,
groups: [
[
"series1",
"series2",
"series3",
]
],
groupsZeroAs: "positive"
}
}
},
{
options: {
title: {
text: "Groups zero treated as 'negative' value"
},
data: {
columns: [
["series1", -11, -11, -11, -11],
["series2", 4, 4, 4, 4],
["series3", 0, 1, 0, -1]
],
type: "area",
order: null,
groups: [
[
"series1",
"series2",
"series3",
]
],
groupsZeroAs: "negative"
}
}
},
{
options: {
title: {
text: "Groups zero treated as absolute 'zero' value"
},
data: {
columns: [
["series1", -11, -11, -11, -11],
["series2", 4, 4, 4, 4],
["series3", 0, 1, 0, -1]
],
type: "area",
order: null,
groups: [
[
"series1",
"series2",
"series3",
]
],
groupsZeroAs: "zero"
}
}
}
],
OnMinMaxCallback: {
options: {
data: {
Expand Down
2 changes: 1 addition & 1 deletion src/Chart/api/group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export default {
* ["data1", "data2"]
* ]);
*/
groups(groups: string[][]): string[][] {
groups<T = string[][]>(groups: T): T {
const $$ = this.internal;
const {config} = $$;

Expand Down
20 changes: 14 additions & 6 deletions src/ChartInternal/shape/shape.ts
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,7 @@ export default {
getShapeOffset(typeFilter, indices, isSub?: boolean): Function {
const $$ = this;
const {shapeOffsetTargets, indexMapByTargetId} = $$.getShapeOffsetData(typeFilter);
const groupsZeroAs = $$.config.data_groupsZeroAs;

return (d, idx) => {
const {id, value, x} = d;
Expand All @@ -316,27 +317,34 @@ export default {
return scale(value[0]);
}

const y0 = scale($$.getShapeYMin(id));

const dataXAsNumber = Number(x);
const y0 = scale(groupsZeroAs === "zero" ? 0 : $$.getShapeYMin(id));
let offset = y0;

shapeOffsetTargets
.filter(t => t.id !== id)
.filter(t => t.id !== id && ind[t.id] === ind[id])
.forEach(t => {
const {id: tid, rowValueMapByXValue, rowValues, values: tvalues} = t;

// for same stacked group (ind[tid] === ind[id])
if (ind[tid] === ind[id] && indexMapByTargetId[tid] < indexMapByTargetId[id]) {
if (indexMapByTargetId[tid] < indexMapByTargetId[id]) {
const rValue = tvalues[dataXAsNumber];
let row = rowValues[idx];

// check if the x values line up
if (!row || Number(row.x) !== dataXAsNumber) {
row = rowValueMapByXValue[dataXAsNumber];
}

if (row?.value * value >= 0 && isNumber(tvalues[dataXAsNumber])) {
offset += scale(tvalues[dataXAsNumber]) - y0;
if (row?.value * value >= 0 && isNumber(rValue)) {
const addOffset = value === 0 ? (
(groupsZeroAs === "positive" && rValue > 0) ||
(groupsZeroAs === "negative" && rValue < 0)
) : true;

if (addOffset) {
offset += scale(rValue) - y0;
}
}
}
});
Expand Down
18 changes: 18 additions & 0 deletions src/config/Options/data/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,24 @@ export default {
*/
data_groups: <string[][]> [],

/**
* Set how zero value will be treated on groups.<br>
* Possible values:
* - `zero`: 0 will be positioned at absolute axis zero point.
* - `positive`: 0 will be positioned at the top of a stack.
* - `negative`: 0 will be positioned at the bottom of a stack.
* @name data․groupsZeroAs
* @memberof Options
* @type {string}
* @default "positive"
* @see [Demo](https://naver.github.io/billboard.js/demo/#Data.Groups)
* @example
* data: {
* groupsZeroAs: "zero" // "positive" or "negative"
* }
*/
data_groupsZeroAs: <"zero" | "positive" | "negative"> "positive",

/**
* Set color converter function.<br><br>
* This option should a function and the specified function receives color (e.g. '#ff0000') and d that has data parameters like id, value, index, etc. And it must return a string that represents color (e.g. '#00ff00').
Expand Down
2 changes: 1 addition & 1 deletion test/interactions/interaction-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ describe("INTERACTION", () => {
});

it("rect elements should be positioned without gaps", () => {
const rect = [];
const rect: number[] = [];

chart.$.main.selectAll(`.${$EVENT.eventRect}`).each(function(d, i) {
const x = +this.getAttribute("x");
Expand Down
58 changes: 57 additions & 1 deletion test/internals/data-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1136,7 +1136,7 @@ describe("DATA", () => {
}
}
};
})
});

it("should generate chart without error.", () => {
expect(
Expand All @@ -1155,4 +1155,60 @@ describe("DATA", () => {
});
});
});

describe("data.groups", () => {
before(() => {
args = {
data: {
columns: [
["data1", -11, -11, -11, -11],
["data2", 4, 4, 4, 4],
["data3", 0, 1, 0, -1]
],
type: "area",
order: null,
groups: [
[
"data1",
"data2",
"data3",
]
],
groupsZeroAs: "positive"
}
};
});

it("when data.groupsZeroAs='positivie'", () => {
const expectedY = [57, 36, 57, 390];

chart.$.circles.filter(d => d.id === "data3").each(function(d, i) {
expect(+this.getAttribute("cy")).to.be.closeTo(expectedY[i], 1);
});
});

it("set options: data.groupsZeroAs='negative'", () => {
args.data.groupsZeroAs = "negative";
});

it("when data.groupsZeroAs='negative'", () => {
const expectedY = [369, 36, 369, 390];

chart.$.circles.filter(d => d.id === "data3").each(function(d, i) {
expect(+this.getAttribute("cy")).to.be.closeTo(expectedY[i], 1);
});
});

it("set options: data.groupsZeroAs='zero'", () => {
args.data.groupsZeroAs = "zero";
});

it("when data.groupsZeroAs='zero'", () => {
const expectedY = [140, 36, 140, 390];

chart.$.circles.filter(d => d.id === "data3").each(function(d, i) {
expect(+this.getAttribute("cy")).to.be.closeTo(expectedY[i], 1);
});
});
});
});
9 changes: 9 additions & 0 deletions types/options.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1564,6 +1564,15 @@ export interface Data {
*/
groups?: string[][];

/**
* Set how zero value will be treated on groups.<br>
* Possible values:
* - `zero`: 0 will be positioned at absolute axis zero point.
* - `positive`: 0 will be positioned at the top of a stack.
* - `negative`: 0 will be positioned at the bottom of a stack.
*/
groupsZeroAs?: "positive" | "negative" | "zero";

/**
* Set y axis the data related to. y and y2 can be used.
* - **NOTE:** If all data is related to one of the axes, the domain of axis without related data will be replaced by the domain from the axis with related data
Expand Down

0 comments on commit 3de8e7a

Please sign in to comment.