block by syntagmatic 7e7ee384f7cf515f6602

Cardiac Surgery by Surgeon

Full Screen

This example contains bugs!

Cardiac Surgery by Surgeon: Beginning 2008

Based on Mostapha’s Mouse Over Example

index.html

<!DOCTYPE html>

<style>
body {
  font-family: sans-serif;
  font-size: 14px;
}

#tooltip {
  font-family: sans-serif;
  font-size: 12px;
  font-weight: bold;
  color:black;
}

</style>
	
<body>

<link rel="stylesheet" type="text/css" href="//mostapharoudsari.github.io/Honeybee/pc_source_files/css/d3.parcoords.css">
<script src="//d3js.org/d3.v3.min.js"></script>
<script src = "//mostapharoudsari.github.io/Honeybee/pc_source_files/d3/d3.parcoords.js"></script>

<div id="wrapper" class="parcoords" style="width:100%; height:500px"></div>
		
<script>

var color_set = d3.scale.quantile()
  .range(["#d73027","#f46d43","#fdae61","#fee08b","#ffffbf","#d9ef8b","#a6d96a","#66bd63","#1a9850"].reverse());


// load default chart
d3.csv("cardiac_surgery.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: 100, left: 3 * textLength, bottom: 70, right: 20 })
		.alpha(0.5)
    .hideAxis(["Detailed Region", "Region", "Hospital Name", "Year of Hospital Discharge", "Comparison Results", "Procedure"])
		.composite("darker")
		.mode("queue")
		.rate(5)
		.render()
		.brushMode("1D-axes")  // enable brushing
		//.reorderable() // I removed this for now as it can mess up with tooltips
		.interactive();

update_colors("Expected Mortality Rate");

 // click label to activate coloring
graph.svg.selectAll(".dimension")
    .on("click", update_colors)
    .selectAll(".label")
    .attr("transform", "translate(2,-9) rotate(-8)");

//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([Math.sqrt(d3.min(values)), Math.sqrt(d3.max(values))]);
	
	// change colors for each line
	graph.color(function(d){return color_set(Math.sqrt(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)
      .style("pointer-events", "none");

	// 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];})    
      .style("pointer-events", "none");
}

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 (clicked.length > 1) return;
	    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>