I am trying to append images on rectangles and images locations are present in my array named arrFileUrl. Nodes of this color are white and I want to append these images on each of the generated rectangles. How can I do this?
var arrFileUrl = [], arrBrightness = [], arrPattern = [], arrSize = [];
var width = 1260,
height = 1200;
var fill = d3.scale.category10();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
d3.csv("data/Images.csv", function(error, data) {
data.forEach(function(d) {
arrFileUrl.push(d['FingerImageName']);
});
var nodes = d3.range(arrSize.length).map(function(i) {
return {index: i};
});
var force = d3.layout.force()
.nodes(nodes)
.gravity(0.05)
.charge(-1700)
.friction(0.5)
.size([width, height])
.on("tick", tick)
.start();
var node = svg.selectAll(".node")
.data(nodes)
.enter().append("rect")
.attr("class", "node")
.attr("width", 120)
.attr("height", 160)
.style("fill", "#fff")
.style("stroke", "black")
.call(force.drag);
node.append("image")
.attr("xlink:href", "https://github.com/favicon.ico")
.attr("x", 16)
.attr("y", 16)
.attr("width", 100)
.attr("height", 120);
svg.style("opacity", 1e-6)
.transition()
.duration(1000)
.style("opacity", 1);
function tick(e) {
// Push different nodes in different directions for clustering.
var k = 6 * e.alpha;
nodes.forEach(function(o, i) {
o.y += i & 1 ? k : -k;
o.x += i & 2 ? k : -k;
});
node.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; });
}
});
Do it this way:
var node = svg.selectAll(".node")
.data(nodes)
.enter().append("g");//make groups
//add rectangle to the group
node.append("rect")
.attr("class", "node")
.attr("width", 120)
.attr("height", 160)
.style("fill", "#fff")
.style("stroke", "black")
.call(force.drag);
//add image to the group
node.append("image")
.attr("xlink:href", "https://github.com/favicon.ico")
.attr("x", 16)
.attr("y", 16)
.attr("width", 100)
.attr("height", 120);
Tick function translate the full group
function tick(e) {
// Push different nodes in different directions for clustering.
var k = 6 * e.alpha;
nodes.forEach(function(o, i) {
o.y += i & 1 ? k : -k;
o.x += i & 2 ? k : -k;
});
//do transform
node.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
})
}
Problem in your code:
You were appending the image inside the rectangle DOM that is reason why image is not visible.
In tick function you are moving the x and y alone and not of the image, the approach should have been moving both of them in a group and translating the group as done above.
Related
I am trying to add checkboxes and text in a node. See below image of what I am trying to achieve.
I can see the checkbox in elements view but cannot view it in page.
But this is what I am getting for now.
Below is the code used.
(function() {
var width, height, rules, map, tasks, links, nodes, svg, tick, radius, force, link, node;
width = 960;
height = 500;
rules = [
['root', 'iot'],
['root', 'points'],
['root', 'camnative'],
['root', 'classifier'],
['points', 'classifier2'],
['camnative', 'classifier3'],
['classifier', 'consec'],
['iot', 'classifier1'],
['cloudclassif', 'schedule'],
['schedule', 'privacy'],
['privacy', 'roi'],
['roi', 'flooding'],
['classifier1', 'cloudclassif'],
['classifier2', 'cloudclassif'],
['classifier3', 'cloudclassif'],
['consec', 'cloudclassif']
];
map = d3.map();
rules.forEach(function(rule) {
map.set(rule[0], {
fixed: false
});
return map.set(rule[1], false);
});
map.set('root', {
fixed: true,
x: 100,
y: height / 2
});
// map.set('P4', {
// fixed: true,
// x: width / 2 - 100,
// y: height / 2
// });
tasks = map.keys();
links = rules.map(function(rule) {
return {
source: tasks.indexOf(rule[0]),
target: tasks.indexOf(rule[1])
};
});
nodes = tasks.map(function(k) {
var entry;
entry = {
name: k
};
if (map.get(k).fixed) {
entry.fixed = true;
entry.x = map.get(k).x;
entry.y = map.get(k).y;
}
return entry;
});
svg = d3.select("#chart")
.append("svg")
.attr("width", width)
.attr("height", height);
svg.append("svg:defs")
.append("svg:marker")
.attr("id", "arrow")
.attr("viewBox", "0 0 10 10")
.attr("refX", 0)
.attr("refY", 5)
.attr("markerUnits", "strokeWidth")
.attr("markerWidth", 8)
.attr("markerHeight", 6)
// .attr("orient", "auto")
.append("svg:path")
.attr("d", "M 0 0 L 10 5 L 0 10 z");
svg.append("line")
.attr("x1", 5)
.attr("x2", 50)
.attr("y1", 5)
.attr("y2", 50)
.style("stroke", "black")
.attr("stroke-width", 2)
.attr("marker-end", "url(#arrow)");
tick = function() {
var arrowheadLength = 8, // from markerWidth
nodeRadius = 10;
link.each(function(d) {
var x1 = d.source.x,
y1 = d.source.y,
x2 = d.target.x,
y2 = d.target.y,
angle = Math.atan2(y2 - y1, x2 - x1);
d.targetX = x2 - Math.cos(angle) * (nodeRadius + arrowheadLength);
d.targetY = y2 - Math.sin(angle) * (nodeRadius + arrowheadLength);
});
link.selectAll("line").attr("x1", function(d) {
return d.source.x;
}).attr("y1", function(d) {
return d.source.y;
}).attr("x2", function(d) {
return d.targetX;
}).attr("y2", function(d) {
return d.targetY;
}).attr("marker-end", "url(#arrow)");
node.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
};
radius = d3.scale.sqrt().range([0, 6]);
force = d3.layout.force().size([width / 2, height]).charge(-900).linkDistance(function(d) {
return 40;
});
force.nodes(nodes).links(links).on("tick", tick).start();
link = svg.selectAll(".link").data(links).enter().append("g").attr("class", "link");
link.append("line").style("stroke-width", 1).attr("marker-end", "url(#arrow)");
node = svg.selectAll(".node")
.data(nodes)
.enter()
.append("g")
.attr("class", "node")
.call(force.drag);
node.append("rect")
.attr("class", "node")
.attr("width", 100)
.attr("height", 50);
node.append("input")
.attr("type", "checkbox")
.attr("class", "mycheck")
.attr("fill", "black");
node.append("text")
.attr("x", function(d) { return (d) - 3; })
.attr("y", 50 / 2)
.attr("dy", ".35em")
.text(function(d) { return d.name; });
console.log("HERE2");
console.log("HERE5");
}).call(this);
I checked D3 v3 appending checkbox?, but that solution does not work.
Update
Added
node.append("foreignObject")
.attr("width", 100)
.attr("height", 100)
.append("xhtml:chart")
.append("div")
.append("input")
.attr("type", "checkbox");
Add the checkboxes are added to the node.
I played around with this and got a bit further. I found discovered a few things:
As mentioned by others, put the checkbox inside of a <foreignObject>
Use the "xhtml:" prefix before the html objects inside of the foreignObject.
You have some bad math in your data functions. For example: return (d) - 3; returns NaN. You need something like return d.x - 3;
Items inside of the <g> element are positioned relative to the group. In my fiddle example (link at bottom), I positioned the top-lefts of the rectangles at (0,-16).
// Create the <foreignObject>:
let ddiv = node.append("foreignObject")
.attr("x", -10)
.attr("y", -34)
.attr("width", 50)
.attr("height",50)
.append("xhtml:div") // <<<---- xhtml: prefix!
.classed("roundedOne", true)
ddiv.append("xhtml:input") // <<<---- xhtml: prefix!
.attr("type", "checkbox")
.attr("id", (d) => d.name);
ddiv.append("xhtml:label") // <<<---- xhtml: prefix!
.attr("for", (d) => d.name);
Check out my jsFiddle: https://jsfiddle.net/visioguy/yz5tfgjm/
I added some fancy checkbox styling that fit better into your boxes, which is why I added the extra <div> and <label> to your code. You'll have to fiddle with more of the margins and sizes and offsets, and perhaps some of the force-layout parameters to get the layout to work properly.
I am new to javascript and have been stuck at a problem for the better part of 2 weeks. I am trying to make a bar graph that updates in real time using data from Firebase. The structure of my database is:
title:
-------child1
-------child2
-------child3
-------child4
The data to firebase is provided from a python script that is working perfectly and is updating every child of title every 10 seconds.
I made a bar graph that is updating automatically via random number generation.
//Return array of 10 random numbers
var randArray = function() {
for(var i = 0, array = new Array(); i<10; i++) {
array.push(Math.floor(Math.random()*10 + 1))
}
return array
}
var initRandArray = randArray();
var newArray;
var w = 500;
var h = 200;
var barPadding = 1;
var mAx = d3.max(initRandArray)
var yScale = d3.scale.linear()
.domain([0, mAx])
.range([0, h])
var svg = d3.select("section")
.append("svg")
.attr("width", w)
.attr("height", h)
svg.selectAll("rect")
.data(initRandArray)
.enter()
.append("rect")
.attr("x", function(d,i) {return i*(w/initRandArray.length)})
.attr("y", function(d) {return h - yScale(d)})
.attr("width", w / initRandArray.length - barPadding)
.attr("height", function(d){return yScale(d)})
.attr("fill", function(d) {
return "rgb(136, 196, " + (d * 100) + ")";
});
svg.selectAll("text")
.data(initRandArray)
.enter()
.append("text")
.text(function(d){return d})
.attr("x", function(d, i){return (i*(w/initRandArray.length) + 20)})
.attr("y", function(d) {return h - yScale(d) + 15})
.attr("font-family", "sans-serif")
.attr("fill", "white")
setInterval(function() {
newArray = randArray();
var rects = svg.selectAll("rect")
rects.data(newArray)
.enter()
.append("rect")
rects.transition()
.ease("cubic-in-out")
.duration(2000)
.attr("x", function(d,i) {return i*(w/newArray.length)})
.attr("y", function(d) {return h - yScale(d)})
.attr("width", w / newArray.length - barPadding)
.attr("height", function(d){return yScale(d)})
.attr("fill", function(d) {
return "rgb(136, 196, " + (d * 100) + ")";
});
var labels = svg.selectAll("text")
labels.data(newArray)
.enter()
.append("text")
labels.transition()
.ease("cubic-in-out")
.duration(2000)
.text(function(d){return d})
.attr("x", function(d, i){return (i*(w/newArray.length) + 20)})
.attr("y", function(d) {return h - yScale(d) + 15})
.attr("font-family", "sans-serif")
.attr("fill", "white")
}, 3000)
Live bar chart on random number
I need to update the chart using the data from firebase. I already know how to connect firebase to js using the snapshot and have already tried it to no avail.
Also, need some help with the styling of the graph.
Please if anybody knows how I can finish this(its time sensitive).
Here's the code link in jsfiddle: Live bar chart d3
Thanks
Question: Is it possible for me to make each node individually, and then use the force layout to connect them? If not, how would I go about pre-placing the nodes? And if so, can I get some help with the syntax, please?
Context: I am new to D3, and am trying to make a force-directed graph for only five nodes as part of the landing page for an academic project. I am using this example and this example, and sort of want to make a combination of the two by putting my nodes in the arrays.
For example, could I do something like:
var w = 1300;
var h = 10000;
//An area for svg elements
var svgArea = d3.select("body").append("svg")
.attr("width", w)
.attr("height", h);
//All the node definitions
var nodeMain = svgArea.append("a")
.attr("height", 300)
.attr("width", 300)
.append("circle")
.attr("r", 300)
.attr("cx", 650)
.attr("cy", 700)
.attr("fill", "orange");
var nodeMedia = svgArea.append("a")
.attr("height", 200)
.attr("width", 200)
.append("circle")
.attr("r", 200)
.attr("cx", 250)
.attr("cy", 1150)
.attr("fill", "orange");
var nodeRef = svgArea.append("a")
.attr("height", 200)
.attr("width", 200)
.append("circle")
.attr("r", 200)
.attr("cx", 1050)
.attr("cy", 1150)
.attr("fill", "orange");
//Nodes for the visualization
var nodes = [nodeMain, nodeMedia, nodeRef];
//Connected using indices of the array
var edges = [{source: 1, target: 0}, {source: 2, target: 0}];
//Force-directed
var connect = d3.layout.force()
.size([w, h])
.gravity(1)
.distance(100)
.charge(-50);
connect.nodes(nodes).links(edges);
var orb = svgArea.selectAll(".node").data(nodes)
.enter().append("g")
.call(force.drag);
var link = svgArea.selectAll(".link").data(edges)
.enter()
.append("line")
.attr("class", "link");
connect.on("tick", function(){
link.attr("x1", function(d) {return d.source.x})
.attr("y1", function(d) {return d.source.y})
.attr("x2", function(d) {return d.source.x})
.attr("y2", function(d) {return d.source.y});
orb.attr("transform", function(d){ return "translate(" + d.x + "," + d.y + ")";});
});
connect.start();
(And in the event I asked a really silly question, would anyone mind directing me to some D3 resources where I can learn more of the concepts/syntax without emulating/relying purely on examples?)
Thank you in advance, everyone!
I have made slight changes in your code snippet and added necessary comments. Share your queries if any.
var w = 500;
var h = 500;
//An area for svg elements
var svgArea = d3.select("body").append("svg")
.attr("width", w)
.attr("height", h);
//Nodes for the visualization
var nodes = [{
name: "Main",
x: 80,
y: 10
}, {
name: "Media",
x: 15,
y: 40
}, {
name: "Reference",
x: 60,
y: 60
}];
//Connected using indices of the array
var edges = [{
source: 1,
target: 0
}, {
source: 2,
target: 0
}];
//Force-directed
var connect = d3.layout.force()
.size([w, h])
.gravity(1)
.distance(150)
.charge(-200);
connect
.nodes(nodes)
.links(edges);
//Creating links
var link = svgArea.selectAll(".link")
.data(edges)
.enter()
.append("line")
.attr("class", "link")
.style("stroke", "black");
//Creating nodes
var orb = svgArea.selectAll(".node")
.data(nodes)
.enter()
.append("g")
.attr("class", "node");
orb.append("circle")
.attr("r", 10)
.style("fill", "orange");
//Adding Labels
orb.append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) {
return d.name
});
//Adding images
orb.append("image")
.attr("xlink:href", "https://github.com/favicon.ico")
.attr("x", -10)
.attr("y", -10)
.attr("width", 20)
.attr("height", 20);
orb.on("click",function(d){
alert("clicked "+d.name);
});
connect.on("tick", function() {
//Updating the link positions during force simulation.
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
});
//Updating the node position during force simulation
orb.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
});
connect.start();
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
D3 has a rich documentation which is available here: GitHub
Fiddle: https://jsfiddle.net/gilsha/kv05y1hq/
If it's of any help I have the following Plunkers...
http://plnkr.co/edit/TiKKmvydqXNipe103juL?p=preview
http://plnkr.co/edit/ZSmvH05nnAD6cYZb0EM4?p=preview
The first one is to show/hide groups of elements when clicked on.
The second is to demonstrate drag/zoom.
Also the data is externalised into a json file and read in using...
d3.json("data.json", function(error, graph) {
This should enable you to reduce your node definitions down to one function.
I am creating an arc diagram where I'd like to, hopefully, find a way to prevent the overlap of arcs. There's an example of the working bl.ock here.
The darker lines in this case are overlapping lines where multiple nodes share the same edge. I'd like to prevent that, perhaps by doing two passes: the first would alternate the arc to go above the nodes rather than below, giving a sort of helix appearance; the second would draw a slightly larger arc if an arc already exists above/below to help differentiate the links.
var width = 1000,
height = 500,
margin = 20,
pad = margin / 2,
radius = 6,
yfixed = pad + radius;
var color = d3.scale.category10();
// Main
//-----------------------------------------------------
function arcDiagram(graph) {
var radius = d3.scale.sqrt()
.domain([0, 20])
.range([0, 15]);
var svg = d3.select("#chart").append("svg")
.attr("id", "arc")
.attr("width", width)
.attr("height", height);
// create plot within svg
var plot = svg.append("g")
.attr("id", "plot")
.attr("transform", "translate(" + pad + ", " + pad + ")");
// fix graph links to map to objects
graph.links.forEach(function(d,i) {
d.source = isNaN(d.source) ? d.source : graph.nodes[d.source];
d.target = isNaN(d.target) ? d.target : graph.nodes[d.target];
});
linearLayout(graph.nodes);
drawLinks(graph.links);
drawNodes(graph.nodes);
}
// layout nodes linearly
function linearLayout(nodes) {
nodes.sort(function(a,b) {
return a.uniq - b.uniq;
})
var xscale = d3.scale.linear()
.domain([0, nodes.length - 1])
.range([radius, width - margin - radius]);
nodes.forEach(function(d, i) {
d.x = xscale(i);
d.y = yfixed;
});
}
function drawNodes(nodes) {
var gnodes = d3.select("#plot").selectAll("g.node")
.data(nodes)
.enter().append('g');
var nodes = gnodes.append("circle")
.attr("class", "node")
.attr("id", function(d, i) { return d.name; })
.attr("cx", function(d, i) { return d.x; })
.attr("cy", function(d, i) { return d.y; })
.attr("r", 5)
.style("stroke", function(d, i) { return color(d.gender); });
nodes.append("text")
.attr("dx", function(d) { return 20; })
.attr("cy", ".35em")
.text(function(d) { return d.name; })
}
function drawLinks(links) {
var radians = d3.scale.linear()
.range([Math.PI / 2, 3 * Math.PI / 2]);
var arc = d3.svg.line.radial()
.interpolate("basis")
.tension(0)
.angle(function(d) { return radians(d); });
d3.select("#plot").selectAll(".link")
.data(links)
.enter().append("path")
.attr("class", "link")
.attr("transform", function(d,i) {
var xshift = d.source.x + (d.target.x - d.source.x) / 2;
var yshift = yfixed;
return "translate(" + xshift + ", " + yshift + ")";
})
.attr("d", function(d,i) {
var xdist = Math.abs(d.source.x - d.target.x);
arc.radius(xdist / 2);
var points = d3.range(0, Math.ceil(xdist / 3));
radians.domain([0, points.length - 1]);
return arc(points);
});
}
Any pointers on how I might start approaching the problem?
Here is a bl.ock for reference. It shows your original paths in gray, and the proposed paths in red.
First store the counts for how many times a given path occurs:
graph.links.forEach(function(d,i) {
var pathCount = 0;
for (var j = 0; j < i; j++) {
var otherPath = graph.links[j];
if (otherPath.source === d.source && otherPath.target === d.target) {
pathCount++;
}
}
d.pathCount = pathCount;
});
Then once you have that data, I would use an ellipse instead of a radial line since it appears the radial line can only draw a curve for a circle:
d3.select("#plot").selectAll(".ellipse-link")
.data(links)
.enter().append("ellipse")
.attr("fill", "transparent")
.attr("stroke", "gray")
.attr("stroke-width", 1)
.attr("cx", function(d) {
return (d.target.x - d.source.x) / 2 + radius;
})
.attr("cy", pad)
.attr("rx", function(d) {
return Math.abs(d.target.x - d.source.x) / 2;
})
.attr("ry", function(d) {
return 150 + d.pathCount * 20;
})
.attr("transform", function(d,i) {
var xshift = d.source.x - radius;
var yshift = yfixed;
return "translate(" + xshift + ", " + yshift + ")";
});
Note that changing the value for ry above will change the heights of different curves.
Finally you'll have to use a clippath to restrict the area of each ellipse that's actually shown, so that they only display below the nodes. (This is not done in the bl.ock)
I've tried the circle plot example as the following:
var x=20, y=20, r=50;
var sampleSVG = d3.select("#viz")
.append("svg")
.attr("width", 800)
.attr("height", 600);
sampleSVG.append("circle")
.style("stroke", "gray")
.style("fill", "white")
.attr("r", r)
.attr("cx", x)
.attr("cy", y);
But I want to figure out how to plot without a loop a sequence of circles from an array like:
data = [
[10,20,30],
[20,30,15],
[30,10,25]
];
Maybe this example could help?
var data = [
[10,20,30],
[20,30,15],
[30,10,25]
];
var height = 300,
width = 500;
var svg = d3.select('body').append('svg')
.attr('height', height)
.attr('width', width)
.append('g')
.attr('transform', 'translate(30, 30)');
// Bind each nested array to a group element.
// This will create 3 group elements, each of which will hold 3 circles.
var circleRow = svg.selectAll('.row')
.data(data)
.enter().append('g')
.attr('transform', function(d, i) {
return 'translate(30,' + i * 60 + ')';
});
// For each group element 3 circle elements will be appended.
// This is done by binding each element in a nested array to a
// circle element.
circleRow.selectAll('.circle')
.data(function(d, i) { return data[i]; })
.enter().append('circle')
.attr('r', function(d) { return d; })
.attr('cx', function(d, i) { return i * 60; })
.attr('cy', 0);
Live Fiddle