Tuesday, October 4, 2011

Dygraphs Coordinate Systems, 3/3

This is the last of three posts about the coordinate systems used throughout Dygraphs.
  • Part 1: Introduction and the Dygraph Data Coordinate System
  • Part 2: DOM Coordinate System
  • Part 3: Percentage Coordinate System
Dygraph Percentage Coordinate System

Update: Yes, it's really the fraction of the drawable area (from zero to one) rather than a percentage (0 to 100.) If we rename the methods, I'll update this post.

There’s an additional coordinate system which represents percentages from the edges of the drawing area, relative to upper-left corner. These aren’t often required for public API, but can be useful when having to work strictly in the viewable area of the canvas. This is different than the DOM coordinate system which has its origin off the viewable area of the canvas. In this case, this coordinate system is all about the viewable area of the canvas.

These are accessible by the methods toPercentXCoord and toPercentYCoord.

toPercentXCoord takes a data X data value and converts it to a percentage from the left edge of the canvas. This diagram gives you an idea:
And to hit the point home, here’s a snippet of code which demonstrates values created by this function:

for (x = 1; x <= 9; x++) {
 console.log(x, '=>', g.toPercentXCoord(x));
}
1 "=>" 0
2 "=>" 0.125
3 "=>" 0.25
4 "=>" 0.375
5 "=>" 0.5
6 "=>" 0.625
7 "=>" 0.75
8 "=>" 0.875
9 "=>" 1


toPercentYCoord does the same thing along the Y axis, but goes from top to bottom.
The drawing area along the y-axis goes from 0-131, so let’s look at toPercentYCoord.

g.toPercentYCoord(0)
1


g.toPercentYCoord(131)
0


g.toPercentYCoord(120)
0.08396946564885496


g.toPercentYCoord(65.5)
0.5

Converting percentages back to data coordinates can be performed with this function:


function(pct) {
  var range = g.xAxisRange();
  return ((range[1] - range[0]) * pct) + range[0];
}

and a similar one for y-axis conversion. Validation is left up to the reader.

Summary

Each coordinate system has its own origin:
  • The Data coordinate system has its initial origin in the lower left corner, with increasing values moving up and to the right. This doesn’t mean the lower-right corner is (0,0), since the graph can be moved or resized.
  • The DOM coordinate system has its origin in the upper left corner of the unclipped canvas, with increasing values moving down and to the right. This is not the same corner as the percentage coordinate system. However, you are limited to drawing inside the graph area.
  • The percentage coordinate system has its origin in the upper left corner of the clipped canvas, with increasing values moving down and to the right.

API that can be used for translating between these coordinate systems are:
  • toDomXCoord and toDomYCoord convert data coordinate values to DOM coordinate values
  • toDataXCoord and toDataYCoord convert DOM coordinate values to data coordinate values
  • toPercentXCoord and toPercentYCoord convert data coordinate values to canvas percentages.

Monday, October 3, 2011

Dygraphs Coordinate Systems, 2/3

Update: 8:30AM Oct 4: to clarify several items.

This is the second of three posts about the coordinate systems used throughout Dygraphs.
  • Part 1: Introduction and the Dygraph Data Coordinate System
  • Part 2: DOM Coordinate System
  • Part 3: Percentage Coordinate System

DOM Coordinate System

Another coordinate system is the DOM coordinate system, which maps to coordinates of the underlying canvas.

(DOM coordinates are useful for mouse event handling. drawing on the underlay canvas, to name two things.)

DOM coordinates typically have (0,0) in the upper-left corner. While you might expect the corner to be in the drawing area, you’ll be surprised where that corner precisely is.


The Dygraph canvas coordinates don’t begin and end in what you would think of as the drawing area. No, in fact the canvas is widened to take up the space of the entire parent div element.

(People not familiar with Dygraphs may not realize, but the labels themselves are not canvas-drawn. They're part of the HTML DOM. As someone who had to understand these coordinate systems the DOM coordinate origin was a surprise. Other graphing systems draw their labels right on the canvas, e.g. http://code.google.com/apis/chart/, Dygraphs' does not.)

Note: A demo of a Dygraph without axis, using the full parent div element for its drawing area can be found at http://dygraphs.com/tests/unboxed-spark.html.

Clipping and other hackery

Here's an example that will help bring home home the difference between the full canvas area and the area exposed to the user, by way of a fun example, by showing how the image above was drawn:


g.updateOptions({
 underlayCallback: function(ctx) {
   ctx.canvas.width = ctx.canvas.width; // 1
   for (var y = 0; y <= 350; y += 50) {
     ctx.moveTo(0, y);
     ctx.lineTo(500, y);
     ctx.stroke();
   }
   for (var x = 0; x <= 500; x += 50) {
     ctx.moveTo(x, 0);
     ctx.lineTo(x, 350);
     ctx.stroke();
   }
}});


