How to show d3.js circle pack graph in a Rails application? - javascript

I have been trying for long to make this : http://bl.ocks.org/mbostock/4063269#flare.json example work in my sample Rails app to learn about d3.js. But, I am getting hard time to make it work.
I put the following code in my index.html.erb file :
<script type="text/javascript">
var diameter = 960,
format = d3.format(",d"),
color = d3.scale.category20c();
var bubble = d3.layout.pack()
.sort(null)
.size([diameter, diameter])
.padding(1.5);
var svg = d3.select("body").append("svg")
.attr("width", diameter)
.attr("height", diameter)
.attr("class", "bubble");
d3.json("assets/data/flare.json", function(error, root) {
var node = svg.selectAll(".node")
.data(bubble.nodes(classes(root))
.filter(function(d) { return !d.children; }))
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
node.append("title")
.text(function(d) { return d.className + ": " + format(d.value); });
node.append("circle")
.attr("r", function(d) { return d.r; })
.style("fill", function(d) { return color(d.packageName); });
node.append("text")
.attr("dy", ".3em")
.style("text-anchor", "middle")
.text(function(d) { return d.className.substring(0, d.r / 3); });
});
// Returns a flattened hierarchy containing all leaf nodes under the root.
function classes(root) {
var classes = [];
function recurse(name, node) {
if (node.children) node.children.forEach(function(child) { recurse(node.name, child); });
else classes.push({packageName: name, className: node.name, value: node.size});
}
recurse(null, root);
return {children: classes};
}
d3.select(self.frameElement).style("height", diameter + "px");
</script>
I put the flare.json file inside my app/assets/data directory. But, it seems like the javascript can not load the flare.json file from that location. I am just not sure how to make this work :( How to specify the location of the json file so that the javascript can load it and the code works? Any suggestion would be much much helpful.

Instead of loading the json file via d3.json, I rendered its contents on the javascript file and used a variable to store it.
var root = <%= render partial: 'controller_view/flare.json', formats: [:json] %>;

Related

D3.js Appending line name to it

I have this chart:
The symbol names, '$TSLA' and '$AAPL' are not appended to the line, they are simply placed there with the appropriate x and y values. I would like to append them to the line, such that if the line ended at a different position, the symbol name would appear next to it. Like in this example.
Here is the code:
var svg = d3.select('#graph')
.append('svg')
.attr('width', w)
.attr('height', h);
svg.append('path')
.datum(data)
.attr('class', 'stock')
.attr('stroke', '#157145') ....
//I have included the above code because it is what's different from the link-
//my lines are appended to the variable 'svg'. I am, however, selecting the correct class
//in the below code:
....
var sec = d3.selectAll(".stock")
.data(kvals) //this data follows the same pattern as the examples'
.enter().append("g")
.attr("class", "stock");
sec.append("text")
.datum(function(d){
return {
name: d.name,
value: d.values[d.values.length-1]
};
})
.attr("transform", function(d){
console.log(xScale(d.value.date)); //not displayed in console.
return "translate(" + xScale(d.value.date) + "," + yScale(d.value.stock) +")"
})
.attr("x", 3)
.attr("dy", ".35em")
.text(function(d){
return d.name;
});
Thank you for your help. If you need all the code, I can paste it.
Here's the code I used to add the labels. First part (knames) is what I used to plot the line too. (there might be an easier way of doing this):
var knames = d3.scaleOrdinal();
knames.domain(Object.keys(data[0]).filter(function(key){ //only get non-sma values
return !key.match(/([_])|(d)|(band)\w+/g)
}));
var kvals = knames.domain().map(function(name){
return {
name: name,
values: data.map(function(d){
return {
date: d.date,
stock: d[name]
};
})
};
});
var sec = svg.selectAll("stock")
.data(kvals)
.enter().append("g")
.attr("class", "stock");
sec.append("text")
.datum(function(d){
return {
name: d.name,
value: d.values[d.values.length-1]
};
})
.attr("transform", function(d){
return "translate(" + xScale(d.value.date) + "," + yScale(d.value.stock) +")"
})
.attr("x", 7)
.attr("dy", ".3em")
.style("fill", function(d) {
return accent(d.name);
})
.style('font-family', 'Helvetica')
.style('font-size', '11px')
.style('letter-spacing', '1px')
.style('text-transform', 'uppercase')
.text(function(d){
return d.name;
});

d3 Circle Packing - Printing labels with multiple lines

I'm brand new to d3 (and JavaScript in general), and I'm trying to figure out how to alter this example so that each circle has its corresponding size field printed below the name. I tried just altering the JSON so the name fields were something like "MergeEdge\n743", but this did not work (it seems to just ignore the newline and print all on one line). Any tips?
A little tip, usually with d3 you prefer to have separate code for different objectives. It makes it easier to maintain and overall make it more understandable when you read your file.
In your case, we need to add a new text field, with coordinates which place the text below the current one. So we need to add :
node.filter(function(d) { return !d.children; }).append("text")
.attr("dy", "1.2em")
.text(function(d) { return Math.round(d.r).toString(); });
So the entire javaScript code will be :
var svg = d3.select("svg"),
diameter = +svg.attr("width"),
g = svg.append("g").attr("transform", "translate(2,2)"),
format = d3.format(",d");
var pack = d3.pack()
.size([diameter - 4, diameter - 4]);
d3.json("flare.json", function(error, root) {
if (error) throw error;
root = d3.hierarchy(root)
.sum(function(d) { return d.size; })
.sort(function(a, b) { return b.value - a.value; });
var node = g.selectAll(".node")
.data(pack(root).descendants())
.enter().append("g")
.attr("class", function(d) { return d.children ? "node" : "leaf node"; })
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
node.append("title")
.text(function(d) { return d.data.name + "\n" + format(d.value); });
node.append("circle")
.attr("r", function(d) { return d.r; });
node.filter(function(d) { return !d.children; }).append("text")
.attr("dy", "0.3em")
.text(function(d) { return d.data.name.substring(0, d.r / 3); });
node.filter(function(d) { return !d.children; }).append("text")
.attr("dy", "1.2em")
.text(function(d) { return Math.round(d.r).toString(); });
});

WEB API service call in D3.js to make circle packing

first I have made this Web API from where I am getting tree structure object.
private TreeService _studentServices = new TreeService();
private static List<TreeNode> FillRecursive(ICollection<SalaryDetail> flatObjects, int? Refid = null)
{
return flatObjects.Where(x => x.Refid.Equals(Refid)).Select(item => new TreeNode
{
Name = item.Name,
Id = item.Id,
Salary = item.Salary,
Refid = item.Refid,
Children = FillRecursive(flatObjects, item.Id)
}).ToList();
}
// GET api/values
public List<TreeNode> Get()
{
ICollection<SalaryDetail> salarydetails = _studentServices.GetAllSalaryDetails();
var tree = FillRecursive(salarydetails, null);
return tree;
}
then I called This API in D3 as given below to show the data as a D3.js circle packing graph.
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.0/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/d3js/3.5.16/d3.js"></script>
<script>
var diameter = 700,
format = d3.format(",d");
var pack = d3.layout.pack()
.size([diameter - 4, diameter - 4])
.value(function (d) { return d.salary; });
var svg = d3.select("body").append("svg")
.attr("width", diameter)
.attr("height", diameter)
.append("g")
.attr("transform", "translate(2,2)");
d3.json("http://localhost:56935/api/values", function (error, root) {
var node = svg.datum(root).selectAll(".node")
.data(pack.nodes)
.enter().append("g")
.attr("class", function (d) { return d.children ? "node" : "leaf node"; })
.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
});
node.append("title")
.text(function (d) { return d.name + (d.children ? "" : ": " + format(d.salary)); });
node.append("circle")
.attr("r", function (d) { return d.r; });
node.filter(function (d) { return !d.children; }).append("text")
.attr("dy", ".3em")
.style("text-anchor", "middle")
.text(function (d) { return d.name.substring(0, d.r / 3); })
.style("font-size", 20);
});
d3.select(self.frameElement).style("height", diameter + "px");
</script>
</head>
<body>
</body>
Although I am getting data through D3.json() function but not my circle packing graph, Not even getting single error. Please help me out to make these graph. where am I lacking.
The structure of the json returning by Web Api is given below
[{"Id":1,"Name":"James","Salary":null,"Refid":null,"Children":[{"Id":2,"Name":"David","Salary":null,"Refid":1,"Children":[{"Id":3,"Name":"Richard","Salary":null,"Refid":2,"Children":[{"Id":4,"Name":"John","Salary":1000,"Refid":3,"Children":[]},{"Id":5,"Name":"Robert","Salary":4000,"Refid":3,"Children":[]},{"Id":6,"Name":"Paul","Salary":6000,"Refid":3,"Children":[]}]},{"Id":7,"Name":"Kevin","Salary":null,"Refid":2,"Children":[{"Id":8,"Name":"Jason","Salary":5000,"Refid":7,"Children":[]},{"Id":9,"Name":"Mark","Salary":null,"Refid":7,"Children":[{"Id":10,"Name":"Thomas","Salary":1000,"Refid":9,"Children":[]},{"Id":11,"Name":"Donald","Salary":1000,"Refid":9,"Children":[]}]}]}]}]}]
1. Instead of returning a List at your endpoint, return an object (the first element from your current returning list). From D3 docs for pack layout:
the input argument to the layout is the root node of the hierarchy
2. Either rename the Children -> children property name (by default D3 expects hierarchical data to be under children key), or define a different key for your data like this:
var pack = d3.layout.pack()
.size([diameter - 4, diameter - 4])
.value(function (d) { return d.Salary; }) // Note that according to your data structure, your `Salary` should start with a capital letter.
.children(function (d) { return d.Children; }); // Define how to obtain children data.

