difficulty with preventing D3 Hive Plot axes from intersecting - javascript

I build a D3 Hive plot for a series of data sets. For most of the data sets, the results are as expected, but for one data set, the axes are seriously misbehaving.
In particular, the axes are not respecting the inner radius, and thus they extend inward and intersect:
I was wondering if there was some mistake in how we are defining the axes that is leading to this; here is the code for these functions:
radius.domain(d3.extent(visa_lengths, function(d){ return d.length; }));
svg.selectAll(".axis")
.data(visa_lengths)
.enter().append("line")
.attr("class", "axis")
.attr("transform", function(d, i){ return "rotate(" + degrees(angle(i)) + ")";})
.attr("x1", radius.range()[0])
.attr("x2", function(d){return radius(d.length);})
;
The full html is provided here:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Hive Link Plot</title>
<style type="text/css">
.link{
fill: none;
stroke: #000;
stroke-width: 1px;
stroke-opacity: .1;
}
.axis {
stroke: #000;
stroke-width: 1.5px;
}
.link.active {
stroke-width: 3px;
stroke-opacity: 1;
}
.node {
stroke: #000;
}
.node.active {
stroke: red;
stroke-width: 3px;
stroke-opacity: 1;
}
</style>
</head>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script type="text/javascript" src="../static/d3.hive.min.js"></script>
<script type="text/javascript">
var source_cont = "Asia";
var target_cont = "Asia";
plot(source_cont, target_cont);
function plot(source_cont, target_cont){
var width = 1200;
var height = 1200;
var outerRadius = 500;
var innerRadius = 50;
var minLength = 5;
var visa_types = {'free': 1, 'required': 4, 'refused': 2, 'onarrival': 3, 'origin': 0};
var angle = d3.scale.ordinal()
.domain(["origin", "free", "refused", "onarrival", "required"])
.range([0, Math.PI/2, 11 * Math.PI / 12, 13 * Math.PI / 12, 3 * Math.PI / 2]);
var radius = d3.scale.linear()
.range([innerRadius, outerRadius]);
var color = d3.scale.ordinal()
.domain(["origin", "free", "refused", "onarrival", "required"])
.range(["yellow", "blue", "red", "orange", "green"]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width/2 + "," + height/2 + ")");
d3.json("reduced_pairwise_datasets.json", function(error, graph){
nodes = graph.pairwise_nodes[source_cont + "-" + target_cont];
links = graph.pairwise_links[source_cont + "-" + target_cont];
// var origin_links =
var free_links = new Set(links.filter(function(p){ return p.target.type == 'free';}).map(function(d){return d.target.name}));
var onarrival_links = new Set(links.filter(function(p){ return p.target.type == 'onarrival';}).map(function(d){return d.target.name}));
var required_links = new Set(links.filter(function(p){ return p.target.type == 'required';}).map(function(d){return d.target.name}));
var refused_links = new Set(links.filter(function(p){ return p.target.type == 'refused';}).map(function(d){return d.target.name}));
var visa_lengths = [{'name': 'origin', 'length': 50}, {'name': 'free', 'length': free_links.size}, {'name': 'refused', 'length': refused_links.size}, {'name': 'onarrival', 'length': onarrival_links.size}, {'name': 'required', 'length': required_links.size}];
var visa_obj = {'origin': 55, 'free': free_links.size, 'required': required_links.size, 'onarrival': onarrival_links.size, 'refused': refused_links.size};
radius.domain(d3.extent(visa_lengths, function(d){ return d.length; }));
svg.selectAll(".axis")
.data(visa_lengths)
.enter().append("line")
.attr("class", "axis")
.attr("transform", function(d, i){ return "rotate(" + degrees(angle(i)) + ")";})
.attr("x1", radius.range()[0])
.attr("x2", function(d){return radius(d.length);})
;
svg.selectAll(".link")
.data(links)
.enter().append("path")
.attr("class", "link")
.attr("d", d3.hive.link()
.angle(function(d) { return angle(visa_types[d.type]); })
.radius(function(d) { return radius(visa_obj[d.type] * d.y); }))
.style("stroke", function(d) { return color(d.target.type);})
;
svg.selectAll(".node")
.data(nodes)
.enter().append("circle")
.attr("class", "node")
.attr("transform", function(d) { return "rotate(" + degrees(angle(visa_types[d.type])) + ")"; })
.attr("cx", function(d) { return radius(visa_obj[d.type] * d.y); })
.attr("r", 5)
.style("fill", function(d) { return color(d.type);})
;
});
}
function degrees(radians) {
return radians / Math.PI * 180 - 90;
}
</script>
</body>
</html>

Related

Using tooltip in D3 chord diagram to get value - Error: "property of target undefined"

