cactuz 0.0.9 → 0.1.1

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/README.md CHANGED
@@ -46,6 +46,8 @@ npm install cactuz
46
46
  <CactusTree width={800} height={600} {nodes} {links} />
47
47
  ```
48
48
 
49
+ ---
50
+
49
51
  ## API Reference
50
52
 
51
53
  ### CactusTree Component
@@ -53,7 +55,7 @@ npm install cactuz
53
55
  #### Props
54
56
 
55
57
  | Prop | Type | Required | Default | Description |
56
- | ---------- | --------- | -------- | ------- | ---------------------------------- |
58
+ | ---------- | --------- |:--------:|:-------:| ---------------------------------- |
57
59
  | `width` | `number` | yes | - | Canvas width in pixels |
58
60
  | `height` | `number` | yes | - | Canvas height in pixels |
59
61
  | `nodes` | `Node[]` | yes | - | Array of hierarchical nodes |
@@ -69,7 +71,7 @@ npm install cactuz
69
71
  interface Node {
70
72
  id: string; // Unique identifier
71
73
  name: string; // Display name
72
- parent: string | null; // Parent node ID (null for root)
74
+ parent: string | null; // Parent node ID
73
75
  weight?: number; // Optional explicit weight
74
76
  }
75
77
  ```
@@ -91,90 +93,107 @@ interface Options {
91
93
  arcSpan?: number; // Arc span in radians (default: 5π/4)
92
94
  sizeGrowthRate?: number; // Size growth rate (default: 0.75)
93
95
  orientation?: number; // Root orientation in radians (default: π/2)
94
- zoom?: number; // Zoom level (default: 1.0)
96
+ zoom?: number; // Layout zoom factor (default: 1.0)
97
+ numLabels?: number; // Number of labels (default: 30)
95
98
  }
96
99
  ```
97
100
 
101
+ **Note:** Negative values create gaps and connect nodes with links.
102
+
98
103
  #### Styles
99
104
 
105
+ The `styles` prop is a nested object with optional groups and an optional `depths` array containing per-depth overrides. Per-depth overrides take precedence over global group values.
106
+
100
107
  ```typescript
101
108
  interface Styles {
102
- // Node appearance
103
- fill?: string; // Node fill color (default: '#efefef')
104
- fillOpacity?: number; // Node fill opacity (default: 1)
105
- stroke?: string; // Node stroke color (default: '#333333')
106
- strokeWidth?: number; // Node stroke width (default: 1)
107
- strokeOpacity?: number; // Node stroke opacity (default: 1)
108
-
109
- // Labels
110
- label?: string; // Label color (default: '#333333')
111
- labelFontFamily?: string; // Label font (default: 'monospace')
112
- labelLink?: string; // Label link line color (default: '#333333')
113
- labelLinkWidth?: number; // Label link line width (default: 0.5)
114
- labelLinkPadding?: number; // Gap between circle and link start in pixels (default: 0)
115
- labelLinkLength?: number; // Length of visible link line in pixels (default: 5)
116
- labelPadding?: number; // Padding around label text in pixels (default: 1)
117
- labelMinFontSize?: number; // Minimum label font size (default: 8)
118
- labelMaxFontSize?: number; // Maximum label font size (default: 14)
119
- labelLimit?: number; // Maximum number of labels to show (default: 30)
120
- // Shows labels for the N largest nodes by radius (all types)
121
- // Set to 0 to hide all labels (except when hovering)
122
-
123
- // Connections
124
- line?: string; // Tree line color (default: '#333333')
125
- lineWidth?: number; // Tree line width (default: 1)
126
- edge?: string; // Link color (default: '#ff6b6b')
127
- edgeWidth?: number; // Link width (default: 1)
128
- edgeOpacity?: number; // Link opacity (default: 0.1, full opacity when hovering leaf nodes with links)
129
-
130
- // Hover effects
131
- highlight?: boolean; // Enable hover effects (default: true)
132
- highlightFill?: string; // Hover fill color (default: '#ffcc99')
133
- highlightStroke?: string; // Hover stroke color (default: '#ff6600')
134
-
135
- // Depth-specific styling
136
- depths?: DepthStyle[]; // Per-depth style overrides
109
+ node?: {
110
+ fillColor?: string;
111
+ fillOpacity?: number;
112
+ strokeColor?: string;
113
+ strokeOpacity?: number;
114
+ strokeWidth?: number;
115
+ highlight?: {
116
+ fillColor?: string;
117
+ fillOpacity?: number;
118
+ strokeColor?: string;
119
+ strokeOpacity?: number;
120
+ strokeWidth?: number;
121
+ }
122
+ };
123
+ edge?: {
124
+ strokeColor?: string;
125
+ strokeOpacity?: number;
126
+ strokeWidth?: number;
127
+ highlight?: {
128
+ strokeColor?: string;
129
+ strokeOpacity?: number;
130
+ strokeWidth?: number;
131
+ }
132
+ };
133
+ label?: {
134
+ textColor?: string;
135
+ textOpacity?: number;
136
+ fontFamily?: string;
137
+ minFontSize?: number;
138
+ maxFontSize?: number;
139
+ fontWeight?: string;
140
+ padding?: number;
141
+ link?: {
142
+ strokeColor?: string;
143
+ strokeOpacity?: number;
144
+ strokeWidth?: number;
145
+ padding?: number;
146
+ };
147
+ };
148
+ line?: {
149
+ strokeColor?: string;
150
+ strokeOpacity?: number;
151
+ strokeWidth?: number;
152
+ };
153
+ depths?: DepthStyle[]; // Per-depth overrides
137
154
  }
