Skip to content

Commit 98d3b54

Browse files
committed
clip: true clips the mark to the frame
closes #165 related: #181
1 parent 776d3ed commit 98d3b54

File tree

20 files changed

+7352
-22
lines changed

20 files changed

+7352
-22
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -646,6 +646,7 @@ All marks support the following optional channels:
646646
* **title** - a tooltip (a string of text, possibly with newlines)
647647
* **href** - a URL to link to
648648
* **ariaLabel** - a short label representing the value in the accessibility tree
649+
* **clip** - if true, the mark is clipped to the frame’s dimensions
649650

650651
The **fill**, **fillOpacity**, **stroke**, **strokeWidth**, **strokeOpacity**, and **opacity** options can be specified as either channels or constants. When the fill or stroke is specified as a function or array, it is interpreted as a channel; when the fill or stroke is specified as a string, it is interpreted as a constant if a valid CSS color and otherwise it is interpreted as a column name for a channel. Similarly when the fill opacity, stroke opacity, object opacity, stroke width, or radius is specified as a number, it is interpreted as a constant; otherwise it is interpreted as a channel.
651652

src/marks/area.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,11 @@ export class Area extends Mark {
3030
);
3131
this.curve = Curve(curve, tension);
3232
}
33-
render(I, {x, y}, channels) {
33+
render(I, {x, y}, channels, dimensions) {
3434
const {x1: X1, y1: Y1, x2: X2 = X1, y2: Y2 = Y1, z: Z} = channels;
3535
const {dx, dy} = this;
3636
return create("svg:g")
37-
.call(applyIndirectStyles, this)
37+
.call(applyIndirectStyles, this, dimensions)
3838
.call(applyTransform, x, y, dx, dy)
3939
.call(g => g.selectAll()
4040
.data(Z ? group(I, i => Z[i]).values() : [I])

src/marks/arrow.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export class Arrow extends Mark {
4444
this.insetStart = +insetStart;
4545
this.insetEnd = +insetEnd;
4646
}
47-
render(index, {x, y}, channels) {
47+
render(index, {x, y}, channels, dimensions) {
4848
const {x1: X1, y1: Y1, x2: X2 = X1, y2: Y2 = Y1, SW} = channels;
4949
const {dx, dy, strokeWidth, bend, headAngle, headLength, insetStart, insetEnd} = this;
5050
const sw = SW ? i => SW[i] : () => strokeWidth;
@@ -65,7 +65,7 @@ export class Arrow extends Mark {
6565
const wingScale = headLength / 1.5;
6666

6767
return create("svg:g")
68-
.call(applyIndirectStyles, this)
68+
.call(applyIndirectStyles, this, dimensions)
6969
.call(applyTransform, x, y, offset + dx, offset + dy)
7070
.call(g => g.selectAll()
7171
.data(index)

src/marks/bar.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export class AbstractBar extends Mark {
2121
render(index, scales, channels, dimensions) {
2222
const {dx, dy, rx, ry} = this;
2323
return create("svg:g")
24-
.call(applyIndirectStyles, this)
24+
.call(applyIndirectStyles, this, dimensions)
2525
.call(this._transform, scales, dx, dy)
2626
.call(g => g.selectAll()
2727
.data(index)

src/marks/dot.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export class Dot extends Mark {
5454
const [cx, cy] = applyFrameAnchor(this, dimensions);
5555
const circle = this.symbol === symbolCircle;
5656
return create("svg:g")
57-
.call(applyIndirectStyles, this)
57+
.call(applyIndirectStyles, this, dimensions)
5858
.call(applyTransform, x, y, offset + dx, offset + dy)
5959
.call(g => g.selectAll()
6060
.data(index)

src/marks/frame.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export class Frame extends Mark {
2828
const {marginTop, marginRight, marginBottom, marginLeft, width, height} = dimensions;
2929
const {insetTop, insetRight, insetBottom, insetLeft, dx, dy} = this;
3030
return create("svg:rect")
31-
.call(applyIndirectStyles, this)
31+
.call(applyIndirectStyles, this, dimensions)
3232
.call(applyDirectStyles, this)
3333
.call(applyTransform, null, null, offset + dx, offset + dy)
3434
.attr("x", marginLeft + insetLeft)

src/marks/image.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ export class Image extends Mark {
6464
const {dx, dy} = this;
6565
const [cx, cy] = applyFrameAnchor(this, dimensions);
6666
return create("svg:g")
67-
.call(applyIndirectStyles, this)
67+
.call(applyIndirectStyles, this, dimensions)
6868
.call(applyTransform, x, y, offset + dx, offset + dy)
6969
.call(g => g.selectAll()
7070
.data(index)

src/marks/line.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,11 @@ export class Line extends Mark {
3030
this.curve = Curve(curve, tension);
3131
markers(this, options);
3232
}
33-
render(I, {x, y}, channels) {
33+
render(I, {x, y}, channels, dimensions) {
3434
const {x: X, y: Y, z: Z} = channels;
3535
const {dx, dy} = this;
3636
return create("svg:g")
37-
.call(applyIndirectStyles, this)
37+
.call(applyIndirectStyles, this, dimensions)
3838
.call(applyTransform, x, y, offset + dx, offset + dy)
3939
.call(g => g.selectAll()
4040
.data(Z ? group(I, i => Z[i]).values() : [I])

src/marks/link.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,11 @@ export class Link extends Mark {
2828
this.curve = Curve(curve, tension);
2929
markers(this, options);
3030
}
31-
render(index, {x, y}, channels) {
31+
render(index, {x, y}, channels, dimensions) {
3232
const {x1: X1, y1: Y1, x2: X2 = X1, y2: Y2 = Y1} = channels;
3333
const {dx, dy, curve} = this;
3434
return create("svg:g")
35-
.call(applyIndirectStyles, this)
35+
.call(applyIndirectStyles, this, dimensions)
3636
.call(applyTransform, x, y, offset + dx, offset + dy)
3737
.call(g => g.selectAll()
3838
.data(index)

src/marks/rect.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export class Rect extends Mark {
4949
const {marginTop, marginRight, marginBottom, marginLeft, width, height} = dimensions;
5050
const {insetTop, insetRight, insetBottom, insetLeft, dx, dy, rx, ry} = this;
5151
return create("svg:g")
52-
.call(applyIndirectStyles, this)
52+
.call(applyIndirectStyles, this, dimensions)
5353
.call(applyTransform, x, y, dx, dy)
5454
.call(g => g.selectAll()
5555
.data(index)

src/marks/rule.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export class RuleX extends Mark {
3939
const {width, height, marginTop, marginRight, marginLeft, marginBottom} = dimensions;
4040
const {insetTop, insetBottom} = this;
4141
return create("svg:g")
42-
.call(applyIndirectStyles, this)
42+
.call(applyIndirectStyles, this, dimensions)
4343
.call(applyTransform, X && x, null, offset, 0)
4444
.call(g => g.selectAll("line")
4545
.data(index)
@@ -82,7 +82,7 @@ export class RuleY extends Mark {
8282
const {width, height, marginTop, marginRight, marginLeft, marginBottom} = dimensions;
8383
const {insetLeft, insetRight, dx, dy} = this;
8484
return create("svg:g")
85-
.call(applyIndirectStyles, this)
85+
.call(applyIndirectStyles, this, dimensions)
8686
.call(applyTransform, null, Y && y, dx, offset + dy)
8787
.call(g => g.selectAll("line")
8888
.data(index)

src/marks/text.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ export class Text extends Mark {
6363
const {dx, dy, rotate} = this;
6464
const [cx, cy] = applyFrameAnchor(this, dimensions);
6565
return create("svg:g")
66-
.call(applyIndirectTextStyles, this, T)
66+
.call(applyIndirectStyles, this, dimensions)
67+
.call(applyIndirectTextStyles, this, T, dimensions)
6768
.call(applyTransform, x, y, offset + dx, offset + dy)
6869
.call(g => g.selectAll()
6970
.data(index)
@@ -129,7 +130,6 @@ export function textY(data, {y = identity, ...options} = {}) {
129130
}
130131

131132
function applyIndirectTextStyles(selection, mark, T) {
132-
applyIndirectStyles(selection, mark);
133133
applyAttr(selection, "text-anchor", mark.textAnchor);
134134
applyAttr(selection, "font-family", mark.fontFamily);
135135
applyAttr(selection, "font-size", mark.fontSize);

src/marks/tick.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class AbstractTick extends Mark {
1616
render(index, scales, channels, dimensions) {
1717
const {dx, dy} = this;
1818
return create("svg:g")
19-
.call(applyIndirectStyles, this)
19+
.call(applyIndirectStyles, this, dimensions)
2020
.call(this._transform, scales, dx, dy)
2121
.call(g => g.selectAll("line")
2222
.data(index)

src/marks/vector.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export class Vector extends Mark {
4444
const k = anchor === "start" ? 0 : anchor === "end" ? 1 : 0.5;
4545
return create("svg:g")
4646
.attr("fill", "none")
47-
.call(applyIndirectStyles, this)
47+
.call(applyIndirectStyles, this, dimensions)
4848
.call(applyTransform, x, y, offset + dx, offset + dy)
4949
.call(g => g.selectAll()
5050
.data(index)

src/plot.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {Dimensions} from "./dimensions.js";
66
import {Legends, exposeLegends} from "./legends.js";
77
import {arrayify, isOptions, keyword, range, first, second, where} from "./options.js";
88
import {Scales, ScaleFunctions, autoScaleRange, applyScales, exposeScales} from "./scales.js";
9-
import {applyInlineStyles, maybeClassName, styles} from "./style.js";
9+
import {applyInlineStyles, maybeClassName, maybeClip, styles} from "./style.js";
1010
import {basic} from "./transforms/basic.js";
1111

1212
export function plot(options = {}) {
@@ -134,7 +134,7 @@ function filter(index, channels, values) {
134134

135135
export class Mark {
136136
constructor(data, channels = [], options = {}, defaults) {
137-
const {facet = "auto", sort, dx, dy} = options;
137+
const {facet = "auto", sort, dx, dy, clip} = options;
138138
const names = new Set();
139139
this.data = data;
140140
this.sort = isOptions(sort) ? sort : null;
@@ -158,6 +158,7 @@ export class Mark {
158158
});
159159
this.dx = +dx || 0;
160160
this.dy = +dy || 0;
161+
this.clip = maybeClip(clip);
161162
}
162163
initialize(facets, facetChannels) {
163164
let data = arrayify(this.data);

src/style.js

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import {string, number, maybeColorChannel, maybeNumberChannel, isTemporal, isNum
55

66
export const offset = typeof window !== "undefined" && window.devicePixelRatio > 1 ? 0 : 0.5;
77

8+
let nextClipPathId = 0;
9+
810
export function styles(
911
mark,
1012
{
@@ -172,7 +174,16 @@ export function applyGroupedChannelStyles(selection, {target}, {ariaLabel: AL, t
172174
applyTitleGroup(selection, T);
173175
}
174176

175-
export function applyIndirectStyles(selection, mark) {
177+
// clip: true clips to the frame
178+
// TODO: accept other types of clips (paths, urls, x, y, other marks?…)
179+
// https://github.com/observablehq/plot/issues/181
180+
export function maybeClip(clip) {
181+
if (clip === true) return "frame";
182+
if (clip == null || clip === false) return false;
183+
throw new Error(`clip method not implemented: ${clip}`);
184+
}
185+
186+
export function applyIndirectStyles(selection, mark, {width, height, marginLeft, marginRight, marginTop, marginBottom}) {
176187
applyAttr(selection, "aria-label", mark.ariaLabel);
177188
applyAttr(selection, "aria-description", mark.ariaDescription);
178189
applyAttr(selection, "aria-hidden", mark.ariaHidden);
@@ -187,6 +198,17 @@ export function applyIndirectStyles(selection, mark) {
187198
applyAttr(selection, "stroke-dasharray", mark.strokeDasharray);
188199
applyAttr(selection, "shape-rendering", mark.shapeRendering);
189200
applyAttr(selection, "paint-order", mark.paintOrder);
201+
if (mark.clip === "frame") {
202+
const id = `plot-clippath-${++nextClipPathId}`;
203+
const w = width - marginRight - marginLeft;
204+
const h = height - marginTop - marginBottom;
205+
selection.append("clipPath")
206+
.attr("id", id)
207+
.append("rect")
208+
.attr("width", w)
209+
.attr("height", h);
210+
applyAttr(selection, "clip-path", `url(#${id})`);
211+
}
190212
}
191213

192214
export function applyDirectStyles(selection, mark) {

0 commit comments

Comments
 (0)