From 7ce608016c5d29c63e14b70ff2e99f71d5d750c2 Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Mon, 10 Jun 2024 08:35:55 -0700 Subject: [PATCH 1/2] top-level locale option --- src/context.d.ts | 3 +++ src/context.js | 4 ++-- src/plot.d.ts | 3 +++ src/plot.js | 2 +- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/context.d.ts b/src/context.d.ts index ce2c3568d8..06058f2d31 100644 --- a/src/context.d.ts +++ b/src/context.d.ts @@ -12,6 +12,9 @@ export interface Context { /** The current owner SVG element. */ ownerSVGElement: SVGSVGElement; + /** The current locale. Defaults to "en-US". */ + locale: string; + /** The Plot’s (typically generated) class name, for custom styles. */ className: string; diff --git a/src/context.js b/src/context.js index 3e3e55d705..1956e4114d 100644 --- a/src/context.js +++ b/src/context.js @@ -2,8 +2,8 @@ import {creator, select} from "d3"; import {maybeClip} from "./options.js"; export function createContext(options = {}) { - const {document = typeof window !== "undefined" ? window.document : undefined, clip} = options; - return {document, clip: maybeClip(clip)}; + const {locale = "en-US", document = typeof window !== "undefined" ? window.document : undefined, clip} = options; + return {locale, document, clip: maybeClip(clip)}; } export function create(name, {document}) { diff --git a/src/plot.d.ts b/src/plot.d.ts index 05fc238dc5..98924ea97e 100644 --- a/src/plot.d.ts +++ b/src/plot.d.ts @@ -74,6 +74,9 @@ export interface PlotOptions extends ScaleDefaults { // other top-level options + /** The desired locale. Defaults to "en-US". */ + locale?: string; + /** * Custom styles to override Plot’s defaults. Styles may be specified either * as a string of inline styles (*e.g.*, `"color: red;"`, in the same fashion diff --git a/src/plot.js b/src/plot.js index a091d8d8a8..58fd43246a 100644 --- a/src/plot.js +++ b/src/plot.js @@ -349,7 +349,7 @@ export function plot(options = {}) { .attr("font-family", "initial") // fix emoji rendering in Chrome .text("\u26a0\ufe0f") // emoji variation selector .append("title") - .text(`${w.toLocaleString("en-US")} warning${w === 1 ? "" : "s"}. Please check the console.`); + .text(`${w.toLocaleString("en-US")} warning${w === 1 ? "" : "s"}. Please check the console.`); // non-localized } return figure; From 397c223b8c2d286dd0bdc79c1607c0991efb4fac Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Mon, 10 Jun 2024 08:57:35 -0700 Subject: [PATCH 2/2] locale-axis axis tick format --- src/marks/axis.js | 22 +++++++++++----------- test/plots/index.ts | 1 + test/plots/locale.ts | 5 +++++ 3 files changed, 17 insertions(+), 11 deletions(-) create mode 100644 test/plots/locale.ts diff --git a/src/marks/axis.js b/src/marks/axis.js index 406ff70dd5..333faed9d7 100644 --- a/src/marks/axis.js +++ b/src/marks/axis.js @@ -1,5 +1,5 @@ import {InternSet, extent, format, utcFormat} from "d3"; -import {formatDefault} from "../format.js"; +import {formatAuto} from "../format.js"; import {marks} from "../mark.js"; import {radians} from "../math.js"; import {arrayify, constant, identity, keyword, number, range, valueof} from "../options.js"; @@ -384,9 +384,9 @@ function axisTextKy( ...options, dx: anchor === "left" ? +dx - tickSize - tickPadding + +insetLeft : +dx + +tickSize + +tickPadding - insetRight }, - function (scale, data, ticks, tickFormat, channels) { + function (scale, data, options, channels, context) { if (fontVariant === undefined) this.fontVariant = inferFontVariant(scale); - if (text === undefined) channels.text = inferTextChannel(scale, data, ticks, tickFormat, anchor); + if (text === undefined) channels.text = inferTextChannel(scale, data, {...options, anchor}, context); } ); } @@ -430,9 +430,9 @@ function axisTextKx( ...options, dy: anchor === "bottom" ? +dy + +tickSize + +tickPadding - insetBottom : +dy - tickSize - tickPadding + +insetTop }, - function (scale, data, ticks, tickFormat, channels) { + function (scale, data, options, channels, context) { if (fontVariant === undefined) this.fontVariant = inferFontVariant(scale); - if (text === undefined) channels.text = inferTextChannel(scale, data, ticks, tickFormat, anchor); + if (text === undefined) channels.text = inferTextChannel(scale, data, {...options, anchor}, context); } ); } @@ -612,7 +612,7 @@ function axisMark(mark, k, data, properties, options, initialize) { channels[k] = {scale: k, value: identity}; } } - initialize?.call(this, scale, data, ticks, tickFormat, channels); + initialize?.call(this, scale, data, {ticks, tickFormat}, channels, context); const initializedChannels = Object.fromEntries( Object.entries(channels).map(([name, channel]) => { return [name, {...channel, value: valueof(data, channel.value)}]; @@ -641,8 +641,8 @@ function inferTickCount(scale, tickSpacing) { return (max - min) / tickSpacing; } -function inferTextChannel(scale, data, ticks, tickFormat, anchor) { - return {value: inferTickFormat(scale, data, ticks, tickFormat, anchor)}; +function inferTextChannel(scale, data, options, context) { + return {value: inferTickFormat(scale, data, options, context)}; } // D3’s ordinal scales simply use toString by default, but if the ordinal scale @@ -651,15 +651,15 @@ function inferTextChannel(scale, data, ticks, tickFormat, anchor) { // time ticks, we want to use the multi-line time format (e.g., Jan 26) if // possible, or the default ISO format (2014-01-26). TODO We need a better way // to infer whether the ordinal scale is UTC or local time. -export function inferTickFormat(scale, data, ticks, tickFormat, anchor) { +export function inferTickFormat(scale, data, {ticks, tickFormat, anchor}, {locale}) { return typeof tickFormat === "function" && !(scale.type === "log" && scale.tickFormat) ? tickFormat : tickFormat === undefined && data && isTemporal(data) - ? inferTimeFormat(scale.type, data, anchor) ?? formatDefault + ? inferTimeFormat(scale.type, data, anchor) ?? formatAuto(locale) : scale.tickFormat ? scale.tickFormat(typeof ticks === "number" ? ticks : null, tickFormat) : tickFormat === undefined - ? formatDefault + ? formatAuto(locale) : typeof tickFormat === "string" ? (isTemporal(scale.domain()) ? utcFormat : format)(tickFormat) : constant(tickFormat); diff --git a/test/plots/index.ts b/test/plots/index.ts index 907109e590..d285175ecb 100644 --- a/test/plots/index.ts +++ b/test/plots/index.ts @@ -151,6 +151,7 @@ export * from "./likert-survey.js"; export * from "./linear-regression-cars.js"; export * from "./linear-regression-mtcars.js"; export * from "./linear-regression-penguins.js"; +export * from "./locale.js"; export * from "./log-degenerate.js"; export * from "./log-tick-format.js"; export * from "./long-labels.js"; diff --git a/test/plots/locale.ts b/test/plots/locale.ts new file mode 100644 index 0000000000..bbbcf43928 --- /dev/null +++ b/test/plots/locale.ts @@ -0,0 +1,5 @@ +import * as Plot from "@observablehq/plot"; + +export async function localeFrAxis() { + return Plot.plot({locale: "fr", x: {domain: [0, 10e3]}}); +}