Skip to content

revert brush #748

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

Merged
merged 1 commit into from
Feb 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@

*Not yet released. These are forthcoming changes in the main branch.*

Plot now supports [interaction marks](./README.md#interactions)! An interaction mark defines an interactive selection represented as a subset of the mark’s data. For example, the [brush mark](./README.md#brush) allows rectangular selection by clicking and dragging; you can use a brush to select points of interest from a scatterplot and show them in a table. The interactive selection is exposed as *plot*.value. When the selection changes during interaction, the plot emits *input* events. This allows plots to be [Observable views](https://observablehq.com/@observablehq/introduction-to-views), but you can also [listen to *input* events](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget) directly.

The [text mark](./README.md#text) now supports automatic wrapping! The new **lineWidth** option specifies the desired length of a line in ems. The line breaking, wrapping, and text metrics implementations are all rudimentary, but they should be acceptable for text that is mostly ASCII. (For more control, you can hard-wrap text manually.) The **monospace** option now provides convenient defaults for monospaced text.

Plot now supports ARIA attributes for improved accessibility: aria-label, aria-description, aria-hidden. The top-level **ariaLabel** and **ariaDescription** options apply to the root SVG element. The new **ariaLabel** and **ariaDescription** scale options apply to axes; the label defaults to *e.g.* “y-axis” and the description defaults to the scale’s label (*e.g.*, “↑ temperature”). Marks define a group-level aria-label (*e.g.*, “dot”). There is also an optional **ariaLabel** channel for labeling data (*e.g.*, “E 12.7%”), and a group-level **ariaDescription** option for a human-readable description. The **ariaHidden** mark option allows the hiding of decorative elements from the accessibility tree.
Expand Down
32 changes: 0 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1225,38 +1225,6 @@ Plot.vector(wind, {x: "longitude", y: "latitude", length: "speed", rotate: "dire

Returns a new vector with the given *data* and *options*. If neither the **x** nor **y** options are specified, *data* is assumed to be an array of pairs [[*x₀*, *y₀*], [*x₁*, *y₁*], [*x₂*, *y₂*], …] such that **x** = [*x₀*, *x₁*, *x₂*, …] and **y** = [*y₀*, *y₁*, *y₂*, …].

## Interactions

Interactions are special marks that handle user input and define interactive selections. When a plot has an interaction mark, the returned *plot*.value represents the current selection as an array subset of the interaction mark’s data. As the user modifies the selection through interaction with the plot, *input* events are emitted. This design is compatible with [Observable’s viewof operator](https://observablehq.com/@observablehq/introduction-to-views), but you can also listen to *input* events directly via the [EventTarget interface](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget).

### Brush

[Source](./src/marks/brush.js) · [Examples](https://observablehq.com/@observablehq/plot-brush) · Selects points within a single contiguous rectangular region, such as nearby dots in a scatterplot.

#### Plot.brush(*data*, *options*)

```js
Plot.brush(penguins, {x: "culmen_depth_mm", y: "culmen_length_mm"})
```

Returns a new brush with the given *data* and *options*. If neither the **x** nor **y** options are specified, *data* is assumed to be an array of pairs [[*x₀*, *y₀*], [*x₁*, *y₁*], [*x₂*, *y₂*], …] such that **x** = [*x₀*, *x₁*, *x₂*, …] and **y** = [*y₀*, *y₁*, *y₂*, …].

#### Plot.brushX(*data*, *options*)

```js
Plot.brushX(penguins, {x: "culmen_depth_mm"})
```

Equivalent to [Plot.brush](#plotbrushdata-options) except that if the **x** option is not specified, it defaults to the identity function and assumes that *data* = [*x₀*, *x₁*, *x₂*, …].

#### Plot.brushY(*data*, *options*)

```js
Plot.brushY(penguins, {y: "culmen_length_mm"})
```

Equivalent to [Plot.brush](#plotbrushdata-options) except that if the **y** option is not specified, it defaults to the identity function and assumes that *data* = [*y₀*, *y₁*, *y₂*, …].

## Decorations

Decorations are static marks that do not represent data. Currently this includes only [Plot.frame](#frame), although internally Plot’s axes are implemented as decorations and may in the future be exposed here for more flexible configuration.
Expand Down
2 changes: 0 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ export {plot, Mark, marks} from "./plot.js";
export {Area, area, areaX, areaY} from "./marks/area.js";
export {Arrow, arrow} from "./marks/arrow.js";
export {BarX, BarY, barX, barY} from "./marks/bar.js";
export {brush, brushX, brushY} from "./marks/brush.js";
export {Cell, cell, cellX, cellY} from "./marks/cell.js";
export {Dot, dot, dotX, dotY} from "./marks/dot.js";
export {Frame, frame} from "./marks/frame.js";
Expand All @@ -14,7 +13,6 @@ export {RuleX, RuleY, ruleX, ruleY} from "./marks/rule.js";
export {Text, text, textX, textY} from "./marks/text.js";
export {TickX, TickY, tickX, tickY} from "./marks/tick.js";
export {Vector, vector} from "./marks/vector.js";
export {selection} from "./selection.js";
export {valueof} from "./options.js";
export {filter, reverse, sort, shuffle} from "./transforms/basic.js";
export {bin, binX, binY} from "./transforms/bin.js";
Expand Down
87 changes: 0 additions & 87 deletions src/marks/brush.js

This file was deleted.

65 changes: 6 additions & 59 deletions src/plot.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import {create, cross, difference, groups, InternMap, union} from "d3";
import {create, cross, difference, groups, InternMap} from "d3";
import {Axes, autoAxisTicks, autoScaleLabels} from "./axes.js";
import {Channel, channelSort} from "./channel.js";
import {defined} from "./defined.js";
import {Dimensions} from "./dimensions.js";
import {Legends, exposeLegends} from "./legends.js";
import {arrayify, isOptions, keyword, range, first, second, where, take} from "./options.js";
import {arrayify, isOptions, keyword, range, first, second, where} from "./options.js";
import {Scales, ScaleFunctions, autoScaleRange, applyScales, exposeScales} from "./scales.js";
import {selection} from "./selection.js";
import {applyInlineStyles, maybeClassName, styles} from "./style.js";
import {basic} from "./transforms/basic.js";

Expand Down Expand Up @@ -96,21 +95,12 @@ export function plot(options = {}) {
.call(applyInlineStyles, style)
.node();

let initialValue;
for (const mark of marks) {
const channels = markChannels.get(mark) ?? [];
const values = applyScales(channels, scales);
const index = filter(markIndex.get(mark), channels, values);
const node = mark.render(index, scales, values, dimensions, axes);
if (node != null) {
if (node[selection] !== undefined) {
initialValue = markValue(mark, node[selection]);
node.addEventListener("input", () => {
figure.value = markValue(mark, node[selection]);
});
}
svg.appendChild(node);
}
if (node != null) svg.appendChild(node);
}

// Wrap the plot in a figure with a caption, if desired.
Expand All @@ -129,7 +119,6 @@ export function plot(options = {}) {

figure.scale = exposeScales(scaleDescriptors);
figure.legend = exposeLegends(scaleDescriptors, options);
figure.value = initialValue;
return figure;
}

Expand Down Expand Up @@ -200,10 +189,6 @@ function markify(mark) {
return mark instanceof Mark ? mark : new Render(mark);
}

function markValue(mark, selection) {
return selection === null ? mark.data : take(mark.data, selection);
}

class Render extends Mark {
constructor(render) {
super();
Expand Down Expand Up @@ -279,16 +264,15 @@ class Facet extends Mark {
return {index, channels: [...channels, ...subchannels]};
}
render(I, scales, _, dimensions, axes) {
const {data, channels, marks, marksChannels, marksIndexByFacet} = this;
const {marks, marksChannels, marksIndexByFacet} = this;
const {fx, fy} = scales;
const fyDomain = fy && fy.domain();
const fxDomain = fx && fx.domain();
const fyMargins = fy && {marginTop: 0, marginBottom: 0, height: fy.bandwidth()};
const fxMargins = fx && {marginRight: 0, marginLeft: 0, width: fx.bandwidth()};
const subdimensions = {...dimensions, ...fxMargins, ...fyMargins};
const marksValues = marksChannels.map(channels => applyScales(channels, scales));
let selectionByFacet;
const parent = create("svg:g")
return create("svg:g")
.call(g => {
if (fy && axes.y) {
const axis1 = axes.y, axis2 = nolabel(axis1);
Expand Down Expand Up @@ -332,25 +316,10 @@ class Facet extends Mark {
const values = marksValues[i];
const index = filter(marksFacetIndex[i], marksChannels[i], values);
const node = marks[i].render(index, scales, values, subdimensions);
if (node != null) {
if (node[selection] !== undefined) {
if (marks[i].data !== data) throw new Error("selection must use facet data");
if (selectionByFacet === undefined) selectionByFacet = facetMap(channels);
selectionByFacet.set(key, node[selection]);
node.addEventListener("input", () => {
selectionByFacet.set(key, node[selection]);
parent[selection] = facetSelection(selectionByFacet);
});
}
this.appendChild(node);
}
if (node != null) this.appendChild(node);
}
}))
.node();
if (selectionByFacet !== undefined) {
parent[selection] = facetSelection(selectionByFacet);
}
return parent;
}
}

Expand Down Expand Up @@ -393,20 +362,6 @@ function facetTranslate(fx, fy) {
: ky => `translate(0,${fy(ky)})`;
}

// If multiple facets define a selection, then the overall selection is the
// union of the defined selections. As with non-faceted plots, we assume that
// only a single mark is defining the selection; if multiple marks define a
// selection, generally speaking the last one wins, although the behavior is not
// explicitly defined.
function facetSelection(selectionByFacet) {
let selection = null;
for (const value of selectionByFacet.values()) {
if (value === null) continue;
selection = selection === null ? value : union(selection, value);
}
return selection;
}

function facetMap(channels) {
return new (channels.length > 1 ? FacetMap2 : FacetMap);
}
Expand All @@ -424,9 +379,6 @@ class FacetMap {
set(key, value) {
return this._.set(key, value), this;
}
values() {
return this._.values();
}
}

// A Map-like interface that supports paired keys.
Expand All @@ -445,9 +397,4 @@ class FacetMap2 extends FacetMap {
else super.set(key1, new InternMap([[key2, value]]));
return this;
}
*values() {
for (const map of this._.values()) {
yield* map.values();
}
}
}
18 changes: 0 additions & 18 deletions src/selection.js

This file was deleted.

Loading