d3.js code does not work in chrome or after downloading from github [duplicate]

I am very new to D3, and wanted to see how an example would work locally. I copied and pasted the bar graph code to a local file called index.html, and also copied over the data.tsv. For some reason, absolutely nothing is showing up when I open the file on a browser! I tried changing the script src to "d3/d3.v3.min.js" because that is the folder the d3 I downloaded is in. However, this does not work either. For every example I have tried I have yet to successfully view a D3 example. Help would be appreciated!
The index.html code is as follows:
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.bar {
fill: steelblue;
}
.x.axis path {
display: none;
}
</style>
<body>
<script src="d3/d3.v3.min.js"></script>
<script>
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var formatPercent = d3.format(".0%");
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")
.tickFormat(formatPercent);
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.tsv("data.tsv", type, function(error, data) {
x.domain(data.map(function(d) { return d.letter; }));
y.domain([0, d3.max(data, function(d) { return d.frequency; })]);
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")
.style("text-anchor", "end")
.text("Frequency");
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) { return x(d.letter); })
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.frequency); })
.attr("height", function(d) { return height - y(d.frequency); });
});
function type(d) {
d.frequency = +d.frequency;
return d;
}
</script>
and the data.tsv is in the following format:
letter frequency
A .08167
B .01492
C .02780
D .04253
E .12702
F .02288
G .02022
H .06094
I .06973
The d3.tsv method makes an AJAX request for data. On most browsers, this won't work locally due to the Same Origin Policy, which generally prohibits AJAX requests to file:/// urls.
To get an example that uses AJAX running locally, you'll need a local webserver. If you have Python, running
> python -m SimpleHTTPServer
from the command line in the directory with your files will do it.
and if you are using python 3
> python -m http.server 9000
If you prefer node.js, try http-server.
As an alternative, and I was myself suggested by Lars Kotthoff when trying to work with .tsv/.csv files, you can work directly for this purpose on:
http://plnkr.co/
This enables you to work with all the .json / .tsv / .csv files you like, and share them with people for collaboration. You can do this anonymously or not, what matters is that you don't lose the then-generated HTTP address of your plunker.
One thing to pay attention to: you cannot upload directly the files in the way you would do on an FTP server, but you should instead:
Click on "new file"
Type the name of your .csv / .tsv / .json
file as referred to in your code (including the extension)
Copy
and paste the code contained in this file as is.
Don't forget
to update the names of your .css / .js if required so the match with
that of your index.html
As already stated, you're most likely encountering a CORS issue with the XHR in the d3 library for an external resource to parse the JSON data.
However, here is another solution: use JSONP and Express/Node.js in conjunction with a jQuery function to initiate a client-side request for JSON, instead of using the original wrapper for d3 functions.
Had to remove the original d3.json wrapper and populate the adjacency diagrams with data from the request. Begin by cloning or downloading jsonp-d3-experiment and start the server using node server.js. Of course you need to have Node.js installed globally, beginning with Node Packaged Modules (npm). Copy your program into a subdirectory.
Put your JSON data in the jsonp-d3-experiment directory and modify server.js to route the request to your data:
// Return data from callback
server.get('/example', function(req, res)
{
// Read the JSON data and send to JSONP response
readJSON('example.json', function (e, json)
{
if (e) { throw e; }
res.jsonp(json);
});
});
Below is the code I modified for a co-occurrence matrix. I moved the entire script into $.getJSON and removed the d3.json function altogether.
<script>
$.getJSON("http://localhost:8080/example?callback=?", function(result){
miserables = result;
var margin = {
top: 80,
right: 0,
bottom: 10,
left: 80
},
width = 720,
height = 720;
var x = d3.scale.ordinal().rangeBands([0, width]),
z = d3.scale.linear().domain([0, 4]).clamp(true),
c = d3.scale.category10().domain(d3.range(10));
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.style("margin-left", -margin.left + "px")
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var matrix = [],
nodes = miserables.nodes,
n = nodes.length;
// Compute index per node.
nodes.forEach(function(node, i) {
node.index = i;
node.count = 0;
matrix[i] = d3.range(n).map(function(j) {
return {
x: j,
y: i,
z: 0
};
});
});
// Convert links to matrix; count character occurrences.
miserables.links.forEach(function(link) {
matrix[link.source][link.target].z += link.value;
matrix[link.target][link.source].z += link.value;
matrix[link.source][link.source].z += link.value;
matrix[link.target][link.target].z += link.value;
nodes[link.source].count += link.value;
nodes[link.target].count += link.value;
});
// Precompute the orders.
var orders = {
name: d3.range(n).sort(function(a, b) {
return d3.ascending(nodes[a].name, nodes[b].name);
}),
count: d3.range(n).sort(function(a, b) {
return nodes[b].count - nodes[a].count;
}),
group: d3.range(n).sort(function(a, b) {
return nodes[b].group - nodes[a].group;
})
};
// The default sort order.
x.domain(orders.name);
svg.append("rect")
.attr("class", "background")
.attr("width", width)
.attr("height", height);
var row = svg.selectAll(".row")
.data(matrix)
.enter().append("g")
.attr("class", "row")
.attr("transform", function(d, i) {
return "translate(0," + x(i) + ")";
})
.each(row);
row.append("line")
.attr("x2", width);
row.append("text")
.attr("x", -6)
.attr("y", x.rangeBand() / 2)
.attr("dy", ".32em")
.attr("text-anchor", "end")
.text(function(d, i) {
return nodes[i].name;
});
var column = svg.selectAll(".column")
.data(matrix)
.enter().append("g")
.attr("class", "column")
.attr("transform", function(d, i) {
return "translate(" + x(i) + ")rotate(-90)";
});
column.append("line")
.attr("x1", -width);
column.append("text")
.attr("x", 6)
.attr("y", x.rangeBand() / 2)
.attr("dy", ".32em")
.attr("text-anchor", "start")
.text(function(d, i) {
return nodes[i].name;
});
function row(row) {
var cell = d3.select(this).selectAll(".cell")
.data(row.filter(function(d) {
return d.z;
}))
.enter().append("rect")
.attr("class", "cell")
.attr("x", function(d) {
return x(d.x);
})
.attr("width", x.rangeBand())
.attr("height", x.rangeBand())
.style("fill-opacity", function(d) {
return z(d.z);
})
.style("fill", function(d) {
return nodes[d.x].group == nodes[d.y].group ? c(nodes[d.x].group) : null;
})
.on("mouseover", mouseover)
.on("mouseout", mouseout);
}
function mouseover(p) {
d3.selectAll(".row text").classed("active", function(d, i) {
return i == p.y;
});
d3.selectAll(".column text").classed("active", function(d, i) {
return i == p.x;
});
}
function mouseout() {
d3.selectAll("text").classed("active", false);
}
d3.select("#order").on("change", function() {
clearTimeout(timeout);
order(this.value);
});
function order(value) {
x.domain(orders[value]);
var t = svg.transition().duration(2500);
t.selectAll(".row")
.delay(function(d, i) {
return x(i) * 4;
})
.attr("transform", function(d, i) {
return "translate(0," + x(i) + ")";
})
.selectAll(".cell")
.delay(function(d) {
return x(d.x) * 4;
})
.attr("x", function(d) {
return x(d.x);
});
t.selectAll(".column")
.delay(function(d, i) {
return x(i) * 4;
})
.attr("transform", function(d, i) {
return "translate(" + x(i) + ")rotate(-90)";
});
}
var timeout = setTimeout(function() {
order("group");
d3.select("#order").property("selectedIndex", 2).node().focus();
}, 5000);
});
</script>
Notice that now the JSON data is in result, so the easiest thing to do was to assign it to miserables.
Note: jQuery is required for this solution.
Now you should be able to locally open and render all your d3 visualizations without hosting them on a server--simply open them in your browser straight from your local filesystem.
HTH!

