UNPKG

myd3g

Version:

just simple D3js graoh plugin

298 lines (291 loc) 15.7 kB
// src/graphs/Barchart.tsx import { useEffect, useRef } from "react"; import * as d3 from "d3"; import { Fragment, jsx } from "react/jsx-runtime"; function Barchart({ data, width = 928, height = 500, marginTop = 20, marginRight = 0, marginBottom = 30, marginLeft = 40, className }) { const chartRef = useRef(null); useEffect(() => { if (!data || data.length === 0) return; d3.select(chartRef.current).selectAll("*").remove(); const svg = d3.select(chartRef.current).attr("viewBox", [0, 0, width, height]).attr("width", width).attr("height", height).attr("style", "max-width: 100%; height: auto;"); const x = d3.scaleBand().domain(data.map((d) => d.x)).range([marginLeft, width - marginRight]).padding(0.1); const y = d3.scaleLinear().domain([0, d3.max(data, (d) => d.y) || 0]).nice().range([height - marginBottom, marginTop]); const xAxis = d3.axisBottom(x).tickSizeOuter(0); const yAxis = d3.axisLeft(y); svg.append("g").attr("fill", "steelblue").selectAll("rect").data(data).join("rect").attr("x", (d) => x(d.x) ?? 0).attr("y", (d) => y(d.y)).attr("height", (d) => { const height2 = y(0) - y(d.y); return isNaN(height2) ? 0 : height2; }).attr("width", x.bandwidth()); svg.append("g").attr("transform", `translate(0,${height - marginBottom})`).call(xAxis); svg.append("g").attr("transform", `translate(${marginLeft},0)`).call(yAxis).call((g) => g.select(".domain").remove()); function zoom3(svg2) { const extent4 = [[marginLeft, marginTop], [width - marginRight, height - marginTop]]; svg2.call(d3.zoom().scaleExtent([1, 8]).translateExtent(extent4).extent(extent4).on("zoom", zoomed)); function zoomed(event) { x.range([marginLeft, width - marginRight].map((d) => event.transform.applyX(d))); svg2.selectAll("rect").attr("x", (d) => x(d.x)).attr("width", x.bandwidth()); svg2.selectAll("g.x-axis").call(xAxis); } } zoom3(svg); }, [data]); return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx("svg", { className, ref: chartRef }) }); } var Barchart_default = Barchart; // src/graphs/BrushableScatterplot.tsx import { useCallback, useEffect as useEffect2, useRef as useRef2, useState } from "react"; import * as d32 from "d3"; import { jsx as jsx2, jsxs } from "react/jsx-runtime"; var BrushableScatterplot = ({ data, width = 600, height = 400, className, style, xDomainStart, xDomainEnd, yDomainStart, yDomainEnd, speciesValues, colors }) => { const svgRef = useRef2(null); const [selectedPoints, setSelectedPoints] = useState([]); const [speciesValue, setSpeciesValue] = useState([]); const [colorScaleState, setColorScaleState] = useState([]); useCallback(() => { setSpeciesValue(speciesValues); setColorScaleState(colors); }, []); useEffect2(() => { if (!svgRef.current) return; const svg = d32.select(svgRef.current); svg.selectAll("*").remove(); const margin = { top: 20, right: 30, bottom: 40, left: 50 }; const plotWidth = width - margin.left - margin.right; const plotHeight = height - margin.top - margin.bottom; const xScale = d32.scaleLinear().domain([xDomainStart, xDomainEnd]).range([0, plotWidth]); const yScale = d32.scaleLinear().domain([yDomainStart, yDomainEnd]).range([plotHeight, 0]); const chart = svg.append("g").attr("transform", `translate(${margin.left},${margin.top})`); chart.append("g").attr("transform", `translate(0,${plotHeight})`).call(d32.axisBottom(xScale)); chart.append("g").call(d32.axisLeft(yScale)); const colorScale = d32.scaleOrdinal().domain(speciesValue).range(colorScaleState); const points = chart.selectAll("circle").data(data).enter().append("circle").attr("cx", (d) => xScale(d.x)).attr("cy", (d) => yScale(d.y)).attr("r", 6).attr("fill", (d) => colorScale(d == null ? void 0 : d.species)).attr("stroke", "black").attr("class", "dot"); const brush3 = d32.brush().extent([ [0, 0], [plotWidth, plotHeight] ]).on("end", (event) => { if (!event.selection) return; const [[x0, y0], [x1, y1]] = event.selection; const brushedData = data.filter( (d) => xScale(d.x) >= x0 && xScale(d.x) <= x1 && yScale(d.y) >= y0 && yScale(d.y) <= y1 && d.species !== void 0 ); setSelectedPoints(brushedData); points.attr( "fill", (d) => brushedData.some((point) => point.x === d.x && point.y === d.y && (point == null ? void 0 : point.species) === (d == null ? void 0 : d.species)) ? "orange" : colorScale(d == null ? void 0 : d.species) ); }); chart.append("g").attr("class", "brush").call(brush3); }, [width, height]); return /* @__PURE__ */ jsxs("div", { children: [ /* @__PURE__ */ jsx2("svg", { className, ref: svgRef, width, height, style: { border: "1px solid black", ...style } }), /* @__PURE__ */ jsxs("h3", { children: [ "Selected Points: ", selectedPoints.length ] }), /* @__PURE__ */ jsx2("ul", { children: selectedPoints.map((point, index) => /* @__PURE__ */ jsx2("li", { children: `Sepal Length: ${point.x}, Sepal Width: ${point.y}, Species: ${point == null ? void 0 : point.species}` }, index)) }) ] }); }; var BrushableScatterplot_default = BrushableScatterplot; // src/graphs/MultiIndexChart.tsx import { useEffect as useEffect3, useRef as useRef3 } from "react"; import * as d33 from "d3"; import { jsx as jsx3 } from "react/jsx-runtime"; function MultiIndexChart({ data, width = 700, height = 400, margin, className, style }) { const svgRef = useRef3(null); useEffect3(() => { if (!data || Object.keys(data).length === 0) return; margin = { top: 30, right: 60, bottom: 40, left: 50 }; const svg = d33.select(svgRef.current); svg.selectAll("*").remove(); const stocks = Object.keys(data); const indexedData = stocks.map((stock) => ({ name: stock, values: data[stock].map((d) => ({ x: new Date(d.x), y: d.y / data[stock][0].y // Normalize values })) })); const xScale = d33.scaleUtc().domain(d33.extent(indexedData[0].values, (d) => d.x).map((d) => d ?? /* @__PURE__ */ new Date())).range([margin.left, width - margin.right]).clamp(true); const yScale = d33.scaleLinear().domain([ d33.min(indexedData.flatMap((stock) => stock.values.map((d) => d.y))) - 0.2, d33.max(indexedData.flatMap((stock) => stock.values.map((d) => d.y))) + 0.2 ]).rangeRound([height - margin.bottom, margin.top]); const colorScale = d33.scaleOrdinal(d33.schemeCategory10).domain(stocks); const line3 = d33.line().x((d) => xScale(d.x)).y((d) => yScale(d.y)).curve(d33.curveMonotoneX); svg.append("g").attr("transform", `translate(0,${height - margin.bottom})`).call(d33.axisBottom(xScale).ticks(6)); svg.append("g").attr("transform", `translate(${margin.left},0)`).call(d33.axisLeft(yScale)); indexedData.forEach((stock) => { svg.append("path").datum(stock.values).attr("fill", "none").attr("stroke", colorScale(stock.name)).attr("stroke-width", 2).attr("d", line3); svg.append("text").attr("x", xScale(stock.values[stock.values.length - 1].x) + 5).attr("y", yScale(stock.values[stock.values.length - 1].y)).attr("fill", String(colorScale(stock.name))).attr("font-size", "12px").text(stock.name); }); }, [data]); return /* @__PURE__ */ jsx3("svg", { className, ref: svgRef, width, height, style: { ...style } }); } var MultiIndexChart_default = MultiIndexChart; // src/graphs/PannableChart.tsx import { useEffect as useEffect4, useRef as useRef4 } from "react"; import * as d34 from "d3"; import { jsx as jsx4 } from "react/jsx-runtime"; function PannableChart({ data, margin, width, height, className, style }) { const svgRef = useRef4(null); useEffect4(() => { margin = { top: 20, right: 30, bottom: 30, left: 40 }; width = 600 - margin.left - margin.right; height = 300 - margin.top - margin.bottom; if (!svgRef.current) return; const svg = d34.select(svgRef.current); svg.selectAll("*").remove(); const chart = svg.attr("width", width + margin.left + margin.right).attr("height", height + margin.top + margin.bottom).append("g").attr("transform", `translate(${margin.left},${margin.top})`); const xScale = d34.scaleUtc().domain([0, d34.max(data, (d) => d.x)]).range([0, width]); const yScale = d34.scaleLinear().domain([0, d34.max(data, (d) => d.y)]).range([height, 0]); const line3 = d34.area().x((d) => xScale(d.x)).y1((d) => yScale(d.y)).y0(yScale(0)).curve(d34.curveStep); const path = chart.append("path").datum(data).attr("fill", "steelblue").attr("stroke", "steelblue").attr("stroke-width", 2).attr("d", line3); const xAxis = d34.axisBottom(xScale); const xAxisGroup = chart.append("g").attr("transform", `translate(0,${height})`).call(xAxis); const yAxis = d34.axisLeft(yScale); chart.append("g").call(yAxis); const zoom3 = d34.zoom().scaleExtent([1, 1]).translateExtent([ [-width, 0], [2 * width, height] ]).on("zoom", (event) => { const { transform } = event; chart.attr("transform", transform); }); svg.call(zoom3); }, []); return /* @__PURE__ */ jsx4("svg", { className, ref: svgRef, style: { border: "1px solid #ccc", ...style } }); } var PannableChart_default = PannableChart; // src/graphs/ScatterplotMatrix.tsx import { useEffect as useEffect5, useRef as useRef5 } from "react"; import * as d35 from "d3"; import { jsx as jsx5 } from "react/jsx-runtime"; function ScatterplotMatrix({ data, margin, size, padding, className, style }) { const ref = useRef5(null); useEffect5(() => { if (!data || data.length === 0) return; margin = { top: 20, right: 20, bottom: 20, left: 20 }; size = 150; padding = 20; const columns = Object.keys(data[0]); const numCols = columns.length; const color = d35.scaleOrdinal().domain(data.map((_, i) => i.toString())).range(d35.schemeCategory10); const svg = d35.select(ref.current).attr("width", size * numCols + margin.left + margin.right).attr("height", size * numCols + margin.top + margin.bottom).append("g").attr("transform", `translate(${margin.left},${margin.top})`); const scales = {}; columns.forEach((col) => { scales[col] = d35.scaleLinear().domain(d35.extent(data, (d) => d[col])).range([padding, size - padding]); }); function brushed(event) { const selection = event.selection; if (!selection) return; const [[x0, y0], [x1, y1]] = selection; d35.selectAll("circle").classed("selected", (d) => { const x = scales[d.xVar](d[d.xVar]); const y = scales[d.yVar](d[d.yVar]); return x >= x0 && x <= x1 && y >= y0 && y <= y1; }); } columns.forEach((xVar, i) => { columns.forEach((yVar, j) => { const g = svg.append("g").attr("transform", `translate(${i * size},${j * size})`); g.append("rect").attr("fill", "none").attr("stroke", "#ddd").attr("width", size).attr("height", size); const brush3 = d35.brush().extent([[0, 0], [size, size]]).on("brush", brushed); g.append("g").attr("class", "brush").call(brush3); g.selectAll("circle").data(data).enter().append("circle").attr("class", "circle").attr("cx", (d) => scales[xVar](d[xVar])).attr("cy", (d) => scales[yVar](d[yVar])).attr("r", 3).attr("fill", "steelblue").attr("opacity", 0.7).attr("fill", (d) => color(d.C)); }); }); }, []); return /* @__PURE__ */ jsx5("svg", { className, style: { ...style }, ref }); } var ScatterplotMatrix_default = ScatterplotMatrix; // src/graphs/SunburstChart.tsx import { useEffect as useEffect6, useRef as useRef6, useState as useState2 } from "react"; import * as d36 from "d3"; import { jsx as jsx6, jsxs as jsxs2 } from "react/jsx-runtime"; function SunburstChart({ data, width = 600, height = 600, className, style, text }) { const ref = useRef6(null); const [hoveredData, setHoveredData] = useState2({ sequence: [], percentage: 0 }); text = "Hover on chart"; useEffect6(() => { if (!data) return; const radius = width / 2; const partition2 = (data2) => d36.partition().size([2 * Math.PI, radius])( d36.hierarchy(data2).sum((d) => d.value).sort((a, b) => (b.value || 0) - (a.value || 0)) ); const root = partition2(data); const color = d36.scaleOrdinal(d36.quantize(d36.interpolateRainbow, data.children.length + 1)); const arc2 = d36.arc().startAngle((d) => d.x0).endAngle((d) => d.x1).innerRadius((d) => d.y0).outerRadius((d) => d.y1); const svg = d36.select(ref.current).attr("viewBox", [-width / 2, -height / 2, width, height]).style("font", "12px sans-serif"); svg.selectAll("*").remove(); svg.node(); const g = svg.append("g"); g.selectAll("path").data(root.descendants().filter((d) => d.depth)).enter().append("path").attr("fill", (d) => color(d.ancestors().map((d2) => d2.data.name).join("/"))).attr("d", arc2).style("cursor", "pointer").on("mouseenter", function(e, d) { d36.select(this).attr("opacity", 0.7); setHoveredData({ sequence: d.ancestors().map((d2) => d2.data.name), percentage: (d.value || 0) / (root.value || 0) * 100 }); }).on("mouseleave", function() { d36.select(this).attr("opacity", 1); setHoveredData({ sequence: [], percentage: 0 }); }); svg.append("text").attr("text-anchor", "middle").attr("dy", "-0.5em").attr("font-size", "16px").attr("font-weight", "bold").text(`Sunburst Chart`); }, [width, height]); return /* @__PURE__ */ jsxs2("div", { className: "relative flex flex-col items-center mt-4", children: [ hoveredData.percentage > 0 ? /* @__PURE__ */ jsxs2("p", { className: "text-black rounded", children: [ hoveredData.percentage.toFixed(2), "% of visits begin with this sequence." ] }) : /* @__PURE__ */ jsx6("p", { children: text }), /* @__PURE__ */ jsx6("svg", { className, style: { ...style }, ref, width, height }) ] }); } var SunburstChart_default = SunburstChart; // src/graphs/Linechart.tsx import { useEffect as useEffect7, useRef as useRef7 } from "react"; import * as d37 from "d3"; import { jsx as jsx7 } from "react/jsx-runtime"; var LineChart = ({ data, margin, width, height, className, style }) => { const svgRef = useRef7(null); useEffect7(() => { if (!data || data.length === 0) return; margin = { top: 20, right: 30, bottom: 30, left: 40 }, width = 600 - margin.left - margin.right, height = 400 - margin.top - margin.bottom; const svg = d37.select(svgRef.current).attr("width", width + margin.left + margin.right).attr("height", height + margin.top + margin.bottom).append("g").attr("transform", `translate(${margin.left},${margin.top})`); const x = d37.scaleLinear().domain(d37.extent(data, (d) => d.x)).range([0, width]); const y = d37.scaleLinear().domain([0, d37.max(data, (d) => d.y)]).range([height, 0]); const line3 = d37.line().x((d) => x(d.x)).y((d) => y(d.y)).curve(d37.curveMonotoneX); svg.append("g").attr("transform", `translate(0,${height})`).call(d37.axisBottom(x)); svg.append("g").call(d37.axisLeft(y)); svg.append("path").datum(data).attr("fill", "none").attr("stroke", "steelblue").attr("stroke-width", 2).attr("d", line3); }, [data]); return /* @__PURE__ */ jsx7("svg", { className, style: { ...style }, ref: svgRef }); }; var Linechart_default = LineChart; export { Barchart_default as Barchart, BrushableScatterplot_default as BrushableScatterplot, Linechart_default as LineChart, MultiIndexChart_default as MultiIndexChart, PannableChart_default as PannableChart, ScatterplotMatrix_default as ScatterplotMatrix, SunburstChart_default as SunburstChart };