I am trying to create a chord diagram and have the value of each ribbons source and target value as seen be accessible when they are hovered over. I also want to be able to access the index of that value to refer to use the colour.
At the moment I am getting an error of d not being defined whenever I hover over - I'm not sure how to access the values I need.. I don't want to add any other .js libraries if possible.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
}
.group-tick line {
stroke: #000;
}
.ribbons {
fill-opacity: 0.67;
}
.toolTip {
position: absolute;
display: none;
min-width: 80px;
height: auto;
background: none repeat scroll 0 0 #ffffff;
border: 1px solid #6F257F;
padding: 14px;
text-align: center;
}
</style>
<svg width="960" height="960"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var matrix = [
[11975, 5871, 8916, 2868],
[ 1951, 10048, 2060, 6171],
[ 8010, 16145, 8090, 8045],
[ 1013, 990, 940, 6907]
];
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height"),
outerRadius = Math.min(width, height) * 0.5 - 40,
innerRadius = outerRadius - 30;
var formatValue = d3.formatPrefix(",.0", 1e3);
var chord = d3.chord()
.padAngle(0.05)
.sortSubgroups(d3.descending);
var arc = d3.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius);
var ribbon = d3.ribbon()
.radius(innerRadius);
var color = d3.scaleOrdinal()
.domain(d3.range(4))
.range(["#000000", "#FFDD89", "#957244", "#F26223"]);
var g = svg.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")")
.datum(chord(matrix));
var group = g.append("g")
.attr("class", "groups")
.selectAll("g")
.data(function(chords) { return chords.groups; })
.enter().append("g");
group.append("path")
.style("fill", function(d) { return color(d.index); })
.style("stroke", function(d) { return d3.rgb(color(d.index)).darker(); })
.attr("d", arc);
var groupTick = group.selectAll(".group-tick")
.data(function(d) { return groupTicks(d, 1e3); })
.enter().append("g")
.attr("class", "group-tick")
.attr("transform", function(d) { return "rotate(" + (d.angle * 180 / Math.PI - 90) + ") translate(" + outerRadius + ",0)"; });
groupTick.append("line")
.attr("x2", 6);
groupTick
.filter(function(d) { return d.value % 5e3 === 0; })
.append("text")
.attr("x", 8)
.attr("dy", ".35em")
.attr("transform", function(d) { return d.angle > Math.PI ? "rotate(180) translate(-16)" : null; })
.style("text-anchor", function(d) { return d.angle > Math.PI ? "end" : null; })
.text(function(d) { return formatValue(d.value); });
var tooltip = d3.select("body").append("div").attr("class", "toolTip");
g.append("g")
.attr("class", "ribbons")
.selectAll("path")
.data(function(chords) { return chords; })
.enter().append("path")
.attr("d", ribbon)
.style("fill", function(d) { return color(d.target.index); })
.style("stroke", function(d) { return d3.rgb(color(d.target.index)).darker(); })
.on("mouseover", function(d) {
tooltip
.style("left", d3.event.pageX - 50 + "px")
.style("top", d3.event.pageY - 70 + "px")
.style("display", "inline-block")
.html(function(d) {
return (d.target.index) + "Hello";
});
})
.on("mouseout", function(d){
tooltip
.style("display", "none")
})
;
// Returns an array of tick angles and values for a given group and step.
function groupTicks(d, step) {
var k = (d.endAngle - d.startAngle) / d.value;
return d3.range(0, d.value, step).map(function(value) {
return {value: value, angle: value * k + d.startAngle};
});
}
</script>
I'm using the tutorial here: https://bl.ocks.org/mbostock/4062006
The issue is that there is no datum associated with your tooltip:
var tooltip = d3.select("body").append("div").attr("class", "toolTip");
So when you say tooltip.html(function(d) { ... there is no datum associated with that element to use.
Instead, try to use the datum associated with the selected chord:
.on("mouseover", function(d) { // the datum you want
tooltip
.style("left", d3.event.pageX - 50 + "px")
.style("top", d3.event.pageY - 70 + "px")
.style("display", "inline-block")
.html(d.target.index + "Hello");
})
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
}
.group-tick line {
stroke: #000;
}
.ribbons {
fill-opacity: 0.67;
}
.toolTip {
position: absolute;
display: none;
min-width: 80px;
height: auto;
background: none repeat scroll 0 0 #ffffff;
border: 1px solid #6F257F;
padding: 14px;
text-align: center;
}
</style>
<svg width="960" height="960"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var matrix = [
[11975, 5871, 8916, 2868],
[ 1951, 10048, 2060, 6171],
[ 8010, 16145, 8090, 8045],
[ 1013, 990, 940, 6907]
];
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height"),
outerRadius = Math.min(width, height) * 0.5 - 40,
innerRadius = outerRadius - 30;
var formatValue = d3.formatPrefix(",.0", 1e3);
var chord = d3.chord()
.padAngle(0.05)
.sortSubgroups(d3.descending);
var arc = d3.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius);
var ribbon = d3.ribbon()
.radius(innerRadius);
var color = d3.scaleOrdinal()
.domain(d3.range(4))
.range(["#000000", "#FFDD89", "#957244", "#F26223"]);
var g = svg.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")")
.datum(chord(matrix));
var group = g.append("g")
.attr("class", "groups")
.selectAll("g")
.data(function(chords) { return chords.groups; })
.enter().append("g");
group.append("path")
.style("fill", function(d) { return color(d.index); })
.style("stroke", function(d) { return d3.rgb(color(d.index)).darker(); })
.attr("d", arc);
var groupTick = group.selectAll(".group-tick")
.data(function(d) { return groupTicks(d, 1e3); })
.enter().append("g")
.attr("class", "group-tick")
.attr("transform", function(d) { return "rotate(" + (d.angle * 180 / Math.PI - 90) + ") translate(" + outerRadius + ",0)"; });
groupTick.append("line")
.attr("x2", 6);
groupTick
.filter(function(d) { return d.value % 5e3 === 0; })
.append("text")
.attr("x", 8)
.attr("dy", ".35em")
.attr("transform", function(d) { return d.angle > Math.PI ? "rotate(180) translate(-16)" : null; })
.style("text-anchor", function(d) { return d.angle > Math.PI ? "end" : null; })
.text(function(d) { return formatValue(d.value); });
var tooltip = d3.select("body").append("div").attr("class", "toolTip");
g.append("g")
.attr("class", "ribbons")
.selectAll("path")
.data(function(chords) { return chords; })
.enter().append("path")
.attr("d", ribbon)
.style("fill", function(d) { return color(d.target.index); })
.style("stroke", function(d) { return d3.rgb(color(d.target.index)).darker(); })
.on("mouseover", function(d) {
tooltip
.style("left", d3.event.pageX - 50 + "px")
.style("top", d3.event.pageY - 70 + "px")
.style("display", "inline-block")
.html(d.target.index + "Hello");
})
.on("mouseout", function(d){
tooltip
.style("display", "none")
})
;
// Returns an array of tick angles and values for a given group and step.
function groupTicks(d, step) {
var k = (d.endAngle - d.startAngle) / d.value;
return d3.range(0, d.value, step).map(function(value) {
return {value: value, angle: value * k + d.startAngle};
});
}
</script>

