diff --git a/src/channel.js b/src/channel.js index bb67310dd7..314861599c 100644 --- a/src/channel.js +++ b/src/channel.js @@ -5,8 +5,8 @@ import {registry} from "./scales/index.js"; import {isSymbol, maybeSymbol} from "./symbol.js"; import {maybeReduce} from "./transforms/group.js"; -// TODO Type coercion? export function createChannel(data, {scale, type, value, filter, hint}, name) { + if (hint === undefined && typeof value?.transform === "function") hint = value.hint; return inferChannelScale(name, { scale, type, @@ -160,3 +160,10 @@ function ascendingGroup([ak, av], [bk, bv]) { function descendingGroup([ak, av], [bk, bv]) { return descendingDefined(av, bv) || ascendingDefined(ak, bk); } + +export function getSource(channels, key) { + let channel = channels[key]; + if (!channel) return; + while (channel.source) channel = channel.source; + return channel.source === null ? null : channel; +} diff --git a/src/context.d.ts b/src/context.d.ts index 3e23ee0dc0..84b8d13646 100644 --- a/src/context.d.ts +++ b/src/context.d.ts @@ -8,6 +8,9 @@ export interface Context { */ document: Document; + /** The current owner SVG element. */ + ownerSVGElement: SVGSVGElement; + /** The Plot’s (typically generated) class name, for custom styles. */ className: string; diff --git a/src/context.js b/src/context.js index 56d83886bf..c1911f9118 100644 --- a/src/context.js +++ b/src/context.js @@ -1,9 +1,8 @@ import {creator, select} from "d3"; -import {createProjection} from "./projection.js"; -export function createContext(options = {}, dimensions, className) { +export function createContext(options = {}) { const {document = typeof window !== "undefined" ? window.document : undefined} = options; - return {document, className, projection: createProjection(options, dimensions)}; + return {document}; } export function create(name, {document}) { diff --git a/src/facet.js b/src/facet.js index 91ec69d8cf..5398b344e7 100644 --- a/src/facet.js +++ b/src/facet.js @@ -62,7 +62,7 @@ export function facetGroups(data, {fx, fy}) { ); } -export function facetTranslate(fx, fy, {marginTop, marginLeft}) { +export function facetTranslator(fx, fy, {marginTop, marginLeft}) { return fx && fy ? ({x, y}) => `translate(${fx(x) - marginLeft},${fy(y) - marginTop})` : fx diff --git a/src/index.d.ts b/src/index.d.ts index 87cb46de1b..19e9442a31 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -4,6 +4,7 @@ export * from "./curve.js"; export * from "./dimensions.js"; export * from "./format.js"; export * from "./inset.js"; +export * from "./interactions/pointer.js"; export * from "./interval.js"; export * from "./legends.js"; export * from "./mark.js"; @@ -16,6 +17,7 @@ export * from "./marks/bar.js"; export * from "./marks/box.js"; export * from "./marks/cell.js"; export * from "./marks/contour.js"; +export * from "./marks/crosshair.js"; export * from "./marks/delaunay.js"; export * from "./marks/density.js"; export * from "./marks/dot.js"; @@ -31,6 +33,7 @@ export * from "./marks/rect.js"; export * from "./marks/rule.js"; export * from "./marks/text.js"; export * from "./marks/tick.js"; +export * from "./marks/tip.js"; export * from "./marks/tree.js"; export * from "./marks/vector.js"; export * from "./options.js"; diff --git a/src/index.js b/src/index.js index d0fbf51546..fec7e243ec 100644 --- a/src/index.js +++ b/src/index.js @@ -8,6 +8,7 @@ export {BarX, BarY, barX, barY} from "./marks/bar.js"; export {boxX, boxY} from "./marks/box.js"; export {Cell, cell, cellX, cellY} from "./marks/cell.js"; export {Contour, contour} from "./marks/contour.js"; +export {crosshair, crosshairX, crosshairY} from "./marks/crosshair.js"; export {delaunayLink, delaunayMesh, hull, voronoi, voronoiMesh} from "./marks/delaunay.js"; export {Density, density} from "./marks/density.js"; export {Dot, dot, dotX, dotY, circle, hexagon} from "./marks/dot.js"; @@ -24,6 +25,7 @@ export {Rect, rect, rectX, rectY} from "./marks/rect.js"; 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 {Tip, tip} from "./marks/tip.js"; export {tree, cluster} from "./marks/tree.js"; export {Vector, vector, vectorX, vectorY, spike} from "./marks/vector.js"; export {valueof, column, identity, indexOf} from "./options.js"; @@ -39,6 +41,7 @@ export {window, windowX, windowY} from "./transforms/window.js"; export {select, selectFirst, selectLast, selectMaxX, selectMaxY, selectMinX, selectMinY} from "./transforms/select.js"; export {stackX, stackX1, stackX2, stackY, stackY1, stackY2} from "./transforms/stack.js"; export {treeNode, treeLink} from "./transforms/tree.js"; +export {pointer, pointerX, pointerY} from "./interactions/pointer.js"; export {formatIsoDate, formatWeekday, formatMonth} from "./format.js"; export {scale} from "./scales.js"; export {legend} from "./legends.js"; diff --git a/src/interactions/pointer.d.ts b/src/interactions/pointer.d.ts new file mode 100644 index 0000000000..3a3f48521d --- /dev/null +++ b/src/interactions/pointer.d.ts @@ -0,0 +1,16 @@ +import type {Rendered} from "../transforms/basic.js"; + +/** TODO */ +export interface PointerOptions { + /** TODO */ + maxRadius?: number; +} + +/** TODO */ +export function pointer(options: T & PointerOptions): Rendered; + +/** TODO */ +export function pointerX(options: T & PointerOptions): Rendered; + +/** TODO */ +export function pointerY(options: T & PointerOptions): Rendered; diff --git a/src/interactions/pointer.js b/src/interactions/pointer.js new file mode 100644 index 0000000000..ba6e914084 --- /dev/null +++ b/src/interactions/pointer.js @@ -0,0 +1,175 @@ +import {pointer as pointof} from "d3"; +import {applyFrameAnchor} from "../style.js"; + +const states = new WeakMap(); + +function pointerK(kx, ky, {x, y, px, py, maxRadius = 40, channels, ...options} = {}) { + maxRadius = +maxRadius; + // When px or py is used, register an extra channel that the pointer + // interaction can use to control which point is focused; this allows pointing + // to function independently of where the downstream mark (e.g., a tip) is + // displayed. Also default x or y to null to disable maybeTuple etc. + if (px != null) (x ??= null), (channels = {...channels, px: {value: px, scale: "x"}}); + if (py != null) (y ??= null), (channels = {...channels, py: {value: py, scale: "y"}}); + return { + x, + y, + channels, + ...options, + render(index, scales, values, dimensions, context) { + const mark = this; + const svg = context.ownerSVGElement; + + // Isolate state per-pointer, per-plot; if the pointer is reused by + // multiple marks, they will share the same state (e.g., sticky modality). + let state = states.get(svg); + if (!state) states.set(svg, (state = {sticky: false, roots: [], renders: []})); + + // This serves as a unique identifier of the rendered mark per-plot; it is + // used to record the currently-rendered elements (state.roots) so that we + // can tell when a rendered element is clicked on. + let renderIndex = state.renders.push(render) - 1; + + // For faceting, we want to compute the local coordinates of each point, + // which means subtracting out the facet translation, if any. (It’s + // tempting to do this using the local coordinates in SVG, but that’s + // complicated by mark-specific transforms such as dx and dy.) Also, since + // band scales return the upper bound of the band, we have to offset by + // half the bandwidth. + const {x, y, fx, fy} = scales; + let tx = fx ? fx(index.fx) - dimensions.marginLeft : 0; + let ty = fy ? fy(index.fy) - dimensions.marginTop : 0; + if (x?.bandwidth) tx += x.bandwidth() / 2; + if (y?.bandwidth) ty += y.bandwidth() / 2; + + // For faceting, we also need to record the closest point per facet per + // mark (!), since each facet has its own pointer event listeners; we only + // want the closest point across facets to be visible. + const faceted = index.fi != null; + let facetState; + if (faceted) { + let facetStates = state.facetStates; + if (!facetStates) state.facetStates = facetStates = new Map(); + facetState = facetStates.get(mark); + if (!facetState) facetStates.set(mark, (facetState = new Map())); + } + + // The order of precedence when determining the point position is: px & + // py; the middle of x1 & y1 and x2 & y2; or lastly x & y. If any + // dimension is unspecified, we fallback to the frame anchor. + const {x: X, y: Y, x1: X1, y1: Y1, x2: X2, y2: Y2, px: PX, py: PY} = values; + const [cx, cy] = applyFrameAnchor(this, dimensions); + const px = PX ? (i) => PX[i] : X2 ? (i) => (X1[i] + X2[i]) / 2 : X ? (i) => X[i] : () => cx; + const py = PY ? (i) => PY[i] : Y2 ? (i) => (Y1[i] + Y2[i]) / 2 : Y ? (i) => Y[i] : () => cy; + + let i; // currently focused index + let g; // currently rendered mark + let f; // current animation frame + + // When faceting, if more than one pointer would be visible, only show + // this one if it is the closest. We defer rendering using an animation + // frame to allow all pointer events to be received before deciding which + // mark to render; although when hiding, we render immediately. + function update(ii, ri) { + if (faceted) { + if (f) f = cancelAnimationFrame(f); + if (ii == null) facetState.delete(index.fi); + else { + facetState.set(index.fi, ri); + f = requestAnimationFrame(() => { + f = null; + for (const r of facetState.values()) { + if (r < ri) { + ii = null; + break; + } + } + render(ii); + }); + return; + } + } + render(ii); + } + + function render(ii) { + if (i === ii) return; // the tooltip hasn’t moved + i = ii; + const I = i == null ? [] : [i]; + if (faceted) (I.fx = index.fx), (I.fy = index.fy), (I.fi = index.fi); + const r = mark.render(I, scales, values, dimensions, context); + if (g) { + // When faceting, preserve swapped mark and facet transforms; also + // remove ARIA attributes since these are promoted to the parent. This + // is perhaps brittle in that it depends on how Plot renders facets, + // but it produces a cleaner and more accessible SVG structure. + if (faceted) { + const p = g.parentNode; + const ft = g.getAttribute("transform"); + const mt = r.getAttribute("transform"); + ft ? r.setAttribute("transform", ft) : r.removeAttribute("transform"); + mt ? p.setAttribute("transform", mt) : p.removeAttribute("transform"); + r.removeAttribute("aria-label"); + r.removeAttribute("aria-description"); + r.removeAttribute("aria-hidden"); + } + g.replaceWith(r); + } + state.roots[renderIndex] = r; + return (g = r); + } + + function pointermove(event) { + if (state.sticky || (event.pointerType === "mouse" && event.buttons === 1)) return; // dragging + let [xp, yp] = pointof(event); + (xp -= tx), (yp -= ty); // correct for facets and band scales + let ii = null; + let ri = maxRadius * maxRadius; + for (const j of index) { + const dx = kx * (px(j) - xp); + const dy = ky * (py(j) - yp); + const rj = dx * dx + dy * dy; + if (rj <= ri) (ii = j), (ri = rj); + } + update(ii, ri); + } + + function pointerdown(event) { + if (event.pointerType !== "mouse") return; + if (i == null) return; // not pointing + if (state.sticky && state.roots.some((r) => r?.contains(event.target))) return; // stay sticky + if (state.sticky) (state.sticky = false), state.renders.forEach((r) => r(null)); // clear all pointers + else state.sticky = true; + event.stopImmediatePropagation(); // suppress other pointers + } + + function pointerleave(event) { + if (event.pointerType !== "mouse") return; + if (!state.sticky) update(null); + } + + // We listen to the svg element; listening to the window instead would let + // us receive pointer events from farther away, but would also make it + // hard to know when to remove the listeners. (Using a mutation observer + // to watch the entire document is likely too expensive.) + svg.addEventListener("pointerenter", pointermove); + svg.addEventListener("pointermove", pointermove); + svg.addEventListener("pointerdown", pointerdown); + svg.addEventListener("pointerleave", pointerleave); + + return render(null); + } + }; +} + +export function pointer(options) { + return pointerK(1, 1, options); +} + +export function pointerX(options) { + return pointerK(1, 0.01, options); +} + +export function pointerY(options) { + return pointerK(0.01, 1, options); +} diff --git a/src/mark.d.ts b/src/mark.d.ts index 64f4f496c9..cfcecc55c8 100644 --- a/src/mark.d.ts +++ b/src/mark.d.ts @@ -1,4 +1,4 @@ -import type {ChannelDomainSort, Channels, ChannelValue, ChannelValues, ChannelValueSpec} from "./channel.js"; +import type {Channel, ChannelDomainSort, ChannelValue, ChannelValues, ChannelValueSpec} from "./channel.js"; import type {Context} from "./context.js"; import type {Dimensions} from "./dimensions.js"; import type {plot} from "./plot.js"; @@ -126,6 +126,9 @@ export interface MarkOptions { /** A custom mark initializer. */ initializer?: InitializerFunction; + /** A custom render transform. */ + render?: RenderFunction; + /** * The horizontal facet position channel, for mark-level faceting, bound to * the *fx* scale. @@ -445,7 +448,7 @@ export interface MarkOptions { * An object defining additional custom channels. This meta option may be used * by an **initializer** to declare extra channels. */ - channels?: Channels; + channels?: Record; } /** The abstract base class for Mark implementations. */ diff --git a/src/mark.js b/src/mark.js index d1cc8784f4..d268a33434 100644 --- a/src/mark.js +++ b/src/mark.js @@ -1,8 +1,9 @@ import {channelDomain, createChannels, valueObject} from "./channel.js"; import {defined} from "./defined.js"; import {maybeFacetAnchor} from "./facet.js"; +import {maybeValues} from "./options.js"; import {arrayify, isDomainSort, isOptions, keyword, maybeNamed, range, singleton} from "./options.js"; -import {maybeProject} from "./projection.js"; +import {project} from "./projection.js"; import {maybeClip, styles} from "./style.js"; import {basic, initializer} from "./transforms/basic.js"; @@ -22,7 +23,8 @@ export class Mark { marginBottom = margin, marginLeft = margin, clip, - channels: extraChannels + channels: extraChannels, + render } = options; this.data = data; this.sort = isDomainSort(sort) ? sort : null; @@ -37,7 +39,7 @@ export class Mark { } this.facetAnchor = maybeFacetAnchor(facetAnchor); channels = maybeNamed(channels); - if (extraChannels !== undefined) channels = {...maybeNamed(extraChannels), ...channels}; + if (extraChannels !== undefined) channels = {...maybeValues(maybeNamed(extraChannels)), ...channels}; if (defaults !== undefined) channels = {...styles(this, options, defaults), ...channels}; this.channels = Object.fromEntries( Object.entries(channels) @@ -78,13 +80,17 @@ export class Mark { throw new Error(`super-faceting cannot use x or y`); } } + if (render != null) { + if (typeof render !== "function") throw new TypeError(`invalid render transform: ${render}`); + this.renderTransform = render; + } } initialize(facets, facetChannels, plotOptions) { let data = arrayify(this.data); if (facets === undefined && data != null) facets = [range(data)]; const originalFacets = facets; if (this.transform != null) ({facets, data} = this.transform(data, facets, plotOptions)), (data = arrayify(data)); - if (facets !== undefined) facets.original = originalFacets; // needed up read facetChannels + if (facets !== undefined) facets.original = originalFacets; // needed to read facetChannels const channels = createChannels(this.channels, data); if (this.sort != null) channelDomain(data, facets, channels, facetChannels, this.sort); // mutates facetChannels! return {data, facets, channels}; @@ -99,18 +105,22 @@ export class Mark { } return index; } - // If there is a projection, and there are both x and y channels (or x1 and - // y1, or x2 and y2 channels), and those channels are associated with the x - // and y scale respectively (and not already in screen coordinates as with an - // initializer), then apply the projection, replacing the x and y values. Note - // that the x and y scales themselves don’t exist if there is a projection, - // but whether the channels are associated with scales still determines - // whether the projection should apply; think of the projection as a - // combination xy-scale. + // If there is a projection, and there are paired x and y channels associated + // with the x and y scale respectively (and not already in screen coordinates + // as with an initializer), then apply the projection, replacing the x and y + // values. Note that the x and y scales themselves don’t exist if there is a + // projection, but whether the channels are associated with scales still + // determines whether the projection should apply; think of the projection as + // a combination xy-scale. project(channels, values, context) { - maybeProject("x", "y", channels, values, context); - maybeProject("x1", "y1", channels, values, context); - maybeProject("x2", "y2", channels, values, context); + for (const cx in channels) { + if (channels[cx].scale === "x" && /^x|x$/.test(cx)) { + const cy = cx.replace(/^x|x$/, "y"); + if (cy in channels && channels[cy].scale === "y") { + project(cx, cy, values, context.projection); + } + } + } } scale(channels, scales, context) { const values = valueObject(channels, scales); diff --git a/src/marks/axis.js b/src/marks/axis.js index e9f7402445..9cfa89c4b4 100644 --- a/src/marks/axis.js +++ b/src/marks/axis.js @@ -513,7 +513,8 @@ function axisMark(mark, k, ariaLabel, data, options, initialize) { let channels; const m = mark( data, - initializer(options, function (data, facets, _channels, scales) { + initializer(options, function (data, facets, _channels, scales, dimensions, context) { + const initializeFacets = data == null && (k === "fx" || k === "fy"); const {[k]: scale} = scales; if (!scale) throw new Error(`missing scale: ${k}`); let {ticks, tickSpacing, interval} = options; @@ -546,17 +547,16 @@ function axisMark(mark, k, ariaLabel, data, options, initialize) { facets = [range(data)]; } else { channels[k] = {scale: k, value: identity}; - facets = undefined; // computed automatically by plot } } initialize?.call(this, scale, ticks, channels); - return { - data, - facets, - channels: Object.fromEntries( - Object.entries(channels).map(([name, channel]) => [name, {...channel, value: valueof(data, channel.value)}]) - ) - }; + const initializedChannels = Object.fromEntries( + Object.entries(channels).map(([name, channel]) => { + return [name, {...channel, value: valueof(data, channel.value)}]; + }) + ); + if (initializeFacets) facets = context.filterFacets(data, initializedChannels); + return {data, facets, channels: initializedChannels}; }) ); if (data == null) { @@ -576,7 +576,7 @@ function inferTextChannel(scale, ticks, tickFormat) { // D3’s ordinal scales simply use toString by default, but if the ordinal scale // domain (or ticks) are numbers or dates (say because we’re applying a time // interval to the ordinal scale), we want Plot’s default formatter. -function inferTickFormat(scale, ticks, tickFormat) { +export function inferTickFormat(scale, ticks, tickFormat) { return scale.tickFormat ? scale.tickFormat(isIterable(ticks) ? null : ticks, tickFormat) : tickFormat === undefined @@ -635,6 +635,8 @@ function inferScaleOrder(scale) { // inferred from an associated channel, adds an orientation-appropriate arrow. function inferAxisLabel(key, scale, labelAnchor) { const label = scale.label; + // Ignore the implicit label for temporal scales if it’s simply “date”. + if (label?.inferred && isTemporalScale(scale) && /^(date|time|year)$/i.test(label)) return; if (scale.bandwidth || !label?.inferred) return label; const order = inferScaleOrder(scale); return order diff --git a/src/marks/crosshair.d.ts b/src/marks/crosshair.d.ts new file mode 100644 index 0000000000..c0860584e5 --- /dev/null +++ b/src/marks/crosshair.d.ts @@ -0,0 +1,26 @@ +import type {ChannelValueSpec} from "../channel.js"; +import type {CompoundMark, Data, MarkOptions} from "../mark.js"; + +/** Options for the crosshair mark. */ +export interface CrosshairOptions extends MarkOptions { + /** + * The horizontal position channel specifying the crosshair’s center, + * typically bound to the *x* scale. + */ + x?: ChannelValueSpec; + + /** + * The vertical position channel specifying the crosshair’s center, typically + * bound to the *y* scale. + */ + y?: ChannelValueSpec; +} + +/** TODO */ +export function crosshair(data?: Data, options?: CrosshairOptions): CompoundMark; + +/** TODO */ +export function crosshairX(data?: Data, options?: Omit): CompoundMark; + +/** TODO */ +export function crosshairY(data?: Data, options?: Omit): CompoundMark; diff --git a/src/marks/crosshair.js b/src/marks/crosshair.js new file mode 100644 index 0000000000..9c1b06020a --- /dev/null +++ b/src/marks/crosshair.js @@ -0,0 +1,102 @@ +import {getSource} from "../channel.js"; +import {pointer, pointerX, pointerY} from "../interactions/pointer.js"; +import {marks} from "../mark.js"; +import {initializer} from "../transforms/basic.js"; +import {ruleX, ruleY} from "./rule.js"; +import {text} from "./text.js"; + +export function crosshair(data, options = {}) { + const p = pointer({px: options.x, py: options.y}); + return marks(pruleX(data, p, options), pruleY(data, p, options), ptextX(data, p, options), ptextY(data, p, options)); +} + +export function crosshairX(data, options = {}) { + const p = pointerX({px: options.x}); + return marks(pruleX(data, p, options), ptextX(data, p, options)); +} + +export function crosshairY(data, options = {}) { + const p = pointerY({py: options.y}); + return marks(pruleY(data, p, options), ptextY(data, p, options)); +} + +function markOptions( + k, + {channels: pointerChannels, ...pointerOptions}, + {facet, facetAnchor, fx, fy, [k]: p, channels, transform, initializer} +) { + return { + ...pointerOptions, + facet, + facetAnchor, + fx, + fy, + [k]: p, + channels: {...pointerChannels, ...channels}, + transform, + initializer: pxpy(k, initializer) + }; +} + +// Wrap the initializer, if any, mapping px and py to x and y temporarily (e.g., +// for hexbin) then mapping back to px and py for rendering. +function pxpy(k, i) { + if (i == null) return i; + return function (data, facets, {x: x1, y: y1, px, py, ...c1}, ...args) { + const {channels: {x, y, ...c} = {}, ...rest} = i.call(this, data, facets, {...c1, x: px, y: py}, ...args); + return { + channels: { + ...c, + ...(x && {px: x, ...(k === "x" && {x})}), + ...(y && {py: y, ...(k === "y" && {y})}) + }, + ...rest + }; + }; +} + +function pruleX(data, pointerOptions, options) { + return ruleX(data, ruleOptions("x", pointerOptions, options)); +} + +function pruleY(data, pointerOptions, options) { + return ruleY(data, ruleOptions("y", pointerOptions, options)); +} + +function ruleOptions(k, pointerOptions, options) { + const { + color = "currentColor", + ruleStroke: stroke = color, + ruleStrokeOpacity: strokeOpacity = 0.2, + ruleStrokeWidth: strokeWidth + } = options; + return {...markOptions(k, pointerOptions, options), stroke, strokeOpacity, strokeWidth}; +} + +function ptextX(data, pointerOptions, options) { + return text(data, textOptions("x", {...pointerOptions, dy: 9, frameAnchor: "bottom", lineAnchor: "top"}, options)); +} + +function ptextY(data, pointerOptions, options) { + return text(data, textOptions("y", {...pointerOptions, dx: -9, frameAnchor: "left", textAnchor: "end"}, options)); +} + +function textOptions(k, pointerOptions, options) { + const { + color = "currentColor", + textFill: fill = color, + textStroke: stroke = "white", + textStrokeOpacity: strokeOpacity, + textStrokeWidth: strokeWidth = 5 + } = options; + return {...markOptions(k, pointerOptions, textChannel(k, options)), fill, stroke, strokeOpacity, strokeWidth}; +} + +// Rather than aliasing text to have the same definition as x and y, we use an +// initializer to alias the channel values, such that the text channel can be +// derived by an initializer such as hexbin. +function textChannel(source, options) { + return initializer(options, (data, facets, channels) => { + return {channels: {text: {value: getSource(channels, source)?.value}}}; + }); +} diff --git a/src/marks/dot.js b/src/marks/dot.js index f9370a20fa..435ba798ef 100644 --- a/src/marks/dot.js +++ b/src/marks/dot.js @@ -69,7 +69,7 @@ export class Dot extends Mark { const {x: X, y: Y, r: R, rotate: A, symbol: S} = channels; const {r, rotate, symbol} = this; const [cx, cy] = applyFrameAnchor(this, dimensions); - const circle = this.symbol === symbolCircle; + const circle = symbol === symbolCircle; const size = R ? undefined : r * r * Math.PI; if (negative(r)) index = []; return create("svg:g", context) diff --git a/src/marks/rule.js b/src/marks/rule.js index e47ed93723..3efaf1a34f 100644 --- a/src/marks/rule.js +++ b/src/marks/rule.js @@ -121,7 +121,7 @@ export function ruleY(data, options) { // For marks specified either as [0, x] or [x1, x2], or nothing. function maybeOptionalZero(x, x1, x2) { - if (x === undefined) { + if (x == null) { if (x1 === undefined) { if (x2 !== undefined) return [0, x2]; } else { diff --git a/src/marks/text.js b/src/marks/text.js index bf3bf4ca54..3ac6679c88 100644 --- a/src/marks/text.js +++ b/src/marks/text.js @@ -177,7 +177,7 @@ export function textY(data, options = {}) { return new Text(data, maybeIntervalMidX({...remainingOptions, y})); } -function applyIndirectTextStyles(selection, mark, T) { +export function applyIndirectTextStyles(selection, mark, T) { applyAttr(selection, "text-anchor", mark.textAnchor); applyAttr(selection, "font-family", mark.fontFamily); applyAttr(selection, "font-size", mark.fontSize); @@ -187,7 +187,7 @@ function applyIndirectTextStyles(selection, mark, T) { } function inferFontVariant(T) { - return isNumeric(T) || isTemporal(T) ? "tabular-nums" : undefined; + return T && (isNumeric(T) || isTemporal(T)) ? "tabular-nums" : undefined; } // https://developer.mozilla.org/en-US/docs/Web/CSS/font-size @@ -430,21 +430,23 @@ function clipper({monospace, lineWidth, textOverflow}) { case "clip-end": return (text) => clipEnd(text, maxWidth, widthof, ""); case "ellipsis-start": - return (text) => clipStart(text, maxWidth, widthof, "…"); + return (text) => clipStart(text, maxWidth, widthof, ellipsis); case "ellipsis-middle": - return (text) => clipMiddle(text, maxWidth, widthof, "…"); + return (text) => clipMiddle(text, maxWidth, widthof, ellipsis); case "ellipsis-end": - return (text) => clipEnd(text, maxWidth, widthof, "…"); + return (text) => clipEnd(text, maxWidth, widthof, ellipsis); } } +export const ellipsis = "…"; + // Cuts the given text to the given width, using the specified widthof function; // the returned [index, error] guarantees text.slice(0, index) fits within the // specified width with the given error. If the text fits naturally within the // given width, returns [-1, 0]. If the text needs cutting, the given inset // specifies how much space (in the same units as width and widthof) to reserve // for a possible ellipsis character. -function cut(text, width, widthof, inset) { +export function cut(text, width, widthof, inset) { const I = []; // indexes of read character boundaries let w = 0; // current line width for (let i = 0, j = 0, n = text.length; i < n; i = j) { diff --git a/src/marks/tip.d.ts b/src/marks/tip.d.ts new file mode 100644 index 0000000000..38a14b4a39 --- /dev/null +++ b/src/marks/tip.d.ts @@ -0,0 +1,53 @@ +import type {ChannelValueSpec} from "../channel.js"; +import type {Data, FrameAnchor, MarkOptions, RenderableMark} from "../mark.js"; +import type {TextOptions} from "./text.js"; + +/** Options for styling text. TODO Move to TextOptions? */ +type TextStyles = Pick; // prettier-ignore + +/** Options for the tip mark. */ +export interface TipOptions extends MarkOptions, TextStyles { + /** + * The horizontal position channel specifying the tip’s anchor, typically + * bound to the *x* scale. + */ + x?: ChannelValueSpec; + + /** + * The vertical position channel specifying the tip’s anchor, typically + * bound to the *y* scale. + */ + y?: ChannelValueSpec; + + // TODO x1, y1, x2, y2 + + /** + * The frame anchor specifies defaults for **x** and **y** based on the plot’s + * frame; it may be one of the four sides (*top*, *right*, *bottom*, *left*), + * one of the four corners (*top-left*, *top-right*, *bottom-right*, + * *bottom-left*), or the *middle* of the frame. For example, for tips + * distributed horizontally at the top of the frame: + * + * ```js + * Plot.tip(data, {x: "date", frameAnchor: "top"}) + * ``` + */ + frameAnchor?: FrameAnchor; + + /** TODO */ + anchor?: FrameAnchor; +} + +/** + * Returns a new tip mark for the given *data* and *options*. + * + * If either **x** or **y** is not specified, the default is determined by the + * **frameAnchor** option. If none of **x**, **y**, and **frameAnchor** 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₂*, …]. + */ +export function tip(data?: Data, options?: TipOptions): Tip; + +/** The tip mark. */ +export class Tip extends RenderableMark {} diff --git a/src/marks/tip.js b/src/marks/tip.js new file mode 100644 index 0000000000..ed53e6d8ed --- /dev/null +++ b/src/marks/tip.js @@ -0,0 +1,307 @@ +import {select} from "d3"; +import {getSource} from "../channel.js"; +import {create} from "../context.js"; +import {formatDefault} from "../format.js"; +import {Mark} from "../mark.js"; +import {maybeAnchor, maybeFrameAnchor, maybeTuple, number, string} from "../options.js"; +import {applyDirectStyles, applyFrameAnchor, applyIndirectStyles, applyTransform, impliedString} from "../style.js"; +import {inferTickFormat} from "./axis.js"; +import {applyIndirectTextStyles, cut, defaultWidth, ellipsis, monospaceWidth} from "./text.js"; + +const defaults = { + ariaLabel: "tip", + fill: "white", + stroke: "currentColor" +}; + +// These channels are not displayed in the tip; TODO allow customization. +const ignoreChannels = new Set(["geometry", "title", "href", "src", "ariaLabel"]); + +export class Tip extends Mark { + constructor(data, options = {}) { + const { + x, + y, + x1, + x2, + y1, + y2, + anchor, + monospace, + fontFamily = monospace ? "ui-monospace, monospace" : undefined, + fontSize, + fontStyle, + fontVariant, + fontWeight, + lineHeight = 1, + lineWidth = 20, + frameAnchor, + textAnchor = "start", + textPadding = 8, + pointerSize = 12, + pathFilter = "drop-shadow(0 3px 4px rgba(0,0,0,0.2))" + } = options; + super( + data, + { + x: {value: x1 != null && x2 != null ? null : x, scale: "x", optional: true}, // ignore midpoint + y: {value: y1 != null && y2 != null ? null : y, scale: "y", optional: true}, // ignore midpoint + x1: {value: x1, scale: "x", optional: x2 == null}, + y1: {value: y1, scale: "y", optional: y2 == null}, + x2: {value: x2, scale: "x", optional: x1 == null}, + y2: {value: y2, scale: "y", optional: y1 == null} + }, + options, + defaults + ); + this.anchor = maybeAnchor(anchor, "anchor"); + this.previousAnchor = this.anchor ?? "top-left"; + this.frameAnchor = maybeFrameAnchor(frameAnchor); + this.textAnchor = impliedString(textAnchor, "middle"); + this.textPadding = +textPadding; + this.pointerSize = +pointerSize; + this.pathFilter = string(pathFilter); + this.lineHeight = +lineHeight; + this.lineWidth = +lineWidth; + this.monospace = !!monospace; + this.fontFamily = string(fontFamily); + this.fontSize = number(fontSize); + this.fontStyle = string(fontStyle); + this.fontVariant = string(fontVariant); + this.fontWeight = string(fontWeight); + for (const key in defaults) if (key in this.channels) this[key] = defaults[key]; // apply default even if channel + } + render(index, scales, channels, dimensions, context) { + const mark = this; + const {x, y, fx, fy} = scales; + const {ownerSVGElement: svg, document} = context; + const {anchor, monospace, lineHeight, lineWidth} = this; + const {textPadding: r, pointerSize: m, pathFilter} = this; + const {marginTop, marginLeft} = dimensions; + const sources = getSources(channels); + + // The anchor position is the middle of x1 & y1 and x2 & y2, if available, + // or x & y; the former is considered more specific because it’s how we + // disable the implicit stack and interval transforms. If any dimension is + // unspecified, we fallback to the frame anchor. We also need to know the + // facet offsets to detect when the tip would draw outside the plot, and + // thus we need to change the orientation. + const {x: X, y: Y, x1: X1, y1: Y1, x2: X2, y2: Y2} = channels; + const [cx, cy] = applyFrameAnchor(this, dimensions); + const ox = fx ? fx(index.fx) - marginLeft : 0; + const oy = fy ? fy(index.fy) - marginTop : 0; + const px = X2 ? (i) => (X1[i] + X2[i]) / 2 : X ? (i) => X[i] : () => cx; + const py = Y2 ? (i) => (Y1[i] + Y2[i]) / 2 : Y ? (i) => Y[i] : () => cy; + + // Resolve the text metric implementation. We may need an ellipsis for text + // truncation, so we optimistically compute the ellipsis width. + const widthof = monospace ? monospaceWidth : defaultWidth; + const ee = widthof(ellipsis); + + // We borrow the scale’s tick format for facet channels; this is safe for + // ordinal scales (but not continuous scales where the display value may + // need higher precision), and generally better than the default format. + const formatFx = fx && inferTickFormat(fx); + const formatFy = fy && inferTickFormat(fy); + + function* format(sources, i) { + for (const key in sources) { + if (key === "x1" && "x2" in sources) continue; + if (key === "y1" && "y2" in sources) continue; + const channel = sources[key]; + const color = channel.scale === "color" ? channels[key][i] : undefined; + if (key === "x2" && "x1" in sources) { + yield [formatLabel(scales, channel) ?? "x", formatPair(sources.x1, channel, i)]; + } else if (key === "y2" && "y1" in sources) { + yield [formatLabel(scales, channel) ?? "y", formatPair(sources.y1, channel, i)]; + } else { + yield [formatLabel(scales, channel) ?? key, formatDefault(channel.value[i]), color]; + } + } + if (index.fi != null && fx) yield [fx.label ?? "fx", formatFx(index.fx)]; + if (index.fi != null && fy) yield [fy.label ?? "fy", formatFy(index.fy)]; + } + + // We don’t call applyChannelStyles because we only use the channels to + // derive the content of the tip, not its aesthetics. + const g = create("svg:g", context) + .call(applyIndirectStyles, this, dimensions, context) + .call(applyIndirectTextStyles, this) + .call(applyTransform, this, {x: (X || X2) && x, y: (Y || Y2) && y}) + .call((g) => + g + .selectAll() + .data(index) + .enter() + .append("g") + .attr("transform", (i) => `translate(${px(i)},${py(i)})`) + .call(applyDirectStyles, this) + .call((g) => g.append("path").attr("filter", pathFilter)) + .call((g) => + g.append("text").each(function (i) { + const that = select(this); + // prevent style inheritance (from path) + this.setAttribute("fill", "currentColor"); + this.setAttribute("fill-opacity", 1); + this.setAttribute("stroke", "none"); + // iteratively render each channel value + for (const [name, value, color] of format(sources, i)) { + renderLine(that, name, value, color); + } + }) + ) + ); + + // Renders a single line (a name-value pair) to the tip, truncating the text + // as needed, and adding a title if the text is truncated. Note that this is + // just the initial layout of the text; in postrender we will compute the + // exact text metrics and translate the text as needed once we know the + // tip’s orientation (anchor). + function renderLine(selection, name, value, color) { + if (name) name = "\u200b" + name; // zwsp for double-click + let title; + let w = lineWidth * 100; + const [j] = cut(name, w, widthof, ee); + if (j >= 0) { + // name is truncated + name = name.slice(0, j).trimEnd() + ellipsis; + value = ""; + title = value.trim(); + } else { + if (name) value = " " + value; + const [k] = cut(value, w - widthof(name), widthof, ee); + if (k >= 0) { + // value is truncated + value = value.slice(0, k).trimEnd() + ellipsis; + title = value.trim(); + } + } + const line = selection.append("tspan").attr("x", 0).attr("dy", `${lineHeight}em`); + line.append("tspan").attr("font-weight", "bold").text(name); + if (value) line.append(() => document.createTextNode(value)); + if (color) line.append("tspan").text(" ■").attr("fill", color).style("user-select", "none"); + if (title) line.append("title").text(title); + } + + // Only after the plot is attached to the page can we compute the exact text + // metrics needed to determine the tip size and orientation (anchor). + function postrender() { + const {width, height} = dimensions.facet ?? dimensions; + g.selectChildren().each(function (i) { + const {x: tx, width: w, height: h} = this.getBBox(); + let a = anchor; // use the specified anchor, if any + if (a === undefined) { + a = mark.previousAnchor; // favor the previous anchor, if it fits + const x = px(i) + ox; + const y = py(i) + oy; + const fitLeft = x + w + r * 2 < width; + const fitRight = x - w - r * 2 > 0; + const fitTop = y + h + m + r * 2 + 7 < height; + const fitBottom = y - h - m - r * 2 > 0; + const ax = (/-left$/.test(a) ? fitLeft || !fitRight : fitLeft && !fitRight) ? "left" : "right"; + const ay = (/^top-/.test(a) ? fitTop || !fitBottom : fitTop && !fitBottom) ? "top" : "bottom"; + a = mark.previousAnchor = `${ay}-${ax}`; + } + const path = this.firstChild; // note: assumes exactly two children! + const text = this.lastChild; // note: assumes exactly two children! + path.setAttribute("d", getPath(a, m, r, w, h)); + if (tx) for (const t of text.childNodes) t.setAttribute("x", -tx); + text.setAttribute("y", `${+getLineOffset(a, text.childNodes.length, lineHeight).toFixed(6)}em`); + text.setAttribute("transform", `translate(${getTextTranslate(a, m, r, w, h)})`); + }); + } + + // Wait until the plot is inserted into the page so that we can use getBBox + // to compute the exact text dimensions. If the SVG is already connected, as + // when the pointer interaction triggers the re-render, use a faster + // microtask instead of an animation frame; if this SSR (e.g., JSDOM), skip + // this step. Perhaps this could be done synchronously; getting the + // dimensions of the SVG is easy, and although accurate text metrics are + // hard, we could use approximate heuristics. + if (svg.isConnected) Promise.resolve().then(postrender); + else if (typeof requestAnimationFrame !== "undefined") requestAnimationFrame(postrender); + + return g.node(); + } +} + +export function tip(data, {x, y, ...options} = {}) { + if (options.frameAnchor === undefined) [x, y] = maybeTuple(x, y); + return new Tip(data, {...options, x, y}); +} + +function getLineOffset(anchor, length, lineHeight) { + return /^top(?:-|$)/.test(anchor) + ? 0.94 - lineHeight + : /^bottom(?:-|$)/ + ? -0.29 - length * lineHeight + : (length / 2) * lineHeight; +} + +function getTextTranslate(anchor, m, r, width, height) { + switch (anchor) { + case "middle": + return [-width / 2, height / 2]; + case "top-left": + return [r, m + r]; + case "top": + return [-width / 2, m / 2 + r]; + case "top-right": + return [-width - r, m + r]; + case "right": + return [-m / 2 - width - r, height / 2]; + case "bottom-left": + return [r, -m - r]; + case "bottom": + return [-width / 2, -m / 2 - r]; + case "bottom-right": + return [-width - r, -m - r]; + case "left": + return [r + m / 2, height / 2]; + } +} + +function getPath(anchor, m, r, width, height) { + const w = width + r * 2; + const h = height + r * 2; + switch (anchor) { + case "middle": + return `M${-w / 2},${-h / 2}h${w}v${h}h${-w}z`; + case "top-left": + return `M0,0l${m},${m}h${w - m}v${h}h${-w}z`; + case "top": + return `M0,0l${m / 2},${m / 2}h${(w - m) / 2}v${h}h${-w}v${-h}h${(w - m) / 2}z`; + case "top-right": + return `M0,0l${-m},${m}h${m - w}v${h}h${w}z`; + case "right": + return `M0,0l${-m / 2},${-m / 2}v${m / 2 - h / 2}h${-w}v${h}h${w}v${m / 2 - h / 2}z`; + case "bottom-left": + return `M0,0l${m},${-m}h${w - m}v${-h}h${-w}z`; + case "bottom": + return `M0,0l${m / 2},${-m / 2}h${(w - m) / 2}v${-h}h${-w}v${h}h${(w - m) / 2}z`; + case "bottom-right": + return `M0,0l${-m},${-m}h${m - w}v${-h}h${w}z`; + case "left": + return `M0,0l${m / 2},${-m / 2}v${m / 2 - h / 2}h${w}v${h}h${-w}v${m / 2 - h / 2}z`; + } +} + +function getSources({channels}) { + const sources = {}; + for (const key in channels) { + if (ignoreChannels.has(key)) continue; + const source = getSource(channels, key); + if (source) sources[key] = source; + } + return sources; +} + +function formatPair(c1, c2, i) { + return c2.hint?.length // e.g., stackY’s y1 and y2 + ? `${formatDefault(c2.value[i] - c1.value[i])}` + : `${formatDefault(c1.value[i])}–${formatDefault(c2.value[i])}`; +} + +function formatLabel(scales, c) { + return scales[c.scale]?.label ?? c?.label; +} diff --git a/src/options.js b/src/options.js index 294903dc55..02e412083b 100644 --- a/src/options.js +++ b/src/options.js @@ -328,6 +328,15 @@ export function maybeValue(value) { return value === undefined || isOptions(value) ? value : {value}; } +// Like maybeValue, but for an object for values. +export function maybeValues(channels) { + return Object.fromEntries( + Object.entries(channels).map(([name, channel]) => { + return [name, maybeValue(channel)]; + }) + ); +} + // Coerces the given channel values (if any) to numbers. This is useful when // values will be interpolated into other code, such as an SVG transform, and // where we don’t wish to allow unexpected behavior for weird input. @@ -450,8 +459,8 @@ export function isRound(value) { return /^\s*round\s*$/i.test(value); } -export function maybeFrameAnchor(value = "middle") { - return keyword(value, "frameAnchor", [ +export function maybeAnchor(value, name) { + return maybeKeyword(value, name, [ "middle", "top-left", "top", @@ -464,6 +473,10 @@ export function maybeFrameAnchor(value = "middle") { ]); } +export function maybeFrameAnchor(value = "middle") { + return maybeAnchor(value, "frameAnchor"); +} + // Like a sort comparator, returns a positive value if the given array of values // is in ascending order, a negative value if the values are in descending // order. Assumes monotonicity; only tests the first and last values. diff --git a/src/plot.js b/src/plot.js index 801f797c4a..2ae56ee066 100644 --- a/src/plot.js +++ b/src/plot.js @@ -1,13 +1,14 @@ -import {select} from "d3"; +import {creator, select} from "d3"; import {createChannel, inferChannelScale} from "./channel.js"; -import {createContext, create} from "./context.js"; +import {createContext} from "./context.js"; import {createDimensions} from "./dimensions.js"; -import {createFacets, recreateFacets, facetExclude, facetGroups, facetTranslate, facetFilter} from "./facet.js"; +import {createFacets, recreateFacets, facetExclude, facetGroups, facetTranslator, facetFilter} from "./facet.js"; import {createLegends, exposeLegends} from "./legends.js"; import {Mark} from "./mark.js"; import {axisFx, axisFy, axisX, axisY, gridFx, gridFy, gridX, gridY} from "./marks/axis.js"; import {frame} from "./marks/frame.js"; import {arrayify, isColor, isIterable, isNone, isScaleOptions, map, yes, maybeIntervalTransform} from "./options.js"; +import {createProjection} from "./projection.js"; import {createScales, createScaleFunctions, autoScaleRange, exposeScales} from "./scales.js"; import {innerDimensions, outerDimensions} from "./scales.js"; import {position, registry as scaleRegistry} from "./scales/index.js"; @@ -141,7 +142,26 @@ export function plot(options = {}) { const {fx, fy} = scales; const subdimensions = fx || fy ? innerDimensions(scaleDescriptors, dimensions) : dimensions; const superdimensions = fx || fy ? actualDimensions(scales, dimensions) : dimensions; - const context = createContext(options, subdimensions, className); + + // Initialize the context. + const context = createContext(options); + const document = context.document; + const svg = creator("svg").call(document.documentElement); + context.ownerSVGElement = svg; + context.className = className; + context.projection = createProjection(options, subdimensions); + + // Allows e.g. the axis mark to determine faceting lazily. + context.filterFacets = (data, channels) => { + return facetFilter(facets, {channels, groups: facetGroups(data, channels)}); + }; + + // Allows e.g. the tip mark to reference channels and data on other marks. + context.getMarkState = (mark) => { + const state = stateByMark.get(mark); + const facetState = facetStateByMark.get(mark); + return {...state, channels: {...state.channels, ...facetState?.channels}}; + }; // Reinitialize; for deriving channels dependent on other channels. const newByScale = new Set(); @@ -156,9 +176,10 @@ export function plot(options = {}) { state.facets = update.facets; } if (update.channels !== undefined) { - inferChannelScales(update.channels); - Object.assign(state.channels, update.channels); - for (const channel of Object.values(update.channels)) { + const {fx, fy, ...channels} = update.channels; // separate facet channels + inferChannelScales(channels); + Object.assign(state.channels, channels); + for (const channel of Object.values(channels)) { const {scale} = channel; // Initializers aren’t allowed to redefine position scales as this // would introduce a circular dependency; so simply scale these @@ -171,16 +192,9 @@ export function plot(options = {}) { } } // If the initializer returns new mark-level facet channels, we must - // also recompute the facet state. - const {fx, fy} = update.channels; - if (fx != null || fy != null) { - const facetState = facetStateByMark.get(mark) ?? {channels: {}}; - if (fx != null) facetState.channels.fx = fx; - if (fy != null) facetState.channels.fy = fy; - facetState.groups = facetGroups(state.data, facetState.channels); - facetState.facetsIndex = state.facets = facetFilter(facets, facetState); - facetStateByMark.set(mark, facetState); - } + // record that the mark is now faceted. Note: we aren’t actually + // populating the facet state, but subsequently we won’t need it. + if (fx != null || fy != null) facetStateByMark.set(mark, true); } } } @@ -197,6 +211,15 @@ export function plot(options = {}) { Object.assign(scales, newScales); } + // Sort and filter the facets to match the fx and fy domains; this is needed + // because the facets were constructed prior to the fx and fy scales. + let facetDomains, facetTranslate; + if (facets !== undefined) { + facetDomains = {x: fx?.domain(), y: fy?.domain()}; + facets = recreateFacets(facets, facetDomains); + facetTranslate = facetTranslator(fx, fy, dimensions); + } + // Compute value objects, applying scales and projection as needed. for (const [mark, state] of stateByMark) { state.values = mark.scale(state.channels, scales, context); @@ -204,7 +227,7 @@ export function plot(options = {}) { const {width, height} = dimensions; - const svg = create("svg", context) + select(svg) .attr("class", className) .attr("fill", "currentColor") .attr("font-family", "system-ui, sans-serif") @@ -231,66 +254,60 @@ export function plot(options = {}) { }` ) ) - .call(applyInlineStyles, style) - .node(); + .call(applyInlineStyles, style); - // Render facets. - if (facets !== undefined) { - const facetDomains = {x: fx?.domain(), y: fy?.domain()}; + // Render marks. + for (const mark of marks) { + const {channels, values, facets: indexes} = stateByMark.get(mark); - // Sort and filter the facets to match the fx and fy domains; this is needed - // because the facets were constructed prior to the fx and fy scales. - facets = recreateFacets(facets, facetDomains); + // Render a non-faceted mark. + if (facets === undefined || mark.facet === "super") { + let index = null; + if (indexes) { + index = indexes[0]; + index = mark.filter(index, channels, values); + if (index.length === 0) continue; + } + const node = render(mark, index, scales, values, superdimensions, context); + if (node == null) continue; + svg.appendChild(node); + } - // Render the facets. - select(svg) - .selectAll() - .data(facets) - .enter() - .append("g") - .attr("aria-label", "facet") - .attr("transform", facetTranslate(fx, fy, dimensions)) - .each(function (f) { - let empty = true; - for (const mark of marks) { - if (mark.facet === "super") continue; // rendered below - const {channels, values, facets: indexes} = stateByMark.get(mark); - if (!(mark.facetAnchor?.(facets, facetDomains, f) ?? !f.empty)) continue; - let index = null; - if (indexes) { - index = indexes[facetStateByMark.has(mark) ? f.i : 0]; - index = mark.filter(index, channels, values); - if (index.length === 0) continue; - index.fi = f.i; // TODO cleaner way of exposing the current facet index? + // Render a faceted mark. + else { + let g; + for (const f of facets) { + if (!(mark.facetAnchor?.(facets, facetDomains, f) ?? !f.empty)) continue; + let index = null; + if (indexes) { + const faceted = facetStateByMark.has(mark); + index = indexes[faceted ? f.i : 0]; + index = mark.filter(index, channels, values); + if (index.length === 0) continue; + if (faceted) (index.fx = f.x), (index.fy = f.y), (index.fi = f.i); + } + const node = render(mark, index, scales, values, subdimensions, context); + if (node == null) continue; + // Lazily construct the shared group (to drop empty marks). + (g ??= select(svg).append("g")).append(() => node).datum(f); + // Promote ARIA attributes and mark transform to avoid repetition on + // each facet; this assumes that these attributes are consistent across + // facets, but that should be the case! + for (const name of ["aria-label", "aria-description", "aria-hidden", "transform"]) { + if (node.hasAttribute(name)) { + g.attr(name, node.getAttribute(name)); + node.removeAttribute(name); } - const node = mark.render(index, scales, values, subdimensions, context); - if (node == null) continue; - empty = false; - this.appendChild(node); } - if (empty) this.remove(); - }); - } - - // Render non-faceted marks. - for (const mark of marks) { - if (facets !== undefined && mark.facet !== "super") continue; - const {channels, values, facets: indexes} = stateByMark.get(mark); - let index = null; - if (indexes) { - index = indexes[0]; - index = mark.filter(index, channels, values); - if (index.length === 0) continue; + } + g?.selectChildren().attr("transform", facetTranslate); } - const node = mark.render(index, scales, values, superdimensions, context); - if (node != null) svg.appendChild(node); } // Wrap the plot in a figure with a caption, if desired. let figure = svg; const legends = createLegends(scaleDescriptors, context, options); if (caption != null || legends.length > 0) { - const {document} = context; figure = document.createElement("figure"); figure.style.maxWidth = "initial"; for (const legend of legends) figure.appendChild(legend); @@ -349,6 +366,10 @@ class Render extends Mark { render() {} } +function render(mark, ...args) { + return (mark.renderTransform ?? mark.render).apply(mark, args); +} + // Note: mutates channel.value to apply the scale transform, if any. function applyScaleTransforms(channels, options) { for (const name in channels) applyScaleTransform(channels[name], options); diff --git a/src/projection.js b/src/projection.js index 07e8f1e5f2..e7f416f784 100644 --- a/src/projection.js +++ b/src/projection.js @@ -204,19 +204,7 @@ const reflectY = constant( // Applies a point-wise projection to the given paired x and y channels. // Note: mutates values! -export function maybeProject(cx, cy, channels, values, context) { - const x = channels[cx] && channels[cx].scale === "x"; - const y = channels[cy] && channels[cy].scale === "y"; - if (x && y) { - project(cx, cy, values, context.projection); - } else if (x) { - throw new Error(`projection requires paired x and y channels; ${cx} is missing ${cy}`); - } else if (y) { - throw new Error(`projection requires paired x and y channels; ${cy} is missing ${cx}`); - } -} - -function project(cx, cy, values, projection) { +export function project(cx, cy, values, projection) { const x = values[cx]; const y = values[cy]; const n = x.length; @@ -254,13 +242,13 @@ export function projectionAspectRatio(projection, marks) { // Extract the (possibly) scaled values for the x and y channels, and apply the // projection if any. -export function applyPosition(channels, scales, context) { +export function applyPosition(channels, scales, {projection}) { const {x, y} = channels; let position = {}; if (x) position.x = x; if (y) position.y = y; position = valueObject(position, scales); - if (context.projection) maybeProject("x", "y", channels, position, context); + if (projection && x?.scale === "x" && y?.scale === "y") project("x", "y", position, projection); if (x) position.x = coerceNumbers(position.x); if (y) position.y = coerceNumbers(position.y); return position; diff --git a/src/scales.js b/src/scales.js index 1e0138380c..a768865410 100644 --- a/src/scales.js +++ b/src/scales.js @@ -136,8 +136,6 @@ function inferScaleLabel(channels = [], scale) { else if (label !== l) return; } if (label === undefined) return; - // Ignore the implicit label for temporal scales if it’s simply “date”. - if (isTemporalScale(scale) && /^(date|time|year)$/i.test(label)) return; if (!isOrdinalScale(scale) && scale.percent) label = `${label} (%)`; return {inferred: true, toString: () => label}; } @@ -178,7 +176,8 @@ export function innerDimensions({fx, fy}, dimensions) { marginBottom, marginLeft, width: fx ? fx.scale.bandwidth() + marginLeft + marginRight : width, - height: fy ? fy.scale.bandwidth() + marginTop + marginBottom : height + height: fy ? fy.scale.bandwidth() + marginTop + marginBottom : height, + facet: {width, height} }; } diff --git a/src/transforms/basic.d.ts b/src/transforms/basic.d.ts index 3aee313b62..cfc7c988b2 100644 --- a/src/transforms/basic.d.ts +++ b/src/transforms/basic.d.ts @@ -1,7 +1,8 @@ -import type {PlotOptions} from "../plot.js"; import type {ChannelName, Channels, ChannelValue} from "../channel.js"; import type {Context} from "../context.js"; import type {Dimensions} from "../dimensions.js"; +import type {RenderFunction} from "../mark.js"; +import type {PlotOptions} from "../plot.js"; import type {ScaleFunctions} from "../scales.js"; /** @@ -67,6 +68,9 @@ export type Transformed = T & {transform: TransformFunction}; /** Mark options with a mark initializer. */ export type Initialized = T & {initializer: InitializerFunction}; +/** Mark options with a mark render transform. */ +export type Rendered = T & {render: RenderFunction}; + /** * Given an *options* object that may specify some basic transforms (**filter**, * **sort**, or **reverse**) or a custom **transform**, composes those diff --git a/src/transforms/bin.d.ts b/src/transforms/bin.d.ts index 2a4a182dcf..6f5e5552cc 100644 --- a/src/transforms/bin.d.ts +++ b/src/transforms/bin.d.ts @@ -1,4 +1,4 @@ -import type {ChannelReducers, ChannelValueBinSpec} from "../channel.js"; +import type {ChannelReducers, ChannelValue, ChannelValueBinSpec} from "../channel.js"; import type {RangeInterval} from "../interval.js"; import type {Reducer} from "../reducer.js"; import type {Transformed} from "./basic.js"; @@ -102,6 +102,9 @@ export interface BinOptions { * ``` */ interval?: RangeInterval; + + /** For subdividing bins. */ + z?: ChannelValue; } /** diff --git a/src/transforms/centroid.js b/src/transforms/centroid.js index 0588d681c0..6be53a1fc0 100644 --- a/src/transforms/centroid.js +++ b/src/transforms/centroid.js @@ -11,7 +11,7 @@ export function centroid({geometry = identity, ...options} = {}) { const Y = new Float64Array(n); const path = geoPath(projection); for (let i = 0; i < n; ++i) [X[i], Y[i]] = path.centroid(G[i]); - return {data, facets, channels: {x: {value: X}, y: {value: Y}}}; + return {data, facets, channels: {x: {value: X, source: null}, y: {value: Y, source: null}}}; }); } diff --git a/src/transforms/dodge.js b/src/transforms/dodge.js index 06020c3de6..3f9b6a267a 100644 --- a/src/transforms/dodge.js +++ b/src/transforms/dodge.js @@ -61,8 +61,9 @@ function mergeOptions(options) { function dodge(y, x, anchor, padding, r, options) { if (r != null && typeof r !== "number") { - const {channels, sort, reverse} = options; - options = {...options, channels: {r: {value: r, scale: "r"}, ...maybeNamed(channels)}}; + let {channels, sort, reverse} = options; + channels = maybeNamed(channels); + if (channels?.r === undefined) options = {...options, channels: {...channels, r: {value: r, scale: "r"}}}; if (sort === undefined && reverse === undefined) options.sort = {channel: "r", order: "descending"}; } return initializer(options, function (data, facets, channels, scales, dimensions, context) { @@ -127,9 +128,9 @@ function dodge(y, x, anchor, padding, r, options) { data, facets, channels: { - [x]: {value: X}, - [y]: {value: Y}, - ...(R && {r: {value: R}}) + [y]: {value: Y, source: null}, // don’t show in tooltip + [x]: {value: X, source: channels[x]}, + ...(R && {r: {value: R, source: channels.r}}) } }; }); diff --git a/src/transforms/group.d.ts b/src/transforms/group.d.ts index a48caecc4e..24b422aa40 100644 --- a/src/transforms/group.d.ts +++ b/src/transforms/group.d.ts @@ -1,4 +1,4 @@ -import type {ChannelReducers} from "../channel.js"; +import type {ChannelReducers, ChannelValue} from "../channel.js"; import type {Reducer} from "../reducer.js"; import type {Transformed} from "./basic.js"; @@ -33,6 +33,9 @@ export interface GroupOutputOptions { /** If true, reverse the order of generated groups; defaults to false. */ reverse?: boolean; + + /** For subdividing groups. */ + z?: ChannelValue; } /** Output channels (and options) for the group transform. */ diff --git a/src/transforms/hexbin.js b/src/transforms/hexbin.js index ec06624bef..d2ff05e4e1 100644 --- a/src/transforms/hexbin.js +++ b/src/transforms/hexbin.js @@ -1,8 +1,8 @@ +import {isNoneish, map, number, valueof} from "../options.js"; +import {applyPosition} from "../projection.js"; import {sqrt3} from "../symbol.js"; -import {isNoneish, number, valueof} from "../options.js"; import {initializer} from "./basic.js"; import {hasOutput, maybeGroup, maybeOutputs, maybeSubgroup} from "./group.js"; -import {applyPosition} from "../projection.js"; // We don’t want the hexagons to align with the edges of the plot frame, as that // would cause extreme x-values (the upper bound of the default x-scale domain) @@ -78,9 +78,11 @@ export function hexbin(outputs = {fill: "count"}, {binWidth, ...options} = {}) { } // Construct the output channels, and populate the radius scale hint. + const sx = channels.x.scale; + const sy = channels.y.scale; const binChannels = { - x: {value: BX}, - y: {value: BY}, + x: {value: BX, source: scales[sx] ? {value: map(BX, scales[sx].invert), scale: sx} : null}, + y: {value: BY, source: scales[sy] ? {value: map(BY, scales[sy].invert), scale: sy} : null}, ...(Z && {z: {value: GZ}}), ...(F && {fill: {value: GF, scale: "auto"}}), ...(S && {stroke: {value: GS, scale: "auto"}}), @@ -88,7 +90,12 @@ export function hexbin(outputs = {fill: "count"}, {binWidth, ...options} = {}) { ...Object.fromEntries( outputs.map(({name, output}) => [ name, - {scale: "auto", radius: name === "r" ? binWidth / 2 : undefined, value: output.transform()} + { + scale: "auto", + label: output.label, + radius: name === "r" ? binWidth / 2 : undefined, + value: output.transform() + } ]) ) }; diff --git a/src/transforms/stack.js b/src/transforms/stack.js index acb9ded1d6..9cab1c694f 100644 --- a/src/transforms/stack.js +++ b/src/transforms/stack.js @@ -66,11 +66,17 @@ function mergeOptions(options) { return [{offset, order, reverse}, rest]; } +// This is a hint to the tooltip mark that the y1 and y2 channels (for stackY, +// or conversely x1 and x2 for stackX) represent a stacked length, and that the +// tooltip should therefore show y2-y1 instead of an extent. +const lengthy = {length: true}; + function stack(x, y = one, kx, ky, {offset, order, reverse}, options) { const z = maybeZ(options); const [X, setX] = maybeColumn(x); const [Y1, setY1] = column(y); const [Y2, setY2] = column(y); + Y1.hint = Y2.hint = lengthy; offset = maybeOffset(offset); order = maybeOrder(order, offset, ky); return [ @@ -87,8 +93,8 @@ function stack(x, y = one, kx, ky, {offset, order, reverse}, options) { const stacks = X ? Array.from(group(facet, (i) => X[i]).values()) : [facet]; if (O) applyOrder(stacks, O); for (const stack of stacks) { - let yn = 0, - yp = 0; + let yn = 0; + let yp = 0; if (reverse) stack.reverse(); for (const i of stack) { const y = Y[i]; diff --git a/test/marks/rule-test.js b/test/marks/rule-test.js index 8973398725..1b3fdac6a3 100644 --- a/test/marks/rule-test.js +++ b/test/marks/rule-test.js @@ -1,5 +1,6 @@ import * as Plot from "@observablehq/plot"; import assert from "assert"; +import it from "../jsdom.js"; it("ruleX() has the expected defaults", () => { const rule = Plot.ruleX(); @@ -190,10 +191,3 @@ it("ruleY(data, {x1, x2, y}) specifies x1, x2, y", () => { assert.strictEqual(y.value, "2"); assert.strictEqual(y.scale, "y"); }); - -it("rule() is incompatible with a projection", () => { - assert.throws( - () => Plot.ruleX([]).plot({projection: {stream: () => ({})}}), - /projection requires paired x and y channels; x is missing y/ - ); -}); diff --git a/test/output/anscombeQuartet.svg b/test/output/anscombeQuartet.svg index 23a593cb1b..4f38c24c3e 100644 --- a/test/output/anscombeQuartet.svg +++ b/test/output/anscombeQuartet.svg @@ -13,48 +13,151 @@ white-space: pre; } - - + + 1 - + + 2 + + + 3 + + + 4 + + + + series + + + + + + + + + + + + + + + + + + + + + + + + - + + + - + + + 4 6 8 10 12 - + + + ↑ y + + + + + + + + - + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + 5 + 10 + 15 + + + 5 + 10 + 15 + + 5 10 15 - - + + 5 + 10 + 15 + + + + x → + + + + + + + + + @@ -67,35 +170,7 @@ - - - - 2 - - - - - - - - - - - - - - - - - - - - 5 - 10 - 15 - - - + @@ -108,35 +183,7 @@ - - - - 3 - - - - - - - - - - - - - - - - - - - - 5 - 10 - 15 - - - + @@ -149,35 +196,7 @@ - - - - 4 - - - - - - - - - - - - - - - - - - - - 5 - 10 - 15 - - - + @@ -191,13 +210,4 @@ - - series - - - ↑ y - - - x → - \ No newline at end of file diff --git a/test/output/athletesBoxingHeight.svg b/test/output/athletesBoxingHeight.svg index fd1fc53b0f..abc004d7b3 100644 --- a/test/output/athletesBoxingHeight.svg +++ b/test/output/athletesBoxingHeight.svg @@ -13,11 +13,28 @@ white-space: pre; } - - + + Africa - + + Americas + + + Asia + + + Europe + + + Oceania + + + + continent + + + @@ -26,7 +43,9 @@ - + + + 1.5 1.6 1.7 @@ -35,8 +54,19 @@ 2.0 2.1 - - + + + ↑ height + + + + + + + + + + ALG ALG EGY @@ -76,13 +106,7 @@ ALG MAR - - - - Americas - - - + BRA VEN ARG @@ -148,13 +172,7 @@ CUB COL - - - - Asia - - - + AZE KAZ KAZ @@ -253,13 +271,7 @@ KAZ KAZ - - - - Europe - - - + SWE GBR GER @@ -332,13 +344,7 @@ ESP HUN - - - - Oceania - - - + AUS AUS FSM @@ -346,10 +352,4 @@ PNG - - continent - - - ↑ height - \ No newline at end of file diff --git a/test/output/athletesSampleFacet.svg b/test/output/athletesSampleFacet.svg index f9ef88bb65..b4a9a7a225 100644 --- a/test/output/athletesSampleFacet.svg +++ b/test/output/athletesSampleFacet.svg @@ -13,11 +13,97 @@ white-space: pre; } - - + + aquatics - + + archery + + + athletics + + + badminton + + + basketball + + + boxing + + + canoe + + + cycling + + + equestrian + + + fencing + + + football + + + golf + + + gymnastics + + + handball + + + hockey + + + judo + + + modern pentathlon + + + rowing + + + rugby sevens + + + sailing + + + shooting + + + table tennis + + + taekwondo + + + tennis + + + triathlon + + + volleyball + + + weightlifting + + + wrestling + + + + sport + + + @@ -26,28 +112,7 @@ - - Evan Van Moerkerke - Javier Garcia Gadea - Jordan Coelho - Markus Thormeyer - Mehdi Marzouki - Uvis Kalnins - Ziv Kalontarov - Aria Fischer - Farida Osman - Keesja Gofers - Makenzie Fischer - Mariia Shurochkina - Svetlana Romashina - Yekaterina Nemich - - - - - archery - - + @@ -56,12 +121,7 @@ - - - - athletics - - + @@ -70,37 +130,7 @@ - - A Jesus Garcia - Bachir Mahamat - Changrui Xue - Erick Barrondo - Jack Green - Joe Kovacs - Kurt Couto - Luguelin Santos - Paul Kipngetich Tanui - Theo Piniau - Tim Nedow - Yuri Floriani - Brenda Flores - Diana Martin - Hye-Song Kim - Jamile Samuel - Kamia Yousufi - Mirela Demireva - Nikkita Holder - Persis William-Mensah - Rosa Godoy - Shijie Qieyang - Vitoria Cristina Rosa - - - - - badminton - - + @@ -109,20 +139,7 @@ - - Dong Keun Lee - Marc Zwiebler - Tontowi Ahmad - Birgit Michels - Liliyana Natsir - Sapsiree Taerattanachai - - - - - basketball - - + @@ -131,15 +148,7 @@ - - Maryia Papova - - - - - boxing - - + @@ -148,12 +157,7 @@ - - - - canoe - - + @@ -162,20 +166,7 @@ - - Emanuel Silva - Filip Dvorak - Ricardas Nekriosius - Serguey Torres - Stephen Bird - Taras Mishchuk - - - - - cycling - - + @@ -184,21 +175,7 @@ - - Alejandro Valverde Belmonte - Chun Hing Chan - Gregory Bauge - Hersony Canelon - Kleber da Silva Ramos - Matthew Glaetzer - Sam Webster - - - - - equestrian - - + @@ -207,16 +184,7 @@ - - Jeroen Dubbeldam - Kevin Staut - - - - - fencing - - + @@ -225,15 +193,7 @@ - - Gauthier Grumier - - - - - football - - + @@ -242,21 +202,7 @@ - - Davie Selke - Michael Perez - Ashlyn Harris - Isabel Kerschowski - Julie Johnston - Leidy Asprilla - Olivia Schough - - - - - golf - - + @@ -265,15 +211,7 @@ - - Chloe Leurquin - - - - - gymnastics - - + @@ -282,16 +220,7 @@ - - Carolina Rodriguez - Danell Leyva - - - - - handball - - + @@ -300,12 +229,7 @@ - - - - hockey - - + @@ -314,18 +238,7 @@ - - Nic Woods - Robert van der Horst - Vicenc Ruiz - Caitlin van Sickle - - - - - judo - - + @@ -334,12 +247,7 @@ - - - - modern pentathlon - - + @@ -348,12 +256,7 @@ - - - - rowing - - + @@ -362,19 +265,7 @@ - - Alexander Sigurbjornsson - Raul Hernandez Hidalgo - Elena-Lavinia Tarlea - Ilse Paulis - Maaike Head - - - - - rugby sevens - - + @@ -383,18 +274,7 @@ - - Maria Casado - Lomano Lemeki - Moises Duque - Seabelo Senatla - - - - - sailing - - + @@ -403,18 +283,7 @@ - - Gil Cohen - Miho Yoshioka - Aichen Wang - Pieter-Jan Postma - - - - - shooting - - + @@ -423,16 +292,7 @@ - - Alin George Moldoveanu - Will Brown - - - - - table tennis - - + @@ -441,16 +301,7 @@ - - Jieni Shao - Xue Li - - - - - taekwondo - - + @@ -459,16 +310,7 @@ - - Nur Tatar - Rabia Guelec - - - - - tennis - - + @@ -477,17 +319,7 @@ - - Angelique Kerber - Annika Beck - Daria Gavrilova - - - - - triathlon - - + @@ -496,16 +328,7 @@ - - Ben Kanute - Ryan Bailie - - - - - volleyball - - + @@ -514,16 +337,7 @@ - - Pablo Herrera Allepuz - Simone Giannelli - - - - - weightlifting - - + @@ -532,17 +346,7 @@ - - Myong Hyok Kim - Yosuke Nakayama - Anastasiia Lysenko - - - - - wrestling - - + @@ -551,7 +355,9 @@ - + + + @@ -560,7 +366,9 @@ - + + + 40 60 80 @@ -569,7 +377,159 @@ 140 160 - + + + weight → + + + + Evan Van Moerkerke + Javier Garcia Gadea + Jordan Coelho + Markus Thormeyer + Mehdi Marzouki + Uvis Kalnins + Ziv Kalontarov + Aria Fischer + Farida Osman + Keesja Gofers + Makenzie Fischer + Mariia Shurochkina + Svetlana Romashina + Yekaterina Nemich + + + A Jesus Garcia + Bachir Mahamat + Changrui Xue + Erick Barrondo + Jack Green + Joe Kovacs + Kurt Couto + Luguelin Santos + Paul Kipngetich Tanui + Theo Piniau + Tim Nedow + Yuri Floriani + Brenda Flores + Diana Martin + Hye-Song Kim + Jamile Samuel + Kamia Yousufi + Mirela Demireva + Nikkita Holder + Persis William-Mensah + Rosa Godoy + Shijie Qieyang + Vitoria Cristina Rosa + + + Dong Keun Lee + Marc Zwiebler + Tontowi Ahmad + Birgit Michels + Liliyana Natsir + Sapsiree Taerattanachai + + + Maryia Papova + + + Emanuel Silva + Filip Dvorak + Ricardas Nekriosius + Serguey Torres + Stephen Bird + Taras Mishchuk + + + Alejandro Valverde Belmonte + Chun Hing Chan + Gregory Bauge + Hersony Canelon + Kleber da Silva Ramos + Matthew Glaetzer + Sam Webster + + + Jeroen Dubbeldam + Kevin Staut + + + Gauthier Grumier + + + Davie Selke + Michael Perez + Ashlyn Harris + Isabel Kerschowski + Julie Johnston + Leidy Asprilla + Olivia Schough + + + Chloe Leurquin + + + Carolina Rodriguez + Danell Leyva + + + Nic Woods + Robert van der Horst + Vicenc Ruiz + Caitlin van Sickle + + + Alexander Sigurbjornsson + Raul Hernandez Hidalgo + Elena-Lavinia Tarlea + Ilse Paulis + Maaike Head + + + Maria Casado + Lomano Lemeki + Moises Duque + Seabelo Senatla + + + Gil Cohen + Miho Yoshioka + Aichen Wang + Pieter-Jan Postma + + + Alin George Moldoveanu + Will Brown + + + Jieni Shao + Xue Li + + + Nur Tatar + Rabia Guelec + + + Angelique Kerber + Annika Beck + Daria Gavrilova + + + Ben Kanute + Ryan Bailie + + + Pablo Herrera Allepuz + Simone Giannelli + + + Myong Hyok Kim + Yosuke Nakayama + Anastasiia Lysenko + + Amas Daniel Craig Miller Eduard Popp @@ -580,10 +540,4 @@ Natalia Vorobeva - - sport - - - weight → - \ No newline at end of file diff --git a/test/output/athletesSortFacet.svg b/test/output/athletesSortFacet.svg index bbf901026a..523ab0e56b 100644 --- a/test/output/athletesSortFacet.svg +++ b/test/output/athletesSortFacet.svg @@ -13,227 +13,97 @@ white-space: pre; } - - + + boxing - - - - - - + wrestling - - - - - - + canoe - - - - - - + cycling - - - - - - + equestrian - - - - - - + shooting - - - - - - + judo - - - - - - + rowing - - - - - - + weightlifting - - - - - - + sailing - - - - - - + football - - - - - - + tennis - - - - - - + athletics - - - - - - + golf - - - - - - + rugby sevens - - - - - - + aquatics - - - - - - + handball - - - - - - + archery - - - - - - + badminton - - - - - - + basketball - - - - - - + hockey - - - - - - + modern pentathlon - - - - - - + table tennis - - - - - - + taekwondo - - - - - - + triathlon - - - - - - + volleyball - - - - - - + fencing - - - - - - + gymnastics - + + + sport + + + @@ -242,7 +112,9 @@ - + + + 0.0 0.1 0.2 @@ -251,12 +123,92 @@ 0.5 0.6 - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - sport - \ No newline at end of file diff --git a/test/output/athletesSportWeight.svg b/test/output/athletesSportWeight.svg index a562418bb1..2e03b72b90 100644 --- a/test/output/athletesSportWeight.svg +++ b/test/output/athletesSportWeight.svg @@ -13,11 +13,322 @@ white-space: pre; } - - + + aquatics - + + archery + + + athletics + + + badminton + + + basketball + + + boxing + + + canoe + + + cycling + + + equestrian + + + fencing + + + football + + + golf + + + gymnastics + + + handball + + + hockey + + + judo + + + modern pentathlon + + + rowing + + + rugby sevens + + + sailing + + + shooting + + + table tennis + + + taekwondo + + + tennis + + + triathlon + + + volleyball + + + weightlifting + + + wrestling + + + + sport + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -26,7 +337,52 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 40 + 60 + 80 + 100 + 120 + 140 + 160 + + + + weight → + + + @@ -70,21 +426,7 @@ - - - - archery - - - - - - - - - - - + @@ -118,21 +460,7 @@ - - - - athletics - - - - - - - - - - - + @@ -193,21 +521,7 @@ - - - - badminton - - - - - - - - - - - + @@ -231,21 +545,7 @@ - - - - basketball - - - - - - - - - - - + @@ -280,40 +580,12 @@ - - - - - - - - - boxing - - - - - - - - - - - - - - canoe - - - - - - - - - + + + + - + @@ -342,21 +614,7 @@ - - - - cycling - - - - - - - - - - - + @@ -386,21 +644,7 @@ - - - - equestrian - - - - - - - - - - - + @@ -424,21 +668,7 @@ - - - - fencing - - - - - - - - - - - + @@ -470,21 +700,7 @@ - - - - football - - - - - - - - - - - + @@ -512,21 +728,7 @@ - - - - golf - - - - - - - - - - - + @@ -553,21 +755,7 @@ - - - - gymnastics - - - - - - - - - - - + @@ -594,21 +782,7 @@ - - - - handball - - - - - - - - - - - + @@ -642,21 +816,7 @@ - - - - hockey - - - - - - - - - - - + @@ -683,21 +843,7 @@ - - - - judo - - - - - - - - - - - + @@ -745,21 +891,7 @@ - - - - modern pentathlon - - - - - - - - - - - + @@ -780,21 +912,7 @@ - - - - rowing - - - - - - - - - - - + @@ -828,21 +946,7 @@ - - - - rugby sevens - - - - - - - - - - - + @@ -874,21 +978,7 @@ - - - - sailing - - - - - - - - - - - + @@ -916,21 +1006,7 @@ - - - - shooting - - - - - - - - - - - + @@ -971,21 +1047,7 @@ - - - - table tennis - - - - - - - - - - - + @@ -1013,21 +1075,7 @@ - - - - taekwondo - - - - - - - - - - - + @@ -1056,21 +1104,7 @@ - - - - tennis - - - - - - - - - - - + @@ -1096,21 +1130,7 @@ - - - - triathlon - - - - - - - - - - - + @@ -1131,21 +1151,7 @@ - - - - volleyball - - - - - - - - - - - + @@ -1176,21 +1182,7 @@ - - - - weightlifting - - - - - - - - - - - + @@ -1235,39 +1227,7 @@ - - - - wrestling - - - - - - - - - - - - - - - - - - - - - 40 - 60 - 80 - 100 - 120 - 140 - 160 - - + @@ -1309,12 +1269,8 @@ - - - - sport - - weight → + + \ No newline at end of file diff --git a/test/output/autoDotFacet.svg b/test/output/autoDotFacet.svg index 898d97b872..b1380b90f9 100644 --- a/test/output/autoDotFacet.svg +++ b/test/output/autoDotFacet.svg @@ -13,11 +13,22 @@ white-space: pre; } - - + + Biscoe - + + Dream + + + Torgersen + + + + island + + + @@ -32,7 +43,9 @@ - + + + 34 36 38 @@ -47,16 +60,48 @@ 56 58 - + + + ↑ culmen_length_mm + + + - + + + + + + + + + + + + 4,000 + 6,000 + + + 4,000 + 6,000 + + 4,000 6,000 - - + + + body_mass_g → + + + + + + + + @@ -225,21 +270,7 @@ - - - - Dream - - - - - - - 4,000 - 6,000 - - - + @@ -365,21 +396,7 @@ - - - - Torgersen - - - - - - - 4,000 - 6,000 - - - + @@ -433,13 +450,4 @@ - - island - - - ↑ culmen_length_mm - - - body_mass_g → - \ No newline at end of file diff --git a/test/output/autoDotFacet2.svg b/test/output/autoDotFacet2.svg index 2a88fef132..4d3c4c0116 100644 --- a/test/output/autoDotFacet2.svg +++ b/test/output/autoDotFacet2.svg @@ -13,34 +13,131 @@ white-space: pre; } - - + + + Adelie + + + Chinstrap + + + Gentoo + + + + species + + + Biscoe - + + Dream + + + Torgersen + + + + island + + + + + + + + + + + + + + + + + - + + + 35 40 45 50 55 - + + 35 + 40 + 45 + 50 + 55 + + + 35 + 40 + 45 + 50 + 55 + + + + ↑ culmen_length_mm + + + + + + + - + + + + + + + + + + + + 4,000 + 6,000 + + + 4,000 + 6,000 + + 4,000 6,000 - - + + 4,000 + 6,000 + + + + body_mass_g → + + + + + + + + + + @@ -86,32 +183,7 @@ - - - - - - - - - - - 35 - 40 - 45 - 50 - 55 - - - - - - - 4,000 - 6,000 - - - + @@ -236,13 +308,7 @@ - - - - Dream - - - + @@ -300,32 +366,7 @@ - - - - - - - - - - - 35 - 40 - 45 - 50 - 55 - - - - - - - 4,000 - 6,000 - - - + @@ -395,24 +436,7 @@ - - - - Adelie - - - Torgersen - - - - - - - 4,000 - 6,000 - - - + @@ -466,26 +490,4 @@ - - - Chinstrap - - - - - Gentoo - - - - species - - - island - - - ↑ culmen_length_mm - - - body_mass_g → - \ No newline at end of file diff --git a/test/output/autoLineFacet.svg b/test/output/autoLineFacet.svg index 428d706d7c..1066a9f09e 100644 --- a/test/output/autoLineFacet.svg +++ b/test/output/autoLineFacet.svg @@ -13,240 +13,174 @@ white-space: pre; } - - + + Agriculture - - - - - - 1,000 - 2,000 - - - - - - - - + Business services - - - - - - 1,000 - 2,000 - - - - - - - - + Construction - - - + + Education and Health - - 1,000 - 2,000 + + Finance - - - + + Government - - - - Education and Health + + Information - - - + + Leisure and hospitality - - 1,000 - 2,000 + + Manufacturing - - - + + Mining and Extraction - - - - Finance + + Other - - - + + Self-employed - - 1,000 - 2,000 + + Transportation and Utilities - - - + + Wholesale and Retail Trade - - - Government - - + + industry + + + - - 1,000 - 2,000 - - - - - - - - - Information - - + - - 1,000 - 2,000 + + + - - - + + + - - - - Leisure and hospitality + + + - + - - 1,000 - 2,000 + + + - - - + + + - - - - Manufacturing + + + - + - - 1,000 - 2,000 + + + - - - + + + - - - - Mining and Extraction + + + - + - + + + 1,000 2,000 - - - - - - - - Other - - - - - - + 1,000 2,000 - - - + + 1,000 + 2,000 - - - - Self-employed + + 1,000 + 2,000 - - - + + 1,000 + 2,000 - + 1,000 2,000 - - - + + 1,000 + 2,000 - - - - Transportation and Utilities + + 1,000 + 2,000 - - - + + 1,000 + 2,000 - + 1,000 2,000 - - - + + 1,000 + 2,000 - - - - Wholesale and Retail Trade + + 1,000 + 2,000 - - - + + 1,000 + 2,000 - + 1,000 2,000 - + + + ↑ unemployed + + + @@ -254,7 +188,9 @@ - + + + 2000 2002 2004 @@ -262,15 +198,65 @@ 2008 2010 - - - - - - industry + + + + + + + + + + + + + + + - - ↑ unemployed + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/output/ballotStatusRace.svg b/test/output/ballotStatusRace.svg index 59b1b21a96..c89c4f45f3 100644 --- a/test/output/ballotStatusRace.svg +++ b/test/output/ballotStatusRace.svg @@ -13,170 +13,166 @@ white-space: pre; } - - + + WHITE - - - - - + + UNDESIGNATED - - 79.0% - 0.4% - 20.5% + + ASIAN - - + + OTHER - - - - UNDESIGNATED + + TWO or MORE RACES - - - - - + + INDIAN AMERICAN or ALASKA NATIVE - - 78.3% - 0.6% - 21.1% + + BLACK or AFRICAN AMERICAN - - + + NATIVE HAWAIIAN or PACIFIC ISLANDER - - - ASIAN - - + + - - 77.9% - 21.5% - 0.6% - - - - - - - - OTHER - - + - - 74.1% - 0.8% - 25.1% - - - - - - - - TWO or MORE RACES - - + - - 73.9% - 25.3% - 0.8% - - - - - - - - INDIAN AMERICAN or ALASKA NATIVE - - + - - 25.5% - 72.2% - 2.3% - - + + + + - - - - BLACK or AFRICAN AMERICAN - - + - - 67.4% - 31.5% - 1.1% - - + + + + - - - - NATIVE HAWAIIAN or PACIFIC ISLANDER - - + - + + + - + + + 0 20 40 60 - + + + Frequency (%) → + + + + 79.0% + 0.4% + 20.5% + + + 78.3% + 0.6% + 21.1% + + + 77.9% + 21.5% + 0.6% + + + 74.1% + 0.8% + 25.1% + + + 73.9% + 25.3% + 0.8% + + + 25.5% + 72.2% + 2.3% + + + 67.4% + 31.5% + 1.1% + + 41.7% 58.3% - + + + + + + + + + + + + + + + + + + + + + + + + - - - Frequency (%) → \ No newline at end of file diff --git a/test/output/beckerBarley.svg b/test/output/beckerBarley.svg index 9843c8df67..484b671b98 100644 --- a/test/output/beckerBarley.svg +++ b/test/output/beckerBarley.svg @@ -13,11 +13,91 @@ white-space: pre; } - - + + Waseca - + + Crookston + + + Morris + + + University Farm + + + Duluth + + + Grand Rapids + + + + site + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -29,7 +109,45 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -41,7 +159,81 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + Trebi + Wisconsin No. 38 + No. 457 + Glabron + Peatland + Velvet + No. 475 + Manchuria + No. 462 + Svansota + + + Trebi + Wisconsin No. 38 + No. 457 + Glabron + Peatland + Velvet + No. 475 + Manchuria + No. 462 + Svansota + + + Trebi + Wisconsin No. 38 + No. 457 + Glabron + Peatland + Velvet + No. 475 + Manchuria + No. 462 + Svansota + + + Trebi + Wisconsin No. 38 + No. 457 + Glabron + Peatland + Velvet + No. 475 + Manchuria + No. 462 + Svansota + + Trebi Wisconsin No. 38 No. 457 @@ -53,7 +245,24 @@ No. 462 Svansota - + + Trebi + Wisconsin No. 38 + No. 457 + Glabron + Peatland + Velvet + No. 475 + Manchuria + No. 462 + Svansota + + + + variety + + + @@ -62,8 +271,87 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 10 + 20 + 30 + 40 + 50 + 60 + 70 + + + + yield → + + + + + + + + + + + @@ -85,58 +373,7 @@ - - - - Crookston - - - - - - - - - - - - - - - - - - - - - - - - - - - Trebi - Wisconsin No. 38 - No. 457 - Glabron - Peatland - Velvet - No. 475 - Manchuria - No. 462 - Svansota - - - - - - - - - - - - + @@ -158,58 +395,7 @@ - - - - Morris - - - - - - - - - - - - - - - - - - - - - - - - - - - Trebi - Wisconsin No. 38 - No. 457 - Glabron - Peatland - Velvet - No. 475 - Manchuria - No. 462 - Svansota - - - - - - - - - - - - + @@ -231,58 +417,7 @@ - - - - University Farm - - - - - - - - - - - - - - - - - - - - - - - - - - - Trebi - Wisconsin No. 38 - No. 457 - Glabron - Peatland - Velvet - No. 475 - Manchuria - No. 462 - Svansota - - - - - - - - - - - - + @@ -304,58 +439,7 @@ - - - - Duluth - - - - - - - - - - - - - - - - - - - - - - - - - - - Trebi - Wisconsin No. 38 - No. 457 - Glabron - Peatland - Velvet - No. 475 - Manchuria - No. 462 - Svansota - - - - - - - - - - - - + @@ -377,76 +461,7 @@ - - - - Grand Rapids - - - - - - - - - - - - - - - - - - - - - - - - - - - Trebi - Wisconsin No. 38 - No. 457 - Glabron - Peatland - Velvet - No. 475 - Manchuria - No. 462 - Svansota - - - - - - - - - - - - - - - - - - - - - 10 - 20 - 30 - 40 - 50 - 60 - 70 - - - + @@ -469,13 +484,4 @@ - - site - - - variety - - - yield → - \ No newline at end of file diff --git a/test/output/boxplotFacetInterval.svg b/test/output/boxplotFacetInterval.svg index f75a20e406..f1f99b9a77 100644 --- a/test/output/boxplotFacetInterval.svg +++ b/test/output/boxplotFacetInterval.svg @@ -13,57 +13,211 @@ white-space: pre; } - - + + - - 2.2 + + - - + + - - + + - - + + - - - + - - 2.1 + + - - + + - - + + - - + + - - - + - + + + + 2.2 + + + 2.1 + + 2 - + + 1.9 + + + 1.8 + + + 1.7 + + + 1.6 + + + 1.5 + + + 1.4 + + + 1.3 + + + 1.2 + + + + height + + + + + + + + + + + + + + + 40 + 60 + 80 + 100 + 120 + 140 + 160 + + + + weight → + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -82,24 +236,7 @@ - - - - - - - 1.9 - - - - - - - - - - - + @@ -158,24 +295,7 @@ - - - - - - - 1.8 - - - - - - - - - - - + @@ -296,24 +416,7 @@ - - - - - - - 1.7 - - - - - - - - - - - + @@ -439,24 +542,7 @@ - - - - - - - 1.6 - - - - - - - - - - - + @@ -527,24 +613,7 @@ - - - - - - - 1.5 - - - - - - - - - - - + @@ -557,79 +626,4 @@ - - - - - - 1.4 - - - - - - - - - - - - - - - - - 1.3 - - - - - - - - - - - - - - - - - 1.2 - - - - - - - - - - - - 40 - 60 - 80 - 100 - 120 - 140 - 160 - - - - - - - - - - - - - height - - - weight → - \ No newline at end of file diff --git a/test/output/boxplotFacetNegativeInterval.svg b/test/output/boxplotFacetNegativeInterval.svg index f75a20e406..f1f99b9a77 100644 --- a/test/output/boxplotFacetNegativeInterval.svg +++ b/test/output/boxplotFacetNegativeInterval.svg @@ -13,57 +13,211 @@ white-space: pre; } - - + + - - 2.2 + + - - + + - - + + - - + + - - - + - - 2.1 + + - - + + - - + + - - + + - - - + - + + + + 2.2 + + + 2.1 + + 2 - + + 1.9 + + + 1.8 + + + 1.7 + + + 1.6 + + + 1.5 + + + 1.4 + + + 1.3 + + + 1.2 + + + + height + + + + + + + + + + + + + + + 40 + 60 + 80 + 100 + 120 + 140 + 160 + + + + weight → + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -82,24 +236,7 @@ - - - - - - - 1.9 - - - - - - - - - - - + @@ -158,24 +295,7 @@ - - - - - - - 1.8 - - - - - - - - - - - + @@ -296,24 +416,7 @@ - - - - - - - 1.7 - - - - - - - - - - - + @@ -439,24 +542,7 @@ - - - - - - - 1.6 - - - - - - - - - - - + @@ -527,24 +613,7 @@ - - - - - - - 1.5 - - - - - - - - - - - + @@ -557,79 +626,4 @@ - - - - - - 1.4 - - - - - - - - - - - - - - - - - 1.3 - - - - - - - - - - - - - - - - - 1.2 - - - - - - - - - - - - 40 - 60 - 80 - 100 - 120 - 140 - 160 - - - - - - - - - - - - - height - - - weight → - \ No newline at end of file diff --git a/test/output/caltrainDirection.svg b/test/output/caltrainDirection.svg index a29db0b289..ec66bc81d8 100644 --- a/test/output/caltrainDirection.svg +++ b/test/output/caltrainDirection.svg @@ -13,11 +13,41 @@ white-space: pre; } - - + + B - + + L + + + N + + + + + + + + + + + + + + + + 06 AM + 09 AM + 12 PM + 03 PM + 06 PM + 09 PM + 12 AM + + + + @@ -35,12 +65,7 @@ - - - - L - - + @@ -76,30 +101,7 @@ - - - - N - - - - - - - - - - - - 06 AM - 09 AM - 12 PM - 03 PM - 06 PM - 09 PM - 12 AM - - + diff --git a/test/output/crosshairDodge.svg b/test/output/crosshairDodge.svg new file mode 100644 index 0000000000..c238fbb923 --- /dev/null +++ b/test/output/crosshairDodge.svg @@ -0,0 +1,379 @@ + + + + + + + + + + + 35 + 40 + 45 + 50 + 55 + + + culmen_length_mm → + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/output/crosshairDot.svg b/test/output/crosshairDot.svg new file mode 100644 index 0000000000..aa1b7aca4b --- /dev/null +++ b/test/output/crosshairDot.svg @@ -0,0 +1,404 @@ + + + + + + + + + + + + + + 14 + 15 + 16 + 17 + 18 + 19 + 20 + 21 + + + ↑ culmen_depth_mm + + + + + + + + + + 35 + 40 + 45 + 50 + 55 + + + culmen_length_mm → + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/output/crosshairDotFacet.svg b/test/output/crosshairDotFacet.svg new file mode 100644 index 0000000000..c78f6dee37 --- /dev/null +++ b/test/output/crosshairDotFacet.svg @@ -0,0 +1,464 @@ + + + + + Adelie + + + Chinstrap + + + Gentoo + + + + species + + + + + + + + + + + + + + + + + + + + + + + + 14 + 16 + 18 + 20 + + + 14 + 16 + 18 + 20 + + + 14 + 16 + 18 + 20 + + + + ↑ culmen_depth_mm + + + + + + + + + + + + + 35 + 40 + 45 + 50 + 55 + + + + culmen_length_mm → + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/output/crosshairHexbin.svg b/test/output/crosshairHexbin.svg new file mode 100644 index 0000000000..d545460ce1 --- /dev/null +++ b/test/output/crosshairHexbin.svg @@ -0,0 +1,309 @@ + + + + + + + + + + + + + + + + 1.3 + 1.4 + 1.5 + 1.6 + 1.7 + 1.8 + 1.9 + 2.0 + 2.1 + 2.2 + + + ↑ height + + + + + + + + + + + + 40 + 60 + 80 + 100 + 120 + 140 + 160 + + + weight → + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/output/emptyFacet.svg b/test/output/emptyFacet.svg index 04215f8a30..747ce7357f 100644 --- a/test/output/emptyFacet.svg +++ b/test/output/emptyFacet.svg @@ -13,43 +13,49 @@ white-space: pre; } - - + + a - + + b + + + + TYPE + + + - + + + 0 - + + + VALUE + + + - - 1 - 2 - - - - - b - - + - + + + + 1 + 2 + + 1 2 - - - TYPE - - - VALUE PERIOD diff --git a/test/output/footballCoverage.svg b/test/output/footballCoverage.svg index f3e4902dec..6e845f8dbd 100644 --- a/test/output/footballCoverage.svg +++ b/test/output/footballCoverage.svg @@ -13,11 +13,34 @@ white-space: pre; } - - + + C2 - + + C3 + + + C4 + + + M0 + + + M1 + + + M2 + + + T2 + + + + coverage + + + @@ -30,7 +53,87 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -43,7 +146,9 @@ - + + + 0% 5% 10% @@ -56,8 +161,18 @@ 45% 50% - - + + + + + + + + + + + + @@ -91,26 +206,7 @@ - - - - C3 - - - - - - - - - - - - - - - - + @@ -144,26 +240,7 @@ - - - - C4 - - - - - - - - - - - - - - - - + @@ -197,26 +274,7 @@ - - - - M0 - - - - - - - - - - - - - - - - + @@ -250,26 +308,7 @@ - - - - M1 - - - - - - - - - - - - - - - - + @@ -303,26 +342,7 @@ - - - - M2 - - - - - - - - - - - - - - - - + @@ -356,26 +376,7 @@ - - - - T2 - - - - - - - - - - - - - - - - + @@ -410,7 +411,4 @@ - - coverage - \ No newline at end of file diff --git a/test/output/frameFacet.svg b/test/output/frameFacet.svg index 832a3ff663..382578446d 100644 --- a/test/output/frameFacet.svg +++ b/test/output/frameFacet.svg @@ -13,11 +13,50 @@ white-space: pre; } - - + + Adelie - + + Chinstrap + + + Gentoo + + + + species + + + + + + + + + + + + + + + 3,000 + 3,500 + 4,000 + 4,500 + 5,000 + 5,500 + 6,000 + + + + body_mass_g → + + + + + + @@ -170,12 +209,7 @@ - - - - Chinstrap - - + @@ -245,31 +279,7 @@ - - - - Gentoo - - - - - - - - - - - - 3,000 - 3,500 - 4,000 - 4,500 - 5,000 - 5,500 - 6,000 - - - + @@ -395,10 +405,4 @@ - - species - - - body_mass_g → - \ No newline at end of file diff --git a/test/output/functionContourFaceted.svg b/test/output/functionContourFaceted.svg index e2b7f44fd5..bbc1e4a29a 100644 --- a/test/output/functionContourFaceted.svg +++ b/test/output/functionContourFaceted.svg @@ -13,11 +13,24 @@ white-space: pre; } - - + + + cos + + + lin + + + + lin - + + sin + + + + @@ -26,31 +39,7 @@ - - 0 - 2 - 4 - 6 - 8 - 10 - 12 - - - - - - - - - - - - - - - - - + @@ -59,7 +48,9 @@ - + + + 0 2 4 @@ -68,17 +59,54 @@ 10 12 - + + 0 + 2 + 4 + 6 + 8 + 10 + 12 + + + + + + + + + - + + + + 0 + 5 + 10 + + 0 5 10 - + + + + + + + + + + + + + + + @@ -90,16 +118,7 @@ - - - - - cos - - - sin - - + @@ -111,23 +130,7 @@ - - - - - lin - - - - - - - - 0 - 5 - 10 - - + @@ -139,6 +142,11 @@ - + + + + + + \ No newline at end of file diff --git a/test/output/functionContourFaceted2.svg b/test/output/functionContourFaceted2.svg index e2b7f44fd5..bbc1e4a29a 100644 --- a/test/output/functionContourFaceted2.svg +++ b/test/output/functionContourFaceted2.svg @@ -13,11 +13,24 @@ white-space: pre; } - - + + + cos + + + lin + + + + lin - + + sin + + + + @@ -26,31 +39,7 @@ - - 0 - 2 - 4 - 6 - 8 - 10 - 12 - - - - - - - - - - - - - - - - - + @@ -59,7 +48,9 @@ - + + + 0 2 4 @@ -68,17 +59,54 @@ 10 12 - + + 0 + 2 + 4 + 6 + 8 + 10 + 12 + + + + + + + + + - + + + + 0 + 5 + 10 + + 0 5 10 - + + + + + + + + + + + + + + + @@ -90,16 +118,7 @@ - - - - - cos - - - sin - - + @@ -111,23 +130,7 @@ - - - - - lin - - - - - - - - 0 - 5 - 10 - - + @@ -139,6 +142,11 @@ - + + + + + + \ No newline at end of file diff --git a/test/output/futureSplom.svg b/test/output/futureSplom.svg index 12ceb16f6c..80da5e457a 100644 --- a/test/output/futureSplom.svg +++ b/test/output/futureSplom.svg @@ -13,24 +13,8 @@ white-space: pre; } - - - - - - - - −1 - 0 - 1 - - - A - - - - - + + @@ -39,31 +23,7 @@ - - - - - - - - - - - - - - - - −1 - 0 - 1 - - - * - - - - + @@ -72,41 +32,7 @@ - - - - - - - - - - - - - - - - −1 - 0 - 1 - - - - - - - - −1 - 0 - 1 - - - * - - - - + @@ -115,27 +41,25 @@ - - - - - - - - - - - * + + + + + + + + - - - - B + + + + + + + + - - - - + @@ -144,7 +68,9 @@ - + + + @@ -153,31 +79,7 @@ - - - - - - - −1 - 0 - 1 - - - * - - - - - - - - - - - - - + @@ -186,21 +88,25 @@ - - * + + + + + + + + - - - - - - - - - - + + + + + + + + - + @@ -209,24 +115,118 @@ - - * + + + + + + + + - - + + + + + + + + + + + + - + + + + −1 + 0 + 1 + + −1 0 1 - + + −1 + 0 + 1 + + + + + + + + + + + + + + + + + + + + + + −1 + 0 + 1 + + + −1 + 0 + 1 + + + −1 + 0 + 1 + + + + + * + + + * + + + * + + + * + + + * + + + * + + + + + A + + + B + + C - + + + + + \ No newline at end of file diff --git a/test/output/googleTrendsRidgeline.svg b/test/output/googleTrendsRidgeline.svg index deb225ade6..c8cee5c6e2 100644 --- a/test/output/googleTrendsRidgeline.svg +++ b/test/output/googleTrendsRidgeline.svg @@ -13,927 +13,811 @@ white-space: pre; } - - + + Australian bushfires - - - - - - - - - - - - - + + Qasem Soleimani - - 2020 - February - March - April - May - June - July - August - September - October - November - December + + Prince Harry, Meghan - - + + Brexit - - + + Impeachment - - + + Kobe Bryant - - - - Qasem Soleimani + + Wuhan - - + + Nancy Pelosi - - + + Parasite - - + + Roger Stone - - - - Prince Harry, Meghan + + Democratic primaries - - + + Love Is Blind - - + + Stock market - - + + Tom Hanks - - - - Brexit + + Trump coronavirus - - + + Locust - - + + Martial law - - + + Toilet paper - - - - Impeachment + + Animal Crossing - - + + Anthony Fauci - - + + COVID-19 - - + + Tiger King - - - - Kobe Bryant + + Zoom Video Communications - - + + Masks - - + + Stimulus check - - + + Sourdough - - - - Wuhan + + The Last Dance - - + + Kim Jong-un - - + + Murder hornet - - + + SpaceX - - - - Nancy Pelosi + + Black Lives Matter - - + + George Floyd - - + + Ghislaine Maxwell - - + + TikTok - - - - Parasite + + Taylor Swift - - + + Hydroxychloroquine - - + + John Lewis - - + + Regis Philbin - - - - Roger Stone + + William Barr - - + + Beirut explosion - - + + Kamala Harris - - + + QAnon - - - - Democratic primaries + + Chadwick Boseman - - + + Hurricane Laura - - + + Jacob Blake - - + + Naomi Osaka - - - - Love Is Blind + + Wildfires - - + + Hurricane Teddy - - + + Ruth Bader Ginsburg - - + + Breonna Taylor - - - - Stock market + + Presidential debates - - + + Proud Boys - - + + Eddie Van Halen - - + + Amy Coney Barrett - - - - Tom Hanks + + Hunter Biden - - + + Absentee ballot - - + + Joe Biden - - + + Voter turnout - - - - Trump coronavirus + + Alex Trebek - - + + Electoral fraud - - + + Four seasons total landscaping - - + + Hurricane Iota - - - - Locust + + Rudy Giuliani - - + + COVID-19 vaccine - - + + + + + + + + + + + + + + + - - + + + + 2020 + February + March + April + May + June + July + August + September + October + November + December - - - Martial law + + + - - + + - - + + - - + + - - - - Toilet paper + + - - + + - - + + - - + + - - - - Animal Crossing + + - - + + - - + + - - + + - - - - Anthony Fauci + + - - + + - - + + - - + + - - - - COVID-19 + + - - + + - - + + - - + + - - - - Tiger King + + - + - - + + - - + + - - - - Zoom Video Communications + + - - + + - - + + - - + + - - - - Masks + + - - + + - - + + - - + + - - - - Stimulus check + + - - + + - - + + - - + + - - - - Sourdough + + - - + + - - + + - - + + - - - - The Last Dance + + - - + + - - + + - - + + - - - - Kim Jong-un + + - - + + - - + + - - + + - - - - Murder hornet + + - - + + - - + + - - + + - - - - SpaceX + + - - + + - - + + - - + + - - - - Black Lives Matter + + - - + + - - + + - - + + - - - - George Floyd + + - - + + - - + + - - + + - - - Ghislaine Maxwell + + + - - + + - - + + - - + + - - - - TikTok + + - - + + - - + + - - + + - - - - Taylor Swift + + - - + + - - + + - - + + - - - - Hydroxychloroquine + + - - + + - - + + - - + + - - - - John Lewis + + - - + + - - + + - - + + - - - - Regis Philbin + + - - + + - - + + - - + + - - - - William Barr + + - - + + - - + + - - + + - - - - Beirut explosion + + - - + + - - + + - - + + - - - - Kamala Harris + + - - + + - - + + - - + + - - - - QAnon + + - - + + - - + + - - + + - - - - Chadwick Boseman + + - - + + - + - - + + - - - - Hurricane Laura + + - - + + - - + + - - + + - - - - Jacob Blake + + - - + + - - + + - - + + - - - - Naomi Osaka + + - - + + - - + + - - + + - - - - Wildfires + + - - + + - - + + - - + + - - - - Hurricane Teddy + + - - + + - - + + - - + + - - - Ruth Bader Ginsburg + + + - - + + - - + + - + - - - - Breonna Taylor + + - - + + - - + + - + - - - - Presidential debates + + - - + + - - + + - + - - - - Proud Boys + + - - + + - - + + - + - - - - Eddie Van Halen + + - - + + - - + + - + - - - - Amy Coney Barrett + + - - + + - - + + - + - - - - Hunter Biden + + - - + + - - + + - + - - - - Absentee ballot + + - - + + - - + + - + - - - - Joe Biden + + - - + + - - + + - + - - - - Voter turnout + + - - + + - - + + - + - - - - Alex Trebek + + - - + + - - + + - + - - - - Electoral fraud + + - - + + - - + + - + - - - - Four seasons total landscaping + + - - + + - - + + - + - - - - Hurricane Iota + + - - + + - - + + - + - - - - Rudy Giuliani + + - - + + - - + + - + - - - - COVID-19 vaccine + + - - + + - - + + - + diff --git a/test/output/heatmapFaceted.svg b/test/output/heatmapFaceted.svg index cb80d84132..f56f816e99 100644 --- a/test/output/heatmapFaceted.svg +++ b/test/output/heatmapFaceted.svg @@ -13,11 +13,24 @@ white-space: pre; } - - + + + cos + + + lin + + + + lin - + + sin + + + + @@ -26,22 +39,7 @@ - - 0 - 2 - 4 - 6 - 8 - 10 - 12 - - - - - - - - + @@ -50,7 +48,18 @@ - + + + + 0 + 2 + 4 + 6 + 8 + 10 + 12 + + 0 2 4 @@ -59,50 +68,49 @@ 10 12 - + + + - + + + + + + + + 0 5 10 - - + + 0 + 5 + 10 - - - - cos + + + - - sin + + - + - - - - - lin - - - - - - - - 0 - 5 - 10 - - + - + + + + + + \ No newline at end of file diff --git a/test/output/hexbinR.html b/test/output/hexbinR.html index 859360ab29..3f7144b913 100644 --- a/test/output/hexbinR.html +++ b/test/output/hexbinR.html @@ -48,38 +48,88 @@ white-space: pre; } - - + + FEMALE - + + MALE + + + + sex + + + - + + + 35 40 45 50 55 - + + + ↑ culmen_length_mm + + + - + + + + + + + + + + + + + + + + 14 + 16 + 18 + 20 + + + 14 + 16 + 18 + 20 + + 14 16 18 20 - - + + + culmen_depth_mm → + + + + + + + + @@ -87,7 +137,25 @@ - + + + + + + + + + + + + + + + + + + + 9 8 8 @@ -155,33 +223,7 @@ 1 1 - - - - MALE - - - - - - - - - 14 - 16 - 18 - 20 - - - - - - - - - - - + 11 9 8 @@ -255,30 +297,7 @@ 1 1 - - - - - - - - - - 14 - 16 - 18 - 20 - - - - - - - - - - - + 2 1 1 @@ -289,13 +308,4 @@ 1 - - sex - - - ↑ culmen_length_mm - - - culmen_depth_mm → - \ No newline at end of file diff --git a/test/output/hexbinText.svg b/test/output/hexbinText.svg index d5ecb24894..d0a0bb0721 100644 --- a/test/output/hexbinText.svg +++ b/test/output/hexbinText.svg @@ -13,38 +13,88 @@ white-space: pre; } - - + + FEMALE - + + MALE + + + + sex + + + - + + + 35 40 45 50 55 - + + + ↑ culmen_length_mm + + + + + + + + + + + + + + + - + + + 14 16 18 20 - - + + 14 + 16 + 18 + 20 + + + 14 + 16 + 18 + 20 + + + + culmen_depth_mm → + + + + + + + + @@ -52,7 +102,25 @@ - + + + + + + + + + + + + + + + + + + + @@ -111,92 +179,7 @@ - - 4 - 5 - 2 - 6 - 1 - 7 - 3 - 2 - 4 - 6 - 3 - 8 - 1 - 7 - 4 - 3 - 1 - 2 - 1 - 2 - 1 - 1 - 5 - 1 - 2 - 2 - 1 - 5 - 1 - 3 - 2 - 1 - 2 - 1 - 2 - 2 - 1 - 1 - 1 - 1 - 4 - 7 - 7 - 3 - 1 - 10 - 3 - 1 - 2 - 4 - 6 - 2 - 3 - 1 - 1 - 1 - 1 - - - - - MALE - - - - - - - - - 14 - 16 - 18 - 20 - - - - - - - - - - - + @@ -260,7 +243,78 @@ - + + + + + + + + + + + + + + 4 + 5 + 2 + 6 + 1 + 7 + 3 + 2 + 4 + 6 + 3 + 8 + 1 + 7 + 4 + 3 + 1 + 2 + 1 + 2 + 1 + 1 + 5 + 1 + 2 + 2 + 1 + 5 + 1 + 3 + 2 + 1 + 2 + 1 + 2 + 2 + 1 + 1 + 1 + 1 + 4 + 7 + 7 + 3 + 1 + 10 + 3 + 1 + 2 + 4 + 6 + 2 + 3 + 1 + 1 + 1 + 1 + + 16 1 5 @@ -324,40 +378,7 @@ 1 1 - - - - - - - - - - 14 - 16 - 18 - 20 - - - - - - - - - - - - - - - - - - - - - + 1 1 2 @@ -368,13 +389,4 @@ 1 - - sex - - - ↑ culmen_length_mm - - - culmen_depth_mm → - \ No newline at end of file diff --git a/test/output/industryUnemploymentTrack.svg b/test/output/industryUnemploymentTrack.svg index d8f26e8554..be40bdb238 100644 --- a/test/output/industryUnemploymentTrack.svg +++ b/test/output/industryUnemploymentTrack.svg @@ -13,11 +13,75 @@ white-space: pre; } - - + + Construction - + + Wholesale and Retail Trade + + + Manufacturing + + + Leisure and hospitality + + + Business services + + + Education and Health + + + Government + + + Self-employed + + + Finance + + + Transportation and Utilities + + + Other + + + Information + + + Agriculture + + + Mining and Extraction + + + + industry + + + + + + + + + + + + + + 2000 + 2002 + 2004 + 2006 + 2008 + 2010 + + + + 745 812 669 @@ -141,18 +205,7 @@ 2,194 2,440 - - 2,440 - - - 384 - - - - - Wholesale and Retail Trade - - + 1,000 1,023 983 @@ -276,18 +329,7 @@ 2,154 2,071 - - 2,154 - - - 701 - - - - - Manufacturing - - + 734 694 739 @@ -411,18 +453,7 @@ 1,918 1,814 - - 2,010 - - - 596 - - - - - Leisure and hospitality - - + 782 779 789 @@ -546,18 +577,7 @@ 1,804 1,597 - - 1,804 - - - 636 - - - - - Business services - - + 655 587 623 @@ -681,18 +701,7 @@ 1,614 1,740 - - 1,740 - - - 504 - - - - - Education and Health - - + 353 349 381 @@ -816,18 +825,7 @@ 1,175 1,200 - - 1,280 - - - 293 - - - - - Government - - + 430 409 311 @@ -951,18 +949,7 @@ 948 880 - - 1,129 - - - 269 - - - - - Self-employed - - + 239 262 213 @@ -1086,18 +1073,7 @@ 730 680 - - 730 - - - 178 - - - - - Finance - - + 228 240 226 @@ -1221,18 +1197,7 @@ 623 708 - - 708 - - - 184 - - - - - Transportation and Utilities - - + 236 223 192 @@ -1356,18 +1321,7 @@ 657 591 - - 657 - - - 129 - - - - - Other - - + 274 232 247 @@ -1491,18 +1445,7 @@ 609 603 - - 609 - - - 161 - - - - - Information - - + 125 112 140 @@ -1626,18 +1569,7 @@ 313 300 - - 373 - - - 77 - - - - - Agriculture - - + 154 173 152 @@ -1761,34 +1693,7 @@ 318 285 - - 318 - - - 35 - - - - - Mining and Extraction - - - - - - - - - - - 2000 - 2002 - 2004 - 2006 - 2008 - 2010 - - + 19 25 17 @@ -1912,14 +1817,93 @@ 68 79 - + + + + 2,440 + + + 2,154 + + + 2,010 + + + 1,804 + + + 1,740 + + + 1,280 + + + 1,129 + + + 730 + + + 708 + + + 657 + + + 609 + + + 373 + + + 318 + + 125 - + + + + 384 + + + 701 + + + 596 + + + 636 + + + 504 + + + 293 + + + 269 + + + 178 + + + 184 + + + 129 + + + 161 + + + 77 + + + 35 + + 2 - - industry - \ No newline at end of file diff --git a/test/output/internFacetDate.svg b/test/output/internFacetDate.svg index c33b17fdc9..d5cd69f212 100644 --- a/test/output/internFacetDate.svg +++ b/test/output/internFacetDate.svg @@ -13,32 +13,166 @@ white-space: pre; } - - + + 1950 - + + 1960 + + + 1970 + + + 1980 + + + 1990 + + + 2000 + + + + date_of_birth + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + 1.4 + 1.6 + 1.8 + 2.0 + 2.2 + + + 1.4 + 1.6 + 1.8 + 2.0 + 2.2 + + + 1.4 + 1.6 + 1.8 + 2.0 + 2.2 + + + 1.4 + 1.6 + 1.8 + 2.0 + 2.2 + + + 1.4 + 1.6 + 1.8 + 2.0 + 2.2 + + 1.4 1.6 1.8 2.0 2.2 - + + + ↑ height + + + @@ -47,43 +181,43 @@ - - - - - - - - - - - - - - 1960 + + + + + + + + - - - - - - + + + + + + + + - - - - - - + + + + + + + + - - 1.4 - 1.6 - 1.8 - 2.0 - 2.2 + + + + + + + + - + @@ -92,7 +226,44 @@ - + + + + + + + + + + + + + + + 40 + 60 + 80 + 100 + 120 + 140 + 160 + + + + weight → + + + + + + + + + + + + + @@ -167,42 +338,7 @@ - - - - 1970 - - - - - - - - - - - - - - - - - 1.4 - 1.6 - 1.8 - 2.0 - 2.2 - - - - - - - - - - - + @@ -590,42 +726,7 @@ - - - - 1980 - - - - - - - - - - - - - - - - - 1.4 - 1.6 - 1.8 - 2.0 - 2.2 - - - - - - - - - - - + @@ -5308,42 +5409,7 @@ - - - - 1990 - - - - - - - - - - - - - - - - - 1.4 - 1.6 - 1.8 - 2.0 - 2.2 - - - - - - - - - - - + @@ -11002,60 +11068,7 @@ - - - - 2000 - - - - - - - - - - - - - - - - - 1.4 - 1.6 - 1.8 - 2.0 - 2.2 - - - - - - - - - - - - - - - - - - - - - 40 - 60 - 80 - 100 - 120 - 140 - 160 - - + @@ -11111,13 +11124,4 @@ - - date_of_birth - - - ↑ height - - - weight → - \ No newline at end of file diff --git a/test/output/internFacetNaN.svg b/test/output/internFacetNaN.svg index 72f35f0da8..8a70c3402c 100644 --- a/test/output/internFacetNaN.svg +++ b/test/output/internFacetNaN.svg @@ -13,100 +13,312 @@ white-space: pre; } - - + + 1.2 - - - + + 1.3 - - female - male + + 1.4 - - + + 1.5 - - + + 1.6 - - + + 1.7 + + + 1.8 + + + 1.9 + + + 2 + + + 2.1 + + + 2.2 - - - 1.3 + + height + + + + + - + - - female - male + + + - - + + + - - + + + - - + + + - - - - 1.4 + + + - + - + + + + + + + + + + + + + + + + + + + female male - - - + + female + male - - - + + female + male - - - + + female + male - - - - 1.5 + + female + male - - - + + female + male + + + female + male + + + female + male + + + female + male + + + female + male + + + female + male - + female male - + + + sex + + + + + + + + + + + + + + + 40 + 60 + 80 + 100 + 120 + 140 + 160 + + + + weight → + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -124,32 +336,7 @@ - - - - 1.6 - - - - - - - female - male - - - - - - - - - - - - - - + @@ -211,32 +398,7 @@ - - - - 1.7 - - - - - - - female - male - - - - - - - - - - - - - - + @@ -363,32 +525,7 @@ - - - - 1.8 - - - - - - - female - male - - - - - - - - - - - - - - + @@ -529,32 +666,7 @@ - - - - 1.9 - - - - - - - female - male - - - - - - - - - - - - - - + @@ -612,32 +724,7 @@ - - - - 2 - - - - - - - female - male - - - - - - - - - - - - - - + @@ -655,101 +742,8 @@ - - - - 2.1 - - - - - - - female - male - - - - - - - - - - - - - - 2.2 - - - - - - - female - male - - - - - - - - - - - - - - - - - - female - male - - - - - - - - - - - - 40 - 60 - 80 - 100 - 120 - 140 - 160 - - - - - - - - - - - - - - + - - height - - - sex - - - weight → - \ No newline at end of file diff --git a/test/output/metroUnemploymentRidgeline.svg b/test/output/metroUnemploymentRidgeline.svg index 48d7415254..a917bf624c 100644 --- a/test/output/metroUnemploymentRidgeline.svg +++ b/test/output/metroUnemploymentRidgeline.svg @@ -13,651 +13,573 @@ white-space: pre; } - - + + Detroit-Livonia-Dearborn, MI Met Div - - + + Detroit-Warren-Livonia, MI MSA - - + + Warren-Troy-Farmington Hills, MI Met Div - - + + Lawrence-Methuen-Salem, MA-NH NECTA Div - - - - Detroit-Warren-Livonia, MI MSA + + Los Angeles-Long Beach-Glendale, CA Met Div - - + + Miami-Miami Beach-Kendall, FL Met Div - - + + Los Angeles-Long Beach-Santa Ana, CA MSA - - + + Lake County-Kenosha County, IL-WI Met Div - - - - Warren-Troy-Farmington Hills, MI Met Div + + West Palm Beach-Boca Raton-Boynton Beach, FL Met Div - - + + Chicago-Joliet-Naperville, IL Met Div - - + + Chicago-Joliet-Naperville, IL-IN-WI MSA - - + + Miami-Fort Lauderdale-Pompano Beach, FL MSA - - - - Lawrence-Methuen-Salem, MA-NH NECTA Div + + Oakland-Fremont-Hayward, CA Met Div - - + + Gary, IN Met Div - - + + Tacoma, WA Met Div - - + + San Francisco-Oakland-Fremont, CA MSA - - - - Los Angeles-Long Beach-Glendale, CA Met Div + + Camden, NJ Met Div - - + + Brockton-Bridgewater-Easton, MA NECTA Div - - + + Seattle-Tacoma-Bellevue, WA MSA - - + + Fort Lauderdale-Pompano Beach-Deerfield Beach, FL Met Div - - - - Miami-Miami Beach-Kendall, FL Met Div + + New York-White Plains-Wayne, NY-NJ Met Div - - + + Santa Ana-Anaheim-Irvine, CA Met Div - - + + Seattle-Bellevue-Everett, WA Met Div - - + + Newark-Union, NJ-PA Met Div - - - - Los Angeles-Long Beach-Santa Ana, CA MSA + + Taunton-Norton-Raynham, MA NECTA Div - - + + Lowell-Billerica-Chelmsford, MA-NH NECTA Div - - + + New York-Northern New Jersey-Long Island, NY-NJ-PA MSA - - + + San Francisco-San Mateo-Redwood City, CA Met Div - - - - Lake County-Kenosha County, IL-WI Met Div + + Edison-New Brunswick, NJ Met Div - - + + Philadelphia-Camden-Wilmington, PA-NJ-DE-MD MSA - - + + Wilmington, DE-MD-NJ Met Div - - + + Peabody, MA NECTA Div - - - - West Palm Beach-Boca Raton-Boynton Beach, FL Met Div + + Philadelphia, PA Met Div - - + + Fort Worth-Arlington, TX Met Div - - + + Dallas-Fort Worth-Arlington, TX MSA - - + + Dallas-Plano-Irving, TX Met Div - - - - Chicago-Joliet-Naperville, IL Met Div + + Haverhill-North Andover-Amesbury, MA-NH NECTA Div - - + + Boston-Cambridge-Quincy, MA-NH Met NECTA - - + + Boston-Cambridge-Quincy, MA NECTA Div - - + + Nassau-Suffolk, NY Met Div - - - - Chicago-Joliet-Naperville, IL-IN-WI MSA + + Framingham, MA NECTA Div - - + + Nashua, NH-MA NECTA Div - - + + Washington-Arlington-Alexandria, DC-VA-MD-WV Met Div - - + + Washington-Arlington-Alexandria, DC-VA-MD-WV MSA + + + Bethesda-Rockville-Frederick, MD Met Div - - - Miami-Fort Lauderdale-Pompano Beach, FL MSA + + + + + + + + + - - + + + + 2000 + 2002 + 2004 + 2006 + 2008 + 2010 + 2012 - - + + + + - - + + - - - - Oakland-Fremont-Hayward, CA Met Div + + - - + + - - + + - - + + - - - - Gary, IN Met Div + + - - + + - - + + - - + + - - - - Tacoma, WA Met Div + + - - + + - - + + - - + + - - - - San Francisco-Oakland-Fremont, CA MSA + + - + - - - - - + + - - - - Camden, NJ Met Div + + - - + + - - + + - - + + - - - - Brockton-Bridgewater-Easton, MA NECTA Div + + - - + + - - + + - - + + - - - - Seattle-Tacoma-Bellevue, WA MSA + + - - + + - - + + - - + + - - - - Fort Lauderdale-Pompano Beach-Deerfield Beach, FL Met Div + + - - + + - - + + - - + + - - - - New York-White Plains-Wayne, NY-NJ Met Div + + - - + + - - + + - - + + - - - - Santa Ana-Anaheim-Irvine, CA Met Div + + - - + + - - + + - - + + - - - - Seattle-Bellevue-Everett, WA Met Div + + - - + + - - + + - - + + - - - Newark-Union, NJ-PA Met Div + + + - - + + - - + + - - + + - - - - Taunton-Norton-Raynham, MA NECTA Div + + - - + + - - + + - - + + - - - - Lowell-Billerica-Chelmsford, MA-NH NECTA Div + + - - + + - - + + - - + + - - - - New York-Northern New Jersey-Long Island, NY-NJ-PA MSA + + - - + + - - + + - - + + - - - - San Francisco-San Mateo-Redwood City, CA Met Div + + - - + + - - + + - - + + - - - - Edison-New Brunswick, NJ Met Div + + - - + + - - + + - - + + - - - - Philadelphia-Camden-Wilmington, PA-NJ-DE-MD MSA + + - - + + - - + + - - + + - - - - Wilmington, DE-MD-NJ Met Div + + - - + + - + - - + + - - - - Peabody, MA NECTA Div + + - - + + - - + + - - + + - - - - Philadelphia, PA Met Div + + - - + + - - + + - - + + - - - - Fort Worth-Arlington, TX Met Div + + - - + + - - + + - - + + - - - - Dallas-Fort Worth-Arlington, TX MSA + + - - + + + + - - + + - + - - - - Dallas-Plano-Irving, TX Met Div + + - - + + - - + + - + - - - - Haverhill-North Andover-Amesbury, MA-NH NECTA Div + + - - + + - - + + - + - - - - Boston-Cambridge-Quincy, MA-NH Met NECTA + + - - + + - - + + - + - - - - Boston-Cambridge-Quincy, MA NECTA Div + + - - + + - - + + - + - - - - Nassau-Suffolk, NY Met Div + + - - + + - - + + - + - - - - Framingham, MA NECTA Div + + - - + + - - + + - + - - - - Nashua, NH-MA NECTA Div + + - - + + - - + + - + - - - - Washington-Arlington-Alexandria, DC-VA-MD-WV Met Div + + - - + + - - + + - + - - - - Washington-Arlington-Alexandria, DC-VA-MD-WV MSA + + - - + + - - + + - + - - - - Bethesda-Rockville-Frederick, MD Met Div + + - - - - - - - - + + - - 2000 - 2002 - 2004 - 2006 - 2008 - 2010 - 2012 + + - - + + - - + + - + diff --git a/test/output/mobyDickFaceted.svg b/test/output/mobyDickFaceted.svg index e492f99d5d..f6172d5e69 100644 --- a/test/output/mobyDickFaceted.svg +++ b/test/output/mobyDickFaceted.svg @@ -13,11 +13,24 @@ white-space: pre; } - - + + + lower + + + upper + + + + consonant - + + vowel + + + + @@ -26,52 +39,25 @@ - - - - - - - - - - - 0 - 200 - 400 - 600 - 800 - 1,000 - 1,200 - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + - + + + + + + + - - - + @@ -80,7 +66,18 @@ - + + + + + + + + + + + + @@ -89,7 +86,18 @@ - + + + + 0 + 200 + 400 + 600 + 800 + 1,000 + 1,200 + + 0 200 400 @@ -98,7 +106,12 @@ 1,000 1,200 - + + + ↑ Frequency + + + @@ -126,98 +139,7 @@ - - A - B - C - D - E - F - G - H - I - J - K - L - M - N - O - P - Q - R - S - T - U - V - W - X - Y - Z - - - - - - - - - - - - - - - - - - - - - - - - - - lower - - - vowel - - - - - - - - - - - - - - - - - - - - - - - - - upper - - - - - - - - - - - + @@ -245,7 +167,9 @@ - + + + A B C @@ -273,7 +197,85 @@ Y Z - + + A + B + C + D + E + F + G + H + I + J + K + L + M + N + O + P + Q + R + S + T + U + V + W + X + Y + Z + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -281,11 +283,19 @@ - + + + + + + + + + + + + - - - ↑ Frequency \ No newline at end of file diff --git a/test/output/moviesRatingByGenre.svg b/test/output/moviesRatingByGenre.svg index 52a72a4b4c..a25990176e 100644 --- a/test/output/moviesRatingByGenre.svg +++ b/test/output/moviesRatingByGenre.svg @@ -13,14 +13,220 @@ white-space: pre; } - - + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Documentary - + + Black Comedy + + + Drama + + + Musical + + + Western + + + N/A + + + Adventure + + + Thriller/Suspense + + + Action + + + Concert/Performance + + + Comedy + + + Romantic Comedy + + + Horror + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -33,7 +239,35 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -46,7 +280,9 @@ - + + + 0 1 2 @@ -59,7 +295,12 @@ 9 10 - + + + IMDB Rating → + + + @@ -98,28 +339,7 @@ - - - - - - - Black Comedy - - - - - - - - - - - - - - - + @@ -153,28 +373,7 @@ - - - - - - - Drama - - - - - - - - - - - - - - - + @@ -914,28 +1113,7 @@ - - - - - - - Musical - - - - - - - - - - - - - - - + @@ -987,28 +1165,7 @@ - - - - - - - Western - - - - - - - - - - - - - - - + @@ -1045,28 +1202,7 @@ - - - - - - - N/A - - - - - - - - - - - - - - - + @@ -1310,28 +1446,7 @@ - - - - - - - Adventure - - - - - - - - - - - - - - - + @@ -1584,28 +1699,7 @@ - - - - - - - Thriller/Suspense - - - - - - - - - - - - - - - + @@ -1840,28 +1934,7 @@ - - - - - - - Action - - - - - - - - - - - - - - - + @@ -2255,55 +2328,13 @@ - - - - - - - Concert/Performance - - - - - - - - - - - - - - - + - - - - - - - Comedy - - - - - - - - - - - - - - - + @@ -2940,28 +2971,7 @@ - - - - - - - Romantic Comedy - - - - - - - - - - - - - - - + @@ -3093,28 +3103,7 @@ - - - - - - - Horror - - - - - - - - - - - - - - - + @@ -3326,7 +3315,4 @@ - - IMDB Rating → - \ No newline at end of file diff --git a/test/output/multiplicationTable.svg b/test/output/multiplicationTable.svg index bf541780b0..26ecedd248 100644 --- a/test/output/multiplicationTable.svg +++ b/test/output/multiplicationTable.svg @@ -13,803 +13,689 @@ white-space: pre; } - - + + - - 2 + + - - + + - - 2 + + - - + + - - + + - - 4 + + - - - + - + + + + 2 + + 3 - - + + 4 - - + + 5 - - 6 + + 6 - - - - + + 7 - - 4 + + 8 - - + + 9 - - + + + + - - 8 + + - - - - + + - - 5 + + - - + + - - + + - - 10 + + + + + - - - + + + 2 - - 6 + + 3 - - + + 4 - - + + 5 - - 12 + + 6 - - - - + + 7 - - 7 + + 8 - - + + 9 - - + + + + - - 14 + + - - - - + + - - 8 + + + + + - + + + + - - + + - - 16 + + - - - - + + - - 9 + + - - + + - - + + - - 18 + + - - - - + + - - 3 + + - + - - + + - - 6 + + - - - - + + - - + + - - 9 + + - - - - + + - - + + - - 12 + + - - - - + + - - + + - - 15 + + - - - + - - + + - - 18 + + - - - - + + - - + + - - 21 + + - - - - + + - - + + - - 24 + + - - - + + + + + + + - - + + - - 27 + + - - - - + + - - 4 + + - - + + - - + + - - 8 + + - - - - + + - - + + - - 12 + + - - - + - - + + - - 16 + + - - - - + + - - + + - - 20 + + - - - - + + - - + + - - 24 + + - - - - + + - - + + - - 28 + + - - - + - - - - - 32 + + - - - + + + - - + + - - 36 + + - - - - + + - - 5 + + - - + + - - + + - - 10 + + - - - - + + - - + + - - 15 + + - - - - + + - - + + - - 20 + + - - - - + + - - + + - - 25 + + - - - - + + - - + + - - 30 + + - - - - + + - - + + - - 35 + + - - - - + + - + - - 40 + + - - - - + + - + - - 45 + + - - - - + + - - 6 + + - - + + - + - - 12 - - - - - - - + - - 18 + + - - - - + + - + - - 24 + + - - - - + + - + - - 30 + + - - - - + + - - + + - - 36 + + - - - - + + - - + + - - 42 + + - - - - + + - - + + - - 48 + + - - - - + + - - + + - - 54 + + - - - - + + - - 7 + + - - + + - - + + - - 14 + + - - - - + + - - + + - - 21 + + - - - - + + - - + + - - 28 + + - - - - - - + + + 4 - - 35 + + 6 - - - - + + 8 - - + + 10 - - 42 + + 12 - - - - + + 14 - - + + 16 - - 49 + + 18 - - - - + + 6 - - + + 9 - - 56 + + 12 - - - - + + 15 - - + + 18 - - 63 + + 21 - - - - + + 24 - - 8 + + 27 - - + + 8 - - + + 12 - + 16 - - - - - - - + + 20 - + 24 - - - - - - - + + 28 - + 32 - - - - + + 36 - - + + 10 - - 40 + + 15 - - - - + + 20 - - + + 25 - - 48 + + 30 - - - - + + 35 - - + + 40 - - 56 + + 45 - - - - + + 12 - - + + 18 - - 64 + + 24 - - - - + + 30 - - + + 36 - - 72 + + 42 - - - - + + 48 - - 9 + + 54 - - + + 14 - - + + 21 - - 18 + + 28 - - - - + + 35 - - + + 42 - - 27 + + 49 - - - - + + 56 - - + + 63 - - 36 + + 16 - - - - + + 24 - - + + 32 - - 45 + + 40 - - - - + + 48 - - + + 56 - - 54 + + 64 - - - - + + 72 - - + + 18 - - 63 + + 27 - - - - + + 36 - - + + 45 - - 72 + + 54 - - - - + + 63 - - + + 72 - + 81 diff --git a/test/output/penguinCulmen.svg b/test/output/penguinCulmen.svg index bd23be06d3..7f5d472c71 100644 --- a/test/output/penguinCulmen.svg +++ b/test/output/penguinCulmen.svg @@ -13,37 +13,223 @@ white-space: pre; } - - + + + Adelie + + + Chinstrap + + + Gentoo + + + + species + + + FEMALE - + + MALE + + + + sex + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + - + + + + 35 + 40 + 45 + 50 + 55 + + + 35 + 40 + 45 + 50 + 55 + + 35 40 45 50 55 - + + + ↑ culmen_length_mm + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + 15 + 20 + + + 15 + 20 + + + 15 + 20 + + + 15 + 20 + + + + culmen_depth_mm → + + + + + + + + + + + + + @@ -314,110 +500,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 35 - 40 - 45 - 50 - 55 - - - - - - - + @@ -727,79 +810,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 35 - 40 - 45 - 50 - 55 - - - - - - - - - - - 15 - 20 - - - + @@ -1085,84 +1096,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - MALE - - - - - - - - - - - - - - + @@ -1433,96 +1367,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -1832,65 +1677,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 15 - 20 - - - + @@ -2173,95 +1960,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Adelie - - - - - - - - - - - - - - - - - - 15 - 20 - - - + @@ -2600,44 +2299,7 @@ - - - - - - - - - - - Chinstrap - - - - - Gentoo - - - - - - - - - - - - - - - - - - 15 - 20 - - - + @@ -2977,23 +2639,365 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - species - - - sex - - - ↑ culmen_length_mm - - - culmen_depth_mm → - \ No newline at end of file diff --git a/test/output/penguinCulmenArray.svg b/test/output/penguinCulmenArray.svg index 0e462164d8..bdc90ba8ba 100644 --- a/test/output/penguinCulmenArray.svg +++ b/test/output/penguinCulmenArray.svg @@ -13,36 +13,207 @@ white-space: pre; } - - + + + Adelie + + + Chinstrap + + + Gentoo + + + + species + + + FEMALE - + + MALE + + + + sex + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + - + + + + 35 + 40 + 45 + 50 + 55 + + + 35 + 40 + 45 + 50 + 55 + + 35 40 45 50 55 - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + 15 + 20 + + + 15 + 20 + + + 15 + 20 + + + 15 + 20 + + + + @@ -386,109 +557,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 35 - 40 - 45 - 50 - 55 - - - - - - + @@ -832,78 +901,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 35 - 40 - 45 - 50 - 55 - - - - - - - - - - - 15 - 20 - - + @@ -1247,83 +1245,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - MALE - - - - - - - - - - - - - + @@ -1667,95 +1589,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -2099,64 +1933,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 15 - 20 - - + @@ -2500,94 +2277,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Adelie - - - - - - - - - - - - - - - - - - 15 - 20 - - + @@ -2931,43 +2621,7 @@ - - - - - - - - - - - Chinstrap - - - - - Gentoo - - - - - - - - - - - - - - - - - - 15 - 20 - - + @@ -3311,17 +2965,365 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - species - - - sex - \ No newline at end of file diff --git a/test/output/penguinCulmenMarkFacet.svg b/test/output/penguinCulmenMarkFacet.svg index dcf4a57c7c..1ef8750daa 100644 --- a/test/output/penguinCulmenMarkFacet.svg +++ b/test/output/penguinCulmenMarkFacet.svg @@ -13,26 +13,131 @@ white-space: pre; } - - + + + Adelie + + + Chinstrap + + + Gentoo + + + + species + + + FEMALE - + + MALE + + + + sex + + + + + + + + + + + + + + + + + - + + + + 35 + 40 + 45 + 50 + 55 + + + 35 + 40 + 45 + 50 + 55 + + 35 40 45 50 55 - - + + + ↑ culmen_length_mm + + + + + + + + + + + + + + + + + + + + + + 15 + 20 + + + 15 + 20 + + + 15 + 20 + + + 15 + 20 + + + + culmen_depth_mm → + + + + + + + + + + + + + @@ -303,99 +408,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 35 - 40 - 45 - 50 - 55 - - - + @@ -705,68 +718,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 35 - 40 - 45 - 50 - 55 - - - - - - - 15 - 20 - - - + @@ -1052,73 +1004,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - MALE - - - + @@ -1389,85 +1275,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -1777,54 +1585,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 15 - 20 - - - + @@ -2107,84 +1868,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Adelie - - - - - - - 15 - 20 - - - + @@ -2523,33 +2207,7 @@ - - - - - - - - - - - Chinstrap - - - - - Gentoo - - - - - - - 15 - 20 - - - + @@ -2889,23 +2547,365 @@ - - - - - - - - - species - - - sex - - - ↑ culmen_length_mm - - culmen_depth_mm → + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/output/penguinDensityFill.html b/test/output/penguinDensityFill.html index d7a8fb6070..fdbb5e2e35 100644 --- a/test/output/penguinDensityFill.html +++ b/test/output/penguinDensityFill.html @@ -48,35 +48,80 @@ white-space: pre; } - - + + Biscoe - + + Dream + + + Torgersen + + + + island + + + - + + + 35 40 45 50 55 - + + + ↑ culmen_length_mm + + + + + + + + + + + + + - + + + + 180 + 200 + 220 + + + 180 + 200 + 220 + + 180 200 220 - + + + flipper_length_mm → + + + @@ -92,23 +137,7 @@ - - - - - Dream - - - - - - - - 180 - 200 - 220 - - + @@ -124,23 +153,7 @@ - - - - - Torgersen - - - - - - - - 180 - 200 - 220 - - + @@ -156,15 +169,10 @@ - - - island - - - ↑ culmen_length_mm - - - flipper_length_mm → + + + + \ No newline at end of file diff --git a/test/output/penguinDensityZ.html b/test/output/penguinDensityZ.html index d449140b3e..fc1ed9a03b 100644 --- a/test/output/penguinDensityZ.html +++ b/test/output/penguinDensityZ.html @@ -46,35 +46,80 @@ white-space: pre; } - - + + Biscoe - + + Dream + + + Torgersen + + + + island + + + - + + + 35 40 45 50 55 - + + + ↑ culmen_length_mm + + + + + + + + + + + + + - + + + + 180 + 200 + 220 + + + 180 + 200 + 220 + + 180 200 220 - + + + flipper_length_mm → + + + @@ -99,23 +144,7 @@ Gentoo - - - - - Dream - - - - - - - - 180 - 200 - 220 - - + @@ -140,23 +169,7 @@ Chinstrap - - - - - Torgersen - - - - - - - - 180 - 200 - 220 - - + @@ -172,16 +185,11 @@ Adelie - - - island - - - ↑ culmen_length_mm - - - flipper_length_mm → + + + + \ No newline at end of file diff --git a/test/output/penguinDodgeHexbin.svg b/test/output/penguinDodgeHexbin.svg index a6282aa416..bb85609357 100644 --- a/test/output/penguinDodgeHexbin.svg +++ b/test/output/penguinDodgeHexbin.svg @@ -13,11 +13,37 @@ white-space: pre; } - - + + Adelie - + + Chinstrap + + + Gentoo + + + + + + + + + + + + + + + + + + + + + + @@ -26,8 +52,39 @@ - - + + + + + + + + + + + + + + + 3,000 + 3,500 + 4,000 + 4,500 + 5,000 + 5,500 + 6,000 + + + + body_mass_g → + + + + + + + + @@ -180,7 +237,204 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -332,92 +586,7 @@ - - - - Chinstrap - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -487,165 +656,7 @@ - - - - Gentoo - - - - - - - - - - - - - - - - - - - - - 3,000 - 3,500 - 4,000 - 4,500 - 5,000 - 5,500 - 6,000 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -771,7 +782,4 @@ - - body_mass_g → - \ No newline at end of file diff --git a/test/output/penguinFacetAnnotated.svg b/test/output/penguinFacetAnnotated.svg index 4d83f42650..15761c8856 100644 --- a/test/output/penguinFacetAnnotated.svg +++ b/test/output/penguinFacetAnnotated.svg @@ -13,67 +13,59 @@ white-space: pre; } - - + + Biscoe - + + Dream + + + Torgersen + + + + island + + + - - Adelie - Chinstrap - Gentoo - - - - - - - - - - - - - Dream + + + + - + - + + + Adelie Chinstrap Gentoo - - - - - - - - - - - - Torgersen - - - - - + + Adelie + Chinstrap + Gentoo - + Adelie Chinstrap Gentoo - + + + species + + + @@ -82,7 +74,9 @@ - + + + 0 20 40 @@ -91,23 +85,39 @@ 100 120 - - + + + Frequency → + + + + + + + + + + + + + + + + + + + + + + - + + + Torgersen Island only has Adelie penguins! - - island - - - species - - - Frequency → - \ No newline at end of file diff --git a/test/output/penguinFacetAnnotatedX.svg b/test/output/penguinFacetAnnotatedX.svg index 964c378852..3b1c380543 100644 --- a/test/output/penguinFacetAnnotatedX.svg +++ b/test/output/penguinFacetAnnotatedX.svg @@ -13,93 +13,103 @@ white-space: pre; } - - + + Biscoe - + + Dream + + + Torgersen + + + + island + + + - + + + Adelie Chinstrap Gentoo - + + + species + + + + + + + + - + + + + + + + + + 0 + 50 + 100 + + 0 50 100 - - + + 0 + 50 + 100 + + + + Frequency → + + + + + + + + - - - - Dream - - - - - - - - 0 - 50 - 100 - - - + - - - - Torgersen - - - - - - - - 0 - 50 - 100 - - - + - + + + Torgersen Islandonly has Adeliepenguins! - - island - - - species - - - Frequency → - \ No newline at end of file diff --git a/test/output/penguinFacetDodge.svg b/test/output/penguinFacetDodge.svg index 8f4ff6ae84..3cd648a002 100644 --- a/test/output/penguinFacetDodge.svg +++ b/test/output/penguinFacetDodge.svg @@ -13,11 +13,37 @@ white-space: pre; } - - + + Adelie - + + Chinstrap + + + Gentoo + + + + + + + + + + + + + + + + + + + + + + @@ -26,7 +52,34 @@ - + + + + + + + + + + + + + + + 3,000 + 3,500 + 4,000 + 4,500 + 5,000 + 5,500 + 6,000 + + + + body_mass_g → + + + @@ -179,21 +232,7 @@ - - - - Chinstrap - - - - - - - - - - - + @@ -263,39 +302,7 @@ - - - - Gentoo - - - - - - - - - - - - - - - - - - - - - 3,000 - 3,500 - 4,000 - 4,500 - 5,000 - 5,500 - 6,000 - - + @@ -421,7 +428,4 @@ - - body_mass_g → - \ No newline at end of file diff --git a/test/output/penguinFacetDodgeIdentity.svg b/test/output/penguinFacetDodgeIdentity.svg index d92d6064f4..b53dae2ae0 100644 --- a/test/output/penguinFacetDodgeIdentity.svg +++ b/test/output/penguinFacetDodgeIdentity.svg @@ -13,11 +13,37 @@ white-space: pre; } - - + + Adelie - + + Chinstrap + + + Gentoo + + + + + + + + + + + + + + + + + + + + + + @@ -26,7 +52,34 @@ - + + + + + + + + + + + + + + + 3,000 + 3,500 + 4,000 + 4,500 + 5,000 + 5,500 + 6,000 + + + + body_mass_g → + + + @@ -179,21 +232,7 @@ - - - - Chinstrap - - - - - - - - - - - + @@ -263,39 +302,7 @@ - - - - Gentoo - - - - - - - - - - - - - - - - - - - - - 3,000 - 3,500 - 4,000 - 4,500 - 5,000 - 5,500 - 6,000 - - + @@ -421,7 +428,4 @@ - - body_mass_g → - \ No newline at end of file diff --git a/test/output/penguinFacetDodgeIsland.html b/test/output/penguinFacetDodgeIsland.html index 719b274dec..165676cae9 100644 --- a/test/output/penguinFacetDodgeIsland.html +++ b/test/output/penguinFacetDodgeIsland.html @@ -46,11 +46,37 @@ white-space: pre; } - - + + Adelie - + + Chinstrap + + + Gentoo + + + + + + + + + + + + + + + + + + + + + + @@ -59,7 +85,34 @@ - + + + + + + + + + + + + + + + 3,000 + 3,500 + 4,000 + 4,500 + 5,000 + 5,500 + 6,000 + + + + body_mass_g → + + + @@ -212,21 +265,7 @@ - - - - Chinstrap - - - - - - - - - - - + @@ -296,39 +335,7 @@ - - - - Gentoo - - - - - - - - - - - - - - - - - - - - - 3,000 - 3,500 - 4,000 - 4,500 - 5,000 - 5,500 - 6,000 - - + @@ -454,8 +461,5 @@ - - body_mass_g → - \ No newline at end of file diff --git a/test/output/penguinMassSex.svg b/test/output/penguinMassSex.svg index 065f9882de..8c26390e62 100644 --- a/test/output/penguinMassSex.svg +++ b/test/output/penguinMassSex.svg @@ -13,11 +13,19 @@ white-space: pre; } - - + + FEMALE - + + MALE + + + + sex + + + @@ -25,31 +33,15 @@ - - 0 - 10 - 20 - 30 - 40 - 50 - - - - - - - - - - - - - - - - MALE + + + + + + + - + @@ -57,7 +49,9 @@ - + + + 0 10 20 @@ -65,29 +59,15 @@ 40 50 - - - - - - - - - - - - - - - - - - - - - + + 0 + 10 + 20 + 30 + 40 + 50 - + 0 10 20 @@ -95,7 +75,12 @@ 40 50 - + + + ↑ Frequency + + + @@ -106,7 +91,9 @@ - + + + 2,500 3,000 3,500 @@ -117,24 +104,45 @@ 6,000 6,500 - + + + Body mass (g) → + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + - - - sex - - - ↑ Frequency - - - Body mass (g) → \ No newline at end of file diff --git a/test/output/penguinMassSexSpecies.svg b/test/output/penguinMassSexSpecies.svg index 4bdc3c636e..4f1ba5d920 100644 --- a/test/output/penguinMassSexSpecies.svg +++ b/test/output/penguinMassSexSpecies.svg @@ -13,192 +13,190 @@ white-space: pre; } - - + + + Adelie + + + Chinstrap + + + Gentoo + + + + species + + + FEMALE - + + MALE + + + + sex + + + - - 0 - 10 - 20 - 30 - 40 - - - - - - - - + + + + + + - - - + - + + + 0 10 20 30 40 - - - - - - - - - - - - - - - - - + + 0 + 10 + 20 + 30 + 40 - + 0 10 20 30 40 - + + + ↑ Frequency + + + - + + + + + + + + + + + + + + + + 4,000 + 6,000 + + + 4,000 + 6,000 + + + 4,000 + 6,000 + + 4,000 6,000 - + + + Body mass (g) → + + + + + + + + + + + + + + - - - - - - - MALE - - + - - - - - - + - - - - - - - - - - - 4,000 - 6,000 - - + - - - - - - - Adelie - - - - - - - 4,000 - 6,000 - - + - - + + + - - - Chinstrap + + + - - - - Gentoo + + - - - + + - - 4,000 - 6,000 + + - - - + + - + + + + + + + - - - species - - - sex - - - ↑ Frequency - - - Body mass (g) → \ No newline at end of file diff --git a/test/output/penguinSexMassCulmenSpecies.svg b/test/output/penguinSexMassCulmenSpecies.svg index 2d8921d854..2fbe4e8e3b 100644 --- a/test/output/penguinSexMassCulmenSpecies.svg +++ b/test/output/penguinSexMassCulmenSpecies.svg @@ -13,11 +13,49 @@ white-space: pre; } - - + + FEMALE - + + MALE + + + + sex + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -32,7 +70,9 @@ - + + + @@ -47,7 +87,9 @@ - + + + 34 36 38 @@ -62,7 +104,30 @@ 56 58 - + + + ↑ culmen_length_mm + + + + + + + + + + + + + + + + + + + + + @@ -71,7 +136,27 @@ - + + + + + + + + + + + + + + + + + + + + + @@ -80,7 +165,9 @@ - + + + 3k 3.5k 4k @@ -89,8 +176,35 @@ 5.5k 6k - - + + 3k + 3.5k + 4k + 4.5k + 5k + 5.5k + 6k + + + 3k + 3.5k + 4k + 4.5k + 5k + 5.5k + 6k + + + + body_mass_g → + + + + + + + + @@ -137,55 +251,7 @@ - - - - MALE - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 3k - 3.5k - 4k - 4.5k - 5k - 5.5k - 6k - - - + @@ -229,52 +295,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 3k - 3.5k - 4k - 4.5k - 5k - 5.5k - 6k - - - + @@ -285,13 +306,4 @@ - - sex - - - ↑ culmen_length_mm - - - body_mass_g → - \ No newline at end of file diff --git a/test/output/penguinSpeciesIslandRelative.svg b/test/output/penguinSpeciesIslandRelative.svg index e4e0a94f6f..186534e203 100644 --- a/test/output/penguinSpeciesIslandRelative.svg +++ b/test/output/penguinSpeciesIslandRelative.svg @@ -13,14 +13,33 @@ white-space: pre; } - - + + - + + + + + + + + + Adelie - + + Chinstrap + + + Gentoo + + + + species + + + @@ -33,7 +52,9 @@ - + + + 0 10 20 @@ -46,47 +67,32 @@ 90 100 - + + + ↑ Frequency (%) + + + - - - - - - - - - - Chinstrap - - + - - + + - - - - - - Gentoo + + + - - + + - + - - species - - - ↑ Frequency (%) - \ No newline at end of file diff --git a/test/output/penguinSpeciesIslandSex.svg b/test/output/penguinSpeciesIslandSex.svg index 4eb7e4d6d0..507726a790 100644 --- a/test/output/penguinSpeciesIslandSex.svg +++ b/test/output/penguinSpeciesIslandSex.svg @@ -13,11 +13,22 @@ white-space: pre; } - - + + Adelie - + + Chinstrap + + + Gentoo + + + + species + + + @@ -34,7 +45,43 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -51,7 +98,9 @@ - + + + 0 5 10 @@ -68,17 +117,49 @@ 65 70 - + + + ↑ Frequency + + + + + + + + + + + + + - + + + FEMALE MALE N/A - + + FEMALE + MALE + N/A + + + FEMALE + MALE + N/A + + + + sex + + + @@ -88,96 +169,25 @@ - - - - - - - Chinstrap - - - - - - - - - - - - - - - - - - - - - - - - - FEMALE - MALE - N/A - - + - - + + + + - - - Gentoo - - + + - - - - - - - - - - - - - - - - - - - - - FEMALE - MALE - N/A - - - - - + + - + - - species - - - ↑ Frequency - - - sex - \ No newline at end of file diff --git a/test/output/projectionBleedEdges2.svg b/test/output/projectionBleedEdges2.svg index b1af6ad33a..2b2fdb67b7 100644 --- a/test/output/projectionBleedEdges2.svg +++ b/test/output/projectionBleedEdges2.svg @@ -13,28 +13,32 @@ white-space: pre; } - - + + 1 - - - - - + + 2 - - - - 2 + + + - + - + + + - + + + + + + + \ No newline at end of file diff --git a/test/output/projectionFitBertin1953.svg b/test/output/projectionFitBertin1953.svg index 29200805f2..cd2a461f69 100644 --- a/test/output/projectionFitBertin1953.svg +++ b/test/output/projectionFitBertin1953.svg @@ -13,21 +13,23 @@ white-space: pre; } - - + + a - - - + + b - - - b + + + + + + + - - + diff --git a/test/output/projectionHeightEqualEarth.svg b/test/output/projectionHeightEqualEarth.svg index 419a217896..7de650c6e6 100644 --- a/test/output/projectionHeightEqualEarth.svg +++ b/test/output/projectionHeightEqualEarth.svg @@ -13,34 +13,40 @@ white-space: pre; } - - + + 0 - - - - - - - - + + 1 - - - - 1 + + + - + - + + + + + + - + + + - + + + + + + + \ No newline at end of file diff --git a/test/output/projectionHeightGeometry.svg b/test/output/projectionHeightGeometry.svg index 2080d79823..24ad13d9ee 100644 --- a/test/output/projectionHeightGeometry.svg +++ b/test/output/projectionHeightGeometry.svg @@ -13,22 +13,24 @@ white-space: pre; } - - + + 0 - - + + 1 - - - - 1 + + + - + - + + + + \ No newline at end of file diff --git a/test/output/projectionHeightMercator.svg b/test/output/projectionHeightMercator.svg index b71ba12991..85432e47ec 100644 --- a/test/output/projectionHeightMercator.svg +++ b/test/output/projectionHeightMercator.svg @@ -13,34 +13,40 @@ white-space: pre; } - - + + 0 - - - - - - - - + + 1 - - - - 1 + + + - + - + + + + + + - + + + - + + + + + + + \ No newline at end of file diff --git a/test/output/projectionHeightOrthographic.svg b/test/output/projectionHeightOrthographic.svg index 6609f0b076..bd0f04ba43 100644 --- a/test/output/projectionHeightOrthographic.svg +++ b/test/output/projectionHeightOrthographic.svg @@ -13,64 +13,68 @@ white-space: pre; } - - + + 0 - - + + 1 - - + + + + 0 - - + + 1 - - - - 1 - - - 0 + + + - + - - + + - - + + - - - - + + + - + - - + + + + + - - - - 1 + + + - - + + - - + + - + - + + + + + + \ No newline at end of file diff --git a/test/output/reducerScaleOverrideFunction.svg b/test/output/reducerScaleOverrideFunction.svg index 9c78ec2c99..a2dd006780 100644 --- a/test/output/reducerScaleOverrideFunction.svg +++ b/test/output/reducerScaleOverrideFunction.svg @@ -13,85 +13,91 @@ white-space: pre; } - - + + FEMALE - + + MALE + + + + sex + + + - - 0 - 20 - 40 - 60 - - - - - - - - - - MALE + + + + + - + - + + + 0 20 40 60 - - - - - - - - - - - - + + 0 + 20 + 40 + 60 - + 0 20 40 60 - + + + ↑ Frequency + + + - + + + Adelie Chinstrap Gentoo - - - - - - - sex - - - ↑ Frequency species + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/output/reducerScaleOverrideImplementation.svg b/test/output/reducerScaleOverrideImplementation.svg index 9c78ec2c99..a2dd006780 100644 --- a/test/output/reducerScaleOverrideImplementation.svg +++ b/test/output/reducerScaleOverrideImplementation.svg @@ -13,85 +13,91 @@ white-space: pre; } - - + + FEMALE - + + MALE + + + + sex + + + - - 0 - 20 - 40 - 60 - - - - - - - - - - MALE + + + + + - + - + + + 0 20 40 60 - - - - - - - - - - - - + + 0 + 20 + 40 + 60 - + 0 20 40 60 - + + + ↑ Frequency + + + - + + + Adelie Chinstrap Gentoo - - - - - - - sex - - - ↑ Frequency species + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/output/reducerScaleOverrideName.svg b/test/output/reducerScaleOverrideName.svg index 9c78ec2c99..a2dd006780 100644 --- a/test/output/reducerScaleOverrideName.svg +++ b/test/output/reducerScaleOverrideName.svg @@ -13,85 +13,91 @@ white-space: pre; } - - + + FEMALE - + + MALE + + + + sex + + + - - 0 - 20 - 40 - 60 - - - - - - - - - - MALE + + + + + - + - + + + 0 20 40 60 - - - - - - - - - - - - + + 0 + 20 + 40 + 60 - + 0 20 40 60 - + + + ↑ Frequency + + + - + + + Adelie Chinstrap Gentoo - - - - - - - sex - - - ↑ Frequency species + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/output/textOverflow.svg b/test/output/textOverflow.svg index 5de634dc1b..d22f0aea12 100644 --- a/test/output/textOverflow.svg +++ b/test/output/textOverflow.svg @@ -13,11 +13,28 @@ white-space: pre; } - - + + clip-start - + + clip-end + + + ellipsis-start + + + ellipsis-middle + + + ellipsis-end + + + monospace + + + + @@ -56,7 +73,9 @@ - + + + The Best Years of Our Lives The Ballad of Gregorio Cortez My Big Fat Independent Movie @@ -95,7 +114,9 @@ 👁️‍🗨️👩‍❤️‍💋‍👩👁️‍🗨️👩‍❤️‍💋‍👩👁️‍🗨️👩‍❤️‍💋‍👩👁️‍🗨️👩‍❤️‍💋‍👩👁️‍🗨️👩‍❤️‍💋‍👩👁️‍🗨️👩‍❤️‍💋‍👩 🧑🏾.👨🏻.👧🏼.👦🏽.🧒🏿.🧑🏾.👨🏻.👧🏼.👦🏽.🧒🏿 - + + + ars of Our LivesThe Best Years of Our Lives Gregorio CortezThe Ballad of Gregorio Cortez ependent MovieMy Big Fat Independent Movie @@ -135,13 +156,9 @@ 👁️‍🗨️👩‍❤️‍💋‍👩👁️‍🗨️👩‍❤️‍💋‍👩👁️‍🗨️👩‍❤️‍💋‍👩👁️‍🗨️👩‍❤️‍💋‍👩👁️‍🗨️👩‍❤️‍💋‍👩👁️‍🗨️👩‍❤️‍💋‍👩👁️‍🗨️👩‍❤️‍💋‍👩👁️‍🗨️👩‍❤️‍💋‍👩👁️‍🗨️👩‍❤️‍💋‍👩 .🧑🏾.👨🏻.👧🏼.👦🏽.🧒🏿🧑🏾.👨🏻.👧🏼.👦🏽.🧒🏿.🧑🏾.👨🏻.👧🏼.👦🏽.🧒🏿 - - - - clip-end - - + + The Best YearsThe Best Years of Our Lives The Ballad of GrThe Ballad of Gregorio Cortez My Big Fat IndeMy Big Fat Independent Movie @@ -181,13 +198,9 @@ 👁️‍🗨️👩‍❤️‍💋‍👩👁️‍🗨️👩‍❤️‍💋‍👩👁️‍🗨️👩‍❤️‍💋‍👩👁️‍🗨️👩‍❤️‍💋‍👩👁️‍🗨️👩‍❤️‍💋‍👩👁️‍🗨️👩‍❤️‍💋‍👩👁️‍🗨️👩‍❤️‍💋‍👩👁️‍🗨️👩‍❤️‍💋‍👩👁️‍🗨️👩‍❤️‍💋‍👩 🧑🏾.👨🏻.👧🏼.👦🏽.🧒🏿.🧑🏾.👨🏻.👧🏼.👦🏽.🧒🏿.🧑🏾.👨🏻.👧🏼.👦🏽.🧒🏿 - - - - ellipsis-start - - + + …rs of Our LivesThe Best Years of Our Lives …regorio CortezThe Ballad of Gregorio Cortez …pendent MovieMy Big Fat Independent Movie @@ -227,13 +240,9 @@ …👩‍❤️‍💋‍👩👁️‍🗨️👩‍❤️‍💋‍👩👁️‍🗨️👩‍❤️‍💋‍👩👁️‍🗨️👩‍❤️‍💋‍👩👁️‍🗨️👩‍❤️‍💋‍👩👁️‍🗨️👩‍❤️‍💋‍👩👁️‍🗨️👩‍❤️‍💋‍👩👁️‍🗨️👩‍❤️‍💋‍👩👁️‍🗨️👩‍❤️‍💋‍👩 ….👨🏻.👧🏼.👦🏽.🧒🏿🧑🏾.👨🏻.👧🏼.👦🏽.🧒🏿.🧑🏾.👨🏻.👧🏼.👦🏽.🧒🏿 - - - - ellipsis-middle - - + + The Be…ur LivesThe Best Years of Our Lives The Ba…CortezThe Ballad of Gregorio Cortez My Big…t MovieMy Big Fat Independent Movie @@ -273,13 +282,9 @@ 👁️‍🗨️👩‍❤️‍💋‍👩…👩‍❤️‍💋‍👩👁️‍🗨️👩‍❤️‍💋‍👩👁️‍🗨️👩‍❤️‍💋‍👩👁️‍🗨️👩‍❤️‍💋‍👩👁️‍🗨️👩‍❤️‍💋‍👩👁️‍🗨️👩‍❤️‍💋‍👩👁️‍🗨️👩‍❤️‍💋‍👩👁️‍🗨️👩‍❤️‍💋‍👩 🧑🏾.👨🏻.….👦🏽.🧒🏿🧑🏾.👨🏻.👧🏼.👦🏽.🧒🏿.🧑🏾.👨🏻.👧🏼.👦🏽.🧒🏿 - - - - ellipsis-end - - + + The Best Year…The Best Years of Our Lives The Ballad of…The Ballad of Gregorio Cortez My Big Fat Ind…My Big Fat Independent Movie @@ -319,13 +324,9 @@ 👁️‍🗨️👩‍❤️‍💋‍👩👁️‍🗨️👩‍❤️‍💋‍👩👁️‍🗨️…👁️‍🗨️👩‍❤️‍💋‍👩👁️‍🗨️👩‍❤️‍💋‍👩👁️‍🗨️👩‍❤️‍💋‍👩👁️‍🗨️👩‍❤️‍💋‍👩👁️‍🗨️👩‍❤️‍💋‍👩👁️‍🗨️👩‍❤️‍💋‍👩 🧑🏾.👨🏻.👧🏼.👦🏽.…🧑🏾.👨🏻.👧🏼.👦🏽.🧒🏿.🧑🏾.👨🏻.👧🏼.👦🏽.🧒🏿 - - - - monospace - - + + The Best Yea…The Best Years of Our Lives The Ballad o…The Ballad of Gregorio Cortez My Big Fat I…My Big Fat Independent Movie @@ -365,6 +366,13 @@ 👁️‍🗨️👩‍❤️‍💋‍👩👁️‍🗨️👩‍❤️‍💋‍👩👁️‍🗨️👩‍❤️‍💋‍👩…👁️‍🗨️👩‍❤️‍💋‍👩👁️‍🗨️👩‍❤️‍💋‍👩👁️‍🗨️👩‍❤️‍💋‍👩👁️‍🗨️👩‍❤️‍💋‍👩👁️‍🗨️👩‍❤️‍💋‍👩👁️‍🗨️👩‍❤️‍💋‍👩 🧑🏾.👨🏻.👧🏼.👦🏽.…🧑🏾.👨🏻.👧🏼.👦🏽.🧒🏿.🧑🏾.👨🏻.👧🏼.👦🏽.🧒🏿 - + + + + + + + + \ No newline at end of file diff --git a/test/output/tipBar.svg b/test/output/tipBar.svg new file mode 100644 index 0000000000..89403a504b --- /dev/null +++ b/test/output/tipBar.svg @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + modern pentathlon + triathlon + golf + archery + taekwondo + badminton + table tennis + tennis + equestrian + fencing + weightlifting + boxing + basketball + rugby sevens + gymnastics + canoe + wrestling + handball + sailing + volleyball + shooting + judo + hockey + cycling + rowing + football + aquatics + athletics + + + sport + + + + + + + + + + 0 + 500 + 1,000 + 1,500 + 2,000 + + + Frequency → + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/output/tipBin.svg b/test/output/tipBin.svg new file mode 100644 index 0000000000..5a315ad72c --- /dev/null +++ b/test/output/tipBin.svg @@ -0,0 +1,142 @@ + + + + + + + + + + + + + + + + + + + 0 + 50 + 100 + 150 + 200 + 250 + 300 + 350 + 400 + 450 + 500 + 550 + 600 + + + ↑ Frequency + + + + + + + + + + + + 40 + 60 + 80 + 100 + 120 + 140 + 160 + + + weight → + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/output/tipBinStack.svg b/test/output/tipBinStack.svg new file mode 100644 index 0000000000..9d96f3d16d --- /dev/null +++ b/test/output/tipBinStack.svg @@ -0,0 +1,189 @@ + + + + + + + + + + + + + + + + + + + 0 + 50 + 100 + 150 + 200 + 250 + 300 + 350 + 400 + 450 + 500 + 550 + 600 + + + ↑ Frequency + + + + + + + + + + + + 40 + 60 + 80 + 100 + 120 + 140 + 160 + + + weight → + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/output/tipCell.svg b/test/output/tipCell.svg new file mode 100644 index 0000000000..5c72903d12 --- /dev/null +++ b/test/output/tipCell.svg @@ -0,0 +1,149 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + aquatics + archery + athletics + badminton + basketball + boxing + canoe + cycling + equestrian + fencing + football + golf + gymnastics + handball + hockey + judo + modern pentathlon + rowing + rugby sevens + sailing + shooting + table tennis + taekwondo + tennis + triathlon + volleyball + weightlifting + wrestling + + + sport + + + + + + + female + male + + + sex + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/output/tipCellFacet.svg b/test/output/tipCellFacet.svg new file mode 100644 index 0000000000..06b40c5f6f --- /dev/null +++ b/test/output/tipCellFacet.svg @@ -0,0 +1,160 @@ + + + + + female + + + male + + + + sex + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + aquatics + archery + athletics + badminton + basketball + boxing + canoe + cycling + equestrian + fencing + football + golf + gymnastics + handball + hockey + judo + modern pentathlon + rowing + rugby sevens + sailing + shooting + table tennis + taekwondo + tennis + triathlon + volleyball + weightlifting + wrestling + + + + sport + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/output/tipDodge.svg b/test/output/tipDodge.svg new file mode 100644 index 0000000000..3127891ebb --- /dev/null +++ b/test/output/tipDodge.svg @@ -0,0 +1,378 @@ + + + + + + + + + + + 35 + 40 + 45 + 50 + 55 + + + culmen_length_mm → + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/output/tipDot.svg b/test/output/tipDot.svg new file mode 100644 index 0000000000..1991c3a98c --- /dev/null +++ b/test/output/tipDot.svg @@ -0,0 +1,401 @@ + + + + + + + + + + + + + + 14 + 15 + 16 + 17 + 18 + 19 + 20 + 21 + + + ↑ culmen_depth_mm + + + + + + + + + + 35 + 40 + 45 + 50 + 55 + + + culmen_length_mm → + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/output/tipDotFacets.svg b/test/output/tipDotFacets.svg new file mode 100644 index 0000000000..da6e98dfea --- /dev/null +++ b/test/output/tipDotFacets.svg @@ -0,0 +1,11214 @@ + + + + + 1950 + + + 1960 + + + 1970 + + + 1980 + + + 1990 + + + 2000 + + + + decade of birth + + + + female + + + male + + + + sex + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1.4 + 1.6 + 1.8 + 2.0 + 2.2 + + + 1.4 + 1.6 + 1.8 + 2.0 + 2.2 + + + 1.4 + 1.6 + 1.8 + 2.0 + 2.2 + + + 1.4 + 1.6 + 1.8 + 2.0 + 2.2 + + + 1.4 + 1.6 + 1.8 + 2.0 + 2.2 + + + 1.4 + 1.6 + 1.8 + 2.0 + 2.2 + + + + ↑ height + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 50 + 100 + 150 + + + 50 + 100 + 150 + + + + weight → + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/output/tipDotFilter.svg b/test/output/tipDotFilter.svg new file mode 100644 index 0000000000..172bb3ef50 --- /dev/null +++ b/test/output/tipDotFilter.svg @@ -0,0 +1,395 @@ + + + + + + + + + + + + + + 14 + 15 + 16 + 17 + 18 + 19 + 20 + 21 + + + ↑ culmen_depth_mm + + + + + + + + + + 35 + 40 + 45 + 50 + 55 + + + culmen_length_mm → + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/output/tipGeoCentroid.svg b/test/output/tipGeoCentroid.svg new file mode 100644 index 0000000000..462df284b3 --- /dev/null +++ b/test/output/tipGeoCentroid.svg @@ -0,0 +1,21 @@ + + + + + + + + \ No newline at end of file diff --git a/test/output/tipHexbin.svg b/test/output/tipHexbin.svg new file mode 100644 index 0000000000..2e6d1ea5f5 --- /dev/null +++ b/test/output/tipHexbin.svg @@ -0,0 +1,306 @@ + + + + + + + + + + + + + + + + 1.3 + 1.4 + 1.5 + 1.6 + 1.7 + 1.8 + 1.9 + 2.0 + 2.1 + 2.2 + + + ↑ height + + + + + + + + + + + + 40 + 60 + 80 + 100 + 120 + 140 + 160 + + + weight → + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/output/tipLine.svg b/test/output/tipLine.svg new file mode 100644 index 0000000000..4a1d9412d9 --- /dev/null +++ b/test/output/tipLine.svg @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + 60 + 70 + 80 + 90 + 100 + 110 + 120 + 130 + 140 + 150 + 160 + 170 + 180 + 190 + + + ↑ Close + + + + + + + + + + 2014 + 2015 + 2016 + 2017 + 2018 + + + + + + \ No newline at end of file diff --git a/test/output/tipRaster.svg b/test/output/tipRaster.svg new file mode 100644 index 0000000000..66a9e730af --- /dev/null +++ b/test/output/tipRaster.svg @@ -0,0 +1,20 @@ + + + + + + + \ No newline at end of file diff --git a/test/output/tipRule.svg b/test/output/tipRule.svg new file mode 100644 index 0000000000..99b687131f --- /dev/null +++ b/test/output/tipRule.svg @@ -0,0 +1,382 @@ + + + + + + + + + + + + + 3,000 + 3,500 + 4,000 + 4,500 + 5,000 + 5,500 + 6,000 + + + body_mass_g → + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/output/trafficHorizon.html b/test/output/trafficHorizon.html index 76083f4fd4..bb11d9b198 100644 --- a/test/output/trafficHorizon.html +++ b/test/output/trafficHorizon.html @@ -50,8 +50,8 @@ white-space: pre; } - - + + @@ -65,7 +65,9 @@ - + + + Mon 04 12 PM Tue 05 @@ -79,7 +81,9 @@ Sat 09 12 PM - + + + @@ -87,1696 +91,1519 @@ - + - + - + - + - + - + - + - + - - Von der Heydt - - - - + - + - + - + - + - + - + - + - + - + - - Kirschheck - - - - + - + - + - + - + - + - + - + - + - + - - Saarbrücken-Neuhaus - - - - + - + - + - + - + - + - + - + - + - + - - Riegelsberg - - - - + - + - + - + - + - + - + - + - + - + - - Holz - - - - + - + - + - + - + - + - + - + - + - + - - Göttelborn - - - - + - + - + - + - + - + - + - + - + - + - - Illingen - - - - + - + - + - + - + - + - + + + - + - + - + - - AS Eppelborn - - - - + - + - + - + - + - + - + - + - + - + - - Hasborn - - - - + - + - + - + - + - + - + - + - + - + - - Kastel - - - - + - + - + - + - + - + - + - + - + - + - - Otzenhausen - - - - + - + - + - + - + - + - + - + - + - + - - Bierfeld - - - - + - + - + - + - + - + - + - + - + - + - - Nonnweiler - - - - + - + - + - + - + - + - + - + - + - + - - Hetzerath - - - - + - + - + - + - + - + - + - + - + - + - - Laufeld - - - - + - + - + + + - + - + - + - + - + - + - + - - Nettersheim - - - - + - + - + - + - + - + - + - + - + - + - - Euskirchen/Bliesheim - - - - + - + - + - + - + - + - + - + - + - + - - Hürth - - - - + - + - + - + - + - + - + - + - + - + - - Köln-Nord - - - - + - + - + - + - + - + - + - + - + - + - - Schloss Burg - - - - + - + - + - + - + - + - + - + - + - + - - Hagen-Vorhalle - - - - + - + - + - + - + - + - + - + - + - + - - Hengsen - - - - + - + - + - + - + - + - + - + - + + + - + - - Unna - - - - + - + - + - + - + - + - + - + - + - + - - Ascheberg - - - - + - + - + - + - + - + - + - + - + - + - - Ladbergen - - - - + - + - + - + - + - + - + - + - + - + - - Lotte - - - - + - + - + - + - + - + - + - + - + - + - - HB-Silbersee - - - - + - + - + - + - + - + - + - + - + - + - - HB-Weserbrücke - - - - + - + - + - + - + - + - + - + - + - + - - HB-Mahndorfer See - - - - + - + - + - + - + - + - + - + - + - + - - Groß Ippener - - - - + - + - + - + - + + + - + - + - + - + - + - - Uphusen - - - - + - + - + - + - + - + - + - + - + - + - - Bockel - - - - + - + - + - + - + - + - + - + - + - + - - Dibbersen - - - - + - + - + - + - + - + - + - + - + - + - - Glüsingen - - - - + - + - + - + - + - + - + - + - + - + - - Barsbüttel - - - - + - + - + - + - + - + - + - + - + - + - - Bad Schwartau - - - - + - + - + - + - + - + - + - + - + - + - - Oldenburg (Holstein) - - - - + - + - + - + - + - + - + - + - + @@ -1784,7 +1611,120 @@ - + + + + Von der Heydt + + + Kirschheck + + + Saarbrücken-Neuhaus + + + Riegelsberg + + + Holz + + + Göttelborn + + + Illingen + + + AS Eppelborn + + + Hasborn + + + Kastel + + + Otzenhausen + + + Bierfeld + + + Nonnweiler + + + Hetzerath + + + Laufeld + + + Nettersheim + + + Euskirchen/Bliesheim + + + Hürth + + + Köln-Nord + + + Schloss Burg + + + Hagen-Vorhalle + + + Hengsen + + + Unna + + + Ascheberg + + + Ladbergen + + + Lotte + + + HB-Silbersee + + + HB-Weserbrücke + + + HB-Mahndorfer See + + + Groß Ippener + + + Uphusen + + + Bockel + + + Dibbersen + + + Glüsingen + + + Barsbüttel + + + Bad Schwartau + + + Oldenburg (Holstein) + + Neustadt i. H.-Süd diff --git a/test/output/usPopulationStateAgeGrouped.svg b/test/output/usPopulationStateAgeGrouped.svg index a6e688877b..79e337cfa7 100644 --- a/test/output/usPopulationStateAgeGrouped.svg +++ b/test/output/usPopulationStateAgeGrouped.svg @@ -13,14 +13,118 @@ white-space: pre; } - - + + - + + + + + + + + + + + + + + + + + + CA - + + TX + + + NY + + + FL + + + PA + + + IL + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -34,7 +138,9 @@ - + + + @@ -48,7 +154,9 @@ - + + + 0.0M 0.5M 1.0M @@ -62,7 +170,12 @@ 5.0M 5.5M - + + + ↑ population + + + <10 10-19 20-29 @@ -73,32 +186,7 @@ 70-79 ≥80 - - - - - - - - - - TX - - - - - - - - - - - - - - - - + <10 10-19 20-29 @@ -109,32 +197,7 @@ 70-79 ≥80 - - - - - - - - - - NY - - - - - - - - - - - - - - - - + <10 10-19 20-29 @@ -145,32 +208,7 @@ 70-79 ≥80 - - - - - - - - - - FL - - - - - - - - - - - - - - - - + <10 10-19 20-29 @@ -181,32 +219,7 @@ 70-79 ≥80 - - - - - - - - - - PA - - - - - - - - - - - - - - - - + <10 10-19 20-29 @@ -217,32 +230,7 @@ 70-79 ≥80 - - - - - - - - - - IL - - - - - - - - - - - - - - - - + <10 10-19 20-29 @@ -253,11 +241,25 @@ 70-79 ≥80 - + + + + + + + + + + + + + + + + + + - - - ↑ population \ No newline at end of file diff --git a/test/output/walmartsDecades.svg b/test/output/walmartsDecades.svg index d2183b4cca..ad0b6ccffb 100644 --- a/test/output/walmartsDecades.svg +++ b/test/output/walmartsDecades.svg @@ -13,73 +13,71 @@ white-space: pre; } - - + + 1960’s - - - - - - - - - - - - + 1970’s - - + + 1980’s - - + + 1990’s - - + + 2000’s - - - 1980’s + + + - + - - + + - - + + + + + - - - 1990’s + + + - - + + + + + - + - - + + - - - 2000’s + + + - - + + - - + + + + + - + diff --git a/test/plots/crosshair.ts b/test/plots/crosshair.ts new file mode 100644 index 0000000000..806808bd6b --- /dev/null +++ b/test/plots/crosshair.ts @@ -0,0 +1,43 @@ +import * as Plot from "@observablehq/plot"; +import * as d3 from "d3"; + +export async function crosshairDodge() { + const penguins = await d3.csv("data/penguins.csv", d3.autoType); + return Plot.plot({ + height: 160, + marks: [ + Plot.dot(penguins, Plot.dodgeY({x: "culmen_length_mm", r: "body_mass_g"})), + Plot.crosshairX(penguins, Plot.dodgeY({x: "culmen_length_mm", r: "body_mass_g"})) + ] + }); +} + +export async function crosshairDot() { + const penguins = await d3.csv("data/penguins.csv", d3.autoType); + return Plot.plot({ + marks: [ + Plot.dot(penguins, {x: "culmen_length_mm", y: "culmen_depth_mm", stroke: "sex"}), + Plot.crosshair(penguins, {x: "culmen_length_mm", y: "culmen_depth_mm"}) + ] + }); +} + +export async function crosshairDotFacet() { + const penguins = await d3.csv("data/penguins.csv", d3.autoType); + return Plot.plot({ + marks: [ + Plot.dot(penguins, {x: "culmen_length_mm", y: "culmen_depth_mm", fy: "species", stroke: "sex"}), + Plot.crosshair(penguins, {x: "culmen_length_mm", y: "culmen_depth_mm", fy: "species"}) + ] + }); +} + +export async function crosshairHexbin() { + const olympians = await d3.csv("data/athletes.csv", d3.autoType); + return Plot.plot({ + marks: [ + Plot.hexagon(olympians, Plot.hexbin({r: "count"}, {x: "weight", y: "height"})), + Plot.crosshair(olympians, Plot.hexbin({r: "count"}, {x: "weight", y: "height"})) + ] + }); +} diff --git a/test/plots/index.ts b/test/plots/index.ts index ee9e6ab576..fd35318b1b 100644 --- a/test/plots/index.ts +++ b/test/plots/index.ts @@ -54,6 +54,7 @@ export * from "./crimean-war-arrow.js"; export * from "./crimean-war-line.js"; export * from "./crimean-war-overlapped.js"; export * from "./crimean-war-stacked.js"; +export * from "./crosshair.js"; export * from "./d3-survey-2015-comfort.js"; export * from "./d3-survey-2015-why.js"; export * from "./darker-dodge.js"; @@ -272,6 +273,7 @@ export * from "./stargazers.js"; export * from "./stocks-index.js"; export * from "./text-overflow.js"; export * from "./this-is-just-to-say.js"; +export * from "./tip.js"; export * from "./traffic-horizon.js"; export * from "./travelers-covid-drop.js"; export * from "./travelers-year-over-year.js"; diff --git a/test/plots/tip.ts b/test/plots/tip.ts new file mode 100644 index 0000000000..93c1f5748d --- /dev/null +++ b/test/plots/tip.ts @@ -0,0 +1,145 @@ +import * as Plot from "@observablehq/plot"; +import * as d3 from "d3"; +import {feature, mesh} from "topojson-client"; + +function tipped(mark, options = {}, pointer = Plot.pointer) { + return Plot.marks(mark, Plot.tip(mark.data, pointer(derive(mark, options)))); +} + +function tippedX(mark, options = {}) { + return tipped(mark, options, Plot.pointerX); +} + +function tippedY(mark, options = {}) { + return tipped(mark, options, Plot.pointerY); +} + +function derive(mark, options = {}) { + return Plot.initializer({...options, x: null, y: null}, (data, facets, channels, scales, dimensions, context) => { + return (context as any).getMarkState(mark); + }); +} + +export async function tipBar() { + const olympians = await d3.csv("data/athletes.csv", d3.autoType); + return tippedY(Plot.barX(olympians, Plot.groupY({x: "count"}, {y: "sport", sort: {y: "x"}}))).plot({marginLeft: 100}); +} + +export async function tipBin() { + const olympians = await d3.csv("data/athletes.csv", d3.autoType); + return tippedX(Plot.rectY(olympians, Plot.binX({y: "count"}, {x: "weight"}))).plot(); +} + +export async function tipBinStack() { + const olympians = await d3.csv("data/athletes.csv", d3.autoType); + return tippedX(Plot.rectY(olympians, Plot.binX({y: "count"}, {x: "weight", fill: "sex"}))).plot(); +} + +export async function tipCell() { + const olympians = await d3.csv("data/athletes.csv", d3.autoType); + return Plot.plot({ + height: 400, + marginLeft: 100, + color: {scheme: "blues"}, + marks: [tippedY(Plot.cell(olympians, Plot.group({fill: "count"}, {x: "sex", y: "sport"})))] + }); +} + +export async function tipCellFacet() { + const olympians = await d3.csv("data/athletes.csv", d3.autoType); + return Plot.plot({ + height: 400, + marginLeft: 100, + color: {scheme: "blues"}, + marks: [tippedY(Plot.cell(olympians, Plot.groupY({fill: "count"}, {fx: "sex", y: "sport"})))] + }); +} + +export async function tipDodge() { + const penguins = await d3.csv("data/penguins.csv", d3.autoType); + return tipped(Plot.dot(penguins, Plot.dodgeY({x: "culmen_length_mm", r: "body_mass_g"}))).plot({height: 160}); +} + +export async function tipDot() { + const penguins = await d3.csv("data/penguins.csv", d3.autoType); + return tipped(Plot.dot(penguins, {x: "culmen_length_mm", y: "culmen_depth_mm", stroke: "sex"})).plot(); +} + +export async function tipDotFacets() { + const athletes = await d3.csv("data/athletes.csv", d3.autoType); + return Plot.plot({ + grid: true, + fy: { + label: "decade of birth", + interval: "10 years" + }, + marks: [ + tipped( + Plot.dot(athletes, { + x: "weight", + y: "height", + fx: "sex", + fy: "date_of_birth", + channels: {name: "name", sport: "sport"} + }) + ) + ] + }); +} + +export async function tipDotFilter() { + const penguins = await d3.csv("data/penguins.csv", d3.autoType); + const xy = {x: "culmen_length_mm", y: "culmen_depth_mm", stroke: "sex"}; + const [dot1, tip1] = tipped(Plot.dot(penguins, {...xy, filter: (d) => d.sex === "MALE"}), {anchor: "left"}); + const [dot2, tip2] = tipped(Plot.dot(penguins, {...xy, filter: (d) => d.sex === "FEMALE"}), {anchor: "right"}); + return Plot.marks(dot1, dot2, tip1, tip2).plot(); +} + +export async function tipGeoCentroid() { + const [[counties, countymesh]] = await Promise.all([ + d3 + .json("data/us-counties-10m.json") + .then((us) => [feature(us, us.objects.counties), mesh(us, us.objects.counties)]) + ]); + // Alternatively, using centroid (slower): + // const pointer = Plot.pointer(Plot.centroid()); + const {x, y} = Plot.geoCentroid(); + const pointer = Plot.pointer({px: x, py: y, x, y}); + return Plot.plot({ + width: 960, + height: 600, + projection: "albers-usa", + marks: [ + Plot.geo(countymesh), + Plot.geo(counties, {...pointer, stroke: "red", strokeWidth: 2}), + Plot.tip(counties.features, {...pointer, channels: {name: (d) => d.properties.name}}) + ] + }); +} + +export async function tipHexbin() { + const olympians = await d3.csv("data/athletes.csv", d3.autoType); + return tipped(Plot.hexagon(olympians, Plot.hexbin({r: "count"}, {x: "weight", y: "height"}))).plot(); +} + +export async function tipLine() { + const aapl = await d3.csv("data/aapl.csv", d3.autoType); + return tippedX(Plot.lineY(aapl, {x: "Date", y: "Close"})).plot(); +} + +export async function tipRaster() { + const ca55 = await d3.csv("data/ca55-south.csv", d3.autoType); + const domain = {type: "MultiPoint", coordinates: ca55.map((d) => [d.GRID_EAST, d.GRID_NORTH])} as const; + return Plot.plot({ + width: 640, + height: 484, + projection: {type: "reflect-y", inset: 3, domain}, + color: {type: "diverging"}, + marks: [tipped(Plot.raster(ca55, {x: "GRID_EAST", y: "GRID_NORTH", fill: "MAG_IGRF90", interpolate: "nearest"}))] + }); +} + +export async function tipRule() { + const penguins = await d3.csv("data/penguins.csv", d3.autoType); + return tippedX(Plot.ruleX(penguins, {x: "body_mass_g"})).plot(); +}