forked from MapComplete/MapComplete
More work on statistics
This commit is contained in:
parent
56b1337743
commit
5ef9a57bb0
5 changed files with 325 additions and 173 deletions
|
@ -7,61 +7,233 @@ import ChartJs from "./Base/ChartJs";
|
|||
import Loading from "./Base/Loading";
|
||||
import {Utils} from "../Utils";
|
||||
import Combine from "./Base/Combine";
|
||||
import BaseUIElement from "./BaseUIElement";
|
||||
import TagRenderingChart from "./BigComponents/TagRenderingChart";
|
||||
import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig";
|
||||
import {ChartConfiguration} from "chart.js";
|
||||
import {FixedUiElement} from "./Base/FixedUiElement";
|
||||
|
||||
export default class StatisticsGUI {
|
||||
|
||||
public static setup(): void{
|
||||
|
||||
private static readonly homeUrl = "https://raw.githubusercontent.com/pietervdvn/MapComplete/develop/Docs/Tools/stats/"
|
||||
private static readonly stats_files = "file-overview.json"
|
||||
private readonly index = UIEventSource.FromPromise(Utils.downloadJson(StatisticsGUI.homeUrl + StatisticsGUI.stats_files))
|
||||
|
||||
|
||||
new VariableUiElement(index.map(paths => {
|
||||
public setup(): void {
|
||||
|
||||
|
||||
new VariableUiElement(this.index.map(paths => {
|
||||
if (paths === undefined) {
|
||||
return new Loading("Loading overview...")
|
||||
}
|
||||
const downloaded = new UIEventSource<{ features: ChangeSetData[] }[]>([])
|
||||
|
||||
for (const filepath of paths) {
|
||||
Utils.downloadJson(homeUrl + filepath).then(data => {
|
||||
Utils.downloadJson(StatisticsGUI.homeUrl + filepath).then(data => {
|
||||
data.features.forEach(item => {
|
||||
item.properties = {...item.properties, ...item.properties.metadata}
|
||||
delete item.properties.metadata
|
||||
})
|
||||
downloaded.data.push(data)
|
||||
downloaded.ping()
|
||||
})
|
||||
}
|
||||
|
||||
return new VariableUiElement(downloaded.map(downloaded => {
|
||||
const themeBreakdown = new Map<string, number>()
|
||||
for (const feats of downloaded) {
|
||||
console.log("Feats:", feats)
|
||||
for (const feat of feats.features) {
|
||||
const key = feat.properties.metadata.theme
|
||||
const count = themeBreakdown.get(key) ?? 0
|
||||
themeBreakdown.set(key, count + 1)
|
||||
}
|
||||
}
|
||||
return new Combine([
|
||||
new VariableUiElement(downloaded.map(dl => "Downloaded " + dl.length + " items")),
|
||||
new VariableUiElement(downloaded.map(l => [...l]).stabilized(250).map(downloaded => {
|
||||
const overview = ChangesetsOverview.fromDirtyData([].concat(...downloaded.map(d => d.features)))
|
||||
.filter(cs => new Date(cs.properties.date) > new Date(2022,6,1))
|
||||
|
||||
// return overview.breakdownPerDay(overview.themeBreakdown)
|
||||
return overview.breakdownPer(overview.themeBreakdown, "month")
|
||||
})).SetClass("block w-full h-full")
|
||||
]).SetClass("block w-full h-full")
|
||||
|
||||
const keys = Array.from(themeBreakdown.keys())
|
||||
const values = keys.map( k => themeBreakdown.get(k))
|
||||
|
||||
console.log(keys, values)
|
||||
return new Combine([
|
||||
"Got " + downloaded.length + " files out of " + paths.length,
|
||||
new ChartJs({
|
||||
type: "pie",
|
||||
data: {
|
||||
datasets: [{data: values}],
|
||||
labels: keys
|
||||
}
|
||||
}).SetClass("w-1/3 h-full")
|
||||
]).SetClass("block w-full h-full")
|
||||
})).SetClass("block w-full h-full")
|
||||
})).SetClass("block w-full h-full").AttachTo("maindiv")
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const homeUrl = "https://raw.githubusercontent.com/pietervdvn/MapComplete/develop/Docs/Tools/stats/"
|
||||
const stats_files = "file-overview.json"
|
||||
const index = UIEventSource.FromPromise(Utils.downloadJson(homeUrl + stats_files))
|
||||
class ChangesetsOverview {
|
||||
|
||||
private static readonly theme_remappings = {
|
||||
"metamap": "maps",
|
||||
"groen": "buurtnatuur",
|
||||
"updaten van metadata met mapcomplete": "buurtnatuur",
|
||||
"Toevoegen of dit natuurreservaat toegangkelijk is": "buurtnatuur",
|
||||
"wiki:mapcomplete/fritures": "fritures",
|
||||
"wiki:MapComplete/Fritures": "fritures",
|
||||
"lits": "lit",
|
||||
"pomp": "cyclofix",
|
||||
"wiki:user:joost_schouppe/campersite": "campersite",
|
||||
"wiki-user-joost_schouppe-geveltuintjes": "geveltuintjes",
|
||||
"wiki-user-joost_schouppe-campersite": "campersite",
|
||||
"wiki-User-joost_schouppe-campersite": "campersite",
|
||||
"wiki-User-joost_schouppe-geveltuintjes": "geveltuintjes",
|
||||
"wiki:User:joost_schouppe/campersite": "campersite",
|
||||
"arbres": "arbres_llefia",
|
||||
"aed_brugge": "aed",
|
||||
"https://llefia.org/arbres/mapcomplete.json": "arbres_llefia",
|
||||
"https://llefia.org/arbres/mapcomplete1.json": "arbres_llefia",
|
||||
"toevoegen of dit natuurreservaat toegangkelijk is": "buurtnatuur",
|
||||
"testing mapcomplete 0.0.0": "buurtnatuur",
|
||||
"entrances": "indoor",
|
||||
"https://raw.githubusercontent.com/osmbe/play/master/mapcomplete/geveltuinen/geveltuinen.json": "geveltuintjes"
|
||||
}
|
||||
private readonly _meta: ChangeSetData[];
|
||||
|
||||
public static fromDirtyData(meta: ChangeSetData[]){
|
||||
return new ChangesetsOverview(meta.map(cs => ChangesetsOverview.cleanChangesetData(cs)))
|
||||
}
|
||||
|
||||
private constructor(meta: ChangeSetData[]) {
|
||||
this._meta = meta;
|
||||
}
|
||||
|
||||
public filter(predicate: (cs: ChangeSetData) => boolean) {
|
||||
return new ChangesetsOverview(this._meta.filter(predicate))
|
||||
}
|
||||
|
||||
private static cleanChangesetData(cs: ChangeSetData): ChangeSetData {
|
||||
if (cs.properties.theme === undefined) {
|
||||
cs.properties.theme = cs.properties.comment.substr(cs.properties.comment.lastIndexOf("#") + 1)
|
||||
}
|
||||
cs.properties.theme = cs.properties.theme.toLowerCase()
|
||||
const remapped = ChangesetsOverview.theme_remappings[cs.properties.theme]
|
||||
cs.properties.theme = remapped ?? cs.properties.theme
|
||||
if (cs.properties.theme.startsWith("https://raw.githubusercontent.com/")) {
|
||||
cs.properties.theme = "gh://" + cs.properties.theme.substr("https://raw.githubusercontent.com/".length)
|
||||
}
|
||||
if (cs.properties.modify + cs.properties.delete + cs.properties.create == 0) {
|
||||
cs.properties.theme = "EMPTY CS"
|
||||
}
|
||||
try {
|
||||
cs.properties.host = new URL(cs.properties.host).host
|
||||
} catch (e) {
|
||||
|
||||
}
|
||||
return cs
|
||||
}
|
||||
|
||||
public themeBreakdown = new TagRenderingConfig({
|
||||
id: "theme-breakdown",
|
||||
question: "What theme was used?",
|
||||
freeform: {
|
||||
key: "theme"
|
||||
},
|
||||
render: "{theme}"
|
||||
}, "statistics.themes")
|
||||
|
||||
public ThemeBreakdown(): BaseUIElement {
|
||||
return new TagRenderingChart(
|
||||
<any>this._meta,
|
||||
this.themeBreakdown,
|
||||
{
|
||||
chartType: "doughnut",
|
||||
sort: true,
|
||||
groupToOtherCutoff: 25
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
public getAllDays(perMonth = false): string[] {
|
||||
let earliest: Date = undefined
|
||||
let latest: Date = undefined;
|
||||
let allDates = new Set<string>();
|
||||
this._meta.forEach((value, key) => {
|
||||
const d = new Date(value.properties.date);
|
||||
Utils.SetMidnight(d)
|
||||
if(perMonth){
|
||||
d.setUTCDate(1)
|
||||
}
|
||||
if (earliest === undefined) {
|
||||
earliest = d
|
||||
} else if (d < earliest) {
|
||||
earliest = d
|
||||
}
|
||||
if (latest === undefined) {
|
||||
latest = d
|
||||
} else if (d > latest) {
|
||||
latest = d
|
||||
}
|
||||
allDates.add(d.toISOString())
|
||||
})
|
||||
|
||||
while (earliest < latest) {
|
||||
earliest.setDate(earliest.getDate() + 1)
|
||||
allDates.add(earliest.toISOString())
|
||||
}
|
||||
const days = Array.from(allDates)
|
||||
days.sort()
|
||||
return days
|
||||
}
|
||||
|
||||
public breakdownPer(tr: TagRenderingConfig, period: "day" | "month" = "day" ): BaseUIElement {
|
||||
const {labels, data} = TagRenderingChart.extractDataAndLabels(tr, <any>this._meta, {
|
||||
sort: true
|
||||
})
|
||||
if (labels === undefined || data === undefined) {
|
||||
return new FixedUiElement("No labels or data given...")
|
||||
}
|
||||
// labels: ["cyclofix", "buurtnatuur", ...]; data : [ ["cyclofix-changeset", "cyclofix-changeset", ...], ["buurtnatuur-cs", "buurtnatuur-cs"], ... ]
|
||||
|
||||
const datasets: { label: string /*themename*/, data: number[]/*counts per day*/, backgroundColor: string }[] = []
|
||||
|
||||
const allDays = this.getAllDays()
|
||||
for (let i = 0; i < labels.length; i++) {
|
||||
const label = labels[i];
|
||||
const changesetsForTheme = <ChangeSetData[]><any>data[i]
|
||||
const perDay: ChangeSetData[][] = []
|
||||
for (const day of allDays) {
|
||||
const today: ChangeSetData[] = []
|
||||
for (const changeset of changesetsForTheme) {
|
||||
const csDate = new Date(changeset.properties.date)
|
||||
Utils.SetMidnight(csDate)
|
||||
if(period === "month"){
|
||||
csDate.setUTCDate(1)
|
||||
}
|
||||
if (csDate.toISOString() !== day) {
|
||||
continue
|
||||
}
|
||||
today.push(changeset)
|
||||
}
|
||||
perDay.push(today)
|
||||
}
|
||||
datasets.push({
|
||||
data: perDay.map(cs => cs.length),
|
||||
backgroundColor: TagRenderingChart.backgroundColors[i % TagRenderingChart.backgroundColors.length],
|
||||
label
|
||||
})
|
||||
}
|
||||
|
||||
const perDayData = {
|
||||
labels: allDays.map(d => d.substr(0, d.indexOf("T"))),
|
||||
datasets
|
||||
}
|
||||
|
||||
const config = <ChartConfiguration>{
|
||||
type: 'bar',
|
||||
data: perDayData,
|
||||
options: {
|
||||
responsive: true,
|
||||
scales: {
|
||||
x: {
|
||||
stacked: true,
|
||||
},
|
||||
y: {
|
||||
stacked: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new ChartJs(config)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
interface ChangeSetData {
|
||||
"id": number,
|
||||
|
@ -92,11 +264,9 @@ interface ChangeSetData {
|
|||
"harmful": any,
|
||||
"checked": boolean,
|
||||
"check_date": any,
|
||||
"metadata": {
|
||||
"host": string,
|
||||
"theme": string,
|
||||
"imagery": string,
|
||||
"language": string
|
||||
}
|
||||
"host": string,
|
||||
"theme": string,
|
||||
"imagery": string,
|
||||
"language": string
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue