var margin = {top: 20, right: 20, bottom: 20, left: 200}; var height = 500 - margin.top - margin.bottom; var width = 960 - margin.left - margin.right; var data = []; var libraries = []; var timePrintFormat = d3.time.format("%H:%M"); var rectHeight = 9.5; //D3 -> better to iterate and append + select via className // or -> bind data to dom and select via variable name // Data is loading var target_url = "http://www.lib.berkeley.edu/hours" var encoded_url = encodeURI(target_url); var API_KEY = "1dd8ab3e6c55463dbd719a12731e7226a6948da4e498adb9d7369cde2973976dbf5965386c757b8746ba2129328a79ee8f3695c0d6004c246325190bd2875591e6257d1a6281122f6c0bbf3908595a4c"; var url = "https://api.import.io/store/connector/_magic?url=" + encoded_url + "&format=JSON&js=false&_user=1dd8ab3e-6c55-463d-bd71-9a12731e7226&_apikey=" + API_KEY data = backUpData.tables[0]; processData(); d3.json(url, function(error, json) { if (error) { alert("Cannot load realtime data -> defaulting to mon-fri schedule") return console.warn(error); } data = json.tables[0]; processData(); }); function processData() { //Multiple time input woes var formatTime1 = d3.time.format("%I:%M %p"); // `11:12 am` var formatTime2 = d3.time.format("%I %p"); // `11 am` var formatTime3 = d3.time.format("%H"); // `23` [0,23] function processTime(time) { if (!_.includes(time, '-')) { return {start:0, end:0}; } if (time === '24-hour Study Hall') { return {start:formatTime3.parse("0"), end:formatTime3.parse("23")}; } var parts = time.split('-'); return { start: to24(parts[0]), end: to24(parts[1]) } } function to24(time) { var parseFormat1 = formatTime1.parse(time); var parseFormat2 = formatTime2.parse(time); if (parseFormat1) { return parseFormat1 } if (parseFormat2) { return parseFormat2 } if (_.includes(time,'midnight')) { return formatTime3.parse("23") } if (_.includes(time,'noon')) { return formatTime3.parse("12") } } libraries = data.results; _.forEach(libraries, function(lib) { var hours = lib['libraryhours_values']; delete lib['libraryhours_values'] hours = _.isArray(hours) ? hours : [hours] lib['hours'] = _.map(hours, function(time) { return processTime(time); }) }) visualization(); } function createClassName(input) { return input.replace(/\s+/g, '-').replace(/[^a-zA-Z-]/g, '').toLowerCase(); } function visualization() { d3.selectAll("svg").remove(); var svg = d3.select("body").append("svg") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); var scaleHeight = d3.scale.linear() .domain([0, _.size(libraries)]) .range([0, height]); var timeScale = d3.time.scale() .domain([timePrintFormat.parse("00:00"), timePrintFormat.parse("23:59")]) .range([0, width]); var xAxis = d3.svg.axis() .scale(timeScale) .ticks(d3.time.hour, 2) .tickFormat(timePrintFormat); console.log(libraries) //Keep each row in a G element -> goal is to have row have the info //While the individual bars are dumb for the most part _.forEach(libraries, function(lib, i) { var className = createClassName(lib['libraryinfo_link_1/_text']); const row = svg.append("g").classed(className, true); _.forEach(lib["hours"], function(hours) { //This rect is used to capture mouse events row.append('rect') .classed("background-row", true) .attr("x", 0) .attr("y", scaleHeight(i)) .attr("width", width) .attr("height", rectHeight) .on('click', function() { window.open(lib.libraryinfo_link_1) }) drawRow(className, hours, i) }) //mouseover specific elements within the `g` element rather than entire thing? row.on('mouseover', function() { d3.select(this).style('fill', 'orange') d3.selectAll("rect." + className).style("fill", "orange") //why need to select rect seperatly when inside g-element }) row.on('mouseout', function() { d3.select(this).style('fill', 'black') d3.selectAll("rect." + className).style("fill", "none") }) const libraryLabel = row.append("g") .attr("class", "libraryLabel") .attr("transform", function() { return "translate(" + 0 + "," + (scaleHeight(i) + rectHeight/2) + ")"; }) libraryLabel.append("text") .attr("x", -6) .attr("dy", ".35em") .text(lib["libraryinfo_link_1/_text"]) .on('click', function() { var win = window.open(lib['libraryinfo_link_1'], '_blank'); win.focus(); }) libraryLabel.append("line") .attr("x2", width); }) function drawRow(className, hours, i) { overflow = hours.end < hours.start; originalEnd = hours.end if (overflow) { hours.end = timePrintFormat.parse("23:59"); } drawRect(className, hours, i) if (overflow) { hours.start = timePrintFormat.parse("00:00"); hours.end = originalEnd drawRect(className, hours, i) } } function drawRect(className, hours, i) { const rowElement = d3.select("g." + className); rowElement.append("rect") .classed(className, true) .attr("x", function() { return timeScale(hours.start); }) .attr("y", scaleHeight(i)) .attr("width", function() { return timeScale(hours.end) - timeScale(hours.start); }) .attr("height", rectHeight) } //Drawing axis svg.append("g") .attr("class", "x top axis") .call(xAxis.orient("top")); //What would do to have actions when select individual ticks? svg.append("g") .attr("class", "x bottom axis") .attr("transform", "translate(0," + height + ")") .call(xAxis.orient("bottom")); var timeLine = {} timeLine.getXPos = function() { var current = new Date() current_semi_processed = current.getHours() + ":" + current.getMinutes() return timeScale(timePrintFormat.parse(current_semi_processed)) } timeLine.drawCurrentTime = function() { var xPos = timeLine.getXPos(); svg.append("line") .classed('currentTime', true) .attr("x1", xPos) .attr("x2", xPos) .attr("y1", 0) .attr("y2", height) .style("stroke", "maroon") } timeLine.updateCurrentTime = function() { var xPos = timeLine.getXPos(); d3.select('line.currentTime').transition() .duration(1000) .attr("x1", xPos) .attr("x2", xPos) } timeLine.drawCurrentTime() d3.timer(timeLine.updateCurrentTime, 1000 * 60) // every minute update current time line }