mapcomplete/UI/BigComponents/Histogram.ts

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

155 lines
5.6 KiB
TypeScript
Raw Normal View History

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