Skip to content

Commit

Permalink
Merge pull request #4174 from GordonSmith/STACKED_COLUMN_TOTAL
Browse files Browse the repository at this point in the history
feat: Add showDomainTotal to Column Chart
  • Loading branch information
GordonSmith authored Mar 8, 2024
2 parents b1cb722 + c7de54a commit f4bb950
Show file tree
Hide file tree
Showing 6 changed files with 181 additions and 33 deletions.
32 changes: 32 additions & 0 deletions demos/gallery/samples/chart/Column/StackedTotals.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Column } from "@hpcc-js/chart";

new Column()
.target("target")
.columns(["", "Failed", "TODO", "Passed"])
.data([
["Joe Cocker", 10, 10, 10],
["Stephen Tyler", 8, 10, 12],
["Einar Solberg", 19, 10, 1]
])
.showValue(true)
.showValueAsPercent("domain")
.showValueAsPercentFormat(".2%")
.showDomainTotal(true)
.yAxisType("linear")
.xAxisType("ordinal")
.maxPointSize(26)
.xAxisOrdinalPaddingOuter(0.1)
.xAxisOrdinalPaddingInner(0.6)
.xAxisOverlapMode("stagger")
.valueCentered(true)
.valueAnchor("middle")
.xAxisSeriesPaddingInner(3)
.yAxisStacked(true)
.yAxisGuideLines(true)
.yAxisHidden(true)
.yAxisDomainPadding(0.1)
.xAxisHidden(false)
.xAxisFocus(false)
.xAxisGuideLines(false)
.render()
;
10 changes: 7 additions & 3 deletions packages/chart/.vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@
"version": "0.2.0",
"configurations": [
{
"name": "index.html",
"name": "Launch Edge",
"request": "launch",
"type": "pwa-msedge",
"type": "msedge",
"url": "file:///${workspaceRoot}/index.html",
"webRoot": "${workspaceFolder}"
"webRoot": "${workspaceFolder}",
"sourceMapPathOverrides": {
"webpack:///./*": "${workspaceRoot}/*",
"webpack:///*": "/*"
}
},
]
}
24 changes: 23 additions & 1 deletion packages/chart/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,20 @@
.placeholder {
position: absolute;
left: 8px;
top: 8px;
top: 28px;
right: 8px;
bottom: 8px;
}
</style>
</head>

<body onresize="doResize()">
<div>
Stacked:
<input type="checkbox" value="stacked" onclick="doStacked(this.checked)" />
Bar:
<input type="checkbox" value="bar" onclick="doBar(this.checked)" />
</div>
<div id="placeholder">
</div>
<script>
Expand All @@ -53,6 +59,22 @@
.lazyRender();
}
}

function doStacked(stacked) {
if (app) {
app
.yAxisStacked(stacked)
.lazyRender();
}
}

function doBar(bar) {
if (app) {
app
.orientation(!bar ? "horizontal" : "vertical")
.lazyRender();
}
}
</script>
</body>

