137 lines
No EOL
4.7 KiB
TypeScript
137 lines
No EOL
4.7 KiB
TypeScript
import {VariableUiElement} from "../Base/VariableUIElement";
|
|
import {Store, UIEventSource} from "../../Logic/UIEventSource";
|
|
import Table from "../Base/Table";
|
|
import Combine from "../Base/Combine";
|
|
import {FixedUiElement} from "../Base/FixedUiElement";
|
|
import {Utils} from "../../Utils";
|
|
import BaseUIElement from "../BaseUIElement";
|
|
import Svg from "../../Svg";
|
|
|
|
export default class Histogram<T> extends VariableUiElement {
|
|
|
|
private static defaultPalette = [
|
|
"#ff5858",
|
|
"#ffad48",
|
|
"#ffff59",
|
|
"#56bd56",
|
|
"#63a9ff",
|
|
"#9d62d9",
|
|
"#fa61fa"
|
|
]
|
|
|
|
constructor(values: Store<string[]>,
|
|
title: string | BaseUIElement,
|
|
countTitle: string | BaseUIElement,
|
|
options?: {
|
|
assignColor?: (t0: string) => string,
|
|
sortMode?: "name" | "name-rev" | "count" | "count-rev"
|
|
}
|
|
) {
|
|
const sortMode = new UIEventSource<"name" | "name-rev" | "count" | "count-rev">(options?.sortMode ?? "name")
|
|
const sortName = new VariableUiElement(sortMode.map(m => {
|
|
switch (m) {
|
|
case "name":
|
|
return Svg.up_svg()
|
|
case "name-rev":
|
|
return Svg.up_svg().SetStyle("transform: rotate(180deg)")
|
|
default:
|
|
return Svg.circle_svg()
|
|
}
|
|
}))
|
|
const titleHeader = new Combine([sortName.SetClass("w-4 mr-2"), title]).SetClass("flex")
|
|
.onClick(() => {
|
|
if (sortMode.data === "name") {
|
|
sortMode.setData("name-rev")
|
|
} else {
|
|
sortMode.setData("name")
|
|
}
|
|
})
|
|
|
|
const sortCount = new VariableUiElement(sortMode.map(m => {
|
|
switch (m) {
|
|
case "count":
|
|
return Svg.up_svg()
|
|
case "count-rev":
|
|
return Svg.up_svg().SetStyle("transform: rotate(180deg)")
|
|
default:
|
|
return Svg.circle_svg()
|
|
}
|
|
}))
|
|
|
|
|
|
const countHeader = new Combine([sortCount.SetClass("w-4 mr-2"), countTitle]).SetClass("flex").onClick(() => {
|
|
if (sortMode.data === "count-rev") {
|
|
sortMode.setData("count")
|
|
} else {
|
|
sortMode.setData("count-rev")
|
|
}
|
|
})
|
|
|
|
const header = [
|
|
titleHeader,
|
|
countHeader
|
|
]
|
|
|
|
super(values.map(values => {
|
|
|
|
if (values === undefined) {
|
|
return undefined;
|
|
}
|
|
|
|
values = Utils.NoNull(values)
|
|
|
|
const counts = new Map<string, number>()
|
|
for (const value of values) {
|
|
const c = counts.get(value) ?? 0;
|
|
counts.set(value, c + 1);
|
|
}
|
|
|
|
const keys = Array.from(counts.keys());
|
|
|
|
switch (sortMode.data) {
|
|
case "name":
|
|
keys.sort()
|
|
break;
|
|
case "name-rev":
|
|
keys.sort().reverse(/*Copy of array, inplace reverse if fine*/)
|
|
break;
|
|
case "count":
|
|
keys.sort((k0, k1) => counts.get(k0) - counts.get(k1))
|
|
break;
|
|
case "count-rev":
|
|
keys.sort((k0, k1) => counts.get(k1) - counts.get(k0))
|
|
break;
|
|
}
|
|
|
|
const max = Math.max(...Array.from(counts.values()))
|
|
|
|
const fallbackColor = (keyValue: string) => {
|
|
const index = keys.indexOf(keyValue)
|
|
return Histogram.defaultPalette[index % Histogram.defaultPalette.length]
|
|
};
|
|
let actualAssignColor = undefined;
|
|
if (options?.assignColor === undefined) {
|
|
actualAssignColor = fallbackColor;
|
|
} else {
|
|
actualAssignColor = (keyValue: string) => {
|
|
return options.assignColor(keyValue) ?? fallbackColor(keyValue)
|
|
}
|
|
}
|
|
|
|
return new Table(
|
|
header,
|
|
keys.map(key => [
|
|
key,
|
|
new Combine([
|
|
new Combine([new FixedUiElement("" + counts.get(key)).SetClass("font-bold rounded-full block")])
|
|
.SetClass("flex justify-center rounded border border-black")
|
|
.SetStyle(`background: ${actualAssignColor(key)}; width: ${100 * counts.get(key) / max}%`)
|
|
]).SetClass("block w-full")
|
|
|
|
]),{
|
|
contentStyle:keys.map(_ => ["width: 20%"])
|
|
}
|
|
).SetClass("w-full zebra-table");
|
|
}, [sortMode]));
|
|
}
|
|
} |