Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using RequireJS to load D3 and Word Cloud Layout

I am experiencing issues when trying to load D3 v4.8 and word cloud layout component (https://github.com/jasondavies/d3-cloud) using the RequireJS. While both D3.js and d3.layout.cloud.js are being downloaded to the browser, an exception is being thrown indicating the d3.layout.cloud is not a function.

Here is how I am configuring the RequireJS:

require.config({
    waitSeconds: 10,

    baseUrl: './scripts',

    paths: {
        d3: 'd3.min',
        jquery: 'jquery-2.1.0.min',
        cloud: 'd3.layout.cloud'
    },

    shim: {


    cloud: {
        deps:['jquery', 'd3']
    }

}

});

The line of code that throws an exception is d3.layout.cloud().size([width, height]) and can be found in the function below:

function wordCloud(selector) {

    var width = $(selector).width();
    var height = $(selector).height();

    //var fill = d3.scale.category20();

    //Construct the word cloud's SVG element
    var svg = d3.select(selector).append("svg")
        .attr("width", width)
        .attr("height", height)
        .append("g")
        .attr("transform", "translate("+ width/2 +","+ height/2 +")")

    var fill = d3.scale.category20();

    //Draw the word cloud
    function draw(words) {
        var cloud = svg.selectAll("g text")
            .data(words, function(d) { return d.text; })

        //Entering words
        cloud.enter()
            .append("text")
            .style("font-family", "Impact")
            .style("fill", function(d, i) { return fill(i); })
            .attr("text-anchor", "middle")
            .attr('font-size', 1)
            .style("cursor", "hand")
            .text(function(d) { return d.text; })
            .on("click", function (d, i){
                window.open("http://www.google.com/?q=" + d.text, "_blank");
            });

        //Entering and existing words
        cloud
            .transition()
            .duration(600)
            .style("font-size", function(d) { return d.size + "px"; })
            .attr("transform", function(d) {
                return "translate(" + [d.x, d.y] + ")rotate(" + d.rotate + ")";
            })
            .style("fill-opacity", 1);

        //Exiting words
        cloud.exit()
            .transition()
            .duration(200)
            .style('fill-opacity', 1e-6)
            .attr('font-size', 1)
            .remove();
    }


    //Use the module pattern to encapsulate the visualisation code. We'll
    // expose only the parts that need to be public.
    return {

        //Recompute the word cloud for a new set of words. This method will
        // asycnhronously call draw when the layout has been computed.
        //The outside world will need to call this function, so make it part
        // of the wordCloud return value.
        update: function(words) {

            // min/max word size
            var minSize = d3.min(words, function(d) { return d.size; });
            var maxSize = d3.max(words, function(d) { return d.size; });

            var textScale = d3.scale.linear()
                .domain([minSize,maxSize])
                .range([15,30]);

            d3.layout.cloud().size([width, height])
                .words(words.map(function(d) {
                    return {text: d.text, size: textScale(d.size) };
                }))
                .padding(5)
                .rotate(function() { return ~~(Math.random() * 2) * 90; })
                .font("Impact")
                .fontSize(function(d) { return d.size; })
                .on("end", draw)
                .start();
        }
    }

}
like image 564
UncleZen Avatar asked May 03 '17 20:05

UncleZen


2 Answers

The latest version of d3-cloud only depends on d3-dispatch so it should work fine with any version of D3. I think the issue here is that you're using RequireJS (AMD) to reference the d3.layout.cloud.js file, but you're not using RequireJS to use the library (configured as cloud in your example). See the following example:

requirejs.config({
  baseUrl: ".",
  paths: {
    cloud: "d3.layout.cloud" // refers to ./d3.layout.cloud.js
  }
});

requirejs(["cloud"], function(cloud) { // depends on "cloud" defined above
  cloud()
    .size([100, 100])
    .words([{text: "test"}])
    .on("word", function() {
      console.log("it worked!");
    })
    .start();
});

If you prefer to use CommonJS-style require(…) you can also use this with RequireJS if you use the appropriate define(…) syntax, like in this quick example.

like image 171
Jason Davies Avatar answered Oct 06 '22 14:10

Jason Davies


While d3-cloud itself is compatible with both D3 V3 and V4, most examples you will find are not, and your current code is not, it can only work with V3.

To make it work with V4 you need to replace all references to d3.scale. For example, d3.scale.category20() becomes d3.scaleOrdinal(d3.schemeCategory20). For a small runnable example that is compatible with both, see Fiddle #1. It uses RequireJS to load d3, d3-cloud, and jQuery. Try changing the D3 version in require config at top of JS part.

Let's settle on V3 for now, since your code relies on it. There are still a few issues:

Your must use objects for d3 & d3cloud & jQuery that are obtained via a call to require. And with RequireJS this is asynchronous (because it needs to fetch the JS scripts programmatically, and in a browser that can only be done asynchronously). You put your code within a callback function (for more info, see RequireJS docs):

[ Edit: see Jason's answer for alternative syntax. ]

require(['d3', 'd3cloud', 'jQuery'], function(d3, d3cloud, $) {
    /* Your code here.
       Inside "require" function that you just called,
       RequireJS will fetch the 3 named dependencies,
       and at the end will invoke this callback function,
       passing the 3 dependencies as arguments in required order.
     */
});

In this context (and versions) d3 cloud main function is not available under d3.layout.cloud(), so you need to replace that with d3cloud(), assuming that's the name of the argument passed as callback to require.

You must make sure you never pass width or height of 0 to d3cloud.size([width, height]), or it will enter an infinite loop. Unfortunately it can easily happen if you use $(selector).height(), depending on content of your page, and possible "accidents". I suggest var height = $(selector).height() || 10; for example.

Not a programming problem, but the function you pass to .rotate() is taken from an example, and maybe you want to change that: it yields only 2 possible values, 0 or 90, I find this monotonous, the default one is prettier. So I removed the line entirely in the example below. Maybe you'll want it back, just add your .rotate(...) line.

Fiddle #2: a complete working example based on your piece of code.

like image 25
Hugues M. Avatar answered Oct 06 '22 14:10

Hugues M.