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: "/" },
+});