Skip to content

Commit

Permalink
constant numeric x and y for text marks position the text relative to…
Browse files Browse the repository at this point in the history
… the anchor

anchor can be any of "top", "bottom", "left", "right" or combinations such as "top-left"

closes #523
  • Loading branch information
Fil committed Dec 17, 2021
1 parent ebe9187 commit 95eba04
Show file tree
Hide file tree
Showing 2 changed files with 30 additions and 12 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1030,6 +1030,9 @@ The following text-specific constant options are also supported:
* **fontVariant** - the [font variant](https://developer.mozilla.org/en-US/docs/Web/CSS/font-variant); defaults to normal
* **fontWeight** - the [font weight](https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight); defaults to normal
* **rotate** - the rotation in degrees clockwise; defaults to 0
* **anchor** - the anchor point—if the text is not bound to a scale; any of: top, bottom, left, right and combinations thereof such as top-left; defaults to the center of the chart
* **x** - number of pixels to the right relative to the anchor point
* **y** - number of pixels to the bottom relative to the anchor point

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.

Expand Down
39 changes: 27 additions & 12 deletions src/marks/text.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,20 @@ export class Text extends Mark {
fontStyle,
fontVariant,
fontWeight,
anchor,
dx,
dy = "0.32em",
rotate
} = options;
const [vrotate, crotate] = maybeNumber(rotate, 0);
const [vfontSize, cfontSize] = maybeNumber(fontSize);
const [vx, cx] = maybeNumber(x, 0);
const [vy, cy] = maybeNumber(y, 0);
super(
data,
[
{name: "x", value: x, scale: "x", optional: true},
{name: "y", value: y, scale: "y", optional: true},
{name: "x", value: vx, scale: "x", optional: true},
{name: "y", value: vy, scale: "y", optional: true},
{name: "fontSize", value: numberChannel(vfontSize), optional: true},
{name: "rotate", value: numberChannel(vrotate), optional: true},
{name: "text", value: text}
Expand All @@ -44,16 +47,17 @@ export class Text extends Mark {
this.fontStyle = string(fontStyle);
this.fontVariant = string(fontVariant);
this.fontWeight = string(fontWeight);
this.cx = cx;
this.cy = cy;
this.anchor = anchor;
this.dx = string(dx);
this.dy = string(dy);
}
render(I, {x, y}, channels, dimensions) {
const {x: X, y: Y, rotate: R, text: T, fontSize: FS} = channels;
const {width, height, marginTop, marginRight, marginBottom, marginLeft} = dimensions;
const {rotate} = this;
const index = filter(I, X, Y, R).filter(i => nonempty(T[i]));
const cx = (marginLeft + width - marginRight) / 2;
const cy = (marginTop + height - marginBottom) / 2;
const c = textPosition(dimensions, this.anchor, this.cx, this.cy);
return create("svg:g")
.call(applyIndirectTextStyles, this)
.call(applyTransform, x, y, offset, offset)
Expand All @@ -62,14 +66,14 @@ export class Text extends Mark {
.join("text")
.call(applyDirectTextStyles, this)
.call(R ? text => text.attr("transform", X && Y ? i => `translate(${X[i]},${Y[i]}) rotate(${R[i]})`
: X ? i => `translate(${X[i]},${cy}) rotate(${R[i]})`
: Y ? i => `translate(${cx},${Y[i]}) rotate(${R[i]})`
: i => `translate(${cx},${cy}) rotate(${R[i]})`)
: X ? i => `translate(${X[i]},${c[1]}) rotate(${R[i]})`
: Y ? i => `translate(${c[0]},${Y[i]}) rotate(${R[i]})`
: i => `translate(${c[0]},${c[1]}) rotate(${R[i]})`)
: rotate ? text => text.attr("transform", X && Y ? i => `translate(${X[i]},${Y[i]}) rotate(${rotate})`
: X ? i => `translate(${X[i]},${cy}) rotate(${rotate})`
: Y ? i => `translate(${cx},${Y[i]}) rotate(${rotate})`
: `translate(${cx},${cy}) rotate(${rotate})`)
: text => text.attr("x", X ? i => X[i] : cx).attr("y", Y ? i => Y[i] : cy))
: X ? i => `translate(${X[i]},${c[1]}) rotate(${rotate})`
: Y ? i => `translate(${c[0]},${Y[i]}) rotate(${rotate})`
: `translate(${c[0]},${c[1]}) rotate(${rotate})`)
: text => text.attr("x", X ? i => X[i] : c[0]).attr("y", Y ? i => Y[i] : c[1]))
.call(applyAttr, "font-size", FS && (i => FS[i]))
.text(i => T[i])
.call(applyChannelStyles, channels))
Expand Down Expand Up @@ -105,3 +109,14 @@ function applyDirectTextStyles(selection, mark) {
applyAttr(selection, "dx", mark.dx);
applyAttr(selection, "dy", mark.dy);
}

function textPosition({width, height, marginTop, marginRight, marginBottom, marginLeft}, anchor = "", cx = 0, cy = 0) {
const a = anchor.toLowerCase();
const v = a.match(/^(top|bottom|)/)[0];
const h = a.match(/(left|right|)$/)[0];
if (a != ((h && v) ? `${v}-${h}` : h ? h : v))
throw new Error(`Unexpected anchor: ${anchor}`);
const x = h === "left" ? marginLeft : h === "right" ? width - marginRight : (marginLeft + width - marginRight) / 2;
const y = v === "top" ? marginTop : v === "bottom" ? height - marginBottom : (marginTop + height - marginBottom) / 2;
return [x + cx, y + cy];
}

0 comments on commit 95eba04

Please sign in to comment.