Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(shape): Intent to ship stack.normalize #643

Merged
merged 1 commit into from
Nov 8, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions demo/demo.js
Original file line number Diff line number Diff line change
Expand Up @@ -1480,6 +1480,31 @@ var demos = {
},
description: "For selection, click data point or drag over data points"
},
DataStackNormalized: {
options: {
data: {
x: "x",
columns: [
["x", "Item1", "Item2", "Item3", "Item4", "Item5", "Item6", "Item7"],
["data1", 30, 280, 951, 400, 150, 546, 4528],
["data2", 130, 357, 751, 400, 150, 250, 3957],
["data3", 30, 280, 320, 218, 150, 150, 5000]
],
type: "bar",
groups: [
["data1", "data2", "data3"]
],
stack: {
normalize: true
}
},
axis: {
x: {
type: "category"
}
}
}
},
OnMinMaxCallback: {
options: {
data: {
Expand Down
71 changes: 71 additions & 0 deletions spec/internals/data-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1465,4 +1465,75 @@ describe("DATA", () => {
expect(path.split("L").length).to.be.equal(expected.L);
});
});

describe("data.stack", () => {
let chartHeight = 0;

before(() => {
args = {
data: {
columns: [
["data1", 230, 50, 300],
["data2", 198, 87, 580]
],
type: "bar",
groups: [
["data1", "data2"]
],
stack: {
normalize: true
}
}
};
});

it("check for the normalized y axis tick in percentage", () => {
const tick = chart.$.main.selectAll(`.${CLASS.axisY} .tick tspan`);

// check for the y axis to be in percentage
tick.each(function (v, i) {
expect(this.textContent).to.be.equal(`${i * 10}%`);
});
});

it("check for the normalized bar's height", () => {
chartHeight = +chart.$.main.selectAll(`.${CLASS.zoomRect}`).attr("height") - 1;
const bars = chart.$.bar.bars.nodes().concat();

bars.splice(0, 3).forEach((v, i) => {
expect(v.getBBox().height + bars[i].getBBox().height).to.be.equal(chartHeight);
});
});

it("set options data.type='area'", () => {
args.data.type = "area";
args.data.columns = [
["data1", 200, 387, 123],
["data2", 200, 387, 123]
];
});

it("check for the normalized area's height", () => {
let areaHeight = 0;

chart.$.main.selectAll(`.${CLASS.areas} path`).each(function() {
areaHeight += this.getBBox().height;
});

expect(areaHeight).to.be.equal(chartHeight);
});

it("check for the normalized default tooltip", () => {
let tooltipValue = 0;

// show tooltip
chart.tooltip.show({index:1});

chart.$.tooltip.selectAll(".value").each(function() {
tooltipValue += parseInt(this.textContent);
});

expect(tooltipValue).to.be.equal(100);
});
});
});
1 change: 1 addition & 0 deletions src/api/api.group.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ extend(Chart.prototype, {
* @instance
* @memberOf Chart
* @param {Array} groups This argument needs to be an Array that includes one or more Array that includes target ids to be grouped.
* @return {Array} Grouped data names array
* @example
* // data1 and data2 will be a new group.
* chart.groups([
Expand Down
4 changes: 3 additions & 1 deletion src/axis/Axis.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,9 @@ export default class Axis {
const axis = bbAxis(axisParams)
.scale(scale)
.orient(orient)
.tickFormat(tickFormat);
.tickFormat(
tickFormat || ($$.isStackNormalized() && (x => `${x}%`))
);

$$.isTimeSeriesY() ?
// https://github.com/d3/d3/blob/master/CHANGES.md#time-intervals-d3-time
Expand Down
40 changes: 39 additions & 1 deletion src/config/Options.js
Original file line number Diff line number Diff line change
Expand Up @@ -671,10 +671,48 @@ export default class Options {
* }
*/
data_hide: false,

/**
* Filter values to be shown
* The data value is the same as the returned by `.data()`.
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
* @name data․filter
* @memberOf Options
* @type {Function}
* @default undefined
* @example
* data: {
* // filter for id value
* filter: function(v) {
* // v: [{id: "data1", id_org: "data1", values: [
* // {x: 0, value: 130, id: "data2", index: 0}, ...]
* // }, ...]
* return v.id !== "data1";
* }
*/
data_filter: undefined,

/**
* Set data selection enabled.<br><br>
* Set the stacking to be normalized
* - **NOTE:**
* - For stacking, '[data.groups](#.data%25E2%2580%25A4groups)' option should be set
* - y Axis will be set in percentage value (0 ~ 100%)
* - Must have postive values
* @name data․stack․normalize
* @memberOf Options
* @type {Boolean}
* @default false
* @see {@link https://naver.github.io/billboard.js/demo/#Data.DataStackNormalized|Example}
* @example
* data: {
* stack: {
* normalize: true
* }
* }
*/
data_stack_normalize: false,
/**
* Set data selection enabled<br><br>
* If this option is set true, we can select the data points and get/set its state of selection by API (e.g. select, unselect, selected).
* @name data․selection․enabled
* @memberOf Options
Expand Down
78 changes: 78 additions & 0 deletions src/data/data.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ extend(ChartInternal.prototype, {
return !this.isX(key);
},

isStackNormalized() {
const config = this.config;

return config.data_stack_normalize && config.data_groups.length;
},

getXKey(id) {
const $$ = this;
const config = $$.config;
Expand Down Expand Up @@ -276,6 +282,33 @@ extend(ChartInternal.prototype, {
return minMaxData;
},

/**
* Get sum of data per index
* @private
* @return {Array}
*/
getTotalPerIndex() {
const $$ = this;
const cacheKey = "$totalPerIndex";
let sum = $$.getCache(cacheKey);

if ($$.isStackNormalized() && !sum) {
sum = [];

$$.data.targets.forEach(row => {
row.values.forEach((v, i) => {
if (!sum[i]) {
sum[i] = 0;
}

sum[i] += v.value;
});
});
}

return sum;
},

/**
* Get total data sum
* @private
Expand Down Expand Up @@ -714,5 +747,50 @@ extend(ChartInternal.prototype, {
}

return value[type];
},

/**
* Get ratio value
* @param {String} type Ratio for given type
* @param {Object} d Data value object
* @param {Boolean} asPercent Convert the return as percent or not
* @return {Number} Ratio value
* @private
*/
getRatio(type, d, asPercent) {
const $$ = this;
const config = $$.config;
let ratio = d && (d.ratio || d.value);

if (type === "arc") {
// if has padAngle set, calculate rate based on value
if ($$.pie.padAngle()()) {
let total = $$.getTotalDataSum();

if ($$.hiddenTargetIds.length) {
total -= d3Sum($$.api.data.values.call($$.api, $$.hiddenTargetIds));
}

ratio = d.value / total;

// otherwise, based on the rendered angle value
} else {
ratio = (d.endAngle - d.startAngle) / (
Math.PI * ($$.hasType("gauge") && !config.gauge_fullCircle ? 1 : 2)
);
}
} else if (type === "index" && !d.ratio) {
const totalPerIndex = this.getTotalPerIndex();

if (totalPerIndex && d.value) {
d.ratio = d.value / totalPerIndex[d.index];
}

ratio = d.ratio;
} else if (type === "radar") {
ratio = (parseFloat(Math.max(d.value, 0)) / $$.maxValue) * config.radar_size_ratio;
}

return asPercent ? ratio * 100 : ratio;
}
});
5 changes: 5 additions & 0 deletions src/internals/domain.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ extend(ChartInternal.prototype, {
getYDomain(targets, axisId, xDomain) {
const $$ = this;
const config = $$.config;

if ($$.isStackNormalized()) {
return [0, 100];
}

const targetsByAxisId = targets.filter(t => $$.axis.getId(t.id) === axisId);
const yTargets = xDomain ? $$.filterByXDomain(targetsByAxisId, xDomain) : targetsByAxisId;
const yMin = axisId === "y2" ? config.axis_y2_min : config.axis_y_min;
Expand Down
8 changes: 8 additions & 0 deletions src/internals/scale.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ extend(ChartInternal.prototype, {
);
},

/**
* Get y Axis scale function
* @param {Number} min
* @param {Number} max
* @param {Number} domain
* @return {Function} scale
* @private
*/
getY(min, max, domain) {
const scale = this.getScale(min, max, this.isTimeSeriesY());

Expand Down
3 changes: 1 addition & 2 deletions src/internals/tooltip.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,8 @@ extend(ChartInternal.prototype, {
const config = $$.config;
const titleFormat = config.tooltip_format_title || defaultTitleFormat;
const nameFormat = config.tooltip_format_name || (name => name);
const valueFormat = config.tooltip_format_value || defaultValueFormat;
const valueFormat = config.tooltip_format_value || ($$.isStackNormalized() ? ((v, ratio) => `${(ratio * 100).toFixed(2)}%`) : defaultValueFormat);
const order = config.tooltip_order;

const getRowValue = row => $$.getBaseValue(row);
const getBgColor = $$.levelColor ? row => $$.levelColor(row.value) : row => color(row.id);

Expand Down
32 changes: 2 additions & 30 deletions src/shape/arc.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
arc as d3Arc,
pie as d3Pie
} from "d3-shape";
import {sum as d3Sum} from "d3-array";
import {interpolate as d3Interpolate} from "d3-interpolate";
import ChartInternal from "../internals/ChartInternal";
import CLASS from "../config/classes";
Expand Down Expand Up @@ -175,38 +174,11 @@ extend(ChartInternal.prototype, {
return translate;
},

getArcRatio(d) {
const $$ = this;
const config = $$.config;
let val = null;

if (d) {
// if has padAngle set, calculate rate based on value
if ($$.pie.padAngle()()) {
let total = $$.getTotalDataSum();

if ($$.hiddenTargetIds.length) {
total -= d3Sum($$.api.data.values.call($$.api, $$.hiddenTargetIds));
}

val = d.value / total;

// otherwise, based on the rendered angle value
} else {
val = (d.endAngle - d.startAngle) / (
Math.PI * ($$.hasType("gauge") && !config.gauge_fullCircle ? 1 : 2)
);
}
}

return val;
},

convertToArcData(d) {
return this.addName({
id: d.data.id,
value: d.value,
ratio: this.getArcRatio(d),
ratio: this.getRatio("arc", d),
index: d.index,
});
},
Expand All @@ -221,7 +193,7 @@ extend(ChartInternal.prototype, {

const updated = $$.updateAngle(d);
const value = updated ? updated.value : null;
const ratio = $$.getArcRatio(updated);
const ratio = $$.getRatio("arc", updated);
const id = d.data.id;

if (!$$.hasType("gauge") && !$$.meetsArcLabelThreshold(ratio)) {
Expand Down
6 changes: 4 additions & 2 deletions src/shape/bar.js
Original file line number Diff line number Diff line change
Expand Up @@ -173,11 +173,13 @@ extend(ChartInternal.prototype, {
posY = y0;
}

posY -= (y0 - offset);

// 4 points that make a bar
return [
[posX, offset],
[posX, posY - (y0 - offset)],
[posX + barW, posY - (y0 - offset)],
[posX, posY],
[posX + barW, posY],
[posX + barW, offset]
];
};
Expand Down
Loading