@splunk/visualizations 28.4.0 → 28.5.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.
Files changed (160) hide show
  1. package/Area.config.js +28 -7
  2. package/Area.config.js.map +2 -2
  3. package/Area.js +106 -39
  4. package/Area.js.map +2 -2
  5. package/Bar.config.js +28 -7
  6. package/Bar.config.js.map +2 -2
  7. package/Bar.js +106 -39
  8. package/Bar.js.map +2 -2
  9. package/Bubble.config.js +4 -4
  10. package/Bubble.config.js.map +2 -2
  11. package/Bubble.js +78 -30
  12. package/Bubble.js.map +2 -2
  13. package/CHANGELOG.external.md +89 -0
  14. package/ChoroplethSvg.js +74 -26
  15. package/ChoroplethSvg.js.map +2 -2
  16. package/Column.config.js +28 -7
  17. package/Column.config.js.map +2 -2
  18. package/Column.js +106 -39
  19. package/Column.js.map +2 -2
  20. package/Ellipse/config.d.ts.map +1 -1
  21. package/Ellipse.config.js +14 -5
  22. package/Ellipse.config.js.map +4 -4
  23. package/Ellipse.js +130 -73
  24. package/Ellipse.js.map +4 -4
  25. package/Events.js +74 -26
  26. package/Events.js.map +2 -2
  27. package/FillerGauge.js +234 -47
  28. package/FillerGauge.js.map +2 -2
  29. package/Image.js +74 -26
  30. package/Image.js.map +2 -2
  31. package/Line/lineUtils.d.ts.map +1 -1
  32. package/Line/mockdata/LineData.d.ts +95 -0
  33. package/Line/mockdata/LineData.d.ts.map +1 -1
  34. package/Line.config.js +28 -7
  35. package/Line.config.js.map +2 -2
  36. package/Line.js +122 -41
  37. package/Line.js.map +2 -2
  38. package/LinkGraph.js +74 -26
  39. package/LinkGraph.js.map +2 -2
  40. package/Map/PureMap/BubbleLayer.d.ts.map +1 -1
  41. package/Map/PureMap/ClusterLayer.d.ts.map +1 -1
  42. package/Map/PureMap/MapUtils.d.ts +2 -2
  43. package/Map/PureMap/MapUtils.d.ts.map +1 -1
  44. package/Map/PureMap/PureMap.d.ts.map +1 -1
  45. package/Map.js +111 -37
  46. package/Map.js.map +2 -2
  47. package/Markdown/config.d.ts.map +1 -1
  48. package/Markdown.config.js +14 -5
  49. package/Markdown.config.js.map +4 -4
  50. package/Markdown.js +91 -34
  51. package/Markdown.js.map +4 -4
  52. package/MarkerGauge.js +243 -56
  53. package/MarkerGauge.js.map +4 -4
  54. package/NetworkGraph/PureNetworkGraph/PureNetworkGraph.d.ts.map +1 -1
  55. package/NetworkGraph/PureNetworkGraph/components/Node.d.ts.map +1 -1
  56. package/NetworkGraph/PureNetworkGraph/components/ZoomControls.d.ts.map +1 -1
  57. package/NetworkGraph/PureNetworkGraph/utils/dragUtils.d.ts +78 -0
  58. package/NetworkGraph/PureNetworkGraph/utils/dragUtils.d.ts.map +1 -0
  59. package/NetworkGraph/PureNetworkGraph/utils/networkGraphUtils.d.ts +1 -2
  60. package/NetworkGraph/PureNetworkGraph/utils/networkGraphUtils.d.ts.map +1 -1
  61. package/NetworkGraph.config.js.map +2 -2
  62. package/NetworkGraph.js +365 -105
  63. package/NetworkGraph.js.map +4 -4
  64. package/PERFORMANCE.md +23 -0
  65. package/ParallelCoordinates/ParallelCoordinates.d.ts +1 -46
  66. package/ParallelCoordinates/ParallelCoordinates.d.ts.map +1 -1
  67. package/ParallelCoordinates/PureParallelCoordinates/PureParallelCoordinates.d.ts +12 -58
  68. package/ParallelCoordinates/PureParallelCoordinates/PureParallelCoordinates.d.ts.map +1 -1
  69. package/ParallelCoordinates/PureParallelCoordinates/src/ParallelCoordinatesChart.d.ts +1 -0
  70. package/ParallelCoordinates/PureParallelCoordinates/src/ParallelCoordinatesChart.d.ts.map +1 -1
  71. package/ParallelCoordinates.js +150 -153
  72. package/ParallelCoordinates.js.map +4 -4
  73. package/Pie/Pie.d.ts.map +1 -1
  74. package/Pie/config.d.ts.map +1 -1
  75. package/Pie/pieUtils.d.ts +4 -0
  76. package/Pie/pieUtils.d.ts.map +1 -1
  77. package/Pie.config.js +16 -0
  78. package/Pie.config.js.map +2 -2
  79. package/Pie.js +118 -35
  80. package/Pie.js.map +2 -2
  81. package/Punchcard.js +74 -26
  82. package/Punchcard.js.map +2 -2
  83. package/Rectangle/config.d.ts.map +1 -1
  84. package/Rectangle.config.js +14 -5
  85. package/Rectangle.config.js.map +4 -4
  86. package/Rectangle.js +92 -35
  87. package/Rectangle.js.map +4 -4
  88. package/Sankey/PureSankey/PureSankey.d.ts +26 -72
  89. package/Sankey/PureSankey/PureSankey.d.ts.map +1 -1
  90. package/Sankey/PureSankey/SankeyLink.d.ts +3 -19
  91. package/Sankey/PureSankey/SankeyLink.d.ts.map +1 -1
  92. package/Sankey/PureSankey/SankeyNode.d.ts +3 -20
  93. package/Sankey/PureSankey/SankeyNode.d.ts.map +1 -1
  94. package/Sankey/PureSankey/SankeyTooltip.d.ts +3 -24
  95. package/Sankey/PureSankey/SankeyTooltip.d.ts.map +1 -1
  96. package/Sankey.js +270 -336
  97. package/Sankey.js.map +2 -2
  98. package/Scatter.config.js +168 -147
  99. package/Scatter.config.js.map +4 -4
  100. package/Scatter.js +246 -179
  101. package/Scatter.js.map +4 -4
  102. package/SingleValue.js +96 -75
  103. package/SingleValue.js.map +2 -2
  104. package/SingleValueIcon/config.d.ts.map +1 -1
  105. package/SingleValueIcon.config.js +14 -5
  106. package/SingleValueIcon.config.js.map +4 -4
  107. package/SingleValueIcon.js +118 -76
  108. package/SingleValueIcon.js.map +4 -4
  109. package/SingleValueRadial/PureSingleValueRadial/GaugeGraph.d.ts.map +1 -1
  110. package/SingleValueRadial.js +95 -76
  111. package/SingleValueRadial.js.map +2 -2
  112. package/Table/Table.d.ts.map +1 -1
  113. package/Table/hooks.d.ts +2 -0
  114. package/Table/hooks.d.ts.map +1 -1
  115. package/Table.js +95 -49
  116. package/Table.js.map +2 -2
  117. package/Timeline/PureTimeline/PureTimeline.d.ts.map +1 -1
  118. package/Timeline/PureTimeline/src/TimelinePlugin.d.ts.map +1 -1
  119. package/Timeline/PureTimeline/src/TimelineYAxis.d.ts.map +1 -1
  120. package/Timeline/PureTimeline/src/utils/timelineUtils.d.ts +2 -1
  121. package/Timeline/PureTimeline/src/utils/timelineUtils.d.ts.map +1 -1
  122. package/Timeline.js +154 -54
  123. package/Timeline.js.map +2 -2
  124. package/common/GaugeMajorTicks.js +150 -13
  125. package/common/GaugeMajorTicks.js.map +4 -4
  126. package/common/GaugeValueMarker.js +84 -17
  127. package/common/GaugeValueMarker.js.map +4 -4
  128. package/common/OptionsDoc.js.map +1 -1
  129. package/common/OverviewDoc.d.ts.map +1 -1
  130. package/common/OverviewDoc.js +2 -2
  131. package/common/OverviewDoc.js.map +2 -2
  132. package/common/TimeSeriesChartWrapper.d.ts.map +1 -1
  133. package/common/TimeSeriesChartWrapper.js +72 -24
  134. package/common/TimeSeriesChartWrapper.js.map +2 -2
  135. package/common/chartingUtils.d.ts +9 -3
  136. package/common/chartingUtils.d.ts.map +1 -1
  137. package/common/chartingUtils.js +21 -16
  138. package/common/chartingUtils.js.map +2 -2
  139. package/common/editorConfig/AxesChartDataConfig.d.ts +3 -3
  140. package/common/editorConfig/AxesChartDataConfig.d.ts.map +1 -1
  141. package/common/editorConfig/AxesChartDataDisplay.d.ts +4 -0
  142. package/common/editorConfig/AxesChartDataDisplay.d.ts.map +1 -1
  143. package/common/editorConfig/AxesChartLegend.d.ts.map +1 -1
  144. package/common/gaugeUtils.d.ts +37 -0
  145. package/common/gaugeUtils.d.ts.map +1 -1
  146. package/common/gaugeUtils.js +152 -2
  147. package/common/gaugeUtils.js.map +4 -4
  148. package/common/hocs/mockdata/trellisData.d.ts +119 -0
  149. package/common/hocs/mockdata/trellisData.d.ts.map +1 -1
  150. package/common/interfaces/Overview.d.ts +1 -0
  151. package/common/interfaces/Overview.d.ts.map +1 -1
  152. package/common/interfaces/VizConfig.d.ts +2 -1
  153. package/common/interfaces/VizConfig.d.ts.map +1 -1
  154. package/common/utils/mapUtils.js.map +1 -1
  155. package/common/utils/trellisUtils.d.ts.map +1 -1
  156. package/common/utils/trellisUtils.js +4 -6
  157. package/common/utils/trellisUtils.js.map +2 -2
  158. package/common/withDashboardViz.js +74 -26
  159. package/common/withDashboardViz.js.map +2 -2
  160. package/package.json +36 -36
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["src/NetworkGraph/config.ts", "esm-externals:@splunk/ui-utils/i18n", "esm-externals:@splunk/visualization-color-palettes/editors/PresetPalettes", "esm-externals:@splunk/visualization-icons", "esm-externals:@splunk/visualization-icons/placeholders", "esm-externals:@splunk/visualization-schemas", "esm-externals:@splunk/themes", "esm-externals:lodash", "esm-externals:@splunk/visualizations-shared/configUtils", "src/common/utils/configUtils.ts", "src/NetworkGraph/PureNetworkGraph/utils/networkGraphShared.ts", "esm-externals:@splunk/themes/pick", "esm-externals:@splunk/themes/variables", "src/common/ThemedDefaults.tsx", "src/common/editorConfig/BackgroundColor.ts", "esm-externals:dagre", "esm-externals:@splunk/visualizations-shared/iconUtils", "esm-externals:@splunk/visualization-encoding/utils/colorUtils", "src/common/utils/textUtils.ts", "esm-externals:d3-force", "esm-externals:d3-quadtree", "src/NetworkGraph/PureNetworkGraph/utils/networkGraphUtils.ts"],
4
- "sourcesContent": ["import { _ } from '@splunk/ui-utils/i18n';\nimport { defaultPalettesConfig } from '@splunk/visualization-color-palettes/editors/PresetPalettes';\nimport { NetworkGraph as NetworkGraphIcon } from '@splunk/visualization-icons';\nimport { NetworkGraph as NetworkGraphPlaceholderIcon } from '@splunk/visualization-icons/placeholders';\nimport { networkGraphOptionsSchema as optionsSchema } from '@splunk/visualization-schemas';\nimport { variables } from '@splunk/themes';\nimport { DataContract } from '../common/interfaces/DataContract';\nimport { DefaultContext } from '../common/interfaces/DefaultContext';\nimport { EditorConfig } from '../common/interfaces/Editor';\nimport { ThemedDefaults } from '../common/interfaces/ThemedDefaults';\nimport { VizBehavior } from '../common/interfaces/VizBehavior';\nimport { VizCategory } from '../common/interfaces/VizCategory';\nimport { VizConfig } from '../common/interfaces/VizConfig';\nimport { VizSize } from '../common/interfaces/VizSize';\nimport { enhanceConfig } from '../common/utils/configUtils';\nimport { DEFAULT_CHART_HEIGHT, DEFAULT_CHART_WIDTH } from './PureNetworkGraph/utils/networkGraphShared';\nimport { defaultBackgroundColor } from '../common/ThemedDefaults';\nimport getBackgroundColorEditor from '../common/editorConfig/BackgroundColor';\nimport { getDefaultPreferPorts } from './PureNetworkGraph/utils/networkGraphUtils';\n\nconst isDynamicOption = (option, valuesName, contextName) => {\n if (\n option &&\n typeof option === 'string' &&\n (option.trim().startsWith(`> ${valuesName} | gradient(${contextName})`) ||\n option.trim().startsWith(`> ${valuesName} | rangeValue(${contextName})`) ||\n option.trim().startsWith(`> ${valuesName} | matchValue(${contextName})`))\n ) {\n return true;\n }\n return false;\n};\n\nconst defaultContext: DefaultContext = {\n colorRangeConfig: [\n { to: 20, value: '#D41F1F' },\n { from: 20, to: 40, value: '#D94E17' },\n { from: 40, to: 60, value: '#CBA700' },\n { from: 60, to: 80, value: '#669922' },\n { from: 80, value: '#118832' },\n ],\n colorMatchConfig: [{ match: '', value: '#5C33FF' }],\n colorGradientConfig: { colors: ['#252214', '#B846BB'] },\n};\n\nconst dataContract: DataContract = {\n requiredDataSources: [\n {\n name: 'primary',\n description: 'DataSource that powers the visualization',\n },\n ],\n optionalDataSources: [\n {\n name: 'nodeSource',\n description: 'DataSource that populates node data',\n },\n ],\n initialRequestParams: {\n primary: { offset: 0, count: 10000 },\n },\n};\n\nconst size: VizSize = {\n initialWidth: DEFAULT_CHART_WIDTH,\n initialHeight: DEFAULT_CHART_HEIGHT,\n};\n\nconst themes: ThemedDefaults = {\n defaultBackgroundColor,\n defaultNodeColor: props => variables.neutral100(props),\n defaultNodeIconColor: props => variables.contentColorActive(props),\n defaultLinkColor: props => variables.borderColorStrong(props),\n};\n\nconst editorConfig: EditorConfig[] = [\n {\n label: _('Data configurations'),\n layout: [\n [\n {\n label: _('Source'),\n option: 'source',\n editor: 'editor.columnSelector',\n editorProps: {\n dataSourceKey: 'primary',\n supportsDSL: true,\n expectedDataPrimitive: ['series'],\n filterByTypes: ['string', 'number'],\n },\n },\n ],\n [\n {\n label: _('Target'),\n option: 'target',\n editor: 'editor.columnSelector',\n editorProps: {\n dataSourceKey: 'primary',\n supportsDSL: true,\n expectedDataPrimitive: ['series'],\n filterByTypes: ['string', 'number'],\n },\n },\n ],\n [\n {\n label: _('Node labels'),\n option: 'nodeTexts',\n editor: 'editor.columnSelector',\n editorProps: {\n dataSourceKey: 'primary',\n supportsDSL: true,\n expectedDataPrimitive: ['series'],\n filterByTypes: ['string'],\n },\n },\n ],\n [\n {\n label: _('Node values'),\n option: 'nodeValues',\n editor: 'editor.columnSelector',\n editorProps: {\n dataSourceKey: 'primary',\n supportsDSL: true,\n expectedDataPrimitive: ['series'],\n filterByTypes: ['number'],\n },\n },\n ],\n [\n {\n label: _('Tooltip header field'),\n option: 'tooltipHeaderField',\n editor: 'editor.columnSelector',\n editorProps: {\n dataSourceKey: 'primary',\n supportsDSL: true,\n expectedDataPrimitive: ['series'],\n shouldShowInternalFields: false,\n },\n },\n ],\n [\n {\n label: _('Tooltip node fields'),\n option: 'tooltipNodeFields',\n editor: 'editor.columnMultiSelectionByFieldNameEditor',\n },\n ],\n [\n {\n label: _('Result limit'),\n option: 'resultLimit',\n editor: 'editor.number',\n editorProps: {\n min: 0,\n step: 1,\n max: 10000,\n },\n },\n ],\n ],\n },\n {\n label: _('Data display'),\n layout: [\n [\n {\n label: _('Layout'),\n option: 'layout',\n editor: 'editor.select',\n editorProps: {\n values: [\n {\n label: _('Grid'),\n value: 'grid',\n description: _('Arrange nodes in a grid layout.'),\n },\n {\n label: _('Preset'),\n value: 'preset',\n description: _('Use predefined positions for nodes.'),\n },\n {\n label: _('Hierarchical'),\n value: 'hierarchical',\n description: _('Arrange nodes in a hierarchy.'),\n },\n {\n label: _('Force'),\n value: 'force',\n description: _('Use a force-directed layout.'),\n },\n ],\n help: _('Some nodes may have custom positions. Changing the layout will reset them.'),\n showHelp: ({ options }) => options.layout === 'force',\n },\n },\n ],\n // grid only options\n [\n {\n label: _('Grid columns'),\n option: 'gridColumns',\n editor: 'editor.number',\n editorProps: {\n min: 1,\n // max should be same as result limit\n max: 1000,\n step: 1,\n },\n showEditor: ({ options }) => options.layout === 'grid' || options.layout === undefined,\n },\n ],\n // preset only options\n [\n {\n label: _('X positions'),\n option: 'xPositions',\n editor: 'editor.columnSelector',\n editorProps: {\n dataSourceKey: 'primary',\n supportsDSL: true,\n expectedDataPrimitive: ['series'],\n filterByTypes: ['number'],\n },\n showEditor: ({ options }) => options.layout === 'preset',\n },\n ],\n [\n {\n label: _('Y positions'),\n option: 'yPositions',\n editor: 'editor.columnSelector',\n editorProps: {\n dataSourceKey: 'primary',\n supportsDSL: true,\n expectedDataPrimitive: ['series'],\n filterByTypes: ['number'],\n },\n showEditor: ({ options }) => options.layout === 'preset',\n },\n ],\n // hierarchical only options\n [\n {\n label: _('Hierarchy direction'),\n option: 'hierarchyDirection',\n editor: 'editor.select',\n editorProps: {\n values: [\n { label: _('Top to Bottom'), value: 'topBottom' },\n { label: _('Bottom to Top'), value: 'bottomTop' },\n { label: _('Left to Right'), value: 'leftRight' },\n { label: _('Right to Left'), value: 'rightLeft' },\n ],\n },\n showEditor: ({ options }) => options.layout === 'hierarchical',\n },\n ],\n // force only options\n [\n {\n label: _('Charge strength'),\n option: 'chargeStrength',\n editor: 'editor.number',\n editorProps: {\n help: _(\n 'Strength of positive charge (attraction) or negative charge (repulsion) between nodes.'\n ),\n helpFormat: 'tooltip',\n step: 1,\n },\n showEditor: ({ options }) => options.layout === 'force',\n },\n {\n label: _('Charge theta'),\n option: 'chargeTheta',\n editor: 'editor.number',\n editorProps: {\n help: _(\n 'Higher value means faster performance but less predictable node positioning.'\n ),\n helpFormat: 'tooltip',\n step: 0.1,\n min: 0,\n },\n showEditor: ({ options }) => options.layout === 'force',\n },\n ],\n [\n {\n label: _('Charge distance minimum'),\n option: 'chargeDistanceMin',\n editor: 'editor.number',\n editorProps: {\n min: 0,\n step: 1,\n help: _('Minimum distance between nodes to apply the charge force to.'),\n helpFormat: 'tooltip',\n },\n showEditor: ({ options }) => options.layout === 'force',\n },\n ],\n [\n {\n label: _('Charge distance maximum'),\n option: 'chargeDistanceMax',\n editor: 'editor.number',\n editorProps: {\n min: 0,\n step: 1,\n help: _('Maximum distance between nodes to apply the charge force to.'),\n helpFormat: 'tooltip',\n },\n showEditor: ({ options }) => options.layout === 'force',\n },\n ],\n [\n {\n label: _('Collider padding'),\n option: 'collidePadding',\n editor: 'editor.number',\n editorProps: {\n min: 0,\n step: 1,\n help: _('Padding around nodes for repulsion between nodes or clusters.'),\n helpFormat: 'tooltip',\n },\n showEditor: ({ options }) => options.layout === 'force',\n },\n {\n label: _('Collider strength'),\n option: 'collideStrength',\n editor: 'editor.number',\n editorProps: {\n min: 0,\n step: 0.1,\n help: _('Strength of the collider used for node repulsion.'),\n helpFormat: 'tooltip',\n },\n showEditor: ({ options }) => options.layout === 'force',\n },\n ],\n [\n {\n label: _('Node cluster types'),\n option: 'nodeTypes',\n editor: 'editor.columnSelector',\n editorProps: {\n dataSourceKey: 'primary',\n supportsDSL: true,\n expectedDataPrimitive: ['series'],\n tooltip: _('Array of node types used for clustering.'),\n filterByTypes: ['string'],\n },\n showEditor: ({ options }) => options.layout === 'force',\n },\n ],\n [\n {\n label: _('Cluster strength'),\n option: 'clusterStrength',\n editor: 'editor.number',\n editorProps: {\n min: 0,\n max: 1,\n step: 0.01,\n help: _('Strength of clustering force between nodes of the same type.'),\n helpFormat: 'tooltip',\n },\n showEditor: ({ options }) =>\n options.layout === 'force' && options.nodeTypes !== undefined,\n },\n ],\n [\n {\n label: _('Link distance'),\n option: 'linkDistance',\n editor: 'editor.number',\n editorProps: {\n min: 0,\n step: 1,\n help: _('Ideal distance between linked nodes. Enter a higher value for more space.'),\n helpFormat: 'tooltip',\n },\n showEditor: ({ options }) => options.layout === 'force',\n },\n {\n label: _('Link strength'),\n option: 'linkStrength',\n editor: 'editor.number',\n editorProps: {\n min: 0,\n step: 0.1,\n help: _(\n 'Strength of spring force between linked nodes. Higher value makes link lengths closer to linkDistance.'\n ),\n helpFormat: 'tooltip',\n },\n showEditor: ({ options }) => options.layout === 'force',\n },\n ],\n [\n {\n label: _('Horizontal padding (px)'),\n option: 'nodeHorizontalPadding',\n editor: 'editor.number',\n editorProps: {\n min: 0,\n step: 1,\n max: 500,\n },\n showEditor: ({ options }) =>\n options.layout === 'hierarchical' ||\n options.layout === 'grid' ||\n options.layout === undefined,\n },\n {\n label: _('Vertical padding (px)'),\n option: 'nodeVerticalPadding',\n editor: 'editor.number',\n editorProps: {\n min: 0,\n step: 1,\n max: 500,\n },\n showEditor: ({ options }) =>\n options.layout === 'hierarchical' ||\n options.layout === 'grid' ||\n options.layout === undefined,\n },\n ],\n [\n {\n label: _('Link direction'),\n option: 'showDirection',\n editor: 'editor.radioBar',\n editorProps: {\n values: [\n { label: _('Bi-direction'), value: 'bidirection' },\n { label: _('One-way'), value: 'oneWay' },\n { label: _('None'), value: 'none' },\n ],\n },\n },\n ],\n [\n {\n label: _('Link connection style'),\n option: 'linkStyle',\n editor: 'editor.select',\n editorProps: {\n values: [\n { label: _('Straight'), value: 'straight' },\n { label: _('C Shape'), value: 'c' },\n { label: _('S Shape'), value: 's' },\n { label: _('Z Shape'), value: 'z' },\n ],\n },\n },\n ],\n [\n {\n label: _('Link port preference'),\n option: 'preferPorts',\n editor: 'editor.radioBar',\n editorProps: {\n values: [\n { label: _('Horizontal'), value: 'horizontal' },\n { label: _('Vertical'), value: 'vertical' },\n { label: _('None'), value: 'none' },\n ],\n },\n showEditor: ({ options }) => options.linkStyle === 's' || options.linkStyle === 'z',\n overrideDefaultValue: ({ options }) =>\n getDefaultPreferPorts(options?.layout, options?.hierarchyDirection),\n },\n ],\n [\n {\n label: _('Show zoom controls'),\n option: 'showZoomControls',\n editor: 'editor.checkbox',\n editorProps: {\n labelPosition: 'right',\n },\n },\n ],\n ],\n },\n {\n label: _('Color and style'),\n layout: [\n getBackgroundColorEditor({ themes }),\n\n [\n {\n label: _('Node color'),\n filterKeywords: ['range', 'match', 'gradient', 'dynamic coloring'],\n option: 'nodeColors',\n context: 'nodeColorsEditorConfig',\n editor: 'editor.networkGraphDynamicColor',\n editorProps: {\n themes,\n contextName: 'nodeColorsEditorConfig',\n defaultStaticColorValue: '> themes.defaultNodeColor',\n flyoutTitle: _('Dynamic coloring'),\n formatters: [\n {\n label: _('Gradient'),\n value: 'gradient',\n isDefault: true,\n defaults: {\n nodeColorValues: defaultContext.colorGradientConfig,\n },\n },\n {\n label: _('Ranges'),\n value: 'rangeValue',\n defaults: {\n nodeColorValues: defaultContext.colorRangeConfig,\n },\n },\n {\n label: _('Matches'),\n value: 'matchValue',\n defaults: {\n nodeColorValues: defaultContext.colorMatchConfig,\n },\n },\n ],\n dataSelectors: [\n {\n label: _('Node color values'),\n value: 'nodeColorValues',\n },\n ],\n defaultPalettesConfig,\n },\n },\n ],\n [\n {\n label: _('Node color data'),\n option: 'nodeColorValues',\n editor: 'editor.columnSelector',\n showEditor: ({ options }) =>\n isDynamicOption(options.nodeColors, 'nodeColorValues', 'nodeColorsEditorConfig'),\n editorProps: {\n dataSourceKey: 'primary',\n supportsDSL: true,\n expectedDataPrimitive: ['series'],\n hideEditorWhenLoading: true,\n },\n },\n ],\n [\n {\n label: _('Node icon color'),\n filterKeywords: ['range', 'match', 'gradient', 'dynamic coloring'],\n option: 'nodeIconColors',\n editor: 'editor.networkGraphDynamicColor',\n context: 'nodeIconColorsEditorConfig',\n editorProps: {\n themes,\n contextName: 'nodeIconColorsEditorConfig',\n defaultStaticColorValue: '> themes.defaultNodeIconColor',\n flyoutTitle: _('Dynamic coloring'),\n formatters: [\n {\n label: _('Gradient'),\n value: 'gradient',\n isDefault: true,\n defaults: {\n nodeIconColorValues: defaultContext.colorGradientConfig,\n },\n },\n {\n label: _('Ranges'),\n value: 'rangeValue',\n defaults: {\n nodeIconColorValues: defaultContext.colorRangeConfig,\n },\n },\n {\n label: _('Matches'),\n value: 'matchValue',\n defaults: {\n nodeIconColorValues: defaultContext.colorMatchConfig,\n },\n },\n ],\n dataSelectors: [\n {\n label: _('Node Icon color values'),\n value: 'nodeIconColorValues',\n },\n ],\n defaultPalettesConfig,\n },\n },\n ],\n [\n {\n label: _('Node icon color data'),\n option: 'nodeIconColorValues',\n editor: 'editor.columnSelector',\n showEditor: ({ options }) =>\n isDynamicOption(\n options.nodeIconColors,\n 'nodeIconColorValues',\n 'nodeIconColorsEditorConfig'\n ),\n editorProps: {\n dataSourceKey: 'primary',\n supportsDSL: true,\n expectedDataPrimitive: ['series'],\n hideEditorWhenLoading: true,\n },\n },\n ],\n [\n {\n label: _('Link color'),\n filterKeywords: ['range', 'match', 'gradient', 'dynamic coloring'],\n option: 'linkColors',\n context: 'linkColorsEditorConfig',\n editor: 'editor.networkGraphDynamicColor',\n editorProps: {\n themes,\n contextName: 'linkColorsEditorConfig',\n defaultStaticColorValue: '> themes.defaultLinkColor',\n flyoutTitle: _('Dynamic coloring'),\n formatters: [\n {\n label: _('Gradient'),\n value: 'gradient',\n isDefault: true,\n defaults: {\n linkColorValues: defaultContext.colorGradientConfig,\n },\n },\n {\n label: _('Ranges'),\n value: 'rangeValue',\n defaults: {\n linkColorValues: defaultContext.colorRangeConfig,\n },\n },\n {\n label: _('Matches'),\n value: 'matchValue',\n defaults: {\n linkColorValues: defaultContext.colorMatchConfig,\n },\n },\n ],\n dataSelectors: [\n {\n label: _('Link color values'),\n value: 'linkColorValues',\n },\n ],\n defaultPalettesConfig,\n },\n },\n ],\n [\n {\n label: _('Link color data'),\n option: 'linkColorValues',\n editor: 'editor.columnSelector',\n showEditor: ({ options }) =>\n isDynamicOption(options.linkColors, 'linkColorValues', 'linkColorsEditorConfig'),\n editorProps: {\n dataSourceKey: 'primary',\n supportsDSL: true,\n expectedDataPrimitive: ['series'],\n hideEditorWhenLoading: true,\n },\n },\n ],\n ],\n },\n];\n\n/**\n * visualization configuration\n */\nconst config: VizConfig = {\n /**\n * unique viz key\n */\n key: 'splunk.networkGraph',\n /**\n * viz name\n */\n name: 'NetworkGraph',\n category: VizCategory.FLOW,\n status: 'preview',\n /**\n * viz icon, this needs to be updated\n */\n icon: NetworkGraphIcon,\n placeholderIcon: NetworkGraphPlaceholderIcon,\n dataContract,\n size,\n defaultContext,\n optionsSchema,\n editorConfig,\n events: {\n 'node.click': {\n description: 'triggered when user clicks a node',\n },\n 'node.mouseOver': {\n description: 'triggered when user moves the cursor onto a node',\n },\n 'node.mouseOut': {\n description: 'triggered when user moves the cursor away from a node',\n },\n 'node.drag': {\n description: 'triggered when user drags a node',\n },\n },\n supports: [VizBehavior.DYNAMIC_OPTIONS, VizBehavior.EVENTS, VizBehavior.PLACEHOLDER],\n themes,\n requiredProps: [],\n};\n\nexport default enhanceConfig(config);\n", "\nimport * as defaultImport from \"@splunk/ui-utils/i18n\";\nexport * from \"@splunk/ui-utils/i18n\";\nexport default ('default' in defaultImport ? defaultImport.default : defaultImport);\n", "\nimport * as defaultImport from \"@splunk/visualization-color-palettes/editors/PresetPalettes\";\nexport * from \"@splunk/visualization-color-palettes/editors/PresetPalettes\";\nexport default ('default' in defaultImport ? defaultImport.default : defaultImport);\n", "\nimport * as defaultImport from \"@splunk/visualization-icons\";\nexport * from \"@splunk/visualization-icons\";\nexport default ('default' in defaultImport ? defaultImport.default : defaultImport);\n", "\nimport * as defaultImport from \"@splunk/visualization-icons/placeholders\";\nexport * from \"@splunk/visualization-icons/placeholders\";\nexport default ('default' in defaultImport ? defaultImport.default : defaultImport);\n", "\nimport * as defaultImport from \"@splunk/visualization-schemas\";\nexport * from \"@splunk/visualization-schemas\";\nexport default ('default' in defaultImport ? defaultImport.default : defaultImport);\n", "\nimport * as defaultImport from \"@splunk/themes\";\nexport * from \"@splunk/themes\";\nexport default ('default' in defaultImport ? defaultImport.default : defaultImport);\n", "\nimport * as defaultImport from \"lodash\";\nexport * from \"lodash\";\nexport default ('default' in defaultImport ? defaultImport.default : defaultImport);\n", "\nimport * as defaultImport from \"@splunk/visualizations-shared/configUtils\";\nexport * from \"@splunk/visualizations-shared/configUtils\";\nexport default ('default' in defaultImport ? defaultImport.default : defaultImport);\n", "import { get, range } from 'lodash';\nimport { isDynamicOption } from '@splunk/visualizations-shared/configUtils';\nimport { VizConfig } from '../interfaces/VizConfig';\nimport { EditorLayoutConfig, EditorConfig } from '../interfaces/Editor';\n\n// included for backwards compatibility\nexport { enhanceOptionsSchemaWithDynamicOptions } from '@splunk/visualization-schemas';\n\nexport interface PresetEntry {\n name: string;\n value: any;\n label: string;\n}\n\n/**\n * Helper function to enhance a Visualization Config as needed based on behaviors specified in supports block\n * this function also collapse all editor panels\n * @param vizConfig a Visualization Config\n * @returns an enhanced Visualization Config containing updated config entries based on behaviors it supports\n * and containing `open: false` for all editor panels in editorConfig\n */\nfunction enhanceConfig(vizConfig: VizConfig): VizConfig {\n const enhancedConfig: VizConfig = { ...vizConfig };\n\n enhancedConfig.editorConfig = vizConfig.editorConfig.map((config: EditorConfig): EditorConfig => {\n return { ...config, open: config.open || false };\n });\n return enhancedConfig;\n}\n\n/**\n * Helper function to uncollapse all editor panels by adding `open: true` to the editorConfig\n * Created for editor unit tests\n * @param vizConfig a Visualization Config\n * @returns a Visualization Config containing `open: true` for all editor panels in editorConfig\n */\nfunction uncollapseEditorConfig(vizConfig: VizConfig): VizConfig {\n const editorPanelUncollapsedConfig: VizConfig = { ...vizConfig };\n editorPanelUncollapsedConfig.editorConfig = vizConfig.editorConfig.map(\n (config: EditorConfig): EditorConfig => {\n return { ...config, open: true };\n }\n );\n return editorPanelUncollapsedConfig;\n}\n\n// private helpers to improve readability of getInitialPreset\n// this shouldn't be tested since this util isn't something that we expect to use outside of getInitialPreset\n\n/**\n * this checks to make sure that all values within vizContext are compatible with the given preset's context to determine the initialPreset\n * if a preset has explicitly specified a key as being undefined, the vizContext must also has that key as undefined, or else this CANNOT be the initialPreset\n * the inverse is true; if a preset has key is NOT undefined, it must also be NOT undefined in the corresponding vizContext\n * the internal config contents are irrelevant (as the user could customize them in source), just as long as the presence of `undefined`s match up\n *\n * Since the presetConfigs have explicit undefined keys to override the existing config, we need to explicitly check the undefined values\n */\nfunction hasCompatibleContexts(\n vizContext: { [configKey: string]: any },\n presetContext: { [configKey: string]: any }\n): boolean {\n const presetContextKeys = Object.keys(presetContext);\n return presetContextKeys.every(\n (contextKey): boolean =>\n (presetContext[contextKey] === undefined && vizContext[contextKey] === undefined) ||\n (presetContext[contextKey] !== undefined && vizContext[contextKey] !== undefined)\n );\n}\n\n/**\n * Checks to make sure that dynamic (non-dynamic) options in a given preset are also dynamic (non-dynamic) in the vizOptions\n */\nfunction hasCompatibleOptions(\n vizOptions: { [configKey: string]: any },\n presetOptions: { [configKey: string]: any }\n): boolean {\n const presetOptionKeys = Object.keys(presetOptions);\n return presetOptionKeys.every(\n (optionKey): boolean =>\n isDynamicOption(presetOptions[optionKey]) === isDynamicOption(vizOptions[optionKey])\n );\n}\n\n/**\n * Evaluates whether there is an initialPreset that fits the current state of vizContext + vizOptions\n * This function ignores extraneous vizOptions or vizContext keys, as they could be outside the scope of what the preset is intended to configure\n *\n * @param {Object} vizContext - the current vizContext definition\n * @param {Object} vizOptions - the current vizOptions definition\n * @param {PresetEntry[]} presets - the list of presets, one of which can be considered an initialPreset\n */\nfunction getInitialPreset(\n vizContext: { [configKey: string]: any },\n vizOptions: { [configKey: string]: any },\n presets: PresetEntry[]\n): PresetEntry | null {\n return presets.find((preset): boolean => {\n const presetContext = get(preset, ['value', 'context'], {});\n const presetOptions = get(preset, ['value', 'options'], {});\n const presetContextKeys = Object.keys(presetContext);\n const presetOptionKeys = Object.keys(presetOptions);\n\n // all presets have to explicitly specify which options to reset (i.e. need explicitly undefined keys)\n // if not, then the preset is invalid in config.ts\n if (presetContextKeys.length === 0 || presetOptionKeys.length === 0) return false;\n\n return (\n hasCompatibleContexts(vizContext, presetContext) &&\n hasCompatibleOptions(vizOptions, presetOptions)\n );\n });\n}\n\ninterface PrecisionOption {\n label: string;\n value: number;\n}\n\n/**\n * generates number precision values for a Select editor\n *\n * @param {Number} numPrecisionValues - number of integer precision values to generate\n * @returns {{ label: string; value; string }[]} array of { label, value } tuples\n */\nfunction generatePrecisionValues(numPrecisionValues: number): PrecisionOption[] {\n return range(0, numPrecisionValues + 1).map(\n (i): PrecisionOption => ({\n label: `${i} (0${i > 0 ? '.' : ''}${'0'.repeat(i)})`, // e.g. 0.000 for numPrecisionValues = 3\n value: i,\n })\n );\n}\n\nexport { enhanceConfig, getInitialPreset, generatePrecisionValues, uncollapseEditorConfig, isDynamicOption };\n\n/**\n * insert layout in layout array, return a new layout array instead of updating the original\n *\n * @param {EditorLayoutConfig[][]} layout - editor layout\n * @param {Number} index - the index to insert layout item(s)\n * @param {EditorLayoutConfig[][]} layoutItems - the item(s) to insert\n * @returns {EditorLayoutConfig[][]} new editor layout with insertion\n */\nexport const insertLayout = (\n layout: EditorLayoutConfig[][],\n index: number,\n ...layoutItems: EditorLayoutConfig[][]\n): EditorLayoutConfig[][] => [\n // copy part of the array before the specified index\n ...layout.slice(0, index),\n // inserted layout item\n ...layoutItems,\n // copy part of the array after the specified index\n ...layout.slice(index),\n];\n", "export const DEFAULT_CHART_WIDTH = 800;\nexport const DEFAULT_CHART_HEIGHT = 500;\nexport const DEFAULT_NODE_RADIUS = 24;\nexport const MIN_NODE_DIAMETER = 12;\nexport const MAX_NODE_DIAMETER = 200;\nexport const DEFAULT_LINK_WIDTH = 2;\nexport const MIN_LINK_WIDTH = 1;\nexport const MAX_LINK_WIDTH = 20;\n\nexport type NodeTypeStructural = {\n id: string;\n index: number;\n height: number;\n width: number;\n size?: number;\n label?: string;\n type?: string;\n children?: string[];\n parents?: string[];\n x?: number;\n y?: number;\n};\n\nexport type BuiltInIconType =\n | 'arrowsDoubleLeftRight'\n | 'arrowsFourRightLeftUpDown'\n | 'arrowsFourTowardUp'\n | 'bell'\n | 'bookshelf'\n | 'buildings'\n | 'chartColumnMagnifier'\n | 'chartColumnSquare'\n | 'checkCircle'\n | 'chevronsSlash'\n | 'cloud'\n | 'cogChevronsSlash'\n | 'containership'\n | 'cube'\n | 'cylinder'\n | 'cylinderIndexTable'\n | 'cylinderMetrics'\n | 'cylinderMulti'\n | 'cylinderTrace'\n | 'cylinderWaves'\n | 'deviceEdgeHub'\n | 'deviceSignalWaves'\n | 'dotsThreeBalloon'\n | 'drive'\n | 'driveIndexTable'\n | 'driveMagnifier'\n | 'driveSliders'\n | 'envelope'\n | 'ethernetPort'\n | 'exclamationTriangle'\n | 'factory'\n | 'file'\n | 'fileLogs'\n | 'filter'\n | 'folder'\n | 'forwarderHeavy'\n | 'forwarderUniversal'\n | 'gate'\n | 'globe'\n | 'identityCard'\n | 'informationCircle'\n | 'informationDiamond'\n | 'laptopOpen'\n | 'layout'\n | 'license'\n | 'mobilePhone'\n | 'monitor'\n | 'monitorPhone'\n | 'networkDevice'\n | 'nodeBranch'\n | 'nodeModel'\n | 'nodeTopology'\n | 'picture'\n | 'portrait'\n | 'portraitsTwo'\n | 'printer'\n | 'processor'\n | 'reportDouble'\n | 'securityCamera'\n | 'servers'\n | 'serversCloud'\n | 'shoppingCart'\n | 'signalWavesAngled'\n | 'singleIndex'\n | 'slidersTripleHorizontal'\n | 'squaresAndCircles'\n | 'star'\n | 'store'\n | 'telescope'\n | 'token'\n | 'truck'\n | 'wallBricks'\n | 'wifiSignal'\n | 'cellularGateway';\n\n// Brand external icons so plain `string` doesn't swallow the BuiltInIconType union.\ntype ExternalIconType = string & {\n __externalIconType?: never;\n};\n\nexport type IconType = BuiltInIconType | ExternalIconType;\n\nexport type NodeTypeVisual = {\n id: string;\n index: number;\n color?: string;\n icon?: IconType;\n iconColor?: string;\n value?: number;\n tooltipFields?: Record<string, MetaDataType | MetaDataType[]>;\n tooltipHeader?: MetaDataType | MetaDataType[];\n};\n\nexport type LinkType = {\n id: string;\n source: string;\n target: string;\n color?: string;\n width?: number;\n tooltipFields?: Record<string, MetaDataType | MetaDataType[]>;\n};\n\nexport type NodePositionType = {\n x: number;\n y: number;\n};\n\nexport type PositionMapType = {\n [id: string]: NodePositionType;\n};\n\nexport type NodeMapStructuralType = {\n [id: string]: NodeTypeStructural;\n};\n\nexport type NodeMapVisualType = {\n [id: string]: NodeTypeVisual;\n};\n\nexport type LinkMapType = {\n [id: string]: LinkType;\n};\n\nexport type ClusterLinkType = 'same' | 'mixed';\n\nexport type MetaDataType = string | number | boolean | null | undefined;\n\nexport type NodeMetadataType = {\n [key: string]: MetaDataType | MetaDataType[];\n};\n\nexport type NodeMetadataPayloadType = {\n [key: `row.${string}.value`]: MetaDataType | MetaDataType[];\n};\n\n// Event payload interfaces\nexport type NodeEventCallbackType = {\n id: string;\n value?: number;\n index: number;\n};\n\nexport type NodeEventCallbackWithPositionType = NodeEventCallbackType & {\n x: number;\n y: number;\n};\n\nexport type NodeEventPayloadType = {\n name: string;\n value?: number;\n} & NodeMetadataPayloadType;\n\nexport type NodeEventPayloadWithPosition = NodeEventPayloadType & {\n position: {\n x: number;\n y: number;\n };\n};\n\nexport type NodeEventPayloadWithTooltip = NodeEventPayloadType & {\n tooltipContext: {\n tooltipX: number;\n tooltipY: number;\n };\n};\n\n// Event objects\nexport interface NodeClickEvent {\n type: 'node.click';\n payload: NodeEventPayloadType;\n}\n\nexport interface NodeMouseOverEvent {\n type: 'node.mouseOver';\n payload: NodeEventPayloadWithTooltip;\n}\n\nexport interface NodeMouseOutEvent {\n type: 'node.mouseOut';\n payload: NodeEventPayloadWithTooltip;\n}\n\nexport interface NodeDragEvent {\n type: 'node.drag';\n payload: NodeEventPayloadWithPosition;\n}\n", "\nimport * as defaultImport from \"@splunk/themes/pick\";\nexport * from \"@splunk/themes/pick\";\nexport default ('default' in defaultImport ? defaultImport.default : defaultImport);\n", "\nimport * as defaultImport from \"@splunk/themes/variables\";\nexport * from \"@splunk/themes/variables\";\nexport default ('default' in defaultImport ? defaultImport.default : defaultImport);\n", "import pick from '@splunk/themes/pick';\nimport variables from '@splunk/themes/variables';\n\nexport const defaultBackgroundColor = props =>\n pick({\n enterprise: {\n dark: variables.black(props),\n light: variables.white(props),\n },\n prisma: variables.backgroundColorSection(props),\n })(props);\n", "import { _ } from '@splunk/ui-utils/i18n';\nimport { EditorLayoutConfig } from '../interfaces/Editor';\nimport { ThemedDefaults } from '../interfaces/ThemedDefaults';\n\ninterface backgroundEditorProps {\n label?: string;\n themes: ThemedDefaults;\n labelPosition?: string;\n}\n\nconst background: ({ label, themes, labelPosition }: backgroundEditorProps) => EditorLayoutConfig[] = ({\n label,\n themes,\n labelPosition,\n}) => [\n {\n label: _(label || 'Background color'),\n option: 'backgroundColor',\n editor: 'editor.color',\n editorProps: {\n labelPosition: labelPosition || 'top',\n themes,\n },\n },\n];\n\nexport default background;\n", "\nimport * as defaultImport from \"dagre\";\nexport * from \"dagre\";\nexport default ('default' in defaultImport ? defaultImport.default : defaultImport);\n", "\nimport * as defaultImport from \"@splunk/visualizations-shared/iconUtils\";\nexport * from \"@splunk/visualizations-shared/iconUtils\";\nexport default ('default' in defaultImport ? defaultImport.default : defaultImport);\n", "\nimport * as defaultImport from \"@splunk/visualization-encoding/utils/colorUtils\";\nexport * from \"@splunk/visualization-encoding/utils/colorUtils\";\nexport default ('default' in defaultImport ? defaultImport.default : defaultImport);\n", "export const VIZ_FONT_FAMILY =\n \"'Splunk Platform Sans', 'Splunk Data Sans', Roboto, Droid, 'Helvetica Neue', Helvetica, Arial, sans-serif\";\n\n/**\n * Return text width\n * @method computeTextWidth\n * @param {String} text\n * @param {String} font\n * @returns {Number} width of text with font\n */\nexport const computeTextWidth = (text: string, font: string): number => {\n const canvas = document.createElement('canvas');\n const context = canvas.getContext('2d');\n context.font = font;\n const measure = context.measureText(text);\n return measure.width;\n};\n\n/**\n * Return truncated text\n * @method truncateText\n * @param {String} text\n * @param {Number} maxWidth\n * @param {String} truncationType\n * @param {String} font\n * @returns {String} truncated text\n */\nexport const truncateText = (\n text: string,\n maxWidth: number,\n truncationType: 'ellipsisEnd' | 'ellipsisMiddle' | 'ellipsisStart',\n font = `10px ${VIZ_FONT_FAMILY}`\n): string => {\n const canvas = document.createElement('canvas');\n const context = canvas.getContext('2d');\n if (!context) return text;\n\n context.font = font;\n\n const ellipsis = '...';\n const ellipsisWidth = context.measureText(ellipsis).width;\n const fullTextWidth = context.measureText(text).width;\n\n if (fullTextWidth <= maxWidth) {\n return text;\n }\n\n if (truncationType === 'ellipsisEnd') {\n for (let i = text.length - 1; i > 0; i -= 1) {\n const truncatedText = text.slice(0, i) + ellipsis;\n if (context.measureText(truncatedText).width <= maxWidth) {\n return truncatedText;\n }\n }\n } else if (truncationType === 'ellipsisStart') {\n for (let i = 0; i < text.length; i += 1) {\n const truncatedText = ellipsis + text.slice(i);\n if (context.measureText(truncatedText).width <= maxWidth) {\n return truncatedText;\n }\n }\n } else if (truncationType === 'ellipsisMiddle') {\n for (let i = 0; i < text.length / 2; i += 1) {\n const left = text.slice(0, Math.floor(text.length / 2) - i);\n const right = text.slice(Math.ceil(text.length / 2) + i);\n const truncatedText = left + ellipsis + right;\n if (\n context.measureText(left).width + ellipsisWidth + context.measureText(right).width <=\n maxWidth\n ) {\n return truncatedText;\n }\n }\n }\n\n return ellipsis;\n};\n", "\nimport * as defaultImport from \"d3-force\";\nexport * from \"d3-force\";\nexport default ('default' in defaultImport ? defaultImport.default : defaultImport);\n", "\nimport * as defaultImport from \"d3-quadtree\";\nexport * from \"d3-quadtree\";\nexport default ('default' in defaultImport ? defaultImport.default : defaultImport);\n", "import dagre from 'dagre';\nimport { isRemoteIcon, isDataURI, isRelativeUrlPath } from '@splunk/visualizations-shared/iconUtils';\nimport { isColor } from '@splunk/visualization-encoding/utils/colorUtils';\nimport { truncateText, VIZ_FONT_FAMILY, computeTextWidth } from '../../../common/utils/textUtils';\nimport { getForceLayout, ForceLayoutOptionsType, ForceNodeType, ForceLinkType } from './forceLayout';\nimport type {\n IconType,\n NodeMapVisualType,\n NodeMapStructuralType,\n NodeTypeStructural,\n PositionMapType,\n NodePositionType,\n LinkMapType,\n ClusterLinkType,\n NodeMetadataType,\n NodeMetadataPayloadType,\n} from './networkGraphShared';\n\nimport {\n DEFAULT_NODE_RADIUS,\n MAX_NODE_DIAMETER,\n MIN_NODE_DIAMETER,\n DEFAULT_LINK_WIDTH,\n MIN_LINK_WIDTH,\n MAX_LINK_WIDTH,\n} from './networkGraphShared';\n\nexport const MAX_ZOOM = 4;\nexport const MAX_TEXT_WIDTH = 144;\n\nexport const RECT_HORIZONTAL_PADDING = 12;\nexport const RECT_VERTICAL_PADDING = 4;\nexport const LINE_HEIGHT = 16;\nexport const RECT_TOP_MARGIN = 4;\nexport const LABEL_FONT_SIZE = 12;\nexport const FONT = `${LABEL_FONT_SIZE}px ${VIZ_FONT_FAMILY}`;\nexport const DIM_OPACITY = 0.25;\nconst MIN_NODE_SPACING = 4;\nconst MIN_EDGE_LENGTH = 48;\nconst MIN_NODE_CONTAINER_SIZE = DEFAULT_NODE_RADIUS * 2 + MIN_EDGE_LENGTH;\nconst DEFAULT_NODE_HORIZONTAL_SPACING = 0;\nconst DEFAULT_NODE_VERTICAL_SPACING = 0;\nconst FORCE_NODE_PADDING = 24;\nexport type LinkStyleType = 's' | 'z' | 'c' | 'straight';\n\nexport type ShowDirectionType = 'bidirection' | 'oneWay' | 'none';\n\nexport type PreferPortsType = 'none' | 'horizontal' | 'vertical';\n\nexport type LayoutType = 'grid' | 'preset' | 'hierarchical' | 'force';\n\nexport type LayoutDirectionType =\n | 'horizontal'\n | 'vertical'\n | 'topBottom'\n | 'bottomTop'\n | 'leftRight'\n | 'rightLeft';\n\nexport type LayoutOptionsType = {\n // shared options\n nodeHorizontalPadding?: number;\n nodeVerticalPadding?: number;\n\n // hierarchical options\n hierarchyDirection?: LayoutDirectionType;\n\n // preset options\n xPositions?: number[];\n yPositions?: number[];\n\n // grid options\n gridColumns?: number;\n} & ForceLayoutOptionsType;\n\nexport type BoundingBoxType = {\n x: number;\n y: number;\n width: number;\n height: number;\n};\n\nexport type IconRenderInfo = {\n iconUrl?: string;\n shouldRenderRemoteIcon: boolean;\n};\n\nexport const getIconRenderInfo = (icon?: IconType): IconRenderInfo => {\n const isIconString = typeof icon === 'string';\n const isRelativeIcon = isIconString ? isRelativeUrlPath(icon) : false;\n let iconUrl = isIconString ? icon : undefined;\n if (iconUrl && isRelativeIcon && typeof window !== 'undefined' && window.location?.origin) {\n iconUrl = `${window.location.origin}${iconUrl}`;\n }\n const shouldRenderRemoteIcon =\n !!iconUrl && isIconString && (isRemoteIcon(iconUrl) || isDataURI(iconUrl) || isRelativeIcon);\n\n return { iconUrl, shouldRenderRemoteIcon };\n};\n\n// sanitize id for use in SVG\nexport const sanitizeSvgId = (id: string): string => {\n const encoder = new TextEncoder();\n const bytes = encoder.encode(id.normalize('NFC')); // normalize for consistency\n const hex = Array.from(bytes)\n .map(b => b.toString(16).padStart(2, '0'))\n .join('');\n return `id_${hex}`;\n};\n\n// decode id back to original string\nexport const unsanitizeSvgId = (safeId: string): string => {\n // Remove the \"id_\" prefix if present\n const hex = safeId.startsWith('id_') ? safeId.slice(3) : safeId;\n\n // Convert every 2 hex chars into a byte\n const pairs = hex.match(/.{1,2}/g) || [];\n const bytes = new Uint8Array(pairs.map(byte => parseInt(byte, 16)));\n\n // Decode bytes back to string\n const decoder = new TextDecoder();\n return decoder.decode(bytes);\n};\n\n// create dynamic payload from metadata for event payloads\nexport const createDynamicPayload = (metadata: NodeMetadataType = {}): NodeMetadataPayloadType => {\n const dynamicPayload: NodeMetadataPayloadType = {};\n Object.entries(metadata).forEach(([key, value]) => {\n dynamicPayload[`row.${key}.value`] = value;\n });\n return dynamicPayload;\n};\n\nexport const shouldDimNode = (highlightedNode: NodeTypeStructural | null, currentNodeId: string): boolean => {\n if (!highlightedNode) return false;\n if (highlightedNode.id === currentNodeId) return false;\n if (highlightedNode.children?.includes(currentNodeId)) return false;\n if (highlightedNode.parents?.includes(currentNodeId)) return false;\n\n return true;\n};\n\nexport const shouldHighlightLink = (source: string, target: string, highlightedNode: string | null) => {\n if (!highlightedNode) return false;\n return source === highlightedNode || target === highlightedNode;\n};\n\n// generate node map visual\n// when visual changes occur only change visual node map\ntype UniqueNodesVisualParams = {\n nodeIds: string[];\n nodeColors: string[] | string;\n iconColors: string[] | string;\n icons: IconType[];\n nodeValues: number[];\n resultLimit?: number;\n tooltipHeaderField?: string;\n tooltipNodeFields?: string[];\n metadata?: NodeMetadataType[];\n};\n\nconst getValidColor = (nodeColors: string[] | string, index: number): string | null => {\n const color = typeof nodeColors === 'string' ? nodeColors : nodeColors[index];\n return isColor(color) ? color : null;\n};\n\nexport const getUniqueNodesVisual = ({\n nodeIds,\n nodeColors,\n iconColors,\n icons,\n nodeValues,\n resultLimit,\n tooltipHeaderField,\n tooltipNodeFields = [],\n metadata = [],\n}: UniqueNodesVisualParams): NodeMapVisualType => {\n const nodeMapVisual: NodeMapVisualType = {};\n // add nodes from datasource\n // note: we assume nodeIds match up with nodeTexts, nodeValues, nodeColors, icons, and iconColors\n // nodeIds can be the same as from if the user is not using a secondary data source\n nodeIds.slice(0, resultLimit).forEach((id, i) => {\n if (!nodeMapVisual[id]?.index) {\n const nodeMeta = metadata[i] || {};\n nodeMapVisual[id] = {\n id,\n index: i,\n color: getValidColor(nodeColors, i),\n icon: icons[i],\n iconColor: getValidColor(iconColors, i),\n value: nodeValues[i],\n tooltipFields:\n tooltipNodeFields.length > 0\n ? [...tooltipNodeFields].reduce((acc, fieldName) => {\n if (fieldName in nodeMeta) {\n acc[fieldName] = nodeMeta[fieldName];\n }\n return acc;\n }, {})\n : undefined,\n tooltipHeader: tooltipHeaderField ? nodeMeta[tooltipHeaderField] : undefined,\n };\n }\n });\n\n return nodeMapVisual;\n};\n\n// generate node map only used for structural changes to trigger layout recalculations\nexport const getUniqueNodesStruct = (\n source: string[],\n target: string[],\n nodeIds: string[],\n nodeTexts: string[],\n nodeTypes: string[],\n nodeSizes: number[] | number,\n resultLimit?: number\n): NodeMapStructuralType => {\n // for updates that do require a layout recalculation\n const nodeMapStruct: NodeMapStructuralType = {};\n\n // add nodes from datasource\n // note: we assume nodeIds match up with nodeTexts, nodeValues, nodeColors, icons, and iconColors\n // nodeIds can be the same as source if the user is not using a secondary data source\n nodeIds.slice(0, resultLimit).forEach((id, i) => {\n const truncatedText = truncateTextToMaxLines(nodeTexts[i], FONT, MAX_TEXT_WIDTH, 4);\n const textWidth = Math.max(...truncatedText.map(line => computeTextWidth(line, FONT)));\n const labelHeight =\n truncatedText.length > 0 ? RECT_VERTICAL_PADDING * 2 + LINE_HEIGHT * truncatedText.length : 0;\n const diameter = Math.max(\n MIN_NODE_DIAMETER,\n Math.min(\n MAX_NODE_DIAMETER,\n Array.isArray(nodeSizes) ? nodeSizes[i] : (nodeSizes ?? DEFAULT_NODE_RADIUS * 2)\n )\n );\n const nodeHeight = diameter + RECT_TOP_MARGIN + MIN_NODE_SPACING + labelHeight;\n const nodeWidth = Math.max(textWidth + RECT_HORIZONTAL_PADDING * 2, diameter + MIN_NODE_SPACING * 2);\n if (!nodeMapStruct[id]?.index) {\n nodeMapStruct[id] = {\n id,\n index: i,\n // need label to calculate node size, which will be used in the layout algorithm\n label: nodeTexts[i],\n height: nodeHeight,\n width: nodeWidth,\n type: nodeTypes?.[i],\n size: diameter,\n children: [],\n parents: [],\n x: undefined,\n y: undefined,\n };\n }\n });\n\n // add neighbors to nodes\n // this will make it easier to highlight neighbors on hover\n target.forEach((targetId, index) => {\n const sourceId = source[index];\n // add parent neighbors to nodeMap\n if (targetId && nodeMapStruct[targetId] && !nodeMapStruct[targetId].parents.includes(sourceId)) {\n nodeMapStruct[targetId].parents.push(sourceId);\n }\n // add child neighbors to nodeMap\n if (sourceId && nodeMapStruct[sourceId] && !nodeMapStruct[sourceId].children.includes(targetId)) {\n nodeMapStruct[sourceId].children.push(targetId);\n }\n });\n return nodeMapStruct;\n};\n\nexport const updatePositions = (\n nodeMap: NodeMapStructuralType,\n positions: PositionMapType\n): NodeMapStructuralType => {\n const updatedNodeMap: NodeMapStructuralType = {};\n Object.keys(nodeMap).forEach(id => {\n updatedNodeMap[id] = {\n ...nodeMap[id],\n x: positions?.[id] ? positions[id].x : nodeMap[id].x,\n y: positions?.[id] ? positions[id].y : nodeMap[id].y,\n };\n });\n return updatedNodeMap;\n};\n\nexport const getUniqueLinks = (\n source: string[],\n target: (string | null)[],\n linkColors: string[] | string,\n linkWidth: number[] | number,\n resultLimit?: number\n): LinkMapType => {\n const linkMap: LinkMapType = {};\n source.slice(0, resultLimit).forEach((sourceId, index) => {\n const targetId = target[index];\n const linkColor = getValidColor(linkColors, index);\n const width = typeof linkWidth === 'number' ? linkWidth : (linkWidth?.[index] ?? DEFAULT_LINK_WIDTH);\n const clampedWidth = Number.isNaN(width)\n ? DEFAULT_LINK_WIDTH\n : Math.min(MAX_LINK_WIDTH, Math.max(MIN_LINK_WIDTH, width));\n // check for toId, since we can have links with no target\n if (targetId) {\n const linkId = `${sourceId}-${targetId}`;\n // this will overwrite the link if it already exists, we use the last one in the list\n // this is what the splunkbase network graph does\n linkMap[linkId] = {\n id: linkId,\n source: sourceId,\n target: targetId,\n color: linkColor,\n width: clampedWidth,\n };\n }\n });\n return linkMap;\n};\n\n// generate grid x,y positions for nodes\nexport const getGridLayout = (\n nodeMap: NodeMapStructuralType,\n gridColumns: number | undefined,\n nodeHorizontalPadding = DEFAULT_NODE_HORIZONTAL_SPACING,\n nodeVerticalPadding = DEFAULT_NODE_VERTICAL_SPACING\n): PositionMapType => {\n const positions = {};\n const numOfCols = gridColumns ?? Math.ceil(Math.sqrt(Object.keys(nodeMap).length));\n const nodeMaxWidth = Math.max(...Object.values(nodeMap).map(node => node.width), 0);\n const nodeMaxHeight = Math.max(...Object.values(nodeMap).map(node => node.height), 0);\n const columnWidth = Math.max(nodeMaxWidth, MIN_NODE_CONTAINER_SIZE) + nodeHorizontalPadding;\n const rowHeight = Math.max(nodeMaxHeight, MIN_NODE_CONTAINER_SIZE) + nodeVerticalPadding;\n\n Object.keys(nodeMap).forEach((id, index) => {\n positions[id] = {\n x: (index % numOfCols) * columnWidth + MAX_TEXT_WIDTH,\n y: Math.floor(index / numOfCols) * rowHeight + DEFAULT_NODE_RADIUS,\n };\n });\n return positions;\n};\n\n// use user-defined x and y positions to preset the layout\nexport const getPresetLayout = (\n nodeMap: NodeMapStructuralType,\n xPositions: number[],\n yPositions: number[]\n): PositionMapType => {\n const positionsMap: PositionMapType = {};\n const xPosFormatted = xPositions?.map(x => x + 340);\n const yPosFormatted = yPositions?.map(y => y + 280);\n Object.keys(nodeMap).forEach(id => {\n const { index } = nodeMap[id];\n positionsMap[id] = {\n x: xPosFormatted[index],\n y: yPosFormatted[index],\n };\n });\n return positionsMap;\n};\n\n// this can take a long time for > 200 nodes\n// for 200 it takes 26ms, 1000 takes 2 seconds\n// eventually move to a webworker\nexport const getHierarchicalLayout = (\n nodeMap: NodeMapStructuralType,\n nodeHorizontalPadding = DEFAULT_NODE_HORIZONTAL_SPACING,\n nodeVerticalPadding = DEFAULT_NODE_VERTICAL_SPACING,\n hierarchyDirection: LayoutDirectionType = 'leftRight'\n): PositionMapType => {\n let rankdir: string;\n if (hierarchyDirection === 'topBottom' || hierarchyDirection === 'vertical') {\n rankdir = 'TB';\n } else if (hierarchyDirection === 'bottomTop') {\n rankdir = 'BT';\n } else if (hierarchyDirection === 'rightLeft') {\n rankdir = 'RL';\n } else {\n rankdir = 'LR';\n }\n const isVertical =\n hierarchyDirection === 'topBottom' ||\n hierarchyDirection === 'bottomTop' ||\n hierarchyDirection === 'vertical';\n const edgeSeparation = 24;\n const g = new dagre.graphlib.Graph();\n g.setGraph({\n rankdir,\n nodesep: isVertical ? nodeHorizontalPadding : nodeVerticalPadding,\n ranksep: isVertical ? nodeVerticalPadding : nodeHorizontalPadding,\n edgesep: edgeSeparation,\n });\n // set empty object by default to avoid errors\n g.setDefaultEdgeLabel(() => ({}));\n\n // Add nodes and edges\n Object.values(nodeMap).forEach(node => {\n g.setNode(node.id, {\n width: Math.max(node.width, MIN_NODE_CONTAINER_SIZE),\n // note: the label is beneath the node, so height is not symmetrical around the position, so we need to use the node size\n // add padding to make sure the link looks good in s and z shape\n height: Math.max(node.height, MIN_NODE_CONTAINER_SIZE),\n // useful for incremental layouting\n x: node.x || null,\n y: node.y || null,\n });\n });\n Object.values(nodeMap).forEach(node => {\n if (Array.isArray(node.children)) {\n node.children.forEach(childId => {\n if (nodeMap[childId]) {\n g.setEdge(node.id, childId);\n }\n });\n }\n });\n\n dagre.layout(g);\n\n // Convert result to position map\n const positionMap: PositionMapType = {};\n g.nodes().forEach(id => {\n const node = g.node(id);\n if (node && typeof node.x === 'number' && typeof node.y === 'number') {\n positionMap[id] = {\n // offset to keep top left corner within view\n // this should not be needed after autozoom\n x: node.x + 50,\n y: node.y + 16,\n };\n }\n });\n\n return positionMap;\n};\n\nexport const getLayout = (\n layout: LayoutType,\n nodeMapStruct: NodeMapStructuralType,\n layoutOptions: LayoutOptionsType\n): PositionMapType => {\n const {\n hierarchyDirection,\n xPositions,\n yPositions,\n nodeHorizontalPadding,\n nodeVerticalPadding,\n gridColumns,\n d3NodeList,\n d3LinkList,\n chargeStrength,\n chargeTheta,\n chargeDistanceMax,\n chargeDistanceMin,\n linkDistance,\n linkStrength,\n collidePadding,\n collideStrength,\n clusterStrength,\n } = layoutOptions;\n\n if (layout === 'preset' && xPositions && yPositions) {\n return getPresetLayout(nodeMapStruct, xPositions, yPositions);\n }\n if (layout === 'hierarchical') {\n return getHierarchicalLayout(\n nodeMapStruct,\n nodeHorizontalPadding,\n nodeVerticalPadding,\n hierarchyDirection\n );\n }\n if (layout === 'force' && d3NodeList && d3LinkList) {\n return getForceLayout({\n d3NodeList,\n d3LinkList,\n chargeStrength,\n chargeTheta,\n collidePadding,\n collideStrength,\n clusterStrength,\n chargeDistanceMax,\n chargeDistanceMin,\n linkDistance,\n linkStrength,\n });\n }\n return getGridLayout(nodeMapStruct, gridColumns, nodeHorizontalPadding, nodeVerticalPadding);\n};\n\ntype NodeMapWithPositionsParams = {\n source: string[];\n target: (string | null)[];\n nodeIds: string[];\n nodeTexts: string[];\n nodeTypes: string[];\n nodeSize?: number[] | number;\n layout: LayoutType;\n layoutOptions: LayoutOptionsType;\n nodeDragPositions?: Record<string, NodePositionType>;\n resultLimit?: number;\n};\n\nexport const getNodeMapWithPositions = ({\n source,\n target,\n nodeIds,\n nodeTexts,\n nodeTypes,\n nodeSize,\n layout,\n layoutOptions,\n nodeDragPositions,\n resultLimit,\n}: NodeMapWithPositionsParams) => {\n const nodeMapStruct = getUniqueNodesStruct(\n source,\n target,\n nodeIds,\n nodeTexts,\n nodeTypes,\n nodeSize,\n resultLimit\n );\n let updatedLayoutOptions = { ...layoutOptions };\n\n // generate node list and link list for d3-force algorithm, need a copy because d3-force modifies the objects\n if (layout === 'force') {\n const d3NodeList: ForceNodeType[] =\n layout === 'force' &&\n [...new Set(source.slice(0, resultLimit))].map(sourceId => ({\n id: sourceId,\n // add a type if clustering is on\n type: nodeMapStruct[sourceId]?.type,\n r: Number.isFinite(nodeMapStruct[sourceId]?.size)\n ? nodeMapStruct[sourceId].size / 2 + FORCE_NODE_PADDING\n : DEFAULT_NODE_RADIUS + FORCE_NODE_PADDING,\n }));\n\n const d3LinkList: ForceLinkType[] =\n layout === 'force' &&\n source\n .slice(0, resultLimit)\n .map((sourceId, index) => ({\n source: sourceId,\n target: target[index],\n // add a clusterType if clustering is on\n clusterType:\n nodeMapStruct[sourceId]?.type &&\n (nodeMapStruct[sourceId]?.type === nodeMapStruct[target[index]]?.type\n ? ('same' as ClusterLinkType)\n : ('mixed' as ClusterLinkType)),\n }))\n .filter(x => !!x.target && nodeMapStruct[x.source] && nodeMapStruct[x.target]);\n updatedLayoutOptions = { ...updatedLayoutOptions, d3NodeList, d3LinkList };\n }\n\n const positionMap = getLayout(layout, nodeMapStruct, updatedLayoutOptions);\n const nodeMapStructWithPositions = updatePositions(nodeMapStruct, positionMap);\n const nodeMapStructWithDragPositions = updatePositions(nodeMapStructWithPositions, nodeDragPositions);\n return nodeMapStructWithDragPositions;\n};\n\n// get the substring that fits in the line\n// use binary search approach for performance\nconst fitCharacters = (context: CanvasRenderingContext2D, maxWidth: number, prefix: string, word: string) => {\n const space = prefix ? ' ' : '';\n let start = 0;\n let end = word.length;\n let bestFit = 0;\n while (start <= end) {\n const mid = Math.floor((start + end) / 2);\n const candidate = prefix ? `${prefix}${space}${word.slice(0, mid)}` : word.slice(0, mid);\n if (context.measureText(candidate).width <= maxWidth) {\n bestFit = mid;\n start = mid + 1;\n } else {\n end = mid - 1;\n }\n }\n return bestFit;\n};\n\nconst handleWordSplit = (\n context: CanvasRenderingContext2D,\n maxWidth: number,\n currentLineText: string,\n nextWord: string\n) => {\n const charsThatFit = fitCharacters(context, maxWidth, currentLineText, nextWord);\n const splitPart = nextWord.slice(0, Math.max(1, charsThatFit));\n const remainder = nextWord.slice(splitPart.length);\n const space = currentLineText ? ' ' : '';\n const currentLineWithSplitPart = `${currentLineText}${space}${splitPart}`;\n\n return { currentLineWithSplit: currentLineWithSplitPart, remainder };\n};\n\nexport const truncateTextToMaxLines = (\n text = '',\n font = `12px ${VIZ_FONT_FAMILY}`,\n maxWidth = 144,\n maxLines = 2\n): string[] => {\n const canvas = document.createElement('canvas');\n const context = canvas.getContext('2d');\n if (!context) return [text];\n context.font = font;\n\n const wordQueue = text.split(/\\s+/).filter(Boolean);\n const outputLines: string[] = [];\n let currentLineText = '';\n\n while (wordQueue.length && outputLines.length < maxLines) {\n const space = currentLineText ? ' ' : '';\n\n // Handle last line with truncation\n if (outputLines.length === maxLines - 1) {\n const remainingText = `${currentLineText}${space}${wordQueue.join(' ')}`;\n const truncatedText = truncateText(remainingText, maxWidth, 'ellipsisEnd', font);\n outputLines.push(truncatedText);\n break;\n }\n\n const nextWord = wordQueue[0];\n const lineWithNextWord = `${currentLineText}${space}${nextWord}`;\n const fitsOnCurrentLine = context.measureText(lineWithNextWord).width <= maxWidth;\n const isNextWordLongerThanMaxWidth = context.measureText(nextWord).width > maxWidth;\n\n // Word fits fully on the current line\n if (fitsOnCurrentLine) {\n currentLineText = lineWithNextWord;\n wordQueue.shift();\n } else if (!isNextWordLongerThanMaxWidth) {\n // Word fits on an empty line but not current line\n outputLines.push(currentLineText);\n currentLineText = '';\n } else {\n // Word is longer than a line and needs to be split\n const { currentLineWithSplit, remainder } = handleWordSplit(\n context,\n maxWidth,\n currentLineText,\n nextWord\n );\n outputLines.push(currentLineWithSplit);\n currentLineText = '';\n wordQueue[0] = remainder;\n }\n }\n\n if (currentLineText && outputLines.length < maxLines) {\n outputLines.push(currentLineText);\n }\n\n return outputLines;\n};\n\nconst getNodeBounds = (node: NodeTypeStructural) => {\n if (!node) return undefined;\n\n const nodeLabelHeightWithMargin = RECT_TOP_MARGIN + RECT_VERTICAL_PADDING * 2 + LABEL_FONT_SIZE;\n const nodeLabelWidth =\n computeTextWidth(node.label, `14px ${VIZ_FONT_FAMILY}`) + RECT_HORIZONTAL_PADDING * 2;\n const nodeDiameter = node.size ?? DEFAULT_NODE_RADIUS * 2;\n const nodeRadius = nodeDiameter / 2;\n\n return {\n x: node.x - Math.max(nodeRadius, nodeLabelWidth / 2),\n y: node.y - nodeRadius,\n x2: node.x - Math.max(nodeRadius, nodeLabelWidth / 2) + Math.max(nodeRadius * 2, nodeLabelWidth),\n y2: node.y + nodeRadius + nodeLabelHeightWithMargin,\n };\n};\n\nconst getFullVizBoundsWithPadding = (nodeMap: NodeMapStructuralType, padding: number): BoundingBoxType => {\n let minXNode: NodeTypeStructural | undefined;\n let minYNode: NodeTypeStructural | undefined;\n let maxXNode: NodeTypeStructural | undefined;\n let maxYNode: NodeTypeStructural | undefined;\n\n if (Object.keys(nodeMap).length === 0) {\n // Handle empty node map case\n return {\n x: 0,\n y: 0,\n width: 0,\n height: 0,\n };\n }\n\n // iterate through nodes to get the outmost nodes for bounds\n // make sure to include label size in the bounds\n Object.values(nodeMap).forEach(node => {\n if (!getNodeBounds(node)) return;\n if (getNodeBounds(node).x < (getNodeBounds(minXNode)?.x ?? Infinity)) minXNode = node;\n if (getNodeBounds(node).y < (getNodeBounds(minYNode)?.y ?? Infinity)) minYNode = node;\n if (getNodeBounds(node).x2 > (getNodeBounds(maxXNode)?.x2 ?? -Infinity)) maxXNode = node;\n if (getNodeBounds(node).y2 > (getNodeBounds(maxYNode)?.y2 ?? -Infinity)) maxYNode = node;\n });\n\n return {\n x: getNodeBounds(minXNode) ? getNodeBounds(minXNode).x - padding : 0,\n y: getNodeBounds(minYNode) ? getNodeBounds(minYNode).y - padding : 0,\n width: getNodeBounds(maxXNode)\n ? getNodeBounds(maxXNode).x2 - getNodeBounds(minXNode).x + padding * 2\n : 0,\n height: getNodeBounds(maxYNode)\n ? getNodeBounds(maxYNode).y2 - getNodeBounds(minYNode).y + padding * 2\n : 0,\n };\n};\n\nexport const getAutoZoomAndPan = (\n nodeMap: NodeMapStructuralType,\n containerWidth: number,\n containerHeight: number\n) => {\n if (Object.keys(nodeMap).length === 0) {\n // Handle empty node map case\n return {\n autoZoomLevel: 1,\n autoCenter: [0, 0],\n };\n }\n const { width, height, x, y } = getFullVizBoundsWithPadding(nodeMap, DEFAULT_NODE_RADIUS);\n\n // calculate the zoom level based on the bounding box size\n const zoomLevel = Math.min(containerWidth / width, containerHeight / height);\n\n const centerX = x + width / 2;\n const centerY = y + height / 2;\n\n return { autoZoomLevel: Math.min(zoomLevel, MAX_ZOOM), autoCenter: [centerX, centerY] };\n};\n\nexport const nearlyEqual = (a: number, b: number, tolerance = 1e-4, usePercentage = false) => {\n if (!usePercentage) {\n return Math.abs(a - b) < tolerance;\n }\n\n const maxAbs = Math.max(Math.abs(a), Math.abs(b));\n\n if (maxAbs === 0) {\n return Math.abs(a - b) < tolerance;\n }\n\n return Math.abs(a - b) / maxAbs <= tolerance;\n};\n\nexport const calculateMinZoomBounds = (autoZoomLevel: number, height, width) => {\n // min zoom value is 25% of auto zoom value\n // which is the same as clicking on the zoom out control twice\n const minZoomLevel = autoZoomLevel / (1.5 * 1.5);\n const minZoomWidth = width / minZoomLevel;\n const minZoomHeight = height / minZoomLevel;\n\n return { minZoomLevel, minZoomHeight, minZoomWidth };\n};\n\nexport const getZoomDisableState = (\n currentZoom: number | null | undefined,\n autoZoomLevel: number,\n minZoomLevel: number\n) => {\n const currentZoomLevel = currentZoom ?? autoZoomLevel;\n const zoomPercentTolerance = 0.001; // 0.1%\n const disableZoomIn =\n currentZoomLevel >= MAX_ZOOM || nearlyEqual(currentZoomLevel, MAX_ZOOM, zoomPercentTolerance, true);\n const disableZoomOut =\n currentZoomLevel <= minZoomLevel ||\n nearlyEqual(currentZoomLevel, minZoomLevel, zoomPercentTolerance, true);\n\n return { disableZoomIn, disableZoomOut };\n};\n\nexport const getDefaultPreferPorts = (\n layout: LayoutType,\n hierarchyDirection: LayoutDirectionType\n): PreferPortsType => {\n if (layout === 'hierarchical') {\n if (hierarchyDirection === 'topBottom' || hierarchyDirection === 'bottomTop') {\n return 'vertical';\n }\n return 'horizontal';\n }\n return 'none';\n};\n\n/// ////////////////////////////\n// LINK STYLING FUNCTIONS\n/// ////////////////////////////\n\ntype StartEndAngleParams = {\n sourcePosition: NodePositionType;\n targetPosition: NodePositionType;\n linkStyle: LinkStyleType;\n showDirection: ShowDirectionType;\n isBidirectional: boolean;\n preferPorts: PreferPortsType;\n startMargin: number;\n endMargin: number;\n linkWidth: number;\n arrowWidth: number;\n};\n\nconst getStartAndEndAngle = ({\n sourcePosition,\n targetPosition,\n linkStyle,\n showDirection,\n isBidirectional,\n preferPorts,\n startMargin,\n endMargin,\n linkWidth,\n arrowWidth,\n}: StartEndAngleParams) => {\n // this is the angle from the line that connects the start and end\n const angle = Math.atan2(targetPosition.y - sourcePosition.y, targetPosition.x - sourcePosition.x);\n\n // angle adjustment used to snap edge to nearest of 4 ports for z and s edges\n // also used to offset straight and c edges for one way edges\n let diffAngleStart;\n let diffAngleEnd;\n\n if (linkStyle === 'straight' || linkStyle === 'c') {\n diffAngleStart = Math.atan((linkWidth + arrowWidth) / startMargin) / 2;\n diffAngleEnd = Math.atan((linkWidth + arrowWidth) / endMargin) / 2;\n } else {\n // restrict to horizontal or vertical ports\n const portAngle = getIsHorizontalDominant(preferPorts, sourcePosition, targetPosition)\n ? Math.PI * Math.round(angle / Math.PI)\n : Math.PI * Math.round((angle + Math.PI / 2) / Math.PI) - Math.PI / 2;\n diffAngleStart = portAngle - angle;\n diffAngleEnd = diffAngleStart;\n }\n\n let startDiffToggle = 1;\n let endDiffToggle = 1;\n if (linkStyle === 'straight' || linkStyle === 'c') {\n // if direction is one-way and there are 2 directions, we want the lines to start slightly off center so they don't overlap\n startDiffToggle = showDirection === 'oneWay' && isBidirectional ? -1 : 0;\n endDiffToggle = showDirection === 'oneWay' && isBidirectional ? 1 : 0;\n }\n\n const startAngleOffset = startDiffToggle * diffAngleStart;\n const endAngleOffset = endDiffToggle * diffAngleEnd;\n\n return { startAngle: angle + startAngleOffset, endAngle: angle + endAngleOffset };\n};\n\nexport const getIsHorizontalDominant = (\n preferPorts: PreferPortsType,\n sourcePosition: NodePositionType,\n targetPosition: NodePositionType\n) => {\n const minDistanceHorizontal = 72;\n const minDistanceVertical = 72;\n\n const isHorizontalDistanceDominant =\n Math.abs(sourcePosition.x - targetPosition.x) > Math.abs(sourcePosition.y - targetPosition.y);\n\n // use none by default if value is invalid\n if (preferPorts === 'none' || (preferPorts !== 'horizontal' && preferPorts !== 'vertical')) {\n return isHorizontalDistanceDominant;\n }\n if (\n preferPorts === 'horizontal' &&\n (isHorizontalDistanceDominant ||\n Math.abs(sourcePosition.x - targetPosition.x) > minDistanceHorizontal)\n ) {\n return true;\n }\n if (preferPorts === 'vertical' && Math.abs(sourcePosition.y - targetPosition.y) < minDistanceVertical) {\n return true;\n }\n return false;\n};\n\ntype EdgePathParams = {\n sourcePosition: NodePositionType;\n targetPosition: NodePositionType;\n startWithArrowOffset: NodePositionType;\n endWithArrowOffset: NodePositionType;\n start: NodePositionType;\n end: NodePositionType;\n middle: NodePositionType;\n preferPorts: PreferPortsType;\n};\n\nconst getSPath = ({\n sourcePosition,\n targetPosition,\n startWithArrowOffset,\n endWithArrowOffset,\n start,\n end,\n middle,\n preferPorts,\n}: EdgePathParams) => {\n // Horizontal-dominant S\n const isHorizontalDominant = getIsHorizontalDominant(preferPorts, sourcePosition, targetPosition);\n\n if (isHorizontalDominant) {\n const segment = (end.x - start.x) / 7;\n return `\n M ${startWithArrowOffset.x} ${startWithArrowOffset.y} \n L ${start.x + segment} ${start.y} \n C ${middle.x + segment} ${start.y}, ${middle.x - segment} ${end.y}, ${end.x - segment} ${end.y} \n L ${endWithArrowOffset.x} ${endWithArrowOffset.y}\n `;\n }\n const segment = (end.y - start.y) / 7;\n // Vertical-dominant S\n return `\n M ${startWithArrowOffset.x} ${startWithArrowOffset.y} \n L ${start.x} ${start.y + segment} \n C ${start.x} ${middle.y + segment}, ${end.x} ${middle.y - segment}, ${end.x} ${end.y - segment}\n L ${endWithArrowOffset.x} ${endWithArrowOffset.y}\n `;\n};\n\nconst getZPath = ({\n sourcePosition,\n targetPosition,\n startWithArrowOffset,\n endWithArrowOffset,\n start,\n end,\n middle,\n preferPorts,\n}: EdgePathParams) => {\n // radius for corner rounding\n const radius = 8;\n // straight line, no need for Z\n if (\n Math.abs(sourcePosition.x - targetPosition.x) < radius * 2 ||\n Math.abs(sourcePosition.y - targetPosition.y) < radius * 2\n ) {\n return `\n M ${startWithArrowOffset.x} ${startWithArrowOffset.y} \n L ${endWithArrowOffset.x} ${endWithArrowOffset.y}\n `;\n }\n // Horizontal-dominant Z\n const isHorizontalDominant = getIsHorizontalDominant(preferPorts, sourcePosition, targetPosition);\n\n if (isHorizontalDominant) {\n const directionX = end.x > start.x ? 1 : -1;\n const directionY = end.y > start.y ? 1 : -1;\n\n return `\n M ${startWithArrowOffset.x} ${startWithArrowOffset.y}\n L ${middle.x - directionX * radius} ${start.y}\n Q ${middle.x} ${start.y} ${middle.x} ${start.y + directionY * radius}\n L ${middle.x} ${end.y - directionY * radius}\n Q ${middle.x} ${end.y} ${middle.x + directionX * radius} ${end.y}\n L ${endWithArrowOffset.x} ${endWithArrowOffset.y}\n `;\n }\n // Vertical-dominant Z\n const directionX = end.x > start.x ? 1 : -1;\n const directionY = end.y > start.y ? 1 : -1;\n\n return `\n M ${startWithArrowOffset.x} ${startWithArrowOffset.y}\n L ${start.x} ${middle.y - directionY * radius}\n Q ${start.x} ${middle.y} ${start.x + directionX * radius} ${middle.y}\n L ${end.x - directionX * radius} ${middle.y}\n Q ${end.x} ${middle.y} ${end.x} ${middle.y + directionY * radius}\n L ${endWithArrowOffset.x} ${endWithArrowOffset.y}\n `;\n};\n\nconst getCPath = (\n startWithArrowOffset: NodePositionType,\n endWithArrowOffset: NodePositionType,\n start: NodePositionType,\n end: NodePositionType\n) => {\n const angle = Math.atan2(end.y - start.y, end.x - start.x);\n const edgeCurve = 24;\n const dx = (end.x - start.x) ** 2;\n const dy = (end.y - start.y) ** 2;\n const ds = Math.sqrt(dx + dy);\n // control points used for curved edge\n const curvedqx =\n (end.x - start.x) / 2 + start.x - ((edgeCurve * ds) / 400) * Math.cos(angle + Math.PI / 2);\n const curvedqy =\n (end.y - start.y) / 2 + start.y - ((edgeCurve * ds) / 400) * Math.sin(angle + Math.PI / 2);\n\n const linkPath = `\n M ${startWithArrowOffset.x} ${startWithArrowOffset.y} \n Q ${curvedqx} ${curvedqy} ${endWithArrowOffset.x} ${endWithArrowOffset.y}\n `;\n return linkPath;\n};\n\nexport const getArrowHeight = (linkSize: number, linkWidth: number): number => {\n if (linkSize === 1) return 5;\n // min linkSize is 1 so this will not divide by 0\n return linkWidth * (0.5 + 1 / linkSize);\n};\n\nexport const getArrowWidth = (linkSize: number): number => {\n return Math.round(linkSize < 4 ? 8 : linkSize * (2.1 - linkSize / 40));\n};\n\nexport const getMinDistanceForArrow = (arrowHeight: number): number => {\n return arrowHeight + 12;\n};\n\nexport const getMinDistanceForCurve = (arrowHeight: number, linkStyle: LinkStyleType | undefined): number => {\n return linkStyle === 'c' ? arrowHeight * 2 + 12 : arrowHeight * 5 + 12;\n};\n\ntype LinkRenderInfoParams = {\n linkStyle: LinkStyleType;\n showDirection: ShowDirectionType;\n preferPorts: PreferPortsType;\n sourcePosition: NodePositionType;\n targetPosition: NodePositionType;\n sourceSize: number;\n targetSize: number;\n arrowHeight: number;\n};\n\nexport const getLinkRenderInfo = ({\n linkStyle,\n showDirection,\n preferPorts,\n sourcePosition,\n targetPosition,\n sourceSize,\n targetSize,\n arrowHeight,\n}: LinkRenderInfoParams) => {\n const isHorizontalDominant = getIsHorizontalDominant(preferPorts, sourcePosition, targetPosition);\n const verticalDistance = Math.abs(targetPosition.y - sourcePosition.y);\n const horizontalDistance = Math.abs(targetPosition.x - sourcePosition.x);\n const minDistanceForArrow = getMinDistanceForArrow(\n showDirection === 'bidirection' ? arrowHeight * 2 : arrowHeight\n );\n const minDistanceForCurve = getMinDistanceForCurve(arrowHeight, linkStyle);\n const distance = Math.hypot(horizontalDistance, verticalDistance) - (sourceSize / 2 + targetSize / 2);\n const shouldHideArrow = distance < minDistanceForArrow;\n const shouldShowBasicLinkStyle = isHorizontalDominant\n ? horizontalDistance - (sourceSize / 2 + targetSize / 2) < minDistanceForCurve\n : verticalDistance - (sourceSize / 2 + targetSize / 2) < minDistanceForCurve;\n const effectiveLinkStyle = shouldShowBasicLinkStyle ? 'straight' : linkStyle;\n const effectiveShowDirection = shouldHideArrow ? 'none' : showDirection;\n\n return { effectiveLinkStyle, effectiveShowDirection };\n};\n\n// generate edge path for svg, inspired from Tatsuya's code sandbox\n// https://codesandbox.io/p/devbox/network-graph-demo-forked-lv8pnr?file=%2Fsrc%2Findex.mjs%3A517%2C4&workspaceId=ws_TYfFvbbyHMCRyRr9xg5ppW\ntype GetEdgePathParams = {\n linkStyle: LinkStyleType;\n sourcePosition: NodePositionType;\n targetPosition: NodePositionType;\n isBidirectional: boolean;\n showDirection: ShowDirectionType;\n arrowHeight?: number;\n arrowWidth?: number;\n preferPorts?: PreferPortsType;\n sourceNodeSize?: number;\n targetNodeSize?: number;\n linkWidth?: number;\n};\n\nexport const getEdgePath = ({\n linkStyle,\n sourcePosition,\n targetPosition,\n isBidirectional,\n showDirection,\n arrowHeight = 6,\n arrowWidth = 8,\n preferPorts = 'none' as PreferPortsType,\n sourceNodeSize = DEFAULT_NODE_RADIUS * 2,\n targetNodeSize = DEFAULT_NODE_RADIUS * 2,\n linkWidth = 2,\n}: GetEdgePathParams) => {\n const getNodeMargin = (nodeSize?: number) => {\n const radius = (nodeSize ?? DEFAULT_NODE_RADIUS * 2) / 2;\n return radius + arrowHeight + MIN_NODE_SPACING;\n };\n const startMargin = getNodeMargin(sourceNodeSize);\n const endMargin = getNodeMargin(targetNodeSize);\n const validatedlinkStyleType =\n linkStyle !== 'c' && linkStyle !== 'z' && linkStyle !== 's' ? 'straight' : linkStyle;\n // offset to extend the line if there is no arrow tip, this may change depending on the width of the arrow\n const startArrowTipOffset = isBidirectional && showDirection === 'bidirection' ? 0 : -arrowHeight;\n const endArrowTipOffset = showDirection === 'none' ? -arrowHeight : 0;\n // get angle to draw edge\n const { startAngle, endAngle } = getStartAndEndAngle({\n sourcePosition,\n targetPosition,\n linkStyle: validatedlinkStyleType,\n showDirection,\n isBidirectional,\n preferPorts,\n startMargin,\n endMargin,\n linkWidth,\n arrowWidth,\n });\n\n // start and end position used for calculations\n const start = {\n x: sourcePosition.x + Math.cos(startAngle) * startMargin,\n y: sourcePosition.y + Math.sin(startAngle) * startMargin,\n };\n const end = {\n x: targetPosition.x - Math.cos(endAngle) * endMargin,\n y: targetPosition.y - Math.sin(endAngle) * endMargin,\n };\n // start and end position with offset for missing arrow tip\n const startWithArrowOffset = {\n x: start.x + Math.cos(startAngle) * startArrowTipOffset,\n y: start.y + Math.sin(startAngle) * startArrowTipOffset,\n };\n const endWithArrowOffset = {\n x: end.x - Math.cos(endAngle) * endArrowTipOffset,\n y: end.y - Math.sin(endAngle) * endArrowTipOffset,\n };\n // half-way position used as control points for z and s edges\n const middle = {\n x: (end.x - start.x) / 2 + start.x,\n y: (end.y - start.y) / 2 + start.y,\n };\n\n // Z edge\n if (linkStyle === 'z') {\n return getZPath({\n sourcePosition,\n targetPosition,\n startWithArrowOffset,\n endWithArrowOffset,\n start,\n end,\n middle,\n preferPorts,\n });\n }\n // S edge\n if (linkStyle === 's') {\n return getSPath({\n sourcePosition,\n targetPosition,\n startWithArrowOffset,\n endWithArrowOffset,\n start,\n end,\n middle,\n preferPorts,\n });\n }\n // Curved edge\n if (linkStyle === 'c') {\n return getCPath(startWithArrowOffset, endWithArrowOffset, start, end);\n }\n // Straight edge by default\n return `\n M ${startWithArrowOffset.x} ${startWithArrowOffset.y} \n L ${endWithArrowOffset.x} ${endWithArrowOffset.y}\n `;\n};\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { _ } from '@splunk/ui-utils/i18n';\nimport { defaultPalettesConfig } from '@splunk/visualization-color-palettes/editors/PresetPalettes';\nimport { NetworkGraph as NetworkGraphIcon } from '@splunk/visualization-icons';\nimport { NetworkGraph as NetworkGraphPlaceholderIcon } from '@splunk/visualization-icons/placeholders';\nimport { networkGraphOptionsSchema as optionsSchema } from '@splunk/visualization-schemas';\nimport { variables } from '@splunk/themes';\nimport { DataContract } from '../common/interfaces/DataContract';\nimport { DefaultContext } from '../common/interfaces/DefaultContext';\nimport { EditorConfig } from '../common/interfaces/Editor';\nimport { ThemedDefaults } from '../common/interfaces/ThemedDefaults';\nimport { VizBehavior } from '../common/interfaces/VizBehavior';\nimport { VizCategory } from '../common/interfaces/VizCategory';\nimport { VizConfig } from '../common/interfaces/VizConfig';\nimport { VizSize } from '../common/interfaces/VizSize';\nimport { enhanceConfig } from '../common/utils/configUtils';\nimport { DEFAULT_CHART_HEIGHT, DEFAULT_CHART_WIDTH } from './PureNetworkGraph/utils/networkGraphShared';\nimport { defaultBackgroundColor } from '../common/ThemedDefaults';\nimport getBackgroundColorEditor from '../common/editorConfig/BackgroundColor';\nimport { getDefaultPreferPorts } from './PureNetworkGraph/utils/networkGraphUtils';\n\nconst isDynamicOption = (option, valuesName, contextName) => {\n if (\n option &&\n typeof option === 'string' &&\n (option.trim().startsWith(`> ${valuesName} | gradient(${contextName})`) ||\n option.trim().startsWith(`> ${valuesName} | rangeValue(${contextName})`) ||\n option.trim().startsWith(`> ${valuesName} | matchValue(${contextName})`))\n ) {\n return true;\n }\n return false;\n};\n\nconst defaultContext: DefaultContext = {\n colorRangeConfig: [\n { to: 20, value: '#D41F1F' },\n { from: 20, to: 40, value: '#D94E17' },\n { from: 40, to: 60, value: '#CBA700' },\n { from: 60, to: 80, value: '#669922' },\n { from: 80, value: '#118832' },\n ],\n colorMatchConfig: [{ match: '', value: '#5C33FF' }],\n colorGradientConfig: { colors: ['#252214', '#B846BB'] },\n};\n\nconst dataContract: DataContract = {\n requiredDataSources: [\n {\n name: 'primary',\n description: 'DataSource that powers the visualization',\n },\n ],\n optionalDataSources: [\n {\n name: 'nodeSource',\n description: 'DataSource that populates node data',\n },\n ],\n initialRequestParams: {\n primary: { offset: 0, count: 10000 },\n },\n};\n\nconst size: VizSize = {\n initialWidth: DEFAULT_CHART_WIDTH,\n initialHeight: DEFAULT_CHART_HEIGHT,\n};\n\nconst themes: ThemedDefaults = {\n defaultBackgroundColor,\n defaultNodeColor: props => variables.neutral100(props),\n defaultNodeIconColor: props => variables.contentColorActive(props),\n defaultLinkColor: props => variables.borderColorStrong(props),\n};\n\nconst editorConfig: EditorConfig[] = [\n {\n label: _('Data configurations'),\n layout: [\n [\n {\n label: _('Source'),\n option: 'source',\n editor: 'editor.columnSelector',\n editorProps: {\n dataSourceKey: 'primary',\n supportsDSL: true,\n expectedDataPrimitive: ['series'],\n filterByTypes: ['string', 'number'],\n },\n },\n ],\n [\n {\n label: _('Target'),\n option: 'target',\n editor: 'editor.columnSelector',\n editorProps: {\n dataSourceKey: 'primary',\n supportsDSL: true,\n expectedDataPrimitive: ['series'],\n filterByTypes: ['string', 'number'],\n },\n },\n ],\n [\n {\n label: _('Node labels'),\n option: 'nodeTexts',\n editor: 'editor.columnSelector',\n editorProps: {\n dataSourceKey: 'primary',\n supportsDSL: true,\n expectedDataPrimitive: ['series'],\n filterByTypes: ['string'],\n },\n },\n ],\n [\n {\n label: _('Node values'),\n option: 'nodeValues',\n editor: 'editor.columnSelector',\n editorProps: {\n dataSourceKey: 'primary',\n supportsDSL: true,\n expectedDataPrimitive: ['series'],\n filterByTypes: ['number'],\n },\n },\n ],\n [\n {\n label: _('Tooltip header field'),\n option: 'tooltipHeaderField',\n editor: 'editor.columnSelector',\n editorProps: {\n dataSourceKey: 'primary',\n supportsDSL: true,\n expectedDataPrimitive: ['series'],\n shouldShowInternalFields: false,\n },\n },\n ],\n [\n {\n label: _('Tooltip node fields'),\n option: 'tooltipNodeFields',\n editor: 'editor.columnMultiSelectionByFieldNameEditor',\n },\n ],\n [\n {\n label: _('Result limit'),\n option: 'resultLimit',\n editor: 'editor.number',\n editorProps: {\n min: 0,\n step: 1,\n max: 10000,\n },\n },\n ],\n ],\n },\n {\n label: _('Data display'),\n layout: [\n [\n {\n label: _('Layout'),\n option: 'layout',\n editor: 'editor.select',\n editorProps: {\n values: [\n {\n label: _('Grid'),\n value: 'grid',\n description: _('Arrange nodes in a grid layout.'),\n },\n {\n label: _('Preset'),\n value: 'preset',\n description: _('Use predefined positions for nodes.'),\n },\n {\n label: _('Hierarchical'),\n value: 'hierarchical',\n description: _('Arrange nodes in a hierarchy.'),\n },\n {\n label: _('Force'),\n value: 'force',\n description: _('Use a force-directed layout.'),\n },\n ],\n help: _('Some nodes may have custom positions. Changing the layout will reset them.'),\n showHelp: ({ options }) => options.layout === 'force',\n },\n },\n ],\n // grid only options\n [\n {\n label: _('Grid columns'),\n option: 'gridColumns',\n editor: 'editor.number',\n editorProps: {\n min: 1,\n // max should be same as result limit\n max: 1000,\n step: 1,\n },\n showEditor: ({ options }) => options.layout === 'grid' || options.layout === undefined,\n },\n ],\n // preset only options\n [\n {\n label: _('X positions'),\n option: 'xPositions',\n editor: 'editor.columnSelector',\n editorProps: {\n dataSourceKey: 'primary',\n supportsDSL: true,\n expectedDataPrimitive: ['series'],\n filterByTypes: ['number'],\n },\n showEditor: ({ options }) => options.layout === 'preset',\n },\n ],\n [\n {\n label: _('Y positions'),\n option: 'yPositions',\n editor: 'editor.columnSelector',\n editorProps: {\n dataSourceKey: 'primary',\n supportsDSL: true,\n expectedDataPrimitive: ['series'],\n filterByTypes: ['number'],\n },\n showEditor: ({ options }) => options.layout === 'preset',\n },\n ],\n // hierarchical only options\n [\n {\n label: _('Hierarchy direction'),\n option: 'hierarchyDirection',\n editor: 'editor.select',\n editorProps: {\n values: [\n { label: _('Top to Bottom'), value: 'topBottom' },\n { label: _('Bottom to Top'), value: 'bottomTop' },\n { label: _('Left to Right'), value: 'leftRight' },\n { label: _('Right to Left'), value: 'rightLeft' },\n ],\n },\n showEditor: ({ options }) => options.layout === 'hierarchical',\n },\n ],\n // force only options\n [\n {\n label: _('Charge strength'),\n option: 'chargeStrength',\n editor: 'editor.number',\n editorProps: {\n help: _(\n 'Strength of positive charge (attraction) or negative charge (repulsion) between nodes.'\n ),\n helpFormat: 'tooltip',\n step: 1,\n },\n showEditor: ({ options }) => options.layout === 'force',\n },\n {\n label: _('Charge theta'),\n option: 'chargeTheta',\n editor: 'editor.number',\n editorProps: {\n help: _(\n 'Higher value means faster performance but less predictable node positioning.'\n ),\n helpFormat: 'tooltip',\n step: 0.1,\n min: 0,\n },\n showEditor: ({ options }) => options.layout === 'force',\n },\n ],\n [\n {\n label: _('Charge distance minimum'),\n option: 'chargeDistanceMin',\n editor: 'editor.number',\n editorProps: {\n min: 0,\n step: 1,\n help: _('Minimum distance between nodes to apply the charge force to.'),\n helpFormat: 'tooltip',\n },\n showEditor: ({ options }) => options.layout === 'force',\n },\n ],\n [\n {\n label: _('Charge distance maximum'),\n option: 'chargeDistanceMax',\n editor: 'editor.number',\n editorProps: {\n min: 0,\n step: 1,\n help: _('Maximum distance between nodes to apply the charge force to.'),\n helpFormat: 'tooltip',\n },\n showEditor: ({ options }) => options.layout === 'force',\n },\n ],\n [\n {\n label: _('Collider padding'),\n option: 'collidePadding',\n editor: 'editor.number',\n editorProps: {\n min: 0,\n step: 1,\n help: _('Padding around nodes for repulsion between nodes or clusters.'),\n helpFormat: 'tooltip',\n },\n showEditor: ({ options }) => options.layout === 'force',\n },\n {\n label: _('Collider strength'),\n option: 'collideStrength',\n editor: 'editor.number',\n editorProps: {\n min: 0,\n step: 0.1,\n help: _('Strength of the collider used for node repulsion.'),\n helpFormat: 'tooltip',\n },\n showEditor: ({ options }) => options.layout === 'force',\n },\n ],\n [\n {\n label: _('Node cluster types'),\n option: 'nodeTypes',\n editor: 'editor.columnSelector',\n editorProps: {\n dataSourceKey: 'primary',\n supportsDSL: true,\n expectedDataPrimitive: ['series'],\n tooltip: _('Array of node types used for clustering.'),\n filterByTypes: ['string'],\n },\n showEditor: ({ options }) => options.layout === 'force',\n },\n ],\n [\n {\n label: _('Cluster strength'),\n option: 'clusterStrength',\n editor: 'editor.number',\n editorProps: {\n min: 0,\n max: 1,\n step: 0.01,\n help: _('Strength of clustering force between nodes of the same type.'),\n helpFormat: 'tooltip',\n },\n showEditor: ({ options }) =>\n options.layout === 'force' && options.nodeTypes !== undefined,\n },\n ],\n [\n {\n label: _('Link distance'),\n option: 'linkDistance',\n editor: 'editor.number',\n editorProps: {\n min: 0,\n step: 1,\n help: _('Ideal distance between linked nodes. Enter a higher value for more space.'),\n helpFormat: 'tooltip',\n },\n showEditor: ({ options }) => options.layout === 'force',\n },\n {\n label: _('Link strength'),\n option: 'linkStrength',\n editor: 'editor.number',\n editorProps: {\n min: 0,\n step: 0.1,\n help: _(\n 'Strength of spring force between linked nodes. Higher value makes link lengths closer to linkDistance.'\n ),\n helpFormat: 'tooltip',\n },\n showEditor: ({ options }) => options.layout === 'force',\n },\n ],\n [\n {\n label: _('Horizontal padding (px)'),\n option: 'nodeHorizontalPadding',\n editor: 'editor.number',\n editorProps: {\n min: 0,\n step: 1,\n max: 500,\n },\n showEditor: ({ options }) =>\n options.layout === 'hierarchical' ||\n options.layout === 'grid' ||\n options.layout === undefined,\n },\n {\n label: _('Vertical padding (px)'),\n option: 'nodeVerticalPadding',\n editor: 'editor.number',\n editorProps: {\n min: 0,\n step: 1,\n max: 500,\n },\n showEditor: ({ options }) =>\n options.layout === 'hierarchical' ||\n options.layout === 'grid' ||\n options.layout === undefined,\n },\n ],\n [\n {\n label: _('Link direction'),\n option: 'showDirection',\n editor: 'editor.radioBar',\n editorProps: {\n values: [\n { label: _('Bi-direction'), value: 'bidirection' },\n { label: _('One-way'), value: 'oneWay' },\n { label: _('None'), value: 'none' },\n ],\n },\n },\n ],\n [\n {\n label: _('Link connection style'),\n option: 'linkStyle',\n editor: 'editor.select',\n editorProps: {\n values: [\n { label: _('Straight'), value: 'straight' },\n { label: _('C Shape'), value: 'c' },\n { label: _('S Shape'), value: 's' },\n { label: _('Z Shape'), value: 'z' },\n ],\n },\n },\n ],\n [\n {\n label: _('Link port preference'),\n option: 'preferPorts',\n editor: 'editor.radioBar',\n editorProps: {\n values: [\n { label: _('Horizontal'), value: 'horizontal' },\n { label: _('Vertical'), value: 'vertical' },\n { label: _('None'), value: 'none' },\n ],\n },\n showEditor: ({ options }) => options.linkStyle === 's' || options.linkStyle === 'z',\n overrideDefaultValue: ({ options }) =>\n getDefaultPreferPorts(options?.layout, options?.hierarchyDirection),\n },\n ],\n [\n {\n label: _('Show zoom controls'),\n option: 'showZoomControls',\n editor: 'editor.checkbox',\n editorProps: {\n labelPosition: 'right',\n },\n },\n ],\n ],\n },\n {\n label: _('Color and style'),\n layout: [\n getBackgroundColorEditor({ themes }),\n\n [\n {\n label: _('Node color'),\n filterKeywords: ['range', 'match', 'gradient', 'dynamic coloring'],\n option: 'nodeColors',\n context: 'nodeColorsEditorConfig',\n editor: 'editor.networkGraphDynamicColor',\n editorProps: {\n themes,\n contextName: 'nodeColorsEditorConfig',\n defaultStaticColorValue: '> themes.defaultNodeColor',\n flyoutTitle: _('Dynamic coloring'),\n formatters: [\n {\n label: _('Gradient'),\n value: 'gradient',\n isDefault: true,\n defaults: {\n nodeColorValues: defaultContext.colorGradientConfig,\n },\n },\n {\n label: _('Ranges'),\n value: 'rangeValue',\n defaults: {\n nodeColorValues: defaultContext.colorRangeConfig,\n },\n },\n {\n label: _('Matches'),\n value: 'matchValue',\n defaults: {\n nodeColorValues: defaultContext.colorMatchConfig,\n },\n },\n ],\n dataSelectors: [\n {\n label: _('Node color values'),\n value: 'nodeColorValues',\n },\n ],\n defaultPalettesConfig,\n },\n },\n ],\n [\n {\n label: _('Node color data'),\n option: 'nodeColorValues',\n editor: 'editor.columnSelector',\n showEditor: ({ options }) =>\n isDynamicOption(options.nodeColors, 'nodeColorValues', 'nodeColorsEditorConfig'),\n editorProps: {\n dataSourceKey: 'primary',\n supportsDSL: true,\n expectedDataPrimitive: ['series'],\n hideEditorWhenLoading: true,\n },\n },\n ],\n [\n {\n label: _('Node icon color'),\n filterKeywords: ['range', 'match', 'gradient', 'dynamic coloring'],\n option: 'nodeIconColors',\n editor: 'editor.networkGraphDynamicColor',\n context: 'nodeIconColorsEditorConfig',\n editorProps: {\n themes,\n contextName: 'nodeIconColorsEditorConfig',\n defaultStaticColorValue: '> themes.defaultNodeIconColor',\n flyoutTitle: _('Dynamic coloring'),\n formatters: [\n {\n label: _('Gradient'),\n value: 'gradient',\n isDefault: true,\n defaults: {\n nodeIconColorValues: defaultContext.colorGradientConfig,\n },\n },\n {\n label: _('Ranges'),\n value: 'rangeValue',\n defaults: {\n nodeIconColorValues: defaultContext.colorRangeConfig,\n },\n },\n {\n label: _('Matches'),\n value: 'matchValue',\n defaults: {\n nodeIconColorValues: defaultContext.colorMatchConfig,\n },\n },\n ],\n dataSelectors: [\n {\n label: _('Node Icon color values'),\n value: 'nodeIconColorValues',\n },\n ],\n defaultPalettesConfig,\n },\n },\n ],\n [\n {\n label: _('Node icon color data'),\n option: 'nodeIconColorValues',\n editor: 'editor.columnSelector',\n showEditor: ({ options }) =>\n isDynamicOption(\n options.nodeIconColors,\n 'nodeIconColorValues',\n 'nodeIconColorsEditorConfig'\n ),\n editorProps: {\n dataSourceKey: 'primary',\n supportsDSL: true,\n expectedDataPrimitive: ['series'],\n hideEditorWhenLoading: true,\n },\n },\n ],\n [\n {\n label: _('Link color'),\n filterKeywords: ['range', 'match', 'gradient', 'dynamic coloring'],\n option: 'linkColors',\n context: 'linkColorsEditorConfig',\n editor: 'editor.networkGraphDynamicColor',\n editorProps: {\n themes,\n contextName: 'linkColorsEditorConfig',\n defaultStaticColorValue: '> themes.defaultLinkColor',\n flyoutTitle: _('Dynamic coloring'),\n formatters: [\n {\n label: _('Gradient'),\n value: 'gradient',\n isDefault: true,\n defaults: {\n linkColorValues: defaultContext.colorGradientConfig,\n },\n },\n {\n label: _('Ranges'),\n value: 'rangeValue',\n defaults: {\n linkColorValues: defaultContext.colorRangeConfig,\n },\n },\n {\n label: _('Matches'),\n value: 'matchValue',\n defaults: {\n linkColorValues: defaultContext.colorMatchConfig,\n },\n },\n ],\n dataSelectors: [\n {\n label: _('Link color values'),\n value: 'linkColorValues',\n },\n ],\n defaultPalettesConfig,\n },\n },\n ],\n [\n {\n label: _('Link color data'),\n option: 'linkColorValues',\n editor: 'editor.columnSelector',\n showEditor: ({ options }) =>\n isDynamicOption(options.linkColors, 'linkColorValues', 'linkColorsEditorConfig'),\n editorProps: {\n dataSourceKey: 'primary',\n supportsDSL: true,\n expectedDataPrimitive: ['series'],\n hideEditorWhenLoading: true,\n },\n },\n ],\n ],\n },\n];\n\n/**\n * visualization configuration\n */\nconst config: VizConfig = {\n /**\n * unique viz key\n */\n key: 'splunk.networkGraph',\n /**\n * viz name\n */\n name: 'NetworkGraph',\n category: VizCategory.FLOW,\n status: 'preview',\n /**\n * viz icon, this needs to be updated\n */\n icon: NetworkGraphIcon,\n placeholderIcon: NetworkGraphPlaceholderIcon,\n dataContract,\n size,\n defaultContext,\n optionsSchema,\n editorConfig,\n events: {\n 'node.click': {\n description: 'triggered when user clicks a node',\n },\n 'node.mouseOver': {\n description: 'triggered when user moves the cursor onto a node',\n },\n 'node.mouseOut': {\n description: 'triggered when user moves the cursor away from a node',\n },\n 'node.drag': {\n description: 'triggered when user drags a node',\n },\n },\n supports: [VizBehavior.DYNAMIC_OPTIONS, VizBehavior.EVENTS, VizBehavior.PLACEHOLDER],\n themes,\n requiredProps: [],\n};\n\nexport default enhanceConfig(config);\n", "\nimport * as defaultImport from \"@splunk/ui-utils/i18n\";\nexport * from \"@splunk/ui-utils/i18n\";\nexport default ('default' in defaultImport ? defaultImport.default : defaultImport);\n", "\nimport * as defaultImport from \"@splunk/visualization-color-palettes/editors/PresetPalettes\";\nexport * from \"@splunk/visualization-color-palettes/editors/PresetPalettes\";\nexport default ('default' in defaultImport ? defaultImport.default : defaultImport);\n", "\nimport * as defaultImport from \"@splunk/visualization-icons\";\nexport * from \"@splunk/visualization-icons\";\nexport default ('default' in defaultImport ? defaultImport.default : defaultImport);\n", "\nimport * as defaultImport from \"@splunk/visualization-icons/placeholders\";\nexport * from \"@splunk/visualization-icons/placeholders\";\nexport default ('default' in defaultImport ? defaultImport.default : defaultImport);\n", "\nimport * as defaultImport from \"@splunk/visualization-schemas\";\nexport * from \"@splunk/visualization-schemas\";\nexport default ('default' in defaultImport ? defaultImport.default : defaultImport);\n", "\nimport * as defaultImport from \"@splunk/themes\";\nexport * from \"@splunk/themes\";\nexport default ('default' in defaultImport ? defaultImport.default : defaultImport);\n", "\nimport * as defaultImport from \"lodash\";\nexport * from \"lodash\";\nexport default ('default' in defaultImport ? defaultImport.default : defaultImport);\n", "\nimport * as defaultImport from \"@splunk/visualizations-shared/configUtils\";\nexport * from \"@splunk/visualizations-shared/configUtils\";\nexport default ('default' in defaultImport ? defaultImport.default : defaultImport);\n", "import { get, range } from 'lodash';\nimport { isDynamicOption } from '@splunk/visualizations-shared/configUtils';\nimport { VizConfig } from '../interfaces/VizConfig';\nimport { EditorLayoutConfig, EditorConfig } from '../interfaces/Editor';\n\n// included for backwards compatibility\nexport { enhanceOptionsSchemaWithDynamicOptions } from '@splunk/visualization-schemas';\n\nexport interface PresetEntry {\n name: string;\n value: any;\n label: string;\n}\n\n/**\n * Helper function to enhance a Visualization Config as needed based on behaviors specified in supports block\n * this function also collapse all editor panels\n * @param vizConfig a Visualization Config\n * @returns an enhanced Visualization Config containing updated config entries based on behaviors it supports\n * and containing `open: false` for all editor panels in editorConfig\n */\nfunction enhanceConfig(vizConfig: VizConfig): VizConfig {\n const enhancedConfig: VizConfig = { ...vizConfig };\n\n enhancedConfig.editorConfig = vizConfig.editorConfig.map((config: EditorConfig): EditorConfig => {\n return { ...config, open: config.open || false };\n });\n return enhancedConfig;\n}\n\n/**\n * Helper function to uncollapse all editor panels by adding `open: true` to the editorConfig\n * Created for editor unit tests\n * @param vizConfig a Visualization Config\n * @returns a Visualization Config containing `open: true` for all editor panels in editorConfig\n */\nfunction uncollapseEditorConfig(vizConfig: VizConfig): VizConfig {\n const editorPanelUncollapsedConfig: VizConfig = { ...vizConfig };\n editorPanelUncollapsedConfig.editorConfig = vizConfig.editorConfig.map(\n (config: EditorConfig): EditorConfig => {\n return { ...config, open: true };\n }\n );\n return editorPanelUncollapsedConfig;\n}\n\n// private helpers to improve readability of getInitialPreset\n// this shouldn't be tested since this util isn't something that we expect to use outside of getInitialPreset\n\n/**\n * this checks to make sure that all values within vizContext are compatible with the given preset's context to determine the initialPreset\n * if a preset has explicitly specified a key as being undefined, the vizContext must also has that key as undefined, or else this CANNOT be the initialPreset\n * the inverse is true; if a preset has key is NOT undefined, it must also be NOT undefined in the corresponding vizContext\n * the internal config contents are irrelevant (as the user could customize them in source), just as long as the presence of `undefined`s match up\n *\n * Since the presetConfigs have explicit undefined keys to override the existing config, we need to explicitly check the undefined values\n */\nfunction hasCompatibleContexts(\n vizContext: { [configKey: string]: any },\n presetContext: { [configKey: string]: any }\n): boolean {\n const presetContextKeys = Object.keys(presetContext);\n return presetContextKeys.every(\n (contextKey): boolean =>\n (presetContext[contextKey] === undefined && vizContext[contextKey] === undefined) ||\n (presetContext[contextKey] !== undefined && vizContext[contextKey] !== undefined)\n );\n}\n\n/**\n * Checks to make sure that dynamic (non-dynamic) options in a given preset are also dynamic (non-dynamic) in the vizOptions\n */\nfunction hasCompatibleOptions(\n vizOptions: { [configKey: string]: any },\n presetOptions: { [configKey: string]: any }\n): boolean {\n const presetOptionKeys = Object.keys(presetOptions);\n return presetOptionKeys.every(\n (optionKey): boolean =>\n isDynamicOption(presetOptions[optionKey]) === isDynamicOption(vizOptions[optionKey])\n );\n}\n\n/**\n * Evaluates whether there is an initialPreset that fits the current state of vizContext + vizOptions\n * This function ignores extraneous vizOptions or vizContext keys, as they could be outside the scope of what the preset is intended to configure\n *\n * @param {Object} vizContext - the current vizContext definition\n * @param {Object} vizOptions - the current vizOptions definition\n * @param {PresetEntry[]} presets - the list of presets, one of which can be considered an initialPreset\n */\nfunction getInitialPreset(\n vizContext: { [configKey: string]: any },\n vizOptions: { [configKey: string]: any },\n presets: PresetEntry[]\n): PresetEntry | null {\n return presets.find((preset): boolean => {\n const presetContext = get(preset, ['value', 'context'], {});\n const presetOptions = get(preset, ['value', 'options'], {});\n const presetContextKeys = Object.keys(presetContext);\n const presetOptionKeys = Object.keys(presetOptions);\n\n // all presets have to explicitly specify which options to reset (i.e. need explicitly undefined keys)\n // if not, then the preset is invalid in config.ts\n if (presetContextKeys.length === 0 || presetOptionKeys.length === 0) return false;\n\n return (\n hasCompatibleContexts(vizContext, presetContext) &&\n hasCompatibleOptions(vizOptions, presetOptions)\n );\n });\n}\n\ninterface PrecisionOption {\n label: string;\n value: number;\n}\n\n/**\n * generates number precision values for a Select editor\n *\n * @param {Number} numPrecisionValues - number of integer precision values to generate\n * @returns {{ label: string; value; string }[]} array of { label, value } tuples\n */\nfunction generatePrecisionValues(numPrecisionValues: number): PrecisionOption[] {\n return range(0, numPrecisionValues + 1).map(\n (i): PrecisionOption => ({\n label: `${i} (0${i > 0 ? '.' : ''}${'0'.repeat(i)})`, // e.g. 0.000 for numPrecisionValues = 3\n value: i,\n })\n );\n}\n\nexport { enhanceConfig, getInitialPreset, generatePrecisionValues, uncollapseEditorConfig, isDynamicOption };\n\n/**\n * insert layout in layout array, return a new layout array instead of updating the original\n *\n * @param {EditorLayoutConfig[][]} layout - editor layout\n * @param {Number} index - the index to insert layout item(s)\n * @param {EditorLayoutConfig[][]} layoutItems - the item(s) to insert\n * @returns {EditorLayoutConfig[][]} new editor layout with insertion\n */\nexport const insertLayout = (\n layout: EditorLayoutConfig[][],\n index: number,\n ...layoutItems: EditorLayoutConfig[][]\n): EditorLayoutConfig[][] => [\n // copy part of the array before the specified index\n ...layout.slice(0, index),\n // inserted layout item\n ...layoutItems,\n // copy part of the array after the specified index\n ...layout.slice(index),\n];\n", "export const DEFAULT_CHART_WIDTH = 800;\nexport const DEFAULT_CHART_HEIGHT = 500;\nexport const DEFAULT_NODE_RADIUS = 24;\nexport const MIN_NODE_DIAMETER = 12;\nexport const MAX_NODE_DIAMETER = 200;\nexport const DEFAULT_LINK_WIDTH = 2;\nexport const MIN_LINK_WIDTH = 1;\nexport const MAX_LINK_WIDTH = 20;\n\nexport type NodeTypeStructural = {\n id: string;\n index: number;\n height: number;\n width: number;\n size?: number;\n label?: string;\n type?: string;\n children?: string[];\n parents?: string[];\n x?: number;\n y?: number;\n};\n\nexport type BuiltInIconType =\n | 'arrowsDoubleLeftRight'\n | 'arrowsFourRightLeftUpDown'\n | 'arrowsFourTowardUp'\n | 'bell'\n | 'bookshelf'\n | 'buildings'\n | 'chartColumnMagnifier'\n | 'chartColumnSquare'\n | 'checkCircle'\n | 'chevronsSlash'\n | 'cloud'\n | 'cogChevronsSlash'\n | 'containership'\n | 'cube'\n | 'cylinder'\n | 'cylinderIndexTable'\n | 'cylinderMetrics'\n | 'cylinderMulti'\n | 'cylinderTrace'\n | 'cylinderWaves'\n | 'deviceEdgeHub'\n | 'deviceSignalWaves'\n | 'dotsThreeBalloon'\n | 'drive'\n | 'driveIndexTable'\n | 'driveMagnifier'\n | 'driveSliders'\n | 'envelope'\n | 'ethernetPort'\n | 'exclamationTriangle'\n | 'factory'\n | 'file'\n | 'fileLogs'\n | 'filter'\n | 'folder'\n | 'forwarderHeavy'\n | 'forwarderUniversal'\n | 'gate'\n | 'globe'\n | 'identityCard'\n | 'informationCircle'\n | 'informationDiamond'\n | 'laptopOpen'\n | 'layout'\n | 'license'\n | 'mobilePhone'\n | 'monitor'\n | 'monitorPhone'\n | 'networkDevice'\n | 'nodeBranch'\n | 'nodeModel'\n | 'nodeTopology'\n | 'picture'\n | 'portrait'\n | 'portraitsTwo'\n | 'printer'\n | 'processor'\n | 'reportDouble'\n | 'securityCamera'\n | 'servers'\n | 'serversCloud'\n | 'shoppingCart'\n | 'signalWavesAngled'\n | 'singleIndex'\n | 'slidersTripleHorizontal'\n | 'squaresAndCircles'\n | 'star'\n | 'store'\n | 'telescope'\n | 'token'\n | 'truck'\n | 'wallBricks'\n | 'wifiSignal'\n | 'cellularGateway';\n\n// Brand external icons so plain `string` doesn't swallow the BuiltInIconType union.\ntype ExternalIconType = string & {\n __externalIconType?: never;\n};\n\nexport type IconType = BuiltInIconType | ExternalIconType;\n\nexport type NodeTypeVisual = {\n id: string;\n index: number;\n color?: string;\n icon?: IconType;\n iconColor?: string;\n value?: number;\n tooltipFields?: Record<string, MetaDataType | MetaDataType[]>;\n tooltipHeader?: MetaDataType | MetaDataType[];\n};\n\nexport type LinkType = {\n id: string;\n source: string;\n target: string;\n color?: string;\n width?: number;\n tooltipFields?: Record<string, MetaDataType | MetaDataType[]>;\n};\n\nexport type NodePositionType = {\n x: number;\n y: number;\n};\n\nexport type PositionMapType = {\n [id: string]: NodePositionType;\n};\n\nexport type NodeMapStructuralType = {\n [id: string]: NodeTypeStructural;\n};\n\nexport type NodeMapVisualType = {\n [id: string]: NodeTypeVisual;\n};\n\nexport type LinkMapType = {\n [id: string]: LinkType;\n};\n\nexport type ClusterLinkType = 'same' | 'mixed';\n\nexport type MetaDataType = string | number | boolean | null | undefined;\n\nexport type NodeMetadataType = {\n [key: string]: MetaDataType | MetaDataType[];\n};\n\nexport type NodeMetadataPayloadType = {\n [key: `row.${string}.value`]: MetaDataType | MetaDataType[];\n};\n\n// Event payload interfaces\nexport type NodeEventCallbackType = {\n id: string;\n value?: number;\n index: number;\n};\n\nexport type NodeEventCallbackWithPositionType = NodeEventCallbackType & {\n x: number;\n y: number;\n};\n\nexport type NodeEventPayloadType = {\n name: string;\n value?: number;\n} & NodeMetadataPayloadType;\n\nexport type NodeEventPayloadWithPosition = NodeEventPayloadType & {\n position: {\n x: number;\n y: number;\n };\n};\n\nexport type NodeEventPayloadWithTooltip = NodeEventPayloadType & {\n tooltipContext: {\n tooltipX: number;\n tooltipY: number;\n };\n};\n\n// Event objects\nexport interface NodeClickEvent {\n type: 'node.click';\n payload: NodeEventPayloadType;\n}\n\nexport interface NodeMouseOverEvent {\n type: 'node.mouseOver';\n payload: NodeEventPayloadWithTooltip;\n}\n\nexport interface NodeMouseOutEvent {\n type: 'node.mouseOut';\n payload: NodeEventPayloadWithTooltip;\n}\n\nexport interface NodeDragEvent {\n type: 'node.drag';\n payload: NodeEventPayloadWithPosition;\n}\n", "\nimport * as defaultImport from \"@splunk/themes/pick\";\nexport * from \"@splunk/themes/pick\";\nexport default ('default' in defaultImport ? defaultImport.default : defaultImport);\n", "\nimport * as defaultImport from \"@splunk/themes/variables\";\nexport * from \"@splunk/themes/variables\";\nexport default ('default' in defaultImport ? defaultImport.default : defaultImport);\n", "import pick from '@splunk/themes/pick';\nimport variables from '@splunk/themes/variables';\n\nexport const defaultBackgroundColor = props =>\n pick({\n enterprise: {\n dark: variables.black(props),\n light: variables.white(props),\n },\n prisma: variables.backgroundColorSection(props),\n })(props);\n", "import { _ } from '@splunk/ui-utils/i18n';\nimport { EditorLayoutConfig } from '../interfaces/Editor';\nimport { ThemedDefaults } from '../interfaces/ThemedDefaults';\n\ninterface backgroundEditorProps {\n label?: string;\n themes: ThemedDefaults;\n labelPosition?: string;\n}\n\nconst background: ({ label, themes, labelPosition }: backgroundEditorProps) => EditorLayoutConfig[] = ({\n label,\n themes,\n labelPosition,\n}) => [\n {\n label: _(label || 'Background color'),\n option: 'backgroundColor',\n editor: 'editor.color',\n editorProps: {\n labelPosition: labelPosition || 'top',\n themes,\n },\n },\n];\n\nexport default background;\n", "\nimport * as defaultImport from \"dagre\";\nexport * from \"dagre\";\nexport default ('default' in defaultImport ? defaultImport.default : defaultImport);\n", "\nimport * as defaultImport from \"@splunk/visualizations-shared/iconUtils\";\nexport * from \"@splunk/visualizations-shared/iconUtils\";\nexport default ('default' in defaultImport ? defaultImport.default : defaultImport);\n", "\nimport * as defaultImport from \"@splunk/visualization-encoding/utils/colorUtils\";\nexport * from \"@splunk/visualization-encoding/utils/colorUtils\";\nexport default ('default' in defaultImport ? defaultImport.default : defaultImport);\n", "export const VIZ_FONT_FAMILY =\n \"'Splunk Platform Sans', 'Splunk Data Sans', Roboto, Droid, 'Helvetica Neue', Helvetica, Arial, sans-serif\";\n\n/**\n * Return text width\n * @method computeTextWidth\n * @param {String} text\n * @param {String} font\n * @returns {Number} width of text with font\n */\nexport const computeTextWidth = (text: string, font: string): number => {\n const canvas = document.createElement('canvas');\n const context = canvas.getContext('2d');\n context.font = font;\n const measure = context.measureText(text);\n return measure.width;\n};\n\n/**\n * Return truncated text\n * @method truncateText\n * @param {String} text\n * @param {Number} maxWidth\n * @param {String} truncationType\n * @param {String} font\n * @returns {String} truncated text\n */\nexport const truncateText = (\n text: string,\n maxWidth: number,\n truncationType: 'ellipsisEnd' | 'ellipsisMiddle' | 'ellipsisStart',\n font = `10px ${VIZ_FONT_FAMILY}`\n): string => {\n const canvas = document.createElement('canvas');\n const context = canvas.getContext('2d');\n if (!context) return text;\n\n context.font = font;\n\n const ellipsis = '...';\n const ellipsisWidth = context.measureText(ellipsis).width;\n const fullTextWidth = context.measureText(text).width;\n\n if (fullTextWidth <= maxWidth) {\n return text;\n }\n\n if (truncationType === 'ellipsisEnd') {\n for (let i = text.length - 1; i > 0; i -= 1) {\n const truncatedText = text.slice(0, i) + ellipsis;\n if (context.measureText(truncatedText).width <= maxWidth) {\n return truncatedText;\n }\n }\n } else if (truncationType === 'ellipsisStart') {\n for (let i = 0; i < text.length; i += 1) {\n const truncatedText = ellipsis + text.slice(i);\n if (context.measureText(truncatedText).width <= maxWidth) {\n return truncatedText;\n }\n }\n } else if (truncationType === 'ellipsisMiddle') {\n for (let i = 0; i < text.length / 2; i += 1) {\n const left = text.slice(0, Math.floor(text.length / 2) - i);\n const right = text.slice(Math.ceil(text.length / 2) + i);\n const truncatedText = left + ellipsis + right;\n if (\n context.measureText(left).width + ellipsisWidth + context.measureText(right).width <=\n maxWidth\n ) {\n return truncatedText;\n }\n }\n }\n\n return ellipsis;\n};\n", "\nimport * as defaultImport from \"d3-force\";\nexport * from \"d3-force\";\nexport default ('default' in defaultImport ? defaultImport.default : defaultImport);\n", "\nimport * as defaultImport from \"d3-quadtree\";\nexport * from \"d3-quadtree\";\nexport default ('default' in defaultImport ? defaultImport.default : defaultImport);\n", "import dagre from 'dagre';\nimport { isRemoteIcon, isDataURI, isRelativeUrlPath } from '@splunk/visualizations-shared/iconUtils';\nimport { isColor } from '@splunk/visualization-encoding/utils/colorUtils';\nimport { truncateText, VIZ_FONT_FAMILY, computeTextWidth } from '../../../common/utils/textUtils';\nimport { getForceLayout, ForceLayoutOptionsType, ForceNodeType, ForceLinkType } from './forceLayout';\nimport type {\n IconType,\n NodeMapVisualType,\n NodeMapStructuralType,\n NodeTypeStructural,\n PositionMapType,\n NodePositionType,\n LinkMapType,\n ClusterLinkType,\n NodeMetadataType,\n NodeMetadataPayloadType,\n} from './networkGraphShared';\n\nimport {\n DEFAULT_NODE_RADIUS,\n MAX_NODE_DIAMETER,\n MIN_NODE_DIAMETER,\n DEFAULT_LINK_WIDTH,\n MIN_LINK_WIDTH,\n MAX_LINK_WIDTH,\n} from './networkGraphShared';\n\nexport const MAX_ZOOM = 4;\nexport const MAX_TEXT_WIDTH = 144;\n\nexport const RECT_HORIZONTAL_PADDING = 12;\nexport const RECT_VERTICAL_PADDING = 4;\nexport const LINE_HEIGHT = 16;\nexport const RECT_TOP_MARGIN = 4;\nexport const LABEL_FONT_SIZE = 12;\nexport const FONT = `${LABEL_FONT_SIZE}px ${VIZ_FONT_FAMILY}`;\nexport const DIM_OPACITY = 0.25;\nconst MIN_NODE_SPACING = 4;\nconst MIN_EDGE_LENGTH = 48;\nconst MIN_NODE_CONTAINER_SIZE = DEFAULT_NODE_RADIUS * 2 + MIN_EDGE_LENGTH;\nconst DEFAULT_NODE_HORIZONTAL_SPACING = 0;\nconst DEFAULT_NODE_VERTICAL_SPACING = 0;\nconst FORCE_NODE_PADDING = 24;\nexport type LinkStyleType = 's' | 'z' | 'c' | 'straight';\n\nexport type ShowDirectionType = 'bidirection' | 'oneWay' | 'none';\n\nexport type PreferPortsType = 'none' | 'horizontal' | 'vertical';\n\nexport type LayoutType = 'grid' | 'preset' | 'hierarchical' | 'force';\n\nexport type LayoutDirectionType =\n | 'horizontal'\n | 'vertical'\n | 'topBottom'\n | 'bottomTop'\n | 'leftRight'\n | 'rightLeft';\n\nexport type LayoutOptionsType = {\n // shared options\n nodeHorizontalPadding?: number;\n nodeVerticalPadding?: number;\n\n // hierarchical options\n hierarchyDirection?: LayoutDirectionType;\n\n // preset options\n xPositions?: number[];\n yPositions?: number[];\n\n // grid options\n gridColumns?: number;\n} & ForceLayoutOptionsType;\n\nexport type BoundingBoxType = {\n x: number;\n y: number;\n width: number;\n height: number;\n};\n\nexport type IconRenderInfo = {\n iconUrl?: string;\n shouldRenderRemoteIcon: boolean;\n};\n\nexport const getIconRenderInfo = (icon?: IconType): IconRenderInfo => {\n const isIconString = typeof icon === 'string';\n const isRelativeIcon = isIconString ? isRelativeUrlPath(icon) : false;\n let iconUrl = isIconString ? icon : undefined;\n if (iconUrl && isRelativeIcon && typeof window !== 'undefined' && window.location?.origin) {\n iconUrl = `${window.location.origin}${iconUrl}`;\n }\n const shouldRenderRemoteIcon =\n !!iconUrl && isIconString && (isRemoteIcon(iconUrl) || isDataURI(iconUrl) || isRelativeIcon);\n\n return { iconUrl, shouldRenderRemoteIcon };\n};\n\n// sanitize id for use in SVG\nexport const sanitizeSvgId = (id: string): string => {\n const encoder = new TextEncoder();\n const bytes = encoder.encode(id.normalize('NFC')); // normalize for consistency\n const hex = Array.from(bytes)\n .map(b => b.toString(16).padStart(2, '0'))\n .join('');\n return `id_${hex}`;\n};\n\n// decode id back to original string\nexport const unsanitizeSvgId = (safeId: string): string => {\n // Remove the \"id_\" prefix if present\n const hex = safeId.startsWith('id_') ? safeId.slice(3) : safeId;\n\n // Convert every 2 hex chars into a byte\n const pairs = hex.match(/.{1,2}/g) || [];\n const bytes = new Uint8Array(pairs.map(byte => parseInt(byte, 16)));\n\n // Decode bytes back to string\n const decoder = new TextDecoder();\n return decoder.decode(bytes);\n};\n\n// create dynamic payload from metadata for event payloads\nexport const createDynamicPayload = (metadata: NodeMetadataType = {}): NodeMetadataPayloadType => {\n const dynamicPayload: NodeMetadataPayloadType = {};\n Object.entries(metadata).forEach(([key, value]) => {\n dynamicPayload[`row.${key}.value`] = value;\n });\n return dynamicPayload;\n};\n\nexport const shouldDimNode = (highlightedNode: NodeTypeStructural | null, currentNodeId: string): boolean => {\n if (!highlightedNode) return false;\n if (highlightedNode.id === currentNodeId) return false;\n if (highlightedNode.children?.includes(currentNodeId)) return false;\n if (highlightedNode.parents?.includes(currentNodeId)) return false;\n\n return true;\n};\n\nexport const shouldHighlightLink = (source: string, target: string, highlightedNode: string | null) => {\n if (!highlightedNode) return false;\n return source === highlightedNode || target === highlightedNode;\n};\n\n// generate node map visual\n// when visual changes occur only change visual node map\ntype UniqueNodesVisualParams = {\n nodeIds: string[];\n nodeColors: string[] | string;\n iconColors: string[] | string;\n icons: IconType[];\n nodeValues: number[];\n resultLimit?: number;\n tooltipHeaderField?: string;\n tooltipNodeFields?: string[];\n metadata?: NodeMetadataType[];\n};\n\nconst getValidColor = (nodeColors: string[] | string, index: number): string | null => {\n const color = typeof nodeColors === 'string' ? nodeColors : nodeColors[index];\n return isColor(color) ? color : null;\n};\n\nexport const getUniqueNodesVisual = ({\n nodeIds,\n nodeColors,\n iconColors,\n icons,\n nodeValues,\n resultLimit,\n tooltipHeaderField,\n tooltipNodeFields = [],\n metadata = [],\n}: UniqueNodesVisualParams): NodeMapVisualType => {\n const nodeMapVisual: NodeMapVisualType = {};\n // add nodes from datasource\n // note: we assume nodeIds match up with nodeTexts, nodeValues, nodeColors, icons, and iconColors\n // nodeIds can be the same as from if the user is not using a secondary data source\n nodeIds.slice(0, resultLimit).forEach((id, i) => {\n if (!nodeMapVisual[id]?.index) {\n const nodeMeta = metadata[i] || {};\n nodeMapVisual[id] = {\n id,\n index: i,\n color: getValidColor(nodeColors, i),\n icon: icons[i],\n iconColor: getValidColor(iconColors, i),\n value: nodeValues[i],\n tooltipFields:\n tooltipNodeFields.length > 0\n ? [...tooltipNodeFields].reduce((acc, fieldName) => {\n if (fieldName in nodeMeta) {\n acc[fieldName] = nodeMeta[fieldName];\n }\n return acc;\n }, {})\n : undefined,\n tooltipHeader: tooltipHeaderField ? nodeMeta[tooltipHeaderField] : undefined,\n };\n }\n });\n\n return nodeMapVisual;\n};\n\n// generate node map only used for structural changes to trigger layout recalculations\nexport const getUniqueNodesStruct = (\n source: string[],\n target: string[],\n nodeIds: string[],\n nodeTexts: string[],\n nodeTypes: string[],\n nodeSizes: number[] | number,\n resultLimit?: number\n): NodeMapStructuralType => {\n // for updates that do require a layout recalculation\n const nodeMapStruct: NodeMapStructuralType = {};\n\n // add nodes from datasource\n // note: we assume nodeIds match up with nodeTexts, nodeValues, nodeColors, icons, and iconColors\n // nodeIds can be the same as source if the user is not using a secondary data source\n nodeIds.slice(0, resultLimit).forEach((id, i) => {\n const truncatedText = truncateTextToMaxLines(nodeTexts[i], FONT, MAX_TEXT_WIDTH, 4);\n const textWidth = Math.max(...truncatedText.map(line => computeTextWidth(line, FONT)));\n const labelHeight =\n truncatedText.length > 0 ? RECT_VERTICAL_PADDING * 2 + LINE_HEIGHT * truncatedText.length : 0;\n const diameter = Math.max(\n MIN_NODE_DIAMETER,\n Math.min(\n MAX_NODE_DIAMETER,\n Array.isArray(nodeSizes) ? nodeSizes[i] : (nodeSizes ?? DEFAULT_NODE_RADIUS * 2)\n )\n );\n const nodeHeight = diameter + RECT_TOP_MARGIN + MIN_NODE_SPACING + labelHeight;\n const nodeWidth = Math.max(textWidth + RECT_HORIZONTAL_PADDING * 2, diameter + MIN_NODE_SPACING * 2);\n if (!nodeMapStruct[id]?.index) {\n nodeMapStruct[id] = {\n id,\n index: i,\n // need label to calculate node size, which will be used in the layout algorithm\n label: nodeTexts[i],\n height: nodeHeight,\n width: nodeWidth,\n type: nodeTypes?.[i],\n size: diameter,\n children: [],\n parents: [],\n x: undefined,\n y: undefined,\n };\n }\n });\n\n // add neighbors to nodes\n // this will make it easier to highlight neighbors on hover\n target.forEach((targetId, index) => {\n const sourceId = source[index];\n // add parent neighbors to nodeMap\n if (targetId && nodeMapStruct[targetId] && !nodeMapStruct[targetId].parents.includes(sourceId)) {\n nodeMapStruct[targetId].parents.push(sourceId);\n }\n // add child neighbors to nodeMap\n if (sourceId && nodeMapStruct[sourceId] && !nodeMapStruct[sourceId].children.includes(targetId)) {\n nodeMapStruct[sourceId].children.push(targetId);\n }\n });\n return nodeMapStruct;\n};\n\nexport const updatePositions = (\n nodeMap: NodeMapStructuralType,\n positions: PositionMapType\n): NodeMapStructuralType => {\n const updatedNodeMap: NodeMapStructuralType = {};\n Object.keys(nodeMap).forEach(id => {\n updatedNodeMap[id] = {\n ...nodeMap[id],\n x: positions?.[id] ? positions[id].x : nodeMap[id].x,\n y: positions?.[id] ? positions[id].y : nodeMap[id].y,\n };\n });\n return updatedNodeMap;\n};\n\nexport const getUniqueLinks = (\n source: string[],\n target: (string | null)[],\n linkColors: string[] | string,\n linkWidth: number[] | number,\n resultLimit?: number\n): LinkMapType => {\n const linkMap: LinkMapType = {};\n source.slice(0, resultLimit).forEach((sourceId, index) => {\n const targetId = target[index];\n const linkColor = getValidColor(linkColors, index);\n const width = typeof linkWidth === 'number' ? linkWidth : (linkWidth?.[index] ?? DEFAULT_LINK_WIDTH);\n const clampedWidth = Number.isNaN(width)\n ? DEFAULT_LINK_WIDTH\n : Math.min(MAX_LINK_WIDTH, Math.max(MIN_LINK_WIDTH, width));\n // check for toId, since we can have links with no target\n if (targetId) {\n const linkId = `${sourceId}-${targetId}`;\n // this will overwrite the link if it already exists, we use the last one in the list\n // this is what the splunkbase network graph does\n linkMap[linkId] = {\n id: linkId,\n source: sourceId,\n target: targetId,\n color: linkColor,\n width: clampedWidth,\n };\n }\n });\n return linkMap;\n};\n\n// generate grid x,y positions for nodes\nexport const getGridLayout = (\n nodeMap: NodeMapStructuralType,\n gridColumns: number | undefined,\n nodeHorizontalPadding = DEFAULT_NODE_HORIZONTAL_SPACING,\n nodeVerticalPadding = DEFAULT_NODE_VERTICAL_SPACING\n): PositionMapType => {\n const positions = {};\n const numOfCols = gridColumns ?? Math.ceil(Math.sqrt(Object.keys(nodeMap).length));\n const nodeMaxWidth = Math.max(...Object.values(nodeMap).map(node => node.width), 0);\n const nodeMaxHeight = Math.max(...Object.values(nodeMap).map(node => node.height), 0);\n const columnWidth = Math.max(nodeMaxWidth, MIN_NODE_CONTAINER_SIZE) + nodeHorizontalPadding;\n const rowHeight = Math.max(nodeMaxHeight, MIN_NODE_CONTAINER_SIZE) + nodeVerticalPadding;\n\n Object.keys(nodeMap).forEach((id, index) => {\n positions[id] = {\n x: (index % numOfCols) * columnWidth + MAX_TEXT_WIDTH,\n y: Math.floor(index / numOfCols) * rowHeight + DEFAULT_NODE_RADIUS,\n };\n });\n return positions;\n};\n\n// use user-defined x and y positions to preset the layout\nexport const getPresetLayout = (\n nodeMap: NodeMapStructuralType,\n xPositions: number[],\n yPositions: number[]\n): PositionMapType => {\n const positionsMap: PositionMapType = {};\n const xPosFormatted = xPositions?.map(x => x + 340);\n const yPosFormatted = yPositions?.map(y => y + 280);\n Object.keys(nodeMap).forEach(id => {\n const { index } = nodeMap[id];\n positionsMap[id] = {\n x: xPosFormatted[index],\n y: yPosFormatted[index],\n };\n });\n return positionsMap;\n};\n\n// this can take a long time for > 200 nodes\n// for 200 it takes 26ms, 1000 takes 2 seconds\n// eventually move to a webworker\nexport const getHierarchicalLayout = (\n nodeMap: NodeMapStructuralType,\n nodeHorizontalPadding = DEFAULT_NODE_HORIZONTAL_SPACING,\n nodeVerticalPadding = DEFAULT_NODE_VERTICAL_SPACING,\n hierarchyDirection: LayoutDirectionType = 'leftRight'\n): PositionMapType => {\n let rankdir: string;\n if (hierarchyDirection === 'topBottom' || hierarchyDirection === 'vertical') {\n rankdir = 'TB';\n } else if (hierarchyDirection === 'bottomTop') {\n rankdir = 'BT';\n } else if (hierarchyDirection === 'rightLeft') {\n rankdir = 'RL';\n } else {\n rankdir = 'LR';\n }\n const isVertical =\n hierarchyDirection === 'topBottom' ||\n hierarchyDirection === 'bottomTop' ||\n hierarchyDirection === 'vertical';\n const edgeSeparation = 24;\n const g = new dagre.graphlib.Graph();\n g.setGraph({\n rankdir,\n nodesep: isVertical ? nodeHorizontalPadding : nodeVerticalPadding,\n ranksep: isVertical ? nodeVerticalPadding : nodeHorizontalPadding,\n edgesep: edgeSeparation,\n });\n // set empty object by default to avoid errors\n g.setDefaultEdgeLabel(() => ({}));\n\n // Add nodes and edges\n Object.values(nodeMap).forEach(node => {\n g.setNode(node.id, {\n width: Math.max(node.width, MIN_NODE_CONTAINER_SIZE),\n // note: the label is beneath the node, so height is not symmetrical around the position, so we need to use the node size\n // add padding to make sure the link looks good in s and z shape\n height: Math.max(node.height, MIN_NODE_CONTAINER_SIZE),\n // useful for incremental layouting\n x: node.x || null,\n y: node.y || null,\n });\n });\n Object.values(nodeMap).forEach(node => {\n if (Array.isArray(node.children)) {\n node.children.forEach(childId => {\n if (nodeMap[childId]) {\n g.setEdge(node.id, childId);\n }\n });\n }\n });\n\n dagre.layout(g);\n\n // Convert result to position map\n const positionMap: PositionMapType = {};\n g.nodes().forEach(id => {\n const node = g.node(id);\n if (node && typeof node.x === 'number' && typeof node.y === 'number') {\n positionMap[id] = {\n // offset to keep top left corner within view\n // this should not be needed after autozoom\n x: node.x + 50,\n y: node.y + 16,\n };\n }\n });\n\n return positionMap;\n};\n\nexport const getLayout = (\n layout: LayoutType,\n nodeMapStruct: NodeMapStructuralType,\n layoutOptions: LayoutOptionsType\n): PositionMapType => {\n const {\n hierarchyDirection,\n xPositions,\n yPositions,\n nodeHorizontalPadding,\n nodeVerticalPadding,\n gridColumns,\n d3NodeList,\n d3LinkList,\n chargeStrength,\n chargeTheta,\n chargeDistanceMax,\n chargeDistanceMin,\n linkDistance,\n linkStrength,\n collidePadding,\n collideStrength,\n clusterStrength,\n } = layoutOptions;\n\n if (layout === 'preset' && xPositions && yPositions) {\n return getPresetLayout(nodeMapStruct, xPositions, yPositions);\n }\n if (layout === 'hierarchical') {\n return getHierarchicalLayout(\n nodeMapStruct,\n nodeHorizontalPadding,\n nodeVerticalPadding,\n hierarchyDirection\n );\n }\n if (layout === 'force' && d3NodeList && d3LinkList) {\n return getForceLayout({\n d3NodeList,\n d3LinkList,\n chargeStrength,\n chargeTheta,\n collidePadding,\n collideStrength,\n clusterStrength,\n chargeDistanceMax,\n chargeDistanceMin,\n linkDistance,\n linkStrength,\n });\n }\n return getGridLayout(nodeMapStruct, gridColumns, nodeHorizontalPadding, nodeVerticalPadding);\n};\n\ntype NodeMapWithPositionsParams = {\n source: string[];\n target: (string | null)[];\n nodeIds: string[];\n nodeTexts: string[];\n nodeTypes: string[];\n nodeSize?: number[] | number;\n layout: LayoutType;\n layoutOptions: LayoutOptionsType;\n resultLimit?: number;\n};\n\nexport const getNodeMapWithPositions = ({\n source,\n target,\n nodeIds,\n nodeTexts,\n nodeTypes,\n nodeSize,\n layout,\n layoutOptions,\n resultLimit,\n}: NodeMapWithPositionsParams) => {\n const nodeMapStruct = getUniqueNodesStruct(\n source,\n target,\n nodeIds,\n nodeTexts,\n nodeTypes,\n nodeSize,\n resultLimit\n );\n let updatedLayoutOptions = { ...layoutOptions };\n\n // generate node list and link list for d3-force algorithm, need a copy because d3-force modifies the objects\n if (layout === 'force') {\n const d3NodeList: ForceNodeType[] =\n layout === 'force' &&\n [...new Set(source.slice(0, resultLimit))].map(sourceId => ({\n id: sourceId,\n // add a type if clustering is on\n type: nodeMapStruct[sourceId]?.type,\n r: Number.isFinite(nodeMapStruct[sourceId]?.size)\n ? nodeMapStruct[sourceId].size / 2 + FORCE_NODE_PADDING\n : DEFAULT_NODE_RADIUS + FORCE_NODE_PADDING,\n }));\n\n const d3LinkList: ForceLinkType[] =\n layout === 'force' &&\n source\n .slice(0, resultLimit)\n .map((sourceId, index) => ({\n source: sourceId,\n target: target[index],\n // add a clusterType if clustering is on\n clusterType:\n nodeMapStruct[sourceId]?.type &&\n (nodeMapStruct[sourceId]?.type === nodeMapStruct[target[index]]?.type\n ? ('same' as ClusterLinkType)\n : ('mixed' as ClusterLinkType)),\n }))\n .filter(x => !!x.target && nodeMapStruct[x.source] && nodeMapStruct[x.target]);\n updatedLayoutOptions = { ...updatedLayoutOptions, d3NodeList, d3LinkList };\n }\n\n const positionMap = getLayout(layout, nodeMapStruct, updatedLayoutOptions);\n return updatePositions(nodeMapStruct, positionMap);\n};\n\n// get the substring that fits in the line\n// use binary search approach for performance\nconst fitCharacters = (context: CanvasRenderingContext2D, maxWidth: number, prefix: string, word: string) => {\n const space = prefix ? ' ' : '';\n let start = 0;\n let end = word.length;\n let bestFit = 0;\n while (start <= end) {\n const mid = Math.floor((start + end) / 2);\n const candidate = prefix ? `${prefix}${space}${word.slice(0, mid)}` : word.slice(0, mid);\n if (context.measureText(candidate).width <= maxWidth) {\n bestFit = mid;\n start = mid + 1;\n } else {\n end = mid - 1;\n }\n }\n return bestFit;\n};\n\nconst handleWordSplit = (\n context: CanvasRenderingContext2D,\n maxWidth: number,\n currentLineText: string,\n nextWord: string\n) => {\n const charsThatFit = fitCharacters(context, maxWidth, currentLineText, nextWord);\n const splitPart = nextWord.slice(0, Math.max(1, charsThatFit));\n const remainder = nextWord.slice(splitPart.length);\n const space = currentLineText ? ' ' : '';\n const currentLineWithSplitPart = `${currentLineText}${space}${splitPart}`;\n\n return { currentLineWithSplit: currentLineWithSplitPart, remainder };\n};\n\nexport const truncateTextToMaxLines = (\n text = '',\n font = `12px ${VIZ_FONT_FAMILY}`,\n maxWidth = 144,\n maxLines = 2\n): string[] => {\n const canvas = document.createElement('canvas');\n const context = canvas.getContext('2d');\n if (!context) return [text];\n context.font = font;\n\n const wordQueue = text.split(/\\s+/).filter(Boolean);\n const outputLines: string[] = [];\n let currentLineText = '';\n\n while (wordQueue.length && outputLines.length < maxLines) {\n const space = currentLineText ? ' ' : '';\n\n // Handle last line with truncation\n if (outputLines.length === maxLines - 1) {\n const remainingText = `${currentLineText}${space}${wordQueue.join(' ')}`;\n const truncatedText = truncateText(remainingText, maxWidth, 'ellipsisEnd', font);\n outputLines.push(truncatedText);\n break;\n }\n\n const nextWord = wordQueue[0];\n const lineWithNextWord = `${currentLineText}${space}${nextWord}`;\n const fitsOnCurrentLine = context.measureText(lineWithNextWord).width <= maxWidth;\n const isNextWordLongerThanMaxWidth = context.measureText(nextWord).width > maxWidth;\n\n // Word fits fully on the current line\n if (fitsOnCurrentLine) {\n currentLineText = lineWithNextWord;\n wordQueue.shift();\n } else if (!isNextWordLongerThanMaxWidth) {\n // Word fits on an empty line but not current line\n outputLines.push(currentLineText);\n currentLineText = '';\n } else {\n // Word is longer than a line and needs to be split\n const { currentLineWithSplit, remainder } = handleWordSplit(\n context,\n maxWidth,\n currentLineText,\n nextWord\n );\n outputLines.push(currentLineWithSplit);\n currentLineText = '';\n wordQueue[0] = remainder;\n }\n }\n\n if (currentLineText && outputLines.length < maxLines) {\n outputLines.push(currentLineText);\n }\n\n return outputLines;\n};\n\nconst getNodeBounds = (node: NodeTypeStructural) => {\n if (!node) return undefined;\n\n const truncatedText = truncateTextToMaxLines(node.label ?? '', FONT, MAX_TEXT_WIDTH, 2);\n const textWidth = truncatedText.length\n ? Math.max(...truncatedText.map(line => computeTextWidth(line, FONT)))\n : 0;\n const labelHeight =\n truncatedText.length > 0 ? RECT_VERTICAL_PADDING * 2 + LINE_HEIGHT * truncatedText.length : 0;\n const nodeLabelHeightWithMargin = RECT_TOP_MARGIN + labelHeight;\n const nodeLabelWidth = textWidth + RECT_HORIZONTAL_PADDING * 2;\n const nodeDiameter = node.size ?? DEFAULT_NODE_RADIUS * 2;\n const nodeRadius = nodeDiameter / 2;\n\n return {\n x: node.x - Math.max(nodeRadius, nodeLabelWidth / 2),\n y: node.y - nodeRadius,\n x2: node.x - Math.max(nodeRadius, nodeLabelWidth / 2) + Math.max(nodeRadius * 2, nodeLabelWidth),\n y2: node.y + nodeRadius + nodeLabelHeightWithMargin,\n };\n};\n\nconst getFullVizBoundsWithPadding = (nodeMap: NodeMapStructuralType, padding: number): BoundingBoxType => {\n let minX = Infinity;\n let minY = Infinity;\n let maxX = -Infinity;\n let maxY = -Infinity;\n if (Object.keys(nodeMap).length === 0) {\n // Handle empty node map case\n return {\n x: 0,\n y: 0,\n width: 0,\n height: 0,\n };\n }\n\n // iterate through nodes to get the outmost nodes for bounds\n // make sure to include label size in the bounds\n Object.values(nodeMap).forEach(node => {\n // getNodeBounds does text truncation + width measurement, so compute once per node.\n const bounds = getNodeBounds(node);\n if (!bounds) return;\n if (!Number.isFinite(bounds.x) || !Number.isFinite(bounds.y)) return;\n if (!Number.isFinite(bounds.x2) || !Number.isFinite(bounds.y2)) return;\n\n minX = Math.min(minX, bounds.x);\n minY = Math.min(minY, bounds.y);\n maxX = Math.max(maxX, bounds.x2);\n maxY = Math.max(maxY, bounds.y2);\n });\n\n return {\n x: Number.isFinite(minX) ? minX - padding : 0,\n y: Number.isFinite(minY) ? minY - padding : 0,\n width: Number.isFinite(maxX) && Number.isFinite(minX) ? maxX - minX + padding * 2 : 0,\n height: Number.isFinite(maxY) && Number.isFinite(minY) ? maxY - minY + padding * 2 : 0,\n };\n};\n\nexport const getAutoZoomAndPan = (\n nodeMap: NodeMapStructuralType,\n containerWidth: number,\n containerHeight: number\n) => {\n if (Object.keys(nodeMap).length === 0) {\n // Handle empty node map case\n return {\n autoZoomLevel: 1,\n autoCenter: [0, 0],\n };\n }\n const { width, height, x, y } = getFullVizBoundsWithPadding(nodeMap, DEFAULT_NODE_RADIUS);\n\n // calculate the zoom level based on the bounding box size\n const zoomLevel = Math.min(containerWidth / width, containerHeight / height);\n\n const centerX = x + width / 2;\n const centerY = y + height / 2;\n\n return { autoZoomLevel: Math.min(zoomLevel, MAX_ZOOM), autoCenter: [centerX, centerY] };\n};\n\nexport const nearlyEqual = (a: number, b: number, tolerance = 1e-4, usePercentage = false) => {\n if (!usePercentage) {\n return Math.abs(a - b) < tolerance;\n }\n\n const maxAbs = Math.max(Math.abs(a), Math.abs(b));\n\n if (maxAbs === 0) {\n return Math.abs(a - b) < tolerance;\n }\n\n return Math.abs(a - b) / maxAbs <= tolerance;\n};\n\nexport const calculateMinZoomBounds = (autoZoomLevel: number, height, width) => {\n // min zoom value is 25% of auto zoom value\n // which is the same as clicking on the zoom out control twice\n const minZoomLevel = autoZoomLevel / (1.5 * 1.5);\n const minZoomWidth = width / minZoomLevel;\n const minZoomHeight = height / minZoomLevel;\n\n return { minZoomLevel, minZoomHeight, minZoomWidth };\n};\n\nexport const getZoomDisableState = (\n currentZoom: number | null | undefined,\n autoZoomLevel: number,\n minZoomLevel: number\n) => {\n const currentZoomLevel = currentZoom ?? autoZoomLevel;\n const zoomPercentTolerance = 0.001; // 0.1%\n const disableZoomIn =\n currentZoomLevel >= MAX_ZOOM || nearlyEqual(currentZoomLevel, MAX_ZOOM, zoomPercentTolerance, true);\n const disableZoomOut =\n currentZoomLevel <= minZoomLevel ||\n nearlyEqual(currentZoomLevel, minZoomLevel, zoomPercentTolerance, true);\n\n return { disableZoomIn, disableZoomOut };\n};\n\nexport const getDefaultPreferPorts = (\n layout: LayoutType,\n hierarchyDirection: LayoutDirectionType\n): PreferPortsType => {\n if (layout === 'hierarchical') {\n if (hierarchyDirection === 'topBottom' || hierarchyDirection === 'bottomTop') {\n return 'vertical';\n }\n return 'horizontal';\n }\n return 'none';\n};\n\n/// ////////////////////////////\n// LINK STYLING FUNCTIONS\n/// ////////////////////////////\n\ntype StartEndAngleParams = {\n sourcePosition: NodePositionType;\n targetPosition: NodePositionType;\n linkStyle: LinkStyleType;\n showDirection: ShowDirectionType;\n isBidirectional: boolean;\n preferPorts: PreferPortsType;\n startMargin: number;\n endMargin: number;\n linkWidth: number;\n arrowWidth: number;\n};\n\nconst getStartAndEndAngle = ({\n sourcePosition,\n targetPosition,\n linkStyle,\n showDirection,\n isBidirectional,\n preferPorts,\n startMargin,\n endMargin,\n linkWidth,\n arrowWidth,\n}: StartEndAngleParams) => {\n // this is the angle from the line that connects the start and end\n const angle = Math.atan2(targetPosition.y - sourcePosition.y, targetPosition.x - sourcePosition.x);\n\n // angle adjustment used to snap edge to nearest of 4 ports for z and s edges\n // also used to offset straight and c edges for one way edges\n let diffAngleStart;\n let diffAngleEnd;\n\n if (linkStyle === 'straight' || linkStyle === 'c') {\n diffAngleStart = Math.atan((linkWidth + arrowWidth) / startMargin) / 2;\n diffAngleEnd = Math.atan((linkWidth + arrowWidth) / endMargin) / 2;\n } else {\n // restrict to horizontal or vertical ports\n const portAngle = getIsHorizontalDominant(preferPorts, sourcePosition, targetPosition)\n ? Math.PI * Math.round(angle / Math.PI)\n : Math.PI * Math.round((angle + Math.PI / 2) / Math.PI) - Math.PI / 2;\n diffAngleStart = portAngle - angle;\n diffAngleEnd = diffAngleStart;\n }\n\n let startDiffToggle = 1;\n let endDiffToggle = 1;\n if (linkStyle === 'straight' || linkStyle === 'c') {\n // if direction is one-way and there are 2 directions, we want the lines to start slightly off center so they don't overlap\n startDiffToggle = showDirection === 'oneWay' && isBidirectional ? -1 : 0;\n endDiffToggle = showDirection === 'oneWay' && isBidirectional ? 1 : 0;\n }\n\n const startAngleOffset = startDiffToggle * diffAngleStart;\n const endAngleOffset = endDiffToggle * diffAngleEnd;\n\n return { startAngle: angle + startAngleOffset, endAngle: angle + endAngleOffset };\n};\n\nexport const getIsHorizontalDominant = (\n preferPorts: PreferPortsType,\n sourcePosition: NodePositionType,\n targetPosition: NodePositionType\n) => {\n const minDistanceHorizontal = 72;\n const minDistanceVertical = 72;\n\n const isHorizontalDistanceDominant =\n Math.abs(sourcePosition.x - targetPosition.x) > Math.abs(sourcePosition.y - targetPosition.y);\n\n // use none by default if value is invalid\n if (preferPorts === 'none' || (preferPorts !== 'horizontal' && preferPorts !== 'vertical')) {\n return isHorizontalDistanceDominant;\n }\n if (\n preferPorts === 'horizontal' &&\n (isHorizontalDistanceDominant ||\n Math.abs(sourcePosition.x - targetPosition.x) > minDistanceHorizontal)\n ) {\n return true;\n }\n if (preferPorts === 'vertical' && Math.abs(sourcePosition.y - targetPosition.y) < minDistanceVertical) {\n return true;\n }\n return false;\n};\n\ntype EdgePathParams = {\n sourcePosition: NodePositionType;\n targetPosition: NodePositionType;\n startWithArrowOffset: NodePositionType;\n endWithArrowOffset: NodePositionType;\n start: NodePositionType;\n end: NodePositionType;\n middle: NodePositionType;\n preferPorts: PreferPortsType;\n};\n\nconst getSPath = ({\n sourcePosition,\n targetPosition,\n startWithArrowOffset,\n endWithArrowOffset,\n start,\n end,\n middle,\n preferPorts,\n}: EdgePathParams) => {\n // Horizontal-dominant S\n const isHorizontalDominant = getIsHorizontalDominant(preferPorts, sourcePosition, targetPosition);\n\n if (isHorizontalDominant) {\n const segment = (end.x - start.x) / 7;\n return `\n M ${startWithArrowOffset.x} ${startWithArrowOffset.y} \n L ${start.x + segment} ${start.y} \n C ${middle.x + segment} ${start.y}, ${middle.x - segment} ${end.y}, ${end.x - segment} ${end.y} \n L ${endWithArrowOffset.x} ${endWithArrowOffset.y}\n `;\n }\n const segment = (end.y - start.y) / 7;\n // Vertical-dominant S\n return `\n M ${startWithArrowOffset.x} ${startWithArrowOffset.y} \n L ${start.x} ${start.y + segment} \n C ${start.x} ${middle.y + segment}, ${end.x} ${middle.y - segment}, ${end.x} ${end.y - segment}\n L ${endWithArrowOffset.x} ${endWithArrowOffset.y}\n `;\n};\n\nconst getZPath = ({\n sourcePosition,\n targetPosition,\n startWithArrowOffset,\n endWithArrowOffset,\n start,\n end,\n middle,\n preferPorts,\n}: EdgePathParams) => {\n // radius for corner rounding\n const radius = 8;\n // straight line, no need for Z\n if (\n Math.abs(sourcePosition.x - targetPosition.x) < radius * 2 ||\n Math.abs(sourcePosition.y - targetPosition.y) < radius * 2\n ) {\n return `\n M ${startWithArrowOffset.x} ${startWithArrowOffset.y} \n L ${endWithArrowOffset.x} ${endWithArrowOffset.y}\n `;\n }\n // Horizontal-dominant Z\n const isHorizontalDominant = getIsHorizontalDominant(preferPorts, sourcePosition, targetPosition);\n\n if (isHorizontalDominant) {\n const directionX = end.x > start.x ? 1 : -1;\n const directionY = end.y > start.y ? 1 : -1;\n\n return `\n M ${startWithArrowOffset.x} ${startWithArrowOffset.y}\n L ${middle.x - directionX * radius} ${start.y}\n Q ${middle.x} ${start.y} ${middle.x} ${start.y + directionY * radius}\n L ${middle.x} ${end.y - directionY * radius}\n Q ${middle.x} ${end.y} ${middle.x + directionX * radius} ${end.y}\n L ${endWithArrowOffset.x} ${endWithArrowOffset.y}\n `;\n }\n // Vertical-dominant Z\n const directionX = end.x > start.x ? 1 : -1;\n const directionY = end.y > start.y ? 1 : -1;\n\n return `\n M ${startWithArrowOffset.x} ${startWithArrowOffset.y}\n L ${start.x} ${middle.y - directionY * radius}\n Q ${start.x} ${middle.y} ${start.x + directionX * radius} ${middle.y}\n L ${end.x - directionX * radius} ${middle.y}\n Q ${end.x} ${middle.y} ${end.x} ${middle.y + directionY * radius}\n L ${endWithArrowOffset.x} ${endWithArrowOffset.y}\n `;\n};\n\nconst getCPath = (\n startWithArrowOffset: NodePositionType,\n endWithArrowOffset: NodePositionType,\n start: NodePositionType,\n end: NodePositionType\n) => {\n const angle = Math.atan2(end.y - start.y, end.x - start.x);\n const edgeCurve = 24;\n const dx = (end.x - start.x) ** 2;\n const dy = (end.y - start.y) ** 2;\n const ds = Math.sqrt(dx + dy);\n // control points used for curved edge\n const curvedqx =\n (end.x - start.x) / 2 + start.x - ((edgeCurve * ds) / 400) * Math.cos(angle + Math.PI / 2);\n const curvedqy =\n (end.y - start.y) / 2 + start.y - ((edgeCurve * ds) / 400) * Math.sin(angle + Math.PI / 2);\n\n const linkPath = `\n M ${startWithArrowOffset.x} ${startWithArrowOffset.y} \n Q ${curvedqx} ${curvedqy} ${endWithArrowOffset.x} ${endWithArrowOffset.y}\n `;\n return linkPath;\n};\n\nexport const getArrowHeight = (linkSize: number, linkWidth: number): number => {\n if (linkSize === 1) return 5;\n // min linkSize is 1 so this will not divide by 0\n return linkWidth * (0.5 + 1 / linkSize);\n};\n\nexport const getArrowWidth = (linkSize: number): number => {\n return Math.round(linkSize < 4 ? 8 : linkSize * (2.1 - linkSize / 40));\n};\n\nexport const getMinDistanceForArrow = (arrowHeight: number): number => {\n return arrowHeight + 12;\n};\n\nexport const getMinDistanceForCurve = (arrowHeight: number, linkStyle: LinkStyleType | undefined): number => {\n return linkStyle === 'c' ? arrowHeight * 2 + 12 : arrowHeight * 5 + 12;\n};\n\ntype LinkRenderInfoParams = {\n linkStyle: LinkStyleType;\n showDirection: ShowDirectionType;\n preferPorts: PreferPortsType;\n sourcePosition: NodePositionType;\n targetPosition: NodePositionType;\n sourceSize: number;\n targetSize: number;\n arrowHeight: number;\n};\n\nexport const getLinkRenderInfo = ({\n linkStyle,\n showDirection,\n preferPorts,\n sourcePosition,\n targetPosition,\n sourceSize,\n targetSize,\n arrowHeight,\n}: LinkRenderInfoParams) => {\n const isHorizontalDominant = getIsHorizontalDominant(preferPorts, sourcePosition, targetPosition);\n const verticalDistance = Math.abs(targetPosition.y - sourcePosition.y);\n const horizontalDistance = Math.abs(targetPosition.x - sourcePosition.x);\n const minDistanceForArrow = getMinDistanceForArrow(\n showDirection === 'bidirection' ? arrowHeight * 2 : arrowHeight\n );\n const minDistanceForCurve = getMinDistanceForCurve(arrowHeight, linkStyle);\n const distance = Math.hypot(horizontalDistance, verticalDistance) - (sourceSize / 2 + targetSize / 2);\n const shouldHideArrow = distance < minDistanceForArrow;\n const shouldShowBasicLinkStyle = isHorizontalDominant\n ? horizontalDistance - (sourceSize / 2 + targetSize / 2) < minDistanceForCurve\n : verticalDistance - (sourceSize / 2 + targetSize / 2) < minDistanceForCurve;\n const effectiveLinkStyle = shouldShowBasicLinkStyle ? 'straight' : linkStyle;\n const effectiveShowDirection = shouldHideArrow ? 'none' : showDirection;\n\n return { effectiveLinkStyle, effectiveShowDirection };\n};\n\n// generate edge path for svg, inspired from Tatsuya's code sandbox\n// https://codesandbox.io/p/devbox/network-graph-demo-forked-lv8pnr?file=%2Fsrc%2Findex.mjs%3A517%2C4&workspaceId=ws_TYfFvbbyHMCRyRr9xg5ppW\ntype GetEdgePathParams = {\n linkStyle: LinkStyleType;\n sourcePosition: NodePositionType;\n targetPosition: NodePositionType;\n isBidirectional: boolean;\n showDirection: ShowDirectionType;\n arrowHeight?: number;\n arrowWidth?: number;\n preferPorts?: PreferPortsType;\n sourceNodeSize?: number;\n targetNodeSize?: number;\n linkWidth?: number;\n};\n\nexport const getEdgePath = ({\n linkStyle,\n sourcePosition,\n targetPosition,\n isBidirectional,\n showDirection,\n arrowHeight = 6,\n arrowWidth = 8,\n preferPorts = 'none' as PreferPortsType,\n sourceNodeSize = DEFAULT_NODE_RADIUS * 2,\n targetNodeSize = DEFAULT_NODE_RADIUS * 2,\n linkWidth = 2,\n}: GetEdgePathParams) => {\n const getNodeMargin = (nodeSize?: number) => {\n const radius = (nodeSize ?? DEFAULT_NODE_RADIUS * 2) / 2;\n return radius + arrowHeight + MIN_NODE_SPACING;\n };\n const startMargin = getNodeMargin(sourceNodeSize);\n const endMargin = getNodeMargin(targetNodeSize);\n const validatedlinkStyleType =\n linkStyle !== 'c' && linkStyle !== 'z' && linkStyle !== 's' ? 'straight' : linkStyle;\n // offset to extend the line if there is no arrow tip, this may change depending on the width of the arrow\n const startArrowTipOffset = isBidirectional && showDirection === 'bidirection' ? 0 : -arrowHeight;\n const endArrowTipOffset = showDirection === 'none' ? -arrowHeight : 0;\n // get angle to draw edge\n const { startAngle, endAngle } = getStartAndEndAngle({\n sourcePosition,\n targetPosition,\n linkStyle: validatedlinkStyleType,\n showDirection,\n isBidirectional,\n preferPorts,\n startMargin,\n endMargin,\n linkWidth,\n arrowWidth,\n });\n\n // start and end position used for calculations\n const start = {\n x: sourcePosition.x + Math.cos(startAngle) * startMargin,\n y: sourcePosition.y + Math.sin(startAngle) * startMargin,\n };\n const end = {\n x: targetPosition.x - Math.cos(endAngle) * endMargin,\n y: targetPosition.y - Math.sin(endAngle) * endMargin,\n };\n // start and end position with offset for missing arrow tip\n const startWithArrowOffset = {\n x: start.x + Math.cos(startAngle) * startArrowTipOffset,\n y: start.y + Math.sin(startAngle) * startArrowTipOffset,\n };\n const endWithArrowOffset = {\n x: end.x - Math.cos(endAngle) * endArrowTipOffset,\n y: end.y - Math.sin(endAngle) * endArrowTipOffset,\n };\n // half-way position used as control points for z and s edges\n const middle = {\n x: (end.x - start.x) / 2 + start.x,\n y: (end.y - start.y) / 2 + start.y,\n };\n\n // Z edge\n if (linkStyle === 'z') {\n return getZPath({\n sourcePosition,\n targetPosition,\n startWithArrowOffset,\n endWithArrowOffset,\n start,\n end,\n middle,\n preferPorts,\n });\n }\n // S edge\n if (linkStyle === 's') {\n return getSPath({\n sourcePosition,\n targetPosition,\n startWithArrowOffset,\n endWithArrowOffset,\n start,\n end,\n middle,\n preferPorts,\n });\n }\n // Curved edge\n if (linkStyle === 'c') {\n return getCPath(startWithArrowOffset, endWithArrowOffset, start, end);\n }\n // Straight edge by default\n return `\n M ${startWithArrowOffset.x} ${startWithArrowOffset.y} \n L ${endWithArrowOffset.x} ${endWithArrowOffset.y}\n `;\n};\n"],
5
+ "mappings": "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",
6
6
  "names": ["defaultImport", "defaultImport", "defaultImport", "defaultImport", "defaultImport", "defaultImport", "defaultImport", "config", "defaultImport", "defaultImport", "themes", "defaultImport", "defaultImport", "defaultImport", "defaultImport", "defaultImport", "isDynamicOption", "NetworkGraphIcon", "NetworkGraphPlaceholderIcon", "optionsSchema"]
7
7
  }