Built with blockbuilder.org
forked from newsummit's block: Randomized Stacked Bar Chart
forked from Carscafmoo's block: Randomized Stacked Bar Chart - experimental
xxxxxxxxxx
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v3.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<style>
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.target-circle {
fill: #4cc9fd;
stroke: #048bbf;
stroke-width: 2;
}
.text {
font-size:14px;
fill: #048bbf;
}
.startline {
stroke: #4cc9fd;
stroke-width: 3;
}
body {
font-family: sans-serif;
position: relative;
margin: 10px auto;
}
</style>
</head>
<body>
<div id="main"></div>
<script>
// D3 API: https://github.com/d3/d3-3.x-api-reference/blob/master/API-Reference.md
// This example originated from https://stackoverflow.com/questions/15857286/how-to-create-a-bar-chart-made-up-of-very-small-squares
var svg,
max,
xAxis,
groups,
types,
width,
height,
xScale,
yScale,
timeScale,
translate,
randomize,
randomDate,
deals = {deals: []},
timeSeries = [],
sameDateArr = [],
colorScale,
maxSquares = 5,
numDays = 50,
sumA = sumB = sumC = 0,
dateFormat = d3.time.format("%x"),
startDate = new Date(2016, 0, 1),
endDate = new Date(2016, 2, 1),
OpCreatedDate = new Date(2016, 1, 5),
OpClosedDate = new Date(2016, 1, 25),
squareHeight = 12, // pixel height of squares
margin = {top: 20, right: 20, bottom: 80, left: 40};
// A helper to avoid having to use so many quotes inline
translate = function(x,y){
return "translate(" + x + "," + y + ")";
}
// Help me randomize within an interval
randomize = function(min,max) {
return Math.floor(Math.random()*(max-min+1)+min);
}
// Help me generate random dates with intervals
randomDate = function(start, end) {
return new Date(start.getTime() + Math.random() * (end.getTime() - start.getTime()));
}
/**
* Grouping function for grouping by action type. Passed in as `grouping` param to formatDealTimelineActions
* @param {object} action An action element from the dealTimelineActions
* @return {String}
*/
function groupByActionType(action) {
return action.actionType;
}
/**
* Convert an epoch timestamp into a date string
* @param {int} epoch The epoch -- # of seconds since UTC 1970-01-01 00:00:00
* @return {Date} Date object of the passed in date.
*/
function epochToDate(epoch) {
var d = new Date(0); // The 0 there is the key, which sets the date to the epoch
d.setUTCSeconds(epoch / 1000);
return d;
}
/**
* Format a date to format "YYYY-MM-DD"
* @param {Date} date The date to format
* @return {String} The date in format "YYYY-MM-DD"
*/
function formatDate(date) {
return [date.getFullYear(), String("00" + (date.getMonth() + 1)).slice(-2), String("00" + date.getDate()).slice(-2)].join("-");
}
/**
* Format the deal timeline actions so that they conform to what our graph expects
* @param {integer} index Index of the deal timeline actions to format
* @param {function} grouping A function to use that will take a deal timeline action element and output a group
* name for that element.
* @return {Array} An object in format required by our D3 plot to generate the time series plot;
* An array of objects with keys date and (group names). For this example we are using
* groupings of actionType
*/
function formatDealTimelineActions(actions, grouping) {
// This is probably not the *most* performant method to do this ... but still
// We gotta grab the full list of possible groupings from the data somehow?
var groups = new Set();
_.each(actions, function(x) {
x.group = grouping(x);
groups.add(x.group);
});
// Sort the timeline actions by date:
actions.sort(function(x, y) { return x.activityTimeAt < y.activityTimeAt ? -1 : 1; })
console.log(actions);
var timeSeries = [],
entryProto = {},
entry;
groups.forEach(function(x) { entryProto[x] = 0; });
entry = _.clone(entryProto);
actions.forEach(function(x) {
var date = formatDate(epochToDate(x.activityTimeAt));
if (!entry.date || entry.date < date) {
if (entry.date) {
var newDateString = entry.date;
var newDate = new Date(newDateString);
// go ahead and populate dummies for anything that is missing data
while (newDateString < date) {
newDate.setDate(newDate.getDate() + 1);
newDateString = formatDate(epochToDate(newDate));
// @TODO: Fix me! Probably use a *gasp* date library like moment (or maybe D3 has one)
if (newDateString == entry.date) { continue; } // Crappy work-around for UTC that I'm a little too lazy to deal with right now
timeSeries.push(entry);
entry = _.clone(entryProto);
entry.date = newDateString;
}
} else {
entry.date = date;
}
}
entry[x.group] ++;
});
timeSeries.push(entry);
return timeSeries;
}
/** RANDOMLY GENERATE DATA */
// Help me create a series of random epoch times within a given date
randomTimes = function(date) {
var numTimes = randomize(0, 10);
var i = 0;
var times = [];
var date = date.getTime(); // Get the beginning of the date...
while (i < numTimes) {
times.push(date + randomize(0, 3600 * 24));
i ++;
}
return times;
}
randomGroup = function(grp) {
var groups = ["a", "b", "c", "d"];
return groups[randomize(0, groups.length - 1)];
}
// This color scale assigns a sequence of 10 colors to each data "type" : a,b,c,d
colorScale = d3.scale.category10().domain(["a", "b", "c", "d"]);
// Populate a dummy array with random data
generateRandomData = function() {
var data = [];
for (var day=0; day < numDays; day++) {
var date = randomDate(startDate, endDate);
var times = randomTimes(date);
times.forEach(function(time){
data.push({
"actionType": randomGroup(),
"activityTimeAt": time,
"activityTimeSegment": "DURING",
});
});
}
return data;
}
// Generate three random deal sets, why not -- this is roughly how stuff comes in from the endpoint.
deals.deals[0] = generateRandomData();
deals.deals[1] = generateRandomData();
deals.deals[2] = generateRandomData();
console.log(deals);
// Format that data using the formatting function ... duh ... :-)
timeSeries = formatDealTimelineActions(deals.deals[0], groupByActionType);
console.log('timeseries: ', timeSeries);
// Graph width based on data
width = squareHeight * timeSeries.length;
// Find the height of the tallest column to set a 'y scale'
max = d3.max(timeSeries, function(d){return d.a + d.b + d.c});
height = max * squareHeight;
// Create a linear scale for the x-axis labels
xScale = d3.scale.linear()
.range([0,width])
.domain([0,timeSeries.length]);
// Create a time scale for the viz.. extracted start/end dates from data
timeScale = d3.time.scale()
.range([0, width])
.domain([startDate, endDate]);
var xTime = d3.scale.ordinal()
.domain(d3.time.days(startDate, endDate))
.rangeRoundBands([0, width], .1);
// Create a linear vertical scale so that we can group boxes vertically
yScale = d3.scale.linear()
.domain([0, max])
.rangeRound([height, 0]);
// Define an axis object for x
xAxis = d3.svg.axis()
.scale(timeScale)
.orient("bottom")
.tickFormat(d3.time.format("%b"))
.ticks(d3.time.months, 10)
//.tickSubdivide(2);
// Create svg container and position its main group "g" to top left
svg = d3.select("#main").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.attr("padding-left", "50px")
.append("g")
.attr("transform", translate(margin.left, margin.top + 0.5));
// Set up an "offsets" array for each color type ('a', 'b', 'c'). This
// associates a color domain for each stack segment (starting block, ending block)
timeSeries.forEach(function(d){
var y0 = 0;
d.offsets = colorScale.domain().map(function(type){
return {type: type, y0: y0, y1: y0 += +d[type], value : d[type]}
});
});
// Now, render and position the x-axis in a group element
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
//console.log('timeseries: ', timeSeries);
// Create/position a g element for each tower.
groups = svg.selectAll(".group")
.data(timeSeries)
.enter().append("g")
.attr("transform", function(d,i){
//console.log('time d: ', d);
return "translate(" + xScale(i) + ", -4)";
})
.attr("class", "group");
// Create a g element for each section of a tower.
types = groups.selectAll(".type")
.data(function(d){return d.offsets})
.enter().append("g")
.attr("transform", function(d){ return translate(0, yScale(d.y1))})
.attr("class", "type")
.attr("fill", function(d){return colorScale(d.type)});
// Create the individual squares
types.selectAll("rect")
.data(function(d){return d3.range(0,d.value)})
.enter().append("rect")
.attr("height", squareHeight-0.5)
.attr("width", squareHeight-0.5)
.attr("y", function(d){ return squareHeight * d });
svg.append("line")
.attr('class', 'startline')
.attr("x1", timeScale(OpCreatedDate)) // x position of the first end of the line
.attr("y1", 0 - margin.top) // y position of the first end of the line
.attr("x2", timeScale(OpCreatedDate)) // x position of the second end of the line
.attr("y2", height);
svg.append("circle")
.attr('r', 10)
.attr('class', 'target-circle')
.attr("transform", function(d,i){
return "translate(" + timeScale(OpCreatedDate) + ", " + height + ")";
});
svg.append("line")
.attr('class', 'startline')
.attr("x1", timeScale(OpClosedDate)) // x position of the first end of the line
.attr("y1", 0 - margin.top) // y position of the first end of the line
.attr("x2", timeScale(OpClosedDate)) // x position of the second end of the line
.attr("y2", height);
svg.append("circle")
.attr('r', 10)
.attr('class', 'target-circle')
.attr("transform", function(d,i){
return "translate(" + timeScale(OpClosedDate) + ", " + height + ")";
});
svg.append("text")
.attr("dx", timeScale(OpCreatedDate))
.attr("dy", height + 30)
.attr("class", "text")
.text(dateFormat(OpCreatedDate));
svg.append("text")
.attr("dx", timeScale(OpClosedDate))
.attr("dy", height + 30)
.attr("class", "text")
.text(dateFormat(OpClosedDate));
</script>
</body>
https://d3js.org/d3.v3.js
https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js