17

I am trying to render the following Dendrogram from my Rails app: http://bl.ocks.org/mbostock/4063570

I have a model with many attributes, but I would like to manually nest those attributes and simply use string interpolation to build up my own JSON string, then pass that to d3 directly.

Here is my code:

    <%= javascript_tag do %>
        var width = 960,
        height = 2200;

        var cluster = d3.layout.cluster()
        .size([height, width - 160]);

        var diagonal = d3.svg.diagonal()
        .projection(function(d) { return [d.y, d.x]; });

        var svg = d3.select("body").append("svg")
        .attr("width", width)
        .attr("height", height)
        .append("g")
        .attr("transform", "translate(40,0)");

        **d3.json("/assets/flare.json", function(root) {**
        var nodes = cluster.nodes(root),
        links = cluster.links(nodes);

        var link = svg.selectAll(".link")
        .data(links)
        .enter().append("path")
        .attr("class", "link")
        .attr("d", diagonal);

        var node = svg.selectAll(".node")
        .data(nodes)
        .enter().append("g")
        .attr("class", "node")
        .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; })

        node.append("circle")
        .attr("r", 4.5);

        node.append("text")
        .attr("dx", function(d) { return d.children ? -8 : 8; })
        .attr("dy", 3)
        .style("text-anchor", function(d) { return d.children ? "end" : "start"; })
        .text(function(d) { return d.name; });
        });

        d3.select(self.frameElement).style("height", height + "px");
    <% end %>

Here is my (unminified) JSON string:

var mystring = '{
    "name": "Product",
    "properties": {
        "id": {
            "type": "number",
            "description": "Product identifier",
            "required": true
        },
        "name": {
            "type": "string",
            "description": "Name of the product",
            "required": true
        },
        "price": {
            "type": "number",
            "minimum": 0,
            "required": true
        },
        "tags": {
            "type": "array",
            "items": {
                "type": "string"
            }
        },
        "stock": {
            "type": "object",
            "properties": {
                "warehouse": {
                    "type": "number"
                },
                "retail": {
                    "type": "number"
                }
            }
        }
    }
}';

Things I've tried:

  1. minifying the JSON so it's inputted as just one line (no effect)

  2. running JSON.parse(mystring) on the string

  3. looking through the D3 documentation and and googling for a way to modify the following function to accept a string instead of a file path:

    d3.json("/assets/flare.json", function(root) {
            var nodes = cluster.nodes(root),
            links = cluster.links(nodes);
    
m00am
  • 5,910
  • 11
  • 53
  • 69
Quincy Larson
  • 621
  • 1
  • 6
  • 16

2 Answers2

39

First, lets look at what d3.json does.

d3.json("/assets/flare.json", function(root) {
    // code that uses the object 'root'
});

This loads the file /assets/flare.json from the server, interprets the contents as JSON and passes the resulting object as the root argument to the anonymous function.

Where you already have a JSON object, you don't need to use the d3.json function - you can just use the object directly.

var root = {
   "name": "flare",
   "children": [
     ...
   ]
};
// code that uses the object 'root'

If the object is represented as a string, then you can use JSON.parse to get the object:

var myString = '{"name": "flare","children": [ ... ] }';
var root = JSON.parse(mystring);
// code that uses the object 'root'

Second, lets look at what d3.layout.cluster expects of your data. As per the docs:

... the default children accessor assumes each input data is an object with a children array ...

In other words, you data needs to be of the form:

var mystring = '{
    "name": "Product",
    "children": [
        {
            "name": "id",
            "type": "number",
            "description": "Product identifier",
            "required": true
        },
        ...
        {
            "name": "stock",
            "type": "object",
            "children": [
                {
                    "name: "warehouse",
                    "type": "number"
                },
                {
                    "name": "retail",
                    "type": "number"
                }
            ]
        }
    ]
}
knolleary
  • 9,777
  • 2
  • 28
  • 35
  • 1
    knolleary, thank you for the explanation. This is helpful, but what I am specifically looking for is: How do I change the d3.json("/assets/flare.json", function(root) { line to allow for a local string? d3.json's syntax is: d3.json(url[, callback]). I want to run the anonymous function on my string, rather than a URL. – Quincy Larson Jul 15 '13 at 18:28
  • 6
    Michael, you change the d3.json(..) line by getting rid of it... if you already have the string, you don't need use a function to load content from a url, which is all that d3.json does. The anonymous function currently specified in the d3.json(..) call can simply be explicitly called with the string you have. This is what I was trying to describe in the first half of my answer. – knolleary Jul 17 '13 at 09:31
  • how can i load a json file instead ? it always gives error to me – Dev R Nov 06 '13 at 08:18
  • Dev R: suggest you search around SO as there are plenty of answers to your specific question – knolleary Nov 06 '13 at 20:44
  • 2
    you can see it here https://stackoverflow.com/questions/10934853/d3-js-loading-json-without-a-http-get – Saqib Mobeen Aug 04 '17 at 12:46
1

d3.json actually takes URL as an argument, so instead of giving it the path to the file, I would suggest to delegate data management to the controller (especially, if in future you would need to load it from DB), so to simplify things:

  1. Create a method in your controller, which would actually open the file and return its content:
class YourFlareController < ApplicationController
    def load
        @data = File.read("app/assets/json/flare.json")
        render :json => @data
    end
end
  1. Make sure you have a route in your routes.rb

get "yourflare/load"

  1. And now in your javascript you can simply call

d3.json("http://host/yourflare/load", function(root) {

dehumanizer
  • 1,250
  • 12
  • 15