D3 how to create round axis and style options - javascript

I needed to code a simple app with a graph so I chose D3. I also use the ionic framework to code the app and I implemented all the code in a controller. It displays correct in the app. I know it's best practice to put this in a directive but I also had no luck on making that work.
But I have some trouble learning D3, I need to make a thermometer but I end up with a square. I followed tutorials and did my best. Can someone help me with this?
it needs to look something like this therometer
Code:
$scope.height = window.innerHeight - 110;
/**
* d3 code for the thermometer! d3 is a library for javascript and injected in the index.html in the www map.
*/
//this is an array for the chart is using for the rectangle in the chart itself. it gets it data form the ChallengeService
var bardata = [$scope.challenge.amount];
//this code is used to correctly show the axes of the chart.
var margin = {top: 30 , right: 30 , bottom: 40 , left: 50 };
// these are values needed to drawn the chart correctly $scope.height is the ion-view - header bar - footer bar.
var height = $scope.height - margin.top - margin.bottom,
width = 200 - margin.left - margin.right,
barWidth = 50,
barOffset = 5,
border = 5,
bordercolor = '#F5F5F5';
//used for the axis in scaling
var yScale = d3.scale.linear()
.domain([0, 2184])
.range([0, height]);
var xScale = d3.scale.ordinal()
.domain(d3.range(0, bardata.length))
.rangeBands([0, width])
//this is the actual chart that gets drawn.
var myChart = d3.select('#chart').append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.attr('id' , 'mijnsvg')
.append('g')
.attr('transform', 'translate(' +margin.left +','+margin.right +')')
.selectAll('rect').data(bardata)
.enter().append('rect')
.style('fill', '#d0e4db')
.attr('width', xScale.rangeBand())
.attr('x', function(d,i) {
return xScale(i);
})
//.attr('ry', 75)
.attr('height', 0)
.attr('y', height)
//if they want a border this is the way keep in mind that it will shrink the graph and you need to adjust it when drawing!
//.style("stroke", bordercolor)
//.style("stroke-width", border)
// used for how the chart is rendered this allows me to delay the drawing of the chart till the moment the users is on the tab.
myChart.transition()
.attr('height', function(d) {
return yScale(d);
})
.attr('y', function(d) {
return height - yScale(d);
})
.delay(function(d, i) {
return i * 50;
})
.duration(1300)
.ease('exp')
// theese are both axes being drawn
var vGuideScale1 = d3.scale.linear()
.domain([0, 2184])
.range([height, 0])
var vGuideScale2 = d3.scale.linear()
.domain([0, 2184])
.range([height, 0])
var vAxisLeft = d3.svg.axis()
.scale(vGuideScale1)
.orient('right')
.ticks(5)
.tickFormat(function(d){
return '€ ' + parseInt(d);
})
var vAxisRight = d3.svg.axis()
.scale(vGuideScale2)
.orient('right')
// i use this in d3 to draw the right line otherwise it will fill in values.
.ticks("")
var vGuide = d3.select('#mijnsvg').append('g')
vAxisLeft(vGuide)
vGuide.attr('transform', 'translate('+ margin.left+',' + margin.top+ ')')
vGuide.attr('rx', 50)
vGuide.selectAll('path')
.style({ fill: '#368169', stroke: "#368169"})
vGuide.selectAll('line')
.style({ stroke: "#368169"})
var vGuideRight = d3.select('#mijnsvg').append('g')
vAxisRight(vGuideRight)
vGuideRight.attr('transform', 'translate('+ 164.5 + ',' + margin.top +')' )
vGuideRight.selectAll('path')
.style({ fill: '#368169', stroke: "#368169"})
vGuideRight.selectAll('line')
.style({ stroke: "#368169"})
var hAxis = d3.svg.axis()
.scale(xScale)
.orient('top')
.tickValues('')
var hGuide = d3.select('#mijnsvg').append('g')
hAxis(hGuide)
hGuide.attr('transform', 'translate('+ margin.left+',' + margin.top + ')')
hGuide.selectAll('path')
.style({ fill: '#368169', stroke: "#368169"})
hGuide.selectAll('line')
.style({ stroke: "#368169"})
.style('xr', 100)

