Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions example/.dumi/global.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import * as G2 from '@antv/g2';
import React from 'react';
import * as ReactDOM from 'react-dom/client';
import { createRoot } from 'react-dom/client';

/**
* 增加自己的全局变量,用于 DEMO 中的依赖,以 G2 为例
*/
if (typeof window !== 'undefined' && window) {
(window as any).g2 = extendG2(G2);
(window as any)['@antv/g2'] = G2;
(window as any).React = React;
(window as any).ReactDOM = ReactDOM;
(window as any).globalAdd = (x, y) => x + y;
(window as any).globalCard = globalCard;
(window as any).d3Regression = require('d3-regression');
Expand Down
87 changes: 81 additions & 6 deletions example/docs/manual/codeblock.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,7 @@ title: Codeblock
span.textContent = 1;
span.style.fontSize = '30px';

const timer = setInterval(
() => (span.textContent = +span.textContent + 1),
1000,
);
const timer = setInterval(() => (span.textContent = +span.textContent + 1), 1000);

// 清空监听器
span.clear = () => {
Expand Down Expand Up @@ -176,7 +173,7 @@ globalCard('world');

## G2 inject

```js | ob { inject: true }
```js | ob { inject: true }
import { Chart } from '@antv/g2';

const chart = new Chart({
Expand All @@ -201,7 +198,7 @@ chart.render();

## G2 inject & unpin

```js | ob { pin: false, inject: true }
```js | ob { pin: false, inject: true }
import { Chart } from '@antv/g2';

const chart = new Chart({
Expand All @@ -223,3 +220,81 @@ chart

chart.render();
```

## React Component

```js | ob
import React from 'react';

export default () => {
return (
<div
style={{
padding: '20px',
backgroundColor: '#f0f8ff',
border: '2px solid #4169e1',
borderRadius: '8px',
textAlign: 'center',
}}
>
<h2 style={{ color: '#4169e1', margin: '0 0 10px 0' }}>🎉 React Component Works!</h2>
<p style={{ margin: 0, fontSize: '16px' }}>This is a simple React component rendered successfully.</p>
</div>
);
};
```

## G2 React

```js | ob
import React, { useState, useEffect, useRef } from 'react';
import { Chart } from '@antv/g2';

// 渲染条形图
function renderBarChart(container) {
const chart = new Chart({
container,
});

// 准备数据
const data = [
{ genre: 'Sports', sold: 275 },
{ genre: 'Strategy', sold: 115 },
{ genre: 'Action', sold: 120 },
{ genre: 'Shooter', sold: 350 },
{ genre: 'Other', sold: 150 },
];

// 声明可视化
chart
.interval() // 创建一个 Interval 标记
.data(data) // 绑定数据
.encode('x', 'genre') // 编码 x 通道
.encode('y', 'sold') // 编码 y 通道
.encode('key', 'genre') // 指定 key
.animate('update', { duration: 300 }); // 指定更新动画的时间

// 渲染可视化
chart.render();

return chart;
}

export default () => {
const container = useRef(null);
const chart = useRef(null);

useEffect(() => {
if (!chart.current) {
chart.current = renderBarChart(container.current);
}

return () => {
chart.current.destroy();
chart.current = null;
};
}, []);

return <div ref={container}></div>;
};
```
76 changes: 76 additions & 0 deletions src/slots/LiveExample/ChartPreview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { debounce, uniqueId } from 'lodash-es';
import React, { FC, useCallback, useEffect, useRef } from 'react';
import { compile } from '../CodeEditor/utils';

interface ChartPreviewProps {
code: string;
refresh: number;
}

/**
* 渲染错误信息
*/
function renderError(ref: React.RefObject<HTMLDivElement>, msg: string) {
if (ref.current) {
ref.current.innerHTML = `<div style='color:red;'>JS 执行错误: ${msg}</div>`;
}
}

/**
* 执行 JS 脚本并渲染
*/
function executeChartScript(
code: string,
ref: React.RefObject<HTMLDivElement>,
containerId: React.MutableRefObject<string>,
) {
if (!ref.current) return;
ref.current.innerHTML = `<div id="${containerId.current}"></div>`;

const prevDefine = (window as any).define;
try {
(window as any).define = undefined; // 临时禁用 define

const compiled = compile(code, '', true);

const script = document.createElement('script');
script.textContent = compiled.replace(/'container'|"container"/g, `'${containerId.current}'`);
document.body.appendChild(script);

setTimeout(() => {
if (script.parentNode) script.parentNode.removeChild(script);
}, 100);
} catch (e) {
renderError(ref, String(e));
} finally {
(window as any).define = prevDefine;
}
}

const ChartPreview: FC<ChartPreviewProps> = ({ code, refresh }) => {
const ref = useRef<HTMLDivElement>(null);
const containerId = useRef(`live-chart-${uniqueId()}`);

const debouncedRender = useCallback(
debounce((code: string) => {
executeChartScript(code, ref, containerId);
}, 200),
[],
);

useEffect(() => {
debouncedRender(code);
}, [code, refresh, debouncedRender]);

useEffect(() => {
return () => {
if (ref.current) {
ref.current.innerHTML = '';
}
};
}, []);

return <div ref={ref} />;
};

export default ChartPreview;
16 changes: 16 additions & 0 deletions src/slots/LiveExample/CodeEditor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import SourceCodeEditor from 'dumi/theme-default/slots/SourceCodeEditor';
import React, { FC } from 'react';

interface CodeEditorProps {
codeRef: React.RefObject<HTMLDivElement>;
value: string;
onChange: (v: string) => void;
lang?: string;
isVisible: boolean;
}

export const CodeEditor: FC<CodeEditorProps> = ({ value, onChange, lang, codeRef, isVisible }) => (
<div ref={codeRef} style={{ display: isVisible ? 'block' : 'none' }}>
<SourceCodeEditor onChange={onChange} initialValue={value} lang={lang} />
</div>
);
101 changes: 101 additions & 0 deletions src/slots/LiveExample/IIFEPreview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/**
* IIFE Preview
*/
import { debounce } from 'lodash-es';
import React, { FC, useCallback, useEffect, useRef } from 'react';
import { createRoot, Root } from 'react-dom/client';
import { safeEval } from '../ManualContent/utils';

interface IIFEPreviewProps {
code: string;
refresh: number;
}

/**
* 渲染错误信息
*/
function renderError(ref: React.RefObject<HTMLDivElement>, msg: string) {
if (ref.current) {
ref.current.innerHTML = `<div style='color:red;'>IIFE 执行错误: ${msg}</div>`;
}
}

/**
* 渲染 IIFE 结果
*/
function renderIIFEResultSync(
ref: React.RefObject<HTMLDivElement>,
rootRef: React.MutableRefObject<Root | null>,
val: any,
) {
if (!ref.current) return;
if (val instanceof HTMLElement) {
ref.current.appendChild(val);
} else if (typeof val === 'string') {
ref.current.innerHTML = val;
} else if (React.isValidElement(val)) {
if (!rootRef.current) rootRef.current = createRoot(ref.current);
rootRef.current.render(val);
} else if (val !== undefined && val !== null) {
ref.current.innerHTML = `<div>${JSON.stringify(val)}</div>`;
} else {
ref.current.innerHTML = `<div style='color:red;'>IIFE 未返回 DOM 节点</div>`;
}
}

/**
* 执行 IIFE 代码
*/
function executeIIFE(code: string, ref: React.RefObject<HTMLDivElement>, rootRef: React.MutableRefObject<Root | null>) {
if (!ref.current) return;

ref.current.innerHTML = '';

try {
const result = safeEval(code);

if (result && typeof result.then === 'function') {
result
.then((val: any) => renderIIFEResultSync(ref, rootRef, val))
.catch((e: any) => {
renderError(ref, `Promise 执行错误: ${e}`);
});
} else {
renderIIFEResultSync(ref, rootRef, result);
}
} catch (e) {
renderError(ref, String(e));
}
}

const IIFEPreview: FC<IIFEPreviewProps> = ({ code, refresh }) => {
const ref = useRef<HTMLDivElement>(null);
const rootRef = useRef<Root | null>(null);

const debouncedRender = useCallback(
debounce((code: string) => {
executeIIFE(code, ref, rootRef);
}, 200),
[],
);

useEffect(() => {
debouncedRender(code);
}, [code, refresh, debouncedRender]);

useEffect(() => {
return () => {
if (rootRef.current) {
rootRef.current.unmount();
rootRef.current = null;
}
if (ref.current) {
ref.current.innerHTML = '';
}
};
}, []);

return <div ref={ref} />;
};

export default IIFEPreview;
Loading