@neo4j-ndl/react-graph 1.2.22 → 1.2.24
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/lib/cjs/styling/compile-graph-styles.js +482 -0
- package/lib/cjs/styling/compile-graph-styles.js.map +1 -0
- package/lib/cjs/styling/parse-style-rules.js +69 -0
- package/lib/cjs/styling/parse-style-rules.js.map +1 -0
- package/lib/cjs/styling/style-types.js +119 -0
- package/lib/cjs/styling/style-types.js.map +1 -0
- package/lib/esm/styling/compile-graph-styles.js +478 -0
- package/lib/esm/styling/compile-graph-styles.js.map +1 -0
- package/lib/esm/styling/parse-style-rules.js +63 -0
- package/lib/esm/styling/parse-style-rules.js.map +1 -0
- package/lib/esm/styling/style-types.js +116 -0
- package/lib/esm/styling/style-types.js.map +1 -0
- package/lib/types/styling/compile-graph-styles.d.ts +40 -0
- package/lib/types/styling/compile-graph-styles.d.ts.map +1 -0
- package/lib/types/styling/parse-style-rules.d.ts +49 -0
- package/lib/types/styling/parse-style-rules.d.ts.map +1 -0
- package/lib/types/styling/style-types.d.ts +169 -0
- package/lib/types/styling/style-types.d.ts.map +1 -0
- package/package.json +5 -4
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
*
|
|
4
|
+
* Copyright (c) "Neo4j"
|
|
5
|
+
* Neo4j Sweden AB [http://neo4j.com]
|
|
6
|
+
*
|
|
7
|
+
* This file is part of Neo4j.
|
|
8
|
+
*
|
|
9
|
+
* Neo4j is free software: you can redistribute it and/or modify
|
|
10
|
+
* it under the terms of the GNU General Public License as published by
|
|
11
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
12
|
+
* (at your option) any later version.
|
|
13
|
+
*
|
|
14
|
+
* This program is distributed in the hope that it will be useful,
|
|
15
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
16
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
17
|
+
* GNU General Public License for more details.
|
|
18
|
+
*
|
|
19
|
+
* You should have received a copy of the GNU General Public License
|
|
20
|
+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
21
|
+
*/
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.StyleRuleSchema = exports.StyleSchema = exports.NvlCaptionSchema = exports.CaptionVariationSchema = exports.CaptionValueSchema = exports.WhereSchema = exports.ValueSchema = exports.CypherValueSchema = exports.MatchSchema = exports.SelectorSchema = void 0;
|
|
24
|
+
const zod_1 = require("zod");
|
|
25
|
+
/*
|
|
26
|
+
* Style types defined in Zod, so that we can use the schemas to generate JSON Schema.
|
|
27
|
+
* This is useful for generating documentation, importing and for future integration into editors.
|
|
28
|
+
*
|
|
29
|
+
* Some simple examples of rules:
|
|
30
|
+
* - Match all nodes with label "Person" and apply red color
|
|
31
|
+
* { match: { label: "Person" }, apply: { color: "red" } }
|
|
32
|
+
*
|
|
33
|
+
* - Match all relationships with type "KNOWS" which have the property "name" equal to "John" and apply width 10
|
|
34
|
+
* { match: { reltype: "KNOWS" }, where: { equal: [{ property: "name" }, "John"] }, apply: { width: 10 } }
|
|
35
|
+
*/
|
|
36
|
+
// Selector schemas. This is what can go under the `match` key.
|
|
37
|
+
const LabelSelectorSchema = zod_1.z.object({
|
|
38
|
+
label: zod_1.z.string().nullable(),
|
|
39
|
+
});
|
|
40
|
+
const ReltypeSelectorSchema = zod_1.z.object({
|
|
41
|
+
reltype: zod_1.z.string().nullable(),
|
|
42
|
+
});
|
|
43
|
+
const PropertySelectorSchema = zod_1.z.object({
|
|
44
|
+
property: zod_1.z.string(),
|
|
45
|
+
});
|
|
46
|
+
exports.SelectorSchema = zod_1.z.union([
|
|
47
|
+
LabelSelectorSchema,
|
|
48
|
+
ReltypeSelectorSchema,
|
|
49
|
+
PropertySelectorSchema,
|
|
50
|
+
]);
|
|
51
|
+
exports.MatchSchema = zod_1.z.union([
|
|
52
|
+
LabelSelectorSchema,
|
|
53
|
+
ReltypeSelectorSchema,
|
|
54
|
+
PropertySelectorSchema,
|
|
55
|
+
]);
|
|
56
|
+
// Schemas for the values that can go under the `where` key. Starts with defining the literal values.
|
|
57
|
+
exports.CypherValueSchema = zod_1.z.union([
|
|
58
|
+
zod_1.z.string(),
|
|
59
|
+
zod_1.z.number(),
|
|
60
|
+
zod_1.z.boolean(),
|
|
61
|
+
zod_1.z.null(),
|
|
62
|
+
]);
|
|
63
|
+
exports.ValueSchema = zod_1.z
|
|
64
|
+
.union([exports.SelectorSchema, exports.CypherValueSchema])
|
|
65
|
+
.describe('Either a property/label/reltype selector (e.g., {property: "name"}) or a literal value (string, number, boolean, null).');
|
|
66
|
+
exports.WhereSchema = zod_1.z.lazy(() => zod_1.z.union([
|
|
67
|
+
exports.SelectorSchema,
|
|
68
|
+
zod_1.z.object({ not: exports.WhereSchema }),
|
|
69
|
+
zod_1.z.object({ and: zod_1.z.array(exports.WhereSchema) }),
|
|
70
|
+
zod_1.z.object({ or: zod_1.z.array(exports.WhereSchema) }),
|
|
71
|
+
zod_1.z.object({ equal: zod_1.z.tuple([exports.ValueSchema, exports.ValueSchema]) }),
|
|
72
|
+
zod_1.z.object({ lessThan: zod_1.z.tuple([exports.ValueSchema, exports.ValueSchema]) }),
|
|
73
|
+
zod_1.z.object({ lessThanOrEqual: zod_1.z.tuple([exports.ValueSchema, exports.ValueSchema]) }),
|
|
74
|
+
zod_1.z.object({ greaterThan: zod_1.z.tuple([exports.ValueSchema, exports.ValueSchema]) }),
|
|
75
|
+
zod_1.z.object({ greaterThanOrEqual: zod_1.z.tuple([exports.ValueSchema, exports.ValueSchema]) }),
|
|
76
|
+
zod_1.z.object({ contains: zod_1.z.tuple([exports.ValueSchema, exports.ValueSchema]) }),
|
|
77
|
+
zod_1.z.object({ startsWith: zod_1.z.tuple([exports.ValueSchema, exports.ValueSchema]) }),
|
|
78
|
+
zod_1.z.object({ endsWith: zod_1.z.tuple([exports.ValueSchema, exports.ValueSchema]) }),
|
|
79
|
+
zod_1.z.object({ isNull: exports.ValueSchema }),
|
|
80
|
+
]));
|
|
81
|
+
// Schemas that go under the `apply` key.
|
|
82
|
+
exports.CaptionValueSchema = zod_1.z.union([
|
|
83
|
+
zod_1.z.string(),
|
|
84
|
+
zod_1.z.object({ property: zod_1.z.string() }),
|
|
85
|
+
zod_1.z.object({ useType: zod_1.z.literal(true) }),
|
|
86
|
+
]);
|
|
87
|
+
exports.CaptionVariationSchema = zod_1.z.enum(['bold', 'italic', 'underline']);
|
|
88
|
+
exports.NvlCaptionSchema = zod_1.z.object({
|
|
89
|
+
styles: zod_1.z.array(zod_1.z.string()).optional(),
|
|
90
|
+
value: exports.CaptionValueSchema.optional(),
|
|
91
|
+
key: zod_1.z.string().optional(),
|
|
92
|
+
});
|
|
93
|
+
/**
|
|
94
|
+
* OverlayIcon from NVL - icon displayed on top of a graph element
|
|
95
|
+
* @see @neo4j-nvl/base Node.overlayIcon
|
|
96
|
+
*/
|
|
97
|
+
const NvlOverlayIconSchema = zod_1.z.object({
|
|
98
|
+
url: zod_1.z.string(),
|
|
99
|
+
position: zod_1.z.array(zod_1.z.number()).optional(),
|
|
100
|
+
size: zod_1.z.number().optional(),
|
|
101
|
+
});
|
|
102
|
+
exports.StyleSchema = zod_1.z.object({
|
|
103
|
+
captionAlign: zod_1.z.enum(['top', 'bottom', 'center']).optional(),
|
|
104
|
+
captionSize: zod_1.z.number().optional(),
|
|
105
|
+
captions: zod_1.z.array(exports.NvlCaptionSchema).optional(),
|
|
106
|
+
color: zod_1.z.string().optional(),
|
|
107
|
+
icon: zod_1.z.string().optional(),
|
|
108
|
+
overlayIcon: NvlOverlayIconSchema.optional(),
|
|
109
|
+
size: zod_1.z.number().optional(),
|
|
110
|
+
width: zod_1.z.number().optional(),
|
|
111
|
+
});
|
|
112
|
+
exports.StyleRuleSchema = zod_1.z.object({
|
|
113
|
+
match: exports.MatchSchema,
|
|
114
|
+
where: exports.WhereSchema.optional(),
|
|
115
|
+
apply: exports.StyleSchema,
|
|
116
|
+
disabled: zod_1.z.boolean().optional(),
|
|
117
|
+
priority: zod_1.z.number().optional(),
|
|
118
|
+
});
|
|
119
|
+
//# sourceMappingURL=style-types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"style-types.js","sourceRoot":"","sources":["../../../src/styling/style-types.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n *\n * Copyright (c) \"Neo4j\"\n * Neo4j Sweden AB [http://neo4j.com]\n *\n * This file is part of Neo4j.\n *\n * Neo4j is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n */\n\nimport { type Node, type Relationship } from '@neo4j-nvl/base';\nimport { z } from 'zod';\n\n/*\n * Style types defined in Zod, so that we can use the schemas to generate JSON Schema.\n * This is useful for generating documentation, importing and for future integration into editors.\n *\n * Some simple examples of rules:\n * - Match all nodes with label \"Person\" and apply red color\n * { match: { label: \"Person\" }, apply: { color: \"red\" } }\n *\n * - Match all relationships with type \"KNOWS\" which have the property \"name\" equal to \"John\" and apply width 10\n * { match: { reltype: \"KNOWS\" }, where: { equal: [{ property: \"name\" }, \"John\"] }, apply: { width: 10 } }\n */\n\n// Selector schemas. This is what can go under the `match` key.\nconst LabelSelectorSchema = z.object({\n label: z.string().nullable(),\n});\n\nconst ReltypeSelectorSchema = z.object({\n reltype: z.string().nullable(),\n});\n\nconst PropertySelectorSchema = z.object({\n property: z.string(),\n});\n\nexport const SelectorSchema = z.union([\n LabelSelectorSchema,\n ReltypeSelectorSchema,\n PropertySelectorSchema,\n]);\ntype Selector = z.infer<typeof SelectorSchema>;\n\nexport const MatchSchema = z.union([\n LabelSelectorSchema,\n ReltypeSelectorSchema,\n PropertySelectorSchema,\n]);\n\n// Schemas for the values that can go under the `where` key. Starts with defining the literal values.\nexport const CypherValueSchema = z.union([\n z.string(),\n z.number(),\n z.boolean(),\n z.null(),\n]);\nexport type CypherValue = z.infer<typeof CypherValueSchema>;\n\nexport const ValueSchema = z\n .union([SelectorSchema, CypherValueSchema])\n .describe(\n 'Either a property/label/reltype selector (e.g., {property: \"name\"}) or a literal value (string, number, boolean, null).',\n );\nexport type Value = z.infer<typeof ValueSchema>;\n\n/**\n * Where clause - recursive type for conditional expressions\n * The type is manually defined for the export, since zod doesn't support exporting inferred recursive types.\n */\n\nexport type Where =\n // Selector is useful for finding nodes with multiple labels.\n // for example: {match: {label: \"Person\"}, where: {label: \"Actor\"}}\n // matches nodes with label Person AND Actor\n | Selector\n | { not: Where }\n | { and: Where[] }\n | { or: Where[] }\n | { equal: [Value, Value] }\n | { lessThan: [Value, Value] }\n | { lessThanOrEqual: [Value, Value] }\n | { greaterThan: [Value, Value] }\n | { greaterThanOrEqual: [Value, Value] }\n | { contains: [Value, Value] }\n | { startsWith: [Value, Value] }\n | { endsWith: [Value, Value] }\n\n // Null check matching Cypher's IS NULL\n // Returns true/false (not null), making it safe for null checking\n // Use { not: { isNull: ... } } for IS NOT NULL\n // @see https://neo4j.com/docs/cypher-manual/current/values-and-types/working-with-null/\n | { isNull: Value };\n\nexport const WhereSchema: z.ZodType<Where> = z.lazy(() =>\n z.union([\n SelectorSchema,\n z.object({ not: WhereSchema }),\n z.object({ and: z.array(WhereSchema) }),\n z.object({ or: z.array(WhereSchema) }),\n z.object({ equal: z.tuple([ValueSchema, ValueSchema]) }),\n z.object({ lessThan: z.tuple([ValueSchema, ValueSchema]) }),\n z.object({ lessThanOrEqual: z.tuple([ValueSchema, ValueSchema]) }),\n z.object({ greaterThan: z.tuple([ValueSchema, ValueSchema]) }),\n z.object({ greaterThanOrEqual: z.tuple([ValueSchema, ValueSchema]) }),\n z.object({ contains: z.tuple([ValueSchema, ValueSchema]) }),\n z.object({ startsWith: z.tuple([ValueSchema, ValueSchema]) }),\n z.object({ endsWith: z.tuple([ValueSchema, ValueSchema]) }),\n z.object({ isNull: ValueSchema }),\n ]),\n);\n\n// Schemas that go under the `apply` key.\nexport const CaptionValueSchema = z.union([\n z.string(),\n z.object({ property: z.string() }),\n z.object({ useType: z.literal(true) }),\n]);\nexport type CaptionValue = z.infer<typeof CaptionValueSchema>;\n\nexport const CaptionVariationSchema = z.enum(['bold', 'italic', 'underline']);\nexport type CaptionVariation = z.infer<typeof CaptionVariationSchema>;\n\nexport const NvlCaptionSchema = z.object({\n styles: z.array(z.string()).optional(),\n value: CaptionValueSchema.optional(),\n key: z.string().optional(),\n});\nexport type Caption = z.infer<typeof NvlCaptionSchema>;\n\n/**\n * OverlayIcon from NVL - icon displayed on top of a graph element\n * @see @neo4j-nvl/base Node.overlayIcon\n */\nconst NvlOverlayIconSchema = z.object({\n url: z.string(),\n position: z.array(z.number()).optional(),\n size: z.number().optional(),\n});\n\nexport const StyleSchema = z.object({\n captionAlign: z.enum(['top', 'bottom', 'center']).optional(),\n captionSize: z.number().optional(),\n captions: z.array(NvlCaptionSchema).optional(),\n color: z.string().optional(),\n icon: z.string().optional(),\n overlayIcon: NvlOverlayIconSchema.optional(),\n size: z.number().optional(),\n width: z.number().optional(),\n});\nexport type Style = z.infer<typeof StyleSchema>;\n\nexport const StyleRuleSchema = z.object({\n match: MatchSchema,\n where: WhereSchema.optional(),\n apply: StyleSchema,\n disabled: z.boolean().optional(),\n priority: z.number().optional(),\n});\nexport type StyleRule = z.infer<typeof StyleRuleSchema>;\n\n// Evaluated style values that NVL expects\nexport type EvaluatedNvlNodeStyle = Pick<\n Node,\n | 'icon'\n | 'overlayIcon'\n | 'color'\n | 'size'\n | 'captions'\n | 'captionSize'\n | 'captionAlign'\n>;\nexport type EvaluatedNvlRelationshipStyle = Pick<\n Relationship,\n | 'width'\n | 'color'\n | 'captions'\n | 'captionSize'\n | 'captionAlign'\n | 'overlayIcon'\n>;\n"]}
|
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Copyright (c) "Neo4j"
|
|
4
|
+
* Neo4j Sweden AB [http://neo4j.com]
|
|
5
|
+
*
|
|
6
|
+
* This file is part of Neo4j.
|
|
7
|
+
*
|
|
8
|
+
* Neo4j is free software: you can redistribute it and/or modify
|
|
9
|
+
* it under the terms of the GNU General Public License as published by
|
|
10
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
11
|
+
* (at your option) any later version.
|
|
12
|
+
*
|
|
13
|
+
* This program is distributed in the hope that it will be useful,
|
|
14
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
15
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
16
|
+
* GNU General Public License for more details.
|
|
17
|
+
*
|
|
18
|
+
* You should have received a copy of the GNU General Public License
|
|
19
|
+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
20
|
+
*/
|
|
21
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
22
|
+
var t = {};
|
|
23
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
24
|
+
t[p] = s[p];
|
|
25
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
26
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
27
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
28
|
+
t[p[i]] = s[p[i]];
|
|
29
|
+
}
|
|
30
|
+
return t;
|
|
31
|
+
};
|
|
32
|
+
import { calculateDefaultNodeColors } from '@neo4j-devtools/word-color';
|
|
33
|
+
import { tokens } from '@neo4j-ndl/base';
|
|
34
|
+
function compareByPriorityAscending(a, b) {
|
|
35
|
+
return a.priority - b.priority;
|
|
36
|
+
}
|
|
37
|
+
function collectStyleMatchers(styles) {
|
|
38
|
+
const rulesByLabel = new Map();
|
|
39
|
+
const rulesByType = new Map();
|
|
40
|
+
const globalLabelRules = [];
|
|
41
|
+
const globalReltypeRules = [];
|
|
42
|
+
// Handle null/undefined styles gracefully
|
|
43
|
+
if (!styles || styles.length === 0) {
|
|
44
|
+
return {
|
|
45
|
+
globalLabelRules,
|
|
46
|
+
globalReltypeRules,
|
|
47
|
+
rulesByLabel,
|
|
48
|
+
rulesByType,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
const totalRules = styles.length;
|
|
52
|
+
styles.forEach((style, index) => {
|
|
53
|
+
var _a, _b;
|
|
54
|
+
if ((_a = style.disabled) !== null && _a !== void 0 ? _a : false) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (style.priority !== undefined && style.priority < 0) {
|
|
58
|
+
throw new Error(`StyleRule priority must be >= 0, got ${style.priority}. Negative values are reserved for internal use.`);
|
|
59
|
+
}
|
|
60
|
+
// Fill in priority if not set (negative values preserve original order)
|
|
61
|
+
const { priority } = style, rest = __rest(style, ["priority"]);
|
|
62
|
+
const ruleWithPriority = Object.assign(Object.assign({}, rest), { priority: priority !== null && priority !== void 0 ? priority : index - totalRules });
|
|
63
|
+
// style for nodes
|
|
64
|
+
if ('label' in style.match) {
|
|
65
|
+
// if the label is null/undefined, it's a global rule (matches any node)
|
|
66
|
+
if (style.match.label == null) {
|
|
67
|
+
globalLabelRules.push(ruleWithPriority);
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
// Specific label rule
|
|
71
|
+
const existing = (_b = rulesByLabel.get(style.match.label)) !== null && _b !== void 0 ? _b : [];
|
|
72
|
+
rulesByLabel.set(style.match.label, [...existing, ruleWithPriority]);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// style for relationships
|
|
76
|
+
if ('reltype' in style.match) {
|
|
77
|
+
if (style.match.reltype === null) {
|
|
78
|
+
// Global reltype rule (matches any relationship)
|
|
79
|
+
globalReltypeRules.push(ruleWithPriority);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
// Specific reltype rule
|
|
83
|
+
const existing = rulesByType.get(style.match.reltype) || [];
|
|
84
|
+
rulesByType.set(style.match.reltype, [...existing, ruleWithPriority]);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
return {
|
|
89
|
+
globalLabelRules,
|
|
90
|
+
globalReltypeRules,
|
|
91
|
+
rulesByLabel,
|
|
92
|
+
rulesByType,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function extractPropertyValue(prop) {
|
|
96
|
+
switch (prop.type) {
|
|
97
|
+
case 'string':
|
|
98
|
+
return JSON.parse(prop.stringified);
|
|
99
|
+
case 'number':
|
|
100
|
+
case 'integer':
|
|
101
|
+
case 'float':
|
|
102
|
+
return Number(prop.stringified);
|
|
103
|
+
case 'boolean':
|
|
104
|
+
case 'Boolean':
|
|
105
|
+
return prop.stringified === 'true';
|
|
106
|
+
case 'null':
|
|
107
|
+
return null;
|
|
108
|
+
default:
|
|
109
|
+
return prop.stringified;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Evaluates a WHERE clause using three-valued logic matching Cypher semantics.
|
|
114
|
+
*
|
|
115
|
+
* From Neo4j Cypher Manual:
|
|
116
|
+
* - Comparing any value to `null` results in `null`, not `true` or `false`
|
|
117
|
+
* - Accessing a non-existent property returns `null`
|
|
118
|
+
* - In WHERE clauses, only `true` passes; both `false` and `null` exclude the row
|
|
119
|
+
*
|
|
120
|
+
* Three-valued logic rules:
|
|
121
|
+
* - NOT null → null
|
|
122
|
+
* - true AND null → null, false AND null → false
|
|
123
|
+
* - true OR null → true, false OR null → null
|
|
124
|
+
*
|
|
125
|
+
* @see https://neo4j.com/docs/cypher-manual/current/values-and-types/working-with-null/
|
|
126
|
+
*/
|
|
127
|
+
function evaluateWhere(node, where) {
|
|
128
|
+
if (!where) {
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
if ('equal' in where) {
|
|
132
|
+
const [left, right] = where.equal;
|
|
133
|
+
const leftVal = evaluateValue(left, node);
|
|
134
|
+
const rightVal = evaluateValue(right, node);
|
|
135
|
+
// In Cypher, null = null → null (not true)
|
|
136
|
+
if (leftVal === null || rightVal === null) {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
return leftVal === rightVal;
|
|
140
|
+
}
|
|
141
|
+
if ('not' in where) {
|
|
142
|
+
// Cypher: NOT null → null
|
|
143
|
+
const isMatch = evaluateWhere(node, where.not);
|
|
144
|
+
return isMatch === null ? null : !isMatch;
|
|
145
|
+
}
|
|
146
|
+
if ('lessThan' in where) {
|
|
147
|
+
const [left, right] = where.lessThan;
|
|
148
|
+
return safeCompare(left, right, node, (a, b) => a < b);
|
|
149
|
+
}
|
|
150
|
+
if ('lessThanOrEqual' in where) {
|
|
151
|
+
const [left, right] = where.lessThanOrEqual;
|
|
152
|
+
return safeCompare(left, right, node, (a, b) => a <= b);
|
|
153
|
+
}
|
|
154
|
+
if ('greaterThan' in where) {
|
|
155
|
+
const [left, right] = where.greaterThan;
|
|
156
|
+
return safeCompare(left, right, node, (a, b) => a > b);
|
|
157
|
+
}
|
|
158
|
+
if ('greaterThanOrEqual' in where) {
|
|
159
|
+
const [left, right] = where.greaterThanOrEqual;
|
|
160
|
+
return safeCompare(left, right, node, (a, b) => a >= b);
|
|
161
|
+
}
|
|
162
|
+
if ('contains' in where) {
|
|
163
|
+
const [left, right] = where.contains;
|
|
164
|
+
return safeStringCompare(left, right, node, (a, b) => a.includes(b));
|
|
165
|
+
}
|
|
166
|
+
if ('startsWith' in where) {
|
|
167
|
+
const [left, right] = where.startsWith;
|
|
168
|
+
return safeStringCompare(left, right, node, (a, b) => a.startsWith(b));
|
|
169
|
+
}
|
|
170
|
+
if ('endsWith' in where) {
|
|
171
|
+
const [left, right] = where.endsWith;
|
|
172
|
+
return safeStringCompare(left, right, node, (a, b) => a.endsWith(b));
|
|
173
|
+
}
|
|
174
|
+
if ('isNull' in where) {
|
|
175
|
+
// IS NULL returns true/false (never null)
|
|
176
|
+
const value = evaluateValue(where.isNull, node);
|
|
177
|
+
return value === null;
|
|
178
|
+
}
|
|
179
|
+
if ('and' in where) {
|
|
180
|
+
// Cypher three-valued AND:
|
|
181
|
+
// - If any is false → false
|
|
182
|
+
// - Else if any is null → null
|
|
183
|
+
// - Else → true
|
|
184
|
+
let hasNull = false;
|
|
185
|
+
for (const r of where.and) {
|
|
186
|
+
const isMatch = evaluateWhere(node, r);
|
|
187
|
+
if (isMatch === false) {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
if (isMatch === null) {
|
|
191
|
+
hasNull = true;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return hasNull ? null : true;
|
|
195
|
+
}
|
|
196
|
+
if ('or' in where) {
|
|
197
|
+
// Cypher three-valued OR:
|
|
198
|
+
// - If any is true → true
|
|
199
|
+
// - Else if any is null → null
|
|
200
|
+
// - Else → false
|
|
201
|
+
let hasNull = false;
|
|
202
|
+
for (const r of where.or) {
|
|
203
|
+
const isMatch = evaluateWhere(node, r);
|
|
204
|
+
if (isMatch === true) {
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
if (isMatch === null) {
|
|
208
|
+
hasNull = true;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return hasNull ? null : false;
|
|
212
|
+
}
|
|
213
|
+
if ('label' in where) {
|
|
214
|
+
if ('labelsSorted' in node) {
|
|
215
|
+
return where.label === null || node.labelsSorted.includes(where.label);
|
|
216
|
+
}
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
if ('reltype' in where) {
|
|
220
|
+
if ('type' in node) {
|
|
221
|
+
return where.reltype === null || node.type === where.reltype;
|
|
222
|
+
}
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
if ('property' in where) {
|
|
226
|
+
return where.property === null || where.property in node.properties;
|
|
227
|
+
}
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
function evaluateRelStyle(apply, rel) {
|
|
231
|
+
var _a;
|
|
232
|
+
return Object.assign(Object.assign({}, apply), { captions: (_a = apply.captions) === null || _a === void 0 ? void 0 : _a.map((caption) => {
|
|
233
|
+
const { value, styles } = caption;
|
|
234
|
+
if (typeof value === 'string' || value === undefined) {
|
|
235
|
+
return { styles, value };
|
|
236
|
+
}
|
|
237
|
+
if ('useType' in value) {
|
|
238
|
+
return { styles, value: rel.type };
|
|
239
|
+
}
|
|
240
|
+
if ('property' in value) {
|
|
241
|
+
const prop = rel.properties[value.property];
|
|
242
|
+
if (prop === undefined) {
|
|
243
|
+
return { styles, value: '' };
|
|
244
|
+
}
|
|
245
|
+
const resolvedValue = prop.type === 'string'
|
|
246
|
+
? prop.stringified.slice(1, -1)
|
|
247
|
+
: prop.stringified;
|
|
248
|
+
return { styles, value: resolvedValue };
|
|
249
|
+
}
|
|
250
|
+
return { styles, value: rel.id };
|
|
251
|
+
}) });
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Safely compare two values using Cypher's three-valued logic.
|
|
255
|
+
* Returns `null` if either operand is null (matching Cypher semantics).
|
|
256
|
+
*
|
|
257
|
+
* From Neo4j Cypher Manual:
|
|
258
|
+
* "Comparing any value to `null` using `=` or `<>` results in `null`"
|
|
259
|
+
*
|
|
260
|
+
* @see https://neo4j.com/docs/cypher-manual/current/values-and-types/working-with-null/
|
|
261
|
+
*/
|
|
262
|
+
function safeCompare(left, right, graphItem, compareFn) {
|
|
263
|
+
const leftVal = evaluateValue(left, graphItem);
|
|
264
|
+
const rightVal = evaluateValue(right, graphItem);
|
|
265
|
+
if (leftVal === null || rightVal === null) {
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
return compareFn(leftVal, rightVal);
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* String comparison using Cypher semantics.
|
|
272
|
+
* Returns `null` if either operand is null OR not a string.
|
|
273
|
+
*
|
|
274
|
+
* From Neo4j Cypher Manual:
|
|
275
|
+
* "When these operators are applied to non-string values, they return `null`
|
|
276
|
+
* instead of attempting type coercion."
|
|
277
|
+
*
|
|
278
|
+
* @see https://neo4j.com/docs/cypher-manual/current/expressions/predicates/string-operators/
|
|
279
|
+
*/
|
|
280
|
+
function safeStringCompare(left, right, graphItem, compareFn) {
|
|
281
|
+
const leftVal = evaluateValue(left, graphItem);
|
|
282
|
+
const rightVal = evaluateValue(right, graphItem);
|
|
283
|
+
// Return null if either is null or not a string (no type coercion)
|
|
284
|
+
if (leftVal === null ||
|
|
285
|
+
rightVal === null ||
|
|
286
|
+
typeof leftVal !== 'string' ||
|
|
287
|
+
typeof rightVal !== 'string') {
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
return compareFn(leftVal, rightVal);
|
|
291
|
+
}
|
|
292
|
+
function evaluateValue(value, graphItem) {
|
|
293
|
+
if (typeof value === 'object' && value !== null) {
|
|
294
|
+
if ('property' in value) {
|
|
295
|
+
if (value.property === null) {
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
const prop = graphItem.properties[value.property];
|
|
299
|
+
if (prop === undefined) {
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
return extractPropertyValue(prop);
|
|
303
|
+
}
|
|
304
|
+
if ('label' in value) {
|
|
305
|
+
if ('labelsSorted' in graphItem) {
|
|
306
|
+
return (value.label === null || graphItem.labelsSorted.includes(value.label));
|
|
307
|
+
}
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
if ('reltype' in value) {
|
|
311
|
+
if ('type' in graphItem) {
|
|
312
|
+
return (value.reltype === null ||
|
|
313
|
+
graphItem.type === value.reltype);
|
|
314
|
+
}
|
|
315
|
+
return false;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return value;
|
|
319
|
+
}
|
|
320
|
+
// Default caption selection logic
|
|
321
|
+
const captionPriorityOrder = [
|
|
322
|
+
/^name$/i,
|
|
323
|
+
/^title$/i,
|
|
324
|
+
/^label$/i,
|
|
325
|
+
/name$/i,
|
|
326
|
+
/description$/i,
|
|
327
|
+
/^.+/,
|
|
328
|
+
];
|
|
329
|
+
function getDefaultNodeCaption(node) {
|
|
330
|
+
const propertyKeys = Object.keys(node.properties);
|
|
331
|
+
for (const regex of captionPriorityOrder) {
|
|
332
|
+
const matchingKey = propertyKeys.find((key) => regex.test(key));
|
|
333
|
+
if (matchingKey !== undefined) {
|
|
334
|
+
const prop = node.properties[matchingKey];
|
|
335
|
+
if (prop !== undefined) {
|
|
336
|
+
return { value: { property: matchingKey } };
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
if (node.labelsSorted[0] !== undefined) {
|
|
341
|
+
return { value: { useType: true } };
|
|
342
|
+
}
|
|
343
|
+
return { value: { property: node.id } };
|
|
344
|
+
}
|
|
345
|
+
function evaluateNodeStyle(apply, node) {
|
|
346
|
+
var _a;
|
|
347
|
+
return Object.assign(Object.assign({}, apply), { captions: ((_a = apply.captions) !== null && _a !== void 0 ? _a : [getDefaultNodeCaption(node)]).map((caption) => {
|
|
348
|
+
const { value, styles } = caption;
|
|
349
|
+
if (typeof value === 'string' || value === undefined) {
|
|
350
|
+
return { styles, value };
|
|
351
|
+
}
|
|
352
|
+
if ('useType' in value) {
|
|
353
|
+
return { styles, value: node.labelsSorted[0] };
|
|
354
|
+
}
|
|
355
|
+
if ('property' in value) {
|
|
356
|
+
const prop = node.properties[value.property];
|
|
357
|
+
if (prop === undefined) {
|
|
358
|
+
return { styles, value: '' };
|
|
359
|
+
}
|
|
360
|
+
const resolvedValue = prop.type === 'string'
|
|
361
|
+
? prop.stringified.slice(1, -1)
|
|
362
|
+
: prop.stringified;
|
|
363
|
+
return { styles, value: resolvedValue };
|
|
364
|
+
}
|
|
365
|
+
return { styles, value: node.labelsSorted[0] };
|
|
366
|
+
}) });
|
|
367
|
+
}
|
|
368
|
+
function createRelStyleFunction(rules) {
|
|
369
|
+
return (rel) => {
|
|
370
|
+
const style = {};
|
|
371
|
+
// evaluateWhere uses Cypher-style three-valued logic (true/false/null).
|
|
372
|
+
// Only `true` passes; both `false` and `null` (unknown) skip the rule.
|
|
373
|
+
for (const rule of rules) {
|
|
374
|
+
// Check if the rule applies to this relationship
|
|
375
|
+
if ('reltype' in rule.match) {
|
|
376
|
+
const isReltypeMatch = rule.match.reltype === null || rel.type === rule.match.reltype;
|
|
377
|
+
if (isReltypeMatch && evaluateWhere(rel, rule.where) === true) {
|
|
378
|
+
// Merge the style properties (later rules override earlier ones)
|
|
379
|
+
Object.assign(style, rule.apply);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
return evaluateRelStyle(style, rel);
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
export const DEFAULT_REL_COLOR = tokens.palette.neutral['40'];
|
|
387
|
+
const NO_LABEL_FALLBACK_COLOR = tokens.palette.neutral['40'];
|
|
388
|
+
function createByLabelSetFunction(styleMatchers) {
|
|
389
|
+
// Memoize default color by label string (idea 2: avoid recalculating per node)
|
|
390
|
+
const defaultColorCache = new Map();
|
|
391
|
+
// Cache sorted rules by label set key (idea 1: avoid allocation + sort per node)
|
|
392
|
+
const sortedRulesCache = new Map();
|
|
393
|
+
// Cache merged Style for where-free rule sets (idea 3: skip rule iteration entirely)
|
|
394
|
+
const mergedStyleCache = new Map();
|
|
395
|
+
return (node) => {
|
|
396
|
+
var _a;
|
|
397
|
+
const labelSetKey = node.labelsSorted.join('\0');
|
|
398
|
+
// Fast path: if all rules for this label set are where-free,
|
|
399
|
+
// the merged style is identical for every node with the same labels.
|
|
400
|
+
const cachedStyle = mergedStyleCache.get(labelSetKey);
|
|
401
|
+
if (cachedStyle !== undefined) {
|
|
402
|
+
return evaluateNodeStyle(cachedStyle, node);
|
|
403
|
+
}
|
|
404
|
+
// Memoize default color by first label
|
|
405
|
+
const labelForDefaultColor = (_a = node.labelsSorted[0]) !== null && _a !== void 0 ? _a : null;
|
|
406
|
+
let defaultColor;
|
|
407
|
+
if (labelForDefaultColor === null) {
|
|
408
|
+
defaultColor = NO_LABEL_FALLBACK_COLOR;
|
|
409
|
+
}
|
|
410
|
+
else {
|
|
411
|
+
let cached = defaultColorCache.get(labelForDefaultColor);
|
|
412
|
+
if (cached === undefined) {
|
|
413
|
+
cached =
|
|
414
|
+
calculateDefaultNodeColors(labelForDefaultColor).backgroundColor;
|
|
415
|
+
defaultColorCache.set(labelForDefaultColor, cached);
|
|
416
|
+
}
|
|
417
|
+
defaultColor = cached;
|
|
418
|
+
}
|
|
419
|
+
// Cache sorted rules by label set (avoids repeated allocation + sort)
|
|
420
|
+
let sortedRules = sortedRulesCache.get(labelSetKey);
|
|
421
|
+
if (sortedRules === undefined) {
|
|
422
|
+
const matchingRules = [...styleMatchers.globalLabelRules];
|
|
423
|
+
for (const label of node.labelsSorted) {
|
|
424
|
+
const labelRules = styleMatchers.rulesByLabel.get(label);
|
|
425
|
+
if (labelRules) {
|
|
426
|
+
matchingRules.push(...labelRules);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
sortedRules = matchingRules.toSorted(compareByPriorityAscending);
|
|
430
|
+
sortedRulesCache.set(labelSetKey, sortedRules);
|
|
431
|
+
}
|
|
432
|
+
// Evaluate rules, tracking whether all are where-free
|
|
433
|
+
const collectStyles = { color: defaultColor };
|
|
434
|
+
let isAllWhereFree = true;
|
|
435
|
+
for (const rule of sortedRules) {
|
|
436
|
+
if (rule.where !== undefined) {
|
|
437
|
+
isAllWhereFree = false;
|
|
438
|
+
if (evaluateWhere(node, rule.where) === true) {
|
|
439
|
+
Object.assign(collectStyles, rule.apply);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
else {
|
|
443
|
+
Object.assign(collectStyles, rule.apply);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
// When all rules are where-free, the merged style depends only on labels.
|
|
447
|
+
// Cache it so subsequent nodes with the same label set skip rule iteration.
|
|
448
|
+
if (isAllWhereFree) {
|
|
449
|
+
mergedStyleCache.set(labelSetKey, collectStyles);
|
|
450
|
+
}
|
|
451
|
+
return evaluateNodeStyle(collectStyles, node);
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
const DEFAULT_REL_STYLE = {
|
|
455
|
+
match: { reltype: null },
|
|
456
|
+
apply: {
|
|
457
|
+
color: DEFAULT_REL_COLOR,
|
|
458
|
+
captions: [{ value: { useType: true } }],
|
|
459
|
+
},
|
|
460
|
+
};
|
|
461
|
+
export function compileStyleRules({ reltypes, styleRules, }) {
|
|
462
|
+
var _a;
|
|
463
|
+
const styleMatchers = collectStyleMatchers(styleRules);
|
|
464
|
+
// as relationships can only have one type, we can use a map to store the style function for each type
|
|
465
|
+
const byType = new Map();
|
|
466
|
+
// Compile relationship type-based rules
|
|
467
|
+
for (const reltype of reltypes) {
|
|
468
|
+
const rules = (_a = styleMatchers.rulesByType.get(reltype)) !== null && _a !== void 0 ? _a : [];
|
|
469
|
+
byType.set(reltype, createRelStyleFunction([
|
|
470
|
+
DEFAULT_REL_STYLE,
|
|
471
|
+
...styleMatchers.globalReltypeRules,
|
|
472
|
+
...rules,
|
|
473
|
+
]));
|
|
474
|
+
}
|
|
475
|
+
const byLabelSet = createByLabelSetFunction(styleMatchers);
|
|
476
|
+
return { byLabelSet, byType, styleMatchers };
|
|
477
|
+
}
|
|
478
|
+
//# sourceMappingURL=compile-graph-styles.js.map
|