Lawrence Spencer

Reliable JavaScript


Скачать книгу

id="x12_x_12_i38"> Figure 1.2 presents the same data in a radial layout (http://bl.ocks.org/mbostock/1044242). D3 is very flexible. It is also very concise; each diagram takes just a few dozen lines of pleasingly formatted JavaScript to create.

Figure 1.2

      D3’s home page is http://d3js.org, with source code available at https://github.com/mbostock/d3. This is real JavaScript, not for the faint of heart and orders of magnitude more artful than the field-validators and button-handlers that are sprinkled through a typical website.

      In fact, it’s so artful as to be overwhelming at first read, so we have simplified just one corner of it for discussion. Listing 1-1 is an abridged version of d3.svg.line, a function that creates an SVG line generator. An explanation follows the listing.

      LISTING 1-1: A function to create an SVG line (code filename: rj3\rj3.js)

You would use this function to turn an array of data into an SVG path. SVG paths are just strings in the small language of SVG. Suppose you wanted to draw a line like the one in Figure 1.3.

Figure 1.3

      The SVG <path> element would be

      In English, that says to pick up the pen and move it (“M”) to the (x, y) coordinate (10, 130), and then draw a line (“L”) to (100, 60), and then draw another line to (190, 160), and then finish with a line to (280, 10).

      So how does the code in Listing 1-1 create a path like that? Consider Listing 1-2, which contains a sample call.

      LISTING 1-2: Sample call to rj3.svg.line() (code filename: rj3\pathFromArrays.js)

      On the highlighted line, what ends up in lineGenerator? Well, according to the last line of Listing 1-1, a call to rj3.svg.line() will return something called line. What is that? It is a function nested inside the outer function rj3.svg.line!

      NOTE In JavaScript, functions can nest inside other functions. This becomes an important way to control scope.

      By the way, we have retained D3’s names for most properties and variables so you can study the full listing at https://github.com/mbostock/d3/blob/master/src/svg/line.js if you wish, and be as well-oriented to it as possible. In only a few cases have we attempted to clarify things by changing a variable’s name. If you find it confusing that both the outer and inner functions are named line, well, this is very much in the spirit of all the D3 source code so you might as well learn to enjoy it.

      Yes, the function returns a function. This is a confusing no-no in most languages, but in JavaScript it’s a very idiomatic yes-yes that broadens your architectural options. If you’re going to code industrial-strength JavaScript, get used to functions being first-class objects that are passed as arguments, sent back as return values and just about anything else you can imagine. As first-class citizens of JavaScript, they can even have properties and methods of their own.

      NOTE In JavaScript, functions are objects that can have methods and properties. Your functions can have more flexibility and power than they might in other languages.

      You can see an example of attaching a method to a function in this part of Listing 1-1:

      It creates a function, x, that is a member of the returned function, line. Shortly, you will see how x and its twin, y, are used, and learn the very JavaScript-ey peculiarities of what’s inside them.

      So the call rj3.svg.line() returns a function. Continuing with Listing 1-2, the function is called with arrayData, which becomes the data argument to that inner line function from Listing 1-1. From there, the while loop fills the points array from the incoming data:

      Each element of data, held in the variable d, is passed to the getX and getY functions, which extract the x and y coordinates. (The use of call to invoke getX and getY will be covered at the end of this Case Study, as well as in Chapter 18. The + in front of getX and getY is a little trick to ensure that actual numbers, not numeric strings, go in the points array.) By default, those coordinates are the first and second elements of the 2-element array that comprises each element of arrayData. This occurs in the following snippet of Listing 1-1.

      Next, the segment function is called. This is a function at yet another level of nesting, private to the line function. It fills the segments variable, putting the SVG "M" command in the first element and the path in the second. From Listing 1-1 again:

      The path is produced by the interpolate function, which in the default implementation just joins the points (each implicitly converted to a string), putting an "L" between them. (We’ll cover interpolate in more detail later in this chapter.)

      Thus, the array

      becomes

      As a final step, the two elements of segments ("M" and the points-as-string) are joined in the return statement to produce the SVG path

      That’s the basic operation. Now for some complications that will illustrate additional ways that you can use JavaScript idiomatically.

      Suppose that each point in your data were an object instead of an [x,y] coordinate pair in array form. It might look something like this:

      How could you use rj3.svg.line to draw it? One way would be to transform the data on the way in, as in Listing 1-3.

      LISTING 1-3: Transforming the data on the way in (code filename: rj3\pathFromTransformedObjects.js)

      However, that would be wasteful, as it creates a second, complete copy of the data. It’s the sort of thing a C# programmer accustomed to the efficiencies of LINQ would do. (LINQ peels off just one element at a time from an array as requested, without making a second copy of the whole array.)

      The strategy in Listing 1-3 would also limit your possibilities in the user interface. You probably want your line to change dynamically if the data change. Thanks to the design decision that you’re going to see in a moment, D3 does this for you with no effort – but only with the data it knows about. If you have called its functions with only a one-time copy of the real data, you don’t get this benefit.

      The design decision is exemplified by the little functions, line.x and line.y. Listing 1-4 shows