138
155
  ```
139
156
 
140
157
  #### Depth-Specific Styling
141
158
 
159
+ Each item in `styles.depths` must include a `depth` integer.
160
+
142
161
  ```typescript
143
162
  interface DepthStyle {
144
- depth: number; // Tree depth (0 = root, -1 = leaves)
145
- fill?: string;
146
- fillOpacity?: number;
147
- stroke?: string;
148
- strokeWidth?: number;
149
- strokeOpacity?: number;
150
- label?: string;
151
- labelFontFamily?: string;
152
- line?: string;
153
- lineWidth?: number;
154
- highlight?: boolean;
155
- highlightFill?: string;
156
- highlightStroke?: string;
163
+ depth: number; // 0 = root, positive = deeper levels, negative values may be used for leaf-oriented overrides (implementation uses mapping for negative depths)
164
+ node?: {
165
+ fillColor?: string;
166
+ fillOpacity?: number;
167
+ strokeColor?: string;
168
+ strokeOpacity?: number;
169
+ strokeWidth?: number;
170
+ };
171
+ label?: {
172
+ textColor?: string;
173
+ textOpacity?: number;
174
+ fontFamily?: string;
175
+ minFontSize?: number;
176
+ maxFontSize?: number;
177
+ fontWeight?: string;
178
+ padding?: number;
179
+ link?: {
180
+ strokeColor?: string;
181
+ strokeOpacity?: number;
182
+ strokeWidth?: number;
183
+ padding?: number;
184
+ };
185
+ };
186
+ line?: {
187
+ strokeColor?: string;
188
+ strokeOpacity?: number;
189
+ strokeWidth?: number;
190
+ };
157
191
  }
158
192
  ```
159
193
 
160
194
  ### CactusLayout Class
161
195
 
162
- For custom implementations or non-Svelte environments, you can use the layout algorithm directly:
163
-
164
- ```javascript
165
- import { CactusLayout } from 'cactuz';
166
-
167
- const layout = new CactusLayout(
168
- 800, // width
169
- 600, // height
170
- 1.0, // zoom
171
- 0.5, // overlap
172
- Math.PI, // arcSpan
173
- 0.75, // sizeGrowthRate
174
- );
175
-
176
- const nodeData = layout.render(nodes, 400, 300, -Math.PI / 2);
177
- ```
196
+ For non-Svelte usage you can use the layout algorithm directly.
178
197
 
179
198
  #### Constructor
180
199
 
@@ -222,103 +241,97 @@ interface NodeData {
222
241
  }
223
242
  ```
224
243
 
244
+ ```javascript
245
+ import { CactusLayout } from 'cactuz';
246
+
247
+ const layout = new CactusLayout(
248
+ 800, // width
249
+ 600, // height
250
+ 1.0, // zoom
251
+ 0.5, // overlap
252
+ Math.PI, // arcSpan
253
+ 0.75, // sizeGrowthRate
254
+ );
255
+
256
+ const nodeData = layout.render(nodes, 400, 300, -Math.PI / 2);
257
+ ```
258
+
225
259
  ## Advanced Usage
