@loaders.gl/graphs 5.0.0-alpha.9 → 5.0.0-beta.2
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 +24 -0
- package/README.md +123 -4
- package/THIRD-PARTY-NOTICES.md +17 -0
- package/dist/dot-loader-types.cjs +59 -0
- package/dist/dot-loader-types.cjs.map +7 -0
- package/dist/dot-loader-types.d.ts +25 -8
- package/dist/dot-loader-types.d.ts.map +1 -1
- package/dist/dot-loader-types.js +2 -2
- package/dist/dot-loader-types.js.map +1 -1
- package/dist/dot-loader.cjs +638 -47
- package/dist/dot-loader.cjs.map +3 -3
- package/dist/dot-loader.d.ts +10 -6
- package/dist/dot-loader.d.ts.map +1 -1
- package/dist/dot-loader.js +3 -3
- package/dist/dot-loader.js.map +1 -1
- package/dist/dot-types.d.ts +6 -1
- package/dist/dot-types.d.ts.map +1 -1
- package/dist/gexf-loader-types.cjs +59 -0
- package/dist/gexf-loader-types.cjs.map +7 -0
- package/dist/gexf-loader-types.d.ts +53 -0
- package/dist/gexf-loader-types.d.ts.map +1 -0
- package/dist/gexf-loader-types.js +26 -0
- package/dist/gexf-loader-types.js.map +1 -0
- package/dist/gexf-loader.cjs +2940 -0
- package/dist/gexf-loader.cjs.map +6 -0
- package/dist/gexf-loader.d.ts +24 -0
- package/dist/gexf-loader.d.ts.map +1 -0
- package/dist/gexf-loader.js +13 -0
- package/dist/gexf-loader.js.map +1 -0
- package/dist/gexf-types.d.ts +16 -0
- package/dist/gexf-types.d.ts.map +1 -0
- package/dist/gexf-types.js +5 -0
- package/dist/gexf-types.js.map +1 -0
- package/dist/graph-types.d.ts +28 -0
- package/dist/graph-types.d.ts.map +1 -1
- package/dist/graphml-loader-types.cjs +59 -0
- package/dist/graphml-loader-types.cjs.map +7 -0
- package/dist/graphml-loader-types.d.ts +24 -8
- package/dist/graphml-loader-types.d.ts.map +1 -1
- package/dist/graphml-loader-types.js +2 -2
- package/dist/graphml-loader-types.js.map +1 -1
- package/dist/graphml-loader.cjs +2303 -69
- package/dist/graphml-loader.cjs.map +3 -3
- package/dist/graphml-loader.d.ts +10 -6
- package/dist/graphml-loader.d.ts.map +1 -1
- package/dist/graphml-loader.js +3 -3
- package/dist/graphml-loader.js.map +1 -1
- package/dist/index.cjs +56 -1085
- package/dist/index.cjs.map +4 -3
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/build-graph-tables.d.ts +4 -0
- package/dist/lib/build-graph-tables.d.ts.map +1 -0
- package/dist/lib/build-graph-tables.js +143 -0
- package/dist/lib/build-graph-tables.js.map +1 -0
- package/dist/lib/parse-dot.d.ts +3 -1
- package/dist/lib/parse-dot.d.ts.map +1 -1
- package/dist/lib/parse-dot.js +38 -21
- package/dist/lib/parse-dot.js.map +1 -1
- package/dist/lib/parse-gexf.d.ts +5 -0
- package/dist/lib/parse-gexf.d.ts.map +1 -0
- package/dist/lib/parse-gexf.js +382 -0
- package/dist/lib/parse-gexf.js.map +1 -0
- package/dist/lib/parse-graphml.d.ts +2 -1
- package/dist/lib/parse-graphml.d.ts.map +1 -1
- package/dist/lib/parse-graphml.js +54 -31
- package/dist/lib/parse-graphml.js.map +1 -1
- package/package.json +18 -8
- package/src/dot-loader-types.ts +15 -5
- package/src/dot-loader.ts +7 -4
- package/src/dot-types.ts +7 -1
- package/src/gexf-loader-types.ts +41 -0
- package/src/gexf-loader.ts +17 -0
- package/src/gexf-types.ts +21 -0
- package/src/graph-types.ts +35 -0
- package/src/graphml-loader-types.ts +14 -5
- package/src/graphml-loader.ts +7 -4
- package/src/index.ts +9 -0
- package/src/lib/build-graph-tables.ts +208 -0
- package/src/lib/parse-dot.ts +44 -21
- package/src/lib/parse-gexf.ts +390 -0
- package/src/lib/parse-graphml.ts +92 -40
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loaders.gl/graphs",
|
|
3
|
-
"version": "5.0.0-
|
|
4
|
-
"description": "Graph
|
|
3
|
+
"version": "5.0.0-beta.2",
|
|
4
|
+
"description": "Graph loaders for Graphviz DOT, GraphML, and GEXF",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"repository": {
|
|
@@ -12,8 +12,10 @@
|
|
|
12
12
|
"graph",
|
|
13
13
|
"dot",
|
|
14
14
|
"graphml",
|
|
15
|
+
"gexf",
|
|
15
16
|
"parser",
|
|
16
|
-
"loader"
|
|
17
|
+
"loader",
|
|
18
|
+
"loaders.gl"
|
|
17
19
|
],
|
|
18
20
|
"types": "dist/index.d.ts",
|
|
19
21
|
"main": "dist/index.cjs",
|
|
@@ -33,26 +35,34 @@
|
|
|
33
35
|
"types": "./dist/dot-loader.d.ts",
|
|
34
36
|
"import": "./dist/dot-loader.js",
|
|
35
37
|
"require": "./dist/dot-loader.cjs"
|
|
38
|
+
},
|
|
39
|
+
"./gexf-loader": {
|
|
40
|
+
"types": "./dist/gexf-loader.d.ts",
|
|
41
|
+
"import": "./dist/gexf-loader.js",
|
|
42
|
+
"require": "./dist/gexf-loader.cjs"
|
|
36
43
|
}
|
|
37
44
|
},
|
|
38
45
|
"sideEffects": false,
|
|
39
46
|
"files": [
|
|
40
47
|
"src",
|
|
41
48
|
"dist",
|
|
42
|
-
"README.md"
|
|
49
|
+
"README.md",
|
|
50
|
+
"LICENSE",
|
|
51
|
+
"THIRD-PARTY-NOTICES.md"
|
|
43
52
|
],
|
|
44
53
|
"scripts": {
|
|
45
|
-
"pre-build": "
|
|
54
|
+
"pre-build": "npm run build-metadata",
|
|
55
|
+
"build-metadata": "node ../../scripts/build-graphs-metadata.mjs"
|
|
46
56
|
},
|
|
47
57
|
"peerDependencies": {
|
|
48
58
|
"@loaders.gl/core": "~5.0.0-alpha.0"
|
|
49
59
|
},
|
|
50
|
-
"gitHead": "
|
|
60
|
+
"gitHead": "d259e5bc74caf8d75e808314b7fef2fa1bd70ff7",
|
|
51
61
|
"publishConfig": {
|
|
52
62
|
"access": "public"
|
|
53
63
|
},
|
|
54
64
|
"dependencies": {
|
|
55
|
-
"@loaders.gl/
|
|
56
|
-
"
|
|
65
|
+
"@loaders.gl/documents": "5.0.0-beta.2",
|
|
66
|
+
"apache-arrow": ">= 17.0.0"
|
|
57
67
|
}
|
|
58
68
|
}
|
package/src/dot-loader-types.ts
CHANGED
|
@@ -2,8 +2,18 @@
|
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
3
|
// Copyright (c) vis.gl contributors
|
|
4
4
|
|
|
5
|
-
import type {Loader} from '@loaders.gl/loader-utils';
|
|
6
|
-
import type {
|
|
5
|
+
import type {Loader} from '@loaders.gl/core/loader-utils';
|
|
6
|
+
import type {DOTOutput} from './dot-types';
|
|
7
|
+
import type {GraphShape} from './graph-types';
|
|
8
|
+
|
|
9
|
+
/** Options for selecting the DOT graph output representation. */
|
|
10
|
+
export type DOTLoaderOptions = {
|
|
11
|
+
/** Format-specific graph options. */
|
|
12
|
+
dot?: {
|
|
13
|
+
/** Representation of each named table; defaults to Arrow. */
|
|
14
|
+
shape?: GraphShape;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
7
17
|
|
|
8
18
|
// @ts-ignore __VERSION__ is injected by the build.
|
|
9
19
|
const VERSION = typeof __VERSION__ !== 'undefined' ? __VERSION__ : 'latest';
|
|
@@ -24,8 +34,8 @@ export const DOTLoader = {
|
|
|
24
34
|
mimeTypes: ['text/vnd.graphviz', 'text/x-graphviz', 'application/vnd.graphviz'],
|
|
25
35
|
text: true,
|
|
26
36
|
worker: false,
|
|
27
|
-
dataType: null as unknown as
|
|
37
|
+
dataType: null as unknown as DOTOutput,
|
|
28
38
|
batchType: null as never,
|
|
29
|
-
options: {},
|
|
39
|
+
options: {dot: {shape: 'arrow-table'}},
|
|
30
40
|
preload
|
|
31
|
-
} as const satisfies Loader<
|
|
41
|
+
} as const satisfies Loader<DOTOutput, never, DOTLoaderOptions>;
|
package/src/dot-loader.ts
CHANGED
|
@@ -2,13 +2,16 @@
|
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
3
|
// Copyright (c) vis.gl contributors
|
|
4
4
|
|
|
5
|
-
import {DOTLoader} from './dot-loader-types';
|
|
5
|
+
import {DOTLoader, type DOTLoaderOptions} from './dot-loader-types';
|
|
6
6
|
import {parseDOT} from './lib/parse-dot';
|
|
7
7
|
|
|
8
8
|
/** Parser-bearing loader for synchronous and asynchronous DOT parsing. */
|
|
9
9
|
export const DOTLoaderWithParser = {
|
|
10
10
|
...DOTLoader,
|
|
11
|
-
parse: async (data: ArrayBuffer) =>
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
parse: async (data: ArrayBuffer, options?: DOTLoaderOptions) =>
|
|
12
|
+
parseDOT(new TextDecoder().decode(data), options?.dot?.shape ?? 'arrow-table'),
|
|
13
|
+
parseSync: (data: ArrayBuffer, options?: DOTLoaderOptions) =>
|
|
14
|
+
parseDOT(new TextDecoder().decode(data), options?.dot?.shape ?? 'arrow-table'),
|
|
15
|
+
parseTextSync: (data: string, options?: DOTLoaderOptions) =>
|
|
16
|
+
parseDOT(data, options?.dot?.shape ?? 'arrow-table')
|
|
14
17
|
};
|
package/src/dot-types.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
3
|
// Copyright (c) vis.gl contributors
|
|
4
4
|
|
|
5
|
-
import type {GraphData} from './graph-types';
|
|
5
|
+
import type {GraphData, GraphOutput} from './graph-types';
|
|
6
6
|
|
|
7
7
|
/** Application attributes parsed from DOT. */
|
|
8
8
|
type DOTAttributeMap = Record<string, unknown>;
|
|
@@ -36,3 +36,9 @@ export type DOTGraphData = GraphData & {
|
|
|
36
36
|
/** DOT graph identity, direction, strictness, attributes, and subgraphs. */
|
|
37
37
|
metadata: DOTMetadata;
|
|
38
38
|
};
|
|
39
|
+
|
|
40
|
+
/** DOT tables or compatibility records with graph metadata. */
|
|
41
|
+
export type DOTOutput = GraphOutput & {
|
|
42
|
+
/** Graph identity, defaults, and subgraph descriptors. */
|
|
43
|
+
metadata: DOTMetadata;
|
|
44
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// loaders.gl
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
|
|
5
|
+
import type {Loader} from '@loaders.gl/core/loader-utils';
|
|
6
|
+
import type {GraphShape} from './graph-types';
|
|
7
|
+
import type {GEXFOutput} from './gexf-types';
|
|
8
|
+
|
|
9
|
+
/** Options for selecting the GEXF graph output representation. */
|
|
10
|
+
export type GEXFLoaderOptions = {
|
|
11
|
+
/** GEXF-specific graph options. */
|
|
12
|
+
gexf?: {
|
|
13
|
+
/** Representation of each named table; defaults to Arrow. */
|
|
14
|
+
shape?: GraphShape;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// @ts-ignore __VERSION__ is injected by the build.
|
|
19
|
+
const VERSION = typeof __VERSION__ !== 'undefined' ? __VERSION__ : 'latest';
|
|
20
|
+
|
|
21
|
+
/** Preloads the GEXF parser implementation. */
|
|
22
|
+
async function preload() {
|
|
23
|
+
const {GEXFLoaderWithParser} = await import('@loaders.gl/graphs/gexf-loader');
|
|
24
|
+
return GEXFLoaderWithParser;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Metadata-only loader for static GEXF 1.2 and 1.3 documents. */
|
|
28
|
+
export const GEXFLoader = {
|
|
29
|
+
name: 'GEXF',
|
|
30
|
+
id: 'gexf',
|
|
31
|
+
module: 'graphs',
|
|
32
|
+
version: VERSION,
|
|
33
|
+
extensions: ['gexf'],
|
|
34
|
+
mimeTypes: ['application/gexf+xml'],
|
|
35
|
+
text: true,
|
|
36
|
+
worker: false,
|
|
37
|
+
dataType: null as unknown as GEXFOutput,
|
|
38
|
+
batchType: null as never,
|
|
39
|
+
options: {gexf: {shape: 'arrow-table'}},
|
|
40
|
+
preload
|
|
41
|
+
} as const satisfies Loader<GEXFOutput, never, GEXFLoaderOptions>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// loaders.gl
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
|
|
5
|
+
import {GEXFLoader, type GEXFLoaderOptions} from './gexf-loader-types';
|
|
6
|
+
import {parseGEXF} from './lib/parse-gexf';
|
|
7
|
+
|
|
8
|
+
/** Parser-bearing loader for synchronous and asynchronous GEXF parsing. */
|
|
9
|
+
export const GEXFLoaderWithParser = {
|
|
10
|
+
...GEXFLoader,
|
|
11
|
+
parse: async (data: ArrayBuffer, options?: GEXFLoaderOptions) =>
|
|
12
|
+
parseGEXF(data, options?.gexf?.shape ?? 'arrow-table'),
|
|
13
|
+
parseSync: (data: ArrayBuffer, options?: GEXFLoaderOptions) =>
|
|
14
|
+
parseGEXF(data, options?.gexf?.shape ?? 'arrow-table'),
|
|
15
|
+
parseTextSync: (data: string, options?: GEXFLoaderOptions) =>
|
|
16
|
+
parseGEXF(data, options?.gexf?.shape ?? 'arrow-table')
|
|
17
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// loaders.gl
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
|
|
5
|
+
import type {GraphOutput} from './graph-types';
|
|
6
|
+
|
|
7
|
+
/** Static GEXF document metadata, retained in every output shape. */
|
|
8
|
+
export type GEXFMetadata = {
|
|
9
|
+
/** Document format version, when supplied. */
|
|
10
|
+
version?: string;
|
|
11
|
+
/** Default direction used by edges without a type override. */
|
|
12
|
+
directed: boolean;
|
|
13
|
+
/** Document author, description, keywords, and modification date. */
|
|
14
|
+
attributes: Record<string, string>;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/** Standard graph tables or plain records with GEXF document metadata. */
|
|
18
|
+
export type GEXFOutput = GraphOutput & {
|
|
19
|
+
/** Document metadata and graph-wide direction. */
|
|
20
|
+
metadata: GEXFMetadata;
|
|
21
|
+
};
|
package/src/graph-types.ts
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
3
|
// Copyright (c) vis.gl contributors
|
|
4
4
|
|
|
5
|
+
import type {ArrowTable, ObjectRowTable, Tables} from '@loaders.gl/core/schema';
|
|
6
|
+
|
|
5
7
|
/** A graph node with its original identifier and application attributes. */
|
|
6
8
|
export type GraphNode = {
|
|
7
9
|
/** Original node identifier. */
|
|
@@ -31,3 +33,36 @@ export type GraphData = {
|
|
|
31
33
|
/** Edges in document order. */
|
|
32
34
|
edges: GraphEdge[];
|
|
33
35
|
};
|
|
36
|
+
|
|
37
|
+
/** Standard table representations supported by graph loaders. */
|
|
38
|
+
export type GraphShape = 'arrow-table' | 'object-row-table' | 'plain-graph-data';
|
|
39
|
+
|
|
40
|
+
/** Named node and edge tables in document order. */
|
|
41
|
+
export type GraphTables<TableType = ArrowTable> = Tables<TableType> & {
|
|
42
|
+
/** Node table followed by edge table. */
|
|
43
|
+
tables: [{name: 'nodes'; table: TableType}, {name: 'edges'; table: TableType}];
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/** Graph loader output, with Arrow tables as the default representation. */
|
|
47
|
+
export type GraphOutput = GraphTables | GraphTables<ObjectRowTable> | GraphData;
|
|
48
|
+
|
|
49
|
+
/** Scalar and list types declared by graph format attributes. */
|
|
50
|
+
export type GraphAttributeType =
|
|
51
|
+
| 'boolean'
|
|
52
|
+
| 'int'
|
|
53
|
+
| 'long'
|
|
54
|
+
| 'float'
|
|
55
|
+
| 'double'
|
|
56
|
+
| 'string'
|
|
57
|
+
| {
|
|
58
|
+
/** Declared element type for a GEXF list attribute. */
|
|
59
|
+
list: GraphAttributeType;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/** Declared application attribute schemas for each graph table. */
|
|
63
|
+
export type GraphAttributeSchemas = {
|
|
64
|
+
/** Declared node attributes, including keys with no values. */
|
|
65
|
+
nodes: Map<string, GraphAttributeType>;
|
|
66
|
+
/** Declared edge attributes, including keys with no values. */
|
|
67
|
+
edges: Map<string, GraphAttributeType>;
|
|
68
|
+
};
|
|
@@ -2,8 +2,17 @@
|
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
3
|
// Copyright (c) vis.gl contributors
|
|
4
4
|
|
|
5
|
-
import type {Loader} from '@loaders.gl/loader-utils';
|
|
6
|
-
import type {
|
|
5
|
+
import type {Loader} from '@loaders.gl/core/loader-utils';
|
|
6
|
+
import type {GraphOutput, GraphShape} from './graph-types';
|
|
7
|
+
|
|
8
|
+
/** Options for selecting the GraphML graph output representation. */
|
|
9
|
+
export type GraphMLLoaderOptions = {
|
|
10
|
+
/** Format-specific graph options. */
|
|
11
|
+
graphml?: {
|
|
12
|
+
/** Representation of each named table; defaults to Arrow. */
|
|
13
|
+
shape?: GraphShape;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
7
16
|
|
|
8
17
|
// @ts-ignore __VERSION__ is injected by the build.
|
|
9
18
|
const VERSION = typeof __VERSION__ !== 'undefined' ? __VERSION__ : 'latest';
|
|
@@ -24,8 +33,8 @@ export const GraphMLLoader = {
|
|
|
24
33
|
mimeTypes: ['application/graphml+xml'],
|
|
25
34
|
text: true,
|
|
26
35
|
worker: false,
|
|
27
|
-
dataType: null as unknown as
|
|
36
|
+
dataType: null as unknown as GraphOutput,
|
|
28
37
|
batchType: null as never,
|
|
29
|
-
options: {},
|
|
38
|
+
options: {graphml: {shape: 'arrow-table'}},
|
|
30
39
|
preload
|
|
31
|
-
} as const satisfies Loader<
|
|
40
|
+
} as const satisfies Loader<GraphOutput, never, GraphMLLoaderOptions>;
|
package/src/graphml-loader.ts
CHANGED
|
@@ -2,13 +2,16 @@
|
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
3
|
// Copyright (c) vis.gl contributors
|
|
4
4
|
|
|
5
|
-
import {GraphMLLoader} from './graphml-loader-types';
|
|
5
|
+
import {GraphMLLoader, type GraphMLLoaderOptions} from './graphml-loader-types';
|
|
6
6
|
import {parseGraphML} from './lib/parse-graphml';
|
|
7
7
|
|
|
8
8
|
/** Parser-bearing loader for synchronous and asynchronous GraphML parsing. */
|
|
9
9
|
export const GraphMLLoaderWithParser = {
|
|
10
10
|
...GraphMLLoader,
|
|
11
|
-
parse: async (data: ArrayBuffer) =>
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
parse: async (data: ArrayBuffer, options?: GraphMLLoaderOptions) =>
|
|
12
|
+
parseGraphML(data, options?.graphml?.shape ?? 'arrow-table'),
|
|
13
|
+
parseSync: (data: ArrayBuffer, options?: GraphMLLoaderOptions) =>
|
|
14
|
+
parseGraphML(data, options?.graphml?.shape ?? 'arrow-table'),
|
|
15
|
+
parseTextSync: (data: string, options?: GraphMLLoaderOptions) =>
|
|
16
|
+
parseGraphML(data, options?.graphml?.shape ?? 'arrow-table')
|
|
14
17
|
};
|
package/src/index.ts
CHANGED
|
@@ -6,3 +6,12 @@ export {GraphMLLoader} from './graphml-loader-types';
|
|
|
6
6
|
export type {GraphData, GraphNode, GraphEdge} from './graph-types';
|
|
7
7
|
export {DOTLoader} from './dot-loader-types';
|
|
8
8
|
export type {DOTGraphData, DOTMetadata, DOTSubgraph} from './dot-types';
|
|
9
|
+
|
|
10
|
+
export type {GraphShape, GraphTables, GraphOutput} from './graph-types';
|
|
11
|
+
export type {DOTOutput} from './dot-types';
|
|
12
|
+
export type {DOTLoaderOptions} from './dot-loader-types';
|
|
13
|
+
export type {GraphMLLoaderOptions} from './graphml-loader-types';
|
|
14
|
+
|
|
15
|
+
export {GEXFLoader} from './gexf-loader-types';
|
|
16
|
+
export type {GEXFLoaderOptions} from './gexf-loader-types';
|
|
17
|
+
export type {GEXFMetadata, GEXFOutput} from './gexf-types';
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
// loaders.gl
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
Bool,
|
|
7
|
+
Float32,
|
|
8
|
+
Float64,
|
|
9
|
+
Int32,
|
|
10
|
+
Int64,
|
|
11
|
+
Utf8,
|
|
12
|
+
Struct,
|
|
13
|
+
List,
|
|
14
|
+
Field,
|
|
15
|
+
Table,
|
|
16
|
+
vectorFromArray
|
|
17
|
+
} from 'apache-arrow';
|
|
18
|
+
import type {DataType, Vector} from 'apache-arrow';
|
|
19
|
+
import {convertArrowToSchema} from '@loaders.gl/core/schema-utils';
|
|
20
|
+
import type {ArrowTable, ObjectRowTable} from '@loaders.gl/core/schema';
|
|
21
|
+
import type {
|
|
22
|
+
GraphNode,
|
|
23
|
+
GraphEdge,
|
|
24
|
+
GraphOutput,
|
|
25
|
+
GraphShape,
|
|
26
|
+
GraphAttributeSchemas,
|
|
27
|
+
GraphAttributeType
|
|
28
|
+
} from '../graph-types';
|
|
29
|
+
|
|
30
|
+
/** Constructs named tables without materializing an intermediate plain graph. */
|
|
31
|
+
export function buildGraphTables(
|
|
32
|
+
nodes: Iterable<GraphNode>,
|
|
33
|
+
edges: Iterable<GraphEdge>,
|
|
34
|
+
shape: GraphShape,
|
|
35
|
+
declarations?: GraphAttributeSchemas
|
|
36
|
+
): GraphOutput {
|
|
37
|
+
if (shape === 'plain-graph-data') {
|
|
38
|
+
return {shape, nodes: Array.from(nodes), edges: Array.from(edges)};
|
|
39
|
+
}
|
|
40
|
+
if (shape !== 'arrow-table' && shape !== 'object-row-table') {
|
|
41
|
+
throw new Error(`Unsupported graph shape: ${shape}`);
|
|
42
|
+
}
|
|
43
|
+
const nodeTable = buildGraphTable(nodes, false, shape, declarations?.nodes);
|
|
44
|
+
const edgeTable = buildGraphTable(edges, true, shape, declarations?.edges);
|
|
45
|
+
// Both tables always have the same representation.
|
|
46
|
+
return {
|
|
47
|
+
shape: 'tables',
|
|
48
|
+
tables: [
|
|
49
|
+
{name: 'nodes', table: nodeTable},
|
|
50
|
+
{name: 'edges', table: edgeTable}
|
|
51
|
+
]
|
|
52
|
+
} as GraphOutput;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Builds structural columns and a collision-free application attribute struct. */
|
|
56
|
+
function buildGraphTable(
|
|
57
|
+
records: Iterable<GraphNode | GraphEdge>,
|
|
58
|
+
isEdge: boolean,
|
|
59
|
+
shape: 'arrow-table' | 'object-row-table',
|
|
60
|
+
declarations?: Map<string, GraphAttributeType>
|
|
61
|
+
): ArrowTable | ObjectRowTable {
|
|
62
|
+
const columnNames = isEdge
|
|
63
|
+
? ['id', 'sourceId', 'targetId', 'directed', 'label', 'attributes']
|
|
64
|
+
: ['id', 'label', 'attributes'];
|
|
65
|
+
const columns = new Map<string, unknown[]>(columnNames.map(name => [name, []]));
|
|
66
|
+
for (const record of records) {
|
|
67
|
+
for (const name of columnNames) {
|
|
68
|
+
const value = (record as unknown as Record<string, unknown>)[name];
|
|
69
|
+
columns
|
|
70
|
+
.get(name)!
|
|
71
|
+
.push(
|
|
72
|
+
name === 'id' || name === 'sourceId' || name === 'targetId'
|
|
73
|
+
? String(value)
|
|
74
|
+
: (value ?? null)
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (shape === 'object-row-table') {
|
|
79
|
+
return {
|
|
80
|
+
shape,
|
|
81
|
+
data: columns
|
|
82
|
+
.get('id')!
|
|
83
|
+
.map((_, index) =>
|
|
84
|
+
Object.fromEntries(columnNames.map(name => [name, columns.get(name)![index]]))
|
|
85
|
+
)
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
const vectors: Record<string, Vector> = Object.create(null);
|
|
89
|
+
for (const [name, values] of columns) {
|
|
90
|
+
const type =
|
|
91
|
+
name === 'attributes'
|
|
92
|
+
? inferAttributeType(values, declarations)
|
|
93
|
+
: name === 'directed'
|
|
94
|
+
? new Bool()
|
|
95
|
+
: new Utf8();
|
|
96
|
+
vectors[name] = vectorFromArray(
|
|
97
|
+
values.map(value => normalizeValue(value, type)),
|
|
98
|
+
type
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
const data = new Table(vectors);
|
|
102
|
+
return {shape, schema: convertArrowToSchema(data.schema), data};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Infers a struct using the union of declared and observed application keys. */
|
|
106
|
+
function inferAttributeType(
|
|
107
|
+
values: unknown[],
|
|
108
|
+
declarations?: Map<string, GraphAttributeType>
|
|
109
|
+
): Struct {
|
|
110
|
+
const names = new Set(declarations?.keys());
|
|
111
|
+
for (const value of values) {
|
|
112
|
+
if (value && typeof value === 'object') {
|
|
113
|
+
for (const name of Object.keys(value)) {
|
|
114
|
+
names.add(name);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return new Struct(
|
|
119
|
+
Array.from(names, name => {
|
|
120
|
+
const fieldValues = values.map(
|
|
121
|
+
value => (value as Record<string, unknown> | null)?.[name] ?? null
|
|
122
|
+
);
|
|
123
|
+
return new Field(name, inferValueType(fieldValues, declarations?.get(name)), true);
|
|
124
|
+
})
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Preserves declared scalar types where valid and infers nested DOT attributes. */
|
|
129
|
+
function inferValueType(values: unknown[], declaration?: GraphAttributeType): DataType {
|
|
130
|
+
const present = values.filter(value => value !== null && value !== undefined);
|
|
131
|
+
if (declaration && typeof declaration === 'object') {
|
|
132
|
+
if (!present.every(value => Array.isArray(value))) return new Utf8();
|
|
133
|
+
return new List(
|
|
134
|
+
new Field(
|
|
135
|
+
'item',
|
|
136
|
+
inferValueType(
|
|
137
|
+
present.flatMap(value => value as unknown[]),
|
|
138
|
+
declaration.list
|
|
139
|
+
),
|
|
140
|
+
true
|
|
141
|
+
)
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
if (declaration) {
|
|
145
|
+
const declaredTypes = {
|
|
146
|
+
boolean: new Bool(),
|
|
147
|
+
int: new Int32(),
|
|
148
|
+
long: new Int64(),
|
|
149
|
+
float: new Float32(),
|
|
150
|
+
double: new Float64(),
|
|
151
|
+
string: new Utf8()
|
|
152
|
+
};
|
|
153
|
+
const valid = present.every(value => {
|
|
154
|
+
switch (declaration) {
|
|
155
|
+
case 'boolean':
|
|
156
|
+
return typeof value === 'boolean';
|
|
157
|
+
case 'int':
|
|
158
|
+
return (
|
|
159
|
+
typeof value === 'number' &&
|
|
160
|
+
Number.isInteger(value) &&
|
|
161
|
+
value >= -2147483648 &&
|
|
162
|
+
value <= 2147483647
|
|
163
|
+
);
|
|
164
|
+
case 'long':
|
|
165
|
+
return typeof value === 'bigint' && value >= -(2n ** 63n) && value < 2n ** 63n;
|
|
166
|
+
case 'float':
|
|
167
|
+
case 'double':
|
|
168
|
+
return typeof value === 'number';
|
|
169
|
+
case 'string':
|
|
170
|
+
return typeof value === 'string';
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
return valid ? declaredTypes[declaration] : new Utf8();
|
|
174
|
+
}
|
|
175
|
+
if (present.length && present.every(value => typeof value === 'boolean')) return new Bool();
|
|
176
|
+
if (present.length && present.every(value => typeof value === 'number')) return new Float64();
|
|
177
|
+
if (present.length && present.every(value => Array.isArray(value))) {
|
|
178
|
+
return new List(
|
|
179
|
+
new Field('item', inferValueType(present.flatMap(value => value as unknown[])), true)
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
if (
|
|
183
|
+
present.length &&
|
|
184
|
+
present.every(value => typeof value === 'object' && !Array.isArray(value))
|
|
185
|
+
) {
|
|
186
|
+
return inferAttributeType(present);
|
|
187
|
+
}
|
|
188
|
+
return new Utf8();
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** Normalizes missing fields and mixed values for the selected Arrow type. */
|
|
192
|
+
function normalizeValue(value: unknown, type: DataType): unknown {
|
|
193
|
+
if (value === null || value === undefined) return null;
|
|
194
|
+
if (type instanceof Struct) {
|
|
195
|
+
return Object.fromEntries(
|
|
196
|
+
type.children.map(field => [
|
|
197
|
+
field.name,
|
|
198
|
+
normalizeValue((value as Record<string, unknown>)[field.name], field.type)
|
|
199
|
+
])
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
if (type instanceof List)
|
|
203
|
+
return (value as unknown[]).map(item => normalizeValue(item, type.valueType));
|
|
204
|
+
if (type instanceof Utf8 && typeof value !== 'string') {
|
|
205
|
+
return typeof value === 'object' ? JSON.stringify(value) : String(value);
|
|
206
|
+
}
|
|
207
|
+
return value;
|
|
208
|
+
}
|
package/src/lib/parse-dot.ts
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
3
|
// Copyright (c) vis.gl contributors
|
|
4
4
|
|
|
5
|
-
import type {DOTGraphData} from '../dot-types';
|
|
5
|
+
import type {DOTGraphData, DOTOutput} from '../dot-types';
|
|
6
|
+
import type {GraphShape, GraphNode, GraphEdge} from '../graph-types';
|
|
7
|
+
import {buildGraphTables} from './build-graph-tables';
|
|
6
8
|
|
|
7
9
|
/** Internal DOT parsing representation. */
|
|
8
10
|
type DOTAttributeMap = Record<string, unknown>;
|
|
@@ -62,15 +64,29 @@ type DOTParseResult = {
|
|
|
62
64
|
};
|
|
63
65
|
|
|
64
66
|
/** Parses DOT source into plain graph records without a visualization dependency. */
|
|
65
|
-
export function parseDOT(input: string): DOTGraphData
|
|
67
|
+
export function parseDOT(input: string): DOTGraphData;
|
|
68
|
+
export function parseDOT(input: string, shape: GraphShape): DOTOutput;
|
|
69
|
+
export function parseDOT(input: string, shape: GraphShape = 'plain-graph-data'): DOTOutput {
|
|
66
70
|
const parsed = parseDOTDocument(input);
|
|
67
|
-
const subgraphs = Array.from(parsed.subgraphs.values(), subgraph => ({
|
|
68
|
-
...subgraph,
|
|
69
|
-
attributes: {...subgraph.attributes}
|
|
70
|
-
}));
|
|
71
71
|
return {
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
...buildGraphTables(iterateNodes(parsed), iterateEdges(parsed), shape),
|
|
73
|
+
metadata: {
|
|
74
|
+
id: parsed.id,
|
|
75
|
+
directed: parsed.directed,
|
|
76
|
+
strict: parsed.strict,
|
|
77
|
+
attributes: {...parsed.graphAttributes},
|
|
78
|
+
subgraphs: Array.from(parsed.subgraphs.values(), subgraph => ({
|
|
79
|
+
...subgraph,
|
|
80
|
+
attributes: {...subgraph.attributes}
|
|
81
|
+
}))
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Emits finalized nodes without creating an intermediate record array. */
|
|
87
|
+
function* iterateNodes(parsed: DOTParseResult): Iterable<GraphNode> {
|
|
88
|
+
for (const node of parsed.nodes.values()) {
|
|
89
|
+
yield {
|
|
74
90
|
id: node.id,
|
|
75
91
|
...(typeof node.attributes.label === 'string' ? {label: node.attributes.label} : {}),
|
|
76
92
|
attributes: {
|
|
@@ -79,8 +95,14 @@ export function parseDOT(input: string): DOTGraphData {
|
|
|
79
95
|
? {subgraphs: Array.from(node.subgraphs, id => describeSubgraph(id, parsed.subgraphs))}
|
|
80
96
|
: {})
|
|
81
97
|
}
|
|
82
|
-
}
|
|
83
|
-
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Emits coalesced edges in document order. */
|
|
103
|
+
function* iterateEdges(parsed: DOTParseResult): Iterable<GraphEdge> {
|
|
104
|
+
for (const edge of parsed.edges) {
|
|
105
|
+
yield {
|
|
84
106
|
id: edge.id,
|
|
85
107
|
sourceId: edge.sourceId,
|
|
86
108
|
targetId: edge.targetId,
|
|
@@ -92,15 +114,8 @@ export function parseDOT(input: string): DOTGraphData {
|
|
|
92
114
|
? {subgraphs: edge.subgraphs.map(id => describeSubgraph(id, parsed.subgraphs))}
|
|
93
115
|
: {})
|
|
94
116
|
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
id: parsed.id,
|
|
98
|
-
directed: parsed.directed,
|
|
99
|
-
strict: parsed.strict,
|
|
100
|
-
attributes: {...parsed.graphAttributes},
|
|
101
|
-
subgraphs
|
|
102
|
-
}
|
|
103
|
-
};
|
|
117
|
+
};
|
|
118
|
+
}
|
|
104
119
|
}
|
|
105
120
|
|
|
106
121
|
/** Copies a subgraph descriptor into node or edge attributes. */
|
|
@@ -393,6 +408,10 @@ class DOTParser {
|
|
|
393
408
|
const existingEdge = this.result.strict ? this.strictEdges.get(strictKey) : undefined;
|
|
394
409
|
if (existingEdge) {
|
|
395
410
|
existingEdge.attributes = {...existingEdge.attributes, ...explicitAttributes};
|
|
411
|
+
const explicitId = explicitAttributes.id ?? explicitAttributes.Id ?? explicitAttributes.ID;
|
|
412
|
+
if (typeof explicitId === 'string' || typeof explicitId === 'number') {
|
|
413
|
+
existingEdge.id = String(explicitId);
|
|
414
|
+
}
|
|
396
415
|
existingEdge.directed = deriveDirectedFlag(existingEdge.attributes, directed);
|
|
397
416
|
existingEdge.subgraphs = Array.from(new Set([...existingEdge.subgraphs, ...membership]));
|
|
398
417
|
continue;
|
|
@@ -742,13 +761,17 @@ function readQuotedString(input: string, startIndex: number): {value: string; ne
|
|
|
742
761
|
throw new Error('Unterminated string literal in DOT source.');
|
|
743
762
|
}
|
|
744
763
|
|
|
745
|
-
/** Decodes one quoted-string escape. */
|
|
764
|
+
/** Decodes one quoted-string escape or removes a physical line continuation. */
|
|
746
765
|
function readEscapedCharacter(
|
|
747
766
|
input: string,
|
|
748
767
|
startIndex: number
|
|
749
768
|
): {value: string; nextIndex: number} {
|
|
750
769
|
const next = input[startIndex];
|
|
751
770
|
switch (next) {
|
|
771
|
+
case '\n':
|
|
772
|
+
return {value: '', nextIndex: startIndex + 1};
|
|
773
|
+
case '\r':
|
|
774
|
+
return {value: '', nextIndex: startIndex + (input[startIndex + 1] === '\n' ? 2 : 1)};
|
|
752
775
|
case 'n':
|
|
753
776
|
case 'l':
|
|
754
777
|
case 'L':
|
|
@@ -765,7 +788,7 @@ function readEscapedCharacter(
|
|
|
765
788
|
if (typeof next === 'undefined') {
|
|
766
789
|
throw new Error('Unterminated escape sequence in DOT source.');
|
|
767
790
|
}
|
|
768
|
-
return {value: next
|
|
791
|
+
return {value: `\\${next}`, nextIndex: startIndex + 1};
|
|
769
792
|
}
|
|
770
793
|
}
|
|
771
794
|
}
|