forked from MapComplete/MapComplete
		
	
		
			
				
	
	
		
			154 lines
		
	
	
	
		
			5.6 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			154 lines
		
	
	
	
		
			5.6 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]
 | 
						|
            )
 | 
						|
        )
 | 
						|
    }
 | 
						|
}
 |