How to show multitooltip(have to show two tooltips for different line chart at same time) when hover the line in d3.js?

I am having following code for line charts.
Here I'm showing two line charts in different <div> ids, I have added hover option for the line chart.
<html>
<head>
<script src="http://d3js.org/d3.v3.js"></script>
<style>
.x.axis path {
display: none;
}
.line {
fill: none;
stroke: steelblue;
stroke-width: 1.5px;
}
.overlay {
fill: none;
pointer-events: all;
}
.focus circle {
fill: none;
stroke: steelblue;
}
.axis path,
.axis line {
fill: none;
stroke: grey;
stroke-width: 2;
shape-rendering: crispEdges;
}
.grid .tick {
stroke: lightgrey;
stroke-opacity: 0.7;
shape-rendering: crispEdges;
}
.grid path {
stroke-width: 0;
}
</style>
</head>
<body>
<div id="chart1"></div>
<div id="chart2"></div>
<script>
var data = [{
x: 'w1',
y: 5
}, {
x: 'w2',
y: 28
}, {
x: 'w3',
y: 58
}, {
x: 'w4',
y: 88
}, {
x: 'w5',
y: 8
}, {
x: 'w6',
y: 48
}, {
x: 'w7',
y: 28
}, {
x: 'w8',
y: 68
}, {
x: 'w9',
y: 8
}];
div_id ="#chart1";
div_id1="#chart2";
draw_line(data,div_id);
draw_line(data,div_id1);
function draw_line(data,div_id)
{
width = 600,
height = 260,
margin = {
top: 30,
right: 20,
bottom: 30,
left: 50
};
width -= margin.left - margin.right;
height -= margin.top - margin.bottom;
// var parseDate = d3.time.format("%d-%b-%y").parse;
// var x = d3.time.scale().range([0, width]);
var x = d3.scale.ordinal().rangePoints([0, width]);
var y = d3.scale.linear().range([height, 0]);
var xAxis = d3.svg.axis().scale(x)
.orient("bottom")
// .ticks(0).tickSize(0)
// .tickFormat("").outerTickSize(0);
var yAxis = d3.svg.axis().scale(y)
.orient("left").tickSize(0).ticks(0)
.tickFormat("");
var svg = d3.select(div_id)
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.attr("class", "bg-color")
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// function for the y grid lines
function make_y_axis() {
return d3.svg.axis()
.scale(y)
.orient("left")
.ticks(10);
}
// Draw the y Grid lines
svg.append("g")
.attr("class", "grid")
.call(make_y_axis()
.tickSize(-width, 0, 0)
.tickFormat("")
);
// x.domain(d3.extent(data, function(d) {
// return d.x;
// }));
x.domain(data.map(function(d) { return d.x; }));
y.domain([0, d3.max(data, function(d) {
return d.y;
})]);
data.sort(function(a, b) {
return a.x - b.x;
});
/*x.domain([parseDate(data[0].x), parseDate(data[data.length - 1].x)]);
y.domain(d3.extent(data, function (d) {
return d.y;
}));*/
// Add the X Axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Add the Y Axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
// Add the valueline path
svg.append("path")
.attr("class", "line");
// Define the line
var lineGen = d3.svg.line()
.x(function(d) {
return x(d.x);
})
.y(function(d) {
return y(d.y);
})
svg.append('path')
.attr("class", "line")
.attr('d', lineGen(data));
var focus = svg.append("g")
.attr("class", "focus")
.style("display", "none");
focus.append("circle")
.attr("r", 4.5);
focus.append("text")
.attr("x", 9)
.attr("dy", ".35em");
svg.append("rect")
.attr("class", "overlay")
.attr("width", width)
.attr("height", height)
.on("mouseover", function() {
focus.style("display", null);
})
.on("mouseout", function() {
focus.style("display", "none");
})
.on("mousemove", mousemove);
var bisectDate = d3.bisector(function(d) {
return d.x;
}).left;
function mousemove() {
var xPos = d3.mouse(this)[0];
var leftEdges = x.range();
var width = x.rangeBand();
var j;
for(j=0; xPos > (leftEdges[j] + width); j++) {}
var x0 = x.domain()[j];
i = bisectDate(data, x0, 1),
d0 = data[i - 1],
d1 = data[i],
d = x0 - d0.x > d1.x - x0 ? d1 : d0;
focus.attr("transform", "translate(" + x(d.x) + "," + y(d.y) + ")");
focus.select("text").text(d.x+":"+d.y);
}
}
</script>
</body>
</html>
I have to add mousehover option for both the chart at same...
Example:
If I hover first line chart means I have to show tooltip for both charts at same time and same position.

