| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170 |
- import {pathRound as path} from "d3";
- import {inferFontVariant} from "../axes.js";
- import {create, createContext} from "../context.js";
- import {isNoneish, maybeColorChannel, maybeNumberChannel} from "../options.js";
- import {isOrdinalScale, isThresholdScale} from "../scales.js";
- import {applyInlineStyles, impliedString, maybeClassName} from "../style.js";
- import {inferTickFormat} from "../marks/axis.js";
- function maybeScale(scale, key) {
- if (key == null) return key;
- const s = scale(key);
- if (!s) throw new Error(`scale not found: ${key}`);
- return s;
- }
- export function legendSwatches(color, {opacity, ...options} = {}) {
- if (!isOrdinalScale(color) && !isThresholdScale(color))
- throw new Error(`swatches legend requires ordinal or threshold color scale (not ${color.type})`);
- return legendItems(color, options, (selection, scale, width, height) =>
- selection
- .append("svg")
- .attr("width", width)
- .attr("height", height)
- .attr("fill", scale.scale)
- .attr("fill-opacity", maybeNumberChannel(opacity)[1])
- .append("rect")
- .attr("width", "100%")
- .attr("height", "100%")
- );
- }
- export function legendSymbols(
- symbol,
- {
- fill = symbol.hint?.fill !== undefined ? symbol.hint.fill : "none",
- fillOpacity = 1,
- stroke = symbol.hint?.stroke !== undefined ? symbol.hint.stroke : isNoneish(fill) ? "currentColor" : "none",
- strokeOpacity = 1,
- strokeWidth = 1.5,
- r = 4.5,
- ...options
- } = {},
- scale
- ) {
- const [vf, cf] = maybeColorChannel(fill);
- const [vs, cs] = maybeColorChannel(stroke);
- const sf = maybeScale(scale, vf);
- const ss = maybeScale(scale, vs);
- const size = r * r * Math.PI;
- fillOpacity = maybeNumberChannel(fillOpacity)[1];
- strokeOpacity = maybeNumberChannel(strokeOpacity)[1];
- strokeWidth = maybeNumberChannel(strokeWidth)[1];
- return legendItems(symbol, options, (selection, scale, width, height) =>
- selection
- .append("svg")
- .attr("viewBox", "-8 -8 16 16")
- .attr("width", width)
- .attr("height", height)
- .attr("fill", vf === "color" ? (d) => sf.scale(d) : cf)
- .attr("fill-opacity", fillOpacity)
- .attr("stroke", vs === "color" ? (d) => ss.scale(d) : cs)
- .attr("stroke-opacity", strokeOpacity)
- .attr("stroke-width", strokeWidth)
- .append("path")
- .attr("d", (d) => {
- const p = path();
- symbol.scale(d).draw(p, size);
- return p;
- })
- );
- }
- function legendItems(scale, options = {}, swatch) {
- let {
- columns,
- tickFormat,
- fontVariant = inferFontVariant(scale),
- // TODO label,
- swatchSize = 15,
- swatchWidth = swatchSize,
- swatchHeight = swatchSize,
- marginLeft = 0,
- className,
- style,
- width
- } = options;
- const context = createContext(options);
- className = maybeClassName(className);
- tickFormat = inferTickFormat(scale.scale, scale.domain, undefined, tickFormat);
- const swatches = create("div", context).attr(
- "class",
- `${className}-swatches ${className}-swatches-${columns != null ? "columns" : "wrap"}`
- );
- let extraStyle;
- if (columns != null) {
- extraStyle = `:where(.${className}-swatches-columns .${className}-swatch) {
- display: flex;
- align-items: center;
- break-inside: avoid;
- padding-bottom: 1px;
- }
- :where(.${className}-swatches-columns .${className}-swatch::before) {
- flex-shrink: 0;
- }
- :where(.${className}-swatches-columns .${className}-swatch-label) {
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }`;
- swatches
- .style("columns", columns)
- .selectAll()
- .data(scale.domain)
- .enter()
- .append("div")
- .attr("class", `${className}-swatch`)
- .call(swatch, scale, swatchWidth, swatchHeight)
- .call((item) =>
- item.append("div").attr("class", `${className}-swatch-label`).attr("title", tickFormat).text(tickFormat)
- );
- } else {
- extraStyle = `:where(.${className}-swatches-wrap) {
- display: flex;
- align-items: center;
- min-height: 33px;
- flex-wrap: wrap;
- }
- :where(.${className}-swatches-wrap .${className}-swatch) {
- display: inline-flex;
- align-items: center;
- margin-right: 1em;
- }`;
- swatches
- .selectAll()
- .data(scale.domain)
- .enter()
- .append("span")
- .attr("class", `${className}-swatch`)
- .call(swatch, scale, swatchWidth, swatchHeight)
- .append(function () {
- return this.ownerDocument.createTextNode(tickFormat.apply(this, arguments));
- });
- }
- return swatches
- .call((div) =>
- div.insert("style", "*").text(
- `:where(.${className}-swatches) {
- font-family: system-ui, sans-serif;
- font-size: 10px;
- margin-bottom: 0.5em;
- }
- :where(.${className}-swatch > svg) {
- margin-right: 0.5em;
- overflow: visible;
- }
- ${extraStyle}`
- )
- )
- .style("margin-left", marginLeft ? `${+marginLeft}px` : null)
- .style("width", width === undefined ? null : `${+width}px`)
- .style("font-variant", impliedString(fontVariant, "normal"))
- .call(applyInlineStyles, style)
- .node();
- }
|