226
260
 
227
- ### Custom Styling Example
261
+ ### Global and Depth-based Styling Example
262
+
263
+ This example demonstrates styles and per-depth overrides. It shows a global style for general appearance, then customizes roots and leaves via `depths`.
228
264
 
229
265
  ```svelte
230
266
  <script>
231
267
  import { CactusTree } from 'cactuz';
232
268
 
233
269
  const styles = {
234
- fill: '#f0f8ff',
235
- stroke: '#4682b4',
236
- strokeWidth: 2,
237
- label: '#2c3e50',
238
- labelFontFamily: 'Arial, sans-serif',
239
- labelLimit: 100,
240
- edge: '#e74c3c',
241
- edgeWidth: 3,
242
- highlightFill: '#ffd700',
243
- highlightStroke: '#ff8c00',
270
+ node: {
271
+ fillColor: '#f0f8ff',
272
+ strokeColor: '#4682b4',
273
+ strokeWidth: 2,
274
+ },
275
+ edge: {
276
+ strokeColor: '#e74c3c',
277
+ strokeOpacity: 0.2,
278
+ strokeWidth: 2,
279
+ },
280
+ label: {
281
+ textColor: '#2c3e50',
282
+ textOpacity: 1,
283
+ fontFamily: 'Arial, sans-serif',
284
+ minFontSize: 8,
285
+ maxFontSize: 16,
286
+ padding: 2,
287
+ link: {
288
+ strokeColor: '#aaaaaa',
289
+ strokeOpacity: 1,
290
+ strokeWidth: 0.6,
291
+ padding: 1,
292
+ },
293
+ },
294
+ line: {
295
+ strokeColor: '#cccccc',
296
+ strokeOpacity: 1,
297
+ strokeWidth: 1,
298
+ },
299
+ node: {
300
+ highlight: {
301
+ fillColor: '#ffd700',
302
+ strokeColor: '#ff8c00',
303
+ },
304
+ },
244
305
  depths: [
245
306
  {
246
- depth: 0, // Root styling
247
- fill: '#2c3e50',
248
- stroke: '#ecf0f1',
249
- label: '#ecf0f1',
307
+ depth: 0, // root
308
+ node: { fillColor: '#2c3e50', strokeColor: '#ecf0f1' },
309
+ label: { textColor: '#ecf0f1' },
250
310
  },
251
311
  {
252
- depth: -1, // Leaf styling
253
- fill: '#e74c3c',
254
- stroke: '#c0392b',
255
- label: '#2c3e50',
312
+ depth: -1, // leaves
313
+ node: { fillColor: '#e74c3c', strokeColor: '#c0392b' },
314
+ label: { textColor: '#ffffff' },
256
315
  },
257
316
  ],
258
317
  };
259
318
  </script>
260
319
 
261
- <CactusTree {width} {height} {nodes} {links} {styles} />
262
- ```
263
-
264
- ### Interactive Features
265
-
266
- The component provides several interactive features:
267
-
268
- - **Pan**: Click and drag to pan the visualization
269
- - **Zoom**: Use mouse wheel to zoom in/out
270
- - **Hover**: Hover over nodes to highlight connections
271
-
272
- ## Examples
273
-
274
- ### Basic Tree
275
-
276
- ```svelte
277
- <CactusTree
278
- width={600}
279
- height={400}
280
- nodes={[
281
- { id: 'a', name: 'Root', parent: null },
282
- { id: 'b', name: 'Branch 1', parent: 'a' },
283
- { id: 'c', name: 'Branch 2', parent: 'a' },
284
- { id: 'd', name: 'Leaf 1', parent: 'b' },
285
- { id: 'e', name: 'Leaf 2', parent: 'b' },
286
- ]}
287
- />
320
+ <CactusTree width={800} height={600} {nodes} {links} styles={styles} />
288
321
  ```
289
322
 
290
- ### With Edge Bundling
323
+ ### Negative Overlap and Link Filtering
291
324
 
