|
//Width and height |
|
var w = 600; |
|
var h = 600; |
|
var padding = 25; |
|
var colors; |
|
var firstData; |
|
var secondData; |
|
var words; |
|
var scalesFirstData; |
|
var scalesSecondData; |
|
|
|
// Reset things |
|
d3.select("svg") |
|
.remove(); |
|
|
|
//Create SVG element |
|
var svg = d3.select("#chart") |
|
.append("svg") |
|
.attr("width", w) |
|
.attr("height", h); |
|
|
|
//Define clipping path |
|
svg.append("clipPath") //Make a new clipPath |
|
.attr("id", "chart-area") //Assign an ID |
|
.append("rect") //Within the clipPath, create a new rect |
|
.attr("x", padding) //Set rect's position and size… |
|
.attr("y", padding) |
|
.attr("width", w - padding * 3) |
|
.attr("height", h - padding * 2); |
|
|
|
// Define the div for the tooltip |
|
var div = d3.select("body") |
|
.append("div") |
|
.attr("class", "tooltip") |
|
.style("opacity", 0); |
|
|
|
//Create scale functions |
|
function getScales(dataset) { |
|
var xs = dataset.map(function(d) {return d[0]}); |
|
|
|
var ys = dataset.map(function(d) {return d[1]}); |
|
|
|
//Create scale functions |
|
var xScale = d3.scale |
|
.linear() |
|
.domain( |
|
[Math.min(...xs), |
|
Math.max(...xs) |
|
] |
|
) |
|
.range([padding, w - padding * 2]); |
|
|
|
var yScale = d3.scale |
|
.linear() |
|
.domain( |
|
[Math.min(...ys), |
|
Math.max(...ys) |
|
] |
|
) |
|
.range([w - padding * 2, padding]); |
|
|
|
return [xScale, yScale]; |
|
} |
|
|
|
function sliderInput() { |
|
makeTransition(); |
|
} |
|
|
|
function interpolate(i) { |
|
var t = d3.select('#slider') |
|
.property('value')/10000; |
|
|
|
var newX = d3.interpolate( |
|
scalesFirstData[0](firstData[i][0]), |
|
scalesSecondData[0](secondData[i][0]) |
|
); |
|
|
|
var newY = d3.interpolate( |
|
scalesFirstData[1](firstData[i][1]), |
|
scalesSecondData[1](secondData[i][1]) |
|
); |
|
|
|
return [newX(t), newY(t)]; |
|
} |
|
|
|
function makeTransition() { |
|
|
|
|
|
svg.selectAll("circle") |
|
.attr("cx", function(d, i) { |
|
var interpolatedX = interpolate(i)[0]; |
|
return interpolatedX; |
|
}) |
|
.attr("cy", function(d, i) { |
|
var interpolatedY = interpolate(i)[1]; |
|
return interpolatedY; |
|
}); |
|
} |
|
|
|
function reset() { |
|
document.getElementById('slider').value = 0; |
|
makeTransition(); |
|
} |
|
|
|
//On click, update with new data |
|
d3.select("#reset") |
|
.on("click", function() { |
|
reset(); |
|
}); |
|
|
|
// On click, start the animation |
|
d3.select("#start") |
|
.on("click", function() { |
|
d3.timer(elapsed => { |
|
document.getElementById('slider').value = elapsed * 2 |
|
|
|
makeTransition(); |
|
|
|
if(elapsed >= 5000) { |
|
return true; // this will stop the d3 timer. |
|
} |
|
}); |
|
}); |
|
|
|
var zoom = d3.behavior.zoom() |
|
.translate([0, 0]) |
|
.scale(1) |
|
.scaleExtent([1, 8]) |
|
.on("zoom", zoomed); |
|
|
|
function zoomed(){ |
|
svg.selectAll("circle").attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")"); |
|
|
|
svg.selectAll("text").attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")"); |
|
} |
|
|
|
function showPlot() { |
|
var xScale = scalesFirstData[0]; |
|
var yScale = scalesFirstData[1]; |
|
|
|
//Define X axis |
|
var xAxis = d3.svg.axis() |
|
.scale(xScale) |
|
.orient("bottom") |
|
.ticks(5); |
|
|
|
//Define Y axis |
|
var yAxis = d3.svg.axis() |
|
.scale(yScale) |
|
.orient("left") |
|
.ticks(5); |
|
|
|
//Create circles |
|
svg.append("g") //Create new g |
|
.attr("id", "circles") //Assign ID of 'circles' |
|
.attr("clip-path", "url(#chart-area)") //Add reference to clipPath |
|
.selectAll("circle") |
|
.data(firstData) |
|
.enter() |
|
.append("circle") |
|
.attr("cx", function(d) { |
|
return xScale(d[0]); |
|
}) |
|
.attr("cy", function(d) { |
|
return yScale(d[1]); |
|
}) |
|
.attr("r", 2) |
|
.attr("fill", function(d, i) { |
|
var color = 'black'; |
|
if (colors) {color = colors[i]}; |
|
return color; |
|
}) |
|
.style("opacity", function(d, i) { |
|
var opacity = 0.5; |
|
if (colors) {opacity = 0.8}; |
|
return opacity; |
|
}) |
|
.on("mouseover", function(d, i) { |
|
div.transition() |
|
.duration(200) |
|
.style("opacity", .9); |
|
var text = i; |
|
if (words && i == 0) text = "<ignore>"; |
|
if (words && i >= 1) text = words[i-1]; |
|
if (words) text = words[i]; |
|
div .html(text) |
|
.style("left", (d3.event.pageX) + "px") |
|
.style("top", (d3.event.pageY - 28) + "px"); |
|
} |
|
) |
|
.on("mouseout", function(d) { |
|
div.transition() |
|
.duration(500) |
|
.style("opacity", 0); |
|
} |
|
) |
|
.call(zoom); |
|
|
|
//Create X axis |
|
svg.append("g") |
|
.attr("class", "x axis") |
|
.attr("transform", "translate(0," + (h - padding) + ")") |
|
.call(xAxis); |
|
|
|
//Create Y axis |
|
svg.append("g") |
|
.attr("class", "y axis") |
|
.attr("transform", "translate(" + padding + ",0)") |
|
.call(yAxis); |
|
} |
|
|
|
function toggleSlider() { |
|
if(firstData && secondData) { |
|
document.getElementById("slider").style.visibility = "visible"; |
|
document.getElementById("start").style.visibility = "visible"; |
|
document.getElementById("reset").style.visibility = "visible"; |
|
document.getElementById('slider').value = 0; |
|
} |
|
else { |
|
document.getElementById("slider").style.visibility = "hidden"; |
|
document.getElementById("start").style.visibility = "hidden"; |
|
document.getElementById("reset").style.visibility = "hidden"; |
|
} |
|
} |
|
|
|
// File upload functions |
|
$(document).ready(function() { |
|
|
|
toggleSlider(); |
|
|
|
var uploadColorsFile = function(evt) { |
|
svg.selectAll("circle").remove(); |
|
|
|
var file = evt.target.files[0]; |
|
var reader = new FileReader(); |
|
reader.readAsText(file); |
|
reader.onload = function(event) { |
|
colors = $.csv.toArrays(event.target.result); |
|
|
|
toggleSlider(); |
|
|
|
if (firstData != null && firstData !== "") { |
|
showPlot(); |
|
} |
|
}; |
|
}; |
|
|
|
// Upload selected file and create array |
|
var uploadFile = function(evt) { |
|
svg.selectAll("g").remove(); |
|
var file = evt.target.files[0]; |
|
var reader = new FileReader(); |
|
reader.readAsText(file); |
|
reader.onload = function(event) { |
|
firstData = $.csv.toArrays( |
|
event.target.result); |
|
if(firstData !== null && firstData !== "") { |
|
scalesFirstData = getScales(firstData); |
|
showPlot(); |
|
toggleSlider(); |
|
} |
|
}; |
|
}; |
|
|
|
var uploadSecondData = function(evt) { |
|
var file = evt.target.files[0]; |
|
var reader = new FileReader(); |
|
reader.readAsText(file); |
|
reader.onload = function(event) { |
|
secondData = $.csv.toArrays( |
|
event.target.result); |
|
if(secondData !== null && secondData !== "") { |
|
scalesSecondData = getScales(secondData); |
|
toggleSlider(); |
|
} |
|
}; |
|
}; |
|
|
|
var uploadWordList = function(evt) { |
|
var file = evt.target.files[0]; |
|
var reader = new FileReader(); |
|
reader.readAsText(file); |
|
reader.onload = function(event) { |
|
words = $.csv.toArrays( |
|
event.target.result); |
|
}; |
|
}; |
|
|
|
// Confirm browser supports HTML5 File API |
|
var browserSupportFileUpload = function() { |
|
var isCompatible = false; |
|
if(window.File && window.FileReader && window.FileList && window.Blob) { |
|
isCompatible = true; |
|
} |
|
return isCompatible; |
|
}; |
|
|
|
// event listener for file upload |
|
if (browserSupportFileUpload()) { |
|
document.getElementById('txtFileUpload').addEventListener('change', uploadFile, false); |
|
|
|
document.getElementById('colorsFileUpload').addEventListener('change', uploadColorsFile, false); |
|
|
|
document.getElementById('secondDataUpload').addEventListener('change', uploadSecondData, false); |
|
|
|
document.getElementById('wordListUpload').addEventListener('change', uploadWordList, false); |
|
} else { |
|
$("#introHeader").html('The File APIs is not fully supported in this browser. Please use another browser.'); |
|
} |
|
}); |