Skip to content

Commit 07cf90b

Browse files
Filmbostock
andauthored
support dx, dy on all marks (#488)
* fix #379; universal dx and dy On all marks except text, dx and dy are rendered as a transform (translate) property, possibly with the 0.5px offset on high-density screens. On text marks, the dx and dy properties are used. * update README Co-authored-by: Mike Bostock <[email protected]>
1 parent 151d4aa commit 07cf90b

File tree

13 files changed

+43
-36
lines changed

13 files changed

+43
-36
lines changed

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,10 @@ All marks support the following style options:
511511
* **strokeDasharray** - a comma-separated list of dash lengths (in pixels)
512512
* **mixBlendMode** - the [blend mode](https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode) (*e.g.*, *multiply*)
513513
* **shapeRendering** - the [shape-rendering mode](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/shape-rendering) (*e.g.*, *crispEdges*)
514+
* **dx** - horizontal offset (in pixels; defaults to 0)
515+
* **dy** - vertical offset (in pixels; defaults to 0)
516+
517+
For all marks except [text](#plottextdata-options), the **dx** and **dy** options are rendered as a transform property, possibly including a 0.5px offset on high-density screens.
514518

515519
All marks support the following optional channels:
516520

@@ -891,11 +895,9 @@ The following text-specific constant options are also supported:
891895
* **fontStyle** - the [font style](https://developer.mozilla.org/en-US/docs/Web/CSS/font-style); defaults to normal
892896
* **fontVariant** - the [font variant](https://developer.mozilla.org/en-US/docs/Web/CSS/font-variant); defaults to normal
893897
* **fontWeight** - the [font weight](https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight); defaults to normal
894-
* **dx** - the horizontal offset; defaults to 0
895-
* **dy** - the vertical offset; defaults to 0
896898
* **rotate** - the rotation in degrees clockwise; defaults to 0
897899

898-
The **dx** and **dy** options can be specified either as numbers representing pixels or as a string including units. For example, `"1em"` shifts the text by one [em](https://en.wikipedia.org/wiki/Em_(typography)), which is proportional to the **fontSize**. The **fontSize** and **rotate** options can be specified as either channels or constants. When fontSize or rotate is specified as a number, it is interpreted as a constant; otherwise it is interpreted as a channel.
900+
For text marks, the **dx** and **dy** options can be specified either as numbers representing pixels or as a string including units. For example, `"1em"` shifts the text by one [em](https://en.wikipedia.org/wiki/Em_(typography)), which is proportional to the **fontSize**. The **fontSize** and **rotate** options can be specified as either channels or constants. When fontSize or rotate is specified as a number, it is interpreted as a constant; otherwise it is interpreted as a channel.
899901

900902
#### Plot.text(*data*, *options*)
901903

src/mark.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ const objectToString = Object.prototype.toString;
1313

1414
export class Mark {
1515
constructor(data, channels = [], options = {}, defaults) {
16-
const {facet = "auto", sort} = options;
16+
const {facet = "auto", sort, dx, dy} = options;
1717
const names = new Set();
1818
this.data = data;
1919
this.sort = isOptions(sort) ? sort : null;
@@ -35,6 +35,8 @@ export class Mark {
3535
}
3636
return true;
3737
});
38+
this.dx = +dx || 0;
39+
this.dy = +dy || 0;
3840
}
3941
initialize(facets, facetChannels) {
4042
let data = arrayify(this.data);

src/marks/area.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,10 @@ export class Area extends Mark {
2929
}
3030
render(I, {x, y}, channels) {
3131
const {x1: X1, y1: Y1, x2: X2 = X1, y2: Y2 = Y1, z: Z} = channels;
32+
const {dx, dy} = this;
3233
return create("svg:g")
3334
.call(applyIndirectStyles, this)
34-
.call(applyTransform, x, y)
35+
.call(applyTransform, x, y, dx, dy)
3536
.call(g => g.selectAll()
3637
.data(Z ? group(I, i => Z[i]).values() : [I])
3738
.join("path")

src/marks/bar.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,11 @@ export class AbstractBar extends Mark {
1919
this.ry = impliedString(ry, "auto");
2020
}
2121
render(I, scales, channels, dimensions) {
22-
const {rx, ry} = this;
22+
const {dx, dy, rx, ry} = this;
2323
const index = filter(I, ...this._positions(channels));
2424
return create("svg:g")
2525
.call(applyIndirectStyles, this)
26-
.call(this._transform, scales)
26+
.call(this._transform, scales, dx, dy)
2727
.call(g => g.selectAll()
2828
.data(index)
2929
.join("rect")
@@ -70,8 +70,8 @@ export class BarX extends AbstractBar {
7070
options
7171
);
7272
}
73-
_transform(selection, {x}) {
74-
selection.call(applyTransform, x, null);
73+
_transform(selection, {x}, dx, dy) {
74+
selection.call(applyTransform, x, null, dx, dy);
7575
}
7676
_positions({x1: X1, x2: X2, y: Y}) {
7777
return [X1, X2, Y];
@@ -99,8 +99,8 @@ export class BarY extends AbstractBar {
9999
options
100100
);
101101
}
102-
_transform(selection, {y}) {
103-
selection.call(applyTransform, null, y);
102+
_transform(selection, {y}, dx, dy) {
103+
selection.call(applyTransform, null, y, dx, dy);
104104
}
105105
_positions({y1: Y1, y2: Y2, x: X}) {
106106
return [Y1, Y2, X];

src/marks/dot.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {create} from "d3";
22
import {filter, positive} from "../defined.js";
33
import {Mark, identity, maybeNumber, maybeTuple} from "../mark.js";
4-
import {applyChannelStyles, applyDirectStyles, applyIndirectStyles, applyTransform} from "../style.js";
4+
import {applyChannelStyles, applyDirectStyles, applyIndirectStyles, applyTransform, offset} from "../style.js";
55

66
const defaults = {
77
fill: "none",
@@ -32,11 +32,12 @@ export class Dot extends Mark {
3232
{width, height, marginTop, marginRight, marginBottom, marginLeft}
3333
) {
3434
const {x: X, y: Y, r: R} = channels;
35+
const {dx, dy} = this;
3536
let index = filter(I, X, Y);
3637
if (R) index = index.filter(i => positive(R[i]));
3738
return create("svg:g")
3839
.call(applyIndirectStyles, this)
39-
.call(applyTransform, x, y, 0.5, 0.5)
40+
.call(applyTransform, x, y, offset + dx, offset + dy)
4041
.call(g => g.selectAll()
4142
.data(index)
4243
.join("circle")

src/marks/frame.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {create} from "d3";
22
import {Mark, number} from "../mark.js";
3-
import {applyDirectStyles, applyIndirectStyles, applyTransform} from "../style.js";
3+
import {applyDirectStyles, applyIndirectStyles, applyTransform, offset} from "../style.js";
44

55
const defaults = {
66
fill: "none",
@@ -24,11 +24,11 @@ export class Frame extends Mark {
2424
}
2525
render(I, scales, channels, dimensions) {
2626
const {marginTop, marginRight, marginBottom, marginLeft, width, height} = dimensions;
27-
const {insetTop, insetRight, insetBottom, insetLeft} = this;
27+
const {insetTop, insetRight, insetBottom, insetLeft, dx, dy} = this;
2828
return create("svg:rect")
2929
.call(applyIndirectStyles, this)
3030
.call(applyDirectStyles, this)
31-
.call(applyTransform, null, null, 0.5, 0.5)
31+
.call(applyTransform, null, null, offset + dx, offset + dy)
3232
.attr("x", marginLeft + insetLeft)
3333
.attr("y", marginTop + insetTop)
3434
.attr("width", width - marginLeft - marginRight - insetLeft - insetRight)

src/marks/line.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import {create, group, line as shapeLine} from "d3";
22
import {Curve} from "../curve.js";
33
import {defined} from "../defined.js";
44
import {Mark, indexOf, identity, maybeTuple, maybeZ} from "../mark.js";
5-
import {applyDirectStyles, applyIndirectStyles, applyTransform, applyGroupedChannelStyles} from "../style.js";
5+
import {applyDirectStyles, applyIndirectStyles, applyTransform, applyGroupedChannelStyles, offset} from "../style.js";
66

77
const defaults = {
88
fill: "none",
@@ -28,9 +28,10 @@ export class Line extends Mark {
2828
}
2929
render(I, {x, y}, channels) {
3030
const {x: X, y: Y, z: Z} = channels;
31+
const {dx, dy} = this;
3132
return create("svg:g")
3233
.call(applyIndirectStyles, this)
33-
.call(applyTransform, x, y, 0.5, 0.5)
34+
.call(applyTransform, x, y, offset + dx, offset + dy)
3435
.call(g => g.selectAll()
3536
.data(Z ? group(I, i => Z[i]).values() : [I])
3637
.join("path")

src/marks/link.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import {create, path} from "d3";
22
import {filter} from "../defined.js";
33
import {Mark} from "../mark.js";
44
import {Curve} from "../curve.js";
5-
import {applyChannelStyles, applyDirectStyles, applyIndirectStyles, applyTransform} from "../style.js";
5+
import {applyChannelStyles, applyDirectStyles, applyIndirectStyles, applyTransform, offset} from "../style.js";
66

77
const defaults = {
88
fill: "none",
@@ -28,10 +28,11 @@ export class Link extends Mark {
2828
}
2929
render(I, {x, y}, channels) {
3030
const {x1: X1, y1: Y1, x2: X2 = X1, y2: Y2 = Y1} = channels;
31+
const {dx, dy} = this;
3132
const index = filter(I, X1, Y1, X2, Y2);
3233
return create("svg:g")
3334
.call(applyIndirectStyles, this)
34-
.call(applyTransform, x, y, 0.5, 0.5)
35+
.call(applyTransform, x, y, offset + dx, offset + dy)
3536
.call(g => g.selectAll()
3637
.data(index)
3738
.join("path")

src/marks/rect.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,11 @@ export class Rect extends Mark {
4343
render(I, {x, y}, channels, dimensions) {
4444
const {x1: X1, y1: Y1, x2: X2, y2: Y2} = channels;
4545
const {marginTop, marginRight, marginBottom, marginLeft, width, height} = dimensions;
46-
const {insetTop, insetRight, insetBottom, insetLeft, rx, ry} = this;
46+
const {insetTop, insetRight, insetBottom, insetLeft, dx, dy, rx, ry} = this;
4747
const index = filter(I, X1, Y2, X2, Y2);
4848
return create("svg:g")
4949
.call(applyIndirectStyles, this)
50-
.call(applyTransform, x, y)
50+
.call(applyTransform, x, y, dx, dy)
5151
.call(g => g.selectAll()
5252
.data(index)
5353
.join("rect")

src/marks/rule.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import {create} from "d3";
22
import {filter} from "../defined.js";
33
import {Mark, identity, number} from "../mark.js";
44
import {isCollapsed} from "../scales.js";
5-
import {applyDirectStyles, applyIndirectStyles, applyTransform, applyChannelStyles} from "../style.js";
5+
import {applyDirectStyles, applyIndirectStyles, applyTransform, applyChannelStyles, offset} from "../style.js";
66

77
const defaults = {
88
fill: null,
@@ -39,7 +39,7 @@ export class RuleX extends Mark {
3939
const index = filter(I, X, Y1, Y2);
4040
return create("svg:g")
4141
.call(applyIndirectStyles, this)
42-
.call(applyTransform, X && x, null, 0.5, 0)
42+
.call(applyTransform, X && x, null, offset, 0)
4343
.call(g => g.selectAll("line")
4444
.data(index)
4545
.join("line")
@@ -79,11 +79,11 @@ export class RuleY extends Mark {
7979
render(I, {x, y}, channels, dimensions) {
8080
const {y: Y, x1: X1, x2: X2} = channels;
8181
const {width, height, marginTop, marginRight, marginLeft, marginBottom} = dimensions;
82-
const {insetLeft, insetRight} = this;
82+
const {insetLeft, insetRight, dx, dy} = this;
8383
const index = filter(I, Y, X1, X2);
8484
return create("svg:g")
8585
.call(applyIndirectStyles, this)
86-
.call(applyTransform, null, Y && y, 0, 0.5)
86+
.call(applyTransform, null, Y && y, dx, offset + dy)
8787
.call(g => g.selectAll("line")
8888
.data(index)
8989
.join("line")

src/marks/text.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {create} from "d3";
22
import {filter, nonempty} from "../defined.js";
33
import {Mark, indexOf, identity, string, maybeNumber, maybeTuple, numberChannel} from "../mark.js";
4-
import {applyChannelStyles, applyDirectStyles, applyIndirectStyles, applyAttr, applyTransform} from "../style.js";
4+
import {applyChannelStyles, applyDirectStyles, applyIndirectStyles, applyAttr, applyTransform, offset} from "../style.js";
55

66
const defaults = {};
77

@@ -54,7 +54,7 @@ export class Text extends Mark {
5454
const cy = (marginTop + height - marginBottom) / 2;
5555
return create("svg:g")
5656
.call(applyIndirectTextStyles, this)
57-
.call(applyTransform, x, y, 0.5, 0.5)
57+
.call(applyTransform, x, y, offset, offset)
5858
.call(g => g.selectAll()
5959
.data(index)
6060
.join("text")

src/marks/tick.js

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {create} from "d3";
22
import {filter} from "../defined.js";
33
import {Mark, identity, number} from "../mark.js";
4-
import {applyDirectStyles, applyIndirectStyles, applyTransform, applyChannelStyles} from "../style.js";
4+
import {applyDirectStyles, applyIndirectStyles, applyTransform, applyChannelStyles, offset} from "../style.js";
55

66
const defaults = {
77
fill: null,
@@ -14,10 +14,11 @@ class AbstractTick extends Mark {
1414
}
1515
render(I, scales, channels, dimensions) {
1616
const {x: X, y: Y} = channels;
17+
const {dx, dy} = this;
1718
const index = filter(I, X, Y);
1819
return create("svg:g")
1920
.call(applyIndirectStyles, this)
20-
.call(this._transform, scales)
21+
.call(this._transform, scales, dx, dy)
2122
.call(g => g.selectAll("line")
2223
.data(index)
2324
.join("line")
@@ -51,8 +52,8 @@ export class TickX extends AbstractTick {
5152
this.insetTop = number(insetTop);
5253
this.insetBottom = number(insetBottom);
5354
}
54-
_transform(selection, {x}) {
55-
selection.call(applyTransform, x, null, 0.5, 0);
55+
_transform(selection, {x}, dx, dy) {
56+
selection.call(applyTransform, x, null, offset + dx, dy);
5657
}
5758
_x1(scales, {x: X}) {
5859
return i => X[i];
@@ -90,8 +91,8 @@ export class TickY extends AbstractTick {
9091
this.insetRight = number(insetRight);
9192
this.insetLeft = number(insetLeft);
9293
}
93-
_transform(selection, {y}) {
94-
selection.call(applyTransform, null, y, 0, 0.5);
94+
_transform(selection, {y}, dx, dy) {
95+
selection.call(applyTransform, null, y, dx, offset + dy);
9596
}
9697
_x1(scales, {x: X}, {marginLeft}) {
9798
const {insetLeft} = this;

src/style.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,6 @@ export function applyStyle(selection, name, value) {
131131
}
132132

133133
export function applyTransform(selection, x, y, tx, ty) {
134-
tx = tx ? offset : 0;
135-
ty = ty ? offset : 0;
136134
if (x && x.bandwidth) tx += x.bandwidth() / 2;
137135
if (y && y.bandwidth) ty += y.bandwidth() / 2;
138136
if (tx || ty) selection.attr("transform", `translate(${tx},${ty})`);

0 commit comments

Comments
 (0)