Browse Source
J'ai essayé de séparer une partie "graph" (plus générale) d'une partie "statistiques" plus particulière à ce graphique particulier, mais ce n'est pas évident. Le fichier prive/stats/visites charge les JS et CSS de d3 et pour notre graphique. Il crée alors de graphique des visites. Pour cela, la fonction `spip_d3_statistiques_create(id)` va - créer un objet Spip_d3_graph() notamment en indiquant la locale désirée. Cet objet est stocké dans la balise #id par un jQuery .data() pour pouvoir le réutiliser ensuite (pour ré-actualiser le graphique avec d'autres données) - déclarer comment seront préparées et traitées les données JSON reçues (ici on remplit les dates manquantes, et on actualise le tableau html et le graphique svg) - localiser les dates de d3 avec la locale demandé - préparer le code html et svg nécessaire (appelle la fonction spip_d3_statistiques_prepare_graph() qui va préparer les axes, histogramme, ligne, etc, mais sans connaître encore les données) - executer graph.updateJson(), qui va télécharger le json de données, et appliquer dessus les traitements déclarés, ce qui va in fine exécuter spip_d3_statistiques_update_graph() qui dessinera le graphique) Une navigation au dessus du graphique permet de basculer sur différentes dates et collections de données : 3 mois (90 jours), 2 ans (730 jours), 5 ans (60 mois) et infini (en années). Jusqu'à 2 ans, les données retournées par SPIP sont quotidiennes. En demandant 5 ans, on obtient 60 mois (avec la somme cumulée des visites sur le premier jour du mois) ; en demandant infini (durée -1), on obtient des visites annuelles : ce choix n'est pas anodin car il correspond très exactement à ce qui est proposé en archivage des statistiques : les statistiques de plus de 2 ans sont regroupées en début de mois, et de plus de 5 ans en début d'année. De la sorte, il devrait être possible de mettre une tache périodique qui archive systématiquement les statistiques, pour alléger la base de données, tout en gardant un affichage des graphiques a peu près corrects, même si on perd une partie de l'information.pull/4/head
8 changed files with 892 additions and 253 deletions
@ -0,0 +1,194 @@
|
||||
|
||||
/* |
||||
.line { |
||||
fill: none; |
||||
stroke: steelblue; |
||||
stroke-width: 2px; |
||||
}*/ |
||||
|
||||
.spip_d3_graph { |
||||
--spip-d3--theme-color--light: var(--spip-color-theme-light, #999); |
||||
--spip-d3--theme-color: var(--spip-color-theme, #666); |
||||
--spip-d3--theme-color--dark: var(--spip-color-theme-dark, #444); |
||||
|
||||
--spip-d3--text-color: #444; |
||||
--spip-d3--border-color: #ddd; |
||||
--spip-d3--background-color: #fafafa; |
||||
|
||||
--spip-d3--loader-color: var(--spip-d3--theme-color); |
||||
--spip-d3--loader-color--loading: var(--spip-d3--theme-color--dark); |
||||
|
||||
--spip-d3--line-color: var(--spip-d3--theme-color); |
||||
--spip-d3--line-color--primary: var(--spip-d3--theme-color); |
||||
--spip-d3--line-color--secondary: hsl(calc(var(--spip-color-theme--h) + 120), var(--spip-color-theme--s), var(--spip-color-theme--l)); |
||||
--spip-d3--line-color--tertiary: hsl(calc(var(--spip-color-theme--h) + 240), var(--spip-color-theme--s), var(--spip-color-theme--l)); |
||||
|
||||
--spip-d3--bar-color: var(--spip-d3--theme-color--light); |
||||
--spip-d3--bar-color--hover: var(--spip-d3--theme-color); |
||||
|
||||
--spip-d3--area-color--low: #fff; |
||||
--spip-d3--area-color--high: var(--spip-d3--theme-color); |
||||
|
||||
--spip-d3--grid-color: #ddd; |
||||
} |
||||
|
||||
.spip_d3_graph { |
||||
display: grid; |
||||
place-items: center; |
||||
border: 1px solid var(--spip-d3--border-color); |
||||
background-color: var(--spip-d3--background-color); |
||||
margin-bottom: 3em; |
||||
} |
||||
.spip_d3_graph > * { |
||||
grid-area: 1/1; |
||||
} |
||||
|
||||
/** ratio */ |
||||
.spip_d3_graph_ratio {} |
||||
/** loader */ |
||||
.spip_d3_graph_loader { |
||||
color: var(--spip-d3--loader-color); |
||||
transition: width .3s, height .3s, color .3s; |
||||
width: 60px; |
||||
height: 60px; |
||||
display: none; |
||||
z-index: 1000; |
||||
} |
||||
/** when loading */ |
||||
.spip_d3_graph--loading .spip_d3_graph_loader { |
||||
display: block; |
||||
} |
||||
/** graphique */ |
||||
.spip_d3_graph_inner { |
||||
width: 100%; |
||||
position: relative; /** for tooltip */ |
||||
} |
||||
|
||||
/* ===================== */ |
||||
.spip_d3_svg {} |
||||
.spip_d3_svg_title { |
||||
text-anchor: middle; |
||||
fill: var(--spip-d3--text-color); |
||||
} |
||||
|
||||
.spip_d3_svg_grid line { |
||||
stroke: var(--spip-d3--grid-color); |
||||
shape-rendering: crispEdges; |
||||
stroke-opacity: 0.7; |
||||
} |
||||
.spip_d3_svg_grid path { |
||||
stroke-width: 0; |
||||
} |
||||
.spip_d3_svg_grid--horizontal line { |
||||
stroke-opacity: 0.7; |
||||
} |
||||
.spip_d3_svg_grid--vertical line { |
||||
stroke-opacity: 0.5; |
||||
} |
||||
|
||||
|
||||
|
||||
.spip_d3_svg_area { |
||||
opacity: 0.5; |
||||
} |
||||
|
||||
.spip_d3_svg_line { |
||||
fill: none; |
||||
stroke: var(--spip-d3--line-color); |
||||
stroke-width: 2; |
||||
stroke-miterLimit: 1; |
||||
} |
||||
.spip_d3_svg_line--average { |
||||
stroke: var(--spip-d3--line-color--secondary); |
||||
} |
||||
|
||||
.spip_d3_svg_histogram {} |
||||
.spip_d3_svg_bar { |
||||
fill: var(--spip-d3--bar-color); |
||||
transition: fill .3s; |
||||
} |
||||
.spip_d3_svg_bar--hover, |
||||
.spip_d3_svg_bar:hover { |
||||
fill: var(--spip-d3--bar-color--hover); |
||||
} |
||||
|
||||
|
||||
.spip_d3_svg_overlay { |
||||
fill: none; |
||||
pointer-events: all; |
||||
} |
||||
|
||||
/* ===================== */ |
||||
.spip_d3_tooltip { |
||||
position: absolute; |
||||
opacity: 0; |
||||
border-radius: 5px; |
||||
background-color: var(--spip-color-theme-lightest); |
||||
border: 1px solid var(--spip-color-theme); |
||||
} |
||||
.spip_d3_tooltip_list { |
||||
list-style-type: none; |
||||
margin: 0; |
||||
padding: .5em; |
||||
} |
||||
.spip_d3_tooltip_label { |
||||
font-weight: bold; |
||||
color: var(--spip-color-theme-darker); |
||||
padding-right: .5em; |
||||
} |
||||
.spip_d3_tooltip_value { |
||||
|
||||
} |
||||
|
||||
|
||||
|
||||
|
||||
/* ====================== */ |
||||
.spip_d3_table { |
||||
margin-bottom: 2em; |
||||
} |
||||
|
||||
/** tables responsives */ |
||||
@media (max-width: 760px) { |
||||
|
||||
.spip_table--responsive, |
||||
.spip_table--responsive thead, |
||||
.spip_table--responsive tbody, |
||||
.spip_table--responsive th, |
||||
.spip_table--responsive td, |
||||
.spip_table--responsive tr { |
||||
display: block; |
||||
} |
||||
|
||||
.spip_table--responsive thead tr { |
||||
border: none; |
||||
clip: rect(0 0 0 0); |
||||
height: 1px; |
||||
margin: -1px; |
||||
overflow: hidden; |
||||
padding: 0; |
||||
position: absolute; |
||||
width: 1px; |
||||
} |
||||
|
||||
.spip_table--responsive tr { |
||||
border-top: 1px solid #ccc; |
||||
border-bottom: 1px solid #ccc; |
||||
} |
||||
.spip_table--responsive tr + tr { |
||||
margin-top: 1em; |
||||
} |
||||
.spip_table--responsive td { |
||||
text-align: right; |
||||
} |
||||
table.spip_table--responsive td { |
||||
border-top: none; |
||||
border-bottom: 1px solid rgba(0, 0, 0, .08); |
||||
} |
||||
|
||||
.spip_table--responsive td:before { |
||||
float: left; |
||||
font-weight: bold; |
||||
content: attr(data-label); |
||||
} |
||||
} |
@ -0,0 +1,14 @@
|
||||
|
||||
|
||||
.spip_d3_statistiques { |
||||
width: 100%; |
||||
min-height: 400px; |
||||
padding: 1em; |
||||
box-sizing: border-box; |
||||
} |
||||
.spip_d3_statistiques--visites_quotidiennes {} |
||||
|
||||
|
||||
table.spip.spip_table--statistiques { |
||||
margin-bottom: 3em; |
||||
} |
@ -0,0 +1,301 @@
|
||||
|
||||
/** |
||||
* Affiche le graphique ou le tableau |
||||
*
|
||||
* @param node btn html du bouton |
||||
* @param string id identifiant du graphique |
||||
* @param string to 'svg' ou 'table' |
||||
*/ |
||||
function spip_d3_statistiques_toggle_svg_table(btn, id, to) { |
||||
jQuery(btn).parent().find('.bouton').removeClass('principal'); |
||||
jQuery(btn).addClass('principal'); |
||||
if (to === 'table') { |
||||
jQuery(id).find('.spip_d3_svg').hide().end().find('.spip_d3_table').show(); |
||||
} else { |
||||
jQuery(id).find('.spip_d3_table').hide().end().find('.spip_d3_svg').show(); |
||||
} |
||||
const url = parametre_url(window.document.location.href, 'vue', to); |
||||
window.history.replaceState({}, window.document.title, url); |
||||
} |
||||
|
||||
/** |
||||
* Recharge le graphique avec cette url json |
||||
*
|
||||
* @param node btn html du bouton |
||||
* @param string $id identifiant du graphique |
||||
*/ |
||||
function spip_d3_statistiques_load_json(btn, id) { |
||||
jQuery(btn).parent().find('.bouton').removeClass('principal'); |
||||
jQuery(btn).addClass('principal'); |
||||
const json = btn.dataset.json; |
||||
const json_auteur = btn.dataset.jsonAuteur; |
||||
const csv_auteur = btn.dataset.csvAuteur; |
||||
//const csv_auteur = parametre_url(json_auteur, 'page', 'statistiques.csv');
|
||||
jQuery(btn).closest('.statistiques-nav').find('.btn--stats-json').attr('href', json_auteur); |
||||
jQuery(btn).closest('.statistiques-nav').find('.btn--stats-csv').attr('href', csv_auteur); |
||||
|
||||
const url = parametre_url(window.document.location.href, 'graph', btn.dataset.graph); |
||||
window.history.replaceState({}, window.document.title, url); |
||||
|
||||
document.querySelector(id).dataset.json = json; |
||||
const graph = jQuery(id).data('graph'); |
||||
|
||||
graph.updateJson(); |
||||
} |
||||
|
||||
function spip_d3_statistiques_prepare_graph(id, visible = true) { |
||||
|
||||
const inner = d3.select(id).select('.spip_d3_graph_inner'); |
||||
|
||||
const dimensions = { |
||||
height: 400, |
||||
width: 800, |
||||
margin: { |
||||
top: 50,
|
||||
right: 20,
|
||||
bottom: 20,
|
||||
left: 60 |
||||
} |
||||
}; |
||||
dimensions.inner = { |
||||
width: dimensions.width - dimensions.margin.left - dimensions.margin.right, |
||||
height: dimensions.height - dimensions.margin.top - dimensions.margin.bottom, |
||||
} |
||||
|
||||
const modele = {} |
||||
modele.dimensions = dimensions; |
||||
modele.parseTime = d3.timeParse("%Y-%m-%d"); |
||||
modele.dateFormat = d3.timeFormat("%d %B %Y"); |
||||
|
||||
const x = d3.scaleTime().range([0, dimensions.inner.width ]); |
||||
const y = d3.scaleLinear().range([dimensions.inner.height, 0]); |
||||
modele.x = x; |
||||
modele.y = y; |
||||
|
||||
modele.xAxis = g => g |
||||
.attr("transform", "translate(0, " + dimensions.inner.height + ")") |
||||
.call(d3.axisBottom(x).ticks(dimensions.width / 80)/*.tickSizeOuter(0)*/) |
||||
|
||||
modele.yAxis = g => g |
||||
.call(d3.axisLeft(y).ticks(dimensions.inner.height / 40)); |
||||
|
||||
modele.line = d3.line() |
||||
.curve(d3.curveBasis) |
||||
.x(d => modele.x(d.date)) |
||||
.y(d => modele.y(d.moyenne_mobile)) |
||||
|
||||
// Create one bin per month, use an offset to include the first and last months
|
||||
modele.histogram = d3.bin() |
||||
.value(d => d.date); |
||||
|
||||
modele.rollingSum = (data, windowSize = 7) => { |
||||
const summed = data.map((d, i) => { |
||||
const start = Math.max(0, i - windowSize) |
||||
const end = i + 1; |
||||
//const sum = { ...d };
|
||||
//sum.visites = Math.round(d3.sum(data.slice(start, end), d => d.visites) / (end - start));
|
||||
d.moyenne_mobile = Math.round(d3.sum(data.slice(start, end), d => d.visites) / (end - start)); |
||||
return d; |
||||
}) |
||||
return summed; |
||||
} |
||||
|
||||
const svg_outer = inner |
||||
.append('svg') |
||||
.attr("viewBox", `0 0 ${dimensions.width} ${dimensions.height}`) |
||||
.attr("class", "spip_d3_svg"); |
||||
|
||||
if (!visible) { |
||||
svg_outer.style("display", "none"); |
||||
} |
||||
|
||||
const svg = svg_outer |
||||
.append("g") |
||||
.attr("class", "spip_d3_svg_inner") |
||||
.attr("transform", "translate(" + dimensions.margin.left + ", " + dimensions.margin.top + ")"); |
||||
svg |
||||
.append("g") |
||||
.attr("class", "spip_d3_svg_grid spip_d3_svg_grid--horizontal") |
||||
.call( |
||||
d3.axisLeft(modele.y) |
||||
.ticks(dimensions.inner.height / 40) |
||||
.tickSize(-dimensions.inner.width) |
||||
.tickFormat("") |
||||
) |
||||
|
||||
const tooltip = inner |
||||
.append("div") |
||||
.attr("class", "spip_d3_tooltip") |
||||
.style("opacity", 0); |
||||
tooltip |
||||
.append("ul") |
||||
.attr("class", "spip_d3_tooltip_list"); |
||||
|
||||
|
||||
modele.tooltip = { |
||||
show: () => { |
||||
tooltip |
||||
.transition() |
||||
.duration(200) |
||||
.style("opacity", 1) |
||||
}, |
||||
hide: () => { |
||||
tooltip |
||||
.transition() |
||||
.duration(200) |
||||
.style("opacity", 0) |
||||
}, |
||||
empty: () => { |
||||
tooltip.select('ul').html(""); |
||||
}, |
||||
add: (key, label, value) => { |
||||
const item = tooltip.select('ul') |
||||
.append('li') |
||||
.attr("class", "spip_d3_tooltip_data spip_d3_tooltip_data--" + key); |
||||
item |
||||
.append("strong") |
||||
.attr("class", "spip_d3_tooltip_label") |
||||
.text(label + " :"); |
||||
item |
||||
.append("span") |
||||
.attr("class", "spip_d3_tooltip_value") |
||||
.text(value); |
||||
}, |
||||
update: (event) => { |
||||
const data = modele.data; |
||||
const xp = d3.pointer(event)[0]; |
||||
const x0 = x.invert(xp); |
||||
const i = d3.bisector(d => d.date).left(data, x0); |
||||
d = data[i - 1]; |
||||
|
||||
const pos = inner.node().getBoundingClientRect(); |
||||
tooltip.style("top", (event.clientY - pos.y - 30) + "px"); |
||||
|
||||
if (xp < dimensions.inner.width / 2) { |
||||
tooltip.style("right", "").style("left", (event.clientX - pos.x + 30) + "px"); |
||||
} else { |
||||
tooltip.style("left", "").style("right", (pos.right - event.clientX + 30) + "px"); |
||||
} |
||||
modele.tooltip.empty(); |
||||
if (d) { |
||||
modele.tooltip.add('date', modele.meta.columns.date, d.label); |
||||
modele.tooltip.add('visites', modele.meta.columns.visites, d.visites); |
||||
modele.tooltip.add('moyenne', 'Moyenne mobile', d.moyenne_mobile); |
||||
} else { |
||||
modele.tooltip.add('date', modele.meta.columns.date, '?'); |
||||
} |
||||
} |
||||
} |
||||
|
||||
svg |
||||
.append("rect") |
||||
.attr("class", "spip_d3_svg_overlay") |
||||
.attr("width", modele.dimensions.inner.width) |
||||
.attr("height", modele.dimensions.inner.height) |
||||
.on("mouseover", modele.tooltip.show) |
||||
.on("mouseout", modele.tooltip.hide) |
||||
.on("mousemove", modele.tooltip.update); |
||||
|
||||
svg |
||||
.append('g') |
||||
.attr("class", "spip_d3_svg_histogram") |
||||
.on("mouseover", modele.tooltip.show) |
||||
.on("mouseout", modele.tooltip.hide) |
||||
.on("mousemove", modele.tooltip.update); |
||||
|
||||
svg |
||||
.append('path') |
||||
.attr("class", "spip_d3_svg_line spip_d3_svg_line--average"); |
||||
|
||||
svg |
||||
.append('g') |
||||
.attr("class", "spip_d3_svg_xaxis"); |
||||
|
||||
svg |
||||
.append('g') |
||||
.attr("class", "spip_d3_svg_yaxis") |
||||
|
||||
svg.datum(modele); |
||||
return svg; |
||||
} |
||||
|
||||
|
||||
function spip_d3_statistiques_update_graph(id, _data) { |
||||
|
||||
if (d3.select(id).select('.spip_d3_svg_inner').empty()) { |
||||
spip_d3_statistiques_prepare_graph(id); |
||||
} |
||||
|
||||
const svg = d3.select(id).select('.spip_d3_svg_inner'); |
||||
const modele = svg.datum(); |
||||
const x = modele.x; |
||||
const y = modele.y; |
||||
|
||||
const data = _data.data; |
||||
const meta = _data.meta; |
||||
modele.data = data; // pour tooltip
|
||||
modele.meta = meta; // pour tooltip
|
||||
|
||||
// format the data
|
||||
data.forEach(d => { |
||||
d.label = d.date; |
||||
d.date = new Date(String(d.date)); |
||||
d.visites = +d.visites; |
||||
}); |
||||
modele.rollingSum(data); |
||||
|
||||
x.domain(d3.extent(data, d => d.date)); |
||||
|
||||
modele.histogram.domain(x.domain()) |
||||
|
||||
if (meta.unite === 'day') { |
||||
modele.histogram.thresholds(x.ticks(d3.timeDay)); |
||||
} else if (meta.unite === 'month') { |
||||
modele.histogram.thresholds(x.ticks(d3.timeMonth)); |
||||
} else if (meta.unite === 'year') { |
||||
modele.histogram.thresholds(x.ticks(d3.timeYear)); |
||||
} else { |
||||
throw "meta.unite not in day|month|year"; |
||||
} |
||||
|
||||
// group the data for the bars
|
||||
const bins = modele.histogram(data); |
||||
|
||||
// format the sum
|
||||
bins.forEach((d, i) => { |
||||
d.visites = +0; |
||||
d.moyenne_mobile = +0; |
||||
if (d.length) { |
||||
for (let j = 0; j < d.length; j++) { |
||||
d.visites += d[j].visites; |
||||
d.moyenne_mobile += d[j].moyenne_mobile; |
||||
} |
||||
} |
||||
d.label = d[0].label; |
||||
d.date = d[0].date; |
||||
bins[i] = d; |
||||
}); |
||||
|
||||
y.domain([0, d3.max(bins, d => d.visites)]); |
||||
|
||||
svg |
||||
.select('.spip_d3_svg_histogram') |
||||
.selectAll("rect") |
||||
.data(bins) |
||||
.join('rect') |
||||
.attr("class", "spip_d3_svg_bar spip_d3_svg_bar--primary") |
||||
.attr("x", 1) |
||||
.attr("transform", d => "translate(" + x(d.x0) + "," + y(d.visites) + ")") |
||||
.attr("width", d => x(d.x1) - x(d.x0) - .5) |
||||
.attr("height", d => modele.dimensions.inner.height - y(d.visites)) |
||||
|
||||
svg |
||||
.select('.spip_d3_svg_line') |
||||
.datum(data) |
||||
.attr("d", modele.line); |
||||
|
||||
svg.select('.spip_d3_svg_xaxis').call(modele.xAxis); |
||||
svg.select('.spip_d3_svg_yaxis').call(modele.yAxis); |
||||
|
||||
|
||||
} |
@ -1,130 +1,90 @@
|
||||
<!--[if IE]>[<script type="text/javascript" src="(#CHEMIN{javascript/excanvas.js})"></script>]<![endif]--> |
||||
[<script type="text/javascript" src="(#CHEMIN{javascript/jquery.flot.js})"></script>] |
||||
[<script type="text/javascript" src="(#CHEMIN{javascript/jquery.flot.selection.js})"></script>] |
||||
[<script type="text/javascript" src="(#CHEMIN{javascript/jquery.flot.time.js})"></script>] |
||||
[<script type="text/javascript" src="(#CHEMIN{javascript/jquery.tflot.js})"></script>] |
||||
|
||||
[<link rel="stylesheet" type="text/css" href="(#CHEMIN{css/spip_d3_graph.css})" />] |
||||
[<link rel="stylesheet" type="text/css" href="(#CHEMIN{css/spip_d3_statistiques.css})" />] |
||||
|
||||
[<script type="text/javascript" src="(#CHEMIN{lib/d3/d3.min.js})"></script>] |
||||
[<script type="text/javascript" src="(#CHEMIN{javascript/spip_d3_graph.js})"></script>] |
||||
[<script type="text/javascript" src="(#CHEMIN{javascript/spip_d3_statistiques.js})"></script>] |
||||
|
||||
<script type="text/javascript"> |
||||
|
||||
function trace_stats_table(table, classes, options) { |
||||
$table = $(table); |
||||
if ($table.is(':hidden')) { |
||||
return true; // pas a faire ou deja fait. |
||||
|
||||
function spip_d3_statistiques_create(id, options = {}) { |
||||
if (jQuery(id).data('graph')) { |
||||
return; |
||||
} |
||||
const $nav = jQuery(id).parent().find('.statistiques-nav'); |
||||
if ($nav.find('.groupe-boutons--stats-graph .bouton.principal').length) { |
||||
jQuery(id)[0].dataset.json = $nav.find('.groupe-boutons--stats-graph .bouton.principal').data('json'); |
||||
} |
||||
const table_visible = !!$nav.find('.btn--stats-to-table.principal').length; |
||||
|
||||
// copier le titre des tableaux |
||||
titre = $table.find("caption").text(); |
||||
$table.siblings('.pagination').before("<h3 class='caption'>" + titre + "</h3>"); |
||||
$table |
||||
|
||||
.wrap("<div class='" + classes + "'></div>"); |
||||
|
||||
// mettre les visites avec un fond colore pour le graphique |
||||
$table.find("thead th:eq(1)").data({fill: true, serie: 'bar', color: '#FFD845',lineWidth:0}); |
||||
$table.find("thead th:eq(2)").data({serie: 'line', color: '#7FC4FF'}); |
||||
$table.find("thead th:eq(3)").data({fill: true, serie: 'bar', color: '#A9DD3A',lineWidth:0}); |
||||
|
||||
// mettre les previsions en premier |
||||
// (pour que les autres graph passent par dessus) |
||||
$table.find('thead tr th:first-child').after(function(){ |
||||
return $(this).parent().find('th:eq(3)').detach(); |
||||
}); |
||||
$table.find('tbody tr th:first-child').after(function(){ |
||||
return $(this).parent().find('td:last-child').detach(); |
||||
const graph = new Spip_d3_graph(id, { |
||||
language: '#ENV{lang}', |
||||
d3_directory: '[(#CHEMIN{lib/d3/d3.min.js}|dirname)]', |
||||
}); |
||||
jQuery(id).data('graph', graph); |
||||
graph.set_dataLoader(data => { |
||||
// ici on peuple les dates manquantes du json |
||||
return new Promise((resolve, reject) => { |
||||
function fillInDates(meta, data){ |
||||
// put current data hash for efficient retrieval |
||||
// determine min/max of data |
||||
const currentDates = {}; |
||||
const minDate = meta.start_date; |
||||
let currentDate = minDate; |
||||
const maxDate = meta.end_date; |
||||
|
||||
params = { |
||||
legendeExterne:true, |
||||
legendeActions:true, |
||||
width:($('.large #page').length)?'755px':'560px', // 795px, 600px (sans le tableau de resume) ... |
||||
height:'250px', |
||||
modeDate:true, |
||||
zoom:true, |
||||
parse:{ |
||||
axeOnTitle:true, |
||||
defaultSerie:{ |
||||
bars:{show:true}, |
||||
lines:{show:true}, |
||||
points:{show:false} |
||||
} |
||||
}, |
||||
flot:{ |
||||
grid:{ |
||||
axismargin:10 |
||||
}, |
||||
xaxis:{ |
||||
labelWidth:45, |
||||
monthNames: [ |
||||
'[(#VAL{2000-01-01}|nom_mois)]', |
||||
'[(#VAL{2000-02-01}|nom_mois)]', |
||||
'[(#VAL{2000-03-01}|nom_mois)]', |
||||
'[(#VAL{2000-04-01}|nom_mois)]', |
||||
'[(#VAL{2000-05-01}|nom_mois)]', |
||||
'[(#VAL{2000-06-01}|nom_mois)]', |
||||
'[(#VAL{2000-07-01}|nom_mois)]', |
||||
'[(#VAL{2000-08-01}|nom_mois)]', |
||||
'[(#VAL{2000-09-01}|nom_mois)]', |
||||
'[(#VAL{2000-10-01}|nom_mois)]', |
||||
'[(#VAL{2000-11-01}|nom_mois)]', |
||||
'[(#VAL{2000-12-01}|nom_mois)]' |
||||
] |
||||
}, |
||||
yaxis:{ |
||||
position: "right", |
||||
tickDecimals: 1, |
||||
tickFormatter: function nbFormatter(val, axis) { |
||||
if (val >= 1000000){ |
||||
var fval = (val / 1000000).toFixed(axis.tickDecimals) ; |
||||
return fval.replace(/\.0$/,"") + " M"; |
||||
} |
||||
else{ |
||||
if (val >= 100000) |
||||
return (val / 1000).toFixed(axis.tickDecimals).replace(/\.0$/,"") + " k"; |
||||
else{ |
||||
var fval = val.toFixed(axis.tickDecimals).replace(/\.0$/,"") ; |
||||
return fval.replace(/(\d{3})$/," $1") ; |
||||
} |
||||
data.forEach(d => { |
||||
currentDates[d.date] = d; |
||||
}); |
||||
|
||||
// loop data and fill in missing dates |
||||
const filledInDates = []; |
||||
while (currentDate < maxDate) { |
||||
if (currentDates[currentDate]) { |
||||
filledInDates.push(currentDates[currentDate]); |
||||
} else { |
||||
filledInDates.push({"date": currentDate, "visites": 0}); |
||||
} |
||||
currentDate = graph.nextDate(currentDate, meta.unite, 1); |
||||
} |
||||
|
||||
return filledInDates; |
||||
} |
||||
}, |
||||
infobulle:{show:true} |
||||
} |
||||
|
||||
$table.tFlot($.extend(true, {}, params, options)); |
||||
} |
||||
data.data = fillInDates(data.meta, data.data); |
||||
|
||||
function trace_stats(){ |
||||
trace_stats_table( |
||||
"#visites_quotidiennes", |
||||
"statistiques_visites_quotidiennes statistiques_visites", |
||||
{ |
||||
grille:{weekend:true}, |
||||
flot:{ |
||||
xaxis:{ |
||||
timeformat:"%d %b", |
||||
minTickSize: [1, "day"] |
||||
}, |
||||
bars:{barWidth:24 * 60 * 60 * 1000} |
||||
} |
||||
resolve(data); |
||||
}) |
||||
.then(data => { |
||||
graph.update_table(data); |
||||
spip_d3_statistiques_update_graph(id, data); |
||||
}); |
||||
|
||||
trace_stats_table( |
||||
"#visites_mensuelles", |
||||
"statistiques_visites_mensuelles statistiques_visites", { |
||||
grille:{years:true}, |
||||
flot:{ |
||||
xaxis:{ |
||||
timeformat:"%b %y", |
||||
minTickSize: [1, "month"] |
||||
}, |
||||
bars:{barWidth:30.4 * 24 * 60 * 60 * 1000 /* nb de jours... approximatif */} |
||||
} |
||||
}); |
||||
|
||||
|
||||
graph.loading_start(); |
||||
Promise.resolve() |
||||
.then((d) => { |
||||
// charger la locale de date avant de créer les axes… sinon ils ne sont pas traduits |
||||
return graph.localize_d3_time(d); |
||||
}) |
||||
.then(() => { |
||||
graph.prepare_table(table_visible); |
||||
spip_d3_statistiques_prepare_graph(id, !table_visible); |
||||
graph.updateJson(); |
||||
}); |
||||
|
||||
} |
||||
|
||||
|
||||
function spip_dessiner_statistiques(){ |
||||
spip_d3_statistiques_create("#statistiques_visites"); |
||||
} |
||||
|
||||
jQuery(function($) { |
||||
trace_stats(); |
||||
onAjaxLoad(trace_stats); |
||||
spip_dessiner_statistiques(); |
||||
onAjaxLoad(spip_dessiner_statistiques); |
||||
}); |
||||
|
||||
</script> |
||||
|
||||
|
Loading…
Reference in new issue