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 |