Skip to content

Commit

Permalink
preattentive symbols (#189)
Browse files Browse the repository at this point in the history
* preattentive symbols

Symbols with preattentive features aid pattern detection and reduce overlap.  This symbol set implements ideas of Cleveland, Wilkinson, and others, and has been tested for popularity and discriminability, and adjusted for approximately equal weight.

Many products support up to a dozen different symbol sets.  This is analogous to multiple color schemes, such as Dr. Cindy Brewer's recently supported in d3.

Not knowing d3's plans for symbol sets, I've only made a bare attempt at integrating these, but would be happy to continue this work along whatever lines are appropriate.

* export new symbols

* add tests for new symbols

* PreTieR

* Update README

* Update README.md

Co-authored-by: Philippe Rivière <fil@rezo.net>

* Update README.md

Co-authored-by: Philippe Rivière <fil@rezo.net>

Co-authored-by: Heman Robinson <heman.robinson@gmail.com>
Co-authored-by: Philippe Rivière <fil@rezo.net>
  • Loading branch information
3 people authored Jan 7, 2022
1 parent 67436be commit 1bf1f67
Show file tree
Hide file tree
Showing 19 changed files with 273 additions and 83 deletions.
68 changes: 48 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -929,11 +929,11 @@ Symbols provide a categorical shape encoding as is commonly used in scatterplots

Constructs a new symbol generator of the specified [type](#symbol_type) and [size](#symbol_size). If not specified, *type* defaults to a circle, and *size* defaults to 64.

<a name="_symbol" href="#_symbol">#</a> <i>symbol</i>(<i>arguments</i>…) · [Source](https://github.com/d3/d3-shape/blob/master/src/symbol.js), [Examples](https://observablehq.com/@d3/fitted-symbols)
<a name="_symbol" href="#_symbol">#</a> <i>symbol</i>(<i>arguments</i>…) · [Source](https://github.com/d3/d3-shape/blob/master/src/symbol.js)

Generates a symbol for the given *arguments*. The *arguments* are arbitrary; they are simply propagated to the symbol generator’s accessor functions along with the `this` object. For example, with the default settings, no arguments are needed to produce a circle with area 64 square pixels. If the symbol generator has a [context](#symbol_context), then the symbol is rendered to this context as a sequence of [path method](http://www.w3.org/TR/2dcontext/#canvaspathmethods) calls and this function returns void. Otherwise, a [path data](http://www.w3.org/TR/SVG/paths.html#PathData) string is returned.

<a name="symbol_type" href="#symbol_type">#</a> <i>symbol</i>.<b>type</b>([<i>type</i>]) · [Source](https://github.com/d3/d3-shape/blob/master/src/symbol.js), [Examples](https://observablehq.com/@d3/fitted-symbols)
<a name="symbol_type" href="#symbol_type">#</a> <i>symbol</i>.<b>type</b>([<i>type</i>]) · [Source](https://github.com/d3/d3-shape/blob/master/src/symbol.js)

If *type* is specified, sets the symbol type to the specified function or symbol type and returns this symbol generator. If *type* is a function, the symbol generator’s arguments and *this* are passed through. (See [*selection*.attr](https://github.com/d3/d3-selection/blob/master/README.md#selection_attr) if you are using d3-selection.) If *type* is not specified, returns the current symbol type accessor, which defaults to:

Expand All @@ -943,9 +943,9 @@ function type() {
}
```

See [symbols](#symbols) for the set of built-in symbol types. To implement a custom symbol type, pass an object that implements [*symbolType*.draw](#symbolType_draw).
See [symbolsFill](#symbolsFill) and [symbolsStroke](#symbolsStroke) for built-in symbol types. To implement a custom symbol type, pass an object that implements [*symbolType*.draw](#symbolType_draw).

<a name="symbol_size" href="#symbol_size">#</a> <i>symbol</i>.<b>size</b>([<i>size</i>]) · [Source](https://github.com/d3/d3-shape/blob/master/src/symbol.js), [Examples](https://observablehq.com/@d3/fitted-symbols)
<a name="symbol_size" href="#symbol_size">#</a> <i>symbol</i>.<b>size</b>([<i>size</i>]) · [Source](https://github.com/d3/d3-shape/blob/master/src/symbol.js)

If *size* is specified, sets the size to the specified function or number and returns this symbol generator. If *size* is a function, the symbol generator’s arguments and *this* are passed through. (See [*selection*.attr](https://github.com/d3/d3-selection/blob/master/README.md#selection_attr) if you are using d3-selection.) If *size* is not specified, returns the current size accessor, which defaults to:

Expand All @@ -961,37 +961,65 @@ Specifying the size as a function is useful for constructing a scatterplot with

If *context* is specified, sets the context and returns this symbol generator. If *context* is not specified, returns the current context, which defaults to null. If the context is not null, then the [generated symbol](#_symbol) is rendered to this context as a sequence of [path method](http://www.w3.org/TR/2dcontext/#canvaspathmethods) calls. Otherwise, a [path data](http://www.w3.org/TR/SVG/paths.html#PathData) string representing the generated symbol is returned.

<a name="symbols" href="#symbols">#</a> d3.<b>symbols</b> · [Source](https://github.com/d3/d3-shape/blob/master/src/symbol.js), [Examples](https://observablehq.com/@d3/fitted-symbols)
<a name="symbolsFill" href="#symbolsFill">#</a> d3.<b>symbolsFill</b> · [Source](https://github.com/d3/d3-shape/blob/master/src/symbol.js)

An array containing the set of all built-in symbol types: [circle](#symbolCircle), [cross](#symbolCross), [diamond](#symbolDiamond), [square](#symbolSquare), [star](#symbolStar), [triangle](#symbolTriangle), and [wye](#symbolWye). Useful for constructing the range of an [ordinal scale](https://github.com/d3/d3-scale#ordinal-scales) should you wish to use a shape encoding for categorical data.
An array containing a set of symbol types designed for filling: [circle](#symbolCircle), [cross](#symbolCross), [diamond](#symbolDiamond), [square](#symbolSquare), [star](#symbolStar), [triangle](#symbolTriangle), and [wye](#symbolWye). Useful for constructing the range of an [ordinal scale](https://github.com/d3/d3-scale#ordinal-scales) should you wish to use a shape encoding for categorical data.

<a name="symbolCircle" href="#symbolCircle">#</a> d3.<b>symbolCircle</b> · [Source](https://github.com/d3/d3-shape/blob/master/src/symbol/circle.js), [Examples](https://observablehq.com/@d3/fitted-symbols)
<a name="symbolsStroke" href="#symbolsStroke">#</a> d3.<b>symbolsStroke</b> · [Source](https://github.com/d3/d3-shape/blob/master/src/symbol.js)

The circle symbol type.
An array containing a set of symbol types designed for stroking: [circle](#symbolCircle), [plus](#symbolPlus), [x](#symbolX), [triangle2](#symbolTriangle2), [asterisk](#symbolAsterisk), [square2](#symbolSquare2), and [diamond2](#symbolDiamond2). Useful for constructing the range of an [ordinal scale](https://github.com/d3/d3-scale#ordinal-scales) should you wish to use a shape encoding for categorical data.

<a name="symbolCross" href="#symbolCross">#</a> d3.<b>symbolCross</b> · [Source](https://github.com/d3/d3-shape/blob/master/src/symbol/cross.js), [Examples](https://observablehq.com/@d3/fitted-symbols)
<a name="symbolAsterisk" href="#symbolAsterisk">#</a> d3.<b>symbolAsterisk</b> · [Source](https://github.com/d3/d3-shape/blob/master/src/symbol/asterisk.js)

The Greek cross symbol type, with arms of equal length.
The asterisk symbol type; intended for stroking.

<a name="symbolDiamond" href="#symbolDiamond">#</a> d3.<b>symbolDiamond</b> · [Source](https://github.com/d3/d3-shape/blob/master/src/symbol/diamond.js), [Examples](https://observablehq.com/@d3/fitted-symbols)
<a name="symbolCircle" href="#symbolCircle">#</a> d3.<b>symbolCircle</b> · [Source](https://github.com/d3/d3-shape/blob/master/src/symbol/circle.js)

The rhombus symbol type.
The circle symbol type; intended for either filling or stroking.

<a name="symbolSquare" href="#symbolSquare">#</a> d3.<b>symbolSquare</b> · [Source](https://github.com/d3/d3-shape/blob/master/src/symbol/square.js), [Examples](https://observablehq.com/@d3/fitted-symbols)
<a name="symbolCross" href="#symbolCross">#</a> d3.<b>symbolCross</b> · [Source](https://github.com/d3/d3-shape/blob/master/src/symbol/cross.js)

The square symbol type.
The Greek cross symbol type, with arms of equal length; intended for filling.

<a name="symbolStar" href="#symbolStar">#</a> d3.<b>symbolStar</b> · [Source](https://github.com/d3/d3-shape/blob/master/src/symbol/star.js), [Examples](https://observablehq.com/@d3/fitted-symbols)
<a name="symbolDiamond" href="#symbolDiamond">#</a> d3.<b>symbolDiamond</b> · [Source](https://github.com/d3/d3-shape/blob/master/src/symbol/diamond.js)

The pentagonal star (pentagram) symbol type.
The rhombus symbol type; intended for filling.

<a name="symbolTriangle" href="#symbolTriangle">#</a> d3.<b>symbolTriangle</b> · [Source](https://github.com/d3/d3-shape/blob/master/src/symbol/triangle.js), [Examples](https://observablehq.com/@d3/fitted-symbols)
<a name="symbolDiamond2" href="#symbolDiamond2">#</a> d3.<b>symbolDiamond2</b> · [Source](https://github.com/d3/d3-shape/blob/master/src/symbol/diamond.js)

The up-pointing triangle symbol type.
The rotated square symbol type; intended for stroking.

<a name="symbolWye" href="#symbolWye">#</a> d3.<b>symbolWye</b> · [Source](https://github.com/d3/d3-shape/blob/master/src/symbol/wye.js), [Examples](https://observablehq.com/@d3/fitted-symbols)
<a name="symbolPlus" href="#symbolPlus">#</a> d3.<b>symbolPlus</b> · [Source](https://github.com/d3/d3-shape/blob/master/src/symbol/plus.js)

The Y-shape symbol type.
The plus symbol type; intended for stroking.

<a name="symbolSquare" href="#symbolSquare">#</a> d3.<b>symbolSquare</b> · [Source](https://github.com/d3/d3-shape/blob/master/src/symbol/square.js)

The square symbol type; intended for filling.

<a name="symbolSquare2" href="#symbolSquare2">#</a> d3.<b>symbolSquare2</b> · [Source](https://github.com/d3/d3-shape/blob/master/src/symbol/square2.js)

The square2 symbol type; intended for stroking.

<a name="symbolStar" href="#symbolStar">#</a> d3.<b>symbolStar</b> · [Source](https://github.com/d3/d3-shape/blob/master/src/symbol/star.js)

The pentagonal star (pentagram) symbol type; intended for filling.

<a name="symbolTriangle" href="#symbolTriangle">#</a> d3.<b>symbolTriangle</b> · [Source](https://github.com/d3/d3-shape/blob/master/src/symbol/triangle.js)

The up-pointing triangle symbol type; intended for filling.

<a name="symbolTriangle2" href="#symbolTriangle2">#</a> d3.<b>symbolTriangle2</b> · [Source](https://github.com/d3/d3-shape/blob/master/src/symbol/triangle2.js)

The up-pointing triangle symbol type; intended for stroking.

<a name="symbolWye" href="#symbolWye">#</a> d3.<b>symbolWye</b> · [Source](https://github.com/d3/d3-shape/blob/master/src/symbol/wye.js)

The Y-shape symbol type; intended for filling.

<a name="symbolX" href="#symbolX">#</a> d3.<b>symbolX</b> · [Source](https://github.com/d3/d3-shape/blob/master/src/symbol/x.js)

The X-shape symbol type; intended for stroking.

<a name="pointRadial" href="#pointRadial">#</a> d3.<b>pointRadial</b>(<i>angle</i>, <i>radius</i>) · [Source](https://github.com/d3/d3-shape/blob/master/src/pointRadial.js), [Examples](https://observablehq.com/@d3/radial-area-chart)

Expand Down
8 changes: 7 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,20 @@ export {default as lineRadial, default as radialLine} from "./lineRadial.js"; //
export {default as pointRadial} from "./pointRadial.js";
export {linkHorizontal, linkVertical, linkRadial} from "./link/index.js";

export {default as symbol, symbols} from "./symbol.js";
export {default as symbol, symbolsStroke, symbolsFill, symbolsFill as symbols} from "./symbol.js";
export {default as symbolAsterisk} from "./symbol/asterisk.js";
export {default as symbolCircle} from "./symbol/circle.js";
export {default as symbolCross} from "./symbol/cross.js";
export {default as symbolDiamond} from "./symbol/diamond.js";
export {default as symbolDiamond2} from "./symbol/diamond2.js";
export {default as symbolPlus} from "./symbol/plus.js";
export {default as symbolSquare} from "./symbol/square.js";
export {default as symbolSquare2} from "./symbol/square2.js";
export {default as symbolStar} from "./symbol/star.js";
export {default as symbolTriangle} from "./symbol/triangle.js";
export {default as symbolTriangle2} from "./symbol/triangle2.js";
export {default as symbolWye} from "./symbol/wye.js";
export {default as symbolX} from "./symbol/x.js";

export {default as curveBasisClosed} from "./curve/basisClosed.js";
export {default as curveBasisOpen} from "./curve/basisOpen.js";
Expand Down
22 changes: 11 additions & 11 deletions src/math.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
export var abs = Math.abs;
export var atan2 = Math.atan2;
export var cos = Math.cos;
export var max = Math.max;
export var min = Math.min;
export var sin = Math.sin;
export var sqrt = Math.sqrt;
export const abs = Math.abs;
export const atan2 = Math.atan2;
export const cos = Math.cos;
export const max = Math.max;
export const min = Math.min;
export const sin = Math.sin;
export const sqrt = Math.sqrt;

export var epsilon = 1e-12;
export var pi = Math.PI;
export var halfPi = pi / 2;
export var tau = 2 * pi;
export const epsilon = 1e-12;
export const pi = Math.PI;
export const halfPi = pi / 2;
export const tau = 2 * pi;

export function acos(x) {
return x > 1 ? 0 : x < -1 ? pi : Math.acos(x);
Expand Down
31 changes: 25 additions & 6 deletions src/symbol.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
import {path} from "d3-path";
import constant from "./constant.js";
import asterisk from "./symbol/asterisk.js";
import circle from "./symbol/circle.js";
import cross from "./symbol/cross.js";
import diamond from "./symbol/diamond.js";
import star from "./symbol/star.js";
import diamond2 from "./symbol/diamond2.js";
import plus from "./symbol/plus.js";
import square from "./symbol/square.js";
import square2 from "./symbol/square2.js";
import star from "./symbol/star.js";
import triangle from "./symbol/triangle.js";
import triangle2 from "./symbol/triangle2.js";
import wye from "./symbol/wye.js";
import constant from "./constant.js";
import x from "./symbol/x.js";

export var symbols = [
// These symbols are designed to be filled.
export const symbolsFill = [
circle,
cross,
diamond,
Expand All @@ -18,13 +25,25 @@ export var symbols = [
wye
];

export default function(type, size) {
var context = null;
// These symbols are designed to be stroked (with a width of 1.5px and round caps).
export const symbolsStroke = [
circle,
plus,
x,
triangle2,
asterisk,
square2,
diamond2
];

export default function Symbol(type, size) {
let context = null;

type = typeof type === "function" ? type : constant(type || circle);
size = typeof size === "function" ? size : constant(size === undefined ? 64 : +size);

function symbol() {
var buffer;
let buffer;
if (!context) context = buffer = path();
type.apply(this, arguments).draw(context, +size.apply(this, arguments));
if (buffer) return context = null, buffer + "" || null;
Expand Down
17 changes: 17 additions & 0 deletions src/symbol/asterisk.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import {min, sqrt} from "../math.js";

const sqrt3 = sqrt(3);

export default {
draw(context, size) {
const r = sqrt(size + min(size / 28, 0.75)) * 0.59436;
const t = r / 2;
const u = t * sqrt3;
context.moveTo(0, r);
context.lineTo(0, -r);
context.moveTo(-u, -t);
context.lineTo(u, t);
context.moveTo(-u, t);
context.lineTo(u, -t);
}
};
6 changes: 3 additions & 3 deletions src/symbol/circle.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {pi, tau} from "../math.js";
import {pi, sqrt, tau} from "../math.js";

export default {
draw: function(context, size) {
var r = Math.sqrt(size / pi);
draw(context, size) {
const r = sqrt(size / pi);
context.moveTo(r, 0);
context.arc(0, 0, r, 0, tau);
}
Expand Down
6 changes: 4 additions & 2 deletions src/symbol/cross.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import {sqrt} from "../math.js";

export default {
draw: function(context, size) {
var r = Math.sqrt(size / 5) / 2;
draw(context, size) {
const r = sqrt(size / 5) / 2;
context.moveTo(-3 * r, -r);
context.lineTo(-r, -r);
context.lineTo(-r, -3 * r);
Expand Down
12 changes: 7 additions & 5 deletions src/symbol/diamond.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
var tan30 = Math.sqrt(1 / 3),
tan30_2 = tan30 * 2;
import {sqrt} from "../math.js";

const tan30 = sqrt(1 / 3);
const tan30_2 = tan30 * 2;

export default {
draw: function(context, size) {
var y = Math.sqrt(size / tan30_2),
x = y * tan30;
draw(context, size) {
const y = sqrt(size / tan30_2);
const x = y * tan30;
context.moveTo(0, -y);
context.lineTo(x, 0);
context.lineTo(0, y);
Expand Down
12 changes: 12 additions & 0 deletions src/symbol/diamond2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import {sqrt} from "../math.js";

export default {
draw(context, size) {
const r = sqrt(size) * 0.62625;
context.moveTo(0, -r);
context.lineTo(r, 0);
context.lineTo(0, r);
context.lineTo(-r, 0);
context.closePath();
}
};
11 changes: 11 additions & 0 deletions src/symbol/plus.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import {min, sqrt} from "../math.js";

export default {
draw(context, size) {
const r = sqrt(size - min(size / 7, 2)) * 0.87559;
context.moveTo(-r, 0);
context.lineTo(r, 0);
context.moveTo(0, r);
context.lineTo(0, -r);
}
};
8 changes: 5 additions & 3 deletions src/symbol/square.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import {sqrt} from "../math.js";

export default {
draw: function(context, size) {
var w = Math.sqrt(size),
x = -w / 2;
draw(context, size) {
const w = sqrt(size);
const x = -w / 2;
context.rect(x, x, w, w);
}
};
12 changes: 12 additions & 0 deletions src/symbol/square2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import {sqrt} from "../math.js";

export default {
draw(context, size) {
const r = sqrt(size) * 0.4431;
context.moveTo(r, r);
context.lineTo(r, -r);
context.lineTo(-r, -r);
context.lineTo(-r, r);
context.closePath();
}
};
26 changes: 13 additions & 13 deletions src/symbol/star.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
import {pi, tau} from "../math.js";
import {sin, cos, sqrt, pi, tau} from "../math.js";

var ka = 0.89081309152928522810,
kr = Math.sin(pi / 10) / Math.sin(7 * pi / 10),
kx = Math.sin(tau / 10) * kr,
ky = -Math.cos(tau / 10) * kr;
const ka = 0.89081309152928522810;
const kr = sin(pi / 10) / sin(7 * pi / 10);
const kx = sin(tau / 10) * kr;
const ky = -cos(tau / 10) * kr;

export default {
draw: function(context, size) {
var r = Math.sqrt(size * ka),
x = kx * r,
y = ky * r;
draw(context, size) {
const r = sqrt(size * ka);
const x = kx * r;
const y = ky * r;
context.moveTo(0, -r);
context.lineTo(x, y);
for (var i = 1; i < 5; ++i) {
var a = tau * i / 5,
c = Math.cos(a),
s = Math.sin(a);
for (let i = 1; i < 5; ++i) {
const a = tau * i / 5;
const c = cos(a);
const s = sin(a);
context.lineTo(s * r, -c * r);
context.lineTo(c * x - s * y, s * x + c * y);
}
Expand Down
8 changes: 5 additions & 3 deletions src/symbol/triangle.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
var sqrt3 = Math.sqrt(3);
import {sqrt} from "../math.js";

const sqrt3 = sqrt(3);

export default {
draw: function(context, size) {
var y = -Math.sqrt(size / (sqrt3 * 3));
draw(context, size) {
const y = -sqrt(size / (sqrt3 * 3));
context.moveTo(0, y * 2);
context.lineTo(-sqrt3 * y, -y);
context.lineTo(sqrt3 * y, -y);
Expand Down
15 changes: 15 additions & 0 deletions src/symbol/triangle2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import {sqrt} from "../math.js";

const sqrt3 = sqrt(3);

export default {
draw(context, size) {
const s = sqrt(size) * 0.6824;
const t = s / 2;
const u = (s * sqrt3) / 2; // cos(Math.PI / 6)
context.moveTo(0, -s);
context.lineTo(u, t);
context.lineTo(-u, t);
context.closePath();
}
};
Loading

0 comments on commit 1bf1f67

Please sign in to comment.