This is an example for adding mousehover event to Parallel Coordinates. I needed to make small changes to sourcecode. If you want to try this you need to address this file until Kai implements it to source code.
Last active
November 26, 2020 04:03
-
-
Save mostaphaRoudsari/b4e090bb50146d88aec4 to your computer and use it in GitHub Desktop.
Parallel Coordinates with mouseover highlight and tooltip
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
license: mit |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<style> | |
#wrapper { | |
position: relative; | |
float: left; | |
top: 20px; | |
font-family: sans-serif; | |
font-size: 10px; | |
} | |
#tooltip{ | |
font-family: sans-serif; | |
font-size: 14px; | |
font-weight: bold; | |
color:black; | |
} | |
</style> | |
<body> | |
<link rel="stylesheet" type="text/css" href="http://mostapharoudsari.github.io/honeybee/pc_source_files/css/d3.parcoords.css"> | |
<script src="http://d3js.org/d3.v3.min.js"></script> | |
<script src = "http://mostapharoudsari.github.io/honeybee/pc_source_files/d3/d3.parcoords.js"></script> | |
<div id="wrapper" class="parcoords" style="width:100%; height:420px"></div> | |
<script> | |
var color_set = d3.scale.linear() | |
.range(["#3182bd", "#f33"]); | |
// load default chart | |
d3.csv("results.csv", function(data){ | |
// collect text for first column to adjust left margin | |
var firstCell = data.map(function(d){return d3.values(d)[0]}); | |
// find the longest text size in the first row to adjust left margin | |
var textLength = 0; | |
firstCell.forEach(function(d){ | |
if (d.length > textLength) textLength = d.length; | |
}); | |
// get parallel coordinates | |
graph = d3.parcoords()('#wrapper') | |
.data(data) | |
.margin({ top: 30, left: 3 * textLength, bottom: 40, right: 0 }) | |
.alpha(0.6) | |
.mode("queue") | |
.rate(5) | |
.render() | |
.brushMode("1D-axes") // enable brushing | |
//.reorderable() // I removed this for now as it can mess up with tooltips | |
.interactive(); | |
// add instruction text | |
var instructions = "-Drag around axis to begin brush. -Click axis to clear brush. -Click a label to color data based on axis values. -Hover on each line to highlight." | |
d3.select("#wrapper svg").append("text") | |
.text(instructions) | |
.attr("text-anchor", "middle") | |
.attr("text-decoration", "overline") | |
.attr("transform", "translate(" + graph.width()/2 + "," + (graph.height()-5) + ")");; | |
// set the initial coloring based on the 3rd column | |
update_colors(d3.keys(data[0])[2]); | |
// click label to activate coloring | |
graph.svg.selectAll(".dimension") | |
.on("click", update_colors) | |
.selectAll(".label") | |
.style("font-size", "14px"); // change font sizes of selected lable | |
//add hover event | |
d3.select("#wrapper svg") | |
.on("mousemove", function() { | |
var mousePosition = d3.mouse(this); | |
highlightLineOnClick(mousePosition, true); //true will also add tooltip | |
}) | |
.on("mouseout", function(){ | |
cleanTooltip(); | |
graph.unhighlight(); | |
}); | |
}); | |
// update color and font weight of chart based on axis selection | |
// modified from here: https://syntagmatic.github.io/parallel-coordinates/ | |
function update_colors(dimension) { | |
// change the fonts to bold | |
graph.svg.selectAll(".dimension") | |
.style("font-weight", "normal") | |
.filter(function(d) { return d == dimension; }) | |
.style("font-weight", "bold"); | |
// change color of lines | |
// set domain of color scale | |
var values = graph.data().map(function(d){return parseFloat(d[dimension])}); | |
color_set.domain([d3.min(values), d3.max(values)]); | |
// change colors for each line | |
graph.color(function(d){return color_set([d[dimension]])}).render(); | |
}; | |
// Add highlight for every line on click | |
function getCentroids(data){ | |
// this function returns centroid points for data. I had to change the source | |
// for parallelcoordinates and make compute_centroids public. | |
// I assume this should be already somewhere in graph and I don't need to recalculate it | |
// but I couldn't find it so I just wrote this for now | |
var margins = graph.margin(); | |
var graphCentPts = []; | |
data.forEach(function(d){ | |
var initCenPts = graph.compute_centroids(d).filter(function(d, i){return i%2==0;}); | |
// move points based on margins | |
var cenPts = initCenPts.map(function(d){ | |
return [d[0] + margins["left"], d[1]+ margins["top"]]; | |
}); | |
graphCentPts.push(cenPts); | |
}); | |
return graphCentPts; | |
} | |
function getActiveData(){ | |
// I'm pretty sure this data is already somewhere in graph | |
if (graph.brushed()!=false) return graph.brushed(); | |
return graph.data(); | |
} | |
function isOnLine(startPt, endPt, testPt, tol){ | |
// check if test point is close enough to a line | |
// between startPt and endPt. close enough means smaller than tolerance | |
var x0 = testPt[0]; | |
var y0 = testPt[1]; | |
var x1 = startPt[0]; | |
var y1 = startPt[1]; | |
var x2 = endPt[0]; | |
var y2 = endPt[1]; | |
var Dx = x2 - x1; | |
var Dy = y2 - y1; | |
var delta = Math.abs(Dy*x0 - Dx*y0 - x1*y2+x2*y1)/Math.sqrt(Math.pow(Dx, 2) + Math.pow(Dy, 2)); | |
//console.log(delta); | |
if (delta <= tol) return true; | |
return false; | |
} | |
function findAxes(testPt, cenPts){ | |
// finds between which two axis the mouse is | |
var x = testPt[0]; | |
var y = testPt[1]; | |
// make sure it is inside the range of x | |
if (cenPts[0][0] > x) return false; | |
if (cenPts[cenPts.length-1][0] < x) return false; | |
// find between which segment the point is | |
for (var i=0; i<cenPts.length; i++){ | |
if (cenPts[i][0] > x) return i; | |
} | |
} | |
function cleanTooltip(){ | |
// removes any object under #tooltip is | |
graph.svg.selectAll("#tooltip") | |
.remove(); | |
} | |
function addTooltip(clicked, clickedCenPts){ | |
// sdd tooltip to multiple clicked lines | |
var clickedDataSet = []; | |
var margins = graph.margin() | |
// get all the values into a single list | |
// I'm pretty sure there is a better way to write this is Javascript | |
for (var i=0; i<clicked.length; i++){ | |
for (var j=0; j<clickedCenPts[i].length; j++){ | |
var text = d3.values(clicked[i])[j]; | |
// not clean at all! | |
var x = clickedCenPts[i][j][0] - margins.left; | |
var y = clickedCenPts[i][j][1] - margins.top; | |
clickedDataSet.push([x, y, text]); | |
} | |
}; | |
// add rectangles | |
var fontSize = 14; | |
var padding = 2; | |
var rectHeight = fontSize + 2 * padding; //based on font size | |
graph.svg.selectAll("rect[id='tooltip']") | |
.data(clickedDataSet).enter() | |
.append("rect") | |
.attr("x", function(d) { return d[0] - d[2].length * 5;}) | |
.attr("y", function(d) { return d[1] - rectHeight + 2 * padding; }) | |
.attr("rx", "2") | |
.attr("ry", "2") | |
.attr("id", "tooltip") | |
.attr("fill", "grey") | |
.attr("opacity", 0.9) | |
.attr("width", function(d){return d[2].length * 10;}) | |
.attr("height", rectHeight); | |
// add text on top of rectangle | |
graph.svg.selectAll("text[id='tooltip']") | |
.data(clickedDataSet).enter() | |
.append("text") | |
.attr("x", function(d) { return d[0];}) | |
.attr("y", function(d) { return d[1]; }) | |
.attr("id", "tooltip") | |
.attr("fill", "white") | |
.attr("text-anchor", "middle") | |
.attr("font-size", fontSize) | |
.text( function (d){ return d[2];}) | |
} | |
function getClickedLines(mouseClick){ | |
var clicked = []; | |
var clickedCenPts = []; | |
// find which data is activated right now | |
var activeData = getActiveData(); | |
// find centriod points | |
var graphCentPts = getCentroids(activeData); | |
if (graphCentPts.length==0) return false; | |
// find between which axes the point is | |
var axeNum = findAxes(mouseClick, graphCentPts[0]); | |
if (!axeNum) return false; | |
graphCentPts.forEach(function(d, i){ | |
if (isOnLine(d[axeNum-1], d[axeNum], mouseClick, 2)){ | |
clicked.push(activeData[i]); | |
clickedCenPts.push(graphCentPts[i]); // for tooltip | |
} | |
}); | |
return [clicked, clickedCenPts] | |
} | |
function highlightLineOnClick(mouseClick, drawTooltip){ | |
var clicked = []; | |
var clickedCenPts = []; | |
clickedData = getClickedLines(mouseClick); | |
if (clickedData && clickedData[0].length!=0){ | |
clicked = clickedData[0]; | |
clickedCenPts = clickedData[1]; | |
// highlight clicked line | |
graph.highlight(clicked); | |
if (drawTooltip){ | |
// clean if anything is there | |
cleanTooltip(); | |
// add tooltip | |
addTooltip(clicked, clickedCenPts); | |
} | |
} | |
}; | |
</script> | |
</body> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Case_Description | Heating | Cooling | Pumps & Aux | Vent. Fans | |
---|---|---|---|---|---|
Base Design | 132573 | 60471 | 13158 | 149825 | |
0+ASHRAE Walls | 139144 | 61356 | 12793 | 151225 | |
0+ASHRAE Window | 150893 | 65522 | 12194 | 154333 | |
0+ASHRAE C factor | 133043 | 57580 | 13080 | 149926 | |
0+ASHRAE Fridge | 131315 | 61327 | 13252 | 150119 | |
0+ASHRAE Dishwasher | 132258 | 60717 | 13184 | 149868 | |
0+ASHRAE Rf | 136384 | 60390 | 12972 | 150420 | |
0+ASHRAE fan speed | 132572 | 60474 | 13158 | 150275 | |
0+ASHRAE fan power | 136552 | 58833 | 13019 | 90551 | |
0+Single Speed Compressor | 143919 | 74848 | 12806 | 149825 | |
0+ASHRAE Clng Eff | 134399 | 72331 | 13821 | 147832 | |
0+ASHRAE Htng Eff | 136286 | 60471 | 13025 | 150450 | |
0+ASHRAE Ext light | 132573 | 60471 | 13158 | 149825 | |
0+ASHRAE DHW | 132573 | 60471 | 13158 | 149825 | |
0+ASHRAE DHW eff | 132573 | 60471 | 13158 | 149825 |
Hi @lee1043, Sorry for the late reply. I renamed Honeybee
repository to honeybee
and that's why it was failing. It should work fine now.
Hi @mostaphaRoudsari I'm coming back to this example, using a tree species dataset in Boston, and I was wondering if you could relink to your site to work with this code.
Thanks!
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi @mostaphaRoudsari, I am very interested to use this capability for my project, but couldn't find below dependent files:
It seems the link is broken, wondering if you could help me to get these files.
Thank you very much.