From 9be0de8000f5c72c8695167f84b7acc8780d9d15 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 20 Apr 2026 13:18:33 +0000 Subject: [PATCH] Replace wildcard d3 imports with named sub-package imports Every 'import * as d3 from "d3"' replaced with imports from the specific d3 sub-packages actually used (d3-scale, d3-array, d3-shape, d3-format, d3-time-format, d3-scale-chromatic). Rollup can now tree-shake unused d3 modules, reducing the bundled d3 footprint significantly. Line.js dynamic d3[type]() dispatch replaced with a static generators map. https://claude.ai/code/session_01Bf9veNX9mqfMqrCEGnujAo --- src/Cartesian/Axis.js | 4 ++-- src/Charts/BarChart.js | 12 +++++++----- src/Charts/Histogram.js | 18 ++++++++++-------- src/Charts/PieChart.js | 26 +++++++++++++++----------- src/Charts/ScatterPlot.js | 18 ++++++++++-------- src/Charts/StackedBarChart.js | 18 +++++++++++------- src/Charts/Timeline.js | 25 ++++++++++++++----------- src/Components/Bars.js | 6 +++--- src/Components/Line.js | 8 +++++--- 9 files changed, 77 insertions(+), 58 deletions(-) diff --git a/src/Cartesian/Axis.js b/src/Cartesian/Axis.js index bbd0542..e069b0a 100644 --- a/src/Cartesian/Axis.js +++ b/src/Cartesian/Axis.js @@ -1,6 +1,6 @@ import React from "react" import PropTypes from "prop-types" -import * as d3 from 'd3' +import { format } from 'd3-format' import { useDimensionsContext } from "../Components/Chart"; const axisComponentsByDimension = { @@ -31,7 +31,7 @@ Axis.propTypes = { Axis.defaultProps = { dimension: "x", scale: null, - formatTick: d3.format(","), + formatTick: format(","), } export default Axis diff --git a/src/Charts/BarChart.js b/src/Charts/BarChart.js index ee2da60..1375392 100644 --- a/src/Charts/BarChart.js +++ b/src/Charts/BarChart.js @@ -1,6 +1,8 @@ import React, { useCallback, useMemo } from "react" import PropTypes from "prop-types" -import * as d3 from "d3" +import { scaleBand, scaleLinear } from 'd3-scale' +import { max } from 'd3-array' +import { format } from 'd3-format' import Chart from "../Components/Chart" import Bars from "../Components/Bars" @@ -11,7 +13,7 @@ import { useChartDimensions, accessorPropsType } from "../Utils/utils" import { useTooltip } from "../Utils/useTooltip" const DEFAULT_COLOR = '#9980FA' -const fmt = d3.format(",") +const fmt = format(",") const BarChart = ({ data, xAccessor, yAccessor, xLabel, yLabel, @@ -22,7 +24,7 @@ const BarChart = ({ const { wrapperRef, tooltip, showTooltip, moveTooltip, hideTooltip } = useTooltip() const xScale = useMemo(() => - d3.scaleBand() + scaleBand() .domain(data ? data.map(xAccessor) : []) .range([0, dimensions.boundedWidth]) .padding(barPadding ?? 0.2), @@ -30,8 +32,8 @@ const BarChart = ({ ) const yScale = useMemo(() => - d3.scaleLinear() - .domain([yMin ?? 0, d3.max(data, yAccessor) || 0]) + scaleLinear() + .domain([yMin ?? 0, max(data, yAccessor) || 0]) .range([dimensions.boundedHeight, 0]) .nice(), [data, yAccessor, dimensions.boundedHeight, yMin] diff --git a/src/Charts/Histogram.js b/src/Charts/Histogram.js index 6087c00..bf36de9 100644 --- a/src/Charts/Histogram.js +++ b/src/Charts/Histogram.js @@ -1,6 +1,8 @@ import React, { useCallback, useMemo } from "react" import PropTypes from "prop-types" -import * as d3 from "d3" +import { scaleLinear } from 'd3-scale' +import { extent, max, histogram } from 'd3-array' +import { format } from 'd3-format' import Chart from "../Components/Chart" import Bars from "../Components/Bars" @@ -13,8 +15,8 @@ import { useTooltip } from "../Utils/useTooltip" const DEFAULT_GRADIENT = ["#9980FA", "rgb(226, 222, 243)"] const DEFAULT_COLOR = '#9980FA' -const fmt = d3.format(",") -const fmtFixed = d3.format(",.2~f") +const fmt = format(",") +const fmtFixed = format(",.2~f") const BAR_PADDING = 2 const Histogram = ({ @@ -30,15 +32,15 @@ const Histogram = ({ const numberOfThresholds = thresholds || 9 const xScale = useMemo(() => - d3.scaleLinear() - .domain(d3.extent(data, xAccessor)) + scaleLinear() + .domain(extent(data, xAccessor)) .range([0, dimensions.boundedWidth]) .nice(numberOfThresholds), [data, xAccessor, dimensions.boundedWidth, numberOfThresholds] ) const bins = useMemo(() => - d3.histogram() + histogram() .domain(xScale.domain()) .value(xAccessor) .thresholds(xScale.ticks(numberOfThresholds))(data), @@ -46,8 +48,8 @@ const Histogram = ({ ) const yScale = useMemo(() => - d3.scaleLinear() - .domain([0, d3.max(bins, d => d.length) || 0]) + scaleLinear() + .domain([0, max(bins, d => d.length) || 0]) .range([dimensions.boundedHeight, 0]) .nice(), [bins, dimensions.boundedHeight] diff --git a/src/Charts/PieChart.js b/src/Charts/PieChart.js index 11bc32c..c4b8c4f 100644 --- a/src/Charts/PieChart.js +++ b/src/Charts/PieChart.js @@ -1,6 +1,10 @@ import React, { useCallback, useMemo } from "react" import PropTypes from "prop-types" -import * as d3 from "d3" +import { scaleOrdinal } from 'd3-scale' +import { arc, pie } from 'd3-shape' +import { sum } from 'd3-array' +import { schemeSet2 } from 'd3-scale-chromatic' +import { format } from 'd3-format' import Chart from "../Components/Chart" import ChartLayout from "../Components/ChartLayout" @@ -10,7 +14,7 @@ import { useTooltip } from "../Utils/useTooltip" const DEFAULT_MARGIN = { marginTop: 20, marginRight: 20, marginBottom: 20, marginLeft: 20 } const MIN_LABEL_ANGLE = 0.35 -const fmt = d3.format(",") +const fmt = format(",") const PieChart = ({ data, valueAccessor, labelAccessor, @@ -21,7 +25,7 @@ const PieChart = ({ const { wrapperRef, tooltip, showTooltip, moveTooltip, hideTooltip } = useTooltip() const colorScale = useMemo(() => - d3.scaleOrdinal(Array.isArray(colors) ? colors : d3.schemeSet2), + scaleOrdinal(Array.isArray(colors) ? colors : schemeSet2), [colors] ) @@ -34,7 +38,7 @@ const PieChart = ({ : 0 const arcGenerator = useMemo(() => - d3.arc().innerRadius(resolvedInnerRadius).outerRadius(outerRadius), + arc().innerRadius(resolvedInnerRadius).outerRadius(outerRadius), [resolvedInnerRadius, outerRadius] ) @@ -42,11 +46,11 @@ const PieChart = ({ const r = resolvedInnerRadius > 0 ? (resolvedInnerRadius + outerRadius) / 2 : outerRadius * 0.65 - return d3.arc().innerRadius(r).outerRadius(r) + return arc().innerRadius(r).outerRadius(r) }, [resolvedInnerRadius, outerRadius]) const arcs = useMemo(() => - data ? d3.pie() + data ? pie() .value(valueAccessor) .padAngle(padAngle ?? 0.02) .sort(null)(data) @@ -55,7 +59,7 @@ const PieChart = ({ ) const total = useMemo(() => - data ? d3.sum(data, valueAccessor) : 0, + data ? sum(data, valueAccessor) : 0, [data, valueAccessor] ) @@ -95,22 +99,22 @@ const PieChart = ({
- {arcs.map((arc, i) => { + {arcs.map((a, i) => { const label = labelAccessor ? labelAccessor(data[i]) : String(i) return ( handleSliceEnter(data[i], label, e)} onMouseMove={e => handleSliceMove(data[i], label, e)} onMouseLeave={hideTooltip} /> - {displayLabels && (arc.endAngle - arc.startAngle) >= MIN_LABEL_ANGLE && ( + {displayLabels && (a.endAngle - a.startAngle) >= MIN_LABEL_ANGLE && ( {label} diff --git a/src/Charts/ScatterPlot.js b/src/Charts/ScatterPlot.js index 29589c0..1d9a432 100644 --- a/src/Charts/ScatterPlot.js +++ b/src/Charts/ScatterPlot.js @@ -1,6 +1,8 @@ import React, { useCallback, useMemo } from "react" import PropTypes from "prop-types" -import * as d3 from "d3" +import { scaleLinear } from 'd3-scale' +import { extent } from 'd3-array' +import { format } from 'd3-format' import Chart from "../Components/Chart" import Circles from "../Components/Circles" @@ -11,7 +13,7 @@ import { useChartDimensions, accessorPropsType } from "../Utils/utils" import { useTooltip } from "../Utils/useTooltip" const DEFAULT_COLOR = '#9980FA' -const fmt = d3.format(",") +const fmt = format(",") const ScatterPlot = ({ data, xAccessor, yAccessor, xLabel, yLabel, @@ -22,15 +24,15 @@ const ScatterPlot = ({ const { wrapperRef, tooltip, showTooltip, moveTooltip, hideTooltip } = useTooltip() const xScale = useMemo(() => { - const extent = d3.extent(data, xAccessor) - const domain = extent[0] === extent[1] ? [extent[0] - 1, extent[0] + 1] : extent - return d3.scaleLinear().domain(domain).range([0, dimensions.boundedWidth]).nice() + const ext = extent(data, xAccessor) + const domain = ext[0] === ext[1] ? [ext[0] - 1, ext[0] + 1] : ext + return scaleLinear().domain(domain).range([0, dimensions.boundedWidth]).nice() }, [data, xAccessor, dimensions.boundedWidth]) const yScale = useMemo(() => { - const extent = d3.extent(data, yAccessor) - const domain = extent[0] === extent[1] ? [extent[0] - 1, extent[0] + 1] : extent - return d3.scaleLinear().domain(domain).range([dimensions.boundedHeight, 0]).nice() + const ext = extent(data, yAccessor) + const domain = ext[0] === ext[1] ? [ext[0] - 1, ext[0] + 1] : ext + return scaleLinear().domain(domain).range([dimensions.boundedHeight, 0]).nice() }, [data, yAccessor, dimensions.boundedHeight]) const legendItems = useMemo(() => diff --git a/src/Charts/StackedBarChart.js b/src/Charts/StackedBarChart.js index 9f706f1..4c70efa 100644 --- a/src/Charts/StackedBarChart.js +++ b/src/Charts/StackedBarChart.js @@ -1,6 +1,10 @@ import React, { useCallback, useMemo } from "react" import PropTypes from "prop-types" -import * as d3 from "d3" +import { scaleOrdinal, scaleBand, scaleLinear } from 'd3-scale' +import { stack } from 'd3-shape' +import { max } from 'd3-array' +import { schemeSet2 } from 'd3-scale-chromatic' +import { format } from 'd3-format' import Chart from "../Components/Chart" import Axis from "../Cartesian/Axis" @@ -9,7 +13,7 @@ import Tooltip from "../Components/Tooltip" import { useChartDimensions, accessorPropsType } from "../Utils/utils" import { useTooltip } from "../Utils/useTooltip" -const fmt = d3.format(",") +const fmt = format(",") const StackedBarChart = ({ data, xAccessor, keys, colors, @@ -20,17 +24,17 @@ const StackedBarChart = ({ const { wrapperRef, tooltip, showTooltip, moveTooltip, hideTooltip } = useTooltip() const colorScale = useMemo(() => - d3.scaleOrdinal(Array.isArray(colors) ? colors : d3.schemeSet2).domain(keys), + scaleOrdinal(Array.isArray(colors) ? colors : schemeSet2).domain(keys), [colors, keys] ) const series = useMemo(() => - data && keys && keys.length ? d3.stack().keys(keys)(data) : [], + data && keys && keys.length ? stack().keys(keys)(data) : [], [data, keys] ) const xScale = useMemo(() => - d3.scaleBand() + scaleBand() .domain(data ? data.map(xAccessor) : []) .range([0, dimensions.boundedWidth]) .padding(barPadding ?? 0.2), @@ -38,8 +42,8 @@ const StackedBarChart = ({ ) const yScale = useMemo(() => - d3.scaleLinear() - .domain([0, d3.max(series, s => d3.max(s, d => d[1])) || 0]) + scaleLinear() + .domain([0, max(series, s => max(s, d => d[1])) || 0]) .range([dimensions.boundedHeight, 0]) .nice(), [series, dimensions.boundedHeight] diff --git a/src/Charts/Timeline.js b/src/Charts/Timeline.js index 624c535..c93a77f 100644 --- a/src/Charts/Timeline.js +++ b/src/Charts/Timeline.js @@ -1,6 +1,9 @@ import React, { useCallback, useMemo, useState } from "react" import PropTypes from "prop-types" -import * as d3 from "d3" +import { scaleTime, scaleLinear } from 'd3-scale' +import { extent, bisector } from 'd3-array' +import { timeFormat } from 'd3-time-format' +import { format } from 'd3-format' import Chart from "../Components/Chart" import Line from "../Components/Line" @@ -12,11 +15,11 @@ import Tooltip from "../Components/Tooltip" import { useChartDimensions, accessorPropsType, useUniqueId } from "../Utils/utils" import { useTooltip } from "../Utils/useTooltip" -const formatDate = d3.timeFormat("%-b %-d") -const formatTooltipDate = d3.timeFormat("%-b %-d, %Y") +const formatDate = timeFormat("%-b %-d") +const formatTooltipDate = timeFormat("%-b %-d, %Y") const DEFAULT_GRADIENT = ["rgb(226, 222, 243)", "#f8f9fa"] const DEFAULT_COLOR = '#9980FA' -const fmt = d3.format(",") +const fmt = format(",") const Timeline = ({ data, xAccessor, yAccessor, xLabel, yLabel, @@ -34,15 +37,15 @@ const Timeline = ({ || (color ? [`${color}55`, "rgba(255,255,255,0)"] : DEFAULT_GRADIENT) const xScale = useMemo(() => - d3.scaleTime() - .domain(d3.extent(data, xAccessor)) + scaleTime() + .domain(extent(data, xAccessor)) .range([0, dimensions.boundedWidth]), [data, xAccessor, dimensions.boundedWidth] ) const yScale = useMemo(() => - d3.scaleLinear() - .domain(d3.extent(data, yAccessor)) + scaleLinear() + .domain(extent(data, yAccessor)) .range([dimensions.boundedHeight, 0]) .nice(), [data, yAccessor, dimensions.boundedHeight] @@ -53,20 +56,20 @@ const Timeline = ({ [yLabel, color] ) - const bisect = useMemo(() => d3.bisector(xAccessor).left, [xAccessor]) + const dateBisector = useMemo(() => bisector(xAccessor).left, [xAccessor]) const handleMouseMove = useCallback(e => { const svgEl = e.currentTarget.ownerSVGElement const { left: svgLeft } = svgEl.getBoundingClientRect() const mouseX = e.clientX - svgLeft - dimensions.marginLeft const date = xScale.invert(mouseX) - const idx = bisect(data, date, 1) + const idx = dateBisector(data, date, 1) const d0 = data[idx - 1] const d1 = data[idx] const i = !d1 || (d0 && date - xAccessor(d0) < xAccessor(d1) - date) ? idx - 1 : idx setHoveredIndex(i) showTooltip(e, { datum: data[i] }) - }, [data, xAccessor, xScale, bisect, dimensions.marginLeft, showTooltip]) + }, [data, xAccessor, xScale, dateBisector, dimensions.marginLeft, showTooltip]) const handleMouseLeave = useCallback(() => { setHoveredIndex(null) diff --git a/src/Components/Bars.js b/src/Components/Bars.js index af32fa7..f9ba6dc 100644 --- a/src/Components/Bars.js +++ b/src/Components/Bars.js @@ -1,6 +1,6 @@ import React from "react" import PropTypes from "prop-types" -import * as d3 from 'd3' +import { max } from 'd3-array' import { accessorPropsType, callAccessor } from "../Utils/utils"; const Bars = ({ data, keyAccessor, xAccessor, yAccessor, widthAccessor, heightAccessor, onMouseEnter, onMouseLeave, onMouseMove, ...props }) => ( @@ -11,8 +11,8 @@ const Bars = ({ data, keyAccessor, xAccessor, yAccessor, widthAccessor, heightAc key={keyAccessor(d, i)} x={callAccessor(xAccessor, d, i)} y={callAccessor(yAccessor, d, i)} - width={d3.max([callAccessor(widthAccessor, d, i), 0])} - height={d3.max([callAccessor(heightAccessor, d, i), 0])} + width={max([callAccessor(widthAccessor, d, i), 0])} + height={max([callAccessor(heightAccessor, d, i), 0])} onMouseEnter={onMouseEnter ? e => onMouseEnter(d, i, e) : undefined} onMouseLeave={onMouseLeave ? e => onMouseLeave(d, i, e) : undefined} onMouseMove={onMouseMove ? e => onMouseMove(d, i, e) : undefined} diff --git a/src/Components/Line.js b/src/Components/Line.js index 660863a..131be72 100644 --- a/src/Components/Line.js +++ b/src/Components/Line.js @@ -1,10 +1,12 @@ import React from "react" import PropTypes from "prop-types" -import * as d3 from "d3" +import { line, area, curveMonotoneX } from 'd3-shape' import { accessorPropsType } from "../Utils/utils"; +const generators = { line, area } + const Line = ({ type, data, xAccessor, yAccessor, y0Accessor, interpolation, ...props }) => { - const lineGenerator = d3[type]() + const lineGenerator = generators[type]() .x(xAccessor) .y(yAccessor) .curve(interpolation) @@ -35,7 +37,7 @@ Line.propTypes = { Line.defaultProps = { type: "line", y0Accessor: 0, - interpolation: d3.curveMonotoneX, + interpolation: curveMonotoneX, } export default Line