D3: Get nearest value from ordinal axis on mouseover

In my D3 line chart, i´m trying to create a mouseover effect like in this example: http://bl.ocks.org/mbostock/3902569
In this example the author uses the bisector function, which is, as far as i understand, only supported for linear scales.
The problem is, that in my chart i have an ordinal x axis with different, discrete rangePoint tuples. So if it is like in the situation below (m = mouse position), i want to get the pixel position of the closest x value which would be x2 in this example.
m
|
x1----------x2----------x3
Is there any way to do that?
Using your linked example, here's a quick implementation of the mousemove function for an ordinal scale:
var tickPos = x.range();
function mousemove(d){
var m = d3.mouse(this),
lowDiff = 1e99,
xI = null;
// if you have a large number of ticks
// this search could be optimized
for (var i = 0; i < tickPos.length; i++){
var diff = Math.abs(m[0] - tickPos[i]);
if (diff < lowDiff){
lowDiff = diff;
xI = i;
}
}
focus
.select('text')
.text(ticks[xI]);
focus
.attr("transform","translate(" + tickPos[xI] + "," + y(data[xI].y) + ")");
}
Full code:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.line {
fill: none;
stroke: steelblue;
stroke-width: 1.5px;
}
.overlay {
fill: none;
pointer-events: all;
}
.focus circle {
fill: none;
stroke: steelblue;
}
</style>
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script>
var margin = {
top: 20,
right: 20,
bottom: 30,
left: 50
},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.rangeRoundPoints([0, width]);
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");
var line = d3.svg.line()
.x(function(d) {
return x(d.x);
})
.y(function(d) {
return y(d.y);
});
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 + ")");
var data = [{
x: 'A',
y: Math.random() * 10
}, {
x: 'B',
y: Math.random() * 10
}, {
x: 'C',
y: Math.random() * 10
}, {
x: 'D',
y: Math.random() * 10
}, {
x: 'E',
y: Math.random() * 10
}, {
x: 'F',
y: Math.random() * 10
}, {
x: 'G',
y: Math.random() * 10
}, {
x: 'H',
y: Math.random() * 10
}, {
x: 'I',
y: Math.random() * 10
}, {
x: 'J',
y: Math.random() * 10
}];
var ticks = data.map(function(d) {
return d.x
});
x.domain(ticks);
y.domain(d3.extent(data, function(d) {
return d.y;
}));
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em");
svg.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
var focus = svg.append("g")
.attr("class", "focus")
.style("display", "none");
focus.append("circle")
.attr("r", 4.5);
focus.append("text")
.attr("x", 9)
.attr("dy", ".35em")
.style('')
svg.append("rect")
.attr("class", "overlay")
.attr("width", width)
.attr("height", height)
.on("mouseover", function() {
focus.style("display", null);
})
.on("mouseout", function() {
focus.style("display", "none");
})
.on("mousemove", mousemove);
var tickPos = x.range();
function mousemove(d){
var m = d3.mouse(this),
lowDiff = 1e99,
xI = null;
for (var i = 0; i < tickPos.length; i++){
var diff = Math.abs(m[0] - tickPos[i]);
if (diff < lowDiff){
lowDiff = diff;
xI = i;
}
}
focus
.select('text')
.text(ticks[xI]);
focus
.attr("transform","translate(" + tickPos[xI] + "," + y(data[xI].y) + ")");
}
</script>
Simple Solution:
.on("mousemove", function () {
const x0 = x.invert(d3.mouse(d3.event.currentTarget)[0]),
i = bisectDate(data, x0, 1),
d0 = data[i - 1],
d1 = data[i];
const rangeValueOfFirst = x(d0.date),
rangeValueOfSecond = x(d1.date),
rangeValueOfMousePos = d3.mouse(d3.event.currentTarget)[0],
closestD = Math.abs(rangeValueOfMousePos - rangeValueOfFirst) > Math.abs(rangeValueOfMousePos - rangeValueOfSecond) ? d1 : d0;
const focus = d3.select(".focus");
focus.attr("transform", () => "translate(" + x(closestD.date) + "," + y(closestD.close) + ")");
focus.select("text").text(closestD.close);
});