292
325
  ```svelte
293
326
  <CactusTree
294
327
  width={800}
295
328
  height={600}
296
329
  {nodes}
297
- links={[
298
- { source: 'leaf1', target: 'leaf3' },
299
- { source: 'leaf2', target: 'leaf4' },
300
- ]}
301
- styles={{
302
- edge: '#3498db',
303
- edgeOpacity: 0.3,
304
- edgeWidth: 2,
305
- }}
306
- />
307
- ```
308
-
309
- ### Negative Overlap and Link Filtering
310
-
311
-
312
- ```svelte
313
- <CactusTree
314
- width={1000}
315
- height={800}
316
- {nodes}
317
330
  options={{
318
- overlap: -1.1, // Gaps between nodes
319
- arcSpan: 2 * Math.PI, // Full circle layout
320
- orientation: 7 / 9 * Math.PI, // Leftward growth
321
- zoom: 0.7 // Zoom out to see full chart
331
+ overlap: -1.1, // Gaps between nodes
332
+ arcSpan: 2 * Math.PI, // Full circle layout (radians)
333
+ orientation: (7 / 9) * Math.PI, // Root orientation (radians)
334
+ zoom: 0.7
322
335
  }}
323
336
  />
324
337
  ```
@@ -327,5 +340,4 @@ The component provides several interactive features:
327
340
  <img src="https://github.com/spren9er/cactuz/blob/main/docs/images/cactus_tree_advanced.png?raw=true" alt="cactus-tree-advanced" width="75%" height="75%">
328
341
  </div>
329
342
 
330
- For a negative overlap parameter, nodes are connected by links.
331
- Also, when hovering over leaf nodes, only the links connected to that node are shown, while all other links are hidden. This allows for better readability in dense visualizations.
343
+ For a negative overlap parameter, nodes are connected by links. Also, when hovering over leaf nodes, only the links connected to that node are shown, while all other links are hidden. This allows for better readability in dense visualizations.
@@ -16,7 +16,107 @@
16
16
  buildLookupMaps,
17
17
  } from './cactusTree/layoutUtils.js';
18
18
 
