zeus.ugent.be/content/assets/scripts/datavis/script2.js
2018-05-18 22:19:43 +02:00

335 lines
10 KiB
JavaScript

(function () {
const width = 1000;
const height = 800;
const middle = height / 2;
const barPadding = 1;
const padding = { top: 50, left: 50, right: 40, bottom: 50 };
const formatTime = d3.timeFormat('%b %e %Y');
let prevUpper, prevLower;
let xScale, yScale1, yScale2, xAxis, yAxis1, yAxis2;
let prevUpperButton, prevLowerButton;
var chart, chart2, slider, selection;
var userdata, pricedata, eventdata, tapordersdata, tapusersdata;
d3.csv('/csvdata/haldis-price-time.csv', d => {
return {
date: d3.timeParse('%Y-%m-%d')(d.starttime).setHours(0, 0, 0, 0),
value: parseInt(d.total_price) / 100
};
}).then(function (data) {
pricedata = data;
return d3.csv('/csvdata/haldis-num-users-time.csv', d => {
return {
date: d3.timeParse("%Y-%m-%d")(d.starttime).setHours(0, 0, 0, 0),
value: parseInt(d.num_users)
};
});
}).then(function (data) {
userdata = data;
return d3.csv('/csvdata/eventdata.csv', d => {
return {
date: d3.utcParse("%Y-%m-%dT%H:%M:%S%Z")(d.date).setHours(0, 0, 0, 0),
title: d.title
};
});
}).then(function (data) {
eventdata = data;
return d3.csv('/csvdata/tap-orders-day.csv', d => {
return {
date: d3.timeParse('%Y-%m-%d')(d.created_at).setHours(0, 0, 0, 0),
value: parseInt(d.count)
};
});
}).then(function (data) {
tapordersdata = data;
return d3.csv('/csvdata/tap-orders-users-day.csv', d => {
return {
date: d3.timeParse('%Y-%m-%d')(d.created_at).setHours(0, 0, 0, 0),
value: parseInt(d.count)
};
});
}).then(function (data) {
tapusersdata = data;
xScale = d3.scaleTime().range([padding.left, width - padding.right]);
yScale1 = d3.scaleLinear().range([middle, padding.top]);
yScale2 = d3.scaleLinear().range([padding.bottom, middle]);
xAxis = d3.axisBottom().scale(xScale).ticks().tickFormat('').tickSize(0);
yAxis1 = d3.axisLeft().scale(yScale1).ticks();
yAxis2 = d3.axisLeft().scale(yScale2).ticks();
chart = timeBarChart();
chart.upper = true;
chart.eventdata = eventdata;
chart2 = timeBarChart();
chart2.upper = false;
chart2.eventdata = eventdata;
selection = d3.select('#barchart').append('g');
selection.append('g')
.attr('class', 'x axis')
.attr('transform', `translate(0, ${middle})`);
selection.append('g')
.attr('class', 'y axis')
.attr('transform', `translate(${padding.left}, 0)`);
selection.append('g')
.attr('class', 'y axis2')
.attr('transform', `translate(${padding.left}, ${middle - padding.top})`);
slider = timeSlider();
prevUpper = userdata;
prevLower = tapordersdata;
updateData(userdata, true);
updateData(tapordersdata, false);
prevUpperButton = d3.select("#user-button");
prevLowerButton = d3.select("#tap-order-button");
setActiveButton('#haldis-user-button', true);
setActiveButton('#tap-order-button', false);
slider.on('slide', domain => {
chart.domain.start = domain[0];
chart.domain.end = domain[1];
chart2.domain.start = domain[0];
chart2.domain.end = domain[1];
const d1 = filterData(prevUpper, chart.domain.start, chart.domain.end);
const d2 = filterData(prevLower, chart2.domain.start, chart2.domain.end);
selection.datum(d1).call(chart);
selection.datum(d2).call(chart2);
});
d3.select('#slider').call(slider);
});
function setActiveButton(name, upper) {
curButton = d3.select(name);
if (upper) {
prevUpperButton.classed("is-focused", false);
prevUpperButton = curButton;
} else {
prevLowerButton.classed("is-focused", false);
prevLowerButton = curButton;
}
curButton.classed("is-focused", true);
}
function filterData(data, beginTime, endTime) {
return _(data).filter(e => beginTime <= e.date && e.date <= endTime).value();
}
function timeBarChart() {
function my(svg) {
let eventdata = my.eventdata;
var dt = svg.datum();
const t = d3.transition()
.ease(d3.easeLinear)
.duration(100);
xScale.domain([my.domain.start, my.domain.end]);
svg.select('.x.axis')
.transition(t)
.call(xAxis);
if (my.upper) {
yScale1.domain(d3.extent(dt, d => d.value));
svg.select('.y.axis')
.transition(t)
.call(yAxis1);
} else {
yScale2.domain(d3.extent(dt, d => d.value));
svg.select('.y.axis2')
.transition(t)
.call(yAxis2);
}
function update() {
let g = d3.select('svg#barchart > g');
var selection, sellines, eventlines;
if (my.upper) {
selection = g.selectAll('.datacircle');
sellines = g.selectAll('.line');
eventlines = g.selectAll('.eventline');
} else {
selection = g.selectAll('.datacircle2');
sellines = g.selectAll('.line2');
eventlines = g.selectAll('.eventline2');
}
sellines
.attr('x1', d => xScale(d.date))
.attr('y1', () => my.upper ? middle : padding.top)
.attr('x2', d => xScale(d.date))
.attr('y2', d => my.upper ? yScale1(d.value) : yScale2(d.value));
eventlines
.attr('x1', d => xScale(d.date))
.attr('y1', () => my.upper ? middle : padding.top)
.attr('x2', d => xScale(d.date))
.attr('y2', (d) => {
let yValue = 0;
selection.data().forEach(function (el) {
if (el.date === d.date) {
yValue = el.value;
}
})
return my.upper ? yScale1(yValue) : yScale2(yValue);
});
selection
.attr('cx', d => xScale(d.date))
.attr('cy', d => my.upper ? yScale1(d.value) : yScale2(d.value));
}
const fed = filterData(eventdata, my.domain.start, my.domain.end);
var selection, sellines, eventlines;
if (my.upper) {
selection = svg.selectAll('.datacircle').data(dt, d => d.date);
sellines = svg.selectAll('.line').data(dt, d => d.date);
eventlines = svg.selectAll('.eventline').data(fed, d => d.date);
} else {
selection = svg.selectAll('.datacircle2').data(dt, d => d.date);
sellines = svg.selectAll('.line2').data(dt, d => d.date);
eventlines = svg.selectAll('.eventline2').data(fed, d => d.date);
}
const fmtStr = 'DD/MM/YY';
sellines.exit().remove();
sellines.enter()
.append('line')
.attr('stroke', '#ddd')
.attr('stroke-width', 2)
.attr('class', () => my.upper ? 'line' : 'line2')
.attr('transform', () => my.upper ? `translate(0, 0)` : `translate(0, ${middle - padding.bottom})`)
.merge(sellines);
eventlines.exit().remove();
eventlines.enter()
.append('line')
.attr('stroke', '#f4a442')
.attr('stroke-width', 2)
.attr('class', () => my.upper ? 'eventline' : 'eventline2')
.attr('transform', () => my.upper ? `translate(0, 0)` : `translate(0, ${middle - padding.bottom})`)
.on("mouseover", function (d) {
const tooltip = d3.select('.tooltip');
tooltip
.style("opacity", .9)
.html(moment(d.date).format(fmtStr) + ' - ' + d.title);
// We calculate the bounding rects after setting the html
let rect = d3.select(this).node().getBoundingClientRect();
let t_rect = tooltip.node().getBoundingClientRect();
tooltip
.style("left", (rect.left + rect.width / 2 - t_rect.width / 2) + "px")
.style("top", (rect.top - t_rect.height - 5) + "px");
})
.on("mouseout", _ => {
const tooltip = d3.select('.tooltip');
tooltip.style("opacity", 0);
})
.merge(eventlines);
selection.exit().remove();
selection.enter()
.append('circle')
.attr('fill', 'lightblue')
.attr('class', () => my.upper ? 'datacircle' : 'datacircle2')
.attr('transform', () => my.upper ? `translate(0, 0)` : `translate(0, ${middle - padding.bottom})`)
.on("mouseover", function (d) {
const tooltip = d3.select('.tooltip');
tooltip
.style("opacity", .9)
.html(moment(d.date).format(fmtStr) + ' - ' + d.value);
// We calculate the bounding rects after setting the html
let rect = d3.select(this).node().getBoundingClientRect();
let t_rect = tooltip.node().getBoundingClientRect();
tooltip
.style("left", (rect.left + rect.width / 2 - t_rect.width / 2) + "px")
.style("top", (rect.top - t_rect.height - 5) + "px");
})
.on("mouseout", _ => {
const tooltip = d3.select('.tooltip');
tooltip.style("opacity", 0);
})
.merge(selection)
.attr('r', 3)
;
update();
}
return my;
}
function updateData(data, upper) {
domain = d3.extent(data, d => d.date);
chart.domain = {
'start': domain[0],
'end': domain[1]
};
chart2.domain = {
'start': domain[0],
'end': domain[1]
};
const times = _(data).map(e => moment(e.date));
if (upper) {
prevUpper = data;
const fd = filterData(prevLower, chart.domain.start, chart.domain.end);
selection.datum(data).call(chart);
selection.datum(fd).call(chart2);
slider.domain([times.min(), times.max()]).data(times.value(), true);
} else {
prevLower = data;
const fd = filterData(prevUpper, chart.domain.start, chart.domain.end);
selection.datum(data).call(chart2);
selection.datum(fd).call(chart);
slider.domain([times.min(), times.max()]).data(times.value(), true);
}
}
d3.select('#haldis-user-button').on('click', () => {
updateData(userdata, true);
setActiveButton('#haldis-user-button', true);
});
d3.select('#haldis-price-button').on('click', () => {
updateData(pricedata, true);
setActiveButton('#haldis-price-button', true);
});
d3.select('#tap-order-button').on('click', () => {
updateData(tapordersdata, false);
setActiveButton('#tap-order-button', false);
});
d3.select('#tap-user-button').on('click', () => {
updateData(tapusersdata, false);
setActiveButton('#tap-user-button', false);
});
})();