d3 - legend for parallel coordinate chart not displayed

I have a parallel coordinate chart. I want to display a legend. From the example from Bostock's website, I wrote something similar. But, I can only see the colours of the lines being shown and not the text.
The text is being returned correctly as well.
What am I performing incorrectly ?
Here is my code:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
svg {
font: 10px sans-serif;
}
.background path {
fill: none;
stroke: #ddd;
shape-rendering: crispEdges;
}
.foreground path {
fill: none;
stroke: steelblue;
}
.brush .extent {
fill-opacity: .3;
stroke: #fff;
shape-rendering: crispEdges;
}
.axis line,
.axis path {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.axis text {
text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, 0 -1px 0 #fff, -1px 0 0 #fff;
cursor: move;
}
.legend rect {
fill:white;
stroke:black;
opacity:0.8;}
</style>
<body>
<div style="width:1140px; height:550px;margin-left:50px;margin-top:50px;" id="chartDiv"></div>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-legend/1.5.0/d3-legend.js"></script>
<script>
var margin = {top: 30, right: 10, bottom: 10, left: 10},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.ordinal().rangePoints([0, width], 1),
y = {},
dragging = {};
var line = d3.svg.line(),
axis = d3.svg.axis().orient("left"),
background,
foreground;
var svg = d3.select("#chartDiv").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 color = d3.scale.ordinal()
.domain(['white','other','african-american','hispanic','asian/pacific islander'])
.range(['#ffae19','#4ca64c','#4682B4','#737373', '#ff4c4c']);
d3.csv("SurvivalProbability.csv", function(error, cars) {
// Extract the list of dimensions and create a scale for each.
x.domain(dimensions = d3.keys(cars[0]).filter(function(d) {
if(d === "Ethnicity") {
y[d] = d3.scale.ordinal()
.domain(cars.map(function(p) { return p[d]; }))
.rangePoints([height, 0]);
}
else if(d === "Site") {
y[d] = d3.scale.ordinal()
.domain(cars.map(function(p) { return p[d]; }))
.rangePoints([height, 0]);
}
else if(d === "Tcategory") {
y[d] = d3.scale.ordinal()
.domain(cars.map(function(p) { return p[d]; }))
.rangePoints([height, 0]);
}
else if(d === "Nodal_Disease") {
y[d] = d3.scale.ordinal()
.domain(cars.map(function(p) { return p[d]; }))
.rangePoints([height, 0]);
}
else if(d === "Chemotherapy") {
y[d] = d3.scale.ordinal()
.domain(cars.map(function(p) { return p[d]; }))
.rangePoints([height, 0]);
}
else if(d === "Local_Therapy") {
y[d] = d3.scale.ordinal()
.domain(cars.map(function(p) { return p[d]; }))
.rangePoints([height, 0]);
}
else {
y[d] = d3.scale.linear()
.domain(d3.extent(cars, function(p) { return +p[d]; }))
.range([height, 0]);
}
return true;
}));
// Add grey background lines for context.
background = svg.append("g")
.attr("class", "background")
.selectAll("path")
.data(cars)
.enter().append("path")
.attr("d", path);
// Add blue foreground lines for focus.
/*foreground = svg.append("g")
.attr("class", "foreground")
.selectAll("path")
.data(cars)
.enter().append("path")
.attr("d", path);*/
foreground = svg.append("g")
.attr("class", "foreground")
.selectAll("path")
.data(cars)
.enter().append("path")
.attr("d", path)
.style("stroke", function(d) {
return color(d.Ethnicity);
})
// Add a group element for each dimension.
var g = svg.selectAll(".dimension")
.data(dimensions)
.enter().append("g")
.attr("class", "dimension")
.attr("transform", function(d) { return "translate(" + x(d) + ")"; })
.call(d3.behavior.drag()
.origin(function(d) { return {x: x(d)}; })
.on("dragstart", function(d) {
dragging[d] = x(d);
background.attr("visibility", "hidden");
})
.on("drag", function(d) {
dragging[d] = Math.min(width, Math.max(0, d3.event.x));
foreground.attr("d", path);
dimensions.sort(function(a, b) { return position(a) - position(b); });
x.domain(dimensions);
g.attr("transform", function(d) { return "translate(" + position(d) + ")"; })
})
.on("dragend", function(d) {
delete dragging[d];
transition(d3.select(this)).attr("transform", "translate(" + x(d) + ")");
transition(foreground).attr("d", path);
background
.attr("d", path)
.transition()
.delay(500)
.duration(0)
.attr("visibility", null);
}));
// Add an axis and title.
g.append("g")
.attr("class", "axis")
.each(function(d) { d3.select(this).call(axis.scale(y[d])); })
.append("text")
.style("text-anchor", "middle")
.attr("y", -9)
.text(function(d) { return d; });
// Add and store a brush for each axis.
g.append("g")
.attr("class", "brush")
.each(function(d) {
d3.select(this).call(y[d].brush = d3.svg.brush().y(y[d]).on("brushstart", brushstart).on("brush", brush));
})
.selectAll("rect")
.attr("x", -8)
.attr("width", 16);
var legendRectSize = 10;
var legendSpacing = 100;
var legend = d3.select('svg')
.append("g")
.selectAll("g")
.data(color.domain())
.enter()
.append('g')
.attr('class', 'legend')
.attr('transform', function(d, i) {
var height = legendRectSize;
var x = 925;
var y = (i * height) + 50;
return 'translate(' + x + ',' + y + ')';
});
legend.append('rect')
.attr('width', legendRectSize)
.attr('height', legendRectSize)
.style('fill', color)
.style('stroke', color);
legend.append('text')
.attr('x', legendRectSize + legendSpacing)
.attr('y', legendRectSize - legendSpacing)
.text(function(d) { return d; });
});
function position(d) {
var v = dragging[d];
return v == null ? x(d) : v;
}
function transition(g) {
return g.transition().duration(500);
}
// Returns the path for a given data point.
function path(d) {
return line(dimensions.map(function(p) { return [position(p), y[p](d[p])]; }));
}
function brushstart() {
d3.event.sourceEvent.stopPropagation();
}
// Handles a brush event, toggling the display of foreground lines.
function brush() {
var actives = dimensions.filter(function(p) { return !y[p].brush.empty(); }),
extents = actives.map(function(p) { return y[p].brush.extent(); });
foreground.style("display", function(d) {
return actives.every(function(p, i) {
return extents[i][0] <= d[p] && d[p] <= extents[i][1];
}) ? null : "none";
});
}
</script>
The code is correct to show the legend you have to adjust the margin a little.
//rightmargin is set to 200
var margin = {top: 30, right: 200, bottom: 10, left: 10},
and width is like
width = 1100 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
Placement of the legend text as below
var legendRectSize = 10;
var legendSpacing = 10;
var legend = d3.select('svg')
.append("g")
.selectAll("g")
.data(color.domain())
.enter()
.append('g')
.attr('class', 'legend')
.attr('transform', function(d, i) {
var height = legendRectSize;
var x = 900;
var y = (i * height) + 50;
return 'translate(' + x + ',' + y + ')';
});
That all :)
Working code here
Hope this helps!

