From a76b75cc5121dc24c92c45eeac14f4f52ab380bd Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Wed, 21 Sep 2022 11:14:24 -0700 Subject: [PATCH 01/17] facetReindex for stack --- src/facet.js | 39 +++++++++++++++++++++ src/plot.js | 23 +++++++++++-- src/transforms/stack.js | 16 ++++++--- test/output/stackExclude.svg | 67 ++++++++++++++++++++++++++++++++++++ test/plots/index.js | 1 + test/plots/stack-exclude.js | 26 ++++++++++++++ 6 files changed, 165 insertions(+), 7 deletions(-) create mode 100644 src/facet.js create mode 100644 test/output/stackExclude.svg create mode 100644 test/plots/stack-exclude.js diff --git a/src/facet.js b/src/facet.js new file mode 100644 index 0000000000..97b3efbfab --- /dev/null +++ b/src/facet.js @@ -0,0 +1,39 @@ +import {isTypedArray, slice} from "./options.js"; + +export function facetReindex(facets, n) { + // Count the number of overlapping indexes across facets. + const overlap = new Uint8Array(n); + let count = 0; + for (const facet of facets) { + for (const i of facet) { + if (overlap[i]) ++count; + overlap[i] = 1; + } + } + + // For each overlapping index (duplicate number), assign a new unique index at + // the end of the existing array. For example, [[0, 1, 2], [2, 1, 3]] would + // become [[0, 1, 2], [4, 5, 3]]. Attach a reindex function to the facet + // array, to be able to read the values associated with the old index in + // unaffected channels. + if (count > 0) { + facets = facets.map((facet) => slice(facet, Uint32Array)); + const plan = new Uint32Array(count); + const reindex = (i) => (i < n ? i : plan[i - n]); + let c = 0; + overlap.fill(0); + for (const [j, facet] of facets.entries()) { + if (j) facet.reindex = reindex; + for (let k = 0; k < facet.length; ++k) { + const i = facet[k]; + if (overlap[i]) { + plan[c] = i; + facet[k] = n + c; + ++c; + } + overlap[i] = 1; + } + } + } + return {facets, n: n + count}; +} diff --git a/src/plot.js b/src/plot.js index 0e53efdce0..0db5824f92 100644 --- a/src/plot.js +++ b/src/plot.js @@ -1,4 +1,4 @@ -import {cross, difference, groups, InternMap, select} from "d3"; +import {cross, difference, groups, InternMap, max, select} from "d3"; import {Axes, autoAxisTicks, autoScaleLabels} from "./axes.js"; import {Channel, Channels, channelDomain, valueObject} from "./channel.js"; import {Context, create} from "./context.js"; @@ -609,8 +609,25 @@ export function plot(options = {}) { .attr("transform", facetTranslate(fx, fy)) .each(function (key) { const j = indexByFacet.get(key); - for (const [mark, {channels, values, facets}] of stateByMark) { - const facet = facets ? mark.filter(facets[j] ?? facets[0], channels, values) : null; + for (const [mark, {channels, values: original, facets}] of stateByMark) { + // this facet is possibly reindexed + let values = original; + let F = facets?.[j]; + const r = F?.reindex; + if (r) { + const m = max(facets[j]); + const long = Object.keys(values).filter((key) => values[key].length >= m); + const V = Object.fromEntries(long.map((key) => [key, []])); + F = []; + for (const i of facets[j]) { + const k = r(i); + F.push(k); + for (const key of long) V[key][k] = original[key][i]; + values = {...original, ...V}; + } + } + + const facet = facets ? mark.filter(F ?? facets[0], channels, values) : null; const node = mark.render(facet, scales, values, subdimensions, context); if (node != null) this.appendChild(node); } diff --git a/src/transforms/stack.js b/src/transforms/stack.js index 7018ff5ce9..315bbffab5 100644 --- a/src/transforms/stack.js +++ b/src/transforms/stack.js @@ -1,5 +1,6 @@ import {InternMap, cumsum, group, groupSort, greatest, max, min, rollup, sum} from "d3"; import {ascendingDefined} from "../defined.js"; +import {facetReindex} from "../facet.js"; import {field, column, maybeColumn, maybeZ, mid, range, valueof, maybeZero, one} from "../options.js"; import {basic} from "./basic.js"; @@ -138,6 +139,10 @@ function mergeOptions(options) { return [{offset, order, reverse}, rest]; } +function arrayElement(X, {reindex}) { + return typeof reindex === "function" ? (i) => X[reindex(i)] : (i) => X[i]; +} + function stack(x, y = one, ky, {offset, order, reverse}, options) { const z = maybeZ(options); const [X, setX] = maybeColumn(x); @@ -146,24 +151,27 @@ function stack(x, y = one, ky, {offset, order, reverse}, options) { offset = maybeOffset(offset); order = maybeOrder(order, offset, ky); return [ - basic(options, (data, facets) => { + basic(options, function (data, facets) { + let n = data.length; + ({facets, n} = facetReindex(facets, n)); + const X = x == null ? undefined : setX(valueof(data, x)); const Y = valueof(data, y, Float64Array); const Z = valueof(data, z); const O = order && order(data, X, Y, Z); - const n = data.length; const Y1 = setY1(new Float64Array(n)); const Y2 = setY2(new Float64Array(n)); const facetstacks = []; for (const facet of facets) { - const stacks = X ? Array.from(group(facet, (i) => X[i]).values()) : [facet]; + const stacks = X ? Array.from(group(facet, arrayElement(X, facet)).values()) : [facet]; + const getY = arrayElement(Y, facet); if (O) applyOrder(stacks, O); for (const stack of stacks) { let yn = 0, yp = 0; if (reverse) stack.reverse(); for (const i of stack) { - const y = Y[i]; + const y = getY(i); if (y < 0) yn = Y2[i] = (Y1[i] = yn) + y; else if (y > 0) yp = Y2[i] = (Y1[i] = yp) + y; else Y2[i] = Y1[i] = yp; // NaN or zero diff --git a/test/output/stackExclude.svg b/test/output/stackExclude.svg new file mode 100644 index 0000000000..1ce2c188dc --- /dev/null +++ b/test/output/stackExclude.svg @@ -0,0 +1,67 @@ + + + + + 0 + + + 1 + + + 2 + + + 3 + + + 4 + + + 5 + + + + + a + + + b + + + c + + + + + + + + 12 + + + + + + 0 + + + + + + + 01 + + \ No newline at end of file diff --git a/test/plots/index.js b/test/plots/index.js index 9a94cecfee..301e77eb0a 100644 --- a/test/plots/index.js +++ b/test/plots/index.js @@ -207,6 +207,7 @@ export {default as singleValueBar} from "./single-value-bar.js"; export {default as singleValueBin} from "./single-value-bin.js"; export {default as softwareVersions} from "./software-versions.js"; export {default as sparseCell} from "./sparse-cell.js"; +export {default as stackExclude} from "./stack-exclude.js"; export {default as stackedBar} from "./stacked-bar.js"; export {default as stackedRect} from "./stacked-rect.js"; export {default as stargazers} from "./stargazers.js"; diff --git a/test/plots/stack-exclude.js b/test/plots/stack-exclude.js new file mode 100644 index 0000000000..e2f2afdc81 --- /dev/null +++ b/test/plots/stack-exclude.js @@ -0,0 +1,26 @@ +import * as Plot from "@observablehq/plot"; + +export default async function () { + const data = Float64Array.of(1, 2, 3); + const facets = ["a", "b", "c"]; + return Plot.plot({ + height: 180, + facet: {data, x: facets}, + marks: [ + Plot.barY(data, { + stroke: (d) => d, // channel as accessor + fill: data, // channel as array + fillOpacity: 0.5, + facet: "exclude" + }), + Plot.textY( + data, + Plot.stackY({ + y: data, + text: (d, i) => i, // the original index + facet: "exclude" + }) + ) + ] + }); +} From 4e732d1fdc0f8946d1b245f64b4f1d8811bc89d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?= Date: Mon, 26 Sep 2022 14:40:52 +0200 Subject: [PATCH 02/17] a plan for facets reindexation --- src/facet.js | 36 ++- src/plot.js | 11 +- src/transforms/stack.js | 29 +- test/output/musicRevenueGroup.svg | 484 ++++++++++++++++++++++++++++++ test/output/stackExclude.svg | 3 +- test/plots/index.js | 1 + test/plots/music-revenue-group.js | 32 ++ 7 files changed, 562 insertions(+), 34 deletions(-) create mode 100644 test/output/musicRevenueGroup.svg create mode 100644 test/plots/music-revenue-group.js diff --git a/src/facet.js b/src/facet.js index 97b3efbfab..d9887d0827 100644 --- a/src/facet.js +++ b/src/facet.js @@ -1,7 +1,9 @@ -import {isTypedArray, slice} from "./options.js"; +import {max} from "d3"; +import {slice} from "./options.js"; -export function facetReindex(facets, n) { +export function facetReindex(facets) { // Count the number of overlapping indexes across facets. + let n = max(facets, (facet) => max(facet)) + 1; const overlap = new Uint8Array(n); let count = 0; for (const facet of facets) { @@ -18,22 +20,34 @@ export function facetReindex(facets, n) { // unaffected channels. if (count > 0) { facets = facets.map((facet) => slice(facet, Uint32Array)); - const plan = new Uint32Array(count); - const reindex = (i) => (i < n ? i : plan[i - n]); - let c = 0; + const plan = (facets.plan = new Uint32Array(n + count)); + let j = 0; + for (; j < n; ++j) plan[j] = j; overlap.fill(0); - for (const [j, facet] of facets.entries()) { - if (j) facet.reindex = reindex; + for (const facet of facets) { for (let k = 0; k < facet.length; ++k) { const i = facet[k]; if (overlap[i]) { - plan[c] = i; - facet[k] = n + c; - ++c; + plan[j] = i; + facet[k] = j; + j++; } overlap[i] = 1; } } } - return {facets, n: n + count}; + return facets; +} + +// returns a function that reads X with the facets’ reindexing plan +export function getter({plan}, X) { + return !plan || X.length === plan.length ? (i) => X[i] : (i) => X[plan[i]]; +} + +// returns an array of X expanded along the facets’ reindexing plan +export function expander({plan}, X) { + if (!plan || X.length === plan.length) return X; + const V = new X.constructor(plan.length); + for (let i = 0; i < plan.length; ++i) V[i] = X[plan[i]]; + return V; } diff --git a/src/plot.js b/src/plot.js index 0db5824f92..0f3c0f88fb 100644 --- a/src/plot.js +++ b/src/plot.js @@ -1,4 +1,4 @@ -import {cross, difference, groups, InternMap, max, select} from "d3"; +import {cross, difference, groups, InternMap, select} from "d3"; import {Axes, autoAxisTicks, autoScaleLabels} from "./axes.js"; import {Channel, Channels, channelDomain, valueObject} from "./channel.js"; import {Context, create} from "./context.js"; @@ -613,14 +613,13 @@ export function plot(options = {}) { // this facet is possibly reindexed let values = original; let F = facets?.[j]; - const r = F?.reindex; - if (r) { - const m = max(facets[j]); - const long = Object.keys(values).filter((key) => values[key].length >= m); + if (F && j > 0 && facets.plan) { + const {plan} = facets; + const long = Object.keys(values).filter((key) => values[key].length === plan.length); const V = Object.fromEntries(long.map((key) => [key, []])); F = []; for (const i of facets[j]) { - const k = r(i); + const k = plan[i]; F.push(k); for (const key of long) V[key][k] = original[key][i]; values = {...original, ...V}; diff --git a/src/transforms/stack.js b/src/transforms/stack.js index 315bbffab5..b9f158a4e1 100644 --- a/src/transforms/stack.js +++ b/src/transforms/stack.js @@ -1,6 +1,6 @@ import {InternMap, cumsum, group, groupSort, greatest, max, min, rollup, sum} from "d3"; import {ascendingDefined} from "../defined.js"; -import {facetReindex} from "../facet.js"; +import {facetReindex, getter, expander} from "../facet.js"; import {field, column, maybeColumn, maybeZ, mid, range, valueof, maybeZero, one} from "../options.js"; import {basic} from "./basic.js"; @@ -139,10 +139,6 @@ function mergeOptions(options) { return [{offset, order, reverse}, rest]; } -function arrayElement(X, {reindex}) { - return typeof reindex === "function" ? (i) => X[reindex(i)] : (i) => X[i]; -} - function stack(x, y = one, ky, {offset, order, reverse}, options) { const z = maybeZ(options); const [X, setX] = maybeColumn(x); @@ -152,20 +148,21 @@ function stack(x, y = one, ky, {offset, order, reverse}, options) { order = maybeOrder(order, offset, ky); return [ basic(options, function (data, facets) { - let n = data.length; - ({facets, n} = facetReindex(facets, n)); - const X = x == null ? undefined : setX(valueof(data, x)); const Y = valueof(data, y, Float64Array); const Z = valueof(data, z); const O = order && order(data, X, Y, Z); - const Y1 = setY1(new Float64Array(n)); - const Y2 = setY2(new Float64Array(n)); + + // make facets exclusive + facets = facetReindex(facets, data.length); + const Y1 = setY1(new Float64Array((facets.plan || data).length)); + const Y2 = setY2(new Float64Array((facets.plan || data).length)); + const facetstacks = []; for (const facet of facets) { - const stacks = X ? Array.from(group(facet, arrayElement(X, facet)).values()) : [facet]; - const getY = arrayElement(Y, facet); - if (O) applyOrder(stacks, O); + const stacks = X ? Array.from(group(facet, getter(facets, X)).values()) : [facet]; + const getY = getter(facets, Y); + if (O) applyOrder(stacks, getter(facets, O)); for (const stack of stacks) { let yn = 0, yp = 0; @@ -179,7 +176,7 @@ function stack(x, y = one, ky, {offset, order, reverse}, options) { } facetstacks.push(stacks); } - if (offset) offset(facetstacks, Y1, Y2, Z); + if (offset) offset(facetstacks, Y1, Y2, expander(facets, Z)); return {data, facets}; }), X, @@ -404,8 +401,8 @@ function orderZDomain(Z, domain) { return Z.map((z) => domain.get(z)); } -function applyOrder(stacks, O) { +function applyOrder(stacks, o) { for (const stack of stacks) { - stack.sort((i, j) => ascendingDefined(O[i], O[j])); + stack.sort((i, j) => ascendingDefined(o(i), o(j))); } } diff --git a/test/output/musicRevenueGroup.svg b/test/output/musicRevenueGroup.svg new file mode 100644 index 0000000000..6f2f3eadba --- /dev/null +++ b/test/output/musicRevenueGroup.svg @@ -0,0 +1,484 @@ + + + + + Disc + + + Download + + + Other + + + Streaming + + + Tape + + + Vinyl + group + + + + 1980 + + + 1990 + + + 2000 + + + 2010 + + + + + + −20 + + + + −10 + + + + 0 + + + + 10 + + + + 20 + ↑ Annual revenue (billions, adj.) + + + + + −20 + + + + −10 + + + + 0 + + + + 10 + + + + 20 + + + + + + −20 + + + + −10 + + + + 0 + + + + 10 + + + + 20 + + + + + + −20 + + + + −10 + + + + 0 + + + + 10 + + + + 20 + + + + + + −20 + + + + −10 + + + + 0 + + + + 10 + + + + 20 + + + + + + −20 + + + + −10 + + + + 0 + + + + 10 + + + + 20 + + + + + + CD + Disc + + + CD Single + Disc + + + SACD + Disc + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Download Album + Download + + + Download Music Video + Download + + + Download Single + Download + + + Other Digital + Download + + + Ringtones & Ringbacks + Download + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DVD Audio + Other + + + Kiosk + Other + + + Music Video (Physical) + Other + + + Synchronization + Other + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Limited Tier Paid Subscription + Streaming + + + On-Demand Streaming (Ad-Supported) + Streaming + + + Other Ad-Supported Streaming + Streaming + + + Paid Subscription + Streaming + + + SoundExchange Distributions + Streaming + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 8 - Track + Tape + + + Cassette + Tape + + + Cassette Single + Tape + + + Other Tapes + Tape + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + LP/EP + Vinyl + + + Vinyl Single + Vinyl + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/output/stackExclude.svg b/test/output/stackExclude.svg index 1ce2c188dc..1631981e99 100644 --- a/test/output/stackExclude.svg +++ b/test/output/stackExclude.svg @@ -54,8 +54,9 @@ + - 0 + 02 diff --git a/test/plots/index.js b/test/plots/index.js index 301e77eb0a..d18e9baa01 100644 --- a/test/plots/index.js +++ b/test/plots/index.js @@ -134,6 +134,7 @@ export {default as mobyDickLetterRelativeFrequency} from "./moby-dick-letter-rel export {default as morleyBoxplot} from "./morley-boxplot.js"; export {default as moviesProfitByGenre} from "./movies-profit-by-genre.js"; export {default as musicRevenue} from "./music-revenue.js"; +export {default as musicRevenueGroup} from "./music-revenue-group.js"; export {default as ordinalBar} from "./ordinal-bar.js"; export {default as penguinAnnotated} from "./penguin-annotated.js"; export {default as penguinCulmen} from "./penguin-culmen.js"; diff --git a/test/plots/music-revenue-group.js b/test/plots/music-revenue-group.js new file mode 100644 index 0000000000..e5a3662eda --- /dev/null +++ b/test/plots/music-revenue-group.js @@ -0,0 +1,32 @@ +import * as Plot from "@observablehq/plot"; +import * as d3 from "d3"; + +export default async function () { + const data = await d3.csv("data/riaa-us-revenue.csv", d3.autoType); + const stack = {x: "year", y: "revenue", z: "format", order: "appearance", reverse: true}; + return Plot.plot({ + marginRight: 90, + facet: {data, y: "group", marginRight: 90}, + y: { + grid: true, + label: "↑ Annual revenue (billions, adj.)", + transform: (d) => d / 1000 + }, + marks: [ + Plot.areaY(data, Plot.stackY({...stack, fill: "group", title: (d) => `${d.format}\n${d.group}`})), + Plot.areaY( + data, + Plot.stackY({ + ...stack, + y: (d) => -1 - d.revenue, + fill: "#eee", + stroke: "#fff", + facet: "exclude" + }) + ), + Plot.lineY(data, Plot.stackY2({...stack, stroke: "white", strokeWidth: 1})), + Plot.ruleY([0]), + Plot.frame() + ] + }); +} From c87a5219a84070fdf86501dda0a1613a3f63113b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?= Date: Mon, 26 Sep 2022 15:08:51 +0200 Subject: [PATCH 03/17] =?UTF-8?q?we=20really=20need=20to=20pass=20in=20the?= =?UTF-8?q?=20full=20range=20for=20n,=20otherwise=20we=20risk=20mixing=20u?= =?UTF-8?q?nused=20facet=20indices=20and=20expanded=20indices=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/facet.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/facet.js b/src/facet.js index d9887d0827..562d4d8bf9 100644 --- a/src/facet.js +++ b/src/facet.js @@ -1,9 +1,7 @@ -import {max} from "d3"; import {slice} from "./options.js"; -export function facetReindex(facets) { +export function facetReindex(facets, n) { // Count the number of overlapping indexes across facets. - let n = max(facets, (facet) => max(facet)) + 1; const overlap = new Uint8Array(n); let count = 0; for (const facet of facets) { @@ -15,9 +13,9 @@ export function facetReindex(facets) { // For each overlapping index (duplicate number), assign a new unique index at // the end of the existing array. For example, [[0, 1, 2], [2, 1, 3]] would - // become [[0, 1, 2], [4, 5, 3]]. Attach a reindex function to the facet - // array, to be able to read the values associated with the old index in - // unaffected channels. + // become [[0, 1, 2], [4, 5, 3]]. Attach a plan to the facets array, to be + // able to read the values associated with the old index in unaffected + // channels. if (count > 0) { facets = facets.map((facet) => slice(facet, Uint32Array)); const plan = (facets.plan = new Uint32Array(n + count)); From 0da452f991f35c0511a2ed912d95fda34a586d08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?= Date: Mon, 26 Sep 2022 15:51:16 +0200 Subject: [PATCH 04/17] this contrived example tests for the proper application of order and offset with reindexed facets --- test/output/musicRevenueContrived.svg | 412 ++++++++++++++++++++++++++ test/plots/index.js | 1 + test/plots/music-revenue-contrived.js | 40 +++ 3 files changed, 453 insertions(+) create mode 100644 test/output/musicRevenueContrived.svg create mode 100644 test/plots/music-revenue-contrived.js diff --git a/test/output/musicRevenueContrived.svg b/test/output/musicRevenueContrived.svg new file mode 100644 index 0000000000..f446964356 --- /dev/null +++ b/test/output/musicRevenueContrived.svg @@ -0,0 +1,412 @@ + + + + + Disc + + + Download + + + Other + + + Streaming + + + Tape + + + Vinyl + group + + + + 1980 + + + 1990 + + + 2000 + + + 2010 + + + + + + 0 + + + + 10 + ↑ Annual revenue (billions, adj.) + + + + + 0 + + + + 10 + + + + + + 0 + + + + 10 + + + + + + 0 + + + + 10 + + + + + + 0 + + + + 10 + + + + + + 0 + + + + 10 + + + + + + + + + + + + + + + + + + + + + + + + + + + + CD + Disc + + + CD Single + Disc + + + SACD + Disc + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Download Album + Download + + + Download Music Video + Download + + + Download Single + Download + + + Other Digital + Download + + + Ringtones & Ringbacks + Download + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DVD Audio + Other + + + Kiosk + Other + + + Music Video (Physical) + Other + + + Synchronization + Other + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Limited Tier Paid Subscription + Streaming + + + On-Demand Streaming (Ad-Supported) + Streaming + + + Other Ad-Supported Streaming + Streaming + + + Paid Subscription + Streaming + + + SoundExchange Distributions + Streaming + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 8 - Track + Tape + + + Cassette + Tape + + + Cassette Single + Tape + + + Other Tapes + Tape + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + LP/EP + Vinyl + + + Vinyl Single + Vinyl + + + + + + + + + + + + \ No newline at end of file diff --git a/test/plots/index.js b/test/plots/index.js index d18e9baa01..ea54334e31 100644 --- a/test/plots/index.js +++ b/test/plots/index.js @@ -134,6 +134,7 @@ export {default as mobyDickLetterRelativeFrequency} from "./moby-dick-letter-rel export {default as morleyBoxplot} from "./morley-boxplot.js"; export {default as moviesProfitByGenre} from "./movies-profit-by-genre.js"; export {default as musicRevenue} from "./music-revenue.js"; +export {default as musicRevenueContrived} from "./music-revenue-contrived.js"; export {default as musicRevenueGroup} from "./music-revenue-group.js"; export {default as ordinalBar} from "./ordinal-bar.js"; export {default as penguinAnnotated} from "./penguin-annotated.js"; diff --git a/test/plots/music-revenue-contrived.js b/test/plots/music-revenue-contrived.js new file mode 100644 index 0000000000..0bee1b535c --- /dev/null +++ b/test/plots/music-revenue-contrived.js @@ -0,0 +1,40 @@ +import * as Plot from "@observablehq/plot"; +import * as d3 from "d3"; + +export default async function () { + const data = await d3.csv("data/riaa-us-revenue.csv", d3.autoType); + const stack = { + x: "year", + y: "revenue", + z: "format", + // order and offset are used to test that these options follow facet reindexation + order: ["Cassette", "Paid Subscription"], + offset: "wiggle", + reverse: true + }; + return Plot.plot({ + marginRight: 90, + facet: {data, y: "group", marginRight: 90}, + y: { + grid: true, + label: "↑ Annual revenue (billions, adj.)", + transform: (d) => d / 1000 + }, + marks: [ + Plot.areaY( + data, + Plot.stackY({ + ...stack, + y: (d) => -1 - d.revenue, + fill: "#eee", + stroke: "#fff", + facet: "exclude" + }) + ), + Plot.areaY(data, Plot.stackY({...stack, fill: "group", title: (d) => `${d.format}\n${d.group}`})), + Plot.lineY(data, Plot.stackY2({...stack, stroke: "white", strokeWidth: 1})), + Plot.ruleY([0]), + Plot.frame() + ] + }); +} From 89bbc963c8cc7b227cf31c64087d79d4d62283c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?= Date: Mon, 26 Sep 2022 16:06:59 +0200 Subject: [PATCH 05/17] exclusive facets on the dodge transform --- src/transforms/dodge.js | 20 +- test/output/penguinDodgeReindexed.svg | 746 ++++++++++++++++++++++++++ test/plots/index.js | 1 + test/plots/penguin-dodge-reindexed.js | 11 + 4 files changed, 771 insertions(+), 7 deletions(-) create mode 100644 test/output/penguinDodgeReindexed.svg create mode 100644 test/plots/penguin-dodge-reindexed.js diff --git a/src/transforms/dodge.js b/src/transforms/dodge.js index 513a7b1efa..a74fbd755e 100644 --- a/src/transforms/dodge.js +++ b/src/transforms/dodge.js @@ -3,6 +3,7 @@ import {finite, positive} from "../defined.js"; import {identity, maybeNamed, number, valueof} from "../options.js"; import {coerceNumbers} from "../scales.js"; import {initializer} from "./basic.js"; +import {facetReindex, getter} from "../facet.js"; const anchorXLeft = ({marginLeft}) => [1, marginLeft]; const anchorXRight = ({width, marginRight}) => [-1, width - marginRight]; @@ -90,21 +91,26 @@ function dodge(y, x, anchor, padding, options) { return initializer(options, function (data, facets, {[x]: X, r: R}, scales, dimensions) { if (!X) throw new Error(`missing channel: ${x}`); X = coerceNumbers(valueof(X.value, scales[X.scale] || identity)); + + // make facets exclusive + facets = facetReindex(facets, data.length); + const r = R ? undefined : this.r !== undefined ? this.r : options.r !== undefined ? number(options.r) : 3; if (R) R = coerceNumbers(valueof(R.value, scales[R.scale] || identity)); let [ky, ty] = anchor(dimensions); const compare = ky ? compareAscending : compareSymmetric; - const Y = new Float64Array(X.length); - const radius = R ? (i) => R[i] : () => r; + const Y = new Float64Array((facets.plan || X).length); + const radius = R ? getter(facets, R) : () => r; + const getX = getter(facets, X); for (let I of facets) { const tree = IntervalTree(); - I = I.filter(R ? (i) => finite(X[i]) && positive(R[i]) : (i) => finite(X[i])); + I = I.filter(R ? (i) => finite(getX(i)) && positive(radius(i)) : (i) => finite(getX(i))); const intervals = new Float64Array(2 * I.length + 2); for (const i of I) { const ri = radius(i); const y0 = ky ? ri + padding : 0; // offset baseline for varying radius - const l = X[i] - ri; - const h = X[i] + ri; + const l = getX(i) - ri; + const h = getX(i) + ri; // The first two positions are 0 to test placing the dot on the baseline. let k = 2; @@ -114,8 +120,8 @@ function dodge(y, x, anchor, padding, options) { // https://observablehq.com/@mbostock/circle-offset-along-line tree.queryInterval(l - padding, h + padding, ([, , j]) => { const yj = Y[j] - y0; - const dx = X[i] - X[j]; - const dr = padding + (R ? R[i] + R[j] : 2 * r); + const dx = getX(i) - getX(j); + const dr = padding + (R ? radius(i) + radius(j) : 2 * r); const dy = Math.sqrt(dr * dr - dx * dx); intervals[k++] = yj - dy; intervals[k++] = yj + dy; diff --git a/test/output/penguinDodgeReindexed.svg b/test/output/penguinDodgeReindexed.svg new file mode 100644 index 0000000000..c6a90c6451 --- /dev/null +++ b/test/output/penguinDodgeReindexed.svg @@ -0,0 +1,746 @@ + + + + + Biscoe + + + Dream + + + Torgersen + island + + + + 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/plots/index.js b/test/plots/index.js index ea54334e31..568ff1f55d 100644 --- a/test/plots/index.js +++ b/test/plots/index.js @@ -150,6 +150,7 @@ export {default as penguinDensityFill} from "./penguin-density-fill.js"; export {default as penguinDensityZ} from "./penguin-density-z.js"; export {default as penguinDodge} from "./penguin-dodge.js"; export {default as penguinDodgeHexbin} from "./penguin-dodge-hexbin.js"; +export {default as penguinDodgeReindexed} from "./penguin-dodge-reindexed.js"; export {default as penguinDodgeVoronoi} from "./penguin-dodge-voronoi.js"; export {default as penguinFacetDodge} from "./penguin-facet-dodge.js"; export {default as penguinFacetDodgeIdentity} from "./penguin-facet-dodge-identity.js"; diff --git a/test/plots/penguin-dodge-reindexed.js b/test/plots/penguin-dodge-reindexed.js new file mode 100644 index 0000000000..e4d7b76480 --- /dev/null +++ b/test/plots/penguin-dodge-reindexed.js @@ -0,0 +1,11 @@ +import * as Plot from "@observablehq/plot"; +import * as d3 from "d3"; + +export default async function () { + const penguins = await d3.csv("data/penguins.csv", d3.autoType); + return Plot.plot({ + facet: {data: penguins, y: "island"}, + height: 400, + marks: [Plot.dot(penguins, Plot.dodgeY({x: "body_mass_g", facet: "exclude", fill: "island"}))] + }); +} From 62e63bfe0b912972299bee436a9c7225619c3458 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?= Date: Mon, 26 Sep 2022 16:49:25 +0200 Subject: [PATCH 06/17] Plot.map becomes reindex-aware --- src/transforms/map.js | 9 +- test/output/penguinCumsumExclude.svg | 806 +++++++++++++++++++++++++++ test/plots/index.js | 3 +- test/plots/penguin-cumsum-exclude.js | 37 ++ 4 files changed, 851 insertions(+), 4 deletions(-) create mode 100644 test/output/penguinCumsumExclude.svg create mode 100644 test/plots/penguin-cumsum-exclude.js diff --git a/src/transforms/map.js b/src/transforms/map.js index b62ea564c0..6016cebc68 100644 --- a/src/transforms/map.js +++ b/src/transforms/map.js @@ -1,6 +1,7 @@ import {count, group, rank} from "d3"; import {maybeZ, take, valueof, maybeInput, column} from "../options.js"; import {basic} from "./basic.js"; +import {facetReindex, getter, expander} from "../facet.js"; /** * ```js @@ -58,11 +59,13 @@ export function map(outputs = {}, options = {}) { }); return { ...basic(options, (data, facets) => { + // make facets exclusive + facets = facetReindex(facets, data.length); const Z = valueof(data, z); - const X = channels.map(({input}) => valueof(data, input)); - const MX = channels.map(({setOutput}) => setOutput(new Array(data.length))); + const X = channels.map(({input}) => expander(facets, valueof(data, input))); + const MX = channels.map(({setOutput}) => setOutput(new Array((facets.plan || data).length))); for (const facet of facets) { - for (const I of Z ? group(facet, (i) => Z[i]).values() : [facet]) { + for (const I of Z ? group(facet, getter(facets, Z)).values() : [facet]) { channels.forEach(({map}, i) => map.map(I, X[i], MX[i])); } } diff --git a/test/output/penguinCumsumExclude.svg b/test/output/penguinCumsumExclude.svg new file mode 100644 index 0000000000..3e0fb57005 --- /dev/null +++ b/test/output/penguinCumsumExclude.svg @@ -0,0 +1,806 @@ + + + + + −300 + + + −200 + + + −100 + + + 0 + + + 100 + + + 200 + + + + + Biscoe + + + Dream + + + Torgersen + island + + + + 3,000 + + + 4,000 + + + 5,000 + + + 6,000 + + + + + 3,000 + + + 4,000 + + + 5,000 + + + 6,000 + + + + + 3,000 + + + 4,000 + + + 5,000 + + + 6,000 + body_mass_g → + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/plots/index.js b/test/plots/index.js index 568ff1f55d..bcd47de6a0 100644 --- a/test/plots/index.js +++ b/test/plots/index.js @@ -144,7 +144,7 @@ export {default as penguinCulmenDelaunay} from "./penguin-culmen-delaunay.js"; export {default as penguinCulmenDelaunayMesh} from "./penguin-culmen-delaunay-mesh.js"; export {default as penguinCulmenDelaunaySpecies} from "./penguin-culmen-delaunay-species.js"; export {default as penguinCulmenVoronoi} from "./penguin-culmen-voronoi.js"; -export {default as penguinVoronoi1D} from "./penguin-voronoi-1d.js"; +export {default as penguinCumsumExclude} from "./penguin-cumsum-exclude.js"; export {default as penguinDensity} from "./penguin-density.js"; export {default as penguinDensityFill} from "./penguin-density-fill.js"; export {default as penguinDensityZ} from "./penguin-density-z.js"; @@ -170,6 +170,7 @@ export {default as penguinSpeciesGroup} from "./penguin-species-group.js"; export {default as penguinSpeciesIsland} from "./penguin-species-island.js"; export {default as penguinSpeciesIslandRelative} from "./penguin-species-island-relative.js"; export {default as penguinSpeciesIslandSex} from "./penguin-species-island-sex.js"; +export {default as penguinVoronoi1D} from "./penguin-voronoi-1d.js"; export {default as polylinear} from "./polylinear.js"; export {default as randomBins} from "./random-bins.js"; export {default as randomBinsXY} from "./random-bins-xy.js"; diff --git a/test/plots/penguin-cumsum-exclude.js b/test/plots/penguin-cumsum-exclude.js new file mode 100644 index 0000000000..d3f665a6be --- /dev/null +++ b/test/plots/penguin-cumsum-exclude.js @@ -0,0 +1,37 @@ +import * as Plot from "@observablehq/plot"; +import * as d3 from "d3"; + +export default async function () { + const penguins = await d3.csv("data/penguins.csv", d3.autoType); + return Plot.plot({ + facet: {data: penguins, x: "island"}, + width: 860, + height: 300, + y: {nice: true}, + marks: [ + Plot.frame(), + Plot.ruleY([0]), + Plot.dot( + penguins, + Plot.mapY( + "cumsum", + Plot.sort("body_mass_g", {x: "body_mass_g", y: -1, fill: "island", facet: "exclude", z: null, r: 1}) + ) + ), + Plot.lineY( + penguins, + Plot.mapY( + "cumsum", + Plot.sort("body_mass_g", { + x: "body_mass_g", + y: 1, + strokeWidth: 2, + stroke: "island", + facet: "include", + z: null + }) + ) + ) + ] + }); +} From c82c2c4a60aa57e9c856c07010888250c2956449 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?= Date: Thu, 29 Sep 2022 12:09:17 +0200 Subject: [PATCH 07/17] Plot.bin can ingest reindexed facets --- src/facet.js | 6 +- src/transforms/bin.js | 3 +- test/output/musicRevenueBin.svg | 2318 +++++++++++++++++++++++++++++++ test/plots/index.js | 1 + test/plots/music-revenue-bin.js | 34 + 5 files changed, 2360 insertions(+), 2 deletions(-) create mode 100644 test/output/musicRevenueBin.svg create mode 100644 test/plots/music-revenue-bin.js diff --git a/src/facet.js b/src/facet.js index 562d4d8bf9..5c42d63b38 100644 --- a/src/facet.js +++ b/src/facet.js @@ -44,8 +44,12 @@ export function getter({plan}, X) { // returns an array of X expanded along the facets’ reindexing plan export function expander({plan}, X) { - if (!plan || X.length === plan.length) return X; + if (!plan || !X || X.length === plan.length) return X; const V = new X.constructor(plan.length); for (let i = 0; i < plan.length; ++i) V[i] = X[plan[i]]; return V; } + +export function originals(facets) { + return facets.plan ? facets.map((facet) => facet.map((i) => facets.plan[i])) : facets; +} diff --git a/src/transforms/bin.js b/src/transforms/bin.js index 91ab1666d5..016aceed26 100644 --- a/src/transforms/bin.js +++ b/src/transforms/bin.js @@ -29,6 +29,7 @@ import { } from "./group.js"; import {maybeInsetX, maybeInsetY} from "./inset.js"; import {maybeInterval} from "./interval.js"; +import {originals} from "../facet.js"; /** * ```js @@ -182,7 +183,7 @@ function binn( for (const o of outputs) o.initialize(data); if (sort) sort.initialize(data); if (filter) filter.initialize(data); - for (const facet of facets) { + for (const facet of originals(facets)) { const groupFacet = []; for (const o of outputs) o.scope("facet", facet); if (sort) sort.scope("facet", facet); diff --git a/test/output/musicRevenueBin.svg b/test/output/musicRevenueBin.svg new file mode 100644 index 0000000000..30ec190394 --- /dev/null +++ b/test/output/musicRevenueBin.svg @@ -0,0 +1,2318 @@ + + + + + Disc + + + Download + + + Other + + + Streaming + + + Tape + + + Vinyl + group + + + + 1970 + + + 1980 + + + 1990 + + + 2000 + + + 2010 + + + 2020 + + + + + + −100 + + + + −50 + + + + 0 + + + + 50 + + + + 100 + ↑ Annual revenue (billions, adj.) + + + + + −100 + + + + −50 + + + + 0 + + + + 50 + + + + 100 + + + + + + −100 + + + + −50 + + + + 0 + + + + 50 + + + + 100 + + + + + + −100 + + + + −50 + + + + 0 + + + + 50 + + + + 100 + + + + + + −100 + + + + −50 + + + + 0 + + + + 50 + + + + 100 + + + + + + −100 + + + + −50 + + + + 0 + + + + 50 + + + + 100 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + CD + Disc (2) + + + CD + Disc (5) + + + CD + Disc (5) + + + CD + Disc (5) + + + CD + Disc (5) + + + CD + Disc (5) + + + CD + Disc (5) + + + CD + Disc (5) + + + CD + Disc (5) + + + CD + Disc (5) + + + CD Single + Disc (2) + + + CD Single + Disc (5) + + + CD Single + Disc (5) + + + CD Single + Disc (5) + + + CD Single + Disc (5) + + + CD Single + Disc (5) + + + CD Single + Disc (5) + + + CD Single + Disc (5) + + + CD Single + Disc (5) + + + CD Single + Disc (5) + + + SACD + Disc (2) + + + SACD + Disc (5) + + + SACD + Disc (5) + + + SACD + Disc (5) + + + SACD + Disc (5) + + + SACD + Disc (5) + + + SACD + Disc (5) + + + SACD + Disc (5) + + + SACD + Disc (5) + + + SACD + Disc (5) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Download Album + Download (2) + + + Download Album + Download (5) + + + Download Album + Download (5) + + + Download Album + Download (5) + + + Download Album + Download (5) + + + Download Album + Download (5) + + + Download Album + Download (5) + + + Download Album + Download (5) + + + Download Album + Download (5) + + + Download Album + Download (5) + + + Download Music Video + Download (2) + + + Download Music Video + Download (5) + + + Download Music Video + Download (5) + + + Download Music Video + Download (5) + + + Download Music Video + Download (5) + + + Download Music Video + Download (5) + + + Download Music Video + Download (5) + + + Download Music Video + Download (5) + + + Download Music Video + Download (5) + + + Download Music Video + Download (5) + + + Download Single + Download (2) + + + Download Single + Download (5) + + + Download Single + Download (5) + + + Download Single + Download (5) + + + Download Single + Download (5) + + + Download Single + Download (5) + + + Download Single + Download (5) + + + Download Single + Download (5) + + + Download Single + Download (5) + + + Download Single + Download (5) + + + Other Digital + Download (2) + + + Other Digital + Download (5) + + + Other Digital + Download (5) + + + Other Digital + Download (5) + + + Other Digital + Download (5) + + + Other Digital + Download (5) + + + Other Digital + Download (5) + + + Other Digital + Download (5) + + + Other Digital + Download (5) + + + Other Digital + Download (5) + + + Ringtones & Ringbacks + Download (2) + + + Ringtones & Ringbacks + Download (5) + + + Ringtones & Ringbacks + Download (5) + + + Ringtones & Ringbacks + Download (5) + + + Ringtones & Ringbacks + Download (5) + + + Ringtones & Ringbacks + Download (5) + + + Ringtones & Ringbacks + Download (5) + + + Ringtones & Ringbacks + Download (5) + + + Ringtones & Ringbacks + Download (5) + + + Ringtones & Ringbacks + Download (5) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DVD Audio + Other (2) + + + DVD Audio + Other (5) + + + DVD Audio + Other (5) + + + DVD Audio + Other (5) + + + DVD Audio + Other (5) + + + DVD Audio + Other (5) + + + DVD Audio + Other (5) + + + DVD Audio + Other (5) + + + DVD Audio + Other (5) + + + DVD Audio + Other (5) + + + Kiosk + Other (2) + + + Kiosk + Other (5) + + + Kiosk + Other (5) + + + Kiosk + Other (5) + + + Kiosk + Other (5) + + + Kiosk + Other (5) + + + Kiosk + Other (5) + + + Kiosk + Other (5) + + + Kiosk + Other (5) + + + Kiosk + Other (5) + + + Music Video (Physical) + Other (2) + + + Music Video (Physical) + Other (5) + + + Music Video (Physical) + Other (5) + + + Music Video (Physical) + Other (5) + + + Music Video (Physical) + Other (5) + + + Music Video (Physical) + Other (5) + + + Music Video (Physical) + Other (5) + + + Music Video (Physical) + Other (5) + + + Music Video (Physical) + Other (5) + + + Music Video (Physical) + Other (5) + + + Synchronization + Other (2) + + + Synchronization + Other (5) + + + Synchronization + Other (5) + + + Synchronization + Other (5) + + + Synchronization + Other (5) + + + Synchronization + Other (5) + + + Synchronization + Other (5) + + + Synchronization + Other (5) + + + Synchronization + Other (5) + + + Synchronization + Other (5) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Limited Tier Paid Subscription + Streaming (2) + + + Limited Tier Paid Subscription + Streaming (5) + + + Limited Tier Paid Subscription + Streaming (5) + + + Limited Tier Paid Subscription + Streaming (5) + + + Limited Tier Paid Subscription + Streaming (5) + + + Limited Tier Paid Subscription + Streaming (5) + + + Limited Tier Paid Subscription + Streaming (5) + + + Limited Tier Paid Subscription + Streaming (5) + + + Limited Tier Paid Subscription + Streaming (5) + + + Limited Tier Paid Subscription + Streaming (5) + + + On-Demand Streaming (Ad-Supported) + Streaming (2) + + + On-Demand Streaming (Ad-Supported) + Streaming (5) + + + On-Demand Streaming (Ad-Supported) + Streaming (5) + + + On-Demand Streaming (Ad-Supported) + Streaming (5) + + + On-Demand Streaming (Ad-Supported) + Streaming (5) + + + On-Demand Streaming (Ad-Supported) + Streaming (5) + + + On-Demand Streaming (Ad-Supported) + Streaming (5) + + + On-Demand Streaming (Ad-Supported) + Streaming (5) + + + On-Demand Streaming (Ad-Supported) + Streaming (5) + + + On-Demand Streaming (Ad-Supported) + Streaming (5) + + + Other Ad-Supported Streaming + Streaming (2) + + + Other Ad-Supported Streaming + Streaming (5) + + + Other Ad-Supported Streaming + Streaming (5) + + + Other Ad-Supported Streaming + Streaming (5) + + + Other Ad-Supported Streaming + Streaming (5) + + + Other Ad-Supported Streaming + Streaming (5) + + + Other Ad-Supported Streaming + Streaming (5) + + + Other Ad-Supported Streaming + Streaming (5) + + + Other Ad-Supported Streaming + Streaming (5) + + + Other Ad-Supported Streaming + Streaming (5) + + + Paid Subscription + Streaming (2) + + + Paid Subscription + Streaming (5) + + + Paid Subscription + Streaming (5) + + + Paid Subscription + Streaming (5) + + + Paid Subscription + Streaming (5) + + + Paid Subscription + Streaming (5) + + + Paid Subscription + Streaming (5) + + + Paid Subscription + Streaming (5) + + + Paid Subscription + Streaming (5) + + + Paid Subscription + Streaming (5) + + + SoundExchange Distributions + Streaming (2) + + + SoundExchange Distributions + Streaming (5) + + + SoundExchange Distributions + Streaming (5) + + + SoundExchange Distributions + Streaming (5) + + + SoundExchange Distributions + Streaming (5) + + + SoundExchange Distributions + Streaming (5) + + + SoundExchange Distributions + Streaming (5) + + + SoundExchange Distributions + Streaming (5) + + + SoundExchange Distributions + Streaming (5) + + + SoundExchange Distributions + Streaming (5) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 8 - Track + Tape (2) + + + 8 - Track + Tape (5) + + + 8 - Track + Tape (5) + + + 8 - Track + Tape (5) + + + 8 - Track + Tape (5) + + + 8 - Track + Tape (5) + + + 8 - Track + Tape (5) + + + 8 - Track + Tape (5) + + + 8 - Track + Tape (5) + + + 8 - Track + Tape (5) + + + Cassette + Tape (2) + + + Cassette + Tape (5) + + + Cassette + Tape (5) + + + Cassette + Tape (5) + + + Cassette + Tape (5) + + + Cassette + Tape (5) + + + Cassette + Tape (5) + + + Cassette + Tape (5) + + + Cassette + Tape (5) + + + Cassette + Tape (5) + + + Cassette Single + Tape (2) + + + Cassette Single + Tape (5) + + + Cassette Single + Tape (5) + + + Cassette Single + Tape (5) + + + Cassette Single + Tape (5) + + + Cassette Single + Tape (5) + + + Cassette Single + Tape (5) + + + Cassette Single + Tape (5) + + + Cassette Single + Tape (5) + + + Cassette Single + Tape (5) + + + Other Tapes + Tape (2) + + + Other Tapes + Tape (5) + + + Other Tapes + Tape (5) + + + Other Tapes + Tape (5) + + + Other Tapes + Tape (5) + + + Other Tapes + Tape (5) + + + Other Tapes + Tape (5) + + + Other Tapes + Tape (5) + + + Other Tapes + Tape (5) + + + Other Tapes + Tape (5) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + LP/EP + Vinyl (2) + + + LP/EP + Vinyl (5) + + + LP/EP + Vinyl (5) + + + LP/EP + Vinyl (5) + + + LP/EP + Vinyl (5) + + + LP/EP + Vinyl (5) + + + LP/EP + Vinyl (5) + + + LP/EP + Vinyl (5) + + + LP/EP + Vinyl (5) + + + LP/EP + Vinyl (5) + + + Vinyl Single + Vinyl (2) + + + Vinyl Single + Vinyl (5) + + + Vinyl Single + Vinyl (5) + + + Vinyl Single + Vinyl (5) + + + Vinyl Single + Vinyl (5) + + + Vinyl Single + Vinyl (5) + + + Vinyl Single + Vinyl (5) + + + Vinyl Single + Vinyl (5) + + + Vinyl Single + Vinyl (5) + + + Vinyl Single + Vinyl (5) + + + + + + + \ No newline at end of file diff --git a/test/plots/index.js b/test/plots/index.js index bcd47de6a0..b7551d8de0 100644 --- a/test/plots/index.js +++ b/test/plots/index.js @@ -134,6 +134,7 @@ export {default as mobyDickLetterRelativeFrequency} from "./moby-dick-letter-rel export {default as morleyBoxplot} from "./morley-boxplot.js"; export {default as moviesProfitByGenre} from "./movies-profit-by-genre.js"; export {default as musicRevenue} from "./music-revenue.js"; +export {default as musicRevenueBin} from "./music-revenue-bin.js"; export {default as musicRevenueContrived} from "./music-revenue-contrived.js"; export {default as musicRevenueGroup} from "./music-revenue-group.js"; export {default as ordinalBar} from "./ordinal-bar.js"; diff --git a/test/plots/music-revenue-bin.js b/test/plots/music-revenue-bin.js new file mode 100644 index 0000000000..bff44dbeca --- /dev/null +++ b/test/plots/music-revenue-bin.js @@ -0,0 +1,34 @@ +import * as Plot from "@observablehq/plot"; +import * as d3 from "d3"; + +export default async function () { + const data = await d3.csv("data/riaa-us-revenue.csv", d3.autoType); + const stack = {x: "year", y: "revenue", z: "format", order: "value", reverse: true}; + return Plot.plot({ + marginRight: 90, + facet: {data, y: "group", marginRight: 90}, + y: { + grid: true, + label: "↑ Annual revenue (billions, adj.)", + transform: (d) => d / 1000, + nice: true + }, + marks: [ + Plot.frame(), + Plot.rectY( + data, + Plot.binX( + {y: "sum"}, + { + ...stack, + y: (d) => -d.revenue, + fill: "#eee", + facet: "exclude" + } + ) + ), + Plot.rectY(data, Plot.binX({y: "sum"}, {...stack, fill: "group", title: (d) => `${d.format}\n${d.group}`})), + Plot.ruleY([0]) + ] + }); +} From f852a740bc8447cd8bfd913e039454fd62c117ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?= Date: Thu, 29 Sep 2022 12:36:53 +0200 Subject: [PATCH 08/17] a test that actually tests reindexed facets --- test/output/musicRevenueBin.svg | 1674 +++++++++++++++---------------- test/plots/music-revenue-bin.js | 10 +- 2 files changed, 844 insertions(+), 840 deletions(-) diff --git a/test/output/musicRevenueBin.svg b/test/output/musicRevenueBin.svg index 30ec190394..a55a967481 100644 --- a/test/output/musicRevenueBin.svg +++ b/test/output/musicRevenueBin.svg @@ -188,34 +188,34 @@ - - - - + + + + - - - - - - - - - + + + + + + + + + - - - - - - + + + + + + @@ -223,81 +223,61 @@ - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + - - - - + + + + + + + + + + + + + + - - - - - - - - - - - - - - + + + + - - - - - - - - - - - - - - + + + + + + + + + + + + + + @@ -306,8 +286,18 @@ - - + + + + + + + + + + + + @@ -315,9 +305,19 @@ - - - + + + + + + + + + + + + + @@ -326,10 +326,10 @@ - - - - + + + + @@ -338,36 +338,36 @@ + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + @@ -375,19 +375,19 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + @@ -398,35 +398,35 @@ CD Disc (5) - + CD Disc (5) - + CD Disc (5) - + CD Disc (5) - + CD Disc (5) - + CD Disc (5) - + CD Disc (5) - + CD Disc (5) - + CD Disc (5) @@ -442,31 +442,31 @@ CD Single Disc (5) - + CD Single Disc (5) - + CD Single Disc (5) - + CD Single Disc (5) - + CD Single Disc (5) - + CD Single Disc (5) - + CD Single Disc (5) - + CD Single Disc (5) @@ -494,19 +494,19 @@ SACD Disc (5) - + SACD Disc (5) - + SACD Disc (5) - + SACD Disc (5) - + SACD Disc (5) @@ -518,10 +518,10 @@ - - - - + + + + @@ -529,43 +529,43 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + @@ -573,51 +573,31 @@ - - - - - + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + @@ -626,8 +606,18 @@ - - + + + + + + + + + + + + @@ -635,11 +625,21 @@ - - - - - + + + + + + + + + + + + + + + @@ -648,36 +648,36 @@ + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + @@ -685,19 +685,19 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + @@ -724,19 +724,19 @@ Download Album Download (5) - + Download Album Download (5) - + Download Album Download (5) - + Download Album Download (5) - + Download Album Download (5) @@ -764,19 +764,19 @@ Download Music Video Download (5) - + Download Music Video Download (5) - + Download Music Video Download (5) - + Download Music Video Download (5) - + Download Music Video Download (5) @@ -804,19 +804,19 @@ Download Single Download (5) - + Download Single Download (5) - + Download Single Download (5) - + Download Single Download (5) - + Download Single Download (5) @@ -852,11 +852,11 @@ Other Digital Download (5) - + Other Digital Download (5) - + Other Digital Download (5) @@ -884,19 +884,19 @@ Ringtones & Ringbacks Download (5) - + Ringtones & Ringbacks Download (5) - + Ringtones & Ringbacks Download (5) - + Ringtones & Ringbacks Download (5) - + Ringtones & Ringbacks Download (5) @@ -908,10 +908,10 @@ - - - - + + + + @@ -919,43 +919,43 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + @@ -964,50 +964,40 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + @@ -1016,8 +1006,8 @@ - - + + @@ -1025,9 +1015,19 @@ - - - + + + + + + + + + + + + + @@ -1036,10 +1036,10 @@ - - - - + + + + @@ -1048,56 +1048,56 @@ + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - + + + + - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + @@ -1120,23 +1120,23 @@ DVD Audio Other (5) - + DVD Audio Other (5) - + DVD Audio Other (5) - + DVD Audio Other (5) - + DVD Audio Other (5) - + DVD Audio Other (5) @@ -1164,19 +1164,19 @@ Kiosk Other (5) - + Kiosk Other (5) - + Kiosk Other (5) - + Kiosk Other (5) - + Kiosk Other (5) @@ -1192,31 +1192,31 @@ Music Video (Physical) Other (5) - + Music Video (Physical) Other (5) - + Music Video (Physical) Other (5) - + Music Video (Physical) Other (5) - + Music Video (Physical) Other (5) - + Music Video (Physical) Other (5) - + Music Video (Physical) Other (5) - + Music Video (Physical) Other (5) @@ -1248,15 +1248,15 @@ Synchronization Other (5) - + Synchronization Other (5) - + Synchronization Other (5) - + Synchronization Other (5) @@ -1268,10 +1268,10 @@ - - - - + + + + @@ -1279,43 +1279,43 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + @@ -1323,71 +1323,71 @@ - - - - - - - - - - - - - - - + + + + + - - - - + + + + + + + + + + + + + + - - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + @@ -1396,10 +1396,10 @@ - - - - + + + + @@ -1414,20 +1414,20 @@ - - - - + + + + - - - - + + + + @@ -1435,19 +1435,19 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + @@ -1482,11 +1482,11 @@ Limited Tier Paid Subscription Streaming (5) - + Limited Tier Paid Subscription Streaming (5) - + Limited Tier Paid Subscription Streaming (5) @@ -1518,15 +1518,15 @@ On-Demand Streaming (Ad-Supported) Streaming (5) - + On-Demand Streaming (Ad-Supported) Streaming (5) - + On-Demand Streaming (Ad-Supported) Streaming (5) - + On-Demand Streaming (Ad-Supported) Streaming (5) @@ -1562,11 +1562,11 @@ Other Ad-Supported Streaming Streaming (5) - + Other Ad-Supported Streaming Streaming (5) - + Other Ad-Supported Streaming Streaming (5) @@ -1594,19 +1594,19 @@ Paid Subscription Streaming (5) - + Paid Subscription Streaming (5) - + Paid Subscription Streaming (5) - + Paid Subscription Streaming (5) - + Paid Subscription Streaming (5) @@ -1634,19 +1634,19 @@ SoundExchange Distributions Streaming (5) - + SoundExchange Distributions Streaming (5) - + SoundExchange Distributions Streaming (5) - + SoundExchange Distributions Streaming (5) - + SoundExchange Distributions Streaming (5) @@ -1659,105 +1659,85 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - + + + + + - - - - + + + + + + + + + + + + + + - - - - - - - - - - - - - - + + + + - - - - - - - - - - - - - - + + + + + + + + + + + + + + @@ -1766,8 +1746,18 @@ - - + + + + + + + + + + + + @@ -1775,9 +1765,19 @@ - - - + + + + + + + + + + + + + @@ -1786,48 +1786,48 @@ - - - - - - - - - - - - + + + + + + + + + + + + - - - - + + + + - - - - - - - - - - - - - - + + + + + + + + + + + + + + @@ -1835,34 +1835,34 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + - + 8 - Track Tape (2) - + 8 - Track Tape (5) - + 8 - Track Tape (5) - + 8 - Track Tape (5) @@ -1890,39 +1890,39 @@ 8 - Track Tape (5) - + Cassette Tape (2) - + Cassette Tape (5) - + Cassette Tape (5) - + Cassette Tape (5) - + Cassette Tape (5) - + Cassette Tape (5) - + Cassette Tape (5) - + Cassette Tape (5) - + Cassette Tape (5) @@ -1938,27 +1938,27 @@ Cassette Single Tape (5) - + Cassette Single Tape (5) - + Cassette Single Tape (5) - + Cassette Single Tape (5) - + Cassette Single Tape (5) - + Cassette Single Tape (5) - + Cassette Single Tape (5) @@ -1970,11 +1970,11 @@ Cassette Single Tape (5) - + Other Tapes Tape (2) - + Other Tapes Tape (5) @@ -2018,10 +2018,10 @@ - - - - + + + + @@ -2029,43 +2029,43 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + @@ -2073,71 +2073,51 @@ - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + - - - - + + + + + + + + + + + + + + - - - - + + + + - - - - - - - - - - - - - - + + + + @@ -2146,8 +2126,18 @@ - - + + + + + + + + + + + + @@ -2155,9 +2145,19 @@ - - - + + + + + + + + + + + + + @@ -2166,10 +2166,10 @@ - - - - + + + + @@ -2178,46 +2178,46 @@ + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - + + + + - - - - + + + + + + + + + + + + + + @@ -2225,88 +2225,88 @@ - - - + + + - + LP/EP Vinyl (2) - + LP/EP Vinyl (5) - + LP/EP Vinyl (5) - + LP/EP Vinyl (5) - + LP/EP Vinyl (5) - + LP/EP Vinyl (5) - + LP/EP Vinyl (5) - + LP/EP Vinyl (5) - + LP/EP Vinyl (5) - + LP/EP Vinyl (5) - + Vinyl Single Vinyl (2) - + Vinyl Single Vinyl (5) - + Vinyl Single Vinyl (5) - + Vinyl Single Vinyl (5) - + Vinyl Single Vinyl (5) - + Vinyl Single Vinyl (5) - + Vinyl Single Vinyl (5) - + Vinyl Single Vinyl (5) - + Vinyl Single Vinyl (5) - + Vinyl Single Vinyl (5) diff --git a/test/plots/music-revenue-bin.js b/test/plots/music-revenue-bin.js index bff44dbeca..37a4a19060 100644 --- a/test/plots/music-revenue-bin.js +++ b/test/plots/music-revenue-bin.js @@ -19,15 +19,19 @@ export default async function () { data, Plot.binX( {y: "sum"}, - { + Plot.windowY({ ...stack, + k: 7, y: (d) => -d.revenue, fill: "#eee", facet: "exclude" - } + }) ) ), - Plot.rectY(data, Plot.binX({y: "sum"}, {...stack, fill: "group", title: (d) => `${d.format}\n${d.group}`})), + Plot.rectY( + data, + Plot.binX({y: "sum"}, Plot.windowY({...stack, k: 7, fill: "group", title: (d) => `${d.format}\n${d.group}`})) + ), Plot.ruleY([0]) ] }); From 2b4fa98bdd68eb1cef6008492664e50af56f0d24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?= Date: Thu, 29 Sep 2022 12:37:12 +0200 Subject: [PATCH 09/17] the group transform is plan-aware --- src/transforms/group.js | 3 +- test/output/musicRevenueBars.svg | 9986 ++++++++++++++++++++++++++++++ test/plots/index.js | 1 + test/plots/music-revenue-bars.js | 49 + 4 files changed, 10038 insertions(+), 1 deletion(-) create mode 100644 test/output/musicRevenueBars.svg create mode 100644 test/plots/music-revenue-bars.js diff --git a/src/transforms/group.js b/src/transforms/group.js index 5aa82be38a..bde9b3bad0 100644 --- a/src/transforms/group.js +++ b/src/transforms/group.js @@ -31,6 +31,7 @@ import { percentile } from "../options.js"; import {basic} from "./basic.js"; +import {originals} from "../facet.js"; /** * ```js @@ -162,7 +163,7 @@ function groupn( for (const o of outputs) o.initialize(data); if (sort) sort.initialize(data); if (filter) filter.initialize(data); - for (const facet of facets) { + for (const facet of originals(facets)) { const groupFacet = []; for (const o of outputs) o.scope("facet", facet); if (sort) sort.scope("facet", facet); diff --git a/test/output/musicRevenueBars.svg b/test/output/musicRevenueBars.svg new file mode 100644 index 0000000000..67d0421b08 --- /dev/null +++ b/test/output/musicRevenueBars.svg @@ -0,0 +1,9986 @@ + + + + + Disc + + + Download + + + Other + + + Streaming + + + Tape + + + Vinyl + group + + + + 1975 + + + 1980 + + + 1985 + + + 1990 + + + 1995 + + + 2000 + + + 2005 + + + 2010 + + + 2015 + + + + + + −20 + + + + −10 + + + + 0 + + + + 10 + + + + 20 + ↑ Annual revenue (billions, adj.) + + + + + −20 + + + + −10 + + + + 0 + + + + 10 + + + + 20 + + + + + + −20 + + + + −10 + + + + 0 + + + + 10 + + + + 20 + + + + + + −20 + + + + −10 + + + + 0 + + + + 10 + + + + 20 + + + + + + −20 + + + + −10 + + + + 0 + + + + 10 + + + + 20 + + + + + + −20 + + + + −10 + + + + 0 + + + + 10 + + + + 20 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + CD + Disc (1) + + + CD + Disc (1) + + + CD + Disc (1) + + + CD + Disc (1) + + + CD + Disc (1) + + + CD + Disc (1) + + + CD + Disc (1) + + + CD + Disc (1) + + + CD + Disc (1) + + + CD + Disc (1) + + + CD + Disc (1) + + + CD + Disc (1) + + + CD + Disc (1) + + + CD + Disc (1) + + + CD + Disc (1) + + + CD + Disc (1) + + + CD + Disc (1) + + + CD + Disc (1) + + + CD + Disc (1) + + + CD + Disc (1) + + + CD + Disc (1) + + + CD + Disc (1) + + + CD + Disc (1) + + + CD + Disc (1) + + + CD + Disc (1) + + + CD + Disc (1) + + + CD + Disc (1) + + + CD + Disc (1) + + + CD + Disc (1) + + + CD + Disc (1) + + + CD + Disc (1) + + + CD + Disc (1) + + + CD + Disc (1) + + + CD + Disc (1) + + + CD + Disc (1) + + + CD + Disc (1) + + + CD + Disc (1) + + + CD + Disc (1) + + + CD + Disc (1) + + + CD + Disc (1) + + + CD + Disc (1) + + + CD + Disc (1) + + + CD + Disc (1) + + + CD + Disc (1) + + + CD + Disc (1) + + + CD + Disc (1) + + + CD + Disc (1) + + + CD Single + Disc (1) + + + CD Single + Disc (1) + + + CD Single + Disc (1) + + + CD Single + Disc (1) + + + CD Single + Disc (1) + + + CD Single + Disc (1) + + + CD Single + Disc (1) + + + CD Single + Disc (1) + + + CD Single + Disc (1) + + + CD Single + Disc (1) + + + CD Single + Disc (1) + + + CD Single + Disc (1) + + + CD Single + Disc (1) + + + CD Single + Disc (1) + + + CD Single + Disc (1) + + + CD Single + Disc (1) + + + CD Single + Disc (1) + + + CD Single + Disc (1) + + + CD Single + Disc (1) + + + CD Single + Disc (1) + + + CD Single + Disc (1) + + + CD Single + Disc (1) + + + CD Single + Disc (1) + + + CD Single + Disc (1) + + + CD Single + Disc (1) + + + CD Single + Disc (1) + + + CD Single + Disc (1) + + + CD Single + Disc (1) + + + CD Single + Disc (1) + + + CD Single + Disc (1) + + + CD Single + Disc (1) + + + CD Single + Disc (1) + + + CD Single + Disc (1) + + + CD Single + Disc (1) + + + CD Single + Disc (1) + + + CD Single + Disc (1) + + + CD Single + Disc (1) + + + CD Single + Disc (1) + + + CD Single + Disc (1) + + + CD Single + Disc (1) + + + CD Single + Disc (1) + + + CD Single + Disc (1) + + + CD Single + Disc (1) + + + CD Single + Disc (1) + + + CD Single + Disc (1) + + + CD Single + Disc (1) + + + CD Single + Disc (1) + + + SACD + Disc (1) + + + SACD + Disc (1) + + + SACD + Disc (1) + + + SACD + Disc (1) + + + SACD + Disc (1) + + + SACD + Disc (1) + + + SACD + Disc (1) + + + SACD + Disc (1) + + + SACD + Disc (1) + + + SACD + Disc (1) + + + SACD + Disc (1) + + + SACD + Disc (1) + + + SACD + Disc (1) + + + SACD + Disc (1) + + + SACD + Disc (1) + + + SACD + Disc (1) + + + SACD + Disc (1) + + + SACD + Disc (1) + + + SACD + Disc (1) + + + SACD + Disc (1) + + + SACD + Disc (1) + + + SACD + Disc (1) + + + SACD + Disc (1) + + + SACD + Disc (1) + + + SACD + Disc (1) + + + SACD + Disc (1) + + + SACD + Disc (1) + + + SACD + Disc (1) + + + SACD + Disc (1) + + + SACD + Disc (1) + + + SACD + Disc (1) + + + SACD + Disc (1) + + + SACD + Disc (1) + + + SACD + Disc (1) + + + SACD + Disc (1) + + + SACD + Disc (1) + + + SACD + Disc (1) + + + SACD + Disc (1) + + + SACD + Disc (1) + + + SACD + Disc (1) + + + SACD + Disc (1) + + + SACD + Disc (1) + + + SACD + Disc (1) + + + SACD + Disc (1) + + + SACD + Disc (1) + + + SACD + Disc (1) + + + SACD + Disc (1) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Download Album + Download (1) + + + Download Album + Download (1) + + + Download Album + Download (1) + + + Download Album + Download (1) + + + Download Album + Download (1) + + + Download Album + Download (1) + + + Download Album + Download (1) + + + Download Album + Download (1) + + + Download Album + Download (1) + + + Download Album + Download (1) + + + Download Album + Download (1) + + + Download Album + Download (1) + + + Download Album + Download (1) + + + Download Album + Download (1) + + + Download Album + Download (1) + + + Download Album + Download (1) + + + Download Album + Download (1) + + + Download Album + Download (1) + + + Download Album + Download (1) + + + Download Album + Download (1) + + + Download Album + Download (1) + + + Download Album + Download (1) + + + Download Album + Download (1) + + + Download Album + Download (1) + + + Download Album + Download (1) + + + Download Album + Download (1) + + + Download Album + Download (1) + + + Download Album + Download (1) + + + Download Album + Download (1) + + + Download Album + Download (1) + + + Download Album + Download (1) + + + Download Album + Download (1) + + + Download Album + Download (1) + + + Download Album + Download (1) + + + Download Album + Download (1) + + + Download Album + Download (1) + + + Download Album + Download (1) + + + Download Album + Download (1) + + + Download Album + Download (1) + + + Download Album + Download (1) + + + Download Album + Download (1) + + + Download Album + Download (1) + + + Download Album + Download (1) + + + Download Album + Download (1) + + + Download Album + Download (1) + + + Download Album + Download (1) + + + Download Album + Download (1) + + + Download Music Video + Download (1) + + + Download Music Video + Download (1) + + + Download Music Video + Download (1) + + + Download Music Video + Download (1) + + + Download Music Video + Download (1) + + + Download Music Video + Download (1) + + + Download Music Video + Download (1) + + + Download Music Video + Download (1) + + + Download Music Video + Download (1) + + + Download Music Video + Download (1) + + + Download Music Video + Download (1) + + + Download Music Video + Download (1) + + + Download Music Video + Download (1) + + + Download Music Video + Download (1) + + + Download Music Video + Download (1) + + + Download Music Video + Download (1) + + + Download Music Video + Download (1) + + + Download Music Video + Download (1) + + + Download Music Video + Download (1) + + + Download Music Video + Download (1) + + + Download Music Video + Download (1) + + + Download Music Video + Download (1) + + + Download Music Video + Download (1) + + + Download Music Video + Download (1) + + + Download Music Video + Download (1) + + + Download Music Video + Download (1) + + + Download Music Video + Download (1) + + + Download Music Video + Download (1) + + + Download Music Video + Download (1) + + + Download Music Video + Download (1) + + + Download Music Video + Download (1) + + + Download Music Video + Download (1) + + + Download Music Video + Download (1) + + + Download Music Video + Download (1) + + + Download Music Video + Download (1) + + + Download Music Video + Download (1) + + + Download Music Video + Download (1) + + + Download Music Video + Download (1) + + + Download Music Video + Download (1) + + + Download Music Video + Download (1) + + + Download Music Video + Download (1) + + + Download Music Video + Download (1) + + + Download Music Video + Download (1) + + + Download Music Video + Download (1) + + + Download Music Video + Download (1) + + + Download Music Video + Download (1) + + + Download Music Video + Download (1) + + + Download Single + Download (1) + + + Download Single + Download (1) + + + Download Single + Download (1) + + + Download Single + Download (1) + + + Download Single + Download (1) + + + Download Single + Download (1) + + + Download Single + Download (1) + + + Download Single + Download (1) + + + Download Single + Download (1) + + + Download Single + Download (1) + + + Download Single + Download (1) + + + Download Single + Download (1) + + + Download Single + Download (1) + + + Download Single + Download (1) + + + Download Single + Download (1) + + + Download Single + Download (1) + + + Download Single + Download (1) + + + Download Single + Download (1) + + + Download Single + Download (1) + + + Download Single + Download (1) + + + Download Single + Download (1) + + + Download Single + Download (1) + + + Download Single + Download (1) + + + Download Single + Download (1) + + + Download Single + Download (1) + + + Download Single + Download (1) + + + Download Single + Download (1) + + + Download Single + Download (1) + + + Download Single + Download (1) + + + Download Single + Download (1) + + + Download Single + Download (1) + + + Download Single + Download (1) + + + Download Single + Download (1) + + + Download Single + Download (1) + + + Download Single + Download (1) + + + Download Single + Download (1) + + + Download Single + Download (1) + + + Download Single + Download (1) + + + Download Single + Download (1) + + + Download Single + Download (1) + + + Download Single + Download (1) + + + Download Single + Download (1) + + + Download Single + Download (1) + + + Download Single + Download (1) + + + Download Single + Download (1) + + + Download Single + Download (1) + + + Download Single + Download (1) + + + Other Digital + Download (1) + + + Other Digital + Download (1) + + + Other Digital + Download (1) + + + Other Digital + Download (1) + + + Other Digital + Download (1) + + + Other Digital + Download (1) + + + Other Digital + Download (1) + + + Other Digital + Download (1) + + + Other Digital + Download (1) + + + Other Digital + Download (1) + + + Other Digital + Download (1) + + + Other Digital + Download (1) + + + Other Digital + Download (1) + + + Other Digital + Download (1) + + + Other Digital + Download (1) + + + Other Digital + Download (1) + + + Other Digital + Download (1) + + + Other Digital + Download (1) + + + Other Digital + Download (1) + + + Other Digital + Download (1) + + + Other Digital + Download (1) + + + Other Digital + Download (1) + + + Other Digital + Download (1) + + + Other Digital + Download (1) + + + Other Digital + Download (1) + + + Other Digital + Download (1) + + + Other Digital + Download (1) + + + Other Digital + Download (1) + + + Other Digital + Download (1) + + + Other Digital + Download (1) + + + Other Digital + Download (1) + + + Other Digital + Download (1) + + + Other Digital + Download (1) + + + Other Digital + Download (1) + + + Other Digital + Download (1) + + + Other Digital + Download (1) + + + Other Digital + Download (1) + + + Other Digital + Download (1) + + + Other Digital + Download (1) + + + Other Digital + Download (1) + + + Other Digital + Download (1) + + + Other Digital + Download (1) + + + Other Digital + Download (1) + + + Other Digital + Download (1) + + + Other Digital + Download (1) + + + Other Digital + Download (1) + + + Other Digital + Download (1) + + + Ringtones & Ringbacks + Download (1) + + + Ringtones & Ringbacks + Download (1) + + + Ringtones & Ringbacks + Download (1) + + + Ringtones & Ringbacks + Download (1) + + + Ringtones & Ringbacks + Download (1) + + + Ringtones & Ringbacks + Download (1) + + + Ringtones & Ringbacks + Download (1) + + + Ringtones & Ringbacks + Download (1) + + + Ringtones & Ringbacks + Download (1) + + + Ringtones & Ringbacks + Download (1) + + + Ringtones & Ringbacks + Download (1) + + + Ringtones & Ringbacks + Download (1) + + + Ringtones & Ringbacks + Download (1) + + + Ringtones & Ringbacks + Download (1) + + + Ringtones & Ringbacks + Download (1) + + + Ringtones & Ringbacks + Download (1) + + + Ringtones & Ringbacks + Download (1) + + + Ringtones & Ringbacks + Download (1) + + + Ringtones & Ringbacks + Download (1) + + + Ringtones & Ringbacks + Download (1) + + + Ringtones & Ringbacks + Download (1) + + + Ringtones & Ringbacks + Download (1) + + + Ringtones & Ringbacks + Download (1) + + + Ringtones & Ringbacks + Download (1) + + + Ringtones & Ringbacks + Download (1) + + + Ringtones & Ringbacks + Download (1) + + + Ringtones & Ringbacks + Download (1) + + + Ringtones & Ringbacks + Download (1) + + + Ringtones & Ringbacks + Download (1) + + + Ringtones & Ringbacks + Download (1) + + + Ringtones & Ringbacks + Download (1) + + + Ringtones & Ringbacks + Download (1) + + + Ringtones & Ringbacks + Download (1) + + + Ringtones & Ringbacks + Download (1) + + + Ringtones & Ringbacks + Download (1) + + + Ringtones & Ringbacks + Download (1) + + + Ringtones & Ringbacks + Download (1) + + + Ringtones & Ringbacks + Download (1) + + + Ringtones & Ringbacks + Download (1) + + + Ringtones & Ringbacks + Download (1) + + + Ringtones & Ringbacks + Download (1) + + + Ringtones & Ringbacks + Download (1) + + + Ringtones & Ringbacks + Download (1) + + + Ringtones & Ringbacks + Download (1) + + + Ringtones & Ringbacks + Download (1) + + + Ringtones & Ringbacks + Download (1) + + + Ringtones & Ringbacks + Download (1) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DVD Audio + Other (1) + + + DVD Audio + Other (1) + + + DVD Audio + Other (1) + + + DVD Audio + Other (1) + + + DVD Audio + Other (1) + + + DVD Audio + Other (1) + + + DVD Audio + Other (1) + + + DVD Audio + Other (1) + + + DVD Audio + Other (1) + + + DVD Audio + Other (1) + + + DVD Audio + Other (1) + + + DVD Audio + Other (1) + + + DVD Audio + Other (1) + + + DVD Audio + Other (1) + + + DVD Audio + Other (1) + + + DVD Audio + Other (1) + + + DVD Audio + Other (1) + + + DVD Audio + Other (1) + + + DVD Audio + Other (1) + + + DVD Audio + Other (1) + + + DVD Audio + Other (1) + + + DVD Audio + Other (1) + + + DVD Audio + Other (1) + + + DVD Audio + Other (1) + + + DVD Audio + Other (1) + + + DVD Audio + Other (1) + + + DVD Audio + Other (1) + + + DVD Audio + Other (1) + + + DVD Audio + Other (1) + + + DVD Audio + Other (1) + + + DVD Audio + Other (1) + + + DVD Audio + Other (1) + + + DVD Audio + Other (1) + + + DVD Audio + Other (1) + + + DVD Audio + Other (1) + + + DVD Audio + Other (1) + + + DVD Audio + Other (1) + + + DVD Audio + Other (1) + + + DVD Audio + Other (1) + + + DVD Audio + Other (1) + + + DVD Audio + Other (1) + + + DVD Audio + Other (1) + + + DVD Audio + Other (1) + + + DVD Audio + Other (1) + + + DVD Audio + Other (1) + + + DVD Audio + Other (1) + + + DVD Audio + Other (1) + + + Kiosk + Other (1) + + + Kiosk + Other (1) + + + Kiosk + Other (1) + + + Kiosk + Other (1) + + + Kiosk + Other (1) + + + Kiosk + Other (1) + + + Kiosk + Other (1) + + + Kiosk + Other (1) + + + Kiosk + Other (1) + + + Kiosk + Other (1) + + + Kiosk + Other (1) + + + Kiosk + Other (1) + + + Kiosk + Other (1) + + + Kiosk + Other (1) + + + Kiosk + Other (1) + + + Kiosk + Other (1) + + + Kiosk + Other (1) + + + Kiosk + Other (1) + + + Kiosk + Other (1) + + + Kiosk + Other (1) + + + Kiosk + Other (1) + + + Kiosk + Other (1) + + + Kiosk + Other (1) + + + Kiosk + Other (1) + + + Kiosk + Other (1) + + + Kiosk + Other (1) + + + Kiosk + Other (1) + + + Kiosk + Other (1) + + + Kiosk + Other (1) + + + Kiosk + Other (1) + + + Kiosk + Other (1) + + + Kiosk + Other (1) + + + Kiosk + Other (1) + + + Kiosk + Other (1) + + + Kiosk + Other (1) + + + Kiosk + Other (1) + + + Kiosk + Other (1) + + + Kiosk + Other (1) + + + Kiosk + Other (1) + + + Kiosk + Other (1) + + + Kiosk + Other (1) + + + Kiosk + Other (1) + + + Kiosk + Other (1) + + + Kiosk + Other (1) + + + Kiosk + Other (1) + + + Kiosk + Other (1) + + + Kiosk + Other (1) + + + Music Video (Physical) + Other (1) + + + Music Video (Physical) + Other (1) + + + Music Video (Physical) + Other (1) + + + Music Video (Physical) + Other (1) + + + Music Video (Physical) + Other (1) + + + Music Video (Physical) + Other (1) + + + Music Video (Physical) + Other (1) + + + Music Video (Physical) + Other (1) + + + Music Video (Physical) + Other (1) + + + Music Video (Physical) + Other (1) + + + Music Video (Physical) + Other (1) + + + Music Video (Physical) + Other (1) + + + Music Video (Physical) + Other (1) + + + Music Video (Physical) + Other (1) + + + Music Video (Physical) + Other (1) + + + Music Video (Physical) + Other (1) + + + Music Video (Physical) + Other (1) + + + Music Video (Physical) + Other (1) + + + Music Video (Physical) + Other (1) + + + Music Video (Physical) + Other (1) + + + Music Video (Physical) + Other (1) + + + Music Video (Physical) + Other (1) + + + Music Video (Physical) + Other (1) + + + Music Video (Physical) + Other (1) + + + Music Video (Physical) + Other (1) + + + Music Video (Physical) + Other (1) + + + Music Video (Physical) + Other (1) + + + Music Video (Physical) + Other (1) + + + Music Video (Physical) + Other (1) + + + Music Video (Physical) + Other (1) + + + Music Video (Physical) + Other (1) + + + Music Video (Physical) + Other (1) + + + Music Video (Physical) + Other (1) + + + Music Video (Physical) + Other (1) + + + Music Video (Physical) + Other (1) + + + Music Video (Physical) + Other (1) + + + Music Video (Physical) + Other (1) + + + Music Video (Physical) + Other (1) + + + Music Video (Physical) + Other (1) + + + Music Video (Physical) + Other (1) + + + Music Video (Physical) + Other (1) + + + Music Video (Physical) + Other (1) + + + Music Video (Physical) + Other (1) + + + Music Video (Physical) + Other (1) + + + Music Video (Physical) + Other (1) + + + Music Video (Physical) + Other (1) + + + Music Video (Physical) + Other (1) + + + Synchronization + Other (1) + + + Synchronization + Other (1) + + + Synchronization + Other (1) + + + Synchronization + Other (1) + + + Synchronization + Other (1) + + + Synchronization + Other (1) + + + Synchronization + Other (1) + + + Synchronization + Other (1) + + + Synchronization + Other (1) + + + Synchronization + Other (1) + + + Synchronization + Other (1) + + + Synchronization + Other (1) + + + Synchronization + Other (1) + + + Synchronization + Other (1) + + + Synchronization + Other (1) + + + Synchronization + Other (1) + + + Synchronization + Other (1) + + + Synchronization + Other (1) + + + Synchronization + Other (1) + + + Synchronization + Other (1) + + + Synchronization + Other (1) + + + Synchronization + Other (1) + + + Synchronization + Other (1) + + + Synchronization + Other (1) + + + Synchronization + Other (1) + + + Synchronization + Other (1) + + + Synchronization + Other (1) + + + Synchronization + Other (1) + + + Synchronization + Other (1) + + + Synchronization + Other (1) + + + Synchronization + Other (1) + + + Synchronization + Other (1) + + + Synchronization + Other (1) + + + Synchronization + Other (1) + + + Synchronization + Other (1) + + + Synchronization + Other (1) + + + Synchronization + Other (1) + + + Synchronization + Other (1) + + + Synchronization + Other (1) + + + Synchronization + Other (1) + + + Synchronization + Other (1) + + + Synchronization + Other (1) + + + Synchronization + Other (1) + + + Synchronization + Other (1) + + + Synchronization + Other (1) + + + Synchronization + Other (1) + + + Synchronization + Other (1) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Limited Tier Paid Subscription + Streaming (1) + + + Limited Tier Paid Subscription + Streaming (1) + + + Limited Tier Paid Subscription + Streaming (1) + + + Limited Tier Paid Subscription + Streaming (1) + + + Limited Tier Paid Subscription + Streaming (1) + + + Limited Tier Paid Subscription + Streaming (1) + + + Limited Tier Paid Subscription + Streaming (1) + + + Limited Tier Paid Subscription + Streaming (1) + + + Limited Tier Paid Subscription + Streaming (1) + + + Limited Tier Paid Subscription + Streaming (1) + + + Limited Tier Paid Subscription + Streaming (1) + + + Limited Tier Paid Subscription + Streaming (1) + + + Limited Tier Paid Subscription + Streaming (1) + + + Limited Tier Paid Subscription + Streaming (1) + + + Limited Tier Paid Subscription + Streaming (1) + + + Limited Tier Paid Subscription + Streaming (1) + + + Limited Tier Paid Subscription + Streaming (1) + + + Limited Tier Paid Subscription + Streaming (1) + + + Limited Tier Paid Subscription + Streaming (1) + + + Limited Tier Paid Subscription + Streaming (1) + + + Limited Tier Paid Subscription + Streaming (1) + + + Limited Tier Paid Subscription + Streaming (1) + + + Limited Tier Paid Subscription + Streaming (1) + + + Limited Tier Paid Subscription + Streaming (1) + + + Limited Tier Paid Subscription + Streaming (1) + + + Limited Tier Paid Subscription + Streaming (1) + + + Limited Tier Paid Subscription + Streaming (1) + + + Limited Tier Paid Subscription + Streaming (1) + + + Limited Tier Paid Subscription + Streaming (1) + + + Limited Tier Paid Subscription + Streaming (1) + + + Limited Tier Paid Subscription + Streaming (1) + + + Limited Tier Paid Subscription + Streaming (1) + + + Limited Tier Paid Subscription + Streaming (1) + + + Limited Tier Paid Subscription + Streaming (1) + + + Limited Tier Paid Subscription + Streaming (1) + + + Limited Tier Paid Subscription + Streaming (1) + + + Limited Tier Paid Subscription + Streaming (1) + + + Limited Tier Paid Subscription + Streaming (1) + + + Limited Tier Paid Subscription + Streaming (1) + + + Limited Tier Paid Subscription + Streaming (1) + + + Limited Tier Paid Subscription + Streaming (1) + + + Limited Tier Paid Subscription + Streaming (1) + + + Limited Tier Paid Subscription + Streaming (1) + + + Limited Tier Paid Subscription + Streaming (1) + + + Limited Tier Paid Subscription + Streaming (1) + + + Limited Tier Paid Subscription + Streaming (1) + + + Limited Tier Paid Subscription + Streaming (1) + + + On-Demand Streaming (Ad-Supported) + Streaming (1) + + + On-Demand Streaming (Ad-Supported) + Streaming (1) + + + On-Demand Streaming (Ad-Supported) + Streaming (1) + + + On-Demand Streaming (Ad-Supported) + Streaming (1) + + + On-Demand Streaming (Ad-Supported) + Streaming (1) + + + On-Demand Streaming (Ad-Supported) + Streaming (1) + + + On-Demand Streaming (Ad-Supported) + Streaming (1) + + + On-Demand Streaming (Ad-Supported) + Streaming (1) + + + On-Demand Streaming (Ad-Supported) + Streaming (1) + + + On-Demand Streaming (Ad-Supported) + Streaming (1) + + + On-Demand Streaming (Ad-Supported) + Streaming (1) + + + On-Demand Streaming (Ad-Supported) + Streaming (1) + + + On-Demand Streaming (Ad-Supported) + Streaming (1) + + + On-Demand Streaming (Ad-Supported) + Streaming (1) + + + On-Demand Streaming (Ad-Supported) + Streaming (1) + + + On-Demand Streaming (Ad-Supported) + Streaming (1) + + + On-Demand Streaming (Ad-Supported) + Streaming (1) + + + On-Demand Streaming (Ad-Supported) + Streaming (1) + + + On-Demand Streaming (Ad-Supported) + Streaming (1) + + + On-Demand Streaming (Ad-Supported) + Streaming (1) + + + On-Demand Streaming (Ad-Supported) + Streaming (1) + + + On-Demand Streaming (Ad-Supported) + Streaming (1) + + + On-Demand Streaming (Ad-Supported) + Streaming (1) + + + On-Demand Streaming (Ad-Supported) + Streaming (1) + + + On-Demand Streaming (Ad-Supported) + Streaming (1) + + + On-Demand Streaming (Ad-Supported) + Streaming (1) + + + On-Demand Streaming (Ad-Supported) + Streaming (1) + + + On-Demand Streaming (Ad-Supported) + Streaming (1) + + + On-Demand Streaming (Ad-Supported) + Streaming (1) + + + On-Demand Streaming (Ad-Supported) + Streaming (1) + + + On-Demand Streaming (Ad-Supported) + Streaming (1) + + + On-Demand Streaming (Ad-Supported) + Streaming (1) + + + On-Demand Streaming (Ad-Supported) + Streaming (1) + + + On-Demand Streaming (Ad-Supported) + Streaming (1) + + + On-Demand Streaming (Ad-Supported) + Streaming (1) + + + On-Demand Streaming (Ad-Supported) + Streaming (1) + + + On-Demand Streaming (Ad-Supported) + Streaming (1) + + + On-Demand Streaming (Ad-Supported) + Streaming (1) + + + On-Demand Streaming (Ad-Supported) + Streaming (1) + + + On-Demand Streaming (Ad-Supported) + Streaming (1) + + + On-Demand Streaming (Ad-Supported) + Streaming (1) + + + On-Demand Streaming (Ad-Supported) + Streaming (1) + + + On-Demand Streaming (Ad-Supported) + Streaming (1) + + + On-Demand Streaming (Ad-Supported) + Streaming (1) + + + On-Demand Streaming (Ad-Supported) + Streaming (1) + + + On-Demand Streaming (Ad-Supported) + Streaming (1) + + + On-Demand Streaming (Ad-Supported) + Streaming (1) + + + Other Ad-Supported Streaming + Streaming (1) + + + Other Ad-Supported Streaming + Streaming (1) + + + Other Ad-Supported Streaming + Streaming (1) + + + Other Ad-Supported Streaming + Streaming (1) + + + Other Ad-Supported Streaming + Streaming (1) + + + Other Ad-Supported Streaming + Streaming (1) + + + Other Ad-Supported Streaming + Streaming (1) + + + Other Ad-Supported Streaming + Streaming (1) + + + Other Ad-Supported Streaming + Streaming (1) + + + Other Ad-Supported Streaming + Streaming (1) + + + Other Ad-Supported Streaming + Streaming (1) + + + Other Ad-Supported Streaming + Streaming (1) + + + Other Ad-Supported Streaming + Streaming (1) + + + Other Ad-Supported Streaming + Streaming (1) + + + Other Ad-Supported Streaming + Streaming (1) + + + Other Ad-Supported Streaming + Streaming (1) + + + Other Ad-Supported Streaming + Streaming (1) + + + Other Ad-Supported Streaming + Streaming (1) + + + Other Ad-Supported Streaming + Streaming (1) + + + Other Ad-Supported Streaming + Streaming (1) + + + Other Ad-Supported Streaming + Streaming (1) + + + Other Ad-Supported Streaming + Streaming (1) + + + Other Ad-Supported Streaming + Streaming (1) + + + Other Ad-Supported Streaming + Streaming (1) + + + Other Ad-Supported Streaming + Streaming (1) + + + Other Ad-Supported Streaming + Streaming (1) + + + Other Ad-Supported Streaming + Streaming (1) + + + Other Ad-Supported Streaming + Streaming (1) + + + Other Ad-Supported Streaming + Streaming (1) + + + Other Ad-Supported Streaming + Streaming (1) + + + Other Ad-Supported Streaming + Streaming (1) + + + Other Ad-Supported Streaming + Streaming (1) + + + Other Ad-Supported Streaming + Streaming (1) + + + Other Ad-Supported Streaming + Streaming (1) + + + Other Ad-Supported Streaming + Streaming (1) + + + Other Ad-Supported Streaming + Streaming (1) + + + Other Ad-Supported Streaming + Streaming (1) + + + Other Ad-Supported Streaming + Streaming (1) + + + Other Ad-Supported Streaming + Streaming (1) + + + Other Ad-Supported Streaming + Streaming (1) + + + Other Ad-Supported Streaming + Streaming (1) + + + Other Ad-Supported Streaming + Streaming (1) + + + Other Ad-Supported Streaming + Streaming (1) + + + Other Ad-Supported Streaming + Streaming (1) + + + Other Ad-Supported Streaming + Streaming (1) + + + Other Ad-Supported Streaming + Streaming (1) + + + Other Ad-Supported Streaming + Streaming (1) + + + Paid Subscription + Streaming (1) + + + Paid Subscription + Streaming (1) + + + Paid Subscription + Streaming (1) + + + Paid Subscription + Streaming (1) + + + Paid Subscription + Streaming (1) + + + Paid Subscription + Streaming (1) + + + Paid Subscription + Streaming (1) + + + Paid Subscription + Streaming (1) + + + Paid Subscription + Streaming (1) + + + Paid Subscription + Streaming (1) + + + Paid Subscription + Streaming (1) + + + Paid Subscription + Streaming (1) + + + Paid Subscription + Streaming (1) + + + Paid Subscription + Streaming (1) + + + Paid Subscription + Streaming (1) + + + Paid Subscription + Streaming (1) + + + Paid Subscription + Streaming (1) + + + Paid Subscription + Streaming (1) + + + Paid Subscription + Streaming (1) + + + Paid Subscription + Streaming (1) + + + Paid Subscription + Streaming (1) + + + Paid Subscription + Streaming (1) + + + Paid Subscription + Streaming (1) + + + Paid Subscription + Streaming (1) + + + Paid Subscription + Streaming (1) + + + Paid Subscription + Streaming (1) + + + Paid Subscription + Streaming (1) + + + Paid Subscription + Streaming (1) + + + Paid Subscription + Streaming (1) + + + Paid Subscription + Streaming (1) + + + Paid Subscription + Streaming (1) + + + Paid Subscription + Streaming (1) + + + Paid Subscription + Streaming (1) + + + Paid Subscription + Streaming (1) + + + Paid Subscription + Streaming (1) + + + Paid Subscription + Streaming (1) + + + Paid Subscription + Streaming (1) + + + Paid Subscription + Streaming (1) + + + Paid Subscription + Streaming (1) + + + Paid Subscription + Streaming (1) + + + Paid Subscription + Streaming (1) + + + Paid Subscription + Streaming (1) + + + Paid Subscription + Streaming (1) + + + Paid Subscription + Streaming (1) + + + Paid Subscription + Streaming (1) + + + Paid Subscription + Streaming (1) + + + Paid Subscription + Streaming (1) + + + SoundExchange Distributions + Streaming (1) + + + SoundExchange Distributions + Streaming (1) + + + SoundExchange Distributions + Streaming (1) + + + SoundExchange Distributions + Streaming (1) + + + SoundExchange Distributions + Streaming (1) + + + SoundExchange Distributions + Streaming (1) + + + SoundExchange Distributions + Streaming (1) + + + SoundExchange Distributions + Streaming (1) + + + SoundExchange Distributions + Streaming (1) + + + SoundExchange Distributions + Streaming (1) + + + SoundExchange Distributions + Streaming (1) + + + SoundExchange Distributions + Streaming (1) + + + SoundExchange Distributions + Streaming (1) + + + SoundExchange Distributions + Streaming (1) + + + SoundExchange Distributions + Streaming (1) + + + SoundExchange Distributions + Streaming (1) + + + SoundExchange Distributions + Streaming (1) + + + SoundExchange Distributions + Streaming (1) + + + SoundExchange Distributions + Streaming (1) + + + SoundExchange Distributions + Streaming (1) + + + SoundExchange Distributions + Streaming (1) + + + SoundExchange Distributions + Streaming (1) + + + SoundExchange Distributions + Streaming (1) + + + SoundExchange Distributions + Streaming (1) + + + SoundExchange Distributions + Streaming (1) + + + SoundExchange Distributions + Streaming (1) + + + SoundExchange Distributions + Streaming (1) + + + SoundExchange Distributions + Streaming (1) + + + SoundExchange Distributions + Streaming (1) + + + SoundExchange Distributions + Streaming (1) + + + SoundExchange Distributions + Streaming (1) + + + SoundExchange Distributions + Streaming (1) + + + SoundExchange Distributions + Streaming (1) + + + SoundExchange Distributions + Streaming (1) + + + SoundExchange Distributions + Streaming (1) + + + SoundExchange Distributions + Streaming (1) + + + SoundExchange Distributions + Streaming (1) + + + SoundExchange Distributions + Streaming (1) + + + SoundExchange Distributions + Streaming (1) + + + SoundExchange Distributions + Streaming (1) + + + SoundExchange Distributions + Streaming (1) + + + SoundExchange Distributions + Streaming (1) + + + SoundExchange Distributions + Streaming (1) + + + SoundExchange Distributions + Streaming (1) + + + SoundExchange Distributions + Streaming (1) + + + SoundExchange Distributions + Streaming (1) + + + SoundExchange Distributions + Streaming (1) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 8 - Track + Tape (1) + + + 8 - Track + Tape (1) + + + 8 - Track + Tape (1) + + + 8 - Track + Tape (1) + + + 8 - Track + Tape (1) + + + 8 - Track + Tape (1) + + + 8 - Track + Tape (1) + + + 8 - Track + Tape (1) + + + 8 - Track + Tape (1) + + + 8 - Track + Tape (1) + + + 8 - Track + Tape (1) + + + 8 - Track + Tape (1) + + + 8 - Track + Tape (1) + + + 8 - Track + Tape (1) + + + 8 - Track + Tape (1) + + + 8 - Track + Tape (1) + + + 8 - Track + Tape (1) + + + 8 - Track + Tape (1) + + + 8 - Track + Tape (1) + + + 8 - Track + Tape (1) + + + 8 - Track + Tape (1) + + + 8 - Track + Tape (1) + + + 8 - Track + Tape (1) + + + 8 - Track + Tape (1) + + + 8 - Track + Tape (1) + + + 8 - Track + Tape (1) + + + 8 - Track + Tape (1) + + + 8 - Track + Tape (1) + + + 8 - Track + Tape (1) + + + 8 - Track + Tape (1) + + + 8 - Track + Tape (1) + + + 8 - Track + Tape (1) + + + 8 - Track + Tape (1) + + + 8 - Track + Tape (1) + + + 8 - Track + Tape (1) + + + 8 - Track + Tape (1) + + + 8 - Track + Tape (1) + + + 8 - Track + Tape (1) + + + 8 - Track + Tape (1) + + + 8 - Track + Tape (1) + + + 8 - Track + Tape (1) + + + 8 - Track + Tape (1) + + + 8 - Track + Tape (1) + + + 8 - Track + Tape (1) + + + 8 - Track + Tape (1) + + + 8 - Track + Tape (1) + + + 8 - Track + Tape (1) + + + Cassette + Tape (1) + + + Cassette + Tape (1) + + + Cassette + Tape (1) + + + Cassette + Tape (1) + + + Cassette + Tape (1) + + + Cassette + Tape (1) + + + Cassette + Tape (1) + + + Cassette + Tape (1) + + + Cassette + Tape (1) + + + Cassette + Tape (1) + + + Cassette + Tape (1) + + + Cassette + Tape (1) + + + Cassette + Tape (1) + + + Cassette + Tape (1) + + + Cassette + Tape (1) + + + Cassette + Tape (1) + + + Cassette + Tape (1) + + + Cassette + Tape (1) + + + Cassette + Tape (1) + + + Cassette + Tape (1) + + + Cassette + Tape (1) + + + Cassette + Tape (1) + + + Cassette + Tape (1) + + + Cassette + Tape (1) + + + Cassette + Tape (1) + + + Cassette + Tape (1) + + + Cassette + Tape (1) + + + Cassette + Tape (1) + + + Cassette + Tape (1) + + + Cassette + Tape (1) + + + Cassette + Tape (1) + + + Cassette + Tape (1) + + + Cassette + Tape (1) + + + Cassette + Tape (1) + + + Cassette + Tape (1) + + + Cassette + Tape (1) + + + Cassette + Tape (1) + + + Cassette + Tape (1) + + + Cassette + Tape (1) + + + Cassette + Tape (1) + + + Cassette + Tape (1) + + + Cassette + Tape (1) + + + Cassette + Tape (1) + + + Cassette + Tape (1) + + + Cassette + Tape (1) + + + Cassette + Tape (1) + + + Cassette + Tape (1) + + + Cassette Single + Tape (1) + + + Cassette Single + Tape (1) + + + Cassette Single + Tape (1) + + + Cassette Single + Tape (1) + + + Cassette Single + Tape (1) + + + Cassette Single + Tape (1) + + + Cassette Single + Tape (1) + + + Cassette Single + Tape (1) + + + Cassette Single + Tape (1) + + + Cassette Single + Tape (1) + + + Cassette Single + Tape (1) + + + Cassette Single + Tape (1) + + + Cassette Single + Tape (1) + + + Cassette Single + Tape (1) + + + Cassette Single + Tape (1) + + + Cassette Single + Tape (1) + + + Cassette Single + Tape (1) + + + Cassette Single + Tape (1) + + + Cassette Single + Tape (1) + + + Cassette Single + Tape (1) + + + Cassette Single + Tape (1) + + + Cassette Single + Tape (1) + + + Cassette Single + Tape (1) + + + Cassette Single + Tape (1) + + + Cassette Single + Tape (1) + + + Cassette Single + Tape (1) + + + Cassette Single + Tape (1) + + + Cassette Single + Tape (1) + + + Cassette Single + Tape (1) + + + Cassette Single + Tape (1) + + + Cassette Single + Tape (1) + + + Cassette Single + Tape (1) + + + Cassette Single + Tape (1) + + + Cassette Single + Tape (1) + + + Cassette Single + Tape (1) + + + Cassette Single + Tape (1) + + + Cassette Single + Tape (1) + + + Cassette Single + Tape (1) + + + Cassette Single + Tape (1) + + + Cassette Single + Tape (1) + + + Cassette Single + Tape (1) + + + Cassette Single + Tape (1) + + + Cassette Single + Tape (1) + + + Cassette Single + Tape (1) + + + Cassette Single + Tape (1) + + + Cassette Single + Tape (1) + + + Cassette Single + Tape (1) + + + Other Tapes + Tape (1) + + + Other Tapes + Tape (1) + + + Other Tapes + Tape (1) + + + Other Tapes + Tape (1) + + + Other Tapes + Tape (1) + + + Other Tapes + Tape (1) + + + Other Tapes + Tape (1) + + + Other Tapes + Tape (1) + + + Other Tapes + Tape (1) + + + Other Tapes + Tape (1) + + + Other Tapes + Tape (1) + + + Other Tapes + Tape (1) + + + Other Tapes + Tape (1) + + + Other Tapes + Tape (1) + + + Other Tapes + Tape (1) + + + Other Tapes + Tape (1) + + + Other Tapes + Tape (1) + + + Other Tapes + Tape (1) + + + Other Tapes + Tape (1) + + + Other Tapes + Tape (1) + + + Other Tapes + Tape (1) + + + Other Tapes + Tape (1) + + + Other Tapes + Tape (1) + + + Other Tapes + Tape (1) + + + Other Tapes + Tape (1) + + + Other Tapes + Tape (1) + + + Other Tapes + Tape (1) + + + Other Tapes + Tape (1) + + + Other Tapes + Tape (1) + + + Other Tapes + Tape (1) + + + Other Tapes + Tape (1) + + + Other Tapes + Tape (1) + + + Other Tapes + Tape (1) + + + Other Tapes + Tape (1) + + + Other Tapes + Tape (1) + + + Other Tapes + Tape (1) + + + Other Tapes + Tape (1) + + + Other Tapes + Tape (1) + + + Other Tapes + Tape (1) + + + Other Tapes + Tape (1) + + + Other Tapes + Tape (1) + + + Other Tapes + Tape (1) + + + Other Tapes + Tape (1) + + + Other Tapes + Tape (1) + + + Other Tapes + Tape (1) + + + Other Tapes + Tape (1) + + + Other Tapes + Tape (1) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + LP/EP + Vinyl (1) + + + LP/EP + Vinyl (1) + + + LP/EP + Vinyl (1) + + + LP/EP + Vinyl (1) + + + LP/EP + Vinyl (1) + + + LP/EP + Vinyl (1) + + + LP/EP + Vinyl (1) + + + LP/EP + Vinyl (1) + + + LP/EP + Vinyl (1) + + + LP/EP + Vinyl (1) + + + LP/EP + Vinyl (1) + + + LP/EP + Vinyl (1) + + + LP/EP + Vinyl (1) + + + LP/EP + Vinyl (1) + + + LP/EP + Vinyl (1) + + + LP/EP + Vinyl (1) + + + LP/EP + Vinyl (1) + + + LP/EP + Vinyl (1) + + + LP/EP + Vinyl (1) + + + LP/EP + Vinyl (1) + + + LP/EP + Vinyl (1) + + + LP/EP + Vinyl (1) + + + LP/EP + Vinyl (1) + + + LP/EP + Vinyl (1) + + + LP/EP + Vinyl (1) + + + LP/EP + Vinyl (1) + + + LP/EP + Vinyl (1) + + + LP/EP + Vinyl (1) + + + LP/EP + Vinyl (1) + + + LP/EP + Vinyl (1) + + + LP/EP + Vinyl (1) + + + LP/EP + Vinyl (1) + + + LP/EP + Vinyl (1) + + + LP/EP + Vinyl (1) + + + LP/EP + Vinyl (1) + + + LP/EP + Vinyl (1) + + + LP/EP + Vinyl (1) + + + LP/EP + Vinyl (1) + + + LP/EP + Vinyl (1) + + + LP/EP + Vinyl (1) + + + LP/EP + Vinyl (1) + + + LP/EP + Vinyl (1) + + + LP/EP + Vinyl (1) + + + LP/EP + Vinyl (1) + + + LP/EP + Vinyl (1) + + + LP/EP + Vinyl (1) + + + LP/EP + Vinyl (1) + + + Vinyl Single + Vinyl (1) + + + Vinyl Single + Vinyl (1) + + + Vinyl Single + Vinyl (1) + + + Vinyl Single + Vinyl (1) + + + Vinyl Single + Vinyl (1) + + + Vinyl Single + Vinyl (1) + + + Vinyl Single + Vinyl (1) + + + Vinyl Single + Vinyl (1) + + + Vinyl Single + Vinyl (1) + + + Vinyl Single + Vinyl (1) + + + Vinyl Single + Vinyl (1) + + + Vinyl Single + Vinyl (1) + + + Vinyl Single + Vinyl (1) + + + Vinyl Single + Vinyl (1) + + + Vinyl Single + Vinyl (1) + + + Vinyl Single + Vinyl (1) + + + Vinyl Single + Vinyl (1) + + + Vinyl Single + Vinyl (1) + + + Vinyl Single + Vinyl (1) + + + Vinyl Single + Vinyl (1) + + + Vinyl Single + Vinyl (1) + + + Vinyl Single + Vinyl (1) + + + Vinyl Single + Vinyl (1) + + + Vinyl Single + Vinyl (1) + + + Vinyl Single + Vinyl (1) + + + Vinyl Single + Vinyl (1) + + + Vinyl Single + Vinyl (1) + + + Vinyl Single + Vinyl (1) + + + Vinyl Single + Vinyl (1) + + + Vinyl Single + Vinyl (1) + + + Vinyl Single + Vinyl (1) + + + Vinyl Single + Vinyl (1) + + + Vinyl Single + Vinyl (1) + + + Vinyl Single + Vinyl (1) + + + Vinyl Single + Vinyl (1) + + + Vinyl Single + Vinyl (1) + + + Vinyl Single + Vinyl (1) + + + Vinyl Single + Vinyl (1) + + + Vinyl Single + Vinyl (1) + + + Vinyl Single + Vinyl (1) + + + Vinyl Single + Vinyl (1) + + + Vinyl Single + Vinyl (1) + + + Vinyl Single + Vinyl (1) + + + Vinyl Single + Vinyl (1) + + + Vinyl Single + Vinyl (1) + + + Vinyl Single + Vinyl (1) + + + Vinyl Single + Vinyl (1) + + + + + + + \ No newline at end of file diff --git a/test/plots/index.js b/test/plots/index.js index b7551d8de0..e29d0e10a6 100644 --- a/test/plots/index.js +++ b/test/plots/index.js @@ -134,6 +134,7 @@ export {default as mobyDickLetterRelativeFrequency} from "./moby-dick-letter-rel export {default as morleyBoxplot} from "./morley-boxplot.js"; export {default as moviesProfitByGenre} from "./movies-profit-by-genre.js"; export {default as musicRevenue} from "./music-revenue.js"; +export {default as musicRevenueBars} from "./music-revenue-bars.js"; export {default as musicRevenueBin} from "./music-revenue-bin.js"; export {default as musicRevenueContrived} from "./music-revenue-contrived.js"; export {default as musicRevenueGroup} from "./music-revenue-group.js"; diff --git a/test/plots/music-revenue-bars.js b/test/plots/music-revenue-bars.js new file mode 100644 index 0000000000..e29eda1572 --- /dev/null +++ b/test/plots/music-revenue-bars.js @@ -0,0 +1,49 @@ +import * as Plot from "@observablehq/plot"; +import * as d3 from "d3"; + +export default async function () { + const data = await d3.csv("data/riaa-us-revenue.csv", d3.autoType); + const stack = {x: (d) => d["year"].getFullYear(), y: "revenue", z: "format", order: "value", reverse: true}; + return Plot.plot({ + marginRight: 90, + marginBottom: 35, + facet: {data, y: "group", marginRight: 90}, + x: {ticks: d3.range(1975, 2020, 5), tickFormat: ""}, + y: { + grid: true, + label: "↑ Annual revenue (billions, adj.)", + transform: (d) => d / 1000, + nice: true + }, + marks: [ + Plot.frame(), + Plot.barY( + data, + Plot.groupX( + {y: "sum"}, + Plot.windowY({ + ...stack, + k: 3, + y: (d) => -d.revenue, + fill: "group", + facet: "exclude", + order: "sum" + }) + ) + ), + Plot.barY( + data, + Plot.groupX( + {y: "sum"}, + Plot.windowY({ + ...stack, + k: 3, + fill: "group", + title: (d) => `${d.format}\n${d.group}` + }) + ) + ), + Plot.ruleY([0]) + ] + }); +} From 8f1ea2fdb5adf0c0965b96a8afef117bea1bea96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?= Date: Thu, 29 Sep 2022 12:40:01 +0200 Subject: [PATCH 10/17] rename --- .../{musicRevenueContrived.svg => musicRevenueWiggle.svg} | 0 test/plots/index.js | 2 +- .../{music-revenue-contrived.js => music-revenue-wiggle.js} | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename test/output/{musicRevenueContrived.svg => musicRevenueWiggle.svg} (100%) rename test/plots/{music-revenue-contrived.js => music-revenue-wiggle.js} (100%) diff --git a/test/output/musicRevenueContrived.svg b/test/output/musicRevenueWiggle.svg similarity index 100% rename from test/output/musicRevenueContrived.svg rename to test/output/musicRevenueWiggle.svg diff --git a/test/plots/index.js b/test/plots/index.js index e29d0e10a6..504d5d2aac 100644 --- a/test/plots/index.js +++ b/test/plots/index.js @@ -136,8 +136,8 @@ export {default as moviesProfitByGenre} from "./movies-profit-by-genre.js"; export {default as musicRevenue} from "./music-revenue.js"; export {default as musicRevenueBars} from "./music-revenue-bars.js"; export {default as musicRevenueBin} from "./music-revenue-bin.js"; -export {default as musicRevenueContrived} from "./music-revenue-contrived.js"; export {default as musicRevenueGroup} from "./music-revenue-group.js"; +export {default as musicRevenueWiggle} from "./music-revenue-wiggle.js"; export {default as ordinalBar} from "./ordinal-bar.js"; export {default as penguinAnnotated} from "./penguin-annotated.js"; export {default as penguinCulmen} from "./penguin-culmen.js"; diff --git a/test/plots/music-revenue-contrived.js b/test/plots/music-revenue-wiggle.js similarity index 100% rename from test/plots/music-revenue-contrived.js rename to test/plots/music-revenue-wiggle.js From 526a12a9c02cc822590ddda4a8c4dc6dd50ca492 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?= Date: Thu, 29 Sep 2022 17:16:44 +0200 Subject: [PATCH 11/17] select transforms work with reindexed facets --- src/facet.js | 2 +- src/transforms/select.js | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/facet.js b/src/facet.js index 5c42d63b38..8a55c04be0 100644 --- a/src/facet.js +++ b/src/facet.js @@ -39,7 +39,7 @@ export function facetReindex(facets, n) { // returns a function that reads X with the facets’ reindexing plan export function getter({plan}, X) { - return !plan || X.length === plan.length ? (i) => X[i] : (i) => X[plan[i]]; + return !X ? X : !plan || X.length === plan.length ? (i) => X[i] : (i) => X[plan[i]]; } // returns an array of X expanded along the facets’ reindexing plan diff --git a/src/transforms/select.js b/src/transforms/select.js index 5f972851d3..7e6f5e6c6b 100644 --- a/src/transforms/select.js +++ b/src/transforms/select.js @@ -1,6 +1,7 @@ import {greatest, group, least} from "d3"; import {maybeZ, valueof} from "../options.js"; import {basic} from "./basic.js"; +import {expander, getter} from "../facet.js"; /** * Selects the points of each series selected by the *selector*, which can be @@ -143,12 +144,13 @@ function selectChannel(v, selector, options) { } const z = maybeZ(options); return basic(options, (data, facets) => { - const Z = valueof(data, z); - const V = valueof(data, v); + const gz = getter(facets, valueof(data, z)); + const V = expander(facets, valueof(data, v)); const selectFacets = []; + if ("plan" in facets) selectFacets.plan = facets.plan; for (const facet of facets) { const selectFacet = []; - for (const I of Z ? group(facet, (i) => Z[i]).values() : [facet]) { + for (const I of gz ? group(facet, gz).values() : [facet]) { for (const i of selector(I, V)) { selectFacet.push(i); } From 859d038c7d1b50364e07182ddca26d5d1a2f5707 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?= Date: Thu, 29 Sep 2022 16:28:29 +0200 Subject: [PATCH 12/17] tests for select & facet reindex --- test/output/generativeRoses.svg | 296 ++++++++++++++++++++++++++++++++ test/plots/generative-roses.js | 40 +++++ test/plots/index.js | 1 + 3 files changed, 337 insertions(+) create mode 100644 test/output/generativeRoses.svg create mode 100644 test/plots/generative-roses.js diff --git a/test/output/generativeRoses.svg b/test/output/generativeRoses.svg new file mode 100644 index 0000000000..e4a7205c79 --- /dev/null +++ b/test/output/generativeRoses.svg @@ -0,0 +1,296 @@ + + + + + + + + + First + + + + + Last + + + + + MaxX + + + + + MinX + + + + + MaxY + + + + + MinY + + + + + + + + + + First + + + + + Last + + + + + MaxX + + + + + MinX + + + + + MaxY + + + + + MinY + + + + + + + + + + First + + + + + Last + + + + + MaxX + + + + + MinX + + + + + MaxY + + + + + MinY + + + + + + + + + + First + + + + + Last + + + + + MaxX + + + + + MinX + + + + + MaxY + + + + + MinY + + + + + + + + + + First + + + + + Last + + + + + MaxX + + + + + MinX + + + + + MaxY + + + + + MinY + + + + + + + + + + First + + + + + Last + + + + + MaxX + + + + + MinX + + + + + MaxY + + + + + MinY + + + + + + + + + + First + + + + + Last + + + + + MaxX + + + + + MinX + + + + + MaxY + + + + + MinY + + + + + + + + + + First + + + + + Last + + + + + MaxX + + + + + MinX + + + + + MaxY + + + + + MinY + + + + \ No newline at end of file diff --git a/test/plots/generative-roses.js b/test/plots/generative-roses.js new file mode 100644 index 0000000000..cb9d2df848 --- /dev/null +++ b/test/plots/generative-roses.js @@ -0,0 +1,40 @@ +import * as Plot from "@observablehq/plot"; +import * as d3 from "d3"; + +// Generate roses from a cumulative sum of vectors with various angles. The +// twist is that each facet selects a subset of these angles to ignore (with +// facet: "exclude"). +export default async function () { + const data = d3.range(0, 48, 0.7); + const mapped = Plot.mapY( + "cumsum", + Plot.mapX("cumsum", { + facet: "exclude", + x: Math.sin, + y: Math.cos + }) + ); + return Plot.plot({ + facet: { + data, + x: (d, i) => (i % 8) % 3, + y: (d, i) => Math.floor((i % 8) / 3), + marginRight: 80 + }, + axis: null, + marks: [ + Plot.line(data, {...mapped, curve: "natural"}), + ["First", "Last", "MaxX", "MinX", "MaxY", "MinY"].map((p) => + Plot.dot( + data, + Plot[`select${p}`]({ + ...mapped, + fill: () => p, + title: () => p, + r: 6 + }) + ) + ) + ] + }); +} diff --git a/test/plots/index.js b/test/plots/index.js index 504d5d2aac..2ab5647a06 100644 --- a/test/plots/index.js +++ b/test/plots/index.js @@ -75,6 +75,7 @@ export {default as footballCoverage} from "./football-coverage.js"; export {default as frameCorners} from "./frame-corners.js"; export {default as fruitSales} from "./fruit-sales.js"; export {default as fruitSalesDate} from "./fruit-sales-date.js"; +export {default as generativeRoses} from "./generative-roses.js"; export {default as gistempAnomaly} from "./gistemp-anomaly.js"; export {default as gistempAnomalyMoving} from "./gistemp-anomaly-moving.js"; export {default as gistempAnomalyTransform} from "./gistemp-anomaly-transform.js"; From cb91484924a0fd8a182d5a83a06e8399e0fb443c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?= Date: Thu, 29 Sep 2022 17:49:56 +0200 Subject: [PATCH 13/17] test plot for hexbin+reindexed facets --- test/output/hexbinExclude.svg | 374 ++++++++++++++++++++++++++++++++++ test/plots/hexbin-exclude.js | 45 ++++ test/plots/index.js | 1 + 3 files changed, 420 insertions(+) create mode 100644 test/output/hexbinExclude.svg create mode 100644 test/plots/hexbin-exclude.js diff --git a/test/output/hexbinExclude.svg b/test/output/hexbinExclude.svg new file mode 100644 index 0000000000..809f1780bd --- /dev/null +++ b/test/output/hexbinExclude.svg @@ -0,0 +1,374 @@ + + + + + 35 + + + 40 + + + 45 + + + 50 + + + 55 + + + 60 + ↑ culmen_length_mm + + + + Adelie + + + Chinstrap + + + Gentoo + species + + + + 15 + + + 20 + + + + + 15 + + + 20 + + + + + 15 + + + 20 + culmen_depth_mm → + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/plots/hexbin-exclude.js b/test/plots/hexbin-exclude.js new file mode 100644 index 0000000000..5018469c62 --- /dev/null +++ b/test/plots/hexbin-exclude.js @@ -0,0 +1,45 @@ +import * as Plot from "@observablehq/plot"; +import * as d3 from "d3"; + +export default async function () { + const penguins = await d3.csv("data/penguins.csv", d3.autoType); + const noise = d3.randomNormal.source(d3.randomLcg(42))(); + return Plot.plot({ + width: 960, + height: 320, + inset: 14, + facet: { + data: penguins, + x: "species", + marginRight: 80 + }, + marks: [ + Plot.frame(), + Plot.dot( + penguins, + Plot.hexbin( + {fillOpacity: "count"}, + Plot.map( + { + x: (X) => X.map((d) => d + noise()), + y: (Y) => Y.map((d) => d + noise()) + }, + { + x: "culmen_depth_mm", + y: "culmen_length_mm", + fill: "species", + facet: "exclude" + } + ) + ) + ), + Plot.dot( + penguins, + Plot.hexbin( + {fillOpacity: "count"}, + {x: "culmen_depth_mm", y: "culmen_length_mm", fill: "species", stroke: "species"} + ) + ) + ] + }); +} diff --git a/test/plots/index.js b/test/plots/index.js index 2ab5647a06..dbd041ec4c 100644 --- a/test/plots/index.js +++ b/test/plots/index.js @@ -86,6 +86,7 @@ export {default as gridChoroplethDx} from "./grid-choropleth-dx.js"; export {default as groupedRects} from "./grouped-rects.js"; export {default as hadcrutWarmingStripes} from "./hadcrut-warming-stripes.js"; export {default as hexbin} from "./hexbin.js"; +export {default as hexbinExclude} from "./hexbin-exclude.js"; export {default as hexbinOranges} from "./hexbin-oranges.js"; export {default as hexbinR} from "./hexbin-r.js"; export {default as hexbinSymbol} from "./hexbin-symbol.js"; From c23dc133d26afbf8ce9ee4ff064f1f5669f57f46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?= Date: Thu, 29 Sep 2022 17:50:08 +0200 Subject: [PATCH 14/17] hexbin+facet reindex --- src/transforms/hexbin.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/transforms/hexbin.js b/src/transforms/hexbin.js index ebd0e50dc8..6240651cd6 100644 --- a/src/transforms/hexbin.js +++ b/src/transforms/hexbin.js @@ -3,6 +3,7 @@ import {sqrt3} from "../symbols.js"; import {identity, isNoneish, number, valueof} from "../options.js"; import {initializer} from "./basic.js"; import {hasOutput, maybeGroup, maybeOutputs, maybeSubgroup} from "./group.js"; +import {originals} from "../facet.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) @@ -101,7 +102,7 @@ export function hexbin(outputs = {fill: "count"}, options = {}) { const BY = []; let i = -1; for (const o of outputs) o.initialize(data); - for (const facet of facets) { + for (const facet of originals(facets)) { const binFacet = []; for (const o of outputs) o.scope("facet", facet); for (const [f, I] of maybeGroup(facet, G)) { From 8ece034ebe94a811f71c5920dff4a0c4941b2583 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?= Date: Thu, 29 Sep 2022 17:53:50 +0200 Subject: [PATCH 15/17] the tree transform creates new facets (not tested) --- src/transforms/tree.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/transforms/tree.js b/src/transforms/tree.js index 830d6a5c6c..a61795cd0d 100644 --- a/src/transforms/tree.js +++ b/src/transforms/tree.js @@ -2,6 +2,7 @@ import {stratify, tree} from "d3"; import {ascendingDefined} from "../defined.js"; import {column, identity, isObject, one, valueof} from "../options.js"; import {basic} from "./basic.js"; +import {originals} from "../facet.js"; /** * Based on the tree options described above, populates the **x** and **y** @@ -155,7 +156,7 @@ export function treeLink(options = {}) { if (layout.nodeSize) layout.nodeSize([1, 1]); if (layout.separation && treeSeparation !== undefined) layout.separation(treeSeparation ?? one); for (const o of outputs) o[output_values] = o[output_setValues]([]); - for (const facet of facets) { + for (const facet of originals(facets)) { const treeFacet = []; const root = rootof(facet.filter((i) => P[i] != null)).each((node) => (node.data = data[node.data])); if (treeSort != null) root.sort(treeSort); From 950829544aeb739d856d47a5eb8c20f620a590e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?= Date: Thu, 29 Sep 2022 23:00:57 +0200 Subject: [PATCH 16/17] no shortcuts! --- src/transforms/bin.js | 51 ++- src/transforms/group.js | 32 +- src/transforms/hexbin.js | 31 +- src/transforms/tree.js | 10 +- test/output/hexbinExclude.svg | 625 +++++++++++++++++--------------- test/plots/hexbin-exclude.js | 2 +- test/plots/music-revenue-bin.js | 12 +- 7 files changed, 419 insertions(+), 344 deletions(-) diff --git a/src/transforms/bin.js b/src/transforms/bin.js index 016aceed26..03646fd303 100644 --- a/src/transforms/bin.js +++ b/src/transforms/bin.js @@ -1,7 +1,14 @@ -import {bin as binner, extent, thresholdFreedmanDiaconis, thresholdScott, thresholdSturges, utcTickInterval} from "d3"; +import { + bin as binner, + extent, + sum, + thresholdFreedmanDiaconis, + thresholdScott, + thresholdSturges, + utcTickInterval +} from "d3"; import { valueof, - range, identity, maybeColumn, maybeTuple, @@ -29,7 +36,7 @@ import { } from "./group.js"; import {maybeInsetX, maybeInsetY} from "./inset.js"; import {maybeInterval} from "./interval.js"; -import {originals} from "../facet.js"; +import {expander, getter, originals} from "../facet.js"; /** * ```js @@ -173,23 +180,30 @@ function binn( const GZ = Z && setGZ([]); const GF = F && setGF([]); const GS = S && setGS([]); - const BX = bx ? bx(data) : [[, , (I) => I]]; - const BY = by ? by(data) : [[, , (I) => I]]; + const BX = bx ? bx(data, facets) : [[, , (I) => I]]; + const BY = by ? by(data, facets) : [[, , (I) => I]]; const BX1 = bx && setBX1([]); const BX2 = bx && setBX2([]); const BY1 = by && setBY1([]); const BY2 = by && setBY2([]); + + const eG = getter(facets, G); + const eK = getter(facets, K); + const gZ = getter(facets, Z); + const gF = getter(facets, F); + const gS = getter(facets, S); + let i = 0; for (const o of outputs) o.initialize(data); if (sort) sort.initialize(data); if (filter) filter.initialize(data); - for (const facet of originals(facets)) { + for (const facet of facets) { const groupFacet = []; for (const o of outputs) o.scope("facet", facet); if (sort) sort.scope("facet", facet); if (filter) filter.scope("facet", facet); - for (const [f, I] of maybeGroup(facet, G)) { - for (const [k, g] of maybeGroup(I, K)) { + for (const [f, I] of maybeGroup(facet, eG)) { + for (const [k, g] of maybeGroup(I, eK)) { for (const [x1, x2, fx] of BX) { const bb = fx(g); for (const [y1, y2, fy] of BY) { @@ -197,11 +211,11 @@ function binn( const b = fy(bb); if (filter && !filter.reduce(b, extent)) continue; groupFacet.push(i++); - groupData.push(reduceData.reduce(b, data, extent)); + groupData.push(reduceData.reduce(originals(b), data, extent)); if (K) GK.push(k); - if (Z) GZ.push(G === Z ? f : Z[b[0]]); - if (F) GF.push(G === F ? f : F[b[0]]); - if (S) GS.push(G === S ? f : S[b[0]]); + if (Z) GZ.push(G === Z ? f : gZ(b[0])); + if (F) GF.push(G === F ? f : gF(b[0])); + if (S) GS.push(G === S ? f : gS(b[0])); if (BX1) BX1.push(x1), BX2.push(x2); if (BY1) BY1.push(y1), BY2.push(y2); for (const o of outputs) o.reduce(b, extent); @@ -249,8 +263,8 @@ function maybeBinValueTuple(options) { function maybeBin(options) { if (options == null) return; const {value, cumulative, domain = extent, thresholds} = options; - const bin = (data) => { - let V = valueof(data, value, Array); // d3.bin prefers Array input + const bin = (data, facets) => { + let V = expander(facets, valueof(data, value, Array)); // d3.bin prefers Array input const bin = binner().value((i) => V[i]); if (isTemporal(V) || isTimeThresholds(thresholds)) { V = V.map(coerceDate); @@ -280,7 +294,7 @@ function maybeBin(options) { } bin.thresholds(t).domain(d); } - let bins = bin(range(data)).map(binset); + let bins = bin(union(facets)).map(binset); if (cumulative) bins = (cumulative < 0 ? bins.reverse() : bins).map(bincumset); return bins.map(binfilter); }; @@ -366,3 +380,10 @@ function binfilter([{x0, x1}, set]) { function binempty() { return new Uint32Array(0); } + +function union(facets) { + const U = new Uint32Array(sum(facets, (d) => d.length)); + let c = 0; + for (const facet of facets) for (const i of facet) U[c++] = i; + return U; +} diff --git a/src/transforms/group.js b/src/transforms/group.js index bde9b3bad0..20507f6f33 100644 --- a/src/transforms/group.js +++ b/src/transforms/group.js @@ -31,7 +31,7 @@ import { percentile } from "../options.js"; import {basic} from "./basic.js"; -import {originals} from "../facet.js"; +import {getter, originals} from "../facet.js"; /** * ```js @@ -159,26 +159,34 @@ function groupn( const GZ = Z && setGZ([]); const GF = F && setGF([]); const GS = S && setGS([]); + + const eG = getter(facets, G); + const eX = getter(facets, X); + const eY = getter(facets, Y); + const gZ = getter(facets, Z); + const gF = getter(facets, F); + const gS = getter(facets, S); + let i = 0; for (const o of outputs) o.initialize(data); if (sort) sort.initialize(data); if (filter) filter.initialize(data); - for (const facet of originals(facets)) { + for (const facet of facets) { const groupFacet = []; for (const o of outputs) o.scope("facet", facet); if (sort) sort.scope("facet", facet); if (filter) filter.scope("facet", facet); - for (const [f, I] of maybeGroup(facet, G)) { - for (const [y, gg] of maybeGroup(I, Y)) { - for (const [x, g] of maybeGroup(gg, X)) { + for (const [f, I] of maybeGroup(facet, eG)) { + for (const [y, gg] of maybeGroup(I, eY)) { + for (const [x, g] of maybeGroup(gg, eX)) { if (filter && !filter.reduce(g)) continue; groupFacet.push(i++); - groupData.push(reduceData.reduce(g, data)); + groupData.push(reduceData.reduce(originals(g), data)); if (X) GX.push(x); if (Y) GY.push(y); - if (Z) GZ.push(G === Z ? f : Z[g[0]]); - if (F) GF.push(G === F ? f : F[g[0]]); - if (S) GS.push(G === S ? f : S[g[0]]); + if (Z) GZ.push(G === Z ? f : gZ(g[0])); + if (F) GF.push(G === F ? f : gF(g[0])); + if (S) GS.push(G === S ? f : gS(g[0])); for (const o of outputs) o.reduce(g); if (sort) sort.reduce(g); } @@ -257,10 +265,10 @@ export function maybeEvaluator(name, reduce, inputs) { }; } -export function maybeGroup(I, X) { - return X +export function maybeGroup(I, x) { + return x ? sort( - grouper(I, (i) => X[i]), + grouper(I, (i) => x(i)), first ) : [[, I]]; diff --git a/src/transforms/hexbin.js b/src/transforms/hexbin.js index 6240651cd6..14fd0b5cc0 100644 --- a/src/transforms/hexbin.js +++ b/src/transforms/hexbin.js @@ -3,7 +3,7 @@ import {sqrt3} from "../symbols.js"; import {identity, isNoneish, number, valueof} from "../options.js"; import {initializer} from "./basic.js"; import {hasOutput, maybeGroup, maybeOutputs, maybeSubgroup} from "./group.js"; -import {originals} from "../facet.js"; +import {getter} from "../facet.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) @@ -101,19 +101,28 @@ export function hexbin(outputs = {fill: "count"}, options = {}) { const BX = []; const BY = []; let i = -1; + + // Mind reindexed facets + const eG = getter(facets, G); + const gX = getter(facets, X); + const gY = getter(facets, Y); + const gZ = getter(facets, Z); + const gF = getter(facets, F); + const gS = getter(facets, S); + const gQ = getter(facets, Q); for (const o of outputs) o.initialize(data); - for (const facet of originals(facets)) { + for (const facet of facets) { const binFacet = []; for (const o of outputs) o.scope("facet", facet); - for (const [f, I] of maybeGroup(facet, G)) { - for (const bin of hbin(I, X, Y, binWidth)) { + for (const [f, I] of maybeGroup(facet, eG)) { + for (const bin of hbin(I, gX, gY, binWidth)) { binFacet.push(++i); BX.push(bin.x); BY.push(bin.y); - if (Z) GZ.push(G === Z ? f : Z[bin[0]]); - if (F) GF.push(G === F ? f : F[bin[0]]); - if (S) GS.push(G === S ? f : S[bin[0]]); - if (Q) GQ.push(G === Q ? f : Q[bin[0]]); + if (Z) GZ.push(G === Z ? f : gZ(bin[0])); + if (F) GF.push(G === F ? f : gF(bin[0])); + if (S) GS.push(G === S ? f : gS(bin[0])); + if (Q) GQ.push(G === Q ? f : gQ(bin[0])); for (const o of outputs) o.reduce(bin); } } @@ -140,12 +149,12 @@ export function hexbin(outputs = {fill: "count"}, options = {}) { }); } -function hbin(I, X, Y, dx) { +function hbin(I, x, y, dx) { const dy = dx * (1.5 / sqrt3); const bins = new Map(); for (const i of I) { - let px = X[i], - py = Y[i]; + let px = x(i), + py = y(i); if (isNaN(px) || isNaN(py)) continue; let pj = Math.round((py = (py - oy) / dy)), pi = Math.round((px = (px - ox) / dx - (pj & 1) / 2)), diff --git a/src/transforms/tree.js b/src/transforms/tree.js index a61795cd0d..da2e99ac51 100644 --- a/src/transforms/tree.js +++ b/src/transforms/tree.js @@ -2,7 +2,7 @@ import {stratify, tree} from "d3"; import {ascendingDefined} from "../defined.js"; import {column, identity, isObject, one, valueof} from "../options.js"; import {basic} from "./basic.js"; -import {originals} from "../facet.js"; +import {getter} from "../facet.js"; /** * Based on the tree options described above, populates the **x** and **y** @@ -51,20 +51,20 @@ export function treeNode(options = {}) { y: Y, frameAnchor, ...basic(remainingOptions, (data, facets) => { - const P = normalize(valueof(data, path)); + const gP = getter(facets, normalize(valueof(data, path))); const X = setX([]); const Y = setY([]); let treeIndex = -1; const treeData = []; const treeFacets = []; - const rootof = stratify().path((i) => P[i]); + const rootof = stratify().path(gP); const layout = treeLayout(); if (layout.nodeSize) layout.nodeSize([1, 1]); if (layout.separation && treeSeparation !== undefined) layout.separation(treeSeparation ?? one); for (const o of outputs) o[output_values] = o[output_setValues]([]); for (const facet of facets) { const treeFacet = []; - const root = rootof(facet.filter((i) => P[i] != null)).each((node) => (node.data = data[node.data])); + const root = rootof(facet.filter((i) => gP(i) != null)).each((node) => (node.data = data[node.data])); if (treeSort != null) root.sort(treeSort); layout(root); for (const node of root.descendants()) { @@ -156,7 +156,7 @@ export function treeLink(options = {}) { if (layout.nodeSize) layout.nodeSize([1, 1]); if (layout.separation && treeSeparation !== undefined) layout.separation(treeSeparation ?? one); for (const o of outputs) o[output_values] = o[output_setValues]([]); - for (const facet of originals(facets)) { + for (const facet of facets) { const treeFacet = []; const root = rootof(facet.filter((i) => P[i] != null)).each((node) => (node.data = data[node.data])); if (treeSort != null) root.sort(treeSort); diff --git a/test/output/hexbinExclude.svg b/test/output/hexbinExclude.svg index 809f1780bd..ca4edb5e0f 100644 --- a/test/output/hexbinExclude.svg +++ b/test/output/hexbinExclude.svg @@ -14,23 +14,20 @@ } - + 35 - + 40 - + 45 - + 50 - + 55 - - - 60 ↑ culmen_length_mm @@ -45,330 +42,360 @@ species - - 15 + + 14 + + + 16 - + + 18 + + 20 - - 15 + + 14 + + + 16 - + + 18 + + 20 - - 15 + + 14 + + + 16 + + + 18 - + 20 culmen_depth_mm → - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/plots/hexbin-exclude.js b/test/plots/hexbin-exclude.js index 5018469c62..c862a43cf0 100644 --- a/test/plots/hexbin-exclude.js +++ b/test/plots/hexbin-exclude.js @@ -3,7 +3,7 @@ import * as d3 from "d3"; export default async function () { const penguins = await d3.csv("data/penguins.csv", d3.autoType); - const noise = d3.randomNormal.source(d3.randomLcg(42))(); + const noise = d3.randomNormal.source(d3.randomLcg(42))(0, 0.1); return Plot.plot({ width: 960, height: 320, diff --git a/test/plots/music-revenue-bin.js b/test/plots/music-revenue-bin.js index 37a4a19060..adfa085c44 100644 --- a/test/plots/music-revenue-bin.js +++ b/test/plots/music-revenue-bin.js @@ -22,6 +22,7 @@ export default async function () { Plot.windowY({ ...stack, k: 7, + interval: d3.utcYear.every(5), y: (d) => -d.revenue, fill: "#eee", facet: "exclude" @@ -30,7 +31,16 @@ export default async function () { ), Plot.rectY( data, - Plot.binX({y: "sum"}, Plot.windowY({...stack, k: 7, fill: "group", title: (d) => `${d.format}\n${d.group}`})) + Plot.binX( + {y: "sum"}, + Plot.windowY({ + ...stack, + k: 7, + interval: d3.utcYear.every(5), + fill: "group", + title: (d) => `${d.format}\n${d.group}` + }) + ) ), Plot.ruleY([0]) ] From aee4469cb82ad1d02469ed018558b4b8a63b0a00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?= Date: Fri, 30 Sep 2022 12:49:22 +0200 Subject: [PATCH 17/17] fix reduceData on reindexed facets --- src/facet.js | 4 ++-- src/transforms/bin.js | 2 +- src/transforms/group.js | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/facet.js b/src/facet.js index 8a55c04be0..cb2ff6cb71 100644 --- a/src/facet.js +++ b/src/facet.js @@ -50,6 +50,6 @@ export function expander({plan}, X) { return V; } -export function originals(facets) { - return facets.plan ? facets.map((facet) => facet.map((i) => facets.plan[i])) : facets; +export function originals({plan}, I) { + return plan ? I.map((i) => plan[i]) : I; } diff --git a/src/transforms/bin.js b/src/transforms/bin.js index 03646fd303..71a926e001 100644 --- a/src/transforms/bin.js +++ b/src/transforms/bin.js @@ -211,7 +211,7 @@ function binn( const b = fy(bb); if (filter && !filter.reduce(b, extent)) continue; groupFacet.push(i++); - groupData.push(reduceData.reduce(originals(b), data, extent)); + groupData.push(reduceData.reduce(originals(facets, b), data, extent)); if (K) GK.push(k); if (Z) GZ.push(G === Z ? f : gZ(b[0])); if (F) GF.push(G === F ? f : gF(b[0])); diff --git a/src/transforms/group.js b/src/transforms/group.js index 20507f6f33..cd3b320455 100644 --- a/src/transforms/group.js +++ b/src/transforms/group.js @@ -181,7 +181,7 @@ function groupn( for (const [x, g] of maybeGroup(gg, eX)) { if (filter && !filter.reduce(g)) continue; groupFacet.push(i++); - groupData.push(reduceData.reduce(originals(g), data)); + groupData.push(reduceData.reduce(originals(facets, g), data)); if (X) GX.push(x); if (Y) GY.push(y); if (Z) GZ.push(G === Z ? f : gZ(g[0]));