How to display property text on mouseover in d3 map

I am new to d3 and trying to figure out how to get a property ("NAME") to show up when hovering over a polygon in a map. I am aware that I should be doing something along the lines of .on("mouseover", function(d,i) { some function that returns properties.NAME } but can't figure out where to go from there. Here is the js as written, which just statically places the NAME property on each polygon:
<script>
var width = 950,
height = 650;
var projection = d3.geo.albers()
.scale(120000)
.center([22.85, 40.038]);
var path = d3.geo.path()
.projection(projection);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
d3.json("newnabes.json", function(error, topology) {
var nabes = topojson.object(topology, topology.objects.temp);
svg.selectAll("path")
.data(nabes.geometries)
.enter().append("path")
.attr("d", path);
svg.selectAll(".subunit-label")
.data(nabes.geometries)
.enter().append("text")
.attr("class", function(d) { return "subunit-label " + d.id; })
.attr("transform", function(d) { return "translate(" + path.centroid(d) + ")"; })
.attr("dy", ".35em")
.text(function(d) { return d.properties.NAME; });
});
</script>
Here is a small chunck of the json
{"type":"Topology",
"transform":{
"scale":[0.00003242681758896625,0.000024882264664420337],
"translate":[-75.28010087738252,39.889167081829875]},
"objects":{
"temp":{
"type":"GeometryCollection",
"geometries":[{
"type":"Polygon",
"id":1,
"arcs":[[0,1,2,3,4,5,6]],
"properties":{"NAME":"Haddington"}
},{
"type":"Polygon",
"id":2,
"arcs":[[7,8,9,10,-3,11]],
"properties":{"NAME":"Carroll Park"}
}...
Thanks
So I figured it out, courtesy of: Show data on mouseover of circle
The simplest solution is to just append the names to the svg title attribute:
svg.selectAll("path")
.data(nabes.geometries)
.append("svg:title")
.attr("class", function(d) { return "path " + d.id; })
.attr("transform", function(d) { return "translate(" + path.centroid(d) + ")"; })
.attr("dy", ".35em")
.text(function(d) { return d.properties.NAME; });
Still investigating a more stylish solution to the problem (eg tipsy).

Categories

Resources