Combining D3 Visualization

I am trying to combine two D3 Visualizations. I found a question before, but it did not really have a solution.
When I combine the two files the visualizations overlap and produce this:
the streamgraph component:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
}
.chart {
background: #fff;
}
p {
font: 12px helvetica;
}
.axis path, .axis line {
fill: none;
stroke: #000;
stroke-width: 2px;
shape-rendering: crispEdges;
}
button {
position: absolute;
right: 50px;
top: 10px;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.js"></script>
<div class="chart">
</div>
<script>
chart("Data.csv", "blue");
var datearray = [];
var colorrange = [];
function chart(csvpath, color) {
if (color == "blue") {
colorrange = ["#045A8D", "#2B8CBE", "#74A9CF", "#A6BDDB", "#D0D1E6", "#F1EEF6"];
}
else if (color == "pink") {
colorrange = ["#980043", "#DD1C77", "#DF65B0", "#C994C7", "#D4B9DA", "#F1EEF6"];
}
else if (color == "orange") {
colorrange = ["#B30000", "#E34A33", "#FC8D59", "#FDBB84", "#FDD49E", "#FEF0D9"];
}
strokecolor = colorrange[0];
var format = d3.time.format("%m/%d/%y");
var margin = {top: 20, right: 40, bottom: 30, left: 50};
var width = document.body.clientWidth - margin.left - margin.right;
var height = 400 - margin.top - margin.bottom;
var tooltip = d3.select("body")
.append("div")
.attr("class", "remove")
.style("position", "absolute")
.style("z-index", "20")
.style("visibility", "hidden")
.style("top", "30px")
.style("left", "75px");
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height-10, 0]);
var z = d3.scale.ordinal()
.range(colorrange);
var xAxis = d3.svg.axis()
.orient("bottom")
.scale(x)
.ticks(d3.time.years, 10); //tick on every 10 years
/*.scale(x)
.orient("bottom")
.text(date)
//;*/
//. tickFormat(x)
//. tickValues(date)
//was already there but out of view -> changed the left margin
var yAxis = d3.svg.axis()
.scale(y);
var stack = d3.layout.stack()
.offset("silhouette")
.values(function(d) { return d.values; })
.x(function(d) { return d.date; })
.y(function(d) { return d.value; });
var nest = d3.nest()
.key(function(d) { return d.key; });
var area = d3.svg.area()
.interpolate("cardinal")
.x(function(d) { return x(d.date); })
.y0(function(d) { return y(d.y0); })
.y1(function(d) { return y(d.y0 + d.y); });
var svg = d3.select(".chart").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 + ")");
/* correct this function
var graph = d3.csv(csvpath, function(data) {
data.forEach(function(d) {
d.date = format.parse(d.date);
d.value = +d.value;
});*/
var graph = d3.csv(csvpath, function(raw) {
var data = [];
raw.forEach(function (d) {
data.push({
key: d.Country,
date : new Date(1980,0,1), //I had a bug in creating the right dates
value : parseInt(d['1980-1989'].replace(',','')) //get rid of the thousand separator
});
data.push({
key: d.Country,
date : new Date(1990,0,1),
value : parseInt(d['1990-1999'].replace(',',''))
});
data.push({
key: d.Country,
date : new Date(2000,0,1),
value : parseInt(d['2000-2009'].replace(',','') )
});
});
var layers = stack(nest.entries(data));
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.y0 + d.y; })]);
svg.selectAll(".layer")
.data(layers)
.enter().append("path")
.attr("class", "layer")
.attr("d", function(d) { return area(d.values); })
.style("fill", function(d, i) { return z(i); });
//adding .text causes axis to dissapear
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
//.text(date)
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + width + ", 0)")
//.text(value)
.call(yAxis.orient("right"));
svg.append("g")
.attr("class", "y axis")
.call(yAxis.orient("left"));
var pro;
svg.selectAll(".layer")
.attr("opacity", 1)
.on("mouseover", function(d, i) {
svg.selectAll(".layer").transition()
.duration(250)
.attr("opacity", function(d, j) {
return j != i ? 0.6 : 1;
})})
.on("mousemove", function(d, i) {
var mousex = d3.mouse(this);
mousex = mousex[0];
var invertedx = x.invert(mousex);
//find the largest smaller element
var dd = d.values.filter(function(d) { return d.date <= invertedx; });
dd = dd[dd.length -1]; //use the last element
pro = dd.value;
d3.select(this)
.classed("hover", true)
.attr("stroke", strokecolor)
.attr("stroke-width", "0.5px");
tooltip.html( "<p>" + d.key + "<br>" + pro + "</p>" ).style("visibility", "visible");
})
.on("mouseout", function(d, i) {
svg.selectAll(".layer")
.transition()
.duration(250)
.attr("opacity", "1");
d3.select(this)
.classed("hover", false)
.attr("stroke-width", "0px");
tooltip.html( "<p>" + d.key + "<br>" + pro + "</p>" ).style("visibility", "hidden");
})
var vertical = d3.select(".chart")
.append("div")
.attr("class", "remove")
.style("position", "absolute")
.style("z-index", "19")
.style("width", "1px")
.style("height", "380px")
.style("top", "10px")
.style("bottom", "30px")
.style("left", "0px")
.style("background", "#fff");
d3.select(".chart")
.on("mousemove", function(){
var mousex = d3.mouse(this);
mousex = mousex[0] + 5;
vertical.style("left", mousex + "px" )})
.on("mouseover", function(){
var mousex = d3.mouse(this);
mousex = mousex[0] + 5;
vertical.style("left", mousex + "px")});
});
}
</script>
Map component:
<!DOCTYPE html>
<meta charset="utf-8">
<title>U.S Immigration Data Visualization</title>
<style>
.country:hover{
stroke: #fff;
stroke-width: 1.5px;
}
.text{
font-size:10px;
text-transform:capitalize;
}
#container {
margin: 10px 10%;
border:2px solid #000;
border-radius: 5px;
height:100%;
overflow:hidden;
background: #e1eafe;
}
.hidden {
display: none;
}
div.tooltip {
color: #222;
background: #fff;
padding: .5em;
text-shadow: #f5f5f5 0 1px 0;
border-radius: 2px;
box-shadow: 0px 0px 2px 0px #a6a6a6;
opacity: 0.9;
position: absolute;
}
.graticule {
fill: none;
stroke: #bbb;
stroke-width: .5px;
stroke-opacity: .5;
}
.equator {
stroke: #ccc;
stroke-width: 1px;
}
</style>
</head>
<br>
<h1><center>U.S Immigration Data Visualization</center></h1>
<h2><b>Work in Progress</b></h2>
<h3><b>Ex-USSR countries included in Russia</b></h3>
<h3><b>Ex-Yugoslavia included in Macedonia</b></h3>
<div id="container"></div>
<script src="js/d3.min.js"></script>
<script src="js/topojson.v1.min.js"></script>
<script src="http://d3js.org/d3.geo.tile.v0.min.js"></script>
<script>
d3.select(window).on("resize", throttle);
var zoom = d3.behavior.zoom()
.scaleExtent([1, 9])
.on("zoom", move);
var width = document.getElementById('container').offsetWidth;
var height = width / 2;
var topo,projection,path,svg,g;
var graticule = d3.geo.graticule();
var tooltip = d3.select("#container").append("div").attr("class", "tooltip hidden");
setup(width,height);
function setup(width,height){
projection = d3.geo.mercator()
.translate([(width/2), (height/2)])
.scale( width / 2 / Math.PI);
path = d3.geo.path().projection(projection);
svg = d3.select("#container").append("svg")
.attr("width", width)
.attr("height", height)
.call(zoom)
.on("click", click)
.append("g");
g = svg.append("g");
}
d3.json("data/world-topo-min.json", function(error, world) {
var countries = topojson.feature(world, world.objects.countries).features;
topo = countries;
draw(topo);
});
function draw(topo) {
svg.append("path")
.datum(graticule)
.attr("class", "graticule")
.attr("d", path);
g.append("path")
.datum({type: "LineString", coordinates: [[-180, 0], [-90, 0], [0, 0], [90, 0], [180, 0]]})
.attr("class", "equator")
.attr("d", path);
var country = g.selectAll(".country").data(topo);
country.enter().insert("path")
.attr("class", "country")
.attr("d", path)
.attr("id", function(d,i) { return d.id; })
.attr("title", function(d,i) { return d.properties.name; })
.style("fill", function(d, i) { return d.properties.color; });
//offsets for tooltips
var offsetL = document.getElementById('container').offsetLeft+20;
var offsetT = document.getElementById('container').offsetTop+10;
//tooltips
country
.on("mousemove", function(d,i) {
var mouse = d3.mouse(svg.node()).map( function(d) { return parseInt(d); } );
tooltip.classed("hidden", false)
.attr("style", "left:"+(mouse[0]+offsetL)+"px;top:"+(mouse[1]+offsetT)+"px")
.html(d.properties.name);
})
.on("mouseout", function(d,i) {
tooltip.classed("hidden", true);
});
//EXAMPLE: adding some capitals from external CSV file
d3.csv("Data.csv", function(err, capitals) {
capitals.forEach(function(i){
addpoint(i.CapitalLongitude, i.CapitalLatitude );
});
});
}
function redraw() {
width = document.getElementById('container').offsetWidth;
height = width / 2;
d3.select('svg').remove();
setup(width,height);
draw(topo);
}
function move() {
var t = d3.event.translate;
var s = d3.event.scale;
zscale = s;
var h = height/4;
t[0] = Math.min(
(width/height) * (s - 1),
Math.max( width * (1 - s), t[0] )
);
t[1] = Math.min(
h * (s - 1) + h * s,
Math.max(height * (1 - s) - h * s, t[1])
);
zoom.translate(t);
g.attr("transform", "translate(" + t + ")scale(" + s + ")");
//adjust the country hover stroke width based on zoom level
d3.selectAll(".country").style("stroke-width", 1.5 / s);
}
var throttleTimer;
function throttle() {
window.clearTimeout(throttleTimer);
throttleTimer = window.setTimeout(function() {
redraw();
}, 200);
}
//geo translation on mouse click in map
function click() {
var latlon = projection.invert(d3.mouse(this));
console.log(latlon);
}
//function to add points and text to the map (used in plotting capitals)
function addpoint(lat,lon,text) {
var gpoint = g.append("g").attr("class", "gpoint");
var x = projection([lat,lon])[0];
var y = projection([lat,lon])[1];
gpoint.append("svg:circle")
.attr("cx", x)
.attr("cy", y)
.attr("class","point")
.attr("r", 1);
//conditional in case a point has no associated text
//if(text.length>0){
// gpoint.append("text")
// .attr("x", x+2)
// .attr("y", y+2)
// .attr("class","text")
// .text(text);
//}
}
</script>
</body>
</html>
This is hard to answer without the code you actually use when you 'combine' the two SVG elements, and without the data or a working example.
What I've done is take the 2 basic components, the streamgraph (and svg node inside <div class="chart">) and the map (a separate svg node in <div id="container"></div>), and create working code that combines the 2:
http://plnkr.co/edit/WjlObRIasLYXOuEL4HDE?p=preview
This is the basic code:
<body>
<div class="chart">
</div>
<div id="container">
</div>
<script type="text/javascript">
var width = 300;
var height = width / 2;
// Equivalent of streamgraph code...
var svg_stream = d3.select(".chart")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("rect")
... // rect attributes
// Equivalent of map code...
var svg_map = d3.select("#container")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("circle")
... // circle attributes
</script>
</body>
What's inside each svg shouldn't make a difference to the positioning, so I've just used a rect to represent the streamgraph and a circle for the map. I've taken as much of the CSS and code from your snippets as it makes sense to. If you combine the two components as in the example above, you should not see any overlap. I'm not really able to correct your version as I don't know how you did it.
Note - You should also avoid defining duplicate variable names (like var svg for both SVGs) when combining the components

Categories