diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..91dfed8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.DS_Store +node_modules \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..1ce5336 --- /dev/null +++ b/README.md @@ -0,0 +1,84 @@ +# @strawberry-vis/g2-rect + +A lightweight React Component for @antv/g2. + +## Installing + +```bash +npm install @strawberry-vis/g2-rect +``` + +```jsx +import React, { useState } from "react"; +import { Chart } from "@strawberry-vis/g2-rect"; + +export function Card() { + return ( + + ); +} +``` + +## API Reference + +# props.**options** + +```js +import React, { useState } from "react"; +import { Chart } from "@strawberry-vis/g2-rect"; + +export function Card() { + const [options, setOptions] = useState({ + type: "interval", + data: [ + { genre: "Sports", sold: 275 }, + { genre: "Strategy", sold: 115 }, + { genre: "Action", sold: 120 }, + { genre: "Shooter", sold: 350 }, + { genre: "Other", sold: 150 }, + ], + encode: { x: "genre", y: "sold" }, + }); + + function onClick() { + setOptions({ ...options, data: options.data.slice(0, 2) }); + } + + return ( +
+ + +
+ ); +} +``` + +# props.**chartRef** + +```js +import React, { useRef, useEffect } from "react"; +import { Chart } from "@strawberry-vis/g2-rect"; + +export function Card() { + const chartRef = useRef(); + + useEffect(() => { + const chart = chartRef.current; + chart.on("afterrender", () => {}); + }, []); + + return ; +} +``` diff --git a/package.json b/package.json new file mode 100644 index 0000000..7b24ab7 --- /dev/null +++ b/package.json @@ -0,0 +1,26 @@ +{ + "name": "@strawberry-vis/g2-rect", + "version": "0.0.1", + "description": "The lightweight React Component for @antv/g2.", + "main": "index.js", + "scripts": { + "dev": "vite dev" + }, + "author": { + "name": "pearmini", + "url": "https://github.com/pearmini" + }, + "license": "ISC", + "dependencies": { + "@antv/g2": "^5.0.22", + "react": "^18.2.0" + }, + "devDependencies": { + "@antv/g-svg": "^1.10.14", + "@types/react": "^18.2.21", + "@types/react-dom": "^18.2.7", + "jsdom": "^22.1.0", + "react-dom": "^18.2.0", + "vite": "^4.4.9" + } +} diff --git a/src/Chart.tsx b/src/Chart.tsx new file mode 100644 index 0000000..7c1ba3c --- /dev/null +++ b/src/Chart.tsx @@ -0,0 +1,53 @@ +import React, { + useRef, + useState, + useEffect, + useImperativeHandle, + forwardRef, + CSSProperties, +} from "react"; +import { Chart as G2Chart, G2Spec, ChartOptions } from "@antv/g2"; + +export type ChartRef = G2Chart | undefined; + +export type ChartProps = { + spec: G2Spec; + options?: ChartOptions; + style?: CSSProperties; + onInit?: () => void; +}; + +export const Chart = forwardRef((props, ref) => { + const { spec, style, onInit, options } = props; + const containerRef = useRef(null); + const chartRef = useRef(); + const [init, setInit] = useState(false); + + useEffect(() => { + if (containerRef.current) { + chartRef.current = new G2Chart({ + ...options, + container: containerRef.current, + }); + setInit(true); + } + return () => { + if (chartRef.current) chartRef.current.destroy(); + }; + }, []); + + useEffect(() => { + if (init) onInit?.(); + }, [init]); + + useEffect(() => { + if (chartRef.current && spec) { + chartRef.current.options(spec); + chartRef.current.render(); + } + }, [spec]); + + useImperativeHandle(ref, () => chartRef.current, [init]); + + return
; +}); diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..0b4742b --- /dev/null +++ b/src/index.ts @@ -0,0 +1 @@ +export { Chart, type ChartProps, type ChartRef } from "./Chart"; \ No newline at end of file diff --git a/test/demos/chart-options.tsx b/test/demos/chart-options.tsx new file mode 100644 index 0000000..bdfd178 --- /dev/null +++ b/test/demos/chart-options.tsx @@ -0,0 +1,25 @@ +import React from "react"; +import { Renderer } from "@antv/g-svg"; +import { Chart } from "../../src"; +import { render } from "./render"; + +function Demo() { + return ( + + ); +} + +export const chartOptions = render(Demo); diff --git a/test/demos/chart-ref.tsx b/test/demos/chart-ref.tsx new file mode 100644 index 0000000..34c3a2f --- /dev/null +++ b/test/demos/chart-ref.tsx @@ -0,0 +1,42 @@ +import React, { useRef } from "react"; +import { Chart, ChartRef } from "../../src"; +import { render } from "./render"; + +function Demo() { + const chartRef = useRef(null); + + function onInit() { + const chart = chartRef.current; + if (chart) { + chart.on("afterrender", () => { + chart.emit("element:highlight", { + data: { + data: { genre: "Sports" }, + }, + }); + }); + } + } + + return ( + + ); +} + +export const chartRef = render(Demo); diff --git a/test/demos/chart-spec.tsx b/test/demos/chart-spec.tsx new file mode 100644 index 0000000..9023026 --- /dev/null +++ b/test/demos/chart-spec.tsx @@ -0,0 +1,23 @@ +import React from "react"; +import { Chart } from "../../src"; +import { render } from "./render"; + +function Demo() { + return ( + + ); +} + +export const chartSpec = render(Demo); diff --git a/test/demos/chart-style.tsx b/test/demos/chart-style.tsx new file mode 100644 index 0000000..1713ebc --- /dev/null +++ b/test/demos/chart-style.tsx @@ -0,0 +1,31 @@ +import React from "react"; +import { Chart } from "../../src"; +import { render } from "./render"; + +function Demo() { + return ( + + ); +} + +export const chartStyle = render(Demo); diff --git a/test/demos/chart-update.tsx b/test/demos/chart-update.tsx new file mode 100644 index 0000000..10956f1 --- /dev/null +++ b/test/demos/chart-update.tsx @@ -0,0 +1,35 @@ +import React, { useState } from "react"; +import { Chart } from "../../src"; +import { render } from "./render"; + +function Demo() { + const data = [ + { genre: "Sports", sold: 275 }, + { genre: "Strategy", sold: 115 }, + { genre: "Action", sold: 120 }, + { genre: "Shooter", sold: 350 }, + { genre: "Other", sold: 150 }, + ]; + + const [options, setOptions] = useState({ + type: "interval", + data, + encode: { x: "genre", y: "sold", color: "genre" }, + }); + + function onClick() { + setOptions({ + ...options, + data: data.sort(() => Math.random() - 0.5), + }); + } + + return ( + <> + + + + ); +} + +export const chartUpdate = render(Demo); diff --git a/test/demos/index.ts b/test/demos/index.ts new file mode 100644 index 0000000..c9f4c1d --- /dev/null +++ b/test/demos/index.ts @@ -0,0 +1,5 @@ +export { chartSpec } from "./chart-spec"; +export { chartUpdate } from "./chart-update"; +export { chartStyle } from "./chart-style"; +export { chartRef } from "./chart-ref"; +export { chartOptions } from "./chart-options"; diff --git a/test/demos/render.tsx b/test/demos/render.tsx new file mode 100644 index 0000000..12261c3 --- /dev/null +++ b/test/demos/render.tsx @@ -0,0 +1,11 @@ +import React from "react"; +import { createRoot } from "react-dom/client"; + +export function render(Demo) { + return () => { + const container = document.createElement("div"); + const root = createRoot(container); + root.render(); + return container; + }; +} diff --git a/test/index.html b/test/index.html new file mode 100644 index 0000000..dcaa0e0 --- /dev/null +++ b/test/index.html @@ -0,0 +1,3 @@ + + + diff --git a/test/main.ts b/test/main.ts new file mode 100644 index 0000000..7fd9279 --- /dev/null +++ b/test/main.ts @@ -0,0 +1,36 @@ +import * as demos from "./demos"; + +const select = document.createElement("select"); +select.style.margin = "1em"; +select.onchange = onChange; +select.style.display = "block"; +document.body.append(select); + +const options = Object.keys(demos).map((d) => { + const option = document.createElement("option"); + option.textContent = d; + option.value = d; + return option; +}); +options.forEach((d) => select.append(d)); + +const initialValue = new URL(location as any).searchParams.get( + "name" +) as string; +if (demos[initialValue]) select.value = initialValue; + +let node; +render(); + +function render() { + if (node) node.remove(); + const demo = demos[select.value]; + node = demo(); + document.body.append(node); +} + +function onChange() { + const { value } = select; + history.pushState({ value }, "", `?name=${value}`); + render(); +} diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..609bfe3 --- /dev/null +++ b/vite.config.js @@ -0,0 +1,7 @@ +import { defineConfig } from "vite"; +import path from "path"; + +export default defineConfig({ + root: path.resolve("./test"), + server: { port: 8080, open: "/" }, +});