Skip to content

Instantly share code, notes, and snippets.

@thomasleveil
Last active August 3, 2019 20:07
Show Gist options
  • Save thomasleveil/01fb37df8deda76c4224f39741fa1c38 to your computer and use it in GitHub Desktop.
Save thomasleveil/01fb37df8deda76c4224f39741fa1c38 to your computer and use it in GitHub Desktop.
où sont passés mes revenus ?
<html>
<head>
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
<script type="text/javascript">
///////////////////////////////////////////////////////////////////////////////////////////
//
// Scrollez vers le bas, jusqu'à la définition de la fonction "drawChart()"
//
///////////////////////////////////////////////////////////////////////////////////////////
google.charts.load('current', { 'packages': ['sankey'] });
google.charts.setOnLoadCallback(drawChart);
const bucketStore = {}
class Bucket {
constructor(name) {
if (name in bucketStore) {
throw new Error(`bucket '${name}' alerady exist`)
}
this.name = name
this.in = 0
this.out = 0
bucketStore[name] = this
}
static get(name) {
if (name in bucketStore) {
return bucketStore[name]
} else {
return new Bucket(name)
}
}
get html() {
let valeur = null
if (this.in == 0 || this.out == 0) {
valeur = Math.abs(this.in - this.out)
} else if (this.in == this.out) {
valeur = this.in
}
if (valeur != null) {
return `
<div class="bucketInfo">
<div class="bucket-header">${this.name}</div>
<div class="bucket-body">
<b>${valeur.toFixed(2)} €</b
</div>
</div>
`
}
const diff = this.in - this.out
let diffHtml = ''
if (diff < 0) {
diffHtml = `<p class="negatif">${diff.toFixed(2)} €</p>`
} else if (diff > 0) {
diffHtml = `<p class="positif">${diff.toFixed(2)} €</p>`
}
return `
<div class="bucketInfo">
<div class="bucket-header">${this.name}</div>
<div class="bucket-body">
<ul>
<li><b>Entrées : </b>${this.in.toFixed(2)} €</li>
<li><b>Sorties : </b>${this.out.toFixed(2)} €</li>
</ul>
${diffHtml}
</div>
</div>
`
}
input(value) {
this.in += value
}
output(value) {
this.out += value
}
}
class Flow {
constructor(dataTable) {
this.data = dataTable
}
_mouvement_avec_category({ depuis, vers, montant, category }) {
// sur le sankey, ça se traduit par 2 lignes
this.data.addRow([depuis, vers, montant])
this.data.addRow([vers, category, montant])
Bucket.get(depuis).output(montant)
Bucket.get(vers).input(montant)
Bucket.get(vers).output(montant)
Bucket.get(category).input(montant)
}
_mouvement_sans_category({ depuis, vers, montant }) {
this.data.addRow([depuis, vers, montant])
Bucket.get(depuis).output(montant)
Bucket.get(vers).input(montant)
}
revenu({ nature, destination, montant, avec_pct_prelevement_a_la_source = null }) {
this._mouvement_sans_category({ depuis: nature, vers: destination, montant })
if (avec_pct_prelevement_a_la_source) {
// on ajoute au montant ce qui a déjà été prélevé à la source par les impôts
const pourcentage = avec_pct_prelevement_a_la_source
const valeur_impot = montant * pourcentage
this._mouvement_sans_category({ depuis: nature, vers: "Impôts et taxes", montant: valeur_impot })
}
}
impot({ nature, depuis, montant }) {
this._mouvement_avec_category({ depuis, vers: nature, montant, category: "Impôts et taxes" })
}
virement_interne({ nature, depuis, vers, montant }) {
// nature ne sert à rien, sauf à documenter le code
this._mouvement_sans_category({ depuis, vers, montant })
}
epargne({ depuis, vers, montant }) {
this._mouvement_avec_category({ depuis, vers, montant, category: "Epargne & Patrimoine" })
}
echeance_credit_investissement({ depuis, vers, montant }) {
this._mouvement_avec_category({ depuis, vers, montant, category: "Epargne & Patrimoine" })
}
echeance_credit_immo({ depuis, vers, montant }) {
this._mouvement_avec_category({ depuis, vers, montant, category: "Logement" })
}
loyer({ depuis, vers, montant }) {
this._mouvement_avec_category({ depuis, vers, montant, category: "Logement" })
}
prelevement_automatique({ depuis, nature, montant, category }) {
this._mouvement_avec_category({ depuis, vers: nature, montant, category })
}
depense({ depuis, nature, montant, category }) {
this._mouvement_avec_category({ depuis, vers: nature, montant, category })
}
}
function drawChart() {
var data = new google.visualization.DataTable();
data.addColumn('string', 'Depuis');
data.addColumn('string', 'Vers');
data.addColumn('number', 'Montant');
flow = new Flow(data);
//////////////////////////////////////////////////////////////////////////////////////
//
//
//
// Décrire ci-dessous les Depenses typiques d'un mois
//
//
//
//////////////////////////////////////////////////////////////////////////////////////
const CMB_CC = "Compte chèque de Germain"
const LIVRET_A_GERMAIN = "Livret A de Germain"
const CREDIT_AGRICOLE_CC = "Compte chèque de Jeannine"
const ASSURANCE_VIE_JEANNINE = "Assurance vie de Jeannine"
const CMB_COMPTE_COMMUN = "Compte commun au CMB avec carte bancaire"
const PRET_MAISON = "Remboursement prêt immobilier"
//////////// Germain //////////////////////
flow.revenu({ nature: "Salaire de Germain", montant: 1450, destination: CMB_CC, avec_pct_prelevement_a_la_source: 5 / 100 })
flow.revenu({ nature: "Location du garage", montant: 80, destination: CMB_CC })
flow.impot({ nature: "Impôt revenus fonciers", depuis: CMB_CC, montant: 40 / 12 })
flow.impot({ nature: "Prélèvements sociaux sur revenus fonciers", depuis: CMB_CC, montant: 10 / 12 })
flow.virement_interne({ nature: "Contribution de Germain au pôt commun", depuis: CMB_CC, vers: CMB_COMPTE_COMMUN, montant: 800 })
flow.epargne({ depuis: CMB_CC, vers: LIVRET_A_GERMAIN, montant: 50 })
flow.echeance_credit_immo({ depuis: CMB_CC, vers: PRET_MAISON, montant: 700 })
// flow.loyer({ depuis: CMB_CC, vers: "M. Dupont", montant: 500 })
flow.prelevement_automatique({ nature: "CMB frais de tenue de compte", depuis: CMB_CC, montant: 60 / 12, category: "Factures récurrentes" })
//////////// Jeannine //////////////////////
flow.revenu({ nature: "Salaire de Jeannine", destination: CREDIT_AGRICOLE_CC, montant: 1800, avec_pct_prelevement_a_la_source: 13 / 100 })
flow.revenu({ nature: "BlaBlaCar", montant: 80, destination: CREDIT_AGRICOLE_CC })
flow.virement_interne({ nature: "Contribution de Jeannine au pôt commun", depuis: CREDIT_AGRICOLE_CC, vers: CMB_COMPTE_COMMUN, montant: 900 })
flow.epargne({ depuis: CREDIT_AGRICOLE_CC, vers: ASSURANCE_VIE_JEANNINE, montant: 200 })
flow.echeance_credit_immo({ depuis: CREDIT_AGRICOLE_CC, vers: PRET_MAISON, montant: 700 })
flow.prelevement_automatique({ nature: "CA frais de tenue de compte", depuis: CREDIT_AGRICOLE_CC, montant: 40 / 12, category: "Factures récurrentes" })
//////////// Autres //////////////////////
flow.revenu({ nature: "Vente de légumes aux voisins", montant: 20, destination: "Tirelire" })
flow.revenu({ nature: "Vente des conneries du grenier sur Le Bon Coin", montant: 50, destination: "Tirelire" })
flow.impot({ nature: "Taxe d'habitation", depuis: CMB_COMPTE_COMMUN, montant: 1200 / 12 })
flow.impot({ nature: "Contribution à l'audiovisuel public", depuis: CMB_COMPTE_COMMUN, montant: 139 / 12 })
flow.depense({ nature: "Courses", depuis: CMB_COMPTE_COMMUN, montant: 800, category: "dépenses quotidiennes" })
flow.depense({ nature: "Carburant", depuis: CMB_COMPTE_COMMUN, montant: 270, category: "dépenses quotidiennes" })
flow.depense({ nature: "Sorties / Loisirs", depuis: CMB_COMPTE_COMMUN, montant: 150, category: "dépenses quotidiennes" })
flow.prelevement_automatique({ nature: "Free Mobile", depuis: CMB_COMPTE_COMMUN, montant: 19.99, category: "Factures récurrentes" })
flow.prelevement_automatique({ nature: "SFR Mobile", depuis: CMB_COMPTE_COMMUN, montant: 15, category: "Factures récurrentes" })
flow.prelevement_automatique({ nature: "SFR ADSL", depuis: CMB_COMPTE_COMMUN, montant: 15, category: "Factures récurrentes" })
flow.prelevement_automatique({ nature: "gaz", depuis: CMB_COMPTE_COMMUN, montant: 35, category: "Factures récurrentes" })
flow.prelevement_automatique({ nature: "électricité", depuis: CMB_COMPTE_COMMUN, montant: 40, category: "Factures récurrentes" })
flow.prelevement_automatique({ nature: "eau", depuis: CMB_COMPTE_COMMUN, montant: 70, category: "Factures récurrentes" })
flow.prelevement_automatique({ nature: "Canal+ sport", depuis: CMB_COMPTE_COMMUN, montant: 25, category: "Factures récurrentes" })
flow.prelevement_automatique({ nature: "assurance voiture", depuis: CMB_COMPTE_COMMUN, montant: 39.15, category: "Factures récurrentes" })
flow.prelevement_automatique({ nature: "assurance voiture", depuis: CMB_COMPTE_COMMUN, montant: 46.85, category: "Factures récurrentes" })
flow.prelevement_automatique({ nature: "mutuelle santé complémentaire", depuis: CMB_COMPTE_COMMUN, montant: 12.25, category: "Factures récurrentes" })
/////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////
// Instantiates and draws our chart, passing in some options.
var chart = new google.visualization.Sankey(document.getElementById('sankey_basic'));
google.visualization.events.addListener(chart, 'error', event => {
console.error(event, data)
console.dir(data.wg.map(({ c: [{ v: source }, { v: dest }, { v: weight }] }) => ({ source, dest, weight })))
})
chart.draw(data, {
width: 1024,
sankey: {
node: {
width: 30,
nodePadding: 15,
interactivity: true
},
iterations: 512
}
});
google.visualization.events.addListener(chart, 'select', event => {
const selection = chart.getSelection()
if (selection.length == 0) {
document.getElementById('info').innerHTML = ''
} else {
const bucket = Bucket.get(selection[0].name)
document.getElementById('info').innerHTML = bucket.html
}
})
google.visualization.events.addListener(chart, 'onmouseover', ({ name }) => {
if (name == undefined) {
document.getElementById('info').innerHTML = ''
} else {
const bucket = Bucket.get(name)
document.getElementById('info').innerHTML = bucket.html
}
})
}
</script>
<style>
#info {
position: absolute;
top: 10px;
left: 10px;
width: 300px;
z-index: 1000;
background-color: white;
}
.bucketInfo {
background-color: #6699ee24;
padding: 5px;
font-size: 10pt;
font-family: sans-serif;
}
.bucket-header {
font-size: 14pt;
}
.bucket-body .positif {
color: green;
}
.bucket-body .negatif {
color: darkorange;
}
</style>
</head>
<body>
<div style="position: relative">
<div id="info" />
</div>
<div id="sankey_basic" style="width: 1024px; height: 700px;"></div>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment