vue-apexsankey 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +76 -0
- package/README.md +406 -0
- package/dist/index.d.ts +269 -0
- package/dist/vue-apexsankey.css +1 -0
- package/dist/vue-apexsankey.js +114 -0
- package/dist/vue-apexsankey.umd.cjs +1 -0
- package/package.json +65 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
## 📄 License Options for vue-apexsankey
|
|
2
|
+
|
|
3
|
+
vue-apexsankey is offered under a **dual-license model** to support individuals, startups, and commercial products of all sizes.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
### 🔓 Community License (Free)
|
|
8
|
+
|
|
9
|
+
For individuals, non-profits, educators, and small businesses with **less than $2 million USD in annual revenue**.
|
|
10
|
+
|
|
11
|
+
✅ What's allowed:
|
|
12
|
+
|
|
13
|
+
- Personal, educational, or non-profit use
|
|
14
|
+
- Commercial use by small orgs (< $2M annual revenue)
|
|
15
|
+
- Modifications and redistribution (with attribution)
|
|
16
|
+
|
|
17
|
+
🚫 Not allowed:
|
|
18
|
+
|
|
19
|
+
- Use by companies or entities over $2M/year revenue
|
|
20
|
+
- Use in competing charting products
|
|
21
|
+
- Sublicensing under different terms
|
|
22
|
+
|
|
23
|
+
➡ By using vue-apexsankey under this license, you confirm that **you qualify as a Small Organization**.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
### 💼 Commercial License (Paid)
|
|
28
|
+
|
|
29
|
+
Required if **you or your affiliated organization earns $2 million USD or more per year**.
|
|
30
|
+
|
|
31
|
+
✅ What's included:
|
|
32
|
+
|
|
33
|
+
- Use in internal tools and commercial applications
|
|
34
|
+
- Modifications and app-level distribution
|
|
35
|
+
- 12-month subscription with updates & support
|
|
36
|
+
|
|
37
|
+
🚫 Not allowed:
|
|
38
|
+
|
|
39
|
+
- Redistribution in toolkits, SDKs, or platforms
|
|
40
|
+
- Use by unlicensed developers
|
|
41
|
+
- Competing charting products
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
### 🔄 OEM / Redistribution License (Paid)
|
|
46
|
+
|
|
47
|
+
Required if you are **embedding vue-apexsankey into a product or platform used by other people**, such as:
|
|
48
|
+
|
|
49
|
+
- No-code dashboards
|
|
50
|
+
- Developer platforms
|
|
51
|
+
- Embedded BI tools
|
|
52
|
+
- White-labeled apps or SDKs
|
|
53
|
+
|
|
54
|
+
✅ What's included:
|
|
55
|
+
|
|
56
|
+
- Redistribution rights for 1 application or product
|
|
57
|
+
- 12-month subscription with updates & support
|
|
58
|
+
|
|
59
|
+
✅ OEM **not required** if your app simply renders static charts and users **cannot** configure or interact with them.
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
### ⚠️ License Acceptance
|
|
64
|
+
|
|
65
|
+
By installing vue-apexsankey (e.g., via `npm install vue-apexsankey`), you are agreeing to the applicable license based on your usage:
|
|
66
|
+
|
|
67
|
+
- Community License (if under $2M revenue)
|
|
68
|
+
- Commercial License (if over $2M revenue)
|
|
69
|
+
- OEM License (if redistributing to third-party users)
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
### 🛠 Need a License or Have Questions?
|
|
74
|
+
|
|
75
|
+
📧 Contact us at [sales@apexcharts.com](mailto:sales@apexcharts.com)
|
|
76
|
+
📚 Read full license agreements here: [https://apexcharts.com/license](https://apexcharts.com/license)
|
package/README.md
ADDED
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
# vue-apexsankey
|
|
2
|
+
|
|
3
|
+
Vue 3 wrapper for [ApexSankey](https://github.com/apexcharts/apexsankey) - A JavaScript library to create Sankey diagrams.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install vue-apexsankey apexsankey @svgdotjs/svg.js
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or with yarn:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
yarn add vue-apexsankey apexsankey @svgdotjs/svg.js
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Or with pnpm:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pnpm add vue-apexsankey apexsankey @svgdotjs/svg.js
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Loading ApexSankey
|
|
24
|
+
|
|
25
|
+
**Important:** You must load ApexSankey before using the Vue component. Choose one of the following methods:
|
|
26
|
+
|
|
27
|
+
### Option 1: ES Module Import (Recommended)
|
|
28
|
+
|
|
29
|
+
Import ApexSankey at your app's entry point to register it globally:
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
// main.ts
|
|
33
|
+
import 'apexsankey'
|
|
34
|
+
import { createApp } from 'vue'
|
|
35
|
+
import App from './App.vue'
|
|
36
|
+
|
|
37
|
+
createApp(App).mount('#app')
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Option 2: CDN Script Tags
|
|
41
|
+
|
|
42
|
+
Add the scripts to your `index.html` before your app bundle:
|
|
43
|
+
|
|
44
|
+
```html
|
|
45
|
+
<!DOCTYPE html>
|
|
46
|
+
<html>
|
|
47
|
+
<head>
|
|
48
|
+
<script src="https://cdn.jsdelivr.net/npm/@svgdotjs/svg.js"></script>
|
|
49
|
+
<script src="https://cdn.jsdelivr.net/npm/apexsankey/apexsankey.min.js"></script>
|
|
50
|
+
</head>
|
|
51
|
+
<body>
|
|
52
|
+
<div id="app"></div>
|
|
53
|
+
<script type="module" src="/src/main.ts"></script>
|
|
54
|
+
</body>
|
|
55
|
+
</html>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Quick Start
|
|
59
|
+
|
|
60
|
+
```vue
|
|
61
|
+
<script setup lang="ts">
|
|
62
|
+
import ApexSankey from 'vue-apexsankey'
|
|
63
|
+
import type { GraphData, SankeyOptions } from 'vue-apexsankey'
|
|
64
|
+
|
|
65
|
+
const data: GraphData = {
|
|
66
|
+
nodes: [
|
|
67
|
+
{ id: 'oil', title: 'Oil' },
|
|
68
|
+
{ id: 'gas', title: 'Natural Gas' },
|
|
69
|
+
{ id: 'coal', title: 'Coal' },
|
|
70
|
+
{ id: 'fossil', title: 'Fossil Fuels' },
|
|
71
|
+
{ id: 'energy', title: 'Energy' }
|
|
72
|
+
],
|
|
73
|
+
edges: [
|
|
74
|
+
{ source: 'oil', target: 'fossil', value: 15 },
|
|
75
|
+
{ source: 'gas', target: 'fossil', value: 20 },
|
|
76
|
+
{ source: 'coal', target: 'fossil', value: 25 },
|
|
77
|
+
{ source: 'fossil', target: 'energy', value: 60 }
|
|
78
|
+
]
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const options: Partial<SankeyOptions> = {
|
|
82
|
+
width: 800,
|
|
83
|
+
height: 600,
|
|
84
|
+
nodeWidth: 20
|
|
85
|
+
}
|
|
86
|
+
</script>
|
|
87
|
+
|
|
88
|
+
<template>
|
|
89
|
+
<ApexSankey :data="data" :options="options" />
|
|
90
|
+
</template>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## License Setup
|
|
94
|
+
|
|
95
|
+
If you have a commercial license, set it once at app initialization before rendering any charts:
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
// main.ts
|
|
99
|
+
import 'apexsankey'
|
|
100
|
+
import { createApp } from 'vue'
|
|
101
|
+
import { setApexSankeyLicense } from 'vue-apexsankey'
|
|
102
|
+
import App from './App.vue'
|
|
103
|
+
|
|
104
|
+
// set license before mounting app
|
|
105
|
+
setApexSankeyLicense('your-license-key-here')
|
|
106
|
+
|
|
107
|
+
createApp(App).mount('#app')
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Using Template Refs
|
|
111
|
+
|
|
112
|
+
Access the underlying `SankeyGraph` instance via template refs:
|
|
113
|
+
|
|
114
|
+
```vue
|
|
115
|
+
<script setup lang="ts">
|
|
116
|
+
import { ref, onMounted } from 'vue'
|
|
117
|
+
import ApexSankey from 'vue-apexsankey'
|
|
118
|
+
import type { GraphData } from 'vue-apexsankey'
|
|
119
|
+
|
|
120
|
+
const chartRef = ref<InstanceType<typeof ApexSankey> | null>(null)
|
|
121
|
+
|
|
122
|
+
const data: GraphData = {
|
|
123
|
+
nodes: [
|
|
124
|
+
{ id: 'a', title: 'A' },
|
|
125
|
+
{ id: 'b', title: 'B' }
|
|
126
|
+
],
|
|
127
|
+
edges: [
|
|
128
|
+
{ source: 'a', target: 'b', value: 10 }
|
|
129
|
+
]
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
onMounted(() => {
|
|
133
|
+
if (chartRef.value?.graph) {
|
|
134
|
+
console.log('Graph instance:', chartRef.value.graph)
|
|
135
|
+
console.log('Max rank:', chartRef.value.graph.maxRank)
|
|
136
|
+
}
|
|
137
|
+
})
|
|
138
|
+
</script>
|
|
139
|
+
|
|
140
|
+
<template>
|
|
141
|
+
<ApexSankey ref="chartRef" :data="data" />
|
|
142
|
+
</template>
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Reactivity
|
|
146
|
+
|
|
147
|
+
The component automatically updates when props change:
|
|
148
|
+
|
|
149
|
+
```vue
|
|
150
|
+
<script setup lang="ts">
|
|
151
|
+
import { ref } from 'vue'
|
|
152
|
+
import ApexSankey from 'vue-apexsankey'
|
|
153
|
+
import type { GraphData, SankeyOptions } from 'vue-apexsankey'
|
|
154
|
+
|
|
155
|
+
const data = ref<GraphData>({
|
|
156
|
+
nodes: [
|
|
157
|
+
{ id: 'a', title: 'A' },
|
|
158
|
+
{ id: 'b', title: 'B' }
|
|
159
|
+
],
|
|
160
|
+
edges: [
|
|
161
|
+
{ source: 'a', target: 'b', value: 10 }
|
|
162
|
+
]
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
const options = ref<Partial<SankeyOptions>>({
|
|
166
|
+
nodeWidth: 20
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
function updateData() {
|
|
170
|
+
// chart will automatically re-render
|
|
171
|
+
data.value = {
|
|
172
|
+
...data.value,
|
|
173
|
+
edges: [
|
|
174
|
+
{ source: 'a', target: 'b', value: 50 }
|
|
175
|
+
]
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function updateOptions() {
|
|
180
|
+
// chart will recreate with new options
|
|
181
|
+
options.value = {
|
|
182
|
+
...options.value,
|
|
183
|
+
nodeWidth: 30
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
</script>
|
|
187
|
+
|
|
188
|
+
<template>
|
|
189
|
+
<div>
|
|
190
|
+
<ApexSankey :data="data" :options="options" />
|
|
191
|
+
<button @click="updateData">Update Data</button>
|
|
192
|
+
<button @click="updateOptions">Update Options</button>
|
|
193
|
+
</div>
|
|
194
|
+
</template>
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Props
|
|
198
|
+
|
|
199
|
+
| Prop | Type | Required | Description |
|
|
200
|
+
| --------- | ------------------------ | -------- | ------------------------------------- |
|
|
201
|
+
| `data` | `GraphData` | Yes | Sankey diagram data (nodes and edges) |
|
|
202
|
+
| `options` | `Partial<SankeyOptions>` | No | Configuration options for the diagram |
|
|
203
|
+
|
|
204
|
+
## Data Format
|
|
205
|
+
|
|
206
|
+
### Nodes
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
interface Node {
|
|
210
|
+
id: string // unique identifier
|
|
211
|
+
title: string // display label
|
|
212
|
+
color?: string // optional custom color
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Edges
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
interface Edge {
|
|
220
|
+
source: string // source node id
|
|
221
|
+
target: string // target node id
|
|
222
|
+
value: number // edge weight/size
|
|
223
|
+
type?: string // optional grouping type
|
|
224
|
+
color?: string // optional custom color
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Complete Data Structure
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
interface GraphData {
|
|
232
|
+
nodes: Node[]
|
|
233
|
+
edges: Edge[]
|
|
234
|
+
options?: {
|
|
235
|
+
order?: string[][][] // custom node ordering
|
|
236
|
+
alignLinkTypes?: boolean // align links by type
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## Options
|
|
242
|
+
|
|
243
|
+
| Option | Type | Default | Description |
|
|
244
|
+
| ------------------ | ---------------------- | ---------------------------- | -------------------------------------------- |
|
|
245
|
+
| `width` | `number \| string` | `800` | Width of graph container |
|
|
246
|
+
| `height` | `number \| string` | `800` | Height of graph container |
|
|
247
|
+
| `canvasStyle` | `string` | `""` | CSS styles for canvas root container |
|
|
248
|
+
| `spacing` | `number` | `100` | Spacing from top and left of graph container |
|
|
249
|
+
| `nodeWidth` | `number` | `20` | Width of graph nodes |
|
|
250
|
+
| `nodeBorderWidth` | `number` | `1` | Border width of nodes in pixels |
|
|
251
|
+
| `nodeBorderColor` | `string` | `""` | Border color of nodes |
|
|
252
|
+
| `onNodeClick` | `(node: Node) => void` | `undefined` | Callback function for node click |
|
|
253
|
+
| `edgeOpacity` | `number` | `0.4` | Opacity value for edges (0 to 1) |
|
|
254
|
+
| `edgeGradientFill` | `boolean` | `true` | Enable gradient fill based on node colors |
|
|
255
|
+
| `enableTooltip` | `boolean` | `false` | Enable tooltip on hover |
|
|
256
|
+
| `enableToolbar` | `boolean` | `false` | Enable/disable graph toolbar |
|
|
257
|
+
| `tooltipId` | `string` | `"sankey-tooltip-container"` | Tooltip HTML element id |
|
|
258
|
+
| `tooltipTemplate` | `(content) => string` | default template | HTML template for tooltip |
|
|
259
|
+
| `tooltipBorderColor` | `string` | `"#BCBCBC"` | Border color of tooltip |
|
|
260
|
+
| `tooltipBGColor` | `string` | `"#FFFFFF"` | Background color of tooltip |
|
|
261
|
+
| `fontSize` | `string` | `"14px"` | Font size of node labels |
|
|
262
|
+
| `fontFamily` | `string` | `""` | Font family of node labels |
|
|
263
|
+
| `fontWeight` | `string` | `"400"` | Font weight of node labels |
|
|
264
|
+
| `fontColor` | `string` | `"#000000"` | Font color of node labels |
|
|
265
|
+
|
|
266
|
+
## Custom Node Ordering
|
|
267
|
+
|
|
268
|
+
You can specify custom node ordering using the `order` option in data:
|
|
269
|
+
|
|
270
|
+
```vue
|
|
271
|
+
<script setup lang="ts">
|
|
272
|
+
import ApexSankey from 'vue-apexsankey'
|
|
273
|
+
import type { GraphData } from 'vue-apexsankey'
|
|
274
|
+
|
|
275
|
+
const data: GraphData = {
|
|
276
|
+
nodes: [
|
|
277
|
+
{ id: 'a', title: 'A' },
|
|
278
|
+
{ id: 'b', title: 'B' },
|
|
279
|
+
{ id: 'c', title: 'C' }
|
|
280
|
+
],
|
|
281
|
+
edges: [
|
|
282
|
+
{ source: 'a', target: 'c', value: 1 },
|
|
283
|
+
{ source: 'b', target: 'c', value: 2 }
|
|
284
|
+
],
|
|
285
|
+
options: {
|
|
286
|
+
order: [
|
|
287
|
+
[['a', 'b']], // first layer
|
|
288
|
+
[['c']] // second layer
|
|
289
|
+
]
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
</script>
|
|
293
|
+
|
|
294
|
+
<template>
|
|
295
|
+
<ApexSankey :data="data" />
|
|
296
|
+
</template>
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
## Custom Tooltip
|
|
300
|
+
|
|
301
|
+
```vue
|
|
302
|
+
<script setup lang="ts">
|
|
303
|
+
import ApexSankey from 'vue-apexsankey'
|
|
304
|
+
import type { GraphData, SankeyOptions } from 'vue-apexsankey'
|
|
305
|
+
|
|
306
|
+
const data: GraphData = {
|
|
307
|
+
nodes: [
|
|
308
|
+
{ id: 'a', title: 'A' },
|
|
309
|
+
{ id: 'b', title: 'B' }
|
|
310
|
+
],
|
|
311
|
+
edges: [
|
|
312
|
+
{ source: 'a', target: 'b', value: 10 }
|
|
313
|
+
]
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const options: Partial<SankeyOptions> = {
|
|
317
|
+
enableTooltip: true,
|
|
318
|
+
tooltipTemplate: ({ source, target, value }) => `
|
|
319
|
+
<div style="padding: 8px;">
|
|
320
|
+
<strong>${source.title}</strong> → <strong>${target.title}</strong>
|
|
321
|
+
<br />
|
|
322
|
+
Value: ${value}
|
|
323
|
+
</div>
|
|
324
|
+
`
|
|
325
|
+
}
|
|
326
|
+
</script>
|
|
327
|
+
|
|
328
|
+
<template>
|
|
329
|
+
<ApexSankey :data="data" :options="options" />
|
|
330
|
+
</template>
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
## Using the Composable
|
|
334
|
+
|
|
335
|
+
For more control, you can use the `useSankey` composable directly:
|
|
336
|
+
|
|
337
|
+
```vue
|
|
338
|
+
<script setup lang="ts">
|
|
339
|
+
import { ref } from 'vue'
|
|
340
|
+
import { useSankey } from 'vue-apexsankey'
|
|
341
|
+
import type { GraphData, SankeyOptions } from 'vue-apexsankey'
|
|
342
|
+
|
|
343
|
+
const containerRef = ref<HTMLElement | null>(null)
|
|
344
|
+
|
|
345
|
+
const data = ref<GraphData>({
|
|
346
|
+
nodes: [
|
|
347
|
+
{ id: 'a', title: 'A' },
|
|
348
|
+
{ id: 'b', title: 'B' }
|
|
349
|
+
],
|
|
350
|
+
edges: [
|
|
351
|
+
{ source: 'a', target: 'b', value: 10 }
|
|
352
|
+
]
|
|
353
|
+
})
|
|
354
|
+
|
|
355
|
+
const options = ref<Partial<SankeyOptions>>({
|
|
356
|
+
nodeWidth: 20
|
|
357
|
+
})
|
|
358
|
+
|
|
359
|
+
const { graph, isReady, render, destroy, recreate } = useSankey({
|
|
360
|
+
containerRef,
|
|
361
|
+
data,
|
|
362
|
+
options
|
|
363
|
+
})
|
|
364
|
+
</script>
|
|
365
|
+
|
|
366
|
+
<template>
|
|
367
|
+
<div ref="containerRef" style="width: 800px; height: 600px;" />
|
|
368
|
+
</template>
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
## TypeScript
|
|
372
|
+
|
|
373
|
+
All types are exported for use in your TypeScript projects:
|
|
374
|
+
|
|
375
|
+
```ts
|
|
376
|
+
import ApexSankey, {
|
|
377
|
+
GraphData,
|
|
378
|
+
Node,
|
|
379
|
+
Edge,
|
|
380
|
+
SankeyOptions,
|
|
381
|
+
CommonOptions,
|
|
382
|
+
NodeOptions,
|
|
383
|
+
EdgeOptions,
|
|
384
|
+
FontOptions,
|
|
385
|
+
TooltipOptions,
|
|
386
|
+
SankeyGraph
|
|
387
|
+
} from 'vue-apexsankey'
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
## SSR Support
|
|
391
|
+
|
|
392
|
+
This component is SSR-safe. It renders an empty container on the server and only initializes the chart client-side after the component is mounted.
|
|
393
|
+
|
|
394
|
+
## Browser Support
|
|
395
|
+
|
|
396
|
+
- Vue 3.3+
|
|
397
|
+
- Modern browsers (Chrome, Firefox, Safari, Edge)
|
|
398
|
+
|
|
399
|
+
## License
|
|
400
|
+
|
|
401
|
+
See [LICENSE](./LICENSE) file for details.
|
|
402
|
+
|
|
403
|
+
## Links
|
|
404
|
+
|
|
405
|
+
- [ApexSankey Documentation](https://apexcharts.com/docs/sankey)
|
|
406
|
+
- [ApexSankey GitHub](https://github.com/apexcharts/apexsankey)
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { ComponentOptionsMixin } from 'vue';
|
|
2
|
+
import { ComponentProvideOptions } from 'vue';
|
|
3
|
+
import { CSSProperties } from 'vue';
|
|
4
|
+
import { DefineComponent } from 'vue';
|
|
5
|
+
import { PublicProps } from 'vue';
|
|
6
|
+
import { Ref } from 'vue';
|
|
7
|
+
import { ShallowRef } from 'vue';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* props definition
|
|
11
|
+
*/
|
|
12
|
+
declare type __VLS_Props = {
|
|
13
|
+
/** sankey diagram data (nodes and edges) */
|
|
14
|
+
data: GraphData;
|
|
15
|
+
/** configuration options for the diagram */
|
|
16
|
+
options?: Partial<SankeyOptions>;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* props for the ApexSankey vue component
|
|
21
|
+
*/
|
|
22
|
+
export declare interface ApexSankeyProps {
|
|
23
|
+
/** sankey diagram data (nodes and edges) */
|
|
24
|
+
data: GraphData;
|
|
25
|
+
/** configuration options for the diagram */
|
|
26
|
+
options?: Partial<SankeyOptions>;
|
|
27
|
+
/** css class name for the container element */
|
|
28
|
+
class?: string;
|
|
29
|
+
/** inline styles for the container element */
|
|
30
|
+
style?: CSSProperties;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* exposed ref interface for accessing the sankey instance
|
|
35
|
+
*/
|
|
36
|
+
export declare interface ApexSankeyRef {
|
|
37
|
+
/** the underlying SankeyGraph instance */
|
|
38
|
+
graph: SankeyGraph | null;
|
|
39
|
+
/** the container html element */
|
|
40
|
+
element: HTMLElement | null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* common options for the sankey diagram
|
|
45
|
+
*/
|
|
46
|
+
export declare interface CommonOptions {
|
|
47
|
+
/** css styles for canvas root container */
|
|
48
|
+
readonly canvasStyle?: string;
|
|
49
|
+
/** enable/disable graph toolbar */
|
|
50
|
+
readonly enableToolbar?: boolean;
|
|
51
|
+
/** height of graph container */
|
|
52
|
+
readonly height?: number | string;
|
|
53
|
+
/** spacing from top and left of graph container */
|
|
54
|
+
readonly spacing?: number;
|
|
55
|
+
/** viewport height */
|
|
56
|
+
readonly viewPortHeight?: number;
|
|
57
|
+
/** viewport width */
|
|
58
|
+
readonly viewPortWidth?: number;
|
|
59
|
+
/** width of graph container */
|
|
60
|
+
readonly width?: number | string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* data options for custom ordering and link alignment
|
|
65
|
+
*/
|
|
66
|
+
export declare interface DataOptions {
|
|
67
|
+
/** custom node ordering - list of layers, each containing bands of node ids */
|
|
68
|
+
readonly order?: string[][][];
|
|
69
|
+
/** whether to align link types across nodes */
|
|
70
|
+
readonly alignLinkTypes?: boolean;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
declare const _default: DefineComponent<__VLS_Props, {
|
|
74
|
+
/** the underlying SankeyGraph instance (reactive ref) */
|
|
75
|
+
graph: ShallowRef<SankeyGraph | null, SankeyGraph | null>;
|
|
76
|
+
/** the container html element */
|
|
77
|
+
element: Ref<HTMLElement | null, HTMLElement | null>;
|
|
78
|
+
/** whether the chart is ready */
|
|
79
|
+
isReady: Ref<boolean, boolean>;
|
|
80
|
+
/** manually re-render the chart */
|
|
81
|
+
render: () => void;
|
|
82
|
+
/** destroy the chart */
|
|
83
|
+
destroy: () => void;
|
|
84
|
+
/** recreate the chart with current options */
|
|
85
|
+
recreate: () => void;
|
|
86
|
+
}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {}, string, PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, ComponentProvideOptions, false, {
|
|
87
|
+
containerRef: HTMLDivElement;
|
|
88
|
+
}, HTMLDivElement>;
|
|
89
|
+
export { _default as ApexSankey }
|
|
90
|
+
export default _default;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* edge definition connecting two nodes
|
|
94
|
+
*/
|
|
95
|
+
export declare interface Edge {
|
|
96
|
+
/** source node id */
|
|
97
|
+
readonly source: string;
|
|
98
|
+
/** target node id */
|
|
99
|
+
readonly target: string;
|
|
100
|
+
/** edge weight/size */
|
|
101
|
+
readonly value: number;
|
|
102
|
+
/** optional grouping type */
|
|
103
|
+
readonly type?: string;
|
|
104
|
+
/** optional custom color for the edge */
|
|
105
|
+
readonly color?: string;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* edge styling options
|
|
110
|
+
*/
|
|
111
|
+
export declare interface EdgeOptions {
|
|
112
|
+
/** enable gradient fill based on source and target node colors */
|
|
113
|
+
readonly edgeGradientFill?: boolean;
|
|
114
|
+
/** opacity value for edges (0 to 1) */
|
|
115
|
+
readonly edgeOpacity?: number;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* font styling options
|
|
120
|
+
*/
|
|
121
|
+
export declare interface FontOptions {
|
|
122
|
+
/** font color of node labels */
|
|
123
|
+
readonly fontColor?: string;
|
|
124
|
+
/** font family of node labels */
|
|
125
|
+
readonly fontFamily?: string;
|
|
126
|
+
/** font size of node labels */
|
|
127
|
+
readonly fontSize?: string;
|
|
128
|
+
/** font weight of node labels */
|
|
129
|
+
readonly fontWeight?: string;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* graph data structure containing nodes, edges, and optional configuration
|
|
134
|
+
*/
|
|
135
|
+
export declare interface GraphData {
|
|
136
|
+
/** array of nodes */
|
|
137
|
+
readonly nodes: Node_2[];
|
|
138
|
+
/** array of edges connecting nodes */
|
|
139
|
+
readonly edges: Edge[];
|
|
140
|
+
/** optional data-level options */
|
|
141
|
+
readonly options?: DataOptions;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* node definition for sankey diagram
|
|
146
|
+
*/
|
|
147
|
+
declare interface Node_2 {
|
|
148
|
+
/** unique identifier for the node */
|
|
149
|
+
readonly id: string;
|
|
150
|
+
/** display label for the node */
|
|
151
|
+
readonly title: string;
|
|
152
|
+
/** optional custom color for the node */
|
|
153
|
+
readonly color?: string;
|
|
154
|
+
}
|
|
155
|
+
export { Node_2 as Node }
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* node styling options
|
|
159
|
+
*/
|
|
160
|
+
export declare interface NodeOptions {
|
|
161
|
+
/** border color of nodes */
|
|
162
|
+
readonly nodeBorderColor?: string;
|
|
163
|
+
/** border width of nodes in pixels */
|
|
164
|
+
readonly nodeBorderWidth?: number;
|
|
165
|
+
/** width of graph nodes */
|
|
166
|
+
readonly nodeWidth?: number;
|
|
167
|
+
/** callback function for node click */
|
|
168
|
+
readonly onNodeClick?: (node: Node_2) => void;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* sankey graph instance returned by ApexSankey
|
|
173
|
+
*/
|
|
174
|
+
export declare interface SankeyGraph {
|
|
175
|
+
/** the graphlib graph instance */
|
|
176
|
+
graph: unknown;
|
|
177
|
+
/** maximum rank in the graph */
|
|
178
|
+
maxRank: number;
|
|
179
|
+
/** re-render the graph */
|
|
180
|
+
render: (options?: {
|
|
181
|
+
keepOldPosition?: boolean;
|
|
182
|
+
}) => void;
|
|
183
|
+
/** clear the canvas */
|
|
184
|
+
clear: () => void;
|
|
185
|
+
/** export to svg */
|
|
186
|
+
exportToSvg: () => void;
|
|
187
|
+
/** canvas height */
|
|
188
|
+
height: number;
|
|
189
|
+
/** canvas width */
|
|
190
|
+
width: number;
|
|
191
|
+
/** canvas spacing */
|
|
192
|
+
spacing: number;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* complete sankey options combining all option interfaces
|
|
197
|
+
*/
|
|
198
|
+
export declare type SankeyOptions = CommonOptions & NodeOptions & EdgeOptions & FontOptions & TooltipOptions;
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* sets the license key for ApexSankey
|
|
202
|
+
* call this once at app initialization before rendering any charts
|
|
203
|
+
*
|
|
204
|
+
* @param key - the license key string
|
|
205
|
+
*
|
|
206
|
+
* @example
|
|
207
|
+
* ```ts
|
|
208
|
+
* import { setApexSankeyLicense } from 'vue-apexsankey'
|
|
209
|
+
*
|
|
210
|
+
* // call at app initialization
|
|
211
|
+
* setApexSankeyLicense('your-license-key-here')
|
|
212
|
+
* ```
|
|
213
|
+
*/
|
|
214
|
+
export declare function setApexSankeyLicense(key: string): void;
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* content passed to tooltip template function
|
|
218
|
+
*/
|
|
219
|
+
export declare interface TooltipContent {
|
|
220
|
+
source: Node_2;
|
|
221
|
+
target: Node_2;
|
|
222
|
+
value: number;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* tooltip configuration options
|
|
227
|
+
*/
|
|
228
|
+
export declare interface TooltipOptions {
|
|
229
|
+
/** enable tooltip on hover */
|
|
230
|
+
readonly enableTooltip?: boolean;
|
|
231
|
+
/** background color of tooltip */
|
|
232
|
+
readonly tooltipBGColor?: string;
|
|
233
|
+
/** border color of tooltip */
|
|
234
|
+
readonly tooltipBorderColor?: string;
|
|
235
|
+
/** tooltip html element id */
|
|
236
|
+
readonly tooltipId?: string;
|
|
237
|
+
/** html template function for tooltip */
|
|
238
|
+
readonly tooltipTemplate?: (content: TooltipContent) => string;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* composable for managing ApexSankey instance lifecycle
|
|
243
|
+
*/
|
|
244
|
+
export declare function useSankey({ containerRef, data, options }: UseSankeyOptions): {
|
|
245
|
+
/** the sankey graph instance */
|
|
246
|
+
graph: ShallowRef<SankeyGraph | null, SankeyGraph | null>;
|
|
247
|
+
/** whether the chart is ready */
|
|
248
|
+
isReady: Ref<boolean, boolean>;
|
|
249
|
+
/** manually re-render the chart */
|
|
250
|
+
render: () => void;
|
|
251
|
+
/** destroy the chart */
|
|
252
|
+
destroy: () => void;
|
|
253
|
+
/** recreate the chart with current options */
|
|
254
|
+
recreate: () => void;
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* options for useSankey composable
|
|
259
|
+
*/
|
|
260
|
+
export declare interface UseSankeyOptions {
|
|
261
|
+
/** ref to the container element */
|
|
262
|
+
containerRef: Ref<HTMLElement | null>;
|
|
263
|
+
/** sankey data */
|
|
264
|
+
data: Ref<GraphData>;
|
|
265
|
+
/** sankey options */
|
|
266
|
+
options?: Ref<Partial<SankeyOptions> | undefined>;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export { }
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.apex-sankey-container[data-v-b70ceb71]{width:100%;height:100%}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { shallowRef as y, ref as h, onMounted as w, onBeforeUnmount as m, watch as k, defineComponent as S, toRef as x, computed as g, createElementBlock as _, openBlock as A, normalizeStyle as b } from "vue";
|
|
2
|
+
function L({ containerRef: o, data: a, options: e }) {
|
|
3
|
+
const n = y(null), r = y(null), s = h(!1);
|
|
4
|
+
function f() {
|
|
5
|
+
return typeof window > "u" ? null : window.ApexSankey || null;
|
|
6
|
+
}
|
|
7
|
+
function i() {
|
|
8
|
+
if (!o.value)
|
|
9
|
+
return;
|
|
10
|
+
const t = f();
|
|
11
|
+
if (!t) {
|
|
12
|
+
console.error(
|
|
13
|
+
"[vue-apexsankey] ApexSankey not found. Make sure to import apexsankey and @svgdotjs/svg.js before using this component."
|
|
14
|
+
);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
r.value = new t(o.value, (e == null ? void 0 : e.value) || {}), n.value = r.value.render(a.value), s.value = !0;
|
|
18
|
+
}
|
|
19
|
+
function c() {
|
|
20
|
+
var t, u;
|
|
21
|
+
!r.value || !o.value || ((u = (t = n.value) == null ? void 0 : t.clear) == null || u.call(t), n.value = r.value.render(a.value));
|
|
22
|
+
}
|
|
23
|
+
function l() {
|
|
24
|
+
var t, u;
|
|
25
|
+
n.value && ((u = (t = n.value).clear) == null || u.call(t)), o.value && (o.value.innerHTML = ""), n.value = null, r.value = null, s.value = !1;
|
|
26
|
+
}
|
|
27
|
+
function p() {
|
|
28
|
+
l(), i();
|
|
29
|
+
}
|
|
30
|
+
return w(() => {
|
|
31
|
+
i();
|
|
32
|
+
}), m(() => {
|
|
33
|
+
l();
|
|
34
|
+
}), k(
|
|
35
|
+
a,
|
|
36
|
+
() => {
|
|
37
|
+
s.value && c();
|
|
38
|
+
},
|
|
39
|
+
{ deep: !0 }
|
|
40
|
+
), k(
|
|
41
|
+
() => e == null ? void 0 : e.value,
|
|
42
|
+
() => {
|
|
43
|
+
s.value && p();
|
|
44
|
+
},
|
|
45
|
+
{ deep: !0 }
|
|
46
|
+
), {
|
|
47
|
+
/** the sankey graph instance */
|
|
48
|
+
graph: n,
|
|
49
|
+
/** whether the chart is ready */
|
|
50
|
+
isReady: s,
|
|
51
|
+
/** manually re-render the chart */
|
|
52
|
+
render: c,
|
|
53
|
+
/** destroy the chart */
|
|
54
|
+
destroy: l,
|
|
55
|
+
/** recreate the chart with current options */
|
|
56
|
+
recreate: p
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
const M = /* @__PURE__ */ S({
|
|
60
|
+
__name: "ApexSankey",
|
|
61
|
+
props: {
|
|
62
|
+
data: {},
|
|
63
|
+
options: {}
|
|
64
|
+
},
|
|
65
|
+
setup(o, { expose: a }) {
|
|
66
|
+
const e = o, n = h(null), r = x(e, "data"), s = x(e, "options"), { graph: f, isReady: i, render: c, destroy: l, recreate: p } = L({
|
|
67
|
+
containerRef: n,
|
|
68
|
+
data: r,
|
|
69
|
+
options: s
|
|
70
|
+
}), t = g(() => {
|
|
71
|
+
var d, v;
|
|
72
|
+
const u = {};
|
|
73
|
+
return (d = e.options) != null && d.width && (u.width = typeof e.options.width == "number" ? `${e.options.width}px` : e.options.width), (v = e.options) != null && v.height && (u.height = typeof e.options.height == "number" ? `${e.options.height}px` : e.options.height), u;
|
|
74
|
+
});
|
|
75
|
+
return a({
|
|
76
|
+
/** the underlying SankeyGraph instance (reactive ref) */
|
|
77
|
+
graph: f,
|
|
78
|
+
/** the container html element */
|
|
79
|
+
element: n,
|
|
80
|
+
/** whether the chart is ready */
|
|
81
|
+
isReady: i,
|
|
82
|
+
/** manually re-render the chart */
|
|
83
|
+
render: c,
|
|
84
|
+
/** destroy the chart */
|
|
85
|
+
destroy: l,
|
|
86
|
+
/** recreate the chart with current options */
|
|
87
|
+
recreate: p
|
|
88
|
+
}), (u, d) => (A(), _("div", {
|
|
89
|
+
ref_key: "containerRef",
|
|
90
|
+
ref: n,
|
|
91
|
+
class: "apex-sankey-container",
|
|
92
|
+
style: b(t.value)
|
|
93
|
+
}, null, 4));
|
|
94
|
+
}
|
|
95
|
+
}), R = (o, a) => {
|
|
96
|
+
const e = o.__vccOpts || o;
|
|
97
|
+
for (const [n, r] of a)
|
|
98
|
+
e[n] = r;
|
|
99
|
+
return e;
|
|
100
|
+
}, j = /* @__PURE__ */ R(M, [["__scopeId", "data-v-b70ceb71"]]);
|
|
101
|
+
function $(o) {
|
|
102
|
+
if (typeof window > "u")
|
|
103
|
+
return;
|
|
104
|
+
const a = window.ApexSankey;
|
|
105
|
+
a && typeof a.setLicense == "function" ? a.setLicense(o) : console.warn(
|
|
106
|
+
"[vue-apexsankey] ApexSankey not found. Make sure apexsankey is loaded before calling setApexSankeyLicense."
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
export {
|
|
110
|
+
j as ApexSankey,
|
|
111
|
+
j as default,
|
|
112
|
+
$ as setApexSankeyLicense,
|
|
113
|
+
L as useSankey
|
|
114
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(function(i,n){typeof exports=="object"&&typeof module<"u"?n(exports,require("vue")):typeof define=="function"&&define.amd?define(["exports","vue"],n):(i=typeof globalThis<"u"?globalThis:i||self,n(i.VueApexSankey={},i.Vue))})(this,(function(i,n){"use strict";function h({containerRef:a,data:s,options:e}){const t=n.shallowRef(null),l=n.shallowRef(null),r=n.ref(!1);function y(){return typeof window>"u"?null:window.ApexSankey||null}function f(){if(!a.value)return;const o=y();if(!o){console.error("[vue-apexsankey] ApexSankey not found. Make sure to import apexsankey and @svgdotjs/svg.js before using this component.");return}l.value=new o(a.value,(e==null?void 0:e.value)||{}),t.value=l.value.render(s.value),r.value=!0}function p(){var o,u;!l.value||!a.value||((u=(o=t.value)==null?void 0:o.clear)==null||u.call(o),t.value=l.value.render(s.value))}function c(){var o,u;t.value&&((u=(o=t.value).clear)==null||u.call(o)),a.value&&(a.value.innerHTML=""),t.value=null,l.value=null,r.value=!1}function d(){c(),f()}return n.onMounted(()=>{f()}),n.onBeforeUnmount(()=>{c()}),n.watch(s,()=>{r.value&&p()},{deep:!0}),n.watch(()=>e==null?void 0:e.value,()=>{r.value&&d()},{deep:!0}),{graph:t,isReady:r,render:p,destroy:c,recreate:d}}const x=((a,s)=>{const e=a.__vccOpts||a;for(const[t,l]of s)e[t]=l;return e})(n.defineComponent({__name:"ApexSankey",props:{data:{},options:{}},setup(a,{expose:s}){const e=a,t=n.ref(null),l=n.toRef(e,"data"),r=n.toRef(e,"options"),{graph:y,isReady:f,render:p,destroy:c,recreate:d}=h({containerRef:t,data:l,options:r}),o=n.computed(()=>{var k,v;const u={};return(k=e.options)!=null&&k.width&&(u.width=typeof e.options.width=="number"?`${e.options.width}px`:e.options.width),(v=e.options)!=null&&v.height&&(u.height=typeof e.options.height=="number"?`${e.options.height}px`:e.options.height),u});return s({graph:y,element:t,isReady:f,render:p,destroy:c,recreate:d}),(u,k)=>(n.openBlock(),n.createElementBlock("div",{ref_key:"containerRef",ref:t,class:"apex-sankey-container",style:n.normalizeStyle(o.value)},null,4))}}),[["__scopeId","data-v-b70ceb71"]]);function w(a){if(typeof window>"u")return;const s=window.ApexSankey;s&&typeof s.setLicense=="function"?s.setLicense(a):console.warn("[vue-apexsankey] ApexSankey not found. Make sure apexsankey is loaded before calling setApexSankeyLicense.")}i.ApexSankey=x,i.default=x,i.setApexSankeyLicense=w,i.useSankey=h,Object.defineProperties(i,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})}));
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vue-apexsankey",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Vue 3 wrapper for ApexSankey - A JavaScript library to create Sankey diagrams",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/vue-apexsankey.umd.cjs",
|
|
7
|
+
"module": "./dist/vue-apexsankey.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/vue-apexsankey.js"
|
|
14
|
+
},
|
|
15
|
+
"require": {
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"default": "./dist/vue-apexsankey.umd.cjs"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"dev": "vite",
|
|
26
|
+
"build": "vue-tsc --noEmit && vite build",
|
|
27
|
+
"preview": "vite preview",
|
|
28
|
+
"typecheck": "vue-tsc --noEmit"
|
|
29
|
+
},
|
|
30
|
+
"peerDependencies": {
|
|
31
|
+
"apexsankey": ">=1.2.0",
|
|
32
|
+
"vue": ">=3.3.0",
|
|
33
|
+
"@svgdotjs/svg.js": ">=3.0.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@vitejs/plugin-vue": "^5.2.1",
|
|
37
|
+
"typescript": "~5.6.0",
|
|
38
|
+
"vite": "^6.0.0",
|
|
39
|
+
"vite-plugin-dts": "^4.3.0",
|
|
40
|
+
"vue": "^3.5.13",
|
|
41
|
+
"vue-tsc": "^2.2.0",
|
|
42
|
+
"apexsankey": "^1.2.5",
|
|
43
|
+
"@svgdotjs/svg.js": "^3.2.4"
|
|
44
|
+
},
|
|
45
|
+
"keywords": [
|
|
46
|
+
"vue",
|
|
47
|
+
"vue3",
|
|
48
|
+
"sankey",
|
|
49
|
+
"diagram",
|
|
50
|
+
"chart",
|
|
51
|
+
"visualization",
|
|
52
|
+
"apexsankey",
|
|
53
|
+
"svg"
|
|
54
|
+
],
|
|
55
|
+
"author": "",
|
|
56
|
+
"license": "See LICENSE in LICENSE",
|
|
57
|
+
"repository": {
|
|
58
|
+
"type": "git",
|
|
59
|
+
"url": ""
|
|
60
|
+
},
|
|
61
|
+
"bugs": {
|
|
62
|
+
"url": ""
|
|
63
|
+
},
|
|
64
|
+
"homepage": ""
|
|
65
|
+
}
|