Instead of using rounded corner rect, I'd create a custom path generator:
function roundedRect(w, x, d) { // width of bar, x position and datum value
var arcHeight = 30; // height of arc
var p = "";
p += "M" + (x) + "," + (height); // move to lower left
p += " L" + (x) + "," + (yScale(d) + arcHeight); // line up to start of arc
p += " Q " + (w * 0.25) + "," + yScale(d) + " " + (w * 0.5 + x) + "," + yScale(d); // arc to center point
p += " Q " + (w * 0.75 + x) + "," + yScale(d) + " " + w + "," + (yScale(d) + arcHeight); // arc to end
p += " L" + w + "," + (height); // line down to bottom
return p;
}
You can use this to draw both the border and "bars".
Here's full working code. I cleaned up the axis a bit and added an attrTween function so you could still keep the animation.
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#3.5.3" data-semver="3.5.3" src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.js"></script>
<style>
.axis path {
display: none;
}
.axis line {
stroke: #d0e4db;
}
line {
shape-rendering: crispEdges;
}
.axis .tick line {
stroke: #368169;
stroke-dasharray: 2, 2;
stroke-width: 4;
}
text {
font-family: Verdana;
font-size: 12px;
}
</style>
</head>
<body>
<div id="chart"></div>
<script>
var $scope = {};
$scope.height = 500;
/**
* d3 code for the thermometer! d3 is a library for javascript and injected in the index.html in the www map.
*/
//this is an array for the chart is using for the rectangle in the chart itself. it gets it data form the ChallengeService
var bardata = [1500];
//this code is used to correctly show the axes of the chart.
var margin = {
top: 30,
right: 30,
bottom: 5,
left: 50
};
// these are values needed to drawn the chart correctly $scope.height is the ion-view - header bar - footer bar.
var height = $scope.height - margin.top - margin.bottom,
width = 200 - margin.left - margin.right,
barWidth = 50,
barOffset = 5,
border = 5,
bordercolor = '#F5F5F5';
//used for the axis in scaling
var yScale = d3.scale.linear()
.domain([0, 2184])
.range([height, 0]);
var xScale = d3.scale.ordinal()
.domain(d3.range(0, bardata.length))
.rangeBands([0, width])
//this is the actual chart that gets drawn.
var svg = d3.select('#chart').append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.attr('id', 'mijnsvg')
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.right + ')');
var myChart = svg
.selectAll('rect').data(bardata)
.enter().append('path')
.style('fill', '#d0e4db');
// used for how the chart is rendered this allows me to delay the drawing of the chart till the moment the users is on the tab.
myChart.transition()
.attrTween("d", function(d) {
var interpolate = d3.interpolate(0, d);
return function(t) {
return roundedRect(xScale.rangeBand() - 15, 15, interpolate(t));
}
})
.delay(function(d, i) {
return i * 50;
})
.duration(1300)
.ease('exp');
var border = svg.append("g")
.append("path")
.attr("d", roundedRect(width, 0, 2184))
.style("stroke", "#368169")
.style("stroke-width", 6)
.style("fill", "none");
var yAxis = d3.svg.axis()
.scale(yScale)
.ticks(4)
.tickSize(width / 2)
.tickPadding(15)
.orient("left");
svg
.append("g")
.attr("transform","translate(" + width / 2 + ",0)")
.attr("class", "y axis")
.call(yAxis);
function roundedRect(w, x, d) {
var arcHeight = 30;
var p = "";
p += "M" + (x) + "," + (height);
p += " L" + (x) + "," + (yScale(d) + arcHeight);
p += " Q " + (w * 0.25 + x/2) + "," + yScale(d) + " " + (w * 0.5 + x/2) + "," + yScale(d);
p += " Q " + (w * 0.75 + x/2) + "," + yScale(d) + " " + w + "," + (yScale(d) + arcHeight);
p += " L" + w + "," + (height);
return p;
}
</script>
</body>
</html>

Related

Data binding in d3 v3 donut chart

