<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<script src="https://code.jquery.com/jquery-3.1.0.js"></script>
<script src="https://d3js.org/d3.v4.min.js" charset="utf-8"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-tip/0.7.1/d3-tip.min.js"></script>
<link rel="stylesheet" type="text/css" href="./style.css" media="screen"/>
</head>
<body>
<div id="container">
<div id="cheat"></div>
<div id="cspark"></div>
</div>
<script src="./script.js"></script>
</body>
</html>
var itemSize = 17;
var cellSize = itemSize - 1;
var margin = {top: 50, right: 20, bottom: 20, left: 20};
var width = 120 - margin.right - margin.left;
var height = 200 - margin.top - margin.bottom;
var svg = d3.select('#cheat')
.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 tip = d3.tip()
.attr("class", "d3-tip")
.offset([-10, 0])
.html(function(d) {
return "<div><span>Region:</span> <span style='color:white'>" + d.name + "</span></div>" +
"<div><span>Year:</span> <span style='color:white'>" + d.year + "</span></div>" +
"<div><span>Value:</span> <span style='color:white'>" + d.m2 + "</span></div>";
});
var tip2 = d3.tip()
.attr("class", "d3-tip")
.offset([-10, 0])
.html(function(d) {
return "<div><span>Region:</span> <span style='color:white'>" + d.name + "</span></div>" +
"<div><span>Year:</span> <span style='color:white'>" + d.year + "</span></div>" +
"<div><span>Value:</span> <span style='color:white'>" + d.m2 + "</span></div>";
});
svg.call(tip);
svg.call(tip2);
var domain3 = [0, 80, 90, 95, 100];
var range3 = ['#7A453C', '#956882', '#75A0B9', '#6ED2B0', '#D9F284'];
var colorScale = d3.scaleLinear()
.domain(domain3)
.range(range3);
// load data files
d3.queue()
.defer(d3.csv, './data.csv')
.await(createHeatmap);
///////////////////////////////////////////////////////////
//data heatmap
///////////////////////////////////////////////////////////
function createHeatmap(error, data) {
if(error) {
throw error;
}
var data = data.map(function(item) {
var newItem = {};
newItem.name = item.NAME;
newItem.year = item.YEAR;
newItem.m2 = item.M2;
return newItem;
});
var years = data.map(function(d) {
return d.year;
});
var x_elements = d3.set(years).values();
var names = data.map(function(d) {
return d.name;
});
var y_elements = d3.set(names).values();
var xScale = d3.scaleBand()
.domain(x_elements)
.range([0, x_elements.length * itemSize]);
var xAxis = d3.axisTop(xScale)
.tickFormat(function(d) {
return d;
});
var yScale = d3.scaleBand()
.domain(y_elements)
.range([0, y_elements.length * itemSize]);
var yAxis = d3.axisLeft(yScale)
.tickFormat(function(d) {
return d;
});
///////////////////////////////////////////////////////////
//data line scale
///////////////////////////////////////////////////////////
var groupBy = function(array, key) {
return array.reduce(function(a, v) {
(a[v[key]] = a[v[key]] || []).push(v);
return a;
}, {});
};
var groupedData = groupBy(data, 'name');
var dataWithoutMissingData = [];
for(var i = 0; i < data.length; i++) {
if(data[i].m2 != '') {
dataWithoutMissingData.push(Number(data[i].m2));
}
}
const minYvalue = Math.min.apply(Math, dataWithoutMissingData);
const maxYvalue = Math.max.apply(Math, dataWithoutMissingData);
var x = d3.scaleLinear().domain([0, 4]).range([0, 50]);
var y = d3.scaleLinear().domain([minYvalue, maxYvalue]).range([itemSize-2, 2]);
///////////////////////////////////////////////////////////
// draw HEATMAP
///////////////////////////////////////////////////////////
var cells = svg.selectAll('rect')
.data(data)
.enter()
.append('g')
.append('rect')
.attr('data-id',function(d) {
var data = ['A','B','C']
var idx = data.indexOf(d.name)
return idx;
})
.attr('data-value',function(d) {
return d.m2;
})
.attr('data-idx',function(d,i) {
if(i<3){
var idx = 0
}else if(i<6){
var idx = 1
}else if(i<9){
var idx = 2
}else if(i<12){
var idx = 3
}else if(i<15){
var idx = 4
}
return idx;
})
.attr('class', 'cell')
.attr('width', cellSize)
.attr('height', cellSize)
.attr("rx", 4)
.attr("ry", 4)
.attr('x', function(d) {
return xScale(d.year);
})
.attr('y', function(d) {
return yScale(d.name);
})
.attr('fill', function(d) {
var col;
if(d.m2 == "") {
col = "tomato";
}
else {
col = colorScale(d.m2);
}
return col;
})
.on("mouseover", function(d, i){
var id = $(this).attr('data-id')
var value = $(this).attr('data-value')
var idx = $(this).attr('data-idx')
d3.select('#data-svg-'+id)
.append('circle')
.attr("r", 2)
.style("stroke",'red')
.attr('cx', x(idx))
.attr('cy', y(value))
tip.show(d, i)
})
.on("mouseout", function(d,i){
tip.hide(d, i)
d3.selectAll('.data-svg').selectAll('circle').remove()
});
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.selectAll('text')
.attr('font-weight', 'normal');
svg.append("g")
.attr("class", "x axis")
.call(xAxis)
.selectAll('text')
.attr('font-weight', 'normal')
.style("text-anchor", "start")
.attr("dx", ".8em")
.attr("dy", ".5em")
.attr("transform", function(d) {
return "rotate(-65)";
});
///////////////////////////////////////////////////////////
// draw SPARKLINES
///////////////////////////////////////////////////////////
var line = d3.line()
.x(function(d, i) {
console.log('daa'+i)
return x(i);
})
.y(function(d) {
console.log('daa'+d)
return y(d);
})
.defined(function(d){
return d !== 0
})
Object.keys(groupedData).forEach(function(key,i) {
const sparkData = groupedData[key].map(function(datum) {
return Number(datum['m2']);
})
var sparkSvg = d3.select("#cspark")
.append("svg")
.attr("id","data-svg-"+i)
.attr("data-id",i)
.attr("class","data-svg")
.attr("width", "100%")
.attr("height", itemSize-1)
.on('mousemove', function() { // mouse moving over canvas
var mouse = d3.mouse(this)
var id = $(this).attr('data-id')
var data = d3.select(this).select('path').data()
var element = document.getElementById('data-path-'+id);
console.log(data)
var pos = get_data_on_line(data,mouse)
d3.selectAll('.data-svg').selectAll('circle').remove()
d3.select('#data-svg-'+id)
.append('circle')
.attr("r", 2)
.style("stroke",'red')
.attr('cx', x(pos[1]))
.attr('cy', y(pos[0]))
d3.selectAll('rect').each(function(d,i){
var rect_id = $(this).attr('data-id')
var rect_idx = $(this).attr('data-idx')
var rect_value = $(this).attr('data-value')
d3.select(this).style('opacity','1')
if(rect_id == id && rect_idx == pos[1] ){
d3.select(this).style('opacity','0.5')
tip2.show(d,this)
}
})
})
.on("mouseout", function(d,i){
tip2.hide(d, i)
d3.selectAll('.data-svg').selectAll('circle').remove()
d3.selectAll('rect').style('opacity','1')
})
.append("path")
.datum(sparkData)
.attr("class", "sparkline-path" )
.attr("id","data-path-"+i)
.attr("d", line(sparkData));
})
}
function get_data_on_line(data,mouse){
var x = d3.scaleLinear().domain([0, 4]).range([0, 50]);
var x1 = x.invert(mouse[0]);
var idx = Math.round(x1)
return [data[0][idx],idx]
}
#cheat {
float: left;
}
#cspark {
margin-top: 50px;
width: 50px;
float: left;
}
#cspark svg {
display: inherit;
border: 0.5px lightgrey solid;
}
.sparkline-path {
stroke: black;
stroke-width: 1;
fill: none;
}
.axis path, .axis line {
fill: none;
stroke: none;
shape-rendering: crispEdges;
}
.axis text {
fill: #000;
font-size: 9pt;
}
.d3-tip {
line-height: 1;
font: 10px sans-serif;
padding: 12px;
background: rgba(0, 0, 0, 0.8);
color: rgb(185, 185, 185);
border-radius: 2px;
}
.d3-tip:after {
box-sizing: border-box;
display: inline;
font-size: 10px;
width: 100%;
line-height: 1;
color: rgba(0, 0, 0, 0.8);
content: "\25BC";
position: absolute;
text-align: center;
}
.d3-tip.n:after {
margin: -1px 0 0 0;
top: 100%;
left: 0;
}
NAME,YEAR,M1,M2
A,2000,20,5
B,2000,30,1
C,2000,,10
A,2001,44,48
B,2001,15,51
C,2001,20,5
A,2002,8,99
B,2002,92,52
C,2002,62,
A,2003,2,5
B,2003,89,78
C,2003,9,
A,2004,2,55
B,2004,89,58
C,2004,9,55