334 lines
10 KiB
JavaScript
334 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);
|
|
});
|
|
})();
|