New graphs, add linecharts, add running weekly average number of changesets chart
| Before Width: | Height: | Size: 136 KiB | 
|  | @ -39,6 +39,15 @@ def createBar(options): | |||
|     pyplot.legend() | ||||
| 
 | ||||
| 
 | ||||
| def createLine(options): | ||||
|     data = options["plot"]["count"] | ||||
|     keys = genKeys(data, options["interpetKeysAs"]) | ||||
|     values = list(map(lambda kv: kv["value"], data)) | ||||
| 
 | ||||
|     pyplot.plot(keys, values, label=options["name"]) | ||||
|     pyplot.legend() | ||||
| 
 | ||||
| 
 | ||||
| pyplot_init() | ||||
| title = sys.argv[1] | ||||
| pyplot.title = title | ||||
|  | @ -59,5 +68,8 @@ while (True): | |||
|         createPie(options) | ||||
|     elif (options["plot"]["type"] == "bar"): | ||||
|         createBar(options) | ||||
|     elif (options["plot"]["type"] == "line"): | ||||
|         createLine(options) | ||||
|     else: | ||||
|         print("Unkown type: " + options.type) | ||||
| print("Plot generated") | ||||
|  | @ -179,7 +179,7 @@ interface PlotSpec { | |||
|     name: string, | ||||
|     interpetKeysAs: "date" | "number" | "string" | string | ||||
|     plot: { | ||||
|         type: "pie" | "bar" | ||||
|         type: "pie" | "bar" | "line" | ||||
|         count: { key: string, value: number }[] | ||||
|     } | { | ||||
|         type: "stacked-bar" | ||||
|  | @ -196,6 +196,7 @@ interface PlotSpec { | |||
| function createGraph( | ||||
|     title: string, | ||||
|     ...options: PlotSpec[]) { | ||||
|     console.log("Creating graph",title,"...") | ||||
|     const process = exec("python3 GenPlot.py \"graphs/" + title + "\"", ((error, stdout, stderr) => { | ||||
|         console.log("Python: ", stdout) | ||||
|         if (error !== null) { | ||||
|  | @ -207,7 +208,8 @@ function createGraph( | |||
|     })) | ||||
| 
 | ||||
|     for (const option of options) { | ||||
|         process.stdin._write(JSON.stringify(option) + "\n", "utf-8", undefined) | ||||
|         const d = JSON.stringify(option) + "\n" | ||||
|         process.stdin._write(d, "utf-8", undefined) | ||||
|     } | ||||
|     process.stdin._write("\n", "utf-8", undefined) | ||||
| 
 | ||||
|  | @ -229,6 +231,7 @@ class Histogram<K> { | |||
|     } | ||||
| 
 | ||||
|     public bump(key: K, increase = 1) { | ||||
| 
 | ||||
|         if (this.counts.has(key)) { | ||||
|             this.counts.set(key, increase + this.counts.get(key)) | ||||
|         } else { | ||||
|  | @ -324,6 +327,20 @@ class Histogram<K> { | |||
|         return hist | ||||
|     } | ||||
| 
 | ||||
|     public asRunningAverages(convertToRange: ((key: K) => K[])) { | ||||
|         const newCount = new Histogram<K>() | ||||
|         const self = this | ||||
|         this.counts.forEach((_, key) => { | ||||
|             const keysToCheck = convertToRange(key) | ||||
|             let sum = 0 | ||||
|             for (const k of keysToCheck) { | ||||
|                 sum += self.counts.get(k) ?? 0 | ||||
|             } | ||||
|             newCount.bump(key, sum / keysToCheck.length) | ||||
|         }) | ||||
|         return newCount | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Given a histogram: | ||||
|      * 'a': 3 | ||||
|  | @ -402,6 +419,15 @@ class Histogram<K> { | |||
|         return spec; | ||||
|     } | ||||
| 
 | ||||
|     public asLine(options: { | ||||
|         name: string | ||||
|         compare?: (a: K, b: K) => number | ||||
|     }) { | ||||
|         const spec = this.asPie(options) | ||||
|         spec.plot.type = "line" | ||||
|         return spec | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| class Group<K, V> { | ||||
|  | @ -506,7 +532,8 @@ function createGraphs(allFeatures: ChangeSetData[], appliedFilterDescription: st | |||
|     hist | ||||
|         .createOthersCategory("other", 20) | ||||
|         .addCountToName() | ||||
|         .asBar({name: "Changesets per theme (bar)" + appliedFilterDescription}).render() | ||||
|         .asBar({name: "Changesets per theme (bar)" + appliedFilterDescription}) | ||||
|     .render() | ||||
| 
 | ||||
| 
 | ||||
|     new Histogram<string>(allFeatures.map(f => f.properties.user)) | ||||
|  | @ -516,7 +543,33 @@ function createGraphs(allFeatures: ChangeSetData[], appliedFilterDescription: st | |||
|         { | ||||
|             compare: (a, b) => Number(a) - Number(b), | ||||
|             name: "Contributors per changeset count" + appliedFilterDescription | ||||
|         }).render() | ||||
|         }) | ||||
|     .render() | ||||
| 
 | ||||
| 
 | ||||
|     const csPerDay = new Histogram<string>(allFeatures.map(f => f.properties.date.substr(0, 10))) | ||||
| 
 | ||||
|     const perDayLine = csPerDay | ||||
|         .keyToDate() | ||||
|         .asLine({ | ||||
|             compare: (a, b) => a.getTime() - b.getTime(), | ||||
|             name: "Changesets per day" + appliedFilterDescription | ||||
|         }) | ||||
| 
 | ||||
|     const perDayAvg = csPerDay.asRunningAverages(key => { | ||||
|         const keys = [] | ||||
|         for (let i = 0; i < 7; i++) { | ||||
|             const otherDay = new Date(new Date(key).getTime() - i * 1000 * 60 * 60 * 24) | ||||
|             keys.push(otherDay.toISOString().substr(0, 10)) | ||||
|         } | ||||
|         return keys | ||||
|     }) | ||||
|         .keyToDate() | ||||
|         .asLine({ | ||||
|         compare: (a, b) => a.getTime() - b.getTime(), | ||||
|         name: "Running weekly average" + appliedFilterDescription | ||||
|     }) | ||||
|     createGraph("Changesets per day (line)" + appliedFilterDescription, perDayLine, perDayAvg) | ||||
| 
 | ||||
|     new Histogram<string>(allFeatures.map(f => f.properties.metadata.host)) | ||||
|         .asPie({ | ||||
|  | @ -588,8 +641,25 @@ function createGraphs(allFeatures: ChangeSetData[], appliedFilterDescription: st | |||
| 
 | ||||
| } | ||||
| 
 | ||||
| function createMiscGraphs(allFeatures: ChangeSetData[], emptyCS: ChangeSetData[]) { | ||||
|     new Histogram(emptyCS.map(f => f.properties.date)).keyToDate().asBar({ | ||||
|         name: "Empty changesets by date" | ||||
|     }).render() | ||||
|     const geojson = { | ||||
|         type: "FeatureCollection", | ||||
|         features: allFeatures.map(f => { | ||||
|             try { | ||||
|                 return GeoOperations.centerpoint(f.geometry); | ||||
|             } catch (e) { | ||||
|                 console.error("Could not create center point: ", e, f) | ||||
|             } | ||||
|         }) | ||||
|     } | ||||
|     writeFileSync("centerpoints.geojson", JSON.stringify(geojson, undefined, 2)) | ||||
| } | ||||
| 
 | ||||
| new StatsDownloader("stats").DownloadStats() | ||||
| 
 | ||||
| // new StatsDownloader("stats").DownloadStats()
 | ||||
| const allPaths = readdirSync("stats") | ||||
|     .filter(p => p.startsWith("stats.") && p.endsWith(".json")); | ||||
| let allFeatures: ChangeSetData[] = [].concat(...allPaths | ||||
|  | @ -599,25 +669,7 @@ let allFeatures: ChangeSetData[] = [].concat(...allPaths | |||
| const emptyCS = allFeatures.filter(f => f.properties.metadata.theme === "EMPTY CS") | ||||
| allFeatures = allFeatures.filter(f => f.properties.metadata.theme !== "EMPTY CS") | ||||
| 
 | ||||
| new Histogram(emptyCS.map(f => f.properties.date)).keyToDate().asBar({ | ||||
|     name: "Empty changesets by date" | ||||
| }).render() | ||||
| 
 | ||||
| 
 | ||||
| const geojson = { | ||||
|     type: "FeatureCollection", | ||||
|     features: allFeatures.map(f => { | ||||
|         try { | ||||
|             return GeoOperations.centerpoint(f.geometry); | ||||
|         } catch (e) { | ||||
|             console.error("Could not create center point: ", e, f) | ||||
|         } | ||||
|     }) | ||||
| } | ||||
| 
 | ||||
| writeFileSync("centerpoints.geojson", JSON.stringify(geojson, undefined, 2)) | ||||
| 
 | ||||
| 
 | ||||
| createMiscGraphs(allFeatures, emptyCS) | ||||
| createGraphs(allFeatures, "") | ||||
| createGraphs(allFeatures.filter(f => f.properties.date.startsWith("2020")), " in 2020") | ||||
| createGraphs(allFeatures.filter(f => f.properties.date.startsWith("2021")), " in 2021") | ||||
|  |  | |||
							
								
								
									
										
											BIN
										
									
								
								Docs/Tools/graphs/Changesets per day (line) in 2020.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 293 KiB | 
							
								
								
									
										
											BIN
										
									
								
								Docs/Tools/graphs/Changesets per day (line) in 2021.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 650 KiB | 
							
								
								
									
										
											BIN
										
									
								
								Docs/Tools/graphs/Changesets per day (line).png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 329 KiB | 
| Before Width: | Height: | Size: 273 KiB After Width: | Height: | Size: 269 KiB | 
| Before Width: | Height: | Size: 231 KiB After Width: | Height: | Size: 228 KiB | 
| Before Width: | Height: | Size: 518 KiB After Width: | Height: | Size: 562 KiB | 
| Before Width: | Height: | Size: 546 KiB After Width: | Height: | Size: 586 KiB | 
| Before Width: | Height: | Size: 707 KiB After Width: | Height: | Size: 769 KiB | 
| Before Width: | Height: | Size: 731 KiB After Width: | Height: | Size: 787 KiB | 
| Before Width: | Height: | Size: 445 KiB After Width: | Height: | Size: 485 KiB | 
| Before Width: | Height: | Size: 468 KiB After Width: | Height: | Size: 502 KiB | 
| Before Width: | Height: | Size: 481 KiB After Width: | Height: | Size: 525 KiB | 
| Before Width: | Height: | Size: 533 KiB After Width: | Height: | Size: 571 KiB | 
| Before Width: | Height: | Size: 140 KiB After Width: | Height: | Size: 144 KiB | 
| Before Width: | Height: | Size: 147 KiB After Width: | Height: | Size: 132 KiB | 
| Before Width: | Height: | Size: 148 KiB After Width: | Height: | Size: 149 KiB | 
| Before Width: | Height: | Size: 123 KiB After Width: | Height: | Size: 124 KiB | 
| Before Width: | Height: | Size: 110 KiB After Width: | Height: | Size: 116 KiB | 
| Before Width: | Height: | Size: 110 KiB After Width: | Height: | Size: 116 KiB | 
| Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 100 KiB |