Files
PetitTetonMeteor/imports/ui/Graphs.js

404 lines
14 KiB
JavaScript
Raw Normal View History

import './Graphs.html';
import d3 from 'd3';
let PREFIX = "graphs.";
let SalesTotals = new Meteor.Collection("salesTotals");
Meteor.subscribe("venues");
Meteor.subscribe("productTags");
let salesTotalsSubscription;
//Save the time and options values outside the session also (in addition to saving it in the session), so we can access it in the autorun for the graph without triggering a re-run of the graph.
//Triggering a re-run of the graph from the session change will cause the graph to redraw BEFORE the data arrives from the server (due to the options changing), causing all sorts of grief.
let time = "annual";
let options = "none";
Template.Graphs.onCreated(function() {
let template = Template.instance();
if(!Session.get(PREFIX + "time")) Session.set(PREFIX + "time", time);
else time = Session.get(PREFIX + "time");
if(!Session.get(PREFIX + "options")) Session.set(PREFIX + "options", options);
else options = Session.get(PREFIX + "options");
Tracker.autorun(function() {
salesTotalsSubscription = template.subscribe("salesTotals", Session.get(PREFIX + "time"), Session.get(PREFIX + "options"));
});
});
Template.Graphs.onRendered(function() {
//Reset the pull downs to their former states.
Template.instance().$('select[name="time"]').val(time);
Template.instance().$('select[name="options"]').val(options);
//Build the SVG Graphs
let margin = {top: 10, right: 0, bottom: 30, left: 40};
let width = 960 - margin.left - margin.right;
let height = 500 - margin.top - margin.bottom;
let x0Scale = d3.scaleBand().range([0, width]).padding(0.05);
let x1Scale = d3.scaleBand().padding(0.05);
let yScale = d3.scaleLinear().range([height, 0]);
let svg = d3.select('svg.salesGraph')
.attr("viewBox", "0 0 960 500")
.attr("perserveAspectRatio", "xMidYMid meet")
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
let xLabels = svg.append("g").attr('class', 'xLabels').attr("transform", "translate(0, " + height + ")");
let yLabels = svg.append("g").attr('class', 'yLabels');
Tracker.autorun(function() {
if(salesTotalsSubscription.ready()) {
//NOTE: Setting these time/options based flags is necessary in the autorun since they change, but don't use the session variables for the values, otherwise the autorun will re-run when the session changes, but before the new data arrives from the server.
let showMonths = time == 'monthly';
let showWeeks = time == 'weekly';
let showMarkets = options == 'markets';
let showTypes = options == 'types';
let grouped = time != 'annual' || options != 'none';
let stacked = time != 'annual' && options != 'none';
let x1ScaleKeyFunction = function(d) {return showMonths ? d.month : showWeeks ? Number(d.week) : showMarkets ? d.venueId + "-" + d.year : d.year};
console.log("collection count: " + SalesTotals.find({}).count());
let dataset = SalesTotals.find({}).fetch();
let x1UniqueValues;
if(showMonths) x1UniqueValues = [...new Set(dataset.map(item => item.month))];
else if(showWeeks) x1UniqueValues = ([...new Set(dataset.map(item => Number(item.week)))]).sort(function(a,b) {return a - b});
else if(showMarkets) x1UniqueValues = [...new Set(dataset.map(item => item.venueId))];
else x1UniqueValues = [""];
//Update scale domains
let years = [...new Set(dataset.map(item => item.year))];
x0Scale.domain(years); //Gets the unique set of years from the dataset.
x1Scale.domain(x1UniqueValues).rangeRound([0, x0Scale.bandwidth()]);
console.log("x1Scale's unique values: " + x1UniqueValues);
console.log("x1Scale's bandwidth: " + x1Scale.bandwidth());
yScale.domain([0, d3.max(dataset, function(d) {return d.total;})]);
//Join the data set with the existing data in the svg element.
let barGroups = svg.selectAll('.barGroup').data(years);
let barsInGroup;
let handleBarsInGroup = function(barsInGroup) {
barsInGroup.exit()
.transition()
.duration(500)
.style('opacity', 0)
.remove();
barsInGroup.enter().append('rect')
.attr('class', 'bar')
.attr('y', function(d) {return yScale(d.total)})
.attr('height', function(d) {return height - yScale(d.total)})
.attr('x', function(d) {return x1Scale(x1ScaleKeyFunction(d))})
.attr('width', x1Scale.bandwidth());
barsInGroup
.transition()
.duration(500)
.attr('y', function(d) {return yScale(d.total)})
.attr('height', function(d) {return height - yScale(d.total)})
.attr('x', function(d) {return x1Scale(x1ScaleKeyFunction(d))})
.attr('width', x1Scale.bandwidth());
};
barGroups.exit()
.transition()
.duration(500)
.style('opacity', 0)
.remove();
barsInGroup = barGroups.enter().append('g')
.attr('class', 'barGroup')
.attr('transform', function(d) {return 'translate(' + x0Scale(d) + ',0)'})
.selectAll('.bar')
.data(function(d) {return dataset.filter(function(x) {return x.year == d})}, function(d) {return d._id});
//barGroups = svg.selectAll('.barGroup').data(dataset);
handleBarsInGroup(barsInGroup);
barsInGroup = barGroups
.attr('transform', function(d) {return 'translate(' + x0Scale(d) + ',0)'})
.selectAll('.bar')
.data(function(d) {return dataset.filter(function(x) {return x.year == d})}, function(d) {return d._id});
handleBarsInGroup(barsInGroup);
//barGroups.attr('transform', function(d) {return 'translate(' + x0Scale(d.year) + ',0)'});
//barsInGroup = barsInGroup.data(function(d) {
// console.log("Getting the data for a bar group for year: " + d);
// let r = dataset.filter(function(x) {return x.year == d});
// console.log(r);
// return r;});
////Handle the new data elements by adding them to the svg element.
//let barsInNewGroup = barGroups.enter().append('g')
// .attr('transform', function(d) {return 'translate(' + x0Scale(d.year) + ',0)'})
// .attr('class', 'barGroup')
// .selectAll('bar')
// .data(function(d) {return dataset.filter(function(x) {return x.year == d})});
//
//let barsInModifiedGroup = barGroups.transition()
// .duration(500)
// .attr('transform', function(d) {return 'translate(' + x0Scale(d.year) + ',0)'})
// .selectAll('bar')
// .data(function(d) {return dataset.filter(function(x) {return x.year == d})});
//
//let barHandler = function(barInGroup) {
// barInGroup.enter().append('rect')
// .attr('class', 'bar')
// .attr('y', function(d) {return yScale(d.total)})
// .attr('height', function(d) {return height - yScale(d.total)})
// .attr('x', function(d) {return x1Scale(x1ScaleKeyFunction(d))})
// .attr('width', x1Scale.bandwidth());
// barInGroup.transition()
// .duration(500)
// .attr('y', function(d) {return yScale(d.total)})
// .attr('height', function(d) {return height - yScale(d.total)})
// .attr('x', function(d) {return x1Scale(x1ScaleKeyFunction(d))})
// .attr('width', x1Scale.bandwidth());
// barInGroup.exit()
// .transition()
// .duration(500)
// .remove()
// .attr("x", -x1Scale.bandwidth());
//};
//
//barHandler(barsInNewGroup);
//barHandler(barsInModifiedGroup);
////Transition existing elements to their new states.
//let barsTransition = barGroups.transition()
// .duration(500)
// .attr('y', function(d) {
// return yScale(d.total);
// })
// .attr('height', function(d) {
// return height - yScale(d.total);
// })
// .attr('x', function(d) {return x1Scale(x1ScaleKeyFunction(d))})
// .attr('width', x1Scale.bandwidth());
//
////Transition removed elements off the screen.
//let barsExit = barGroups.exit()
// .transition()
// .duration(500)
// .remove()
// .attr("x", -x1Scale.bandwidth());
//let barTexts = svg.selectAll('text.barText').data(dataset);
//barTexts.enter().append("text")
// .attr("class", "barText")
// .attr("text-anchor", "middle")
// .attr('x', function(d) {return x0Scale(d._id) + x0Scale.bandwidth() / 2;})
// .attr('y', function(d) {return yScale(d.total) - 2;})
// .text(function(d) {return "$" + Math.round(d.total)});
//barTexts.transition()
// .attr("class", "barText")
// .attr("text-anchor", "middle")
// .attr('x', function(d) {return x0Scale(d._id) + x0Scale.bandwidth() / 2;})
// .attr('y', function(d) {return yScale(d.total) - 2;})
// .text(function(d) {return "$" + Math.round(d.total)});
//barTexts.exit()
// .remove();
//Add the x & y axis labels
//xLabels.attr("class", "xAxisLabels").call(d3.axisBottom(x1Scale));
xLabels.attr('class', 'xAxisLabels').
yLabels.attr("class", "yAxisLabels").call(d3.axisLeft(yScale));
}
});
//let margin = {top: 20, right: 20, bottom: 30, left: 80};
//let width = 960 - margin.left - margin.right;
//let height = 500 - margin.top - margin.bottom;
//let x0Scale = d3.scaleBand().range([0, width]).padding(0.1);
//let yScale = d3.scaleLinear().range([height, 0]);
//let svg = d3.select('svg.salesGraph')//d3.select("#graphs").append("svg")
// .attr("viewBox", "0 0 960 500")
// .attr("perserveAspectRatio", "xMidYMid meet")
// .append("g")
// .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
//let xLabels = svg.append("g").attr("transform", "translate(0, " + height + ")");
//let yLabels = svg.append("g");
//
//Tracker.autorun(function() {
// console.log("Autorun");
// if(salesTotalsSubscription.ready()) {
// console.log("Ready");
// let dataset = SalesTotals.find({}).fetch();
//
// //Update scale domains
// x0Scale.domain(dataset.map(function(d) {return d._id}));
// yScale.domain([0, d3.max(dataset, function(d) {return d.total;})]);
//
// //Join the data set with the existing data in the svg element.
// let bars = svg.selectAll('.bar').data(dataset);
//
// //Handle existing elements.
// // bars.[do something here ie: attr('class', 'oldValues')]
// //Handle the new data elements by adding them to the svg element.
// bars.enter().append('rect')
// .attr('class', 'bar')
// .attr('x', function(d) {
// return x0Scale(d._id);
// })
// .attr('width', x0Scale.bandwidth())
// .attr('y', function(d) {
// return yScale(d.total);
// })
// .attr('height', function(d) {
// return height - yScale(d.total);
// });
// //Handle removed data elements?
// // bars.exit().remove();
//
// let barTexts = svg.selectAll('text.barText').data(dataset);
//
// barTexts.enter().append("text")
// .attr("class", "barText")
// .attr("text-anchor", "middle")
// .attr('x', function(d) {return x0Scale(d._id) + x0Scale.bandwidth() / 2;})
// .attr('y', function(d) {return yScale(d.total) - 2;})
// .text(function(d) {return "$" + Math.round(d.total)});
// //Add the x & y axis labels
// xLabels.attr("class", "xAxisLabels").call(d3.axisBottom(x0Scale));
// yLabels.attr("class", "yAxisLabels").call(d3.axisLeft(yScale));
// }
//});
});
Template.Graphs.helpers({
sales: function() {
let sort = [];
sort.push(['year', 'asc']);// year = 1;
if(Session.get(PREFIX + "time") === 'weekly') sort.push(['week', 'asc']); // .week = 1;
if(Session.get(PREFIX + "time") === 'monthly') sort.push(['month', 'asc']); // .month = 1;
if(Session.get(PREFIX + "options") === 'markets') sort.push(['venue', 'asc']); // .month = 1;
return SalesTotals.find({}, {sort: sort});
},
showTime: function(time) {
return Session.get(PREFIX + "time") === time;
},
showOption: function(option) {
return Session.get(PREFIX + "options") === option;
},
formatTotal: function(total) {
return "$" + total.toFixed(2);
}
});
Template.Graphs.events({
'change select[name="time"]': function(event, template) {
Session.set(PREFIX + "time", $(event.target).val());
time = $(event.target).val();
},
'change select[name="options"]': function(event, template) {
Session.set(PREFIX + "options", $(event.target).val());
options = $(event.target).val();
}
});
/*
//Select…
let bars = svg.selectAll("rect").data(dataset, key);
//Enter…
bars.enter()
.append("rect")
.attr("x", w)
.attr("y", function(d) {
return h - yScale(d.total);
})
//.attr("width", x0Scale.rangeBand())
.attr("width", x0Scale.bandwidth())
.attr("height", function(d) {
return yScale(d.total);
})
.attr("fill", function(d) {
return "rgb(0, 0, " + (d.total * 10) + ")";
})
.attr("data-id", function(d){
return d._id;
});
//Update…
bars.transition()
// .delay(function(d, i) {
// return i / dataset.length * 1000;
// }) // this delay will make transistions sequential instead of paralle
.duration(500)
.attr("x", function(d, i) {
return x0Scale(i);
})
.attr("y", function(d) {
return h - yScale(d.total);
})
.attr("width", x0Scale.bandwidth())
.attr("height", function(d) {
return yScale(d.total);
}).attr("fill", function(d) {
return "rgb(0, 0, " + (d.total * 10) + ")";
});
//Exit…
bars.exit()
.transition()
.duration(500)
.attr("x", -x0Scale.bandwidth())
.remove();
//Update all labels
//Select…
let labels = svg.selectAll("text")
.data(dataset, key);
//Enter…
labels.enter()
.append("text")
.text(function(d) {
return d.total;
})
.attr("text-anchor", "middle")
.attr("x", w)
.attr("y", function(d) {
return h - yScale(d.total) + 14;
})
.attr("font-family", "sans-serif")
.attr("font-size", "11px")
.attr("fill", "white");
//Update…
labels.transition()
// .delay(function(d, i) {
// return i / dataset.length * 1000;
// }) // this delay will make transistions sequential instead of paralle
.duration(500)
.attr("x", function(d, i) {
return x0Scale(i) + x0Scale.bandwidth() / 2;
}).attr("y", function(d) {
return h - yScale(d.total) + 14;
}).text(function(d) {
return d.total;
});
//Exit…
labels.exit()
.transition()
.duration(500)
.attr("x", -x0Scale.bandwidth())
.remove();
*/