19
- /** @type {{ width: number, height: number, nodes: Array<{id: string, name: string, parent: string|null, weight?: number}>, links?: Array<{source: string, target: string}>, options?: {overlap?: number, arcSpan?: number, sizeGrowthRate?: number, orientation?: number, zoom?: number}, styles?: {fill?: string, fillOpacity?: number, stroke?: string, strokeWidth?: number, strokeOpacity?: number, label?: string, labelFontFamily?: string, labelLink?: string, labelLinkWidth?: number, labelLinkPadding?: number, labelLinkLength?: number, labelPadding?: number, labelMinFontSize?: number, labelMaxFontSize?: number, lineWidth?: number, line?: string, edge?: string, edgeWidth?: number, edgeOpacity?: number, highlightFill?: string, highlightStroke?: string, highlight?: boolean, labelLimit?: number, depths?: Array<{depth: number, fill?: string, fillOpacity?: number, stroke?: string, strokeWidth?: number, strokeOpacity?: number, label?: string, labelFontFamily?: string, labelLink?: string, labelLinkWidth?: number, lineWidth?: number, line?: string, highlightFill?: string, highlightStroke?: string, highlight?: boolean}>}, pannable?: boolean, zoomable?: boolean }} */
19
+ /** @type {{
20
+ width: number,
21
+ height: number,
22
+ nodes: Array<{ id: string, name: string, parent: string | null, weight?: number }>,
23
+ links?: Array<{ source: string, target: string }>,
24
+ options?: {
25
+ overlap?: number,
26
+ arcSpan?: number,
27
+ sizeGrowthRate?: number,
28
+ orientation?: number,
29
+ zoom?: number,
30
+ numLabels?: number
31
+ },
32
+ styles?: {
33
+ node?: {
34
+ fillColor?: string,
35
+ fillOpacity?: number,
36
+ strokeColor?: string,
37
+ strokeOpacity?: number,
38
+ strokeWidth?: number,
39
+ highlight?: {
40
+ fillColor?: string,
41
+ fillOpacity?: number,
42
+ strokeColor?: string,
43
+ strokeOpacity?: number,
44
+ strokeWidth?: number,
45
+ }
46
+ },
47
+ edge?: {
48
+ strokeColor?: string,
49
+ strokeOpacity?: number,
50
+ strokeWidth?: number,
51
+ highlight?: {
52
+ strokeColor?: string,
53
+ strokeOpacity?: number,
54
+ strokeWidth?: number
55
+ }
56
+ },
57
+ label?: {
58
+ textColor?: string,
59
+ textOpacity?: number,
60
+ fontFamily?: string,
61
+ minFontSize?: number,
62
+ maxFontSize?: number,
63
+ fontWeight?: string,
64
+ padding?: number,
65
+ link?: {
66
+ strokeColor?: string,
67
+ strokeOpacity?: number,
68
+ strokeWidth?: number,
69
+ length?: number
70
+ padding?: number,
71
+ }
72
+ },
73
+ line?: {
74
+ strokeColor?: string,
75
+ strokeOpacity?: number,
76
+ strokeWidth?: number
77
+ },
78
+ depths?: Array<{
79
+ depth: number,
80
+ node?: {
81
+ fillColor?: string,
82
+ fillOpacity?: number,
83
+ strokeColor?: string,
84
+ strokeOpacity?: number,
85
+ strokeWidth?: number,
86
+ highlight?: {
87
+ fillColor?: string,
88
+ fillOpacity?: number,
89
+ strokeColor?: string,
90
+ strokeOpacity?: number,
91
+ strokeWidth?: number,
92
+ }
93
+ },
94
+ label?: {
95
+ textColor?: string,
96
+ textOpacity?: number,
97
+ fontFamily?: string,
98
+ minFontSize?: number,
99
+ maxFontSize?: number,
100
+ fontWeight?: string,
101
+ padding?: number,
102
+ link?: {
103
+ strokeColor?: string,
104
+ strokeOpacity?: number,
105
+ strokeWidth?: number,
106
+ length?: number
107
+ padding?: number,
108
+ }
109
+ },
110
+ line?: {
111
+ strokeColor?: string,
112
+ strokeOpacity?: number,
113
+ strokeWidth?: number
114
+ }
115
+ }>
116
+ },
117
+ pannable?: boolean,
118
+ zoomable?: boolean
119
+ }} */
20
120
  let {
21
121
  width,
22
122
  height,
@@ -34,37 +134,81 @@
34
134
  sizeGrowthRate: 0.75,
35
135
  orientation: Math.PI / 2,
36
136
  zoom: 1.0,
137
+ numLabels: 30,
37
138
  };
38
139
 
39
140
  const defaultStyle = {
40
- fill: '#efefef',
41
- fillOpacity: 1,
42
- stroke: '#333333',
43
- strokeWidth: 1,
44
- strokeOpacity: 1,
45
- label: '#333333',
46
- labelFontFamily: 'monospace',
47
- labelLink: '#333333',
48
- labelLinkWidth: 0.5,
49
- labelLinkPadding: 0,
50
- labelLinkLength: 5,
51
- labelPadding: 1,
52
- labelMinFontSize: 8,
53
- labelMaxFontSize: 14,
54
- lineWidth: 1,
55
- line: '#333333',
56
- edge: '#ff6b6b',
57
- edgeWidth: 1,
58
- edgeOpacity: 0.1,
59
- highlightFill: '#ffcc99',
60
- highlightStroke: '#ff6600',
61
- highlight: true,
62
- labelLimit: 30,
141
+ node: {
142
+ fillColor: '#efefef',
143
+ fillOpacity: 1,
144
+ strokeColor: '#333333',
145
+ strokeOpacity: 1,
146
+ strokeWidth: 1,
147
+ highlight: {
148
+ fillColor: '#ffcc99',
149
+ fillOpacity: 1,
150
+ strokeColor: '#ff6600',
151
+ strokeOpacity: 1,
152
+ strokeWidth: 1,
153
+ enabled: true,
154
+ },
155
+ },
156
+ edge: {
157
+ strokeColor: '#ff6b6b',
158
+ strokeOpacity: 0.1,
159
+ strokeWidth: 1,
160
+ highlight: {
161
+ strokeColor: '#ff6600',
162
+ strokeOpacity: 1,
163
+ strokeWidth: 1,
164
+ },
165
+ },
166
+ label: {
167
+ textColor: '#333333',
168
+ textOpacity: 1,
169
+ fontFamily: 'monospace',
170
+ minFontSize: 8,
171
+ maxFontSize: 14,
172
+ fontWeight: 'normal',
173
+ padding: 1,
174
+ link: {
175
+ strokeColor: '#333333',
176
+ strokeOpacity: 1,
177
+ strokeWidth: 0.5,
178
+ padding: 0,
179
+ },
180
+ },
181
+ line: {
182
+ strokeColor: '#333333',
183
+ strokeOpacity: 1,
184
+ strokeWidth: 1,
185
+ },
186
+ depths: [],
63
187
  };
