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);
    

2 Answers 2

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"
                }
            ]
        }
    ]
}
Sign up to request clarification or add additional context in comments.

5 Comments

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.
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.
how can i load a json file instead ? it always gives error to me
Dev R: suggest you search around SO as there are plenty of answers to your specific question
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) {

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.