So I've been trying to create a donut chart in d3.js and am having trouble adding labels to the chart. My chart data is in an array, but I think because of the "pie" variable, only the "value" from the data is being passed through and not the "text". Have tried multiple ways to try and bring the "text" in but with no luck. Hopefully a fresh set of eyes can see where my mistake is!
var margin = {top: 10, right: 30, bottom: 30, left: 60},
width = 750 - margin.left - margin.right,
height = 520 - margin.top - margin.bottom;
var r = height/3;
var aColor = [
'#0652DD',
'#C4E538',
'#F79F1F',
'#5758BB',
'#D980FA',
"#EA2027"
]
var piedata = [
{text:"Facebook", "value":76},
{text:"Website", "value":13},
{text:"HardwareZone", "value":4},
{text:"YouTube", "value":5},
{text:"Instagram", "value":1},
{text:"Twitter","value":1},
];
var vis = d3.select('#chart2')
.append("svg:svg")
.data([piedata])
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("svg:g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
var pie = d3.layout.pie().sort(null).value(function(d){return d.value;});
// Declare an arc generator function
var arc = d3.svg.arc().innerRadius(r *0.5).outerRadius(r*0.8);
var outerArc = d3.svg.arc()
.innerRadius(r*0.95)
.outerRadius(r*0.95);
// Select paths, use arc generator to draw
var arcs = vis.selectAll("g.slice").data(pie).enter().append("svg:g").attr("class", "slice").attr("transform", "translate(" + width/2 + "," + height/2 + ")");
arcs.append("g:path")
.attr("fill", function(d, i){return aColor[i];})
.attr("d", function (d) {return arc(d);})
.attr("stroke", "white")
.style("stroke-width", "3px")
.style("opacity", 0.7)
;
// Add the polylines between chart and labels:
arcs.append("g:polyline")
.attr("stroke", "black")
.style("fill", "none")
.attr("stroke-width", "1px")
.attr('points', function(d) {
var posA = arc.centroid(d) // line insertion in the slice
var posB = outerArc.centroid(d) + 5 // line break: we use the other arc generator that has been built only for that
var posC = outerArc.centroid(d) + 5; // Label position = almost the same as posB
var midangle = d.startAngle + (d.endAngle - d.startAngle) / 2 // we need the angle to see if the X position will be at the extreme right or extreme left
posC[0] = r * 0.95 * (midangle < Math.PI ? 1 : -1); // multiply by 1 or -1 to put it on the right or on the left
return [posA, posB, posC]
});
//Add text labels
arcs.append("g:label")
.attr('transform', function(d) {
var pos = outerArc.centroid(d);
var midangle = d.startAngle + (d.endAngle - d.startAngle) / 2
pos[0] = r * 0.99 * (midangle < Math.PI ? 1 : -1);
return 'translate(' + pos + ')';
})
.style('text-anchor', function(d) {
var midangle = d.startAngle + (d.endAngle - d.startAngle) / 2
return (midangle < Math.PI ? 'start' : 'end')
})
.text(function(d) { return d.text; }); //this is where the problem is!
Here is how you can add labels:
arcs.append('text')
.text(d => d.data.text)
.attr('dy', 4)
.attr('text-anchor', d => (d.startAngle + d.endAngle) / 2 > Math.PI ? 'end' : 'start')
.attr('x', d => outerArc.centroid(d)[0])
.attr('y', d => outerArc.centroid(d)[1]);
var margin = {top: 10, right: 30, bottom: 30, left: 60},
width = 750 - margin.left - margin.right,
height = 520 - margin.top - margin.bottom;
var r = height/3;
var aColor = [
'#0652DD',
'#C4E538',
'#F79F1F',
'#5758BB',
'#D980FA',
"#EA2027"
]
var piedata = [
{text:"Facebook", "value":76},
{text:"Website", "value":13},
{text:"HardwareZone", "value":4},
{text:"YouTube", "value":5},
{text:"Instagram", "value":1},
{text:"Twitter","value":1},
];
var vis = d3.select('#chart2')
.append("svg:svg")
.data([piedata])
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("svg:g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
var pie = d3.layout.pie().sort(null).value(function(d){return d.value;});
// Declare an arc generator function
var arc = d3.svg.arc().innerRadius(r *0.5).outerRadius(r*0.8);
var outerArc = d3.svg.arc()
.innerRadius(r*0.95)
.outerRadius(r*1.1);
// Select paths, use arc generator to draw
var arcs = vis.selectAll("g.slice").data(pie).enter().append("svg:g").attr("class", "slice").attr("transform", "translate(" + width/2 + "," + height/2 + ")");
arcs.append("g:path")
.attr("fill", function(d, i){return aColor[i];})
.attr("d", function (d) {return arc(d);})
.attr("stroke", "white")
.style("stroke-width", "3px")
.style("opacity", 0.7)
;
// Add the polylines between chart and labels:
arcs.append("g:polyline")
.attr("stroke", "black")
.style("fill", "none")
.attr("stroke-width", "1px")
.attr('points', function(d) {
var posA = arc.centroid(d) // line insertion in the slice
var posB = outerArc.centroid(d) + 5 // line break: we use the other arc generator that has been built only for that
var posC = outerArc.centroid(d) + 5; // Label position = almost the same as posB
var midangle = d.startAngle + (d.endAngle - d.startAngle) / 2 // we need the angle to see if the X position will be at the extreme right or extreme left
posC[0] = r * 0.95 * (midangle < Math.PI ? 1 : -1); // multiply by 1 or -1 to put it on the right or on the left
return [posA, posB, posC]
});
//Add text labels
arcs.append('text')
.text(d => d.data.text)
.attr('dy', 4)
.each(d => console.log(d))
.attr('text-anchor', d => (d.startAngle + d.endAngle) / 2 > Math.PI ? 'end' : 'start')
.attr('x', d => outerArc.centroid(d)[0])
.attr('y', d => outerArc.centroid(d)[1]);
text {
font-size: 16px;
font-family: "Ubuntu";
fill: black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<div id="chart2" />

D3.js Chart Transition Erroring

I'm trying to brush #histogram1 and redraw a sub-chart #histogram2.
The redrawing is not working properly, around line 113 in my example.
The console is occasionally showing errors on the "height" and "y" attributes -
Error: <rect> attribute height: Expected length, "NaN".
Error: <rect> attribute y: Expected length, "NaN".
I am unable to determine where the bad values are coming from?
Can some help me understand what I'm doing wrong?
Thanks
var data = [
{"yr":1940,"type":"E","rate":40},{"yr":1947,"type":"A","rate":20},{"yr":1943,"type":"B","rate":30},{"yr":1950,"type":"B","rate":25},
{"yr":1943,"type":"C","rate":20},{"yr":1941,"type":"A","rate":30},{"yr":1945,"type":"E","rate":40},{"yr":1948,"type":"A","rate":20},
{"yr":1947,"type":"B","rate":30},{"yr":1950,"type":"B","rate":25},{"yr":1945,"type":"C","rate":20},{"yr":1941,"type":"A","rate":30},
{"yr":1944,"type":"B","rate":10},{"yr":1949,"type":"C","rate":20},{"yr":1940,"type":"E","rate":10},{"yr":1940,"type":"E","rate":40},
{"yr":1940,"type":"E","rate":40},{"yr":1947,"type":"A","rate":20},{"yr":1943,"type":"B","rate":30},{"yr":1950,"type":"B","rate":25},
{"yr":1943,"type":"C","rate":20},{"yr":1941,"type":"A","rate":30},{"yr":1945,"type":"E","rate":40},{"yr":1948,"type":"A","rate":20},
{"yr":1947,"type":"B","rate":30},{"yr":1950,"type":"D","rate":25},{"yr":1945,"type":"C","rate":20},{"yr":1941,"type":"A","rate":30},
{"yr":1944,"type":"B","rate":10},{"yr":1949,"type":"C","rate":20},{"yr":1940,"type":"E","rate":10},{"yr":1947,"type":"E","rate":40}
];
// CROSSFILTER Dimensions //
var cfdata = crossfilter(data)
, all = cfdata.groupAll()
, year = cfdata.dimension(function(d) {return d.yr;})
, type = cfdata.dimension(function(d) {return d.type;})
, years= year.group()
, types= type.group().reduceCount()
, typeKeys = types.all()
, keyMap = typeKeys.map (function(d) {return d.key}) ;
// General CHART Dimensions //
var margin = {top: 10, right: 20, bottom: 10, left: 10}
, height = 200 - margin.top - margin.bottom
, width = 400 - margin.left - margin.right
, barPadding = 5 ;
// Setup TOOLTIPS //
var tip = d3.tip()
.attr('class', 'd3-tip')
.html(function(d){return d.value});
// HISTOGRAM 1 : TOTAL BY YEAR //
var min1 = d3.min(years.all(), function(d) {return d.key;})
, max1 = d3.max(years.all(), function(d) {return d.key;})
, range1 = max1 - min1 ;
var xScale1 = d3.scale.linear()
.domain([min1, max1])
.range([0, width]) ;
var yScale1 = d3.scale.linear()
.domain([0, d3.max(years.all(), function(d) {return d.value;})])
.range([height / 2, 0]) ;
var xAxis1 = d3.svg.axis()
.scale(xScale1)
.ticks(5).tickFormat(d3.format("d"))
.orient("bottom") ;
var histogram1 = d3.select("#histogram1").append("svg:svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g");
histogram1.call(tip);
histogram1.append("g")
.attr("class", "axis")
.call(xAxis1)
.attr("transform", "translate(" + margin.left + "," + height / 2 + ")") ;
histogram1.selectAll("rect")
.data(years.all())
.enter().append("rect")
.attr("x", function(d) {return xScale1(d.key) + 0.5 * (width / range1)})
.attr("width", width / range1)
.attr("y", function(d) {return yScale1(d.value);})
.attr("height", function(d) {return (height / 2 - yScale1(d.value));})
.attr("fill", "green")
.attr("stroke", "white")
.on("mouseover", tip.show)
.on("mouseout", tip.hide);
var brush = d3.svg.brush()
.x(xScale1)
.extent([1945, 1946])
.on("brush", brushmove) ;
var brushg = histogram1.append("g")
.attr("class", "brush")
.call(brush) ;
brushg.selectAll("rect")
.attr("height", height / 2) ;
brushg.selectAll(".resize")
.append("path")
.attr("d", resizePath) ;
function brushmove() {
var s = brush.extent()
, lower = parseInt(s[0])
, upper = parseInt(s[1]);
histogram1.selectAll("rect")
.style("opacity", function(d) {return lower <= d.key && d.key <= upper ? "1" : ".2";}) ;
var filt = year.filterRange([lower,upper]);
console.log(filt.top(Infinity));
histogram2.selectAll("rect")
.data(filt.top(Infinity))
.transition()
.attr("y", function(d){ return height - yScale2(d); })
.attr("height", function(d){ return yScale2(d); })
};
// HISTOIGRAM 2 : TOTAL BY TYPE //
var keys2 = typeKeys.map(function(d) {return d.key;})
, min2 = d3.min(types, function(d) {return d.key;})
, max2 = d3.max(types, function(d) {return d.key;})
var xScale2 = d3.scale.ordinal()
.domain(keys2)
.rangeBands([0, width]);
var yScale2 = d3.scale.linear()
.domain([0, d3.max(types.all(), function(d) {return d.value;})])
.range([height / 2, 0]);
var xAxis2 = d3.svg.axis()
.scale(xScale2)
.orient("bottom");
var histogram2 = d3.select("#histogram2").append("svg:svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g");
histogram2.call(tip);
histogram2.append("g")
.attr("class", "axis")
.call(xAxis2)
.attr("transform", "translate(0," + height + ")")
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", function(d) {return "rotate(-65)"});
histogram2.selectAll("rect")
.data(types.all())
.enter().append("rect")
.attr("x", function(d) {return xScale2(d.key);})
.attr("width", width / keyMap.length - barPadding)
.attr("y", function(d) {return yScale2(d.value); })
.attr("height", function(d) {return height - yScale2(d.value);})
.attr("fill", "steelblue")
.on("mouseover", tip.show)
.on("mouseout", tip.hide);
function resizePath(d) {
var e = +(d == "e")
, x = e ? 1 : -1
, y = height / 4;
return "M" + (.5 * x) + "," + y + "A6,6 0 0 " + e + " " + (6.5 * x) + "," + (y + 6) + "V" + (2 * y - 6) + "A6,6 0 0 " + e + " " + (.5 * x) + "," + (2 * y) + "Z" + "M" + (2.5 * x) + "," + (y + 8) + "V" + (2 * y - 8) + "M" + (4.5 * x) + "," + (y + 8) + "V" + (2 * y - 8);
}
/*** d3-tip styles */
.d3-tip {
line-height: 1.5;
padding: 8px;
background: rgba(0, 0, 0, 0.8);
color: #fff;
border-radius: 0px;
text-align: center;
}
.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 {
top: 100%;
left: 0;
margin: -1px 0 0;
}
/*** D3 brush */
.brush .extent {
stroke: #222;
fill-opacity: .125;
shape-rendering: crispEdges;
}
<script src="https://d3js.org/d3.v3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-tip/0.7.1/d3-tip.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crossfilter/1.3.12/crossfilter.min.js"></script>
<div id="histogram1"></div>
<div id="histogram2"></div>
You are using different data when updating the second histogram in response to the brush, from when you initially drew the chart. Generally you'll want to use the same group's data (.all()) in both cases.
In particular,
.data(filt.top(Infinity))
will supply the raw rows of your data to the chart, and
.attr("y", function(d){ return height - yScale2(d); })
.attr("height", function(d){ return yScale2(d); })
will then attempt to pass those row objects to the scale, when the scale is expecting a number. (An object is literally "Not a Number".)
When you apply the filter
year.filterRange([lower,upper]);
that will cause all of the groups in associated crossfilter to re-filter and re-aggregate. (It's very much an imperative, not functional programming, interface. The filter method just returns the same dimension object.)
If you update y and height exactly as you drew it in the first place:
.attr("y", function(d){ return height - yScale2(d.value); })
.attr("height", function(d){ return yScale2(d.value); })
then presto! it filters.
Fiddle with corrected code: http://jsfiddle.net/gordonwoodhull/hjL6rf9u/5/

How to create correlogram using D3 as in the example picture

I am trying to create a corrologram using a set of data using D3.js.
I used R to create the correlation matrix but for visualization I want to use D3js and create a chart which shows the correlation matrix as in the picture. Can anyone guide me on this please.
Interesting problem so I took a whack at it. Using the mtcars dataset and given an R calculated correlation matrix, output in a CSV format using:
write.csv(cor(mtcars), file="data.csv")
Which creates:
"","mpg","cyl","disp","hp","drat","wt","qsec","vs","am","gear","carb"
"mpg",1,-0.852161959426613,-0.847551379262479,-0.776168371826586,0.681171907806749,-0.867659376517228,0.418684033921778,0.664038919127593,0.599832429454648,0.480284757338842,-0.550925073902459
"cyl",-0.852161959426613,1,0.902032872146999,0.83244745272182,-0.69993811382877,0.782495794463241,-0.591242073768869,-0.810811796083005,-0.522607046900675,-0.492686599389471,0.526988293749643
You can replicate your plot with d3:
d3.csv("data.csv", function(error, rows) {
// read in the CSV file and put the data in a d3 format or an array of objects
var data = [];
rows.forEach(function(d) {
var x = d[""]; // x represent the column name
delete d[""];
for (prop in d) {
var y = prop, // y is this row name
value = d[prop]; // correlation value
data.push({
x: x,
y: y,
value: +value
});
}
});
// standard d3 plot setup
var margin = {
top: 25,
right: 80,
bottom: 25,
left: 25
},
width = 500 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom,
domain = d3.set(data.map(function(d) { // our domain is just the column names
return d.x
})).values(),
num = Math.sqrt(data.length), // how many rows and columns
color = d3.scale.linear() // our color scale from red to white to blue
.domain([-1, 0, 1])
.range(["#B22222", "#fff", "#000080"]);
// set-up x and y scale
var x = d3.scale
.ordinal()
.rangePoints([0, width])
.domain(domain),
y = d3.scale
.ordinal()
.rangePoints([0, height])
.domain(domain),
xSpace = x.range()[1] - x.range()[0], // this is the space of each grid space
ySpace = y.range()[1] - y.range()[0];
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 + ")");
// bind our data for each grid space
var cor = svg.selectAll(".cor")
.data(data)
.enter()
.append("g")
.attr("class", "cor")
.attr("transform", function(d) {
return "translate(" + x(d.x) + "," + y(d.y) + ")";
});
// outer rectangle on each grid space
cor.append("rect")
.attr("width", xSpace)
.attr("height", ySpace)
.attr("x", -xSpace / 2)
.attr("y", -ySpace / 2)
// filter out below the diagonal
cor.filter(function(d){
var ypos = domain.indexOf(d.y);
var xpos = domain.indexOf(d.x);
for (var i = (ypos + 1); i < num; i++){
if (i === xpos) return false;
}
return true;
})
// append a text
.append("text")
.attr("y", 5)
.text(function(d) {
if (d.x === d.y) {
return d.x;
} else {
return d.value.toFixed(2);
}
})
// color it
.style("fill", function(d){
if (d.value === 1) {
return "#000";
} else {
return color(d.value);
}
});
// filter above the diagonal
cor.filter(function(d){
var ypos = domain.indexOf(d.y);
var xpos = domain.indexOf(d.x);
for (var i = (ypos + 1); i < num; i++){
if (i === xpos) return true;
}
return false;
})
// add a circle
.append("circle")
.attr("r", function(d){
return (width / (num * 2)) * (Math.abs(d.value) + 0.1);
})
.style("fill", function(d){
if (d.value === 1) {
return "#000";
} else {
return color(d.value);
}
});
// build the "yAxis" color scale
// its a series of rects colored correctly
// to produce a smooth gradient
var aS = d3.scale
.linear()
.range([-margin.top + 5, height + margin.bottom - 5])
.domain([1, -1]);
var yA = d3.svg.axis()
.orient("right")
.scale(aS)
.tickPadding(7);
var aG = svg.append("g")
.attr("class", "y axis")
.call(yA)
.attr("transform", "translate(" + (width + margin.right / 2) + " ,0)")
var iR = d3.range(-1, 1.01, 0.01);
var h = height / iR.length + 3;
iR.forEach(function(d){
aG.append('rect')
.style('fill',color(d))
.style('stroke-width', 0)
.style('stoke', 'none')
.attr('height', h)
.attr('width', 10)
.attr('x', 0)
.attr('y', aS(d))
});
});
Here's the result:
Full working code.
We can use d3 v4 here's the updated code with d3 changes' log.

How to handle data occlusion in d3js?

I am trying to create a sort of raw distribution of the data with:
X axis: Person (a or b)
Y axis: Intensity (on a scale of 0 to 4)
Is there any way in which I can prevent data points from occluding each other? Such as, when there is an existing data point the next data point with the same value (different date) is positioned at an offset from it, as shown in the images below.
Current status of the plot
Desired status of the plot
I am open to changing the data format to get the end result.
Dataset:
condition intensity date
a 2 1
a 3 2
a 0 3
a 1 4
b 3 5
b 3 6
b 4 7
b 4 8
var margin = {
top: 20,
right: 20,
bottom: 30,
left: 40
},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(5);
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 + ")");
d3.csv("data.csv", type, function(error, data) {
if (error) throw error;
x.domain(data.map(function(d) {
return d.condition;
}));
y.domain([0, 4]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
svg.selectAll(".point")
.data(data)
.enter().append("path")
.attr("class", "point")
.attr("d", d3.svg.symbol().type("square").size([500]))
.attr("transform", function(d) {
return "translate(" + x(d.condition) + "," + y(d.intensity) + ")";
});
});
function type(d) {
d.severity = +d.severity;
return d;
}
.bar {
fill: steelblue;
}
.bar:hover {
fill: brown;
}
.axis {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
</body>
Figured out a way.
Store all the data points in an array and check for duplicates. If a duplicate is encountered, it is offset by 40 pixels along X-axis.
var arr = []; // array for holding all the datapoints
var tally = []; // array for storing the duplicates
// Change the squares
svg.selectAll(".point")
.attr("d", d3.svg.symbol().type("square").size([500]))
.attr("transform", function(d) {
var current = (d.condition + "," + d.severity); // current data point
var index = arr.indexOf(current); // get the index of current data point in array arr
if (index == -1) { // if index is -1, then no match found. unique data point
arr.push(current); // push point onto array
position = "translate(" + x(d.condition) + "," + y(d.severity) + ")";
} else {
tally.push(current);
var tallylen = tally.length;
var tindex = tally.indexOf(current);
for (var i = 0; i < tallylen; i++) {
var c = 0;
if (c == tindex) {
position = "translate(" + (x(d.condition) + (40 * (i + 1))) + "," + y(d.severity) + ")";
} else {
c = tindex;
position = "translate(" + (x(d.condition) + (40 * (i + 1 - tindex))) + "," + y(d.severity) + ")";
};
}
};
return position;
})
If someone has a better solution please post it.

D3 Zoom scales Map, not points

I am zooming in on a map upon click but the latitude longitude points do not scale. They are rendered as circles and I would like them to move with the map. I am following the D3 template here: http://bl.ocks.org/mbostock/2206590
var map_width = 960,
map_height = 500,
jsonRoot = '/static/d3/json/',
centered;
var projection = d3.geo.albersUsa()
.scale(1070)
.translate([map_width / 2, map_height / 2]); // default projection type for d3.geo.path
var urls = {
counties: jsonRoot + "us-counties.json",
states: jsonRoot + "us-states.json"
}
, margin = { top: 0, right: 0, bottom: 0, left: 0 }
, width = 960 - margin.right - margin.left
, height = 500
, path = d3.geo.path().projection(projection)
, map;
var q = queue()
.defer(d3.json, jsonRoot + "us-counties.json")
.defer(d3.json, jsonRoot + "us-states.json")
.await(ready);
function ready(error, countylines, statelines) {
window.error = error;
window.countylines = countylines;
window.statelines = statelines;
if (error){
throw error;
}
var stateIds = {};
statelines.features.forEach(function(d) {
stateIds[d.id] = d.properties.name;
});
countylines.features.forEach(function(d) {
d.properties.state = stateIds[d.id.slice(0,2)];
})
// remove the loading text
d3.select('.loading').remove();
map = d3.select('#map').append('svg')
.style('width', width)
.style('height', height);
counties = map.append('g')
.attr('class', 'counties')
.selectAll('path')
.data(countylines.features)
.enter().append('path')
.attr('d', path);
counties.on('mouseover', showCaption)
.on('mousemove', showCaption)
.on('mouseout', function() {
caption.html(starter);
})
.on('click', clicked);
states = map.append('g')
.attr('class', 'states')
.selectAll('path')
.data(statelines.features)
.enter().append('path')
.attr('d', path);
// Captions
var caption = d3.select('#caption')
, starter = caption.html();
function showCaption(d, i) {
var name = [d.properties.name, d.properties.state].join(', ');
caption.html(name);
}
var systemSuccess = function(result){
console.log(result);
}
var site = map.append("circle")
.attr("r",5)
.classed("system", true)
.attr("latitude",37.77521)
.attr("longitude",-122.42854)
.attr("transform", function() {
return "translate(" + projection([-122.42854,37.77521]) + ")";
});
});
})
};
function clicked(d) {
var x, y, k;
if (d && centered !== d) {
var centroid = path.centroid(d);
x = centroid[0];
y = centroid[1];
k = 4;
centered = d;
} else {
x = width / 2;
y = height / 2;
k = 1;
centered = null;
}
counties.selectAll("path")
.classed("active", centered && function(d) { return d === centered; });
counties.transition()
.duration(750)
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")scale(" + k + ")translate(" + -x + "," + -y + ")")
.style("stroke-width", 1.5 / k + "px");
states.transition()
.duration(750)
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")scale(" + k + ")translate(" + -x + "," + -y + ")")
.style("stroke-width", 1.5 / k + "px");
map.selectAll(".system")
.attr("transform", function(d) { return "translate(" + projection([-122.42854, 37.77521 ]) + ")" });
}
});
The map scales appropriately. But not the points.
All help is appreciated!
As Lars suggested, you could do the following.
//Same projection and transformation as applicable to the path elements.
d3.selectAll("circle")
.transition()
.duration(750)
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")scale(" + k + ")translate(" + -x + "," + -y + ")")
I am not sure if the above code would work correctly...although I have used a similar premise through the "zoom" d3 behavior.
If you want your points to retain their size, but be at the right position; you could try semantic zooming
OR
you could keep the resize the circle's radius based on the scale like this:
d3.selectAll("circle")
.attr("r", 5/k);

Categories

Resources