64
188
 
65
189
  // Merge options and styles
66
190
  const mergedOptions = $derived({ ...defaultOptions, ...options });
67
- const mergedStyle = $derived({ ...defaultStyle, ...styles });
191
+ const mergedStyle = $derived({
192
+ node: {
193
+ ...(defaultStyle.node || {}),
194
+ ...(styles.node || {}),
195
+ highlight: {
196
+ ...((defaultStyle.node && defaultStyle.node.highlight) || {}),
197
+ ...((styles.node && styles.node.highlight) || {}),
198
+ },
199
+ },
200
+ edge: {
201
+ ...(defaultStyle.edge || {}),
202
+ ...(styles.edge || {}),
203
+ highlight: {
204
+ ...((defaultStyle.edge && defaultStyle.edge.highlight) || {}),
205
+ ...((styles.edge && styles.edge.highlight) || {}),
206
+ },
207
+ },
208
+ label: { ...(defaultStyle.label || {}), ...(styles.label || {}) },
209
+ line: { ...(defaultStyle.line || {}), ...(styles.line || {}) },
210
+ depths: styles.depths ?? defaultStyle.depths,
211
+ });
68
212
 
69
213
  // Canvas and context
70
214
  /** @type {HTMLCanvasElement} */
@@ -78,8 +222,6 @@
78
222
  let nodeIdToRenderedNodeMap = new SvelteMap();
79
223
  let leafNodes = new SvelteSet();
80
224
  let negativeDepthNodes = new SvelteMap();
81
- // @ts-ignore - Used internally by layout utilities
82
- let nodeIdToNodeMap = new SvelteMap();
83
225
  let depthStyleCache = new SvelteMap();
84
226
  let hierarchicalPathCache = new SvelteMap();
85
227
  let parentToChildrenNodeMap = new SvelteMap();
@@ -106,7 +248,7 @@
106
248
  /** @type {number|null} */
107
249
  let animationFrameId = null;
108
250
 
109
- // Calculate layout and build lookup maps (called on each render like original)
251
+ // Calculate layout and build lookup maps (called on each render)
110
252
  function calculateLayoutAndMaps() {
111
253
  if (!nodes?.length) {
112
254
  renderedNodes = [];
@@ -133,8 +275,6 @@
133
275
  nodeIdToRenderedNodeMap = lookupMaps.nodeIdToRenderedNodeMap;
134
276
  leafNodes = lookupMaps.leafNodes;
135
277
  negativeDepthNodes = lookupMaps.negativeDepthNodes;
136
- nodeIdToNodeMap = lookupMaps.nodeIdToNodeMap;
137
- void nodeIdToNodeMap; // Prevent unused variable warning
138
278
  depthStyleCache = lookupMaps.depthStyleCache;
139
279
  hierarchicalPathCache = lookupMaps.hierarchicalPathCache;
140
280
  parentToChildrenNodeMap = lookupMaps.parentToChildrenNodeMap;
@@ -161,7 +301,7 @@
161
301
  function draw() {
162
302
  if (!canvas || !ctx) return;
163
303
 
164
- // Clear and set up canvas context (like original)
304
+ // Clear and set up canvas context
165
305
  ctx.clearRect(0, 0, canvas.width, canvas.height);
166
306
  ctx.save();
167
307
 
@@ -176,6 +316,7 @@
176
316
  mergedStyle,
177
317
  depthStyleCache,
178
318
  mergedOptions.overlap,
319
+ negativeDepthNodes,
179
320
  );
180
321
 
181
322
  // Draw nodes
@@ -210,6 +351,7 @@
210
351
  mergedStyle,
211
352
  depthStyleCache,
212
353
  negativeDepthNodes,
354
+ mergedOptions.numLabels,
213
355
  panX,
214
356
  panY,
215
357
  );