fathom/assets/js/components/Graph.js

198 lines
5.0 KiB
JavaScript
Raw Normal View History

2016-11-23 18:40:35 +00:00
'use strict';
import { h, render, Component } from 'preact';
2016-12-04 12:23:09 +00:00
import * as d3 from 'd3';
import tip from 'd3-tip';
import * as numbers from '../lib/numbers.js';
2016-12-04 12:23:09 +00:00
d3.tip = tip;
2016-11-23 18:40:35 +00:00
2016-12-04 12:23:09 +00:00
const dayInSeconds = 60 * 60 * 24;
2016-11-23 19:30:09 +00:00
function Chart(element, showPrimary, showSecondary) {
var padt = 10, padb = 20, padr = 40, padl = 40,
h = 300,
w = element.parentNode.clientWidth - padl - padr,
x = d3.scaleBand().range([0, w]).padding(0.2).round(true),
y = d3.scaleLinear().range([h, 0]),
yAxis = d3.axisLeft().scale(y).tickSize(-w + padl + padr),
xAxis = d3.axisBottom().scale(x),
primaryData = [],
secondaryData = [];
var pageviewTip = d3.tip()
.attr('class', 'd3-tip')
.html((d) => '<span>' + numbers.formatWithComma(d.Count) + '</span>' + ' pageviews')
.offset([-12, 0]);
var visitorTip = d3.tip()
.attr('class', 'd3-tip')
.html((d) => '<span>' + numbers.formatWithComma(d.Count) + '</span>' + ' visitors' )
.offset([-12, 0]);
var graph = d3.select('#graph');
var vis = graph
.append('svg')
.attr('width', w + padl + padr)
.attr('height', h + padt + padb)
.append('g')
.attr('transform', 'translate(' + padl + ',' + padt + ')');
vis.call(pageviewTip);
vis.call(visitorTip);
function setData(one, two) {
primaryData = one;
secondaryData = two;
}
function toggleBars(one, two) {
showPrimary = one;
showSecondary = two;
}
function draw() {
var max = d3.max(showPrimary ? primaryData : secondaryData, (d) => d.Count);
var ticks = primaryData.length;
var xTick = Math.round(ticks / 7);
x.domain(primaryData.map((d) => d.Label))
y.domain([0, (max * 1.1)])
2016-12-04 12:23:09 +00:00
// clear all previous data
vis.selectAll('*').remove();
2016-12-04 12:23:09 +00:00
// axes
vis.append("g")
.attr("class", "y axis")
.call(yAxis);
vis.append("g")
.attr("class", "x axis")
.attr('transform', 'translate(0,' + h + ')')
.call(xAxis)
.selectAll('g')
.style('display', (d, i) => i % xTick != 0 ? 'none' : 'block')
2016-12-04 12:23:09 +00:00
// bars
if( showPrimary ) {
var bars = vis.selectAll('g.primary-bar')
.data(primaryData)
.enter()
.append('g')
.attr('class', 'primary-bar')
.attr('transform', function (d, i) { return "translate(" + x(d.Label) + ", 0)" });
bars.append('rect')
.attr('width', x.bandwidth())
.attr('height', (d) => (h - y(d.Count)) )
.attr('y', (d) => y(d.Count))
.on('mouseover', pageviewTip.show)
.on('mouseout', pageviewTip.hide);
}
if(showSecondary) {
var visitorBars = vis.selectAll('g.sub-bar')
.data(secondaryData)
.enter()
.append('g')
.attr('class', 'sub-bar')
.attr('transform', (d, i) => "translate(" + ( x(d.Label) + ( x.bandwidth() * 0.16667 ) ) + ", 0)");
visitorBars.append('rect')
.attr('width', x.bandwidth() * 0.66 )
.attr('height', (d) => (h - y(d.Count)) )
.attr('y', (d) => y(d.Count))
.on('mouseover', visitorTip.show)
.on('mouseout', visitorTip.hide);
}
}
return {
'draw': draw,
'setData': setData,
'toggleBars': toggleBars,
}
}
class Graph extends Component {
constructor(props) {
super(props)
this.fetchData = this.fetchData.bind(this);
this.refreshChart = this.refreshChart.bind(this);
this.data = {
visitors: null,
pageviews: null,
}
}
componentDidMount() {
this.chart = new Chart(document.getElementById('graph'), this.props.showPageviews, this.props.showVisitors)
this.fetchData(this.props.period)
}
componentWillReceiveProps(newProps) {
if(this.props.period != newProps.period) {
this.fetchData(newProps.period)
}
this.chart.toggleBars(newProps.showPageviews, newProps.showVisitors)
this.chart.draw()
}
shouldComponentUpdate() {
return false
}
refreshChart() {
if(this.data.visitors && this.data.pageviews) {
this.chart.setData(this.data.pageviews, this.data.visitors)
this.chart.draw()
}
2016-11-23 18:40:35 +00:00
}
2016-11-23 20:29:54 +00:00
fetchData(period) {
const before = Math.round((+new Date() ) / 1000);
const after = before - ( period * dayInSeconds );
const group = period > 90 ? 'month' : 'day';
2016-11-23 19:30:09 +00:00
// fetch visitor data
fetch(`/api/visits/count/group/${group}?before=${before}&after=${after}`, {
2016-11-23 19:30:09 +00:00
credentials: 'include'
}).then((r) => {
if( r.ok ) {
return r.json();
}
throw new Error();
}).then((data) => {
this.data.visitors = data;
window.requestAnimationFrame(this.refreshChart);
2016-11-23 19:30:09 +00:00
});
// fetch pageview data
fetch(`/api/pageviews/count/group/${group}?before=${before}&after=${after}`, {
credentials: 'include'
}).then((r) => {
if( r.ok ) {
return r.json();
}
throw new Error();
}).then((data) => {
this.data.pageviews = data;
window.requestAnimationFrame(this.refreshChart);
});
2016-11-23 18:40:35 +00:00
}
render() {
return (
2016-12-04 12:23:09 +00:00
<div id="graph"></div>
2016-11-23 18:40:35 +00:00
)
}
}
export default Graph