The line marked // 1, above is important. When the underlayCallback is called, it hands the caller a pre-initialized canvas context such that anything outside the graph area is clipped. (Having a clipping area is useful for graphing lines that exceed the boundaries of the clipped viewport.) A statement like // 1 reads like a no-op, but it has a side-effect of resetting canvas context, clearing the viewport, making that drawing possible. This is what the graph looks like without line // 1:

I hope you see now that (0, 0) doesn't represent the corner of the drawing area, but some spot which is fundamentally inaccessible to the user.

Note: Please don’t use this trick in graphs and applications you care about. This is only a hack for demonstrative purposes.

Conversion methods

The Dygraphs API comes with methods for converting between Data coordinates and Canvas coordinates

toDomXCoord converts from data X coordinates to DOM X coordinates


for (x = 1; x <= 9; x++) {
 console.log(x, '=>', g.toDomXCoord(x));
}
1 "=>56
2 "=>110.875
3 "=>165.75
4 "=>220.625
5 "=>275.5
6 "=>330.375
7 "=>385.25
8 "=>440.125
9 "=>495


toDomYCoord converts from data Y coordinats to DOM Y coordinates

for (y = 0; y <= 120; y+=20) {
 console.log(y, '=>', g.toDomYCoord(y));
}
0 "=>330
20 "=>279.618320610687
40 "=>229.23664122137407
60 "=>178.85496183206106
80 "=>128.4732824427481
100 "=>78.09160305343512
120 "=>27.709923664122137

and toDataXCoord and toDataYCoord convert X and Y values from the DOM coordinate system to the Data coordinate system.


g.toDataXCoord(56)
1
g.toDataYCoord(330)
0

Sunday, October 2, 2011

Dygraphs Coordinate Systems, 1/3

This is the first of three posts about the coordinate systems used throughout Dygraphs.
  • Part 1: Introduction and the Dygraph Data Coordinate System
  • Part 2: DOM Coordinate System
  • Part 3: Percentage Coordinate System
Introduction
This series of articles covers the three primary coordinate systems used in Dygraphs. They are: data coordinates, DOM coordinates, and percentage coordinates.

Each has its own distinct rules about origins and directions.

The entire series is based on this graph:



which is generated by this snippet of code:

new Dygraph(document.getElementById("graph"),
    [
      [1, [10,  10, 100]],
      [2, [15,  20, 110]],
      [3, [10,  30, 100]],
      [4, [15,  40, 110]],
      [5, [10, 120, 100]],
      [6, [15,  50, 110]],
      [7, [10,  70, 100]],
      [8, [15,  90, 110]],
      [9, [10,  50, 100]]
     ], {
       width: 500,
       height: 350,
       customBars: true,
       errorBars: true
     });

Dygraph Data coordinate system

The most obvious coordinate system to dygraphs is its inherent data coordinate system. The best way to demonstrate that coordinate system is through an example:

This highlighted point can be represented by the data coordinate system (4, 40). We’re even aided by the display on top showing the currently selected point.

Not to put too fine a point on it, but the x-value is 4, and the y-value is 40.

Graph data is always specified in the data coordinate system, and dygraphs does the job of translating those into points on the canvas. This should be no surprise to you, reading the code above shows the middle value for x=4 is 40.

This doesn’t always mean that the origin is in the lower-left corner of the graph area, since a graph can be resized and moved, but values almost always increase up and to the right.#

Before moving on to the next coordinate system, let’s play with the graph and its API. You can skip this section if you’re already comfortable with pulling values out of the graph.

Sidebar: Extremes and Visual Ranges

Extremes

Dygraphs provides a function that describes the extremes, or minimum and maximum data values, of the x-axis with xAxisExtremes:
g.xAxisExtremes()
[1, 9]

There is currently no yAxisExtremes function but Dygraphs an open source project; contributions are always welcome.

Visible Ranges

xAxisRange shows the visible data range along the x axis:
g.xAxisRange()
[1, 9]

Similarly yAxisRange shows the visible data range along the specified y axis:
g.yAxisRange()
[0, 131]

You’re wondering why the y axis range ends at 131 and not 120. This is because Dygraphs automatically adds a buffer to the top of most graphs’ y ranges for improved readability.
If you want the range to be precise, you can use the valueRange option with g.updateOptions({valueRange: [0, 120]}):

(Note there is currently a rendering bug where setting the value to 120 does something funny. This graph was rendered with a value range of 0-120.1.)

Remember these are visual ranges and not extremes of the data. So panning a graph along the x axis would result in a change in the x-axis range, but not x-axis extremes.