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

fix: Tidy up StatChart to be more consistent #4215

Merged
merged 1 commit into from
Jul 24, 2024
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
6 changes: 6 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@
"url": "http://localhost:3000/Visualization/",
"webRoot": "${workspaceRoot}"
},
{
"name": "Patch Docs",
"type": "node",
"request": "launch",
"program": "${workspaceRoot}/utils/patch-docs.mjs"
},
{
"name": "index.html (vite)",
"type": "msedge",
Expand Down
2 changes: 1 addition & 1 deletion demos/gallery/.vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
"name": "playground",
"type": "msedge",
"request": "launch",
"url": "file:///${workspaceRoot}/playground.html?./samples/chart/Heat/Week.js",
"url": "file:///${workspaceRoot}/playground.html?./samples/chart/Heat/Week (StdDev).js",
"runtimeArgs": [
"--allow-file-access-from-files",
"--disable-web-security"
Expand Down
43 changes: 42 additions & 1 deletion packages/chart/src/Heat.md
Original file line number Diff line number Diff line change
@@ -1 +1,42 @@
# Heat
# Heat

<!--meta

-->

The Heat Chart is a heatmap chart that displays data in a grid of coloured points. The color of each points is blurred and emphasised based on its neighbours. The chart is useful for visualizing data in a matrix format.

<ClientOnly>
<hpcc-vitepress style="width:100%;height:600px">
<div id="placeholder" style="height:400px">
</div>
<script type="module">
import { Heat } from "@hpcc-js/chart";

new Heat()
.target("placeholder")
.columns(["Day", "Hour", "weight"])
.orientation("vertical")
// .paletteID("RdYlBu")
// .reversePalette(true)
.xAxisType("ordinal")
.yAxisType("time")
.yAxisTypeTimePattern("%H")
.yAxisTickFormat("%H %p")
.yAxisTitle("Hour")
.radius(45)
.blur(25)
.minOpacity(.05)
.data([["Sat", 0, 0.18998719125426], ["Sat", 1, 0.21651075448881343], ["Sat", 2, 0.29360917730014136], ["Sat", 3, 0.1651116355055364], ["Sat", 4, 0.11885237728786953], ["Sat", 5, 0.09315256213264339], ["Sat", 6, 0.19081093933358728], ["Sat", 7, 0.26505462252549605], ["Sat", 8, 0.31304625721290896], ["Sat", 9, 0.18326323889972618], ["Sat", 10, 0.5427922043058862], ["Sat", 11, 0.33890867440986455], ["Sat", 12, 0.22287115322174472], ["Sat", 13, 0.2587162108611005], ["Sat", 14, 0.28441551468915144], ["Sat", 15, 0.19982154681583783], ["Sat", 16, 0.2930569439508717], ["Sat", 17, 0.47723750380299584], ["Sat", 18, 0.22207706211858186], ["Sat", 19, 0.33558402511639085], ["Sat", 20, 0.3690611266071652], ["Sat", 21, 0.5573599155287506], ["Sat", 22, 0.6831520252391093], ["Sat", 23, 0.5778902129422021], ["Fri", 0, 0.22511997013849297], ["Fri", 1, 0.12872968433216836], ["Fri", 2, 0.14983215685472429], ["Fri", 3, 0.2927501476457219], ["Fri", 4, 0.10428671137370601], ["Fri", 5, 0.15568583035698308], ["Fri", 6, 0.24599132277783603], ["Fri", 7, 0.2731898378837191], ["Fri", 8, 0.44567225462048016], ["Fri", 9, 0.20923559143936044], ["Fri", 10, 0.37126545805966676], ["Fri", 11, 0.25775900638903304], ["Fri", 12, 0.3814741051135274], ["Fri", 13, 0.34006938709768136], ["Fri", 14, 0.27593924410503684], ["Fri", 15, 0.4419477474759613], ["Fri", 16, 0.38965124930012096], ["Fri", 17, 0.3172503892478122], ["Fri", 18, 0.46400589048905894], ["Fri", 19, 0.2558543126612278], ["Fri", 20, 0.3590657029853837], ["Fri", 21, 0.15632447799886998], ["Fri", 22, 0.26420326277870526], ["Fri", 23, 0.297398622995917], ["Thur", 0, 0.18232751016901919], ["Thur", 1, 0.10522908735769126], ["Thur", 2, 0.2871142995201194], ["Thur", 3, 0.13264031456847822], ["Thur", 4, 0.18082727623683653], ["Thur", 5, 0.09944495435126643], ["Thur", 6, 0.11995531000488316], ["Thur", 7, 0.32298185555518627], ["Thur", 8, 0.2721533776994879], ["Thur", 9, 0.27001193948954205], ["Thur", 10, 0.4749544279655059], ["Thur", 11, 0.2692331882016368], ["Thur", 12, 0.4508438176709558], ["Thur", 13, 0.4492663733353105], ["Thur", 14, 0.3835056079807946], ["Thur", 15, 0.3464395010469424], ["Thur", 16, 0.34637609647721146], ["Thur", 17, 0.28212732557990894], ["Thur", 18, 0.19046579349029374], ["Thur", 19, 0.3223897386862471], ["Thur", 20, 0.2504833320124048], ["Thur", 21, 0.28639128289431637], ["Thur", 22, 0.4919882701545998], ["Thur", 23, 0.11120547938200998], ["Wed", 0, 0.26539414376986187], ["Wed", 1, 0.1915083896006279], ["Wed", 2, 0.1144094554621247], ["Wed", 3, 0.1915083896006279], ["Wed", 4, 0.1915083896006279], ["Wed", 5, 0.06301033647884767], ["Wed", 6, 0.18917162440973667], ["Wed", 7, 0.2034494131242346], ["Wed", 8, 0.355351933711545], ["Wed", 9, 0.3451218109163239], ["Wed", 10, 0.3226346644031917], ["Wed", 11, 0.20270338677554525], ["Wed", 12, 0.12560445263704206], ["Wed", 13, 0.2519610675488765], ["Wed", 14, 0.30086133062670817], ["Wed", 15, 0.24946221164343108], ["Wed", 16, 0.31534569552000696], ["Wed", 17, 0.4939159736052912], ["Wed", 18, 0.31544642697353115], ["Wed", 19, 0.24156016147712192], ["Wed", 20, 0.27236557847721654], ["Wed", 21, 0.3740930973387977], ["Wed", 22, 0.1085307269282787], ["Wed", 23, 0.13101787344141086], ["Tue", 0, 0.38368457249213195], ["Tue", 1, 0.11328658098527632], ["Tue", 2, 0.14541071077033996], ["Tue", 3, 0.06827291576651778], ["Tue", 4, 0.17107115373307188], ["Tue", 5, 0.11538864700272793], ["Tue", 6, 0.11217648968780919], ["Tue", 7, 0.13902321169712045], ["Tue", 8, 0.3094408893002232], ["Tue", 9, 0.40688502041473745], ["Tue", 10, 0.36313484464602097], ["Tue", 11, 0.4601351437724185], ["Tue", 12, 0.4095924978076847], ["Tue", 13, 0.22557709663316622], ["Tue", 14, 0.3283753345997203], ["Tue", 15, 0.2706332020074705], ["Tue", 16, 0.24600410595721722], ["Tue", 17, 0.5543993311840548], ["Tue", 18, 0.2738964920399142], ["Tue", 19, 0.2781803911141563], ["Tue", 20, 0.17569661936038083], ["Tue", 21, 0.48912841726342815], ["Tue", 22, 0.38632966796969875], ["Tue", 23, 0.18644420525695468], ["Mon", 0, 0.6359227793699938], ["Mon", 1, 0.5221023728137567], ["Mon", 2, 0.37279943958541595], ["Mon", 3, 0.34710013575736504], ["Mon", 4, 0.28285085353288725], ["Mon", 5, 0.25751868261666566], ["Mon", 6, 0.28428921687686476], ["Mon", 7, 0.17089474585761072], ["Mon", 8, 0.36545166807707746], ["Mon", 9, 0.21043056304791902], ["Mon", 10, 0.3696194958825379], ["Mon", 11, 0.3105586505053191], ["Mon", 12, 0.4402250862225449], ["Mon", 13, 0.2582289160630875], ["Mon", 14, 0.2996172716093256], ["Mon", 15, 0.32338989464103557], ["Mon", 16, 0.3249376820005164], ["Mon", 17, 0.22535620329345835], ["Mon", 18, 0.2782090254359703], ["Mon", 19, 0.14818108140584293], ["Mon", 20, 0.32807825351090025], ["Mon", 21, 0.5908569587793598], ["Mon", 22, 0.5080470114204925], ["Mon", 23, 0.2530276960364474], ["Sun", 0, 0.6469792068804184], ["Sun", 1, 0.4528553787783882], ["Sun", 2, 0.540662526620971], ["Sun", 3, 0.48754995027343223], ["Sun", 4, 0.2125643313502361], ["Sun", 5, 0.1999907961108455], ["Sun", 6, 0.19768419922329403], ["Sun", 7, 0.1666001089126883], ["Sun", 8, 0.39061458969829144], ["Sun", 9, 0.3091105719450119], ["Sun", 10, 0.3605096909282889], ["Sun", 11, 0.23504738724596624], ["Sun", 12, 0.2493108587995572], ["Sun", 13, 0.27608139305975626], ["Sun", 14, 0.301086314583818], ["Sun", 15, 0.4038845525503721], ["Sun", 16, 0.46976394580954595], ["Sun", 17, 0.5597125318620746], ["Sun", 18, 0.3975047233847814], ["Sun", 19, 0.19067492630497088], ["Sun", 20, 0.348541567064394], ["Sun", 21, 0.7699334507681412], ["Sun", 22, 0.606812411955852], ["Sun", 23, 1]])
.render()
;
</script>
</hpcc-vitepress>
</ClientOnly>

---

## Published Properties
```@hpcc-js/chart:Heat

```
5 changes: 5 additions & 0 deletions packages/chart/src/Heat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,11 @@ simpleheat.prototype = {
// draw a grayscale heatmap by putting a blurred ellipse at each data point
for (let i = 0, len = this._data.length, p; i < len; i++) {
p = this._data[i];
if (p[2] < 0) {
p[2] = 0;
} else if (p[2] > this._max) {
p[2] = this._max;
}
ctx.globalAlpha = Math.max(p[2] / this._max, minOpacity === undefined ? 0.05 : minOpacity);
ctx.drawImage(this._ellipse, p[0] - this._r, p[1] - this._r);
}
Expand Down
115 changes: 62 additions & 53 deletions packages/chart/src/StatChart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ palette("MinMax");
palette("25%");
palette("50%");

type Mode = "min_max" | "25_75" | "normal";
type View = "min_max" | "25_75" | "normal";
type Tick = { label: string, value: number };
type Ticks = Tick[];
type AxisTick = { label: string, value: string };
Expand All @@ -24,15 +24,16 @@ function myFormatter(format: string): (num: number) => string {
};
}

export type StatChartView = "min_max" | "25_75" | "normal";
export type Quartiles = [number, number, number, number, number];
export type Data = [[number, number, number, number, number, number, number]];

export class StatChart extends HTMLWidget {

private _mean: number;
private _standardDeviation: number;
private _quartiles: number[];
private _selectMode: any;
private _tickFormatter: (_: number) => string;
protected _selectElement: any;
protected _tickFormatter: (_: number) => string;

private _bellCurve: Scatter = new Scatter()
protected _bellCurve: Scatter = new Scatter()
.columns(["", "Std. Dev."])
.paletteID("Quartile")
.interpolate_default("basis")
Expand All @@ -46,7 +47,7 @@ export class StatChart extends HTMLWidget {
.yAxisGuideLines(false) as Scatter
;

private _candle = new QuartileCandlestick()
protected _candle = new QuartileCandlestick()
.columns(["Min", "25%", "50%", "75%", "Max"])
.edgePadding(0)
.roundedCorners(1)
Expand All @@ -59,23 +60,30 @@ export class StatChart extends HTMLWidget {
.innerRectColor(rainbow(10, 0, 100))
;

private stdDev(degrees: number): number {
constructor() {
super();
this
.columns(["Min", "25%", "50%", "75%", "Max", "Mean", "Std. Dev."])
;
}

protected stdDev(degrees: number): number {
return this.mean() + degrees * this.standardDeviation();
}

private formatStdDev(degrees: number): string {
protected formatStdDev(degrees: number): string {
return this._tickFormatter(this.stdDev(degrees));
}

private quartile(q: 0 | 1 | 2 | 3 | 4): number {
return this.data()[0][q];
protected quartile(q: 0 | 1 | 2 | 3 | 4): number {
return this.quartiles()[q];
}

private formatQ(q: 0 | 1 | 2 | 3 | 4): string {
protected formatQ(q: 0 | 1 | 2 | 3 | 4): string {
return this._tickFormatter(this.quartile(q));
}

private domain(mode: Mode): [number, number] {
protected domain(mode: View): [number, number] {
switch (mode) {
case "25_75":
return [this.quartile(1), this.quartile(3)];
Expand All @@ -87,36 +95,22 @@ export class StatChart extends HTMLWidget {
}
}

mean(): number;
mean(_: number): this;
mean(_?: number): this | number {
if (!arguments.length) return this._mean;
this._mean = _;
if (this.data()[0]) {
this.data()[0][5] = _;
}
return this;
protected min(): number {
return this.quartile(0);
}

standardDeviation(): number;
standardDeviation(_: number): this;
standardDeviation(_?: number): this | number {
if (!arguments.length) return this._standardDeviation;
this._standardDeviation = _;
if (this.data()[0]) {
this.data()[0][6] = _;
}
return this;
protected max(): number {
return this.quartile(4);
}

quartiles(): number[];
quartiles(_: number[]): this;
quartiles(_?: number[]): this | number[] {
if (!arguments.length) return this._quartiles;
this._quartiles = _;
if (this.data()[0]) {
this.data()[0] = _.concat(this.data()[0].slice(-2));
}
data(): Data;
data(_: Data): this;
data(_?: Data): Data | this {
if (!arguments.length) return [[...this.quartiles(), this.mean(), this.standardDeviation()]];
const row = _[0];
this.quartiles([row[0], row[1], row[2], row[3], row[4]]);
this.mean(row[5]);
this.standardDeviation(row[6]);
return this;
}

Expand All @@ -127,20 +121,21 @@ export class StatChart extends HTMLWidget {

this._candle.target(element.append("div").node());

this._selectMode = element.append("div")
this._selectElement = element.append("div")
.style("position", "absolute")
.style("top", "0px")
.style("right", "0px").append("select")
.on("change", () => {
this.render();
this.view(this._selectElement.node().value);
this.lazyRender();
})
;
this._selectMode.append("option").attr("value", "min_max").text("Min / Max");
this._selectMode.append("option").attr("value", "25_75").text("25% / 75%");
this._selectMode.append("option").attr("value", "normal").text("Normal");
this._selectElement.append("option").attr("value", "min_max").text("Min / Max");
this._selectElement.append("option").attr("value", "25_75").text("25% / 75%");
this._selectElement.append("option").attr("value", "normal").text("Normal");
}

private bellTicks(mode: Mode): AxisTicks {
protected bellTicks(mode: View): AxisTicks {
let ticks: Ticks;
switch (mode) {
case "25_75":
Expand Down Expand Up @@ -174,15 +169,15 @@ export class StatChart extends HTMLWidget {
];
}

const [domainLow, domainHigh] = this.domain(this._selectMode.node().value);
const [domainLow, domainHigh] = this.domain(this._selectElement.node().value);
return ticks
.filter(sd => sd.value >= domainLow && sd.value <= domainHigh)
.map(sd => ({ label: sd.label, value: sd.value.toString() }))
;
}

updateScatter() {
const mode = this._selectMode.node().value;
const mode = this._selectElement.node().value;
const [domainLow, domainHigh] = this.domain(mode);
const padding = (domainHigh - domainLow) * (this.domainPadding() / 100);

Expand Down Expand Up @@ -222,23 +217,37 @@ export class StatChart extends HTMLWidget {
update(domNode, element) {
super.update(domNode, element);
this._tickFormatter = myFormatter(this.tickFormat());
if (this.data()[0] && this.data()[0].length === 7) {
this.quartiles(this.data()[0].slice(0, 5));
this.mean(this.data()[0][5]);
this.standardDeviation(this.data()[0][6]);
}
this._selectElement.node().value = this.view();
this.updateScatter();
this.updateCandle();
}
}
StatChart.prototype._class += " chart_Stat";

export interface StatChart {
view(): StatChartView;
view(_: StatChartView): this;

tickFormat(): string;
tickFormat(_: string): this;
candleHeight(): number;
candleHeight(_: number): this;
domainPadding(): number;
domainPadding(_: number): this;

mean(): number;
mean(_: number): this;
standardDeviation(): number;
standardDeviation(_: number): this;
quartiles(): Quartiles;
quartiles(_: Quartiles): this;
}
StatChart.prototype.publish("view", "min_max", "set", "View", ["min_max", "25_75", "normal"]);

StatChart.prototype.publish("tickFormat", ".2e", "string", "X-Axis Tick Format");
StatChart.prototype.publish("candleHeight", 20, "number", "Height of candle widget (pixels)");
StatChart.prototype.publish("domainPadding", 10, "number", "Domain value padding");

StatChart.prototype.publish("mean", .5, "number", "Mean");
StatChart.prototype.publish("standardDeviation", .125, "number", "Standard Deviation (σ)");
StatChart.prototype.publish("quartiles", [0, .25, .5, .75, 1], "object", "Quartiles (Min, 25%, 50%, 75%, Max)");
3 changes: 2 additions & 1 deletion packages/chart/src/__tests__/heat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ export class Test extends Heat {

// Calculate the standard deviation of the weight column ---
const extractWeight = df.map(row => row[2]);
const distribution = df.scalar(df.distribution())(extractWeight(data));
export const distribution = df.scalar(df.distribution())(extractWeight(data));
export const quartiles = df.scalar(df.quartile())(extractWeight(data));
jeclrsg marked this conversation as resolved.
Show resolved Hide resolved

// Convert the weight column to standard deviations ---
const toStdDevs = df.map(r => {
Expand Down
5 changes: 3 additions & 2 deletions packages/chart/src/__tests__/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { StdDevTest as Test } from "./heat";
// export { Test3 as Test } from "./test3";
export { Test } from "./stat";
// export { Test3 as Test } from "./test3";
// export { StdDevTest as Test } from "./heat";
16 changes: 16 additions & 0 deletions packages/chart/src/__tests__/stat.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { StatChart } from "../StatChart";
import { distribution, quartiles } from "./heat";

export class Test extends StatChart {

constructor() {
super();
this
.view("normal")
.quartiles(quartiles)
.mean(distribution.mean)
.standardDeviation(distribution.standardDeviation)
.lazyRender()
;
}
}
Loading