Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
118 changes: 118 additions & 0 deletions packages/g6-extension-solid/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
## SolidJS extension for G6

<img width="500" src="https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*rWSiT6dnwfcAAAAAAAAAAAAADmJ7AQ/original" />

This extension allows you to define G6 node by SolidJS component and JSX syntax with fine-grained reactivity.

## Features

- **Familiar JSX syntax**: Write components using JSX just like React, but with SolidJS's reactive primitives
- **Fine-grained reactivity**: Unlike React's virtual DOM, SolidJS uses signals for surgical DOM updates
- **No re-renders**: Component props are reactive signals that update only the parts of the DOM that depend on them

## Usage

1. Install

```bash
npm install @antv/g6-extension-solid
```

2. Import and Register

```js
import { ExtensionCategory, register } from '@antv/g6';
import { SolidNode } from '@antv/g6-extension-solid';

register(ExtensionCategory.NODE, 'solid-node', SolidNode);
```

3. Define Node

SolidJS Node:

```jsx
const SolidNode = (props) => {
return <div>node: {props.id}</div>;
};
```

G Node with SolidJS:

```jsx
import { Group, Rect, Text } from '@antv/g6-extension-solid';

const GNode = (props) => {
return <Group>
<Rect width={100} height={100}></Rect>
<Text text={props.label || "node"} />
</Group>
};
```

Reactive Node:

```jsx
import { createSignal } from 'solid-js';

const ReactiveNode = (props) => {
const [count, setCount] = createSignal(0);

return (
<div onClick={() => setCount(count() + 1)}>
Node {props.id}: {count()} clicks
</div>
);
};
```

4. Use

Use SolidNode:

```jsx
const graph = new Graph({
// ... other options
node: {
type: 'solid-node',
style: {
component: SolidNode,
},
},
});
```

Use GNode:

```jsx
const graph = new Graph({
// ... other options
node: {
type: 'solid-node',
style: {
component: GNode,
},
},
});
```

## Key Differences from React Extension

1. **Reactivity Model**: SolidJS uses signals instead of virtual DOM, providing automatic fine-grained updates
2. **Performance**: No re-renders - only the specific DOM nodes that depend on changed signals are updated
3. **Props Updates**: Node attributes are automatically reactive through signals, no manual re-rendering needed

## Q&A

1. Difference between SolidNode and GNode

SolidNode is a Solid JSX component that renders to regular DOM, while GNode supports JSX syntax but can only use G tag nodes for SVG/Canvas rendering.

2. How does reactivity work?

The extension automatically creates signals for node attributes. When attributes change in G6, the signals update, and SolidJS reactively updates only the parts of the DOM that depend on those signals.

## Resources

