admin管理员组文章数量:1025465
I want to construct a scale that maps a range of successive integers (indexes of characters in a string) to regular intervals in another range of integers (pixels, say 0-600). That is, I would like to assign characters to pixels and conversely as regularly as possible, the length of one not being necessarily a multiple of the other.
For instance, mapping [0,1,2,3] to 400 pixels, I would expect
0 -> 0-99
1 -> 100-199
2 -> 200-299
3 -> 300-399
and conversely
0-99 -> 0
100-199 -> 1
200-299 -> 2
300-399 -> 3
while for mapping 0-4000 to 400 pixels, I would expect
0-9 -> 0
10-19 -> 1
etc.
What is the best scale to use for this in d3 ?
On one hand I am afraid that discrete scales will not use the fact that the domain is equally separated and generate a huge switch statement if the number of elements is big. Since I will use the scale on every 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)
so the interpolator returns unequal intervals (shorter at the ends).
Edit: not using d3, it is 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 e with d3 scales, since it would bee much more readable.
I want to construct a scale that maps a range of successive integers (indexes of characters in a string) to regular intervals in another range of integers (pixels, say 0-600). That is, I would like to assign characters to pixels and conversely as regularly as possible, the length of one not being necessarily a multiple of the other.
For instance, mapping [0,1,2,3] to 400 pixels, I would expect
0 -> 0-99
1 -> 100-199
2 -> 200-299
3 -> 300-399
and conversely
0-99 -> 0
100-199 -> 1
200-299 -> 2
300-399 -> 3
while for mapping 0-4000 to 400 pixels, I would expect
0-9 -> 0
10-19 -> 1
etc.
What is the best scale to use for this in d3 ?
On one hand I am afraid that discrete scales will not use the fact that the domain is equally separated and generate a huge switch statement if the number of elements is big. Since I will use the scale on every 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)
so the interpolator returns unequal intervals (shorter at the ends).
Edit: not using d3, it is 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 e with d3 scales, since it would bee much more readable.
Share Improve this question edited Aug 11, 2016 at 6:24 JulienD asked Aug 10, 2016 at 8:26 JulienDJulienD 7,29310 gold badges56 silver badges86 bronze badges 2- I am a little unclear about what your input and output will be. In the "while for mapping 0-4000 to 400 pixels" part of the question, you have gone from mapping an interval to an integer, as opposed to mapping the an integer to an interval as in the first example. – Steve Clanton Commented Aug 10, 2016 at 15:05
- I meant that if I have to distribute 4000 objects among 400 boxes, I expect 10 of them in each box. Or imagine you want to represent characters in a 4000 chars long string with different colors, and draw them in a canvas that is 400x400 px. Since you choose the input as integers, you just need to round the output of the scale, this is not a problem. – JulienD Commented Aug 10, 2016 at 18:54
1 Answer
Reset to default 5The d3 Quantize Scale is the best option if you want to map onto an interval. The scale maps between discrete values and a continuous interval, though. I am not 100% clear on what you want to do, but let's look at how I could do a few of the things you mention with the quantize scale.
Mapping integers to intervals is straightforward, as long as you know that d3 uses half-open intervals [,) to break up the 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 could also enumerate the discrete values:
var interval = s.invertExtent(0);
d3.range(interval[0], interval[1]); // [0, 1, ... , 399]
These are nice values you've given though, and since you want to map integers to intervals of integers, we will need rounding when numbers aren't divisible. We can just use Math.round though.
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 the integer, but the scale maps a point in an 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 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 output looks like a scale to us with a bar graph with 50% padding (then each position would be the midpoint of an interval that is 132 pixels). I am going to guess the cause is that rangeRound uses round to interpolate, rather than floor.
You could use a function designed for bar graphs also, 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 any of this makes the code simpler.
Once I get to the functions you implement, it seems like the requirements are much simpler. There aren't any intervals involved really. The problem is that there isn't a one-to-one mapping. The best solution is either what you have done or to just use two linear scales with a custom interpolator (to find the floor, rather than rounding.
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);
I want to construct a scale that maps a range of successive integers (indexes of characters in a string) to regular intervals in another range of integers (pixels, say 0-600). That is, I would like to assign characters to pixels and conversely as regularly as possible, the length of one not being necessarily a multiple of the other.
For instance, mapping [0,1,2,3] to 400 pixels, I would expect
0 -> 0-99
1 -> 100-199
2 -> 200-299
3 -> 300-399
and conversely
0-99 -> 0
100-199 -> 1
200-299 -> 2
300-399 -> 3
while for mapping 0-4000 to 400 pixels, I would expect
0-9 -> 0
10-19 -> 1
etc.
What is the best scale to use for this in d3 ?
On one hand I am afraid that discrete scales will not use the fact that the domain is equally separated and generate a huge switch statement if the number of elements is big. Since I will use the scale on every 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)
so the interpolator returns unequal intervals (shorter at the ends).
Edit: not using d3, it is 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 e with d3 scales, since it would bee much more readable.
I want to construct a scale that maps a range of successive integers (indexes of characters in a string) to regular intervals in another range of integers (pixels, say 0-600). That is, I would like to assign characters to pixels and conversely as regularly as possible, the length of one not being necessarily a multiple of the other.
For instance, mapping [0,1,2,3] to 400 pixels, I would expect
0 -> 0-99
1 -> 100-199
2 -> 200-299
3 -> 300-399
and conversely
0-99 -> 0
100-199 -> 1
200-299 -> 2
300-399 -> 3
while for mapping 0-4000 to 400 pixels, I would expect
0-9 -> 0
10-19 -> 1
etc.
What is the best scale to use for this in d3 ?
On one hand I am afraid that discrete scales will not use the fact that the domain is equally separated and generate a huge switch statement if the number of elements is big. Since I will use the scale on every 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)
so the interpolator returns unequal intervals (shorter at the ends).
Edit: not using d3, it is 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 e with d3 scales, since it would bee much more readable.
Share Improve this question edited Aug 11, 2016 at 6:24 JulienD asked Aug 10, 2016 at 8:26 JulienDJulienD 7,29310 gold badges56 silver badges86 bronze badges 2- I am a little unclear about what your input and output will be. In the "while for mapping 0-4000 to 400 pixels" part of the question, you have gone from mapping an interval to an integer, as opposed to mapping the an integer to an interval as in the first example. – Steve Clanton Commented Aug 10, 2016 at 15:05
- I meant that if I have to distribute 4000 objects among 400 boxes, I expect 10 of them in each box. Or imagine you want to represent characters in a 4000 chars long string with different colors, and draw them in a canvas that is 400x400 px. Since you choose the input as integers, you just need to round the output of the scale, this is not a problem. – JulienD Commented Aug 10, 2016 at 18:54
1 Answer
Reset to default 5The d3 Quantize Scale is the best option if you want to map onto an interval. The scale maps between discrete values and a continuous interval, though. I am not 100% clear on what you want to do, but let's look at how I could do a few of the things you mention with the quantize scale.
Mapping integers to intervals is straightforward, as long as you know that d3 uses half-open intervals [,) to break up the 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 could also enumerate the discrete values:
var interval = s.invertExtent(0);
d3.range(interval[0], interval[1]); // [0, 1, ... , 399]
These are nice values you've given though, and since you want to map integers to intervals of integers, we will need rounding when numbers aren't divisible. We can just use Math.round though.
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 the integer, but the scale maps a point in an 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 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 output looks like a scale to us with a bar graph with 50% padding (then each position would be the midpoint of an interval that is 132 pixels). I am going to guess the cause is that rangeRound uses round to interpolate, rather than floor.
You could use a function designed for bar graphs also, 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 any of this makes the code simpler.
Once I get to the functions you implement, it seems like the requirements are much simpler. There aren't any intervals involved really. The problem is that there isn't a one-to-one mapping. The best solution is either what you have done or to just use two linear scales with a custom interpolator (to find the floor, rather than rounding.
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);
本文标签: javascriptBest d3 scale for mapping integers rangeStack Overflow
版权声明:本文标题:javascript - Best d3 scale for mapping integers range - Stack Overflow 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://it.en369.cn/questions/1745636322a2160469.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论