Best d3 scale for displaying integers

I want to build a scale that maps a series of consecutive integers (indices of characters in a string) to regular intervals in another range of integers (pixels, say, 0-600). That is, I would like to assign symbols to pixels and, conversely, as often as possible, the length of one of them is not necessarily a multiple of the other.

For example, matching [0,1,2,3] to 400 pixels, I would expect

0 -> 0-99 1 -> 100-199 2 -> 200-299 3 -> 300-399 

and vice versa

 0-99 -> 0 100-199 -> 1 200-299 -> 2 300-399 -> 3 

whereas for a display from 0-4000 to 400 pixels, I would expect

 0-9 -> 0 10-19 -> 1 etc. 

What is the best scale for this in d3?

On the one hand, I am afraid that discrete scales will not use the fact that the domain is divided equally and generates a huge switch statement if the number of elements is large. Since I will use the scale for each element to draw an image, I am worried about performance.

On the other hand, a linear scale such as

 d3.scaleLinear() .domain([0,399]) // 400 pixels .rangeRound([0,3]) // 4 elements 

gives me

 0 0 66 0 // 1st interval has 66 pixels 67 1 199 1 // other intervals have 132 pixels 200 2 332 2 333 3 // last interval has 66 pixels 400 3 

( fiddle )

therefore, the interpolator returns unequal intervals (shorter at the ends).

Edit: don't use d3, it's not hard to implement:

 function coordinateFromSeqIndex(index, seqlength, canvasSize) { return Math.floor(index * (canvasSize / seqlength)); } function seqIndexFromCoordinate(px, seqlength, canvasSize) { return Math.floor((seqlength/canvasSize) * px); } 

Too bad only if it does not come with d3 scales, as it will become much more readable.

+5
source share
1 answer

d3 Quantize Scale is the best option if you want to display the interval. However, scale maps between discrete values โ€‹โ€‹and continuous intervals. I do not 100% understand what you want to do, but let's see how I could do some of the things you mentioned in the quantization scale.

Matching integers with intervals is simple if you know that d3 uses half-open intervals [,] to split a continuous domain.

 var s1 = d3.scaleQuantize() .domain([0,400]) .range([0,1,2,3]); s1.invertExtent(0); // the array [0,100] represents the interval [0,100) s1.invertExtent(1); // [100,200) s1.invertExtent(2); // [200,300) s1.invertExtent(3); // [300,400) 

You can also list discrete values:

 var interval = s.invertExtent(0); d3.range(interval[0], interval[1]); // [0, 1, ... , 399] 

These are the good values โ€‹โ€‹you specified, and since you want to display integers in integer intervals, we need rounding when the numbers are not divisible. However, we can use Math.round.

 var s2 = d3.scaleQuantize() .domain([0,250]) .range([0,1,2,3]); s2.invertExtent(0); // [0, 62.5) s2.invertExtent(0).map(Math.round); // [0,63) ... have to still interpret as half open 

There is no mapping from the interval itself to an integer, but the scale displays a point in the interval from the domain (which is continuous) to its value in the range.

 [0, 99, 99.9999, 100, 199, 250, 399, 400].map(s1); // [0, 0, 0, 1, 1, 2, 3, 3] 

I also suspect that you switched the output of rangeRound from the linear scale with something else. I get

 var srr = d3.scaleLinear() .domain([0,3]) // 4 elements .rangeRound([0,399]); [0,1,2,3].map(srr); // [0, 133, 266, 399] 

and

 var srr2 = d3.scaleLinear() .domain([0,4]) // 4 intervals created with 5 endpoints .rangeRound([0,400]); [0,1,2,3,4].map(srr2); // [0, 100, 200, 300, 400] 

The result looks like a scale for us with a histogram with 50% coverage (then each position will be the midpoint of the interval equal to 132 pixels). I'm going to guess the reason is that rangeRound uses round for interpolation, not for gender.

You can also use the function designed for histograms if you want the width of the interval.

  var sb = d3.scaleBand().padding(0).domain([0,1,2,3]).rangeRound([0,400]); [0,1,2,3].map(sb); // [0, 100, 200, 300] sb.bandwidth(); // 100 

Not that it simplifies the code.

As soon as I get to the functions you implement, it seems that the requirements are much simpler. Actually there are no intervals. The problem is that there is no single mapping. The best solution is what you did, or just use two linear scales with a custom interpolator (to find the floor, not round off.

 var interpolateFloor = function (a,b) { return function (t) { return Math.floor(a * (1 - t) + b * t); }; } var canvasSize = 400; var sequenceLength = 4000; var coordinateFromSequenceIndex = d3.scaleLinear() .domain([0, sequenceLength]) .range([0, canvasSize]) .interpolate(interpolateFloor); var seqIndexFromCoordinate = d3.scaleLinear() .domain([0, canvasSize ]) .range([0, sequenceLength]) .interpolate(interpolateFloor); 
+3
source

All Articles