I want to get bounding boxes for each country from a topojson, but when I add them as svg rectangles they are bundled together towards 0,0.
Ive re-read the API and played around with the order of the bound coordinates, but that didn't change anything! Also, I tried to use the SVG method getBBox() on the country paths but that produced the same result.
Any ideas?
var width = 700,
height = 400,
bboxes = [];
d3.queue()
.defer(d3.json, "data/world.topojson")
.await(ready);
//Map projection
var proj = d3.geoMercator()
.scale(100)
.center([-0.0018057527730242487, 11.258678472759552]) //projection center
.translate([width / 2, height / 2]) //translate to center the map in view
//Generate paths based on projection
var myPath = d3.geoPath().projection(proj);
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
//Group for the map features
var map = svg.append("g")
.attr("class", "map");
function ready(error, geodata) {
if (error) return console.log(error); //unknown error, check the console
//Create a path for each map feature in the data
map.selectAll("path")
.data(topojson.feature(geodata, geodata.objects.subunits).features) //generate features from TopoJSON
.enter()
.append("path")
.attr("class", "country")
.attr("id", function(d) {
return d.id;
})
.attr("d", myPath);
bboxes = boundingExtent(topojson.feature(geodata, geodata.objects.subunits).features);
svg.selectAll("rect")
.data(bboxes)
.enter()
.append("rect")
.attr("id", function(d){
return d.id;
})
.attr("class", "bb")
.attr("x1", function(d) {
return d.x;
})
.attr("y1", function(d) {
return d.y;
})
.attr("width", function(d) {
return d.width;
})
.attr("height", function(d) {
return d.height;
})
}
function boundingExtent(features) {
var bounds= [];
for (var x in features) {
var boundObj = {};
thisBounds = myPath.bounds(features[x]);
boundObj.id = features[x].id;
boundObj.x = thisBounds[0][0];
boundObj.y = thisBounds[0][1];
boundObj.width = thisBounds[1][0] - thisBounds[0][0];
boundObj.height = thisBounds[1][1] - thisBounds[0][1];
boundObj.path = thisBounds;
bounds.push(boundObj)
}
return bounds;
}
function boundExtentBySvg(){
var countries = svg.selectAll(".country")
countries.each(function(d){
var box = d3.select(this).node().getBBox();
bboxes.push({id: d.id, x: box.x, y : box.y, width: box.width, height : box.height})
})
}
In these lines:
.attr("x1", function(d) {
return d.x;
})
.attr("y1", function(d) {
return d.y;
})
rect does not have an attribute of x1 or y1, I think you meant just x and y.
Here's your code running (note, I switched out the topojson file which caused slight code changes):
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#4.0.0" data-semver="4.0.0" src="https://d3js.org/d3.v4.min.js"></script>
<script data-require="topojson.min.js#3.0.0" data-semver="3.0.0" src="https://unpkg.com/topojson#3.0.0"></script>
</head>
<body>
<svg width="700" height="400"></svg>
<script>
var width = 700,
height = 400,
bboxes = [];
d3.queue()
.defer(d3.json, "https://unpkg.com/world-atlas#1/world/110m.json")
.await(ready);
//Map projection
var proj = d3.geoMercator()
.scale(100)
.center([-0.0018057527730242487, 11.258678472759552]) //projection center
.translate([width / 2, height / 2]) //translate to center the map in view
//Generate paths based on projection
var myPath = d3.geoPath().projection(proj);
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
//Group for the map features
var map = svg.append("g")
.attr("class", "map");
function ready(error, geodata) {
if (error) return console.log(error); //unknown error, check the console
//Create a path for each map feature in the data
map.selectAll("path")
.data(topojson.feature(geodata, geodata.objects.countries).features) //generate features from TopoJSON
.enter()
.append("path")
.attr("class", "country")
.attr("id", function(d) {
return d.id;
})
.attr("d", myPath);
bboxes = boundingExtent(topojson.feature(geodata, geodata.objects.countries).features);
svg.selectAll("rect")
.data(bboxes)
.enter()
.append("rect")
.attr("id", function(d) {
return d.id;
})
.attr("class", "bb")
.attr("x", function(d) {
return d.x;
})
.attr("y", function(d) {
return d.y;
})
.attr("width", function(d) {
return d.width;
})
.attr("height", function(d) {
return d.height;
})
.style("fill", "none")
.style("stroke", "steelblue");
}
function boundingExtent(features) {
var bounds = [];
for (var x in features) {
var boundObj = {};
thisBounds = myPath.bounds(features[x]);
boundObj.id = features[x].id;
boundObj.x = thisBounds[0][0];
boundObj.y = thisBounds[0][1];
boundObj.width = thisBounds[1][0] - thisBounds[0][0];
boundObj.height = thisBounds[1][1] - thisBounds[0][1];
boundObj.path = thisBounds;
bounds.push(boundObj)
}
console.log(bounds)
return bounds;
}
function boundExtentBySvg() {
var countries = svg.selectAll(".country")
countries.each(function(d) {
var box = d3.select(this).node().getBBox();
bboxes.push({
id: d.id,
x: box.x,
y: box.y,
width: box.width,
height: box.height
})
})
}
</script>
</body>
</html>
Related
Very new to D3 and coming from R but looking to develop some interactive choropleth maps.
I'm wanting to develop this zoomable choropleth: https://bl.ocks.org/cmgiven/d39ec773c4f063a463137748097ff52f
into something like this V5 choropleth with easily attachable data: https://bl.ocks.org/denisemauldin/3436a3ae06f73a492228059a515821fe
I can mainipulate the V5 choropleth fairly easily, still figuring out how to adjust continuous scale to a discrete scale but I don't really know how to build the zoomable svg in V5.
I think in your question you are asking about continuous scale, here is a code snippet for continuous scaling for an zoomable choropleth, What is just apply the same mechanism in this bl.ocks.org just wrap everything in <g> group tag and apply the zoom on this <g> tag:
const zoom = d3.zoom()
.scaleExtent([1, 8])
.on('zoom', zoomed);
svg.call(zoom);
function zoomed() {
zoomG.selectAll('path') // To prevent stroke width from scaling
.attr('transform', d3.event.transform);
}
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
var unemployment = d3.map();
var stateNames = d3.map();
var path = d3.geoPath();
var x = d3.scaleLinear()
.domain([1, 10])
.rangeRound([600, 860]);
var color = d3.scaleThreshold()
.domain(d3.range(0, 10))
.range(d3.schemeBlues[9]);
var g = svg.append("g")
.attr("class", "key")
.attr("transform", "translate(0,40)");
g.selectAll("rect")
.data(color.range().map(function(d) {
d = color.invertExtent(d);
if (d[0] == null) d[0] = x.domain()[0];
if (d[1] == null) d[1] = x.domain()[1];
return d;
}))
.enter().append("rect")
.attr("height", 8)
.attr("x", function(d) { return x(d[0]); })
.attr("width", function(d) { return x(d[1]) - x(d[0]); })
.attr("fill", function(d) { return color(d[0]); });
g.append("text")
.attr("class", "caption")
.attr("x", x.range()[0])
.attr("y", -6)
.attr("fill", "#000")
.attr("text-anchor", "start")
.attr("font-weight", "bold")
.text("Unemployment rate");
g.call(d3.axisBottom(x)
.tickSize(13)
.tickFormat(function(x, i) { return i ? x : x + "%"; })
.tickValues(color.domain()))
.select(".domain")
.remove();
var promises = [
d3.json("https://d3js.org/us-10m.v1.json"),
d3.tsv("https://gist.githubusercontent.com/denisemauldin/3436a3ae06f73a492228059a515821fe/raw/954210175a18c646eb751d59e04d5a4814fe3b03/us-state-names.tsv", function(d) {
stateNames.set(d.id, d.name)
}),
d3.tsv("https://gist.githubusercontent.com/denisemauldin/3436a3ae06f73a492228059a515821fe/raw/954210175a18c646eb751d59e04d5a4814fe3b03/map.tsv", function(d) {
unemployment.set(d.name, +d.value);
})
]
Promise.all(promises).then(ready)
function ready([us]) {
let zoomG = svg.append("g");
zoomG.append("g")
.attr("class", "counties")
.selectAll("path")
.data(topojson.feature(us, us.objects.states).features)
.enter().append("path")
.attr("fill", function(d) {
var sn = stateNames.get(d.id)
d.rate = unemployment.get(stateNames.get(d.id)) || 0
var col = color(d.rate);
if (col) {
return col
} else {
return '#ffffff'
}
})
.attr("d", path)
.append("title")
.text(function(d) {
return d.rate + "%"; });
zoomG.append("path")
.datum(topojson.mesh(us, us.objects.states, function(a, b) { return a !== b; }))
.attr("class", "states")
.attr("d", path);
const zoom = d3.zoom()
.scaleExtent([1, 8])
.on('zoom', zoomed);
svg.call(zoom);
function zoomed() {
zoomG.selectAll('path') // To prevent stroke width from scaling
.attr('transform', d3.event.transform);
}
}
.counties {
fill: none;
}
.states {
fill: none;
stroke: #fff;
stroke-linejoin: round;
}
<!DOCTYPE html>
<meta charset="utf-8">
<svg width="960" height="600"></svg>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script>
<script src="https://d3js.org/topojson.v2.min.js"></script>
I am undertaking yet another attempt to draw a family tree with d3. For this, I would like to use the usual node-link graph (like this one):
But with a link style like that usually found in d3 trees, i.e. be the Bezier curves with horizontal (or vertical) ends:
Is it possible to change the links accordingly, without diving into the d3-force code?
If you are just looking to match the style of the links, no need to dive into the d3-force code, it only calculates position, not anything related to styling.
Each link has a x and y values for both the source and the target. If you replace the line that is found linking source and target in most force layout examples with a path, you can use these x and y values to pretty much style any type of link you want.
I'm using d3v4+ below - your examples use d3v3.
Option 1 - Use the Built In Links
In d3v3 you would use d3.svg.diagonal, but now there is d3.linkVertical() and d3.linkHorizontal() to achieve the same thing. With this we can use:
d3.linkVertical()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; }));
And then shape paths representing links with:
link.attr("d",d3.linkVertical()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; }));
I've only done a vertical styling below - but you could determine if the difference in the x coordinates is greater than the y coordinates to determine if you should apply horizontal or vertical styling.
var svg = d3.select("svg");
var nodes = "abcdefg".split("").map(function(d) {
return {name:d};
})
var links = "bcdef".split("").map(function(d) {
return {target:"a", source:d}
})
links.push({target:"d", source:"b"},{target:"d", source:"g"})
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) { return d.name; }))
.force("charge", d3.forceManyBody().strength(-1000))
.force("center", d3.forceCenter(250,150));
var node = svg.append("g")
.selectAll("circle")
.data(nodes)
.enter().append("circle")
.attr("r", 5)
var link = svg.append("g")
.selectAll("path")
.data(links)
.enter().append("path")
simulation
.nodes(nodes)
.on("tick", ticked)
.force("link")
.links(links);
function ticked() {
link.attr("d", d3.linkVertical()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; }));
node
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
}
path {
stroke: black;
stroke-width: 2px;
fill:none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width="500" height="300">
Option 2 - Manually Specify Path
We can substitute the line used to connect nodes with a path, we can supply the d attribute of the path manually given the datum of the path contains the x,y of the target and the source. Perhaps something like:
path.attr("d", function(d) {
var x0 = d.source.x;
var y0 = d.source.y;
var x1 = d.target.x;
var y1 = d.target.y;
var xcontrol = x1 * 0.5 + x0 * 0.5;
return ["M",x0,y0,"C",xcontrol,y0,xcontrol,y1,x1,y1].join(" ");
})
Again, I've only done only one styling here, this time horizontal, but adding a check to see if horizontal or vertical links are needed should be fairly straightforward:
var svg = d3.select("svg");
var nodes = "abcdefg".split("").map(function(d) {
return {name:d};
})
var links = "bcdef".split("").map(function(d) {
return {target:"a", source:d}
})
links.push({target:"d", source:"b"},{target:"d", source:"g"})
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) { return d.name; }))
.force("charge", d3.forceManyBody().strength(-1000))
.force("center", d3.forceCenter(250,150));
var node = svg.append("g")
.selectAll("circle")
.data(nodes)
.enter().append("circle")
.attr("r", 5)
var link = svg.append("g")
.selectAll("path")
.data(links)
.enter().append("path")
simulation
.nodes(nodes)
.on("tick", ticked)
.force("link")
.links(links);
function ticked() {
link.attr("d", function(d) {
var x0 = d.source.x;
var y0 = d.source.y;
var x1 = d.target.x;
var y1 = d.target.y;
var xcontrol = x1 * 0.5 + x0 * 0.5;
return ["M",x0,y0,"C",xcontrol,y0,xcontrol,y1,x1,y1].join(" ");
})
node
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
}
path {
stroke: black;
stroke-width: 2px;
fill:none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width="500" height="300">
Option 3 - Use a Custom Curve Generator
I include this because I just recently answered a question on custom curves, that by chance uses the same styling. This way we can define the path of each link with:
var line = d3.line().curve(d3.someCurve))
and
link.attr("d", function(d) {
return line([[d.source.x,d.source.y],[d.target.x,d.target.y]]);
})
I've added a couple lines to build on the example above too, the curves can be either vertical or horizontal:
var curve = function(context) {
var custom = d3.curveLinear(context);
custom._context = context;
custom.point = function(x,y) {
x = +x, y = +y;
switch (this._point) {
case 0: this._point = 1;
this._line ? this._context.lineTo(x, y) : this._context.moveTo(x, y);
this.x0 = x; this.y0 = y;
break;
case 1: this._point = 2;
default:
if (Math.abs(this.x0 - x) > Math.abs(this.y0 - y)) {
var x1 = this.x0 * 0.5 + x * 0.5;
this._context.bezierCurveTo(x1,this.y0,x1,y,x,y);
}
else {
var y1 = this.y0 * 0.5 + y * 0.5;
this._context.bezierCurveTo(this.x0,y1,x,y1,x,y);
}
this.x0 = x; this.y0 = y;
break;
}
}
return custom;
}
var svg = d3.select("svg");
var line = d3.line()
.curve(curve);
var nodes = "abcdefg".split("").map(function(d) {
return {name:d};
})
var links = "bcdef".split("").map(function(d) {
return {target:"a", source:d}
})
links.push({target:"d", source:"b"},{target:"d", source:"g"})
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) { return d.name; }))
.force("charge", d3.forceManyBody().strength(-1000))
.force("center", d3.forceCenter(250,150));
var node = svg.append("g")
.selectAll("circle")
.data(nodes)
.enter().append("circle")
.attr("r", 5)
var link = svg.append("g")
.selectAll("path")
.data(links)
.enter().append("path")
simulation
.nodes(nodes)
.on("tick", ticked)
.force("link")
.links(links);
function ticked() {
link.
attr("d", function(d) {
return line([[d.source.x,d.source.y],[d.target.x,d.target.y]]);
})
node
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
}
path {
stroke: black;
stroke-width: 2px;
fill:none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width="500" height="300">
This option will work with canvas as well (as will option 1 if I'm not mistaken).
I have created a d3 force layout, and it works very well. Now I will add a group of data to my graph. I hope I could control the center of my new nodes. For example, supposed the center is (100,100), I hope the new nodes lay out into rectangle area like [(50,50) to (150,150)] as a whole.
var width = 500,
height = 500;
var nodes = [{id:0, n:'Tom'}, {id:1, n:'Join'}, {id:2, n:'John'}, {id:3, n:'Bob'}, {id:4, n:'4'}, {id:5, n:'5'}, {id:6, n:'6'}];
var links = [{source:0,target:1},{source:0,target:2},{source:0,target:3},{source:0,target:4},{source:0,target:5},{source:1,target:5},{source:1,target:6}];
// init force
var force = d3.layout.force()
.charge(-120)
.linkDistance(120)
.size([width, height]);
// init svg
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
// set tick function
force.on("tick", function () {
d3.selectAll(".link").attr("x1", function (d) {
return d.source.x;
})
.attr("y1", function (d) {
return d.source.y;
})
.attr("x2", function (d) {
return d.target.x;
})
.attr("y2", function (d) {
return d.target.y;
});
// controll the coordinates here
d3.selectAll(".node").attr("transform", function(d){
if(d.flag == 1){
d.x = Math.max(50, Math.min(150, d.x));
d.y = Math.max(50, Math.min(150, d.y));
}
return "translate("+d.x+","+d.y+")";
});
}).on('end', function(){
svg.selectAll(".node").each(function(d){d.fixed=true;});
});
function setData(ns, ls){
var update = svg.selectAll(".link").data(ls);
update.enter().append("line")
.attr("class", "link")
.style("stroke-width", 1);
update.exit().remove();
update = svg.selectAll(".node").data(ns);
update.enter().append("g")
.attr("class", "node")
.attr("id", function(d){return d.id})
.call(force.drag)
.call(function(p){
p.append("image")
.attr("class", "nodeimage")
.attr("width", "30px")
.attr("height", "30px")
.attr("x", "-15px")
.attr("y", "-15px");
p.append("text")
.attr("class", "nodetext")
.attr("dx", "-10px")
.attr("dy", "20px")
.style("font-size", "15px")
.text(function(d){return d.n});
});
update.exit().remove();
update.selectAll(".nodeimage")
.each(function() {
d3.select(this).datum(d3.select(this.parentNode).datum());
})
.attr("xlink:href", function(d){
var img;
if(d.flag == 1){
img = "http://www.gravatar.com/avatar/1eccef322f0beef11e0e47ed7963189b/?default=&s=80"
}else{
img = "http://www.gravatar.com/avatar/a1338368fe0b4f3d301398a79c171987/?default=&s=80";
}
return img;
});
force.nodes(ns)
.links(ls)
.start();
}
//init
setData(nodes, links);
setTimeout(function(){
//generate new data and merge to old data
nodes = nodes.concat(generateNewData());
setData(nodes, links);
//how do i control the coordinate of new nodes?
}, 3000);
function generateNewData(){
var ns = [];
for(var i = 0; i < 10; i++){
ns.push({id:i+100,n:'n'+i,flag:1});
}
return ns;
}
Here is my demo of jsfiddle:http://jsfiddle.net/cs4xhs7s/4/
The latest demo shows that the nodes can display in the rectangle, however, their coordinates are the same. I hope it is an available force layout.
https://jsfiddle.net/wpnq15mf/1/
var width = 500,
height = 500;
var nodes = [{id:0, n:'Tom'}, {id:1, n:'Join'}, {id:2, n:'John'}, {id:3, n:'Bob'}, {id:4, n:'4'}, {id:5, n:'5'}, {id:6, n:'6'}];
var links = [{source:0,target:1},{source:0,target:2},{source:0,target:3},{source:0,target:4},{source:0,target:5},{source:1,target:5},{source:1,target:6}];
// init force
var force = d3.layout.force()
.charge(-500)
.linkDistance(120)
.gravity(0.1)
.size([width, height]);
// init svg
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
// set tick function
force.on("tick", function () {
d3.selectAll(".link").attr("x1", function (d) {
return d.source.x;
})
.attr("y1", function (d) {
return d.source.y;
})
.attr("x2", function (d) {
return d.target.x;
})
.attr("y2", function (d) {
return d.target.y;
});
// controll the coordinates here
d3.selectAll(".node").attr("transform", function(d){
if(d.flag == 1){
d.x = Math.max(50, Math.min(150, d.x));
d.y = Math.max(50, Math.min(150, d.y));
}
return "translate("+d.x+","+d.y+")";
});
}).on('end', function(){
svg.selectAll(".node").each(function(d){d.fixed=true;});
});
function setData(ns, ls){
var update = svg.selectAll(".link").data(ls);
update.enter().append("line")
.attr("class", "link")
.style("stroke-width", 1);
update.exit().remove();
update = svg.selectAll(".node").data(ns);
update.enter().append("g")
.attr("class", "node")
.attr("id", function(d){return d.id})
.call(force.drag)
.call(function(p){
p.append("image")
.attr("class", "nodeimage")
.attr("width", "30px")
.attr("height", "30px")
.attr("x", "-15px")
.attr("y", "-15px");
p.append("text")
.attr("class", "nodetext")
.attr("dx", "-10px")
.attr("dy", "20px")
.style("font-size", "15px")
.text(function(d){return d.n});
});
update.exit().remove();
update.selectAll(".nodeimage")
.each(function() {
d3.select(this).datum(d3.select(this.parentNode).datum());
})
.attr("xlink:href", function(d){
var img;
if(d.flagx == 1){
img = "http://www.gravatar.com/avatar/1eccef322f0beef11e0e47ed7963189b/?default=&s=80"
}else{
img = "http://www.gravatar.com/avatar/a1338368fe0b4f3d301398a79c171987/?default=&s=80";
}
return img;
});
force.nodes(ns)
.links(ls)
.start();
}
//init
setData(nodes, links);
setTimeout(function(){
//generate new data and merge to old data
nodes = nodes.concat(generateNewData());
links = links.concat(generateNewLinks());
setData(nodes, links);
//how do i control the coordinate of new nodes?
}, 3000);
function generateNewData(){
var ns = [];
ns.push({id:6,n:'n'+i,flag:1, flagx:1});
for(var i = 1; i < 10; i++){
ns.push({id:i+6,n:'n'+i, flagx:1});
}
return ns;
}
function generateNewLinks(){
var ns = [];
ns.push({source:7,target:8});
ns.push({source:7,target:9});
ns.push({source:7,target:10});
ns.push({source:7,target:11});
ns.push({source:7,target:12});
ns.push({source:7,target:13});
ns.push({source:7,target:14});
ns.push({source:7,target:15});
ns.push({source:7,target:16});
return ns;
}
.node {
stroke: #fff;
stroke-width: 1.5px;
}
.link {
stroke: #999;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
The lines of my chart are drawing off my chart. I've tried to replace this code:
yE.domain(d3.extent(data, function(E) { return E.close;}));
With this:
yE.domain([0,d3.max(data, function(E) {
return Math.max(E.close, E.Map1, EMap2, E.MapII);
})]);
Based on the answer from Bill: d3.js: dataset array w/ multiple y-axis values
Mine doesn't work.
My entire code:
var marginE = {top: 30, right: 20, bottom: 30, left: 50},
widthE = 400 - marginE.left - marginE.right,
heightE = 270 - marginE.top - marginE.bottom;
// Parse the date / time
var parseDateTimeE = d3.time.format("%Y-%m-%d%H:%M").parse;
// Set the ranges
var xE = d3.time.scale().range([0, widthE]);
var yE = d3.scale.linear().range([heightE, 0]);
// Define the axEs
var xAxisE = d3.svg.axis().scale(xE)
.orient("bottom").ticks(6);
var yAxisE = d3.svg.axis().scale(yE)
.orient("left").ticks(6);
var areaE = d3.svg.area()
.interpolate("bundle")
.x(function(e) { return xE(e.date); })
.y0(heightE)
.y1(function(e) { return yE(e.close); });
// Adds the svg canvas
var svgE = d3.select(".eur")
.append("svg")
.attr("width", widthE + marginE.left + marginE.right)
.attr("height", heightE + marginE.top + marginE.bottom)
.attr('id', 'charteur')
.attr('viewBox', '0 0 400 270')
.attr('perserveAspectRatio', 'xMinYMid')
.append("g")
.attr("transform",'translate(' + marginE.left + ',' + marginE.top + ')')
.attr('width', widthE)
.attr('height', heightE)
.style("font-size","12px");
// Get the data
d3.json("php/downl_EUR.php", function(error, data) {
data.forEach(function(E) {
E.date = parseDateTimeE(E.date +E.time);
E.close = +E.close;
E.MaP1 = +E.MaP1;
E.MaP2 = +E.MaP2;
E.MaPII = +E.MaPII; });
// Define the line
var valuelineE = d3.svg.line()
.interpolate("bundle")
.x(function(E) { return xE(E.date); })
.y(function(E) { return yE(E.close); });
var valuelineE2 = d3.svg.line()
.interpolate("bundle")
.x(function(E) { return xE(E.date); })
.y(function(E) { return yE(E.MaP1); });
var valuelineE3 = d3.svg.line()
.interpolate("bundle")
.x(function(E) { return xE(E.date); })
.y(function(E) { return yE(E.MaP2); });
var valuelineE4 = d3.svg.line()
.interpolate("bundle")
.x(function(E) { return xE(E.date); })
.y(function(E) { return yE(E.MaPII); });
// Scale the range of the data
xE.domain(d3.extent(data, function(E) { return E.date; }));
yE.domain(d3.extent(data, function(E) { return E.close;})); //****
//yE.domain([0,d3.max(data, function(E) {return Math.max(E.close, E.MapII);})]); ****
svgE.append("path")
.datum(data)
.attr("class", "area")
.attr("d", areaE);
// Add the valueline path.
svgE.append("path")
.attr("class", "lineE")
.style("stroke", "steelblue")
.attr("d", valuelineE(data));
svgE.append("path")
.attr("class", "lineE")
.style("stroke", "red")
.attr("d", valuelineE2(data));
svgE.append("path")
.attr("class", "lineE")
.style("stroke", "green")
.attr("d", valuelineE3(data));
svgE.append("path")
.attr("class", "lineE")
.style("stroke-dasharray", ("3, 3"))
.attr("d", valuelineE4(data));
// Add the X Axis
svgE.append("g")
.attr("class", "XaxisE")
.attr("transform", "translate(0," + heightE + ")")
.call(xAxisE);
// Add the Y Axis
svgE.append("g")
.attr("class", "YaxisE")
.call(yAxisE); });
I found the solution myself. Instead of MaP1, Map2 & MaPII, use map1, map2 and mapii -> no capital letters !! (also in the php-file)
Everything works fine now...
I have a list of images and a list of image titles. I want to be able to show a hover state (change css) for the title when I mouse over its corresponding image, but I cannot figure out how to connect the two pieces of data. My code is below. I have it so that when you click on the top number the information appears beneath.
<!DOCTYPE html>
<html>
<head>
<script src="d3.v2.js"></script>
<title>Untitled</title>
</head>
<body>
<script type="text/javascript" >
function barStack(d) {
var l = d[0].length
while (l--) {
var posBase = 0, negBase = 0;
d.forEach(function(d) {
d=d[l]
d.size = Math.abs(d.y)
if (d.y<0) {
d.y0 = negBase
negBase-=d.size
} else
{
d.y0 = posBase = posBase + d.size
}
})
}
d.extent= d3.extent(d3.merge(d3.merge(d.map(function(e) { return e.map(function(f) { return [f.y0,f.y0-f.size]})}))))
return d
}
var ratiodata = [[{y:3.3}],[{y:-1.5}]]
var imageList = [
[3.3, 28, -1.5, 13, 857, 3, 4, 7, [{paintingfile:"676496.jpg", title:"Dessert1"}, {paintingfile:"676528.jpg", title: "Dessert2"}]
]]
var h=400
var w=1350
var margin=25
var color = d3.scale.category10()
var div = d3.select("body").append("div")
.attr("class", "imgtooltip")
.style("opacity", 0);
var x = d3.scale.ordinal()
.domain(['255'])
.rangeRoundBands([margin,w-margin], .1)
var y = d3.scale.linear()
.range([h-margin,0+margin])
var xAxis = d3.svg.axis().scale(x).orient("bottom").tickSize(6, 0)
var yAxis = d3.svg.axis().scale(y).orient("left")
barStack(ratiodata)
y.domain(ratiodata.extent)
var svg = d3.select("body")
.append("svg")
.attr("height",h)
.attr("width",w)
svg.selectAll(".series")
.data(ratiodata)
.enter()
.append("g")
.classed("series",true)
.style("fill","orange")
.selectAll("rect").data(Object)
.enter()
.append("rect")
.attr("x",function(d,i) { return x(x.domain()[i])})
.attr("y",function(d) { return y(d.y0)})
.attr("height",function(d) { return y(0)-y(d.size)})
.attr("width",x.rangeBand());
svg.selectAll("text")
.data(imageList)
.enter()
.append("text")
.text(function(d) {
return d[0];
})
.attr("x", function(d, i) {
return x(x.domain()[i]) + x.rangeBand() / 2;
})
.attr("y", function(d) {
return h - (32.1100917431*d[0] +150);
})
.attr("font-size", "16px")
.attr("fill", "#000")
.attr("text-anchor", "middle")
//.on("click", function(d) {console.log(d[1]);})
.on("click", function(d) {
//Update the tooltip position and value
d3.selectAll("ul")
.remove();
d3.selectAll("li")
.remove();
d3.select("#PaintingDetails")
.append("ul")
.selectAll("li")
.data(d[8])
.enter()
.append("li")
.text(function(d){
return (d.title);
});
d3.select("#imageBox")
.append("ul")
.selectAll("li")
.data(d[8])
.enter()
.append("li")
.classed("Myimageslist",true)
.append("img")
.classed("Myimages",true)
.attr("src", function(d){
return ("http://images.tastespotting.com/thumbnails/" + d.paintingfile);
})
.attr("align", "top");
d3.select(".Myimages")
.on("mouseover", function(){
d3.select("#PaintingDetails")
.selectAll("li")
.classed("selected", true)
});
});
svg.append("g").attr("class","axis x").attr("transform","translate (0 "+y(0)+")").call(xAxis);
// svg.append("g").attr("class","axis y").attr("transform","translate ("+x(margin)+" 0)").call(yAxis);
</script>
<div id="PaintingDetails"></div>
<div id="imageBox"></div>
</body>
</html>
The quick and dirty solution would be to simply use the index of the data element to figure out which title matches which image:
d3.selectAll(".Myimages")
.on("mouseover", function(d, i) {
d3.select("#PaintingDetails")
.selectAll("li")
.classed("selected", function(e, j) { return j == i; })
});