- [SolidJS Documentation](https://www.solidjs.com/)
- [G6 Custom Nodes](https://g6.antv.antgroup.com/examples/element/custom-node/)
5 changes: 5 additions & 0 deletions packages/g6-extension-solid/__tests__/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"rules": {
"no-console": "off"
}
}
114 changes: 114 additions & 0 deletions packages/g6-extension-solid/__tests__/dataset/euro-cup.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
{
"nodes": [
{
"id": "50251337",
"x": 50,
"y": 68,
"isTeamA": "1",
"player_id": "50251337",
"player_shirtnumber": "19",
"player_enName": "Justin Kluivert",
"player_name": "尤斯廷-克鲁伊维特"
},
{
"id": "50436685",
"x": 25,
"y": 68,
"isTeamA": "1",
"player_id": "50436685",
"player_shirtnumber": "24",
"player_enName": "Antoine Semenyo",
"player_name": "塞门约"
},
{
"id": "50204813",
"x": 50,
"y": 89,
"isTeamA": "1",
"player_id": "50204813",
"player_shirtnumber": "9",
"player_enName": "Dominic Solanke",
"player_name": "索兰克"
},
{
"id": "50250175",
"x": 75,
"y": 68,
"isTeamA": "1",
"player_id": "50250175",
"player_shirtnumber": "16",
"player_enName": "Marcus Tavernier",
"player_name": "塔韦尼耶"
},
{
"id": "50213675",
"x": 65,
"y": 48,
"isTeamA": "1",
"player_id": "50213675",
"player_shirtnumber": "4",
"player_enName": "Lewis Cook",
"player_name": "刘易斯-库克"
},
{
"id": "50186648",
"x": 35,
"y": 48,
"isTeamA": "1",
"player_id": "50186648",
"player_shirtnumber": "10",
"player_enName": "Ryan Christie",
"player_name": "克里斯蒂"
},
{
"id": "50279448",
"x": 38,
"y": 28,
"isTeamA": "1",
"player_id": "50279448",
"player_shirtnumber": "6",
"player_enName": "Chris Mepham",
"player_name": "迈帕姆"
},
{
"id": "50061646",
"x": 15,
"y": 28,
"isTeamA": "1",
"player_id": "50061646",
"player_shirtnumber": "15",
"player_enName": "Adam Smith",
"player_name": "亚当-史密斯"
},
{
"id": "50472140",
"x": 62,
"y": 28,
"isTeamA": "1",
"player_id": "50472140",
"player_shirtnumber": "27",
"player_enName": "Ilya Zabarnyi",
"player_name": "扎巴尔尼"
},
{
"id": "50544346",
"x": 85,
"y": 28,
"isTeamA": "1",
"player_id": "50544346",
"player_shirtnumber": "3",
"player_enName": "Milos Kerkez",
"player_name": "科尔克兹"
},
{
"id": "50062598",
"x": 50,
"y": 7,
"isTeamA": "1",
"player_id": "50062598",
"player_shirtnumber": "1",
"player_enName": "Neto",
"player_name": "内托"
}
]
}
118 changes: 118 additions & 0 deletions packages/g6-extension-solid/__tests__/demos/euro-cup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/* @jsx preserve */
/* @jsxImportSource solid-js */
import { ExtensionCategory, register } from '@antv/g6';
import { SolidNode } from '@antv/g6-extension-solid';
import styled from 'styled-components';
import data from '../dataset/euro-cup.json';
import { Graph } from '../graph';

const Player = styled.div`
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
`;

const Shirt = styled.div`
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
position: relative;

img {
width: 40px;
position: absolute;
left: 0;
top: 0;
}
`;

const Number = styled.div`
color: #fff;
font-family: 'DingTalk-JinBuTi';
font-size: 10px;
top: 20px;
left: 15px;
z-index: 1;
margin-top: 16px;
margin-left: -2px;
`;

const Label = styled.div`
max-width: 120px;
padding: 0 8px;
color: #fff;
font-size: 10px;
background-image: url('https://mdn.alipayobjects.com/huamei_92awrc/afts/img/A*s2csQ48M0AkAAAAAAAAAAAAADsvfAQ/original');
background-repeat: no-repeat;
background-size: cover;
display: flex;
justify-content: center;
overflow: hidden;
text-overflow: ellipsis;
`;

const PlayerNode = ({ playerInfo }: any) => {
const { isTeamA, player_shirtnumber, player_name } = playerInfo;
return (
<Player>
<Shirt>
<img
src={
isTeamA
? 'https://mdn.alipayobjects.com/huamei_92awrc/afts/img/A*0oAaS42vqWcAAAAAAAAAAAAADsvfAQ/original'
: 'https://mdn.alipayobjects.com/huamei_92awrc/afts/img/A*BYH5SauBNecAAAAAAAAAAAAADsvfAQ/original'
}
/>
<Number>{player_shirtnumber}</Number>
</Shirt>
<Label>{player_name}</Label>
</Player>
);
};

register(ExtensionCategory.NODE, 'solid-node', SolidNode);

export const EuroCup = () => {
return (
<div>
<Graph
options={{
data,
animation: false,
x: 10,
y: 50,
width: 480,
height: 720,
node: {
type: 'solid-node',
style: {
size: [100, 60],
ports: [{ placement: 'center' }],
x: (d: any) => d.x * 3.5,
y: (d: any) => d.y * 3.5,
fill: 'transparent',
component: (data: any) => <PlayerNode playerInfo={data} />,
},
},
plugins: [
{
type: 'background',
width: '480px',
height: '720px',
backgroundImage:
'url(https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*EmPXQLrX2xIAAAAAAAAAAAAADmJ7AQ/original)',
backgroundRepeat: 'no-repeat',
backgroundSize: 'contain',
opacity: 1,
},
],
}}
/>
</div>
);
};
26 changes: 26 additions & 0 deletions packages/g6-extension-solid/__tests__/demos/graph.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/* @jsx preserve */
/* @jsxImportSource solid-js */
import { Graph } from '../graph';

export const G6Graph = () => {
return (
<Graph
options={{
data: {
nodes: [
{ id: 'node-1', style: { x: 100, y: 100, labelText: 'Hello' } },
{ id: 'node-2', style: { x: 300, y: 100, labelText: 'World' } },
],
edges: [{ source: 'node-1', target: 'node-2' }],
},
behaviors: ['drag-element'],
}}
onRender={() => {
console.log('render');
}}
onDestroy={() => {
console.log('destroy');
}}
/>
);
};
4 changes: 4 additions & 0 deletions packages/g6-extension-solid/__tests__/demos/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './euro-cup';
export * from './graph';
export * from './performance-diagnosis';
export * from './react-node';
Loading