feat: sankey diagram WIP
Mermaid ne supporte pas les lien circulaire ce qui rend l'usage impossible pour le momentwip-related-pages
parent
4a4311e829
commit
63482979fe
|
@ -16,4 +16,6 @@
|
|||
dist
|
||||
test-coverage
|
||||
node_modules
|
||||
# outptu stats files
|
||||
el-stats*.json
|
||||
el-stats*.txt
|
||||
|
|
|
@ -13,6 +13,8 @@ import { statsGeneralesDesc } from "./statistiques/v2/generales/StatsGenerales";
|
|||
import { computeStatsSociales } from "./statistiques/v2/sociales/computeStatsSociales";
|
||||
import { statsSocialesDesc } from "./statistiques/v2/sociales/StatsSociales";
|
||||
import { checkDbSchemas } from "./notion/fetch/schemaCheck/checkDbSchemas";
|
||||
import { computeSequencEvtPenalSankeyData } from "./statistiques/v2/penales/computeSankeyData";
|
||||
import { sankeyDataToMermaidDiagram } from "./statistiques/v2/sankey/sankeyDataToMermaidDiagram";
|
||||
|
||||
type ProcessOptions = {
|
||||
dryRun: boolean;
|
||||
|
@ -82,6 +84,11 @@ function buildProcessOptions(): ProcessOptions {
|
|||
const statsPenales = computeStatsPenales(familles);
|
||||
const statsSociales = computeStatsSociales(familles);
|
||||
|
||||
console.log("Sankey Diagram:");
|
||||
writeFileSync(
|
||||
"./el-stats-penal-sankey-diagram.txt",
|
||||
sankeyDataToMermaidDiagram(computeSequencEvtPenalSankeyData(familles))
|
||||
);
|
||||
if (options.dryRun) {
|
||||
console.log(
|
||||
"Dry run => Skip Publishing. Stats are dumped in file el-stats-xxx.json"
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { EvenementFamille } from "../../../data/EvenementFamille";
|
||||
import { Famille } from "../../../data/Famille";
|
||||
import { SankeyData } from "../sankey/SankeyData";
|
||||
export function computeSequencEvtPenalSankeyData(familles: Famille[]) {
|
||||
|
@ -5,15 +6,35 @@ export function computeSequencEvtPenalSankeyData(familles: Famille[]) {
|
|||
|
||||
familles.forEach((f) => {
|
||||
// Compute all transitions, Events are already sorted
|
||||
for (let index = 0; index < f.Evenements.length - 1; index++) {
|
||||
const fromEvt = f.Evenements[index];
|
||||
const toEvt = f.Evenements[index + 1];
|
||||
const fromTransitions = sankeyData[fromEvt.Type] || {};
|
||||
sankeyData[fromEvt.Type] = {
|
||||
...fromTransitions,
|
||||
[toEvt.Type]: (fromTransitions[toEvt.Type] || 0) + 1,
|
||||
const nonNullEvents = f.Evenements.filter(
|
||||
(evt) => evt.Type !== null && (evt.Type as string) !== "null"
|
||||
);
|
||||
|
||||
// remove duplicate types to avoid circular links that are not supported by mermaid
|
||||
const evenements = removeDuplicateTypes(nonNullEvents);
|
||||
|
||||
for (let index = 0; index < evenements.length - 1; index++) {
|
||||
const sourceEvt = evenements[index];
|
||||
const targetEvt = evenements[index + 1];
|
||||
const sourceSankeyData = sankeyData[sourceEvt.Type] || {};
|
||||
sankeyData[sourceEvt.Type] = {
|
||||
...sourceSankeyData,
|
||||
[targetEvt.Type]: (sourceSankeyData[targetEvt.Type] || 0) + 1,
|
||||
};
|
||||
}
|
||||
});
|
||||
return sankeyData;
|
||||
}
|
||||
|
||||
function removeDuplicateTypes(events: EvenementFamille[]): EvenementFamille[] {
|
||||
const seenTypes: { [key: string]: boolean } = {};
|
||||
return events.filter((evt) => {
|
||||
const type = evt.Type as string;
|
||||
if (seenTypes[type]) {
|
||||
return false;
|
||||
} else {
|
||||
seenTypes[type] = true;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
* Sankey diagram transitions
|
||||
*/
|
||||
export type SankeyData = {
|
||||
[fromKey: string]: {
|
||||
[toKey: string]: number;
|
||||
[source: string]: {
|
||||
[target: string]: number;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,5 +1,44 @@
|
|||
import { SankeyData } from "./SankeyData";
|
||||
|
||||
eport function sankeyDataToMermaidDiagram(sankeyData: SankeyData) {
|
||||
|
||||
export type SankeyDiagramOptions = { showValues: boolean };
|
||||
export function sankeyDataToMermaidDiagram(
|
||||
sankeyData: SankeyData,
|
||||
options: SankeyDiagramOptions = { showValues: true }
|
||||
) {
|
||||
const header = `---
|
||||
config:
|
||||
sankey:
|
||||
showValues: ${options.showValues}
|
||||
---
|
||||
sankey-beta
|
||||
`;
|
||||
return header + arrayToCsv(dataLines(sankeyData));
|
||||
}
|
||||
|
||||
function dataLines(sankeyData: SankeyData): Array<[string, string, number]> {
|
||||
return Object.entries(sankeyData).flatMap(([source, targets]) => {
|
||||
return Object.entries(targets).map<[string, string, number]>(
|
||||
([target, count]) => {
|
||||
return [source, target, count];
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function arrayToCsv(data: Array<Array<string | number>>): string {
|
||||
return data.map((dataLine) => dataLine.map(toCsvValue).join(",")).join("\n");
|
||||
}
|
||||
|
||||
function toCsvValue(value: string | number): string {
|
||||
if (typeof value === "number") {
|
||||
return value.toString();
|
||||
} else {
|
||||
// Mermaid does not seem to support diacritics
|
||||
const withoutDiacritics = value
|
||||
.normalize("NFD")
|
||||
.replace(/[\u0300-\u036f]/g, "");
|
||||
|
||||
const escaped = withoutDiacritics.replaceAll('"', '""');
|
||||
return `"${escaped}"`;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue