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() |     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() | pyplot_init() | ||||||
| title = sys.argv[1] | title = sys.argv[1] | ||||||
| pyplot.title = title | pyplot.title = title | ||||||
|  | @ -59,5 +68,8 @@ while (True): | ||||||
|         createPie(options) |         createPie(options) | ||||||
|     elif (options["plot"]["type"] == "bar"): |     elif (options["plot"]["type"] == "bar"): | ||||||
|         createBar(options) |         createBar(options) | ||||||
|  |     elif (options["plot"]["type"] == "line"): | ||||||
|  |         createLine(options) | ||||||
|     else: |     else: | ||||||
|         print("Unkown type: " + options.type) |         print("Unkown type: " + options.type) | ||||||
|  | print("Plot generated") | ||||||
|  | @ -179,7 +179,7 @@ interface PlotSpec { | ||||||
|     name: string, |     name: string, | ||||||
|     interpetKeysAs: "date" | "number" | "string" | string |     interpetKeysAs: "date" | "number" | "string" | string | ||||||
|     plot: { |     plot: { | ||||||
|         type: "pie" | "bar" |         type: "pie" | "bar" | "line" | ||||||
|         count: { key: string, value: number }[] |         count: { key: string, value: number }[] | ||||||
|     } | { |     } | { | ||||||
|         type: "stacked-bar" |         type: "stacked-bar" | ||||||
|  | @ -196,6 +196,7 @@ interface PlotSpec { | ||||||
| function createGraph( | function createGraph( | ||||||
|     title: string, |     title: string, | ||||||
|     ...options: PlotSpec[]) { |     ...options: PlotSpec[]) { | ||||||
|  |     console.log("Creating graph",title,"...") | ||||||
|     const process = exec("python3 GenPlot.py \"graphs/" + title + "\"", ((error, stdout, stderr) => { |     const process = exec("python3 GenPlot.py \"graphs/" + title + "\"", ((error, stdout, stderr) => { | ||||||
|         console.log("Python: ", stdout) |         console.log("Python: ", stdout) | ||||||
|         if (error !== null) { |         if (error !== null) { | ||||||
|  | @ -207,7 +208,8 @@ function createGraph( | ||||||
|     })) |     })) | ||||||
| 
 | 
 | ||||||
|     for (const option of options) { |     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) |     process.stdin._write("\n", "utf-8", undefined) | ||||||
| 
 | 
 | ||||||
|  | @ -229,6 +231,7 @@ class Histogram<K> { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public bump(key: K, increase = 1) { |     public bump(key: K, increase = 1) { | ||||||
|  | 
 | ||||||
|         if (this.counts.has(key)) { |         if (this.counts.has(key)) { | ||||||
|             this.counts.set(key, increase + this.counts.get(key)) |             this.counts.set(key, increase + this.counts.get(key)) | ||||||
|         } else { |         } else { | ||||||
|  | @ -324,6 +327,20 @@ class Histogram<K> { | ||||||
|         return hist |         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: |      * Given a histogram: | ||||||
|      * 'a': 3 |      * 'a': 3 | ||||||
|  | @ -402,6 +419,15 @@ class Histogram<K> { | ||||||
|         return spec; |         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> { | class Group<K, V> { | ||||||
|  | @ -506,7 +532,8 @@ function createGraphs(allFeatures: ChangeSetData[], appliedFilterDescription: st | ||||||
|     hist |     hist | ||||||
|         .createOthersCategory("other", 20) |         .createOthersCategory("other", 20) | ||||||
|         .addCountToName() |         .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)) |     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), |             compare: (a, b) => Number(a) - Number(b), | ||||||
|             name: "Contributors per changeset count" + appliedFilterDescription |             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)) |     new Histogram<string>(allFeatures.map(f => f.properties.metadata.host)) | ||||||
|         .asPie({ |         .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") | const allPaths = readdirSync("stats") | ||||||
|     .filter(p => p.startsWith("stats.") && p.endsWith(".json")); |     .filter(p => p.startsWith("stats.") && p.endsWith(".json")); | ||||||
| let allFeatures: ChangeSetData[] = [].concat(...allPaths | 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") | const emptyCS = allFeatures.filter(f => f.properties.metadata.theme === "EMPTY CS") | ||||||
| allFeatures = 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({ | createMiscGraphs(allFeatures, emptyCS) | ||||||
|     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)) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| createGraphs(allFeatures, "") | createGraphs(allFeatures, "") | ||||||
| createGraphs(allFeatures.filter(f => f.properties.date.startsWith("2020")), " in 2020") | createGraphs(allFeatures.filter(f => f.properties.date.startsWith("2020")), " in 2020") | ||||||
| createGraphs(allFeatures.filter(f => f.properties.date.startsWith("2021")), " in 2021") | 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 |