Skip to content

threshold opacity scale #2289

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

Open
wants to merge 1 commit into
base: mbostock/ordinal-opacity
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions src/legends/ramp.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {quantize, interpolateNumber, piecewise, format, scaleBand, scaleLinear, axisBottom} from "d3";
import {quantize, interpolateNumber, piecewise, format, scaleBand, scaleLinear, axisBottom, scaleThreshold} from "d3";
import {inferFontVariant} from "../axes.js";
import {createContext, create} from "../context.js";
import {map, maybeNumberChannel} from "../options.js";
Expand Down Expand Up @@ -58,7 +58,7 @@ export function legendRamp(color, options) {

// Some D3 scales use scale.interpolate, some scale.interpolator, and some
// scale.round; this normalizes the API so it works with all scale types.
const applyRange = round ? (x, range) => x.rangeRound(range) : (x, range) => x.range(range);
const applyRange = round ? (x, range) => (x.rangeRound ?? x.range)(range) : (x, range) => x.range(range);

const {type, domain, range, interpolate, scale, pivot} = color;

Expand All @@ -70,18 +70,27 @@ export function legendRamp(color, options) {
const interpolator =
range === undefined
? interpolate
: type === "threshold"
? scaleThreshold(
Array.from(domain, (_, i) => (i + 1) / (domain.length + 1)),
Array.from(range, interpolate)
)
: piecewise(interpolate.length === 1 ? interpolatePiecewise(interpolate) : interpolate, range);

// Construct a D3 scale of the same type, but with a range that evenly
// divides the horizontal extent of the legend. (In the common case, the
// domain.length is two, and so the range is simply the extent.) For a
// diverging scale, we need an extra point in the range for the pivot such
// that the pivot is always drawn in the middle.
// that the pivot is always drawn in the middle. For a threshold scale, we
// add some space below and above.
x = applyRange(
scale.copy(),
quantize(
interpolateNumber(marginLeft, width - marginRight),
Math.min(domain.length + (pivot !== undefined), range === undefined ? Infinity : range.length)
Math.min(
domain.length + (pivot !== undefined) + 2 * (type === "threshold"),
range === undefined ? Infinity : range.length + +(type === "threshold")
)
)
);

Expand Down
109 changes: 109 additions & 0 deletions test/output/ordinalOpacityRamp.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<figure class="plot-d6a7b5-figure" style="max-width: initial;"><svg class="plot-ramp" font-family="system-ui, sans-serif" font-size="10" width="240" height="50" viewBox="0 0 240 50" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<style>
:where(.plot-ramp) {
display: block;
height: auto;
height: intrinsic;
max-width: 100%;
overflow: visible;
}

:where(.plot-ramp text) {
white-space: pre;
}
</style>
<image x="0" y="18" width="240" height="10" preserveAspectRatio="none" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAABCAYAAAAxWXB3AAAABmJLR0QA/wD/AP+gvaeTAAAAWUlEQVQ4jd2SSwoAIAhEx+5/6DZCg/gZqFWBoCZPRzScZwCW+8tjozjmO6tqJxbnlX4K57Y2m3n6Z44h36eqMXK4X2SrO+rmV7hZbeVPepR7UrX9pOWllewNAjMBAXvLNnYAAAAASUVORK5CYII="></image>
<g transform="translate(0,28)" fill="none" text-anchor="middle">
<g class="tick" opacity="1" transform="translate(0.5,0)">
<line stroke="currentColor" y2="6" y1="-10"></line>
<text fill="currentColor" y="9" dy="0.71em">0</text>
</g>
<g class="tick" opacity="1" transform="translate(27.167,0)">
<line stroke="currentColor" y2="6" y1="-10"></line>
<text fill="currentColor" y="9" dy="0.71em">1</text>
</g>
<g class="tick" opacity="1" transform="translate(53.833,0)">
<line stroke="currentColor" y2="6" y1="-10"></line>
<text fill="currentColor" y="9" dy="0.71em">2</text>
</g>
<g class="tick" opacity="1" transform="translate(80.5,0)">
<line stroke="currentColor" y2="6" y1="-10"></line>
<text fill="currentColor" y="9" dy="0.71em">3</text>
</g>
<g class="tick" opacity="1" transform="translate(107.167,0)">
<line stroke="currentColor" y2="6" y1="-10"></line>
<text fill="currentColor" y="9" dy="0.71em">4</text>
</g>
<g class="tick" opacity="1" transform="translate(133.833,0)">
<line stroke="currentColor" y2="6" y1="-10"></line>
<text fill="currentColor" y="9" dy="0.71em">5</text>
</g>
<g class="tick" opacity="1" transform="translate(160.5,0)">
<line stroke="currentColor" y2="6" y1="-10"></line>
<text fill="currentColor" y="9" dy="0.71em">6</text>
</g>
<g class="tick" opacity="1" transform="translate(187.167,0)">
<line stroke="currentColor" y2="6" y1="-10"></line>
<text fill="currentColor" y="9" dy="0.71em">7</text>
</g>
<g class="tick" opacity="1" transform="translate(213.833,0)">
<line stroke="currentColor" y2="6" y1="-10"></line>
<text fill="currentColor" y="9" dy="0.71em">8</text>
</g>
<g class="tick" opacity="1" transform="translate(240.5,0)">
<line stroke="currentColor" y2="6" y1="-10"></line>
<text fill="currentColor" y="9" dy="0.71em">9</text>
</g>
</g>
</svg><svg class="plot" fill="currentColor" font-family="system-ui, sans-serif" font-size="10" text-anchor="middle" width="640" height="60" viewBox="0 0 640 60" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<style>
:where(.plot) {
--plot-background: white;
display: block;
height: auto;
height: intrinsic;
max-width: 100%;
}

:where(.plot text),
:where(.plot tspan) {
white-space: pre;
}
</style>
<g aria-label="x-axis tick" aria-hidden="true" fill="none" stroke="currentColor" transform="translate(27,0)">
<path transform="translate(28,30)" d="M0,0L0,6"></path>
<path transform="translate(87,30)" d="M0,0L0,6"></path>
<path transform="translate(146,30)" d="M0,0L0,6"></path>
<path transform="translate(205,30)" d="M0,0L0,6"></path>
<path transform="translate(264,30)" d="M0,0L0,6"></path>
<path transform="translate(323,30)" d="M0,0L0,6"></path>
<path transform="translate(382,30)" d="M0,0L0,6"></path>
<path transform="translate(441,30)" d="M0,0L0,6"></path>
<path transform="translate(500,30)" d="M0,0L0,6"></path>
<path transform="translate(559,30)" d="M0,0L0,6"></path>
</g>
<g aria-label="x-axis tick label" transform="translate(27,9.5)">
<text y="0.71em" transform="translate(28,30)">0</text>
<text y="0.71em" transform="translate(87,30)">1</text>
<text y="0.71em" transform="translate(146,30)">2</text>
<text y="0.71em" transform="translate(205,30)">3</text>
<text y="0.71em" transform="translate(264,30)">4</text>
<text y="0.71em" transform="translate(323,30)">5</text>
<text y="0.71em" transform="translate(382,30)">6</text>
<text y="0.71em" transform="translate(441,30)">7</text>
<text y="0.71em" transform="translate(500,30)">8</text>
<text y="0.71em" transform="translate(559,30)">9</text>
</g>
<g aria-label="cell" fill="red">
<rect x="28" width="53" y="0" height="30" opacity="0"></rect>
<rect x="87" width="53" y="0" height="30" opacity="0.111"></rect>
<rect x="146" width="53" y="0" height="30" opacity="0.222"></rect>
<rect x="205" width="53" y="0" height="30" opacity="0.333"></rect>
<rect x="264" width="53" y="0" height="30" opacity="0.444"></rect>
<rect x="323" width="53" y="0" height="30" opacity="0.556"></rect>
<rect x="382" width="53" y="0" height="30" opacity="0.667"></rect>
<rect x="441" width="53" y="0" height="30" opacity="0.778"></rect>
<rect x="500" width="53" y="0" height="30" opacity="0.889"></rect>
<rect x="559" width="53" y="0" height="30" opacity="1"></rect>
</g>
</svg></figure>
81 changes: 81 additions & 0 deletions test/output/ordinalOpacityThreshold.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<figure class="plot-d6a7b5-figure" style="max-width: initial;"><svg class="plot-ramp" font-family="system-ui, sans-serif" font-size="10" width="240" height="50" viewBox="0 0 240 50" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<style>
:where(.plot-ramp) {
display: block;
height: auto;
height: intrinsic;
max-width: 100%;
overflow: visible;
}

:where(.plot-ramp text) {
white-space: pre;
}
</style>
<image x="0" y="18" width="240" height="10" preserveAspectRatio="none" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAABCAYAAAAxWXB3AAAABmJLR0QA/wD/AP+gvaeTAAAAGElEQVQ4jWNkYGAwZhjZYNT/IxuMaP8DAAO8AM421ctRAAAAAElFTkSuQmCC"></image>
<g transform="translate(0,28)" fill="none" text-anchor="middle" font-variant="tabular-nums">
<g class="tick" opacity="1" transform="translate(60.5,0)">
<line stroke="currentColor" y2="6" y1="-10"></line>
<text fill="currentColor" y="9" dy="0.71em">2</text>
</g>
<g class="tick" opacity="1" transform="translate(120.5,0)">
<line stroke="currentColor" y2="6" y1="-10"></line>
<text fill="currentColor" y="9" dy="0.71em">5</text>
</g>
<g class="tick" opacity="1" transform="translate(180.5,0)">
<line stroke="currentColor" y2="6" y1="-10"></line>
<text fill="currentColor" y="9" dy="0.71em">8</text>
</g>
</g>
</svg><svg class="plot" fill="currentColor" font-family="system-ui, sans-serif" font-size="10" text-anchor="middle" width="640" height="60" viewBox="0 0 640 60" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<style>
:where(.plot) {
--plot-background: white;
display: block;
height: auto;
height: intrinsic;
max-width: 100%;
}

:where(.plot text),
:where(.plot tspan) {
white-space: pre;
}
</style>
<g aria-label="x-axis tick" aria-hidden="true" fill="none" stroke="currentColor" transform="translate(27,0)">
<path transform="translate(28,30)" d="M0,0L0,6"></path>
<path transform="translate(87,30)" d="M0,0L0,6"></path>
<path transform="translate(146,30)" d="M0,0L0,6"></path>
<path transform="translate(205,30)" d="M0,0L0,6"></path>
<path transform="translate(264,30)" d="M0,0L0,6"></path>
<path transform="translate(323,30)" d="M0,0L0,6"></path>
<path transform="translate(382,30)" d="M0,0L0,6"></path>
<path transform="translate(441,30)" d="M0,0L0,6"></path>
<path transform="translate(500,30)" d="M0,0L0,6"></path>
<path transform="translate(559,30)" d="M0,0L0,6"></path>
</g>
<g aria-label="x-axis tick label" transform="translate(27,9.5)">
<text y="0.71em" transform="translate(28,30)">0</text>
<text y="0.71em" transform="translate(87,30)">1</text>
<text y="0.71em" transform="translate(146,30)">2</text>
<text y="0.71em" transform="translate(205,30)">3</text>
<text y="0.71em" transform="translate(264,30)">4</text>
<text y="0.71em" transform="translate(323,30)">5</text>
<text y="0.71em" transform="translate(382,30)">6</text>
<text y="0.71em" transform="translate(441,30)">7</text>
<text y="0.71em" transform="translate(500,30)">8</text>
<text y="0.71em" transform="translate(559,30)">9</text>
</g>
<g aria-label="cell" fill="red">
<rect x="28" width="53" y="0" height="30" opacity="0.2"></rect>
<rect x="87" width="53" y="0" height="30" opacity="0.2"></rect>
<rect x="146" width="53" y="0" height="30" opacity="0.4"></rect>
<rect x="205" width="53" y="0" height="30" opacity="0.4"></rect>
<rect x="264" width="53" y="0" height="30" opacity="0.4"></rect>
<rect x="323" width="53" y="0" height="30" opacity="0.6"></rect>
<rect x="382" width="53" y="0" height="30" opacity="0.6"></rect>
<rect x="441" width="53" y="0" height="30" opacity="0.6"></rect>
<rect x="500" width="53" y="0" height="30" opacity="0.8"></rect>
<rect x="559" width="53" y="0" height="30" opacity="0.8"></rect>
</g>
</svg></figure>
12 changes: 12 additions & 0 deletions test/plots/ordinal-opacity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,15 @@ export async function ordinalOpacity() {
export async function ordinalOpacityImplicitZero() {
return Plot.cellX(d3.range(2, 10), {fill: "red", opacity: Plot.identity}).plot({opacity: {type: "ordinal"}});
}

export async function ordinalOpacityRamp() {
return Plot.cellX(d3.range(10), {fill: "red", opacity: Plot.identity}).plot({
opacity: {type: "ordinal", legend: "ramp"}
});
}

export async function ordinalOpacityThreshold() {
return Plot.cellX(d3.range(10), {fill: "red", opacity: Plot.identity}).plot({
opacity: {type: "threshold", legend: true, domain: [2, 5, 8], range: [0.2, 0.4, 0.6, 0.8]}
});
}