Skip to content

the Plot.stack offset option can be a function #814

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Mar 19, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1866,6 +1866,7 @@ After all values have been stacked from zero, an optional **offset** can be appl
- *expand* (or *normalize*) - rescale each stack to fill [0, 1]
- *center* (or *silhouette*) - align the centers of all stacks
- *wiggle* - translate stacks to minimize apparent movement
- a function that receives as arguments a nested index of stacks, the X1 and X2 arrays (resp. Y1 and Y2 for stackY), and Z, and can modify X1 and X2, typically by subtracting the same offset from each of the X1 and X2 values that pertain to a stack.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’d like this bulleted list to fit on one line. Can you use a short description here, and move the longer description to the paragraph below that talks about offsets? E.g., “If offset is a function, blah blah blah.” We use this style elsewhere in the README if you want to find precedent.


If a given stack has zero total value, the *expand* offset will not adjust the stack’s position. Both the *center* and *wiggle* offsets ensure that the lowest element across stacks starts at zero for better default axes. The *wiggle* offset is recommended for streamgraphs, and if used, changes the default order to *inside-out*; see [Byron & Wattenberg](http://leebyron.com/streamgraph/).

Expand Down
1 change: 1 addition & 0 deletions src/transforms/stack.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ function stack(x, y = () => 1, ky, {offset, order, reverse}, options) {

function maybeOffset(offset) {
if (offset == null) return;
if (typeof offset === "function") return offset;
switch (`${offset}`.toLowerCase()) {
case "expand": case "normalize": return offsetExpand;
case "center": case "silhouette": return offsetCenter;
Expand Down
1 change: 1 addition & 0 deletions test/data/survey.json

Large diffs are not rendered by default.

121 changes: 121 additions & 0 deletions test/output/likertSurvey.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
<figure style="max-width: initial;">
<div class="plot" style="
--swatchWidth: 15px;
--swatchHeight: 15px;
">
<style>
.plot {
font-family: system-ui, sans-serif;
font-size: 10px;
margin-bottom: 0.5em;
margin-left: 0px;
}

.plot-swatch::before {
content: "";
width: var(--swatchWidth);
height: var(--swatchHeight);
margin-right: 0.5em;
background: var(--color);
}

.plot {
display: flex;
align-items: center;
min-height: 33px;
flex-wrap: wrap;
}

.plot-swatch {
display: inline-flex;
align-items: center;
margin-right: 1em;
}
</style><span class="plot-swatch" style="--color: #ca0020;">Strongly Disagree</span><span class="plot-swatch" style="--color: #f4a582;">Disagree</span><span class="plot-swatch" style="--color: #f7f7f7;">Neutral</span><span class="plot-swatch" style="--color: #92c5de;">Agree</span><span class="plot-swatch" style="--color: #0571b0;">Strongly Agree</span>
</div><svg class="plot-2" fill="currentColor" font-family="system-ui, sans-serif" font-size="10" text-anchor="middle" width="640" height="160" viewBox="0 0 640 160" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<style>
.plot-2 {
display: block;
background: white;
height: auto;
height: intrinsic;
max-width: 100%;
}

.plot-2 text,
.plot-2 tspan {
white-space: pre;
}
</style>
<g aria-label="y-axis" aria-description="Question" transform="translate(40,0)" fill="none" text-anchor="end">
<g class="tick" opacity="1" transform="translate(0,33.5)">
<line stroke="currentColor" x2="0"></line><text fill="currentColor" x="-9" dy="0.32em">Q1</text>
</g>
<g class="tick" opacity="1" transform="translate(0,54.5)">
<line stroke="currentColor" x2="0"></line><text fill="currentColor" x="-9" dy="0.32em">Q2</text>
</g>
<g class="tick" opacity="1" transform="translate(0,75.5)">
<line stroke="currentColor" x2="0"></line><text fill="currentColor" x="-9" dy="0.32em">Q3</text>
</g>
<g class="tick" opacity="1" transform="translate(0,96.5)">
<line stroke="currentColor" x2="0"></line><text fill="currentColor" x="-9" dy="0.32em">Q4</text>
</g>
<g class="tick" opacity="1" transform="translate(0,117.5)">
<line stroke="currentColor" x2="0"></line><text fill="currentColor" x="-9" dy="0.32em">Q5</text>
</g><text fill="currentColor" transform="translate(-40,75) rotate(-90)" dy="0.75em" text-anchor="middle">Question</text>
</g>
<g aria-label="x-axis" aria-description="# of answers" transform="translate(0,130)" fill="none" text-anchor="middle" font-variant="tabular-nums">
<g class="tick" opacity="1" transform="translate(72.72222222222223,0)">
<line stroke="currentColor" y2="6"></line><text fill="currentColor" y="9" dy="0.71em">60</text>
</g>
<g class="tick" opacity="1" transform="translate(158.64814814814815,0)">
<line stroke="currentColor" y2="6"></line><text fill="currentColor" y="9" dy="0.71em">40</text>
</g>
<g class="tick" opacity="1" transform="translate(244.57407407407408,0)">
<line stroke="currentColor" y2="6"></line><text fill="currentColor" y="9" dy="0.71em">20</text>
</g>
<g class="tick" opacity="1" transform="translate(330.5,0)">
<line stroke="currentColor" y2="6"></line><text fill="currentColor" y="9" dy="0.71em">0</text>
</g>
<g class="tick" opacity="1" transform="translate(416.4259259259259,0)">
<line stroke="currentColor" y2="6"></line><text fill="currentColor" y="9" dy="0.71em">20</text>
</g>
<g class="tick" opacity="1" transform="translate(502.35185185185185,0)">
<line stroke="currentColor" y2="6"></line><text fill="currentColor" y="9" dy="0.71em">40</text>
</g>
<g class="tick" opacity="1" transform="translate(588.2777777777777,0)">
<line stroke="currentColor" y2="6"></line><text fill="currentColor" y="9" dy="0.71em">60</text>
</g><text fill="currentColor" transform="translate(640,30)" dy="-0.32em" text-anchor="end"># of answers</text>
</g>
<g aria-label="bar">
<rect x="409.4814814814815" width="77.33333333333326" y="24" height="19" fill="#92c5de"></rect>
<rect x="390.14814814814815" width="116" y="45" height="19" fill="#92c5de"></rect>
<rect x="435.2592592592593" width="94.51851851851842" y="66" height="19" fill="#92c5de"></rect>
<rect x="375.1111111111111" width="21.481481481481467" y="87" height="19" fill="#92c5de"></rect>
<rect x="375.1111111111111" width="73.03703703703707" y="108" height="19" fill="#92c5de"></rect>
<rect x="186.07407407407405" width="64.44444444444449" y="24" height="19" fill="#f4a582"></rect>
<rect x="248.37037037037038" width="21.481481481481467" y="45" height="19" fill="#f4a582"></rect>
<rect x="194.66666666666669" width="30.074074074074076" y="66" height="19" fill="#f4a582"></rect>
<rect x="65.77777777777777" width="219.1111111111111" y="87" height="19" fill="#f4a582"></rect>
<rect x="233.33333333333331" width="51.55555555555554" y="108" height="19" fill="#f4a582"></rect>
<rect x="250.51851851851853" width="158.962962962963" y="24" height="19" fill="#f7f7f7"></rect>
<rect x="269.85185185185185" width="120.2962962962963" y="45" height="19" fill="#f7f7f7"></rect>
<rect x="224.74074074074076" width="210.51851851851853" y="66" height="19" fill="#f7f7f7"></rect>
<rect x="284.88888888888886" width="90.22222222222223" y="87" height="19" fill="#f7f7f7"></rect>
<rect x="284.88888888888886" width="90.22222222222223" y="108" height="19" fill="#f7f7f7"></rect>
<rect x="486.8148148148148" width="90.22222222222229" y="24" height="19" fill="#0571b0"></rect>
<rect x="506.14814814814815" width="51.55555555555554" y="45" height="19" fill="#0571b0"></rect>
<rect x="529.7777777777777" width="73.03703703703707" y="66" height="19" fill="#0571b0"></rect>
<rect x="396.59259259259255" width="73.03703703703707" y="87" height="19" fill="#0571b0"></rect>
<rect x="448.14814814814815" width="171.85185185185185" y="108" height="19" fill="#0571b0"></rect>
<rect x="143.11111111111111" width="42.96296296296293" y="24" height="19" fill="#ca0020"></rect>
<rect x="149.55555555555554" width="98.81481481481484" y="45" height="19" fill="#ca0020"></rect>
<rect x="160.29629629629628" width="34.37037037037041" y="66" height="19" fill="#ca0020"></rect>
<rect x="40" width="25.77777777777777" y="87" height="19" fill="#ca0020"></rect>
<rect x="186.07407407407405" width="47.25925925925927" y="108" height="19" fill="#ca0020"></rect>
</g>
<g aria-label="rule" stroke="currentColor" transform="translate(0.5,0)">
<line x1="330" x2="330" y1="20" y2="130"></line>
</g>
</svg>
</figure>
1 change: 1 addition & 0 deletions test/plots/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ export {default as letterFrequencyColumn} from "./letter-frequency-column.js";
export {default as letterFrequencyDot} from "./letter-frequency-dot.js";
export {default as letterFrequencyLollipop} from "./letter-frequency-lollipop.js";
export {default as letterFrequencyWheel} from "./letter-frequency-wheel.js";
export {default as likertSurvey} from "./likert-survey.js";
export {default as logDegenerate} from "./log-degenerate.js";
export {default as metroInequality} from "./metro-inequality.js";
export {default as metroInequalityChange} from "./metro-inequality-change.js";
Expand Down
42 changes: 42 additions & 0 deletions test/plots/likert-survey.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import * as Plot from "@observablehq/plot";
import * as d3 from "d3";

export default async function() {
const data = await d3.json("data/survey.json");
const values = new Map([
["Strongly Disagree", -1],
["Disagree", -1],
["Neutral", 0],
["Agree", 1],
["Strongly Agree", 1]
]);
const order = [...values.keys()];
return Plot.plot({
x: { tickFormat: Math.abs, label: "# of answers" },
y: { tickSize: 0 },
color: { legend: true, domain: order, scheme: "RdBu" },
marks: [
Plot.barX(
data,
Plot.groupY(
{ x: "count" },
{
y: "Question",
fill: "Response",
order,
offset: (facetstacks, X1, X2, Z) => {
for (const S of facetstacks) {
for (const I of S) {
const offset =
d3.sum(I, (i) => (X2[i] - X1[i]) * (1 - values.get(Z[i]))) / 2;
for (const i of I) (X1[i] -= offset), (X2[i] -= offset);
}
}
}
}
)
),
Plot.ruleX([0])
]
});
}