Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Round non zero #243

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
16 changes: 14 additions & 2 deletions README.md
Expand Up @@ -902,7 +902,13 @@ Rounding is sometimes useful for avoiding antialiasing artifacts, though also co

<a name="band_round" href="#band_round">#</a> <i>band</i>.<b>round</b>([<i>round</i>]) · [Source](https://github.com/d3/d3-scale/blob/master/src/band.js), [Examples](https://observablehq.com/@d3/d3-scaleband)

If *round* is specified, enables or disables rounding accordingly. If rounding is enabled, the start and stop of each band will be integers. Rounding is sometimes useful for avoiding antialiasing artifacts, though also consider the [shape-rendering](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/shape-rendering) “crispEdges” styles. Note that if the width of the domain is not a multiple of the cardinality of the range, there may be leftover unused space, even without padding! Use [*band*.align](#band_align) to specify how the leftover space is distributed.
If *round* is specified, enables or disables rounding accordingly. If rounding is enabled, the start and stop of each band will be multiples of the rounding precision. Rounding is sometimes useful for avoiding antialiasing artifacts, though also consider the [shape-rendering](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/shape-rendering) “crispEdges” styles. Note that if the width of the domain is not a multiple of the cardinality of the range, there may be leftover unused space, even without padding! Use [*band*.align](#band_align) to specify how the leftover space is distributed.

To avoid collapsing the scale, rounding is not applied when the range is smaller than the length of the domain times the rounding precision.

<a name="band_roundingPrecision" href="#band_roundingPrecision">#</a> <i>band</i>.<b>roundingPrecision</b>([<i>precision</i>]) · [Source](https://github.com/d3/d3-scale/blob/master/src/band.js)<!-- , [Examples](https://observablehq.com/@d3/d3-scaleband) -->

Sets or reads the rounding precision. A precision of 0.5 is convenient to avoid aliasing artifacts on screens with a pixel density of 2.

<a name="band_paddingInner" href="#band_paddingInner">#</a> <i>band</i>.<b>paddingInner</b>([<i>padding</i>]) · [Source](https://github.com/d3/d3-scale/blob/master/src/band.js), [Examples](https://observablehq.com/@d3/d3-scaleband)

Expand Down Expand Up @@ -968,7 +974,13 @@ Rounding is sometimes useful for avoiding antialiasing artifacts, though also co

<a name="point_round" href="#point_round">#</a> <i>point</i>.<b>round</b>([<i>round</i>]) · [Source](https://github.com/d3/d3-scale/blob/master/src/band.js), [Examples](https://observablehq.com/@d3/d3-scalepoint)

If *round* is specified, enables or disables rounding accordingly. If rounding is enabled, the position of each point will be integers. Rounding is sometimes useful for avoiding antialiasing artifacts, though also consider the [shape-rendering](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/shape-rendering) “crispEdges” styles. Note that if the width of the domain is not a multiple of the cardinality of the range, there may be leftover unused space, even without padding! Use [*point*.align](#point_align) to specify how the leftover space is distributed.
If *round* is specified, enables or disables rounding accordingly. If rounding is enabled, the position of each point will be multiples of the rounding precision. Rounding is sometimes useful for avoiding antialiasing artifacts, though also consider the [shape-rendering](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/shape-rendering) “crispEdges” styles. Note that if the width of the domain is not a multiple of the cardinality of the range, there may be leftover unused space, even without padding! Use [*point*.align](#point_align) to specify how the leftover space is distributed.

To avoid collapsing the scale, rounding is not applied when the range is smaller than the length of the domain times the rounding precision.

<a name="point_roundingPrecision" href="#point_roundingPrecision">#</a> <i>point</i>.<b>roundingPrecision</b>([<i>precision</i>]) · [Source](https://github.com/d3/d3-scale/blob/master/src/band.js)<!-- , [Examples](https://observablehq.com/@d3/d3-scalepoint) -->

Sets or reads the rounding precision. A precision of 0.5 is convenient to avoid aliasing artifacts on screens with a pixel density of 2.

<a name="point_padding" href="#point_padding">#</a> <i>point</i>.<b>padding</b>([<i>padding</i>]) · [Source](https://github.com/d3/d3-scale/blob/master/src/band.js), [Examples](https://observablehq.com/@d3/d3-scalepoint)

Expand Down
11 changes: 9 additions & 2 deletions src/band.js
Expand Up @@ -11,6 +11,7 @@ export default function band() {
step,
bandwidth,
round = false,
roundingPrecision = 1,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we employ the same test as d3-axis so that roundingPrecision can default to 0.5 if devicePixelRatio > 1?

https://github.com/d3/d3-axis/blob/4fc4f2bd287de20c4fc5f1da447ae1194d3acca6/src/axis.js#L39

Also, I think internally perhaps we should store the inverse rounding precision as an integer (e.g., precision = 2 if we want to round to the nearest half-pixel), so as to reduce the likelihood of floating point error.

Also, how about the shorter name scale.precision. Is that too ambiguous?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree with using devicePixelRatio and integer representation. For the name, I think precision presents the risk of being ambiguous (though we have no use for a scale's precision yet).

paddingInner = 0,
paddingOuter = 0,
align = 0.5;
Expand All @@ -23,10 +24,12 @@ export default function band() {
start = reverse ? r1 : r0,
stop = reverse ? r0 : r1;
step = (stop - start) / Math.max(1, n - paddingInner + paddingOuter * 2);
if (round) step = Math.floor(step);
var r = round && step >= roundingPrecision;
if (r) step = roundingPrecision * Math.floor(step / roundingPrecision);
start += (stop - start - step * (n - paddingInner)) * align;
bandwidth = step * (1 - paddingInner);
if (round) start = Math.round(start), bandwidth = Math.round(bandwidth);
if (round) start = roundingPrecision * Math.round(start / roundingPrecision);
if (r) bandwidth = roundingPrecision * Math.round(bandwidth / roundingPrecision);
var values = sequence(n).map(function(i) { return start + step * i; });
return ordinalRange(reverse ? values.reverse() : values);
}
Expand Down Expand Up @@ -55,6 +58,10 @@ export default function band() {
return arguments.length ? (round = !!_, rescale()) : round;
};

scale.roundingPrecision = function(_) {
return arguments.length ? (roundingPrecision = +_, rescale()) : roundingPrecision;
};

scale.padding = function(_) {
return arguments.length ? (paddingInner = Math.min(1, paddingOuter = +_), rescale()) : paddingInner;
};
Expand Down
25 changes: 25 additions & 0 deletions test/band-test.js
Expand Up @@ -181,6 +181,31 @@ tape("band.rangeRound(values) coerces values to numbers", function(test) {
test.end();
});

tape("band.round is ignored when the range is too small and the scale would collapse", function(test) {
var domain = ["A", "B", "C", "D", "E", "F"];
var s1 = scale.scaleBand().domain(domain).rangeRound([0, 3]);
test.deepEqual(domain.map(s1), [ 0, 0.5, 1, 1.5, 2, 2.5 ]);
test.equal(s1.bandwidth(), 0.5);
var s2 = scale.scaleBand().domain(domain).rangeRound([0, 30]);
test.deepEqual(domain.map(s2), [ 0, 5, 10, 15, 20, 25 ]);
var s3 = scale.scaleBand().domain(domain).rangeRound([0, 10]);
test.deepEqual(domain.map(s3), [ 2, 3, 4, 5, 6, 7 ]);
var s4 = scale.scaleBand().domain(domain).range([0, 3]).round(true);
test.deepEqual(domain.map(s4), [ 0, 0.5, 1, 1.5, 2, 2.5 ]);
var s5 = scale.scaleBand().domain(domain).range([0, 1.5]).round(true);
test.deepEqual(domain.map(s5), [ 0, 0.25, 0.5, 0.75, 1, 1.25 ]);
test.equal(s5.bandwidth(), 0.25);
test.end();
});

tape("band.roundingPrecision adjusts the rounding precision", function(test) {
var domain = ["A", "B", "C", "D", "E", "F"];
var s1 = scale.scaleBand().domain(domain).rangeRound([0, 5]).roundingPrecision(0.5);
test.deepEqual(domain.map(s1), [ 1, 1.5, 2, 2.5, 3, 3.5 ]);
test.equal(s1.bandwidth(), 0.5);
test.end();
});

tape("band.paddingInner(p) specifies the inner padding p", function(test) {
var s = scale.scaleBand().domain(["a", "b", "c"]).range([120, 0]).paddingInner(0.1).round(true);
test.deepEqual(s.domain().map(s), [83, 42, 1]);
Expand Down