forked from MapComplete/MapComplete
Add charts to dashboard view
This commit is contained in:
parent
47a184d626
commit
72f7bbd7db
8 changed files with 314 additions and 76 deletions
24
UI/Base/ChartJs.ts
Normal file
24
UI/Base/ChartJs.ts
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import BaseUIElement from "../BaseUIElement";
|
||||||
|
import {Chart, ChartConfiguration, ChartType, DefaultDataPoint, registerables} from 'chart.js';
|
||||||
|
Chart.register(...registerables);
|
||||||
|
|
||||||
|
|
||||||
|
export default class ChartJs<
|
||||||
|
TType extends ChartType = ChartType,
|
||||||
|
TData = DefaultDataPoint<TType>,
|
||||||
|
TLabel = unknown
|
||||||
|
> extends BaseUIElement{
|
||||||
|
private readonly _config: ChartConfiguration<TType, TData, TLabel>;
|
||||||
|
|
||||||
|
constructor(config: ChartConfiguration<TType, TData, TLabel>) {
|
||||||
|
super();
|
||||||
|
this._config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected InnerConstructElement(): HTMLElement {
|
||||||
|
const canvas = document.createElement("canvas");
|
||||||
|
new Chart(canvas, this._config);
|
||||||
|
return canvas;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -38,6 +38,9 @@ export default class Combine extends BaseUIElement {
|
||||||
protected InnerConstructElement(): HTMLElement {
|
protected InnerConstructElement(): HTMLElement {
|
||||||
const el = document.createElement("span")
|
const el = document.createElement("span")
|
||||||
try {
|
try {
|
||||||
|
if(this.uiElements === undefined){
|
||||||
|
console.error("PANIC")
|
||||||
|
}
|
||||||
for (const subEl of this.uiElements) {
|
for (const subEl of this.uiElements) {
|
||||||
if (subEl === undefined || subEl === null) {
|
if (subEl === undefined || subEl === null) {
|
||||||
continue;
|
continue;
|
||||||
|
|
|
@ -33,6 +33,7 @@ export class VariableUiElement extends BaseUIElement {
|
||||||
if (self.isDestroyed) {
|
if (self.isDestroyed) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (el.firstChild) {
|
while (el.firstChild) {
|
||||||
el.removeChild(el.lastChild);
|
el.removeChild(el.lastChild);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ export default abstract class BaseUIElement {
|
||||||
|
|
||||||
protected _constructedHtmlElement: HTMLElement;
|
protected _constructedHtmlElement: HTMLElement;
|
||||||
protected isDestroyed = false;
|
protected isDestroyed = false;
|
||||||
private clss: Set<string> = new Set<string>();
|
private readonly clss: Set<string> = new Set<string>();
|
||||||
private style: string;
|
private style: string;
|
||||||
private _onClick: () => void;
|
private _onClick: () => void;
|
||||||
|
|
||||||
|
@ -114,7 +114,7 @@ export default abstract class BaseUIElement {
|
||||||
if (style !== undefined && style !== "") {
|
if (style !== undefined && style !== "") {
|
||||||
el.style.cssText = style
|
el.style.cssText = style
|
||||||
}
|
}
|
||||||
if (this.clss.size > 0) {
|
if (this.clss?.size > 0) {
|
||||||
try {
|
try {
|
||||||
el.classList.add(...Array.from(this.clss))
|
el.classList.add(...Array.from(this.clss))
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
146
UI/BigComponents/TagRenderingChart.ts
Normal file
146
UI/BigComponents/TagRenderingChart.ts
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
import ChartJs from "../Base/ChartJs";
|
||||||
|
import {OsmFeature} from "../../Models/OsmFeature";
|
||||||
|
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig";
|
||||||
|
import {ChartConfiguration} from 'chart.js';
|
||||||
|
import Combine from "../Base/Combine";
|
||||||
|
|
||||||
|
export default class TagRenderingChart extends Combine {
|
||||||
|
|
||||||
|
private static readonly unkownColor = 'rgba(128, 128, 128, 0.2)'
|
||||||
|
private static readonly unkownBorderColor = 'rgba(128, 128, 128, 0.2)'
|
||||||
|
|
||||||
|
private static readonly otherColor = 'rgba(128, 128, 128, 0.2)'
|
||||||
|
private static readonly otherBorderColor = 'rgba(128, 128, 255)'
|
||||||
|
private static readonly notApplicableColor = 'rgba(128, 128, 128, 0.2)'
|
||||||
|
private static readonly notApplicableBorderColor = 'rgba(255, 0, 0)'
|
||||||
|
|
||||||
|
|
||||||
|
private static readonly backgroundColors = [
|
||||||
|
'rgba(255, 99, 132, 0.2)',
|
||||||
|
'rgba(54, 162, 235, 0.2)',
|
||||||
|
'rgba(255, 206, 86, 0.2)',
|
||||||
|
'rgba(75, 192, 192, 0.2)',
|
||||||
|
'rgba(153, 102, 255, 0.2)',
|
||||||
|
'rgba(255, 159, 64, 0.2)'
|
||||||
|
]
|
||||||
|
|
||||||
|
private static readonly borderColors = [
|
||||||
|
'rgba(255, 99, 132, 1)',
|
||||||
|
'rgba(54, 162, 235, 1)',
|
||||||
|
'rgba(255, 206, 86, 1)',
|
||||||
|
'rgba(75, 192, 192, 1)',
|
||||||
|
'rgba(153, 102, 255, 1)',
|
||||||
|
'rgba(255, 159, 64, 1)'
|
||||||
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a chart about this tagRendering for the given data
|
||||||
|
*/
|
||||||
|
constructor(features: OsmFeature[], tagRendering: TagRenderingConfig, options?: {
|
||||||
|
chartclasses?: string,
|
||||||
|
chartstyle?: string
|
||||||
|
}) {
|
||||||
|
|
||||||
|
const mappings = tagRendering.mappings ?? []
|
||||||
|
if (mappings.length === 0 && tagRendering.freeform?.key === undefined) {
|
||||||
|
super(["TagRendering", tagRendering.id, "does not have mapping or a freeform key - no stats can be made"])
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let unknownCount = 0;
|
||||||
|
let categoryCounts = mappings.map(_ => 0)
|
||||||
|
let otherCount = 0;
|
||||||
|
let notApplicable = 0;
|
||||||
|
for (const feature of features) {
|
||||||
|
const props = feature.properties
|
||||||
|
if(tagRendering.condition !== undefined && !tagRendering.condition.matchesProperties(props)){
|
||||||
|
notApplicable++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tagRendering.IsKnown(props)) {
|
||||||
|
unknownCount++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let foundMatchingMapping = false;
|
||||||
|
for (let i = 0; i < mappings.length; i++) {
|
||||||
|
const mapping = mappings[i];
|
||||||
|
if (mapping.if.matchesProperties(props)) {
|
||||||
|
categoryCounts[i]++
|
||||||
|
foundMatchingMapping = true
|
||||||
|
if (!tagRendering.multiAnswer) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (tagRendering.freeform?.key !== undefined && props[tagRendering.freeform.key] !== undefined) {
|
||||||
|
otherCount++
|
||||||
|
} else if (!foundMatchingMapping) {
|
||||||
|
unknownCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unknownCount + notApplicable === features.length) {
|
||||||
|
console.log("Totals:", features.length+" elements","tr:", tagRendering, "other",otherCount, "unkown",unknownCount, "na", notApplicable)
|
||||||
|
super(["No relevant data for ", tagRendering.id])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const labels = ["Unknown", "Other", "Not applicable", ...mappings?.map(m => m.then.txt) ?? []]
|
||||||
|
const data = [unknownCount, otherCount, notApplicable,...categoryCounts]
|
||||||
|
const borderColor = [TagRenderingChart.unkownBorderColor, TagRenderingChart.otherBorderColor, TagRenderingChart.notApplicableBorderColor]
|
||||||
|
const backgroundColor = [TagRenderingChart.unkownColor, TagRenderingChart.otherColor, TagRenderingChart.notApplicableColor]
|
||||||
|
|
||||||
|
while (borderColor.length < data.length) {
|
||||||
|
borderColor.push(...TagRenderingChart.borderColors)
|
||||||
|
backgroundColor.push(...TagRenderingChart.backgroundColors)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = data.length; i >= 0; i--) {
|
||||||
|
if (data[i] === 0) {
|
||||||
|
labels.splice(i, 1)
|
||||||
|
data.splice(i, 1)
|
||||||
|
borderColor.splice(i, 1)
|
||||||
|
backgroundColor.splice(i, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tagRendering.id === undefined) {
|
||||||
|
console.log(tagRendering)
|
||||||
|
}
|
||||||
|
const config = <ChartConfiguration>{
|
||||||
|
type: tagRendering.multiAnswer ? 'bar' : 'doughnut',
|
||||||
|
data: {
|
||||||
|
labels,
|
||||||
|
datasets: [{
|
||||||
|
data,
|
||||||
|
backgroundColor,
|
||||||
|
borderColor,
|
||||||
|
borderWidth: 1,
|
||||||
|
label: undefined
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
display: !tagRendering.multiAnswer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const chart = new ChartJs(config).SetClass(options?.chartclasses ?? "w-32 h-32");
|
||||||
|
|
||||||
|
if (options.chartstyle !== undefined) {
|
||||||
|
chart.SetStyle(options.chartstyle)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
super([
|
||||||
|
tagRendering.question ?? tagRendering.id,
|
||||||
|
chart])
|
||||||
|
|
||||||
|
this.SetClass("block")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -26,6 +26,8 @@ import {FilterState} from "../Models/FilteredLayer";
|
||||||
import Translations from "./i18n/Translations";
|
import Translations from "./i18n/Translations";
|
||||||
import Constants from "../Models/Constants";
|
import Constants from "../Models/Constants";
|
||||||
import SimpleAddUI from "./BigComponents/SimpleAddUI";
|
import SimpleAddUI from "./BigComponents/SimpleAddUI";
|
||||||
|
import TagRenderingChart from "./BigComponents/TagRenderingChart";
|
||||||
|
import Loading from "./Base/Loading";
|
||||||
|
|
||||||
|
|
||||||
export default class DashboardGui {
|
export default class DashboardGui {
|
||||||
|
@ -170,7 +172,7 @@ export default class DashboardGui {
|
||||||
}
|
}
|
||||||
const map = this.SetupMap();
|
const map = this.SetupMap();
|
||||||
|
|
||||||
Utils.downloadJson("./service-worker-version").then(data => console.log("Service worker", data)).catch(e => console.log("Service worker not active"))
|
Utils.downloadJson("./service-worker-version").then(data => console.log("Service worker", data)).catch(_ => console.log("Service worker not active"))
|
||||||
|
|
||||||
document.getElementById("centermessage").classList.add("hidden")
|
document.getElementById("centermessage").classList.add("hidden")
|
||||||
|
|
||||||
|
@ -180,7 +182,7 @@ export default class DashboardGui {
|
||||||
}
|
}
|
||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
const elementsInview = new UIEventSource([]);
|
const elementsInview = new UIEventSource<{ distance: number, center: [number, number], element: OsmFeature, layer: LayerConfig }[]>([]);
|
||||||
|
|
||||||
function update() {
|
function update() {
|
||||||
elementsInview.setData(self.visibleElements(map, layers))
|
elementsInview.setData(self.visibleElements(map, layers))
|
||||||
|
@ -201,7 +203,7 @@ export default class DashboardGui {
|
||||||
const welcome = new Combine([state.layoutToUse.description, state.layoutToUse.descriptionTail])
|
const welcome = new Combine([state.layoutToUse.description, state.layoutToUse.descriptionTail])
|
||||||
self.currentView.setData({title: state.layoutToUse.title, contents: welcome})
|
self.currentView.setData({title: state.layoutToUse.title, contents: welcome})
|
||||||
const filterViewIsOpened = new UIEventSource(false)
|
const filterViewIsOpened = new UIEventSource(false)
|
||||||
filterViewIsOpened.addCallback(fv => self.currentView.setData({title: "filters", contents: filterView}))
|
filterViewIsOpened.addCallback(_ => self.currentView.setData({title: "filters", contents: filterView}))
|
||||||
|
|
||||||
const newPointIsShown = new UIEventSource(false);
|
const newPointIsShown = new UIEventSource(false);
|
||||||
const addNewPoint = new SimpleAddUI(
|
const addNewPoint = new SimpleAddUI(
|
||||||
|
@ -227,6 +229,36 @@ export default class DashboardGui {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const statistics =
|
||||||
|
new VariableUiElement(elementsInview.stabilized(1000).map(features => {
|
||||||
|
if (features === undefined) {
|
||||||
|
return new Loading("Loading data")
|
||||||
|
}
|
||||||
|
if (features.length === 0) {
|
||||||
|
return "No elements in view"
|
||||||
|
}
|
||||||
|
const els = []
|
||||||
|
for (const layer of state.layoutToUse.layers) {
|
||||||
|
if(layer.name === undefined){
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const featuresForLayer = features.filter(f => f.layer === layer).map(f => f.element)
|
||||||
|
if(featuresForLayer.length === 0){
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
els.push(new Title(layer.name))
|
||||||
|
for (const tagRendering of layer.tagRenderings) {
|
||||||
|
const chart = new TagRenderingChart(featuresForLayer, tagRendering, {
|
||||||
|
chartclasses: "w-full",
|
||||||
|
chartstyle: "height: 60rem"
|
||||||
|
})
|
||||||
|
els.push(chart)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new Combine(els)
|
||||||
|
}))
|
||||||
|
|
||||||
|
|
||||||
new Combine([
|
new Combine([
|
||||||
new Combine([
|
new Combine([
|
||||||
this.viewSelector(new Title(state.layoutToUse.title.Clone(), 2), state.layoutToUse.title.Clone(), welcome, "welcome"),
|
this.viewSelector(new Title(state.layoutToUse.title.Clone(), 2), state.layoutToUse.title.Clone(), welcome, "welcome"),
|
||||||
|
@ -235,7 +267,7 @@ export default class DashboardGui {
|
||||||
this.viewSelector(new Title(
|
this.viewSelector(new Title(
|
||||||
new VariableUiElement(elementsInview.map(elements => "There are " + elements?.length + " elements in view"))),
|
new VariableUiElement(elementsInview.map(elements => "There are " + elements?.length + " elements in view"))),
|
||||||
"Statistics",
|
"Statistics",
|
||||||
new FixedUiElement("Stats"), "statistics"),
|
statistics, "statistics"),
|
||||||
|
|
||||||
this.viewSelector(new FixedUiElement("Filter"),
|
this.viewSelector(new FixedUiElement("Filter"),
|
||||||
"Filters", filterView, "filters"),
|
"Filters", filterView, "filters"),
|
||||||
|
|
|
@ -1050,8 +1050,8 @@ video {
|
||||||
height: 6rem;
|
height: 6rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.h-8 {
|
.h-80 {
|
||||||
height: 2rem;
|
height: 20rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.h-64 {
|
.h-64 {
|
||||||
|
@ -1070,6 +1070,10 @@ video {
|
||||||
height: 3rem;
|
height: 3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.h-8 {
|
||||||
|
height: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
.h-4 {
|
.h-4 {
|
||||||
height: 1rem;
|
height: 1rem;
|
||||||
}
|
}
|
||||||
|
@ -1134,12 +1138,8 @@ video {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.w-8 {
|
.w-80 {
|
||||||
width: 2rem;
|
width: 20rem;
|
||||||
}
|
|
||||||
|
|
||||||
.w-1 {
|
|
||||||
width: 0.25rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.w-24 {
|
.w-24 {
|
||||||
|
@ -1162,6 +1162,10 @@ video {
|
||||||
width: 3rem;
|
width: 3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.w-8 {
|
||||||
|
width: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
.w-4 {
|
.w-4 {
|
||||||
width: 1rem;
|
width: 1rem;
|
||||||
}
|
}
|
||||||
|
@ -1570,10 +1574,6 @@ video {
|
||||||
padding-right: 1rem;
|
padding-right: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pr-2 {
|
|
||||||
padding-right: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pl-1 {
|
.pl-1 {
|
||||||
padding-left: 0.25rem;
|
padding-left: 0.25rem;
|
||||||
}
|
}
|
||||||
|
@ -1642,6 +1642,10 @@ video {
|
||||||
padding-top: 0.125rem;
|
padding-top: 0.125rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pr-2 {
|
||||||
|
padding-right: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
.pl-6 {
|
.pl-6 {
|
||||||
padding-left: 1.5rem;
|
padding-left: 1.5rem;
|
||||||
}
|
}
|
||||||
|
@ -1860,6 +1864,10 @@ video {
|
||||||
z-index: 10001
|
z-index: 10001
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.w-160 {
|
||||||
|
width: 40rem;
|
||||||
|
}
|
||||||
|
|
||||||
.bg-subtle {
|
.bg-subtle {
|
||||||
background-color: var(--subtle-detail-color);
|
background-color: var(--subtle-detail-color);
|
||||||
color: var(--subtle-detail-color-contrast);
|
color: var(--subtle-detail-color-contrast);
|
||||||
|
|
120
test.ts
120
test.ts
|
@ -1,52 +1,76 @@
|
||||||
import * as shops from "./assets/generated/layers/shops.json"
|
import ChartJs from "./UI/Base/ChartJs";
|
||||||
import Combine from "./UI/Base/Combine";
|
import TagRenderingChart from "./UI/BigComponents/TagRenderingChart";
|
||||||
import Img from "./UI/Base/Img";
|
import {OsmFeature} from "./Models/OsmFeature";
|
||||||
import BaseUIElement from "./UI/BaseUIElement";
|
import * as food from "./assets/generated/layers/food.json"
|
||||||
import {VariableUiElement} from "./UI/Base/VariableUIElement";
|
import TagRenderingConfig from "./Models/ThemeConfig/TagRenderingConfig";
|
||||||
import LanguagePicker from "./UI/LanguagePicker";
|
|
||||||
import TagRenderingConfig, {Mapping} from "./Models/ThemeConfig/TagRenderingConfig";
|
|
||||||
import {MappingConfigJson} from "./Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson";
|
|
||||||
import {FixedUiElement} from "./UI/Base/FixedUiElement";
|
|
||||||
import {TagsFilter} from "./Logic/Tags/TagsFilter";
|
|
||||||
import {SearchablePillsSelector} from "./UI/Input/SearchableMappingsSelector";
|
|
||||||
import {UIEventSource} from "./Logic/UIEventSource";
|
import {UIEventSource} from "./Logic/UIEventSource";
|
||||||
|
import Combine from "./UI/Base/Combine";
|
||||||
const mappingsRaw: MappingConfigJson[] = <any>shops.tagRenderings.find(tr => tr.id == "shop_types").mappings
|
const data = new UIEventSource<OsmFeature[]>([
|
||||||
const mappings = mappingsRaw.map((m, i) => TagRenderingConfig.ExtractMapping(m, i, "test", "test"))
|
|
||||||
|
|
||||||
function fromMapping(m: Mapping): { show: BaseUIElement, value: TagsFilter, mainTerm: Record<string, string>, searchTerms?: Record<string, string[]> } {
|
|
||||||
const el: BaseUIElement = m.then
|
|
||||||
let icon: BaseUIElement
|
|
||||||
if (m.icon !== undefined) {
|
|
||||||
icon = new Img(m.icon).SetClass("h-8 w-8 pr-2")
|
|
||||||
} else {
|
|
||||||
icon = new FixedUiElement("").SetClass("h-8 w-1")
|
|
||||||
}
|
|
||||||
const show = new Combine([
|
|
||||||
icon,
|
|
||||||
el.SetClass("block-ruby")
|
|
||||||
]).SetClass("flex items-center")
|
|
||||||
|
|
||||||
return {show, mainTerm: m.then.translations, searchTerms: m.searchTerms, value: m.if};
|
|
||||||
|
|
||||||
}
|
|
||||||
const search = new UIEventSource("")
|
|
||||||
const sp = new SearchablePillsSelector(
|
|
||||||
mappings.map(m => fromMapping(m)),
|
|
||||||
{
|
{
|
||||||
noMatchFound: new VariableUiElement(search.map(s => "Mark this a `"+s+"`")),
|
properties: {
|
||||||
onNoSearch: new FixedUiElement("Search in "+mappingsRaw.length+" categories"),
|
id: "node/1234",
|
||||||
selectIfSingle: true,
|
cuisine:"pizza",
|
||||||
searchValue: search
|
"payment:cash":"yes"
|
||||||
|
},
|
||||||
|
geometry:{
|
||||||
|
type: "Point",
|
||||||
|
coordinates: [0,0]
|
||||||
|
},
|
||||||
|
id: "node/1234",
|
||||||
|
type: "Feature"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
properties: {
|
||||||
|
id: "node/42",
|
||||||
|
cuisine:"pizza",
|
||||||
|
"payment:cash":"yes"
|
||||||
|
},
|
||||||
|
geometry:{
|
||||||
|
type: "Point",
|
||||||
|
coordinates: [1,0]
|
||||||
|
},
|
||||||
|
id: "node/42",
|
||||||
|
type: "Feature"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
properties: {
|
||||||
|
id: "node/452",
|
||||||
|
cuisine:"pasta",
|
||||||
|
"payment:cash":"yes",
|
||||||
|
"payment:cards":"yes"
|
||||||
|
},
|
||||||
|
geometry:{
|
||||||
|
type: "Point",
|
||||||
|
coordinates: [2,0]
|
||||||
|
},
|
||||||
|
id: "node/452",
|
||||||
|
type: "Feature"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
properties: {
|
||||||
|
id: "node/4542",
|
||||||
|
cuisine:"something_comletely_invented",
|
||||||
|
"payment:cards":"yes"
|
||||||
|
},
|
||||||
|
geometry:{
|
||||||
|
type: "Point",
|
||||||
|
coordinates: [3,0]
|
||||||
|
},
|
||||||
|
id: "node/4542",
|
||||||
|
type: "Feature"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
properties: {
|
||||||
|
id: "node/45425",
|
||||||
|
},
|
||||||
|
geometry:{
|
||||||
|
type: "Point",
|
||||||
|
coordinates: [3,0]
|
||||||
|
},
|
||||||
|
id: "node/45425",
|
||||||
|
type: "Feature"
|
||||||
}
|
}
|
||||||
)
|
]);
|
||||||
|
|
||||||
sp.AttachTo("maindiv")
|
new Combine(food.tagRenderings.map(tr => new TagRenderingChart(data, new TagRenderingConfig(tr, "test"), {chartclasses: "w-160 h-160"})))
|
||||||
|
.AttachTo("maindiv")
|
||||||
const lp = new LanguagePicker(["en", "nl"], "")
|
|
||||||
|
|
||||||
new Combine([
|
|
||||||
new VariableUiElement(sp.GetValue().map(tf => new FixedUiElement("Selected tags: " + tf.map(tf => tf.asHumanString(false, false, {})).join(", ")))),
|
|
||||||
lp
|
|
||||||
]).SetClass("flex flex-col")
|
|
||||||
.AttachTo("extradiv")
|
|
Loading…
Reference in a new issue