Expand Down
100 changes: 76 additions & 24 deletions packages/chart/src/Column.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export class Column extends XYAxis {

protected _linearGap: number;
private textLocal = d3Local<Text>();
private stackedTextLocal = d3Local<Text>();
private isHorizontal: boolean;

constructor() {
Expand Down Expand Up @@ -111,34 +112,29 @@ export class Column extends XYAxis {
.paddingInner(this.xAxisSeriesPaddingInner())
.paddingOuter(0)
;
const rowData = this.adjustedData(host);
let domainSums = [];
const seriesSums = [];
const columnLength = this.columns().length;
if (this.showValue()) {
switch (this.showValueAsPercent()) {
case "series":
rowData.forEach((row) => {
row.filter((_, idx) => idx > 0 && idx < columnLength).forEach((col, idx) => {
if (seriesSums[idx + 1] === undefined) {
seriesSums[idx + 1] = 0;
}
seriesSums[idx + 1] += col;
});
const rowData = this.data();
if (this.showValue() && this.showValueAsPercent() === "series") {
rowData.forEach((row) => {
row.filter((_, idx) => idx > 0 && idx < columnLength).forEach((col, idx) => {
if (seriesSums[idx + 1] === undefined) {
seriesSums[idx + 1] = 0;
}
seriesSums[idx + 1] += col;
});
});
}

});
break;
case "domain":
domainSums = rowData.map(row => {
return row.filter((cell, idx) => idx > 0 && idx < columnLength).reduce((sum, cell) => {
return sum + cell;
}, 0);
});
break;
case null:
default:
}
if (this.showDomainTotal() || (this.showValue() && this.showValueAsPercent() === "domain")) {
domainSums = rowData.map(row => {
return row.filter((cell, idx) => idx > 0 && idx < columnLength).reduce((sum, cell) => {
return sum + cell;
}, 0);
});
}

const column = element.selectAll(".dataRow")
.data(this.adjustedData(host))
;
Expand Down Expand Up @@ -209,7 +205,7 @@ export class Column extends XYAxis {
valueText = d3Format(context.showValueAsPercentFormat())(valueText / seriesSum);
break;
case "domain":
const domainSum = typeof dm.sum !== "undefined" ? dm.sum : domainSums[d.idx];
const domainSum = typeof dm.sum !== "undefined" ? dm.sum : domainSums[d.idx - 1];
valueText = d3Format(context.showValueAsPercentFormat())(valueText / domainSum);
break;
case null:
Expand Down Expand Up @@ -460,6 +456,59 @@ export class Column extends XYAxis {
.style("opacity", 0)
.remove()
;

const value4pos = host.yAxisStacked() ? domainSums[dataRowIdx] : Math.max(...dataRow.filter((_, idx) => idx > 0 && idx < columnLength));
const stackedTotalText = element.selectAll(".stackedTotalText").data(context.showDomainTotal() ? [domainSums[dataRowIdx]] : []);
const stackedTotalTextEnter = stackedTotalText.enter().append("g")
.attr("class", "stackedTotalText")
.each(function (this: SVGElement, d) {
context.stackedTextLocal.set(this, new Text().target(this).colorStroke_default("transparent"));
});
stackedTotalTextEnter.merge(stackedTotalText as any)
.each(function (this: SVGElement, d: any) {
const pos = { x: 0, y: 0 };
const domainPos = host.dataPos(dataRow[0]);
const valuePos = host.valuePos(value4pos);

const valueFontFamily = context.valueFontFamily();
const valueFontSize = context.valueFontSize();
const textSize = context.textSize(d, valueFontFamily, valueFontSize);

const isPositive = parseFloat(d) >= 0;
let valueAnchor: "start" | "middle" | "end" = "middle";
if (isHorizontal) {
pos.x = domainPos;
if (isPositive) {
pos.y = valuePos - textSize.height / 2;
} else {
pos.y = valuePos + textSize.height / 2;
}
} else {
valueAnchor = "start";
pos.y = domainPos;
if (isPositive) {
pos.x = valuePos + textSize.width / 2;
} else {
pos.x = valuePos - textSize.width / 2;
}
}

context.stackedTextLocal.get(this)
.pos(pos)
.anchor(valueAnchor)
.fontFamily(valueFontFamily)
.fontSize(valueFontSize)
.text(d)
.render()
;

});
stackedTotalText.exit()
.each(function (this: SVGElement, d) {
context.textLocal.get(this).target(null);
})
.remove()
;
});
column.exit().transition().duration(duration)
.remove()
Expand Down Expand Up @@ -554,6 +603,8 @@ export interface Column {
showValueAsPercent(_: null | "series" | "domain"): this;
showValueAsPercentFormat(): string;
showValueAsPercentFormat(_: string): this;
showDomainTotal(): boolean;
showDomainTotal(_: boolean): this;
valueCentered(): boolean;
valueCentered(_: boolean): this;
valueAnchor(): "start" | "middle" | "end";
Expand Down Expand Up @@ -589,6 +640,7 @@ Column.prototype.publish("showInnerText", false, "boolean", "Show Label in colum
Column.prototype.publish("showValueFormat", ",", "string", "D3 Format for Value", null, { disable: (w: Column) => !w.showValue() || !!w.showValueAsPercent() });
Column.prototype.publish("showValueAsPercent", null, "set", "If showValue is true, optionally show value as a percentage by Series or Domain", [null, "series", "domain"], { disable: w => !w.showValue(), optional: true });
Column.prototype.publish("showValueAsPercentFormat", ".0%", "string", "D3 Format for %", null, { disable: (w: Column) => !w.showValue() || !w.showValueAsPercent() });
Column.prototype.publish("showDomainTotal", false, "boolean", "Show Total Value for Stacked Columns", null);
Column.prototype.publish("valueCentered", false, "boolean", "Show Value in center of column");
Column.prototype.publish("valueAnchor", "middle", "set", "text-anchor for shown value text", ["start", "middle", "end"]);
Column.prototype.publish("xAxisSeriesPaddingInner", 0, "number", "Determines the ratio of the range that is reserved for blank space between band (0->1)");
Expand Down
45 changes: 41 additions & 4 deletions packages/chart/src/test.ts

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion packages/loader/src/meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export const npmPackages = {
"preact": "preact/dist/preact.umd",
"react": "react/umd/react.production.min",
"React": "react/umd/react.production.min",
"react-data-grid": "react-data-grid/lib/bundle",
"react-dom": "react-dom/umd/react-dom.production.min",
"ReactDOM": "react-dom/umd/react-dom.production.min",
"@fluentui/react": "@fluentui/react/dist/fluentui-react.umd",
Expand All @@ -89,7 +90,7 @@ export const localPackages = {
// Keep in sync with util/src/index.ts
export const hpccShims = ["loader", "codemirror-shim", "ddl-shim", "deck-shim", "dgrid-shim", "leaflet-shim", "phosphor-shim", "preact-shim"];
export const packages = [
"comms", "util", "common", "layout", "phosphor", "api", "dgrid", "chart", "other", "form",
"comms", "util", "common", "layout", "phosphor", "api", "dgrid", "dgrid2", "chart", "other", "form",
"tree", "graph", "map", "map-deck", "observable-md",
"react", "composite", "marshaller", "html", "timeline", "codemirror", "eclwatch"
];
Expand Down

0 comments on commit f4bb950

Please sign in to comment.