forked from MapComplete/MapComplete
		
	Refactoring: Histogram now uses chartJS, simplify code
This commit is contained in:
		
							parent
							
								
									3918e5ec04
								
							
						
					
					
						commit
						6ae258c48d
					
				
					 9 changed files with 92 additions and 314 deletions
				
			
		| 
						 | 
					@ -119,7 +119,7 @@ export class WikimediaImageProvider extends ImageProvider {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    SourceIcon(): BaseUIElement {
 | 
					    SourceIcon(): BaseUIElement {
 | 
				
			||||||
        return new SvelteUIElement(Wikimedia_commons_white).SetStyle("width:2em;height: 2em")
 | 
					        return new SvelteUIElement(Wikimedia_commons_white)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public PrepUrl(value: NonNullable<string>): ProvidedImage
 | 
					    public PrepUrl(value: NonNullable<string>): ProvidedImage
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,8 +3,10 @@ import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig"
 | 
				
			||||||
import { TagUtils } from "../../Logic/Tags/TagUtils"
 | 
					import { TagUtils } from "../../Logic/Tags/TagUtils"
 | 
				
			||||||
import { OsmFeature } from "../../Models/OsmFeature"
 | 
					import { OsmFeature } from "../../Models/OsmFeature"
 | 
				
			||||||
import { Utils } from "../../Utils"
 | 
					import { Utils } from "../../Utils"
 | 
				
			||||||
 | 
					import { labels } from "wikibase-sdk/dist/src/helpers/simplify"
 | 
				
			||||||
 | 
					import { isInteger } from "tailwind-merge/dist/lib/validators"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class ChartJsColours {
 | 
					class ChartJsColours {
 | 
				
			||||||
    public static readonly unknownColor = "rgba(128, 128, 128, 0.2)"
 | 
					    public static readonly unknownColor = "rgba(128, 128, 128, 0.2)"
 | 
				
			||||||
    public static readonly unknownBorderColor = "rgba(128, 128, 128, 0.2)"
 | 
					    public static readonly unknownBorderColor = "rgba(128, 128, 128, 0.2)"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -205,7 +207,7 @@ export class ChartJsUtils {
 | 
				
			||||||
                hideUnknown?: boolean
 | 
					                hideUnknown?: boolean
 | 
				
			||||||
                hideNotApplicable?: boolean
 | 
					                hideNotApplicable?: boolean
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
    ) {
 | 
					    ): ChartConfiguration {
 | 
				
			||||||
            const { labels, data } = ChartJsUtils.extractDataAndLabels(tr, features, {
 | 
					            const { labels, data } = ChartJsUtils.extractDataAndLabels(tr, features, {
 | 
				
			||||||
                sort: true,
 | 
					                sort: true,
 | 
				
			||||||
                groupToOtherCutoff: options?.groupToOtherCutoff,
 | 
					                groupToOtherCutoff: options?.groupToOtherCutoff,
 | 
				
			||||||
| 
						 | 
					@ -336,7 +338,7 @@ export class ChartJsUtils {
 | 
				
			||||||
     * @returns undefined if not enough parameters
 | 
					     * @returns undefined if not enough parameters
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    static createConfigForTagRendering<T extends { properties: Record<string, string> }>(tagRendering: TagRenderingConfig, features: T[],
 | 
					    static createConfigForTagRendering<T extends { properties: Record<string, string> }>(tagRendering: TagRenderingConfig, features: T[],
 | 
				
			||||||
                                       options?: TagRenderingChartOptions){
 | 
					                                       options?: TagRenderingChartOptions): ChartConfiguration{
 | 
				
			||||||
        if (tagRendering.mappings?.length === 0 && tagRendering.freeform?.key === undefined) {
 | 
					        if (tagRendering.mappings?.length === 0 && tagRendering.freeform?.key === undefined) {
 | 
				
			||||||
            return undefined
 | 
					            return undefined
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
| 
						 | 
					@ -380,7 +382,7 @@ export class ChartJsUtils {
 | 
				
			||||||
            barchartMode = true
 | 
					            barchartMode = true
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const config = <ChartConfiguration>{
 | 
					        return <ChartConfiguration>{
 | 
				
			||||||
            type: options?.chartType ?? (barchartMode ? "bar" : "pie"),
 | 
					            type: options?.chartType ?? (barchartMode ? "bar" : "pie"),
 | 
				
			||||||
            data: {
 | 
					            data: {
 | 
				
			||||||
                labels,
 | 
					                labels,
 | 
				
			||||||
| 
						 | 
					@ -402,7 +404,49 @@ export class ChartJsUtils {
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return config
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    static createHistogramConfig(keys: string[], counts: Map<string, number>){
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const borderColor = [
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					        const backgroundColor = [
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        while (borderColor.length < keys.length) {
 | 
				
			||||||
 | 
					            borderColor.push(...ChartJsColours.borderColors)
 | 
				
			||||||
 | 
					            backgroundColor.push(...ChartJsColours.backgroundColors)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return  <ChartConfiguration>{
 | 
				
			||||||
 | 
					            type: "bar",
 | 
				
			||||||
 | 
					            data: {
 | 
				
			||||||
 | 
					                labels: keys,
 | 
				
			||||||
 | 
					                datasets: [
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        data: keys.map((k) => counts.get(k)),
 | 
				
			||||||
 | 
					                        backgroundColor,
 | 
				
			||||||
 | 
					                        borderColor,
 | 
				
			||||||
 | 
					                        borderWidth: 1,
 | 
				
			||||||
 | 
					                        label: undefined,
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            options: { scales: {
 | 
				
			||||||
 | 
					                    y: {
 | 
				
			||||||
 | 
					                        ticks: {
 | 
				
			||||||
 | 
					                            stepSize: 1,
 | 
				
			||||||
 | 
					                            callback: (value) =>Number(value).toFixed(0),
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                plugins: {
 | 
				
			||||||
 | 
					                    legend: {
 | 
				
			||||||
 | 
					                        display: false,
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,20 +10,6 @@ export class FixedUiElement extends BaseUIElement {
 | 
				
			||||||
        super()
 | 
					        super()
 | 
				
			||||||
        this.content = html ?? ""
 | 
					        this.content = html ?? ""
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
    AsMarkdown(): string {
 | 
					 | 
				
			||||||
        if (this.HasClass("code")) {
 | 
					 | 
				
			||||||
            if (this.content.indexOf("\n") > 0 || this.HasClass("block")) {
 | 
					 | 
				
			||||||
                return "\n```\n" + this.content + "\n```\n"
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            return "`" + this.content + "`"
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if (this.HasClass("font-bold")) {
 | 
					 | 
				
			||||||
            return "*" + this.content + "*"
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return this.content
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    protected InnerConstructElement(): HTMLElement {
 | 
					    protected InnerConstructElement(): HTMLElement {
 | 
				
			||||||
        const e = document.createElement("span")
 | 
					        const e = document.createElement("span")
 | 
				
			||||||
        e.innerHTML = Utils.purify(this.content)
 | 
					        e.innerHTML = Utils.purify(this.content)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,81 +0,0 @@
 | 
				
			||||||
import BaseUIElement from "../BaseUIElement"
 | 
					 | 
				
			||||||
import { Utils } from "../../Utils"
 | 
					 | 
				
			||||||
import Translations from "../i18n/Translations"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * @deprecated
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export default class Table extends BaseUIElement {
 | 
					 | 
				
			||||||
    private readonly _header: BaseUIElement[]
 | 
					 | 
				
			||||||
    private readonly _contents: BaseUIElement[][]
 | 
					 | 
				
			||||||
    private readonly _contentStyle: string[][]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    constructor(
 | 
					 | 
				
			||||||
        header: (BaseUIElement | string)[],
 | 
					 | 
				
			||||||
        contents: (BaseUIElement | string)[][],
 | 
					 | 
				
			||||||
        options?: {
 | 
					 | 
				
			||||||
            contentStyle?: string[][]
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    ) {
 | 
					 | 
				
			||||||
        super()
 | 
					 | 
				
			||||||
        this._contentStyle = options?.contentStyle ?? [["min-width: 9rem"]]
 | 
					 | 
				
			||||||
        this._header = header?.map(Translations.W)
 | 
					 | 
				
			||||||
        this._contents = contents.map((row) => row.map(Translations.W))
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    protected InnerConstructElement(): HTMLElement {
 | 
					 | 
				
			||||||
        const table = document.createElement("table")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const headerElems = Utils.NoNull(
 | 
					 | 
				
			||||||
            (this._header ?? []).map((elem) => elem.ConstructElement())
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        if (headerElems.length > 0) {
 | 
					 | 
				
			||||||
            const thead = document.createElement("thead")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            const tr = document.createElement("tr")
 | 
					 | 
				
			||||||
            headerElems.forEach((headerElem) => {
 | 
					 | 
				
			||||||
                const td = document.createElement("th")
 | 
					 | 
				
			||||||
                td.appendChild(headerElem)
 | 
					 | 
				
			||||||
                tr.appendChild(td)
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
            thead.appendChild(tr)
 | 
					 | 
				
			||||||
            table.appendChild(thead)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for (let i = 0; i < this._contents.length; i++) {
 | 
					 | 
				
			||||||
            const row = this._contents[i]
 | 
					 | 
				
			||||||
            const tr = document.createElement("tr")
 | 
					 | 
				
			||||||
            for (let j = 0; j < row.length; j++) {
 | 
					 | 
				
			||||||
                try {
 | 
					 | 
				
			||||||
                    const elem = row[j]
 | 
					 | 
				
			||||||
                    if (elem?.ConstructElement === undefined) {
 | 
					 | 
				
			||||||
                        continue
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    const htmlElem = elem?.ConstructElement()
 | 
					 | 
				
			||||||
                    if (htmlElem === undefined) {
 | 
					 | 
				
			||||||
                        continue
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    let style = undefined
 | 
					 | 
				
			||||||
                    if (
 | 
					 | 
				
			||||||
                        this._contentStyle !== undefined &&
 | 
					 | 
				
			||||||
                        this._contentStyle[i] !== undefined &&
 | 
					 | 
				
			||||||
                        this._contentStyle[j] !== undefined
 | 
					 | 
				
			||||||
                    ) {
 | 
					 | 
				
			||||||
                        style = this._contentStyle[i][j]
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    const td = document.createElement("td")
 | 
					 | 
				
			||||||
                    td.style.cssText = style
 | 
					 | 
				
			||||||
                    td.appendChild(htmlElem)
 | 
					 | 
				
			||||||
                    tr.appendChild(td)
 | 
					 | 
				
			||||||
                } catch (e) {
 | 
					 | 
				
			||||||
                    console.error("Could not render an element in a table due to", e, row[j])
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            table.appendChild(tr)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return table
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -13,16 +13,6 @@ export default abstract class BaseUIElement {
 | 
				
			||||||
    protected readonly clss: Set<string> = new Set<string>()
 | 
					    protected readonly clss: Set<string> = new Set<string>()
 | 
				
			||||||
    protected style: string
 | 
					    protected style: string
 | 
				
			||||||
    private _onClick: () => void | Promise<void>
 | 
					    private _onClick: () => void | Promise<void>
 | 
				
			||||||
 | 
					 | 
				
			||||||
    public onClick(f: () => void) {
 | 
					 | 
				
			||||||
        this._onClick = f
 | 
					 | 
				
			||||||
        this.SetClass("cursor-pointer")
 | 
					 | 
				
			||||||
        if (this._constructedHtmlElement !== undefined) {
 | 
					 | 
				
			||||||
            this._constructedHtmlElement.onclick = f
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return this
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    AttachTo(divId: string) {
 | 
					    AttachTo(divId: string) {
 | 
				
			||||||
        const element = document.getElementById(divId)
 | 
					        const element = document.getElementById(divId)
 | 
				
			||||||
        if (element === null) {
 | 
					        if (element === null) {
 | 
				
			||||||
| 
						 | 
					@ -75,11 +65,6 @@ export default abstract class BaseUIElement {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return this
 | 
					        return this
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
    public HasClass(clss: string): boolean {
 | 
					 | 
				
			||||||
        return this.clss.has(clss)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public SetStyle(style: string): this {
 | 
					    public SetStyle(style: string): this {
 | 
				
			||||||
        this.style = style
 | 
					        this.style = style
 | 
				
			||||||
        if (this._constructedHtmlElement !== undefined) {
 | 
					        if (this._constructedHtmlElement !== undefined) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										35
									
								
								src/UI/BigComponents/Histogram.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/UI/BigComponents/Histogram.svelte
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,35 @@
 | 
				
			||||||
 | 
					<script lang="ts">
 | 
				
			||||||
 | 
					  import { Utils } from "../../Utils"
 | 
				
			||||||
 | 
					  import type { ChartConfiguration } from "chart.js"
 | 
				
			||||||
 | 
					  import ChartJs from "../Base/ChartJs.svelte"
 | 
				
			||||||
 | 
					  import { ChartJsUtils } from "../Base/ChartJsUtils"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  export let values: Store<string[]>
 | 
				
			||||||
 | 
					  let counts: Store<Map<string, number>> = 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)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return counts
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let max: Store<number> = counts.mapD(counts => Math.max(...Array.from(counts.values())))
 | 
				
			||||||
 | 
					  let keys: Store<string> = counts.mapD(counts => {
 | 
				
			||||||
 | 
					    const keys = Utils.Dedup(counts.keys())
 | 
				
			||||||
 | 
					    keys.sort(/*inplace sort*/)
 | 
				
			||||||
 | 
					    return keys
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  let config: Store<ChartConfiguration> = keys.mapD(keys => ChartJsUtils.createHistogramConfig(keys, counts.data), [counts])
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{#if $config}
 | 
				
			||||||
 | 
					  <ChartJs config={$config} />
 | 
				
			||||||
 | 
					{/if}
 | 
				
			||||||
| 
						 | 
					@ -1,157 +0,0 @@
 | 
				
			||||||
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 SvelteUIElement from "../Base/SvelteUIElement"
 | 
					 | 
				
			||||||
import Circle from "../../assets/svg/Circle.svelte"
 | 
					 | 
				
			||||||
import ChevronUp from "@babeard/svelte-heroicons/solid/ChevronUp"
 | 
					 | 
				
			||||||
import ChevronDown from "@babeard/svelte-heroicons/solid/ChevronDown"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default class Histogram 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 new SvelteUIElement(ChevronUp)
 | 
					 | 
				
			||||||
                    case "name-rev":
 | 
					 | 
				
			||||||
                        return new SvelteUIElement(ChevronDown)
 | 
					 | 
				
			||||||
                    default:
 | 
					 | 
				
			||||||
                        return new SvelteUIElement(Circle)
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        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 new SvelteUIElement(ChevronUp)
 | 
					 | 
				
			||||||
                    case "count-rev":
 | 
					 | 
				
			||||||
                        return new SvelteUIElement(ChevronDown)
 | 
					 | 
				
			||||||
                    default:
 | 
					 | 
				
			||||||
                        return new SvelteUIElement(Circle)
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        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]
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,8 @@
 | 
				
			||||||
import { Store, UIEventSource } from "../../Logic/UIEventSource"
 | 
					import { Store, UIEventSource } from "../../Logic/UIEventSource"
 | 
				
			||||||
import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization"
 | 
					import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization"
 | 
				
			||||||
import Histogram from "../BigComponents/Histogram"
 | 
					 | 
				
			||||||
import { Feature } from "geojson"
 | 
					import { Feature } from "geojson"
 | 
				
			||||||
 | 
					import SvelteUIElement from "../Base/SvelteUIElement"
 | 
				
			||||||
 | 
					import Histogram from "../BigComponents/Histogram.svelte"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class HistogramViz extends SpecialVisualization {
 | 
					export class HistogramViz extends SpecialVisualization {
 | 
				
			||||||
    funcName = "histogram"
 | 
					    funcName = "histogram"
 | 
				
			||||||
| 
						 | 
					@ -15,21 +16,8 @@ export class HistogramViz extends SpecialVisualization {
 | 
				
			||||||
            name: "key",
 | 
					            name: "key",
 | 
				
			||||||
            doc: "The key to be read and to generate a histogram from",
 | 
					            doc: "The key to be read and to generate a histogram from",
 | 
				
			||||||
            required: true,
 | 
					            required: true,
 | 
				
			||||||
        },
 | 
					        }
 | 
				
			||||||
        {
 | 
					
 | 
				
			||||||
            name: "title",
 | 
					 | 
				
			||||||
            doc: "This text will be placed above the texts (in the first column of the visulasition)",
 | 
					 | 
				
			||||||
            defaultValue: "",
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            name: "countHeader",
 | 
					 | 
				
			||||||
            doc: "This text will be placed above the bars",
 | 
					 | 
				
			||||||
            defaultValue: "",
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            name: "colors*",
 | 
					 | 
				
			||||||
            doc: "(Matches all resting arguments - optional) Matches a regex onto a color value, e.g. `3[a-zA-Z+-]*:#33cc33`",
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    structuredExamples(): { feature: Feature; args: string[] }[] {
 | 
					    structuredExamples(): { feature: Feature; args: string[] }[] {
 | 
				
			||||||
| 
						 | 
					@ -53,27 +41,7 @@ export class HistogramViz extends SpecialVisualization {
 | 
				
			||||||
        tagSource: UIEventSource<Record<string, string>>,
 | 
					        tagSource: UIEventSource<Record<string, string>>,
 | 
				
			||||||
        args: string[]
 | 
					        args: string[]
 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
        let assignColors = undefined
 | 
					        const values: Store<string[]> = tagSource.map((tags) => {
 | 
				
			||||||
        if (args.length >= 3) {
 | 
					 | 
				
			||||||
            const colors = [...args]
 | 
					 | 
				
			||||||
            colors.splice(0, 3)
 | 
					 | 
				
			||||||
            const mapping = colors.map((c) => {
 | 
					 | 
				
			||||||
                const splitted = c.split(":")
 | 
					 | 
				
			||||||
                const value = splitted.pop()
 | 
					 | 
				
			||||||
                const regex = splitted.join(":")
 | 
					 | 
				
			||||||
                return { regex: "^" + regex + "$", color: value }
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
            assignColors = (key) => {
 | 
					 | 
				
			||||||
                for (const kv of mapping) {
 | 
					 | 
				
			||||||
                    if (key.match(kv.regex) !== null) {
 | 
					 | 
				
			||||||
                        return kv.color
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                return undefined
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const listSource: Store<string[]> = tagSource.map((tags) => {
 | 
					 | 
				
			||||||
            const value = tags[args[0]]
 | 
					            const value = tags[args[0]]
 | 
				
			||||||
            try {
 | 
					            try {
 | 
				
			||||||
                if (value === "" || value === undefined) {
 | 
					                if (value === "" || value === undefined) {
 | 
				
			||||||
| 
						 | 
					@ -88,8 +56,6 @@ export class HistogramViz extends SpecialVisualization {
 | 
				
			||||||
                return undefined
 | 
					                return undefined
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
        return new Histogram(listSource, args[1], args[2], {
 | 
					        return new SvelteUIElement(Histogram, { values })
 | 
				
			||||||
            assignColor: assignColors,
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -295,7 +295,7 @@ export class Translation extends BaseUIElement {
 | 
				
			||||||
            tr["_context"] = this.context
 | 
					            tr["_context"] = this.context
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return new Translation(tr, copyContext ? this.context : null)
 | 
					        return new Translation(tr, copyContext ? this.context : undefined)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue