forked from MapComplete/MapComplete
		
	Add a histogram visualisation
This commit is contained in:
		
							parent
							
								
									962e364bad
								
							
						
					
					
						commit
						d4909734a1
					
				
					 2 changed files with 77 additions and 65 deletions
				
			
		| 
						 | 
					@ -17,25 +17,39 @@ export default class Table extends BaseUIElement {
 | 
				
			||||||
        this._contents = contents.map(row => row.map(Translations.W));
 | 
					        this._contents = contents.map(row => row.map(Translations.W));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    AsMarkdown(): string {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const headerMarkdownParts = this._header.map(hel => hel?.AsMarkdown() ?? " ")
 | 
				
			||||||
 | 
					        const header = headerMarkdownParts.join(" | ");
 | 
				
			||||||
 | 
					        const headerSep = headerMarkdownParts.map(part => '-'.repeat(part.length + 2)).join("|")
 | 
				
			||||||
 | 
					        const table = this._contents.map(row => row.map(el => el.AsMarkdown() ?? " ").join("|")).join("\n")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return [header, headerSep, table, ""].join("\n")
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    protected InnerConstructElement(): HTMLElement {
 | 
					    protected InnerConstructElement(): HTMLElement {
 | 
				
			||||||
        const table = document.createElement("table")
 | 
					        const table = document.createElement("table")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const headerElems = Utils.NoNull((this._header ?? []).map(elems => elems.ConstructElement()))
 | 
					        const headerElems = Utils.NoNull((this._header ?? []).map(elems => elems.ConstructElement()))
 | 
				
			||||||
        if (headerElems.length > 0) {
 | 
					        if (headerElems.length > 0) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const thead = document.createElement("thead")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            const tr = document.createElement("tr");
 | 
					            const tr = document.createElement("tr");
 | 
				
			||||||
            headerElems.forEach(headerElem => {
 | 
					            headerElems.forEach(headerElem => {
 | 
				
			||||||
                const td = document.createElement("th")
 | 
					                const td = document.createElement("th")
 | 
				
			||||||
                td.appendChild(headerElem)
 | 
					                td.appendChild(headerElem)
 | 
				
			||||||
                tr.appendChild(td)
 | 
					                tr.appendChild(td)
 | 
				
			||||||
            })
 | 
					            })
 | 
				
			||||||
            table.appendChild(tr)
 | 
					            thead.appendChild(tr)
 | 
				
			||||||
 | 
					            table.appendChild(thead)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for (let i = 0; i < this._contents.length; i++){
 | 
					        for (let i = 0; i < this._contents.length; i++) {
 | 
				
			||||||
            let row = this._contents[i];
 | 
					            let row = this._contents[i];
 | 
				
			||||||
            const tr = document.createElement("tr")
 | 
					            const tr = document.createElement("tr")
 | 
				
			||||||
            for (let j = 0; j < row.length; j++){
 | 
					            for (let j = 0; j < row.length; j++) {
 | 
				
			||||||
                let elem = row[j];
 | 
					                let elem = row[j];
 | 
				
			||||||
                const htmlElem = elem?.ConstructElement()
 | 
					                const htmlElem = elem?.ConstructElement()
 | 
				
			||||||
                if (htmlElem === undefined) {
 | 
					                if (htmlElem === undefined) {
 | 
				
			||||||
| 
						 | 
					@ -43,7 +57,7 @@ export default class Table extends BaseUIElement {
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                let style = undefined;
 | 
					                let style = undefined;
 | 
				
			||||||
                if(this._contentStyle !== undefined && this._contentStyle[i] !== undefined && this._contentStyle[j]!== undefined){
 | 
					                if (this._contentStyle !== undefined && this._contentStyle[i] !== undefined && this._contentStyle[j] !== undefined) {
 | 
				
			||||||
                    style = this._contentStyle[i][j]
 | 
					                    style = this._contentStyle[i][j]
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -58,14 +72,4 @@ export default class Table extends BaseUIElement {
 | 
				
			||||||
        return table;
 | 
					        return table;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    AsMarkdown(): string {
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        const headerMarkdownParts =  this._header.map(hel => hel?.AsMarkdown() ?? " ")
 | 
					 | 
				
			||||||
        const header =headerMarkdownParts.join(" | ");
 | 
					 | 
				
			||||||
        const headerSep = headerMarkdownParts.map(part => '-'.repeat(part.length + 2)).join("|")
 | 
					 | 
				
			||||||
        const table = this._contents.map(row => row.map(el => el.AsMarkdown()?? " ").join("|")).join("\n")
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        return [header, headerSep, table, ""].join("\n")
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -18,9 +18,9 @@ import State from "../State";
 | 
				
			||||||
import {ImageSearcher} from "../Logic/Actors/ImageSearcher";
 | 
					import {ImageSearcher} from "../Logic/Actors/ImageSearcher";
 | 
				
			||||||
import BaseUIElement from "./BaseUIElement";
 | 
					import BaseUIElement from "./BaseUIElement";
 | 
				
			||||||
import LayerConfig from "../Customizations/JSON/LayerConfig";
 | 
					import LayerConfig from "../Customizations/JSON/LayerConfig";
 | 
				
			||||||
import List from "./Base/List";
 | 
					 | 
				
			||||||
import Title from "./Base/Title";
 | 
					import Title from "./Base/Title";
 | 
				
			||||||
import Table from "./Base/Table";
 | 
					import Table from "./Base/Table";
 | 
				
			||||||
 | 
					import Histogram from "./BigComponents/Histogram";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default class SpecialVisualizations {
 | 
					export default class SpecialVisualizations {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -150,57 +150,65 @@ export default class SpecialVisualizations {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                funcName: "histogram",
 | 
					                funcName: "histogram",
 | 
				
			||||||
                docs:"Create a histogram for a list of given values, read from the properties.",
 | 
					                docs: "Create a histogram for a list of given values, read from the properties.",
 | 
				
			||||||
                example: "`{histogram('some_key')}` with properties being `{some_key: ['a','b','a','c']} to create a histogram",
 | 
					                example: "`{histogram('some_key')}` with properties being `{some_key: ['a','b','a','c']} to create a histogram",
 | 
				
			||||||
                args:[
 | 
					                args: [
 | 
				
			||||||
                    {
 | 
					                    {
 | 
				
			||||||
                        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"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        name: "title",
 | 
				
			||||||
 | 
					                        doc: "The text to put above the given values column",
 | 
				
			||||||
 | 
					                        defaultValue: ""
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        name: "countHeader",
 | 
				
			||||||
 | 
					                        doc: "The text to put above the counts",
 | 
				
			||||||
 | 
					                        defaultValue: ""
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        name: "colors",
 | 
				
			||||||
 | 
					                        doc: "(Matches all resting arguments - optional) Matches a regex onto a color value, e.g. `3[a-zA-Z+-]*:#33cc33`"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                ],
 | 
					                ],
 | 
				
			||||||
                constr: (state: State, tagSource: UIEventSource<any>, args: string[]) =>{
 | 
					                constr: (state: State, tagSource: UIEventSource<any>, args: string[]) => {
 | 
				
			||||||
                    return new VariableUiElement(
 | 
					 | 
				
			||||||
                        tagSource
 | 
					 | 
				
			||||||
                            .map(tags => tags[args[0]])
 | 
					 | 
				
			||||||
                            .map(listStr => {
 | 
					 | 
				
			||||||
                            try{
 | 
					 | 
				
			||||||
                                if("" === listStr ?? ""){
 | 
					 | 
				
			||||||
                                    return "Nothing defined";
 | 
					 | 
				
			||||||
                                }
 | 
					 | 
				
			||||||
                                const list : string[] = JSON.parse(listStr)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                                if(list.length === 0){
 | 
					 | 
				
			||||||
                                    return "No values given";
 | 
					 | 
				
			||||||
                                }
 | 
					 | 
				
			||||||
                                
 | 
					 | 
				
			||||||
                                const counts = new Map<string, number>()
 | 
					 | 
				
			||||||
                                for (const key of list) {
 | 
					 | 
				
			||||||
                                    if(key === null || key === undefined || key === ""){
 | 
					 | 
				
			||||||
                                        continue;
 | 
					 | 
				
			||||||
                                    }
 | 
					 | 
				
			||||||
                                    
 | 
					 | 
				
			||||||
                                    if(!counts.has(key)){
 | 
					 | 
				
			||||||
                                        counts.set(key, 1)
 | 
					 | 
				
			||||||
                                    }else{
 | 
					 | 
				
			||||||
                                        counts.set(key, counts.get(key) + 1)
 | 
					 | 
				
			||||||
                                    }
 | 
					 | 
				
			||||||
                                }
 | 
					 | 
				
			||||||
                                const keys = Array.from(counts.keys())
 | 
					 | 
				
			||||||
                                keys.sort()
 | 
					 | 
				
			||||||
                                
 | 
					 | 
				
			||||||
                                return new List(keys.map(key=> new Combine[
 | 
					 | 
				
			||||||
                                    new FixedUiElement(key).SetClass("font-bold"),
 | 
					 | 
				
			||||||
                                        counts.get(key)
 | 
					 | 
				
			||||||
                                    ]));
 | 
					 | 
				
			||||||
                                
 | 
					 | 
				
			||||||
                                
 | 
					 | 
				
			||||||
                            }catch{
 | 
					 | 
				
			||||||
                                return "Could not generate histogram" // TODO translate
 | 
					 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
                            
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    let assignColors = undefined;
 | 
				
			||||||
 | 
					                    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: UIEventSource<string[]> = tagSource
 | 
				
			||||||
 | 
					                        .map(tags => {
 | 
				
			||||||
 | 
					                            try {
 | 
				
			||||||
 | 
					                                const value = tags[args[0]]
 | 
				
			||||||
 | 
					                                if (value === "" || value === undefined) {
 | 
				
			||||||
 | 
					                                    return undefined
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					                                return JSON.parse(value)
 | 
				
			||||||
 | 
					                            } catch (e) {
 | 
				
			||||||
 | 
					                                console.error("Could not load histogram: parsing  of the list failed: ", e)
 | 
				
			||||||
 | 
					                                return undefined;
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        })
 | 
				
			||||||
 | 
					                    return new Histogram(listSource, args[1], args[2], assignColors)
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
| 
						 | 
					@ -262,7 +270,7 @@ export default class SpecialVisualizations {
 | 
				
			||||||
                [
 | 
					                [
 | 
				
			||||||
                    new Title(viz.funcName, 3),
 | 
					                    new Title(viz.funcName, 3),
 | 
				
			||||||
                    viz.docs,
 | 
					                    viz.docs,
 | 
				
			||||||
                    new Table( ["name","default","description"], 
 | 
					                    new Table(["name", "default", "description"],
 | 
				
			||||||
                        viz.args.map(arg => [arg.name, arg.defaultValue ?? "undefined", arg.doc])
 | 
					                        viz.args.map(arg => [arg.name, arg.defaultValue ?? "undefined", arg.doc])
 | 
				
			||||||
                    ),
 | 
					                    ),
 | 
				
			||||||
                    new Title("Example usage", 4),
 | 
					                    new Title("Example usage", 4),
 | 
				
			||||||
| 
						 | 
					@ -275,7 +283,7 @@ export default class SpecialVisualizations {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return new Combine([
 | 
					        return new Combine([
 | 
				
			||||||
            new Title("Special tag renderings",3),
 | 
					                new Title("Special tag renderings", 3),
 | 
				
			||||||
                "In a tagrendering, some special values are substituted by an advanced UI-element. This allows advanced features and visualizations to be reused by custom themes or even to query third-party API's.",
 | 
					                "In a tagrendering, some special values are substituted by an advanced UI-element. This allows advanced features and visualizations to be reused by custom themes or even to query third-party API's.",
 | 
				
			||||||
                "General usage is <b>{func_name()}</b> or <b>{func_name(arg, someotherarg)}</b>. Note that you <i>do not</i> need to use quotes around your arguments, the comma is enough to seperate them. This also implies you cannot use a comma in your args",
 | 
					                "General usage is <b>{func_name()}</b> or <b>{func_name(arg, someotherarg)}</b>. Note that you <i>do not</i> need to use quotes around your arguments, the comma is enough to seperate them. This also implies you cannot use a comma in your args",
 | 
				
			||||||
                ...helpTexts
 | 
					                ...helpTexts
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue