@memberjunction/ng-clustering 0.0.1 → 5.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +441 -27
- package/dist/__tests__/clustering-service.test.d.ts +2 -0
- package/dist/__tests__/clustering-service.test.d.ts.map +1 -0
- package/dist/__tests__/clustering-service.test.js +487 -0
- package/dist/__tests__/clustering-service.test.js.map +1 -0
- package/dist/__tests__/clustering-types.test.d.ts +2 -0
- package/dist/__tests__/clustering-types.test.d.ts.map +1 -0
- package/dist/__tests__/clustering-types.test.js +254 -0
- package/dist/__tests__/clustering-types.test.js.map +1 -0
- package/dist/__tests__/index.test.d.ts +2 -0
- package/dist/__tests__/index.test.d.ts.map +1 -0
- package/dist/__tests__/index.test.js +7 -0
- package/dist/__tests__/index.test.js.map +1 -0
- package/dist/__tests__/scatter-component-logic.test.d.ts +2 -0
- package/dist/__tests__/scatter-component-logic.test.d.ts.map +1 -0
- package/dist/__tests__/scatter-component-logic.test.js +494 -0
- package/dist/__tests__/scatter-component-logic.test.js.map +1 -0
- package/dist/lib/cluster-config-panel.component.d.ts +274 -0
- package/dist/lib/cluster-config-panel.component.d.ts.map +1 -0
- package/dist/lib/cluster-config-panel.component.js +732 -0
- package/dist/lib/cluster-config-panel.component.js.map +1 -0
- package/dist/lib/cluster-scatter.component.d.ts +514 -0
- package/dist/lib/cluster-scatter.component.d.ts.map +1 -0
- package/dist/lib/cluster-scatter.component.js +1357 -0
- package/dist/lib/cluster-scatter.component.js.map +1 -0
- package/dist/lib/clustering.module.d.ts +11 -0
- package/dist/lib/clustering.module.d.ts.map +1 -0
- package/dist/lib/clustering.module.js +34 -0
- package/dist/lib/clustering.module.js.map +1 -0
- package/dist/lib/clustering.service.d.ts +43 -0
- package/dist/lib/clustering.service.d.ts.map +1 -0
- package/dist/lib/clustering.service.js +253 -0
- package/dist/lib/clustering.service.js.map +1 -0
- package/dist/lib/clustering.types.d.ts +271 -0
- package/dist/lib/clustering.types.d.ts.map +1 -0
- package/dist/lib/clustering.types.js +74 -0
- package/dist/lib/clustering.types.js.map +1 -0
- package/dist/public-api.d.ts +6 -0
- package/dist/public-api.d.ts.map +1 -0
- package/dist/public-api.js +11 -0
- package/dist/public-api.js.map +1 -0
- package/package.json +40 -6
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cluster-scatter.component.js","sourceRoot":"","sources":["../../src/lib/cluster-scatter.component.ts","../../src/lib/cluster-scatter.component.html"],"names":[],"mappings":"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","sourcesContent":["/**\n * @fileoverview SVG scatter plot component for cluster visualization.\n *\n * `ClusterScatterComponent` renders an interactive 2D scatter plot where each\n * dot represents a clustered entity record. It supports:\n *\n * - **Zoom / pan** via mouse wheel and drag (SVG viewBox manipulation)\n * - **Tooltips** on hover showing point metadata\n * - **Legend** overlay in the bottom-left corner\n * - **Radial gradient glow** per cluster centroid\n * - **Cancelable `BeforeXXX` events** so consumers can intercept interactions\n * - **Programmatic selection and zoom** via public methods\n * - **Full customization** of dot radius, opacity, color palette, and more\n *\n * @example\n * ```html\n * <mj-cluster-scatter\n * [Points]=\"result.Points\"\n * [Clusters]=\"result.Clusters\"\n * [DotRadius]=\"6\"\n * [ShowTooltip]=\"true\"\n * [ColorPalette]=\"['#ff0000','#00ff00','#0000ff']\"\n * (BeforePointClick)=\"onBeforeClick($event)\"\n * (PointClicked)=\"onPointClicked($event)\"\n * (AfterClusteringComplete)=\"onClustered($event)\"\n * (SelectionChanged)=\"onSelectionChanged($event)\">\n * </mj-cluster-scatter>\n * ```\n */\n\nimport {\n Component,\n Input,\n Output,\n EventEmitter,\n ElementRef,\n ChangeDetectorRef,\n AfterViewInit,\n OnDestroy,\n OnChanges,\n SimpleChanges,\n ViewChild,\n ViewEncapsulation,\n inject,\n} from '@angular/core';\nimport {\n ClusterPoint,\n ClusterInfo,\n ClusterVisualizationResult,\n CancelableEvent,\n ViewportRect,\n ViewportTransform,\n ClusterSelectedEvent,\n CLUSTER_COLORS,\n} from './clustering.types';\n\n@Component({\n standalone: false,\n selector: 'mj-cluster-scatter',\n templateUrl: './cluster-scatter.component.html',\n styleUrls: ['./cluster-scatter.component.css'],\n encapsulation: ViewEncapsulation.None,\n})\nexport class ClusterScatterComponent implements AfterViewInit, OnDestroy, OnChanges {\n private cdr = inject(ChangeDetectorRef);\n\n // ================================================================\n // Data Inputs\n // ================================================================\n\n /**\n * Array of 2D-projected points to render.\n *\n * Each point carries its cluster assignment, label, and metadata.\n * Typically produced by `ClusteringService.RunPipeline()`.\n */\n @Input() Points: ClusterPoint[] = [];\n\n /**\n * Cluster summary information used for the legend and color mapping.\n */\n @Input() Clusters: ClusterInfo[] = [];\n\n /**\n * When `true`, a semi-transparent loading overlay is displayed\n * on top of the scatter plot.\n */\n @Input() IsLoading = false;\n\n // ================================================================\n // Customization Inputs\n // ================================================================\n\n /**\n * Base radius (in SVG units) for each data point dot.\n *\n * @default 5\n */\n @Input() DotRadius = 5;\n\n /**\n * Base opacity for data point dots (0 to 1).\n *\n * When a point is hovered, non-hovered points are dimmed below this value.\n *\n * @default 0.75\n */\n @Input() DotOpacity = 0.75;\n\n /**\n * Radius used for the highlight ring on hovered or selected points.\n *\n * @default 8\n */\n @Input() HighlightRadius = 8;\n\n /**\n * Whether to show the color-coded cluster legend overlay.\n *\n * @default true\n */\n @Input() ShowLegend = true;\n\n /**\n * Whether to show the tooltip popup on point hover.\n *\n * When `false`, `BeforePointHover` and `PointHovered` still fire but\n * the built-in tooltip DOM is not rendered.\n *\n * @default true\n */\n @Input() ShowTooltip = true;\n\n /**\n * Which metadata keys to display inside the tooltip.\n *\n * When empty (the default), **all** metadata keys are shown.\n *\n * @default []\n *\n * @example\n * ```html\n * <mj-cluster-scatter [TooltipFields]=\"['EntityRecordID', 'Score']\">\n * </mj-cluster-scatter>\n * ```\n */\n @Input() TooltipFields: string[] = [];\n\n /**\n * Override the default cluster color palette.\n *\n * Colors are assigned to clusters by index: cluster 0 gets\n * `ColorPalette[0]`, cluster 1 gets `ColorPalette[1]`, etc.\n * Wraps around when there are more clusters than colors.\n *\n * @default CLUSTER_COLORS (10 accessible colors)\n */\n @Input() ColorPalette: string[] = [];\n\n /**\n * Enable or disable zoom via mouse wheel.\n *\n * @default true\n */\n @Input() EnableZoom = true;\n\n /**\n * Enable or disable panning via click-and-drag.\n *\n * @default true\n */\n @Input() EnablePan = true;\n\n /**\n * Whether scatter plot transitions (zoom, highlight) should be animated.\n *\n * @default true\n */\n @Input() AnimateTransitions = true;\n\n /**\n * Externally controlled set of selected point IDs (by `VectorKey`).\n *\n * When provided, the component renders selection rings for matching\n * points and emits `SelectionChanged` when the user modifies the set\n * interactively.\n *\n * @default (empty Set)\n *\n * @example\n * ```html\n * <mj-cluster-scatter\n * [SelectedPointIds]=\"mySelectedIds\"\n * (SelectionChanged)=\"mySelectedIds = $event\">\n * </mj-cluster-scatter>\n * ```\n */\n @Input() SelectedPointIds: Set<string> = new Set<string>();\n\n /**\n * Minimum zoom level (smallest allowed viewBox multiplier).\n *\n * A value of `0.5` means the user can zoom in until the viewBox is\n * half of the default 1000x700 size.\n *\n * @default 0.5\n */\n @Input() MinZoom = 0.5;\n\n /**\n * Maximum zoom level (largest allowed viewBox multiplier).\n *\n * A value of `10` means the user can zoom out until the viewBox is\n * 10x the default 1000x700 size.\n *\n * @default 10\n */\n @Input() MaxZoom = 10;\n\n // ================================================================\n // Outputs (standard events)\n // ================================================================\n\n /**\n * Emitted when a point is clicked (after `BeforePointClick` completes\n * without cancellation).\n */\n @Output() PointClicked = new EventEmitter<ClusterPoint>();\n\n /**\n * Emitted when the user clicks \"Open Record\" in the detail panel.\n * The parent should wire this to `NavigationService.OpenEntityRecord()`.\n */\n @Output() OpenRecordRequested = new EventEmitter<ClusterPoint>();\n\n /**\n * Emitted when the mouse enters or leaves a data point.\n * Emits `null` on mouse-leave.\n */\n @Output() PointHovered = new EventEmitter<ClusterPoint | null>();\n\n // ================================================================\n // Outputs (cancelable \"Before\" events)\n // ================================================================\n\n /**\n * Fires **before** `PointClicked`. Set `event.Cancel = true` to\n * suppress the click entirely.\n *\n * @example\n * ```typescript\n * onBeforeClick(event: CancelableEvent<ClusterPoint>): void {\n * if (event.Data.ClusterId === -1) {\n * event.Cancel = true; // ignore outlier clicks\n * }\n * }\n * ```\n */\n @Output() BeforePointClick = new EventEmitter<CancelableEvent<ClusterPoint>>();\n\n /**\n * Fires **before** the tooltip is shown on hover. Set\n * `event.Cancel = true` to suppress the tooltip for this point.\n */\n @Output() BeforePointHover = new EventEmitter<CancelableEvent<ClusterPoint>>();\n\n /**\n * Fires **before** a zoom or pan operation is applied. Set\n * `event.Cancel = true` to reject the viewport change.\n *\n * The payload contains the **proposed** viewport rectangle.\n */\n @Output() BeforeZoom = new EventEmitter<CancelableEvent<ViewportRect>>();\n\n // ================================================================\n // Outputs (informational events)\n // ================================================================\n\n /**\n * Fires after new `Points` and `Clusters` are received and centroids\n * have been computed, delivering the full visualization result.\n */\n @Output() AfterClusteringComplete = new EventEmitter<ClusterVisualizationResult>();\n\n /**\n * Fires when the user clicks a cluster label or legend swatch.\n */\n @Output() ClusterSelected = new EventEmitter<ClusterSelectedEvent>();\n\n /**\n * Fires whenever the set of selected point IDs changes\n * (via user click or programmatic `SelectPoints` / `ClearSelection`).\n */\n @Output() SelectionChanged = new EventEmitter<Set<string>>();\n\n /**\n * Fires after every zoom or pan that changes the visible viewport.\n */\n @Output() ViewportChanged = new EventEmitter<ViewportRect>();\n\n // ================================================================\n // Internal State\n // ================================================================\n\n /** SVG viewBox parameters: [minX, minY, width, height]. */\n public ViewBox = [0, 0, 1000, 700];\n\n /** Get the current viewport transform for saving/restoring. */\n public GetViewportTransform(): ViewportTransform {\n return {\n TranslateX: this.ViewBox[0],\n TranslateY: this.ViewBox[1],\n Scale: this.defaultWidth / this.ViewBox[2],\n };\n }\n\n /** Restore a previously saved viewport transform. */\n public SetViewportTransform(vt: ViewportTransform): void {\n const width = this.defaultWidth / vt.Scale;\n const height = this.defaultHeight / vt.Scale;\n this.ViewBox = [vt.TranslateX, vt.TranslateY, width, height];\n }\n\n /** Currently hovered point (null when idle). */\n public HoveredPoint: ClusterPoint | null = null;\n /** Screen-space tooltip X position. */\n public TooltipX = 0;\n /** Screen-space tooltip Y position. */\n public TooltipY = 0;\n /** Currently highlighted (single-clicked) point key. */\n public HighlightedKey: string | null = null;\n\n /** Background grid lines for visual orientation. */\n public HorizontalGridLines: number[] = [175, 350, 525];\n /** Background vertical grid lines. */\n public VerticalGridLines: number[] = [250, 500, 750];\n\n /** Computed cluster centroids used for radial glow effects. */\n public ClusterCentroids: Array<{ ClusterId: number; Cx: number; Cy: number; R: number; Color: string }> = [];\n\n /** Filtered metadata entries for the tooltip (computed on hover). */\n public TooltipEntries: Array<{ Key: string; Value: unknown }> = [];\n\n // ================================================================\n // Detail Panel State\n // ================================================================\n\n /** The currently selected point shown in the detail panel. */\n public SelectedPoint: ClusterPoint | null = null;\n\n /** Whether the detail panel is visible. */\n public ShowDetailPanel = false;\n\n /** Metadata entries for the detail panel (computed on selection). */\n public DetailEntries: Array<{ Key: string; Value: unknown }> = [];\n\n /** Cluster members for the selected point's cluster. */\n public ClusterMembers: ClusterPoint[] = [];\n\n /** Whether the cluster members list is expanded. */\n public ClusterMembersExpanded = true;\n\n @ViewChild('svgElement', { static: false }) private svgRef!: ElementRef<SVGSVGElement>;\n\n // Pan/zoom state\n private isPanning = false;\n private panStartX = 0;\n private panStartY = 0;\n private panStartViewBox = [0, 0, 1000, 700];\n private boundOnMouseMove: ((e: MouseEvent) => void) | null = null;\n private boundOnMouseUp: ((e: MouseEvent) => void) | null = null;\n\n /** Default viewBox dimensions used for zoom-limit calculations. */\n private readonly defaultWidth = 1000;\n private readonly defaultHeight = 700;\n\n // ================================================================\n // Lifecycle\n // ================================================================\n\n ngAfterViewInit(): void {\n this.boundOnMouseMove = this.onMouseMove.bind(this);\n this.boundOnMouseUp = this.onMouseUp.bind(this);\n document.addEventListener('mousemove', this.boundOnMouseMove);\n document.addEventListener('mouseup', this.boundOnMouseUp);\n }\n\n ngOnDestroy(): void {\n if (this.boundOnMouseMove) document.removeEventListener('mousemove', this.boundOnMouseMove);\n if (this.boundOnMouseUp) document.removeEventListener('mouseup', this.boundOnMouseUp);\n }\n\n ngOnChanges(changes: SimpleChanges): void {\n this.computeCentroids();\n\n // Emit AfterClusteringComplete when new data arrives\n if ((changes['Points'] || changes['Clusters']) && this.Points.length > 0 && this.Clusters.length > 0) {\n this.AfterClusteringComplete.emit({\n Points: this.Points,\n Clusters: this.Clusters,\n Metrics: {\n SilhouetteScore: 0,\n ClusterCount: this.Clusters.length,\n ComputationTimeMs: 0,\n RecordCount: this.Points.length,\n OutlierCount: this.Points.filter(p => p.ClusterId < 0).length,\n },\n Config: {\n EntityName: '',\n EntityDocumentID: '',\n Algorithm: 'kmeans',\n K: this.Clusters.length,\n Epsilon: 0,\n MinPoints: 0,\n DistanceMetric: 'cosine',\n MaxRecords: this.Points.length,\n Filter: '',\n },\n });\n }\n }\n\n // ================================================================\n // Template Helpers\n // ================================================================\n\n /**\n * Returns the current SVG `viewBox` as a space-separated string.\n *\n * @returns e.g. `\"0 0 1000 700\"`\n */\n public GetViewBoxString(): string {\n return this.ViewBox.join(' ');\n }\n\n /**\n * Resolve the display color for a given point based on its cluster\n * assignment and the active `ColorPalette`.\n *\n * @param point The point to colorize.\n * @returns CSS color string.\n */\n public GetPointColor(point: ClusterPoint): string {\n if (point.ClusterId < 0) return '#64748b'; // outlier gray\n const palette = this.getActivePalette();\n const cluster = this.Clusters.find(c => c.Id === point.ClusterId);\n return cluster?.Color ?? palette[point.ClusterId % palette.length];\n }\n\n /**\n * Compute the SVG radius for a data point.\n *\n * Selected points use the larger `HighlightRadius`; all others use `DotRadius`.\n *\n * @param point The point to size.\n * @returns Radius in SVG units.\n */\n public GetPointRadius(point: ClusterPoint): number {\n if (this.SelectedPointIds.has(point.VectorKey)) {\n return this.HighlightRadius;\n }\n return this.DotRadius;\n }\n\n /**\n * Compute the fill opacity for a data point.\n *\n * Non-hovered points are dimmed when another point is hovered.\n *\n * @param point The point to evaluate.\n * @returns Opacity value between 0 and 1.\n */\n public GetPointOpacity(point: ClusterPoint): number {\n if (this.HoveredPoint && this.HoveredPoint.VectorKey !== point.VectorKey) {\n return this.DotOpacity * 0.5;\n }\n return this.DotOpacity;\n }\n\n /**\n * Handle mouseenter on a data point: show tooltip, fire hover events.\n *\n * @param point The hovered point.\n * @param event The native mouse event (used for tooltip positioning).\n */\n public OnPointMouseEnter(point: ClusterPoint, event: MouseEvent): void {\n const cancelable: CancelableEvent<ClusterPoint> = { Data: point, Cancel: false };\n this.BeforePointHover.emit(cancelable);\n if (cancelable.Cancel) return;\n\n this.HoveredPoint = point;\n this.TooltipX = event.clientX + 16;\n this.TooltipY = event.clientY - 10;\n this.computeTooltipEntries(point);\n this.PointHovered.emit(point);\n this.cdr.detectChanges();\n }\n\n /**\n * Handle mouseleave on a data point: hide tooltip, emit null hover.\n */\n public OnPointMouseLeave(): void {\n this.HoveredPoint = null;\n this.TooltipEntries = [];\n this.PointHovered.emit(null);\n this.cdr.detectChanges();\n }\n\n /**\n * Handle click on a data point: fires `BeforePointClick`, toggles\n * selection, opens the detail panel, then fires `PointClicked` and\n * `SelectionChanged`.\n *\n * @param point The clicked point.\n */\n public OnPointClick(point: ClusterPoint): void {\n const cancelable: CancelableEvent<ClusterPoint> = { Data: point, Cancel: false };\n this.BeforePointClick.emit(cancelable);\n if (cancelable.Cancel) return;\n\n this.HighlightedKey = point.VectorKey;\n\n // Toggle selection\n const newSelection = new Set(this.SelectedPointIds);\n if (newSelection.has(point.VectorKey)) {\n newSelection.delete(point.VectorKey);\n } else {\n newSelection.add(point.VectorKey);\n }\n this.SelectedPointIds = newSelection;\n this.SelectionChanged.emit(newSelection);\n\n // Open detail panel for the clicked point\n this.selectPointForDetail(point);\n\n this.PointClicked.emit(point);\n this.cdr.detectChanges();\n }\n\n /**\n * Whether the given point is the currently highlighted (last-clicked) point.\n *\n * @param point The point to check.\n * @returns `true` if highlighted.\n */\n public IsHighlighted(point: ClusterPoint): boolean {\n return this.HighlightedKey === point.VectorKey;\n }\n\n /**\n * Whether the given point is in the current selection set.\n *\n * @param point The point to check.\n * @returns `true` if selected.\n */\n public IsSelected(point: ClusterPoint): boolean {\n return this.SelectedPointIds.has(point.VectorKey);\n }\n\n /**\n * Handle mouse wheel events for zooming.\n *\n * Fires `BeforeZoom` and `ViewportChanged`. Respects `EnableZoom`,\n * `MinZoom`, and `MaxZoom` constraints.\n *\n * @param event The native wheel event.\n */\n public OnWheel(event: WheelEvent): void {\n event.preventDefault();\n if (!this.EnableZoom) return;\n\n const zoomFactor = event.deltaY > 0 ? 1.1 : 0.9;\n const [minX, minY, width, height] = this.ViewBox;\n\n const minWidth = this.defaultWidth * this.MinZoom;\n const maxWidth = this.defaultWidth * this.MaxZoom;\n const minHeight = this.defaultHeight * this.MinZoom;\n const maxHeight = this.defaultHeight * this.MaxZoom;\n\n const newWidth = Math.max(minWidth, Math.min(maxWidth, width * zoomFactor));\n const newHeight = Math.max(minHeight, Math.min(maxHeight, height * zoomFactor));\n\n const centerX = minX + width / 2;\n const centerY = minY + height / 2;\n\n const proposed: ViewportRect = {\n MinX: centerX - newWidth / 2,\n MinY: centerY - newHeight / 2,\n Width: newWidth,\n Height: newHeight,\n };\n\n const cancelable: CancelableEvent<ViewportRect> = { Data: proposed, Cancel: false };\n this.BeforeZoom.emit(cancelable);\n if (cancelable.Cancel) return;\n\n this.ViewBox = [proposed.MinX, proposed.MinY, proposed.Width, proposed.Height];\n this.ViewportChanged.emit(proposed);\n this.cdr.detectChanges();\n }\n\n /**\n * Start panning on left-mousedown.\n *\n * @param event The native mouse event.\n */\n public OnMouseDown(event: MouseEvent): void {\n if (event.button !== 0 || !this.EnablePan) return;\n this.isPanning = true;\n this.panStartX = event.clientX;\n this.panStartY = event.clientY;\n this.panStartViewBox = [...this.ViewBox];\n }\n\n /**\n * Handle a click on a cluster legend item.\n *\n * @param cluster The cluster that was clicked.\n */\n public OnClusterLegendClick(cluster: ClusterInfo): void {\n this.ClusterSelected.emit({\n ClusterId: cluster.Id,\n Label: cluster.Label,\n Color: cluster.Color,\n MemberCount: cluster.MemberCount,\n });\n }\n\n /**\n * Resolve the human-readable label for a point's cluster.\n *\n * @param point The point to look up.\n * @returns Cluster label string, or `\"Outlier\"` for noise points.\n */\n public GetClusterLabel(point: ClusterPoint): string {\n if (point.ClusterId < 0) return 'Outlier';\n const cluster = this.Clusters.find(c => c.Id === point.ClusterId);\n return cluster?.Label ?? `Cluster ${point.ClusterId + 1}`;\n }\n\n /** Get the label for a cluster by ID (used for SVG text labels on the chart). */\n public GetClusterLabelById(clusterId: number): string {\n const cluster = this.Clusters.find(c => c.Id === clusterId);\n return cluster?.Label ?? '';\n }\n\n /** @internal TrackBy function for point loops. */\n public TrackPointBy(_index: number, point: ClusterPoint): string {\n return point.VectorKey;\n }\n\n /** @internal TrackBy function for cluster loops. */\n public TrackClusterBy(_index: number, cluster: ClusterInfo): number {\n return cluster.Id;\n }\n\n /** @internal TrackBy function for centroid loops. */\n public TrackCentroidBy(_index: number, centroid: { ClusterId: number }): number {\n return centroid.ClusterId;\n }\n\n // ================================================================\n // Public API Methods\n // ================================================================\n\n /**\n * Animate the viewport to center on a specific cluster's centroid.\n *\n * The zoom level is adjusted so that all cluster members are visible\n * with some padding.\n *\n * @param clusterId The numeric ID of the cluster to zoom into.\n *\n * @example\n * ```typescript\n * @ViewChild(ClusterScatterComponent) scatter!: ClusterScatterComponent;\n * this.scatter.ZoomToCluster(2);\n * ```\n */\n public ZoomToCluster(clusterId: number): void {\n const members = this.Points.filter(p => p.ClusterId === clusterId);\n if (members.length === 0) return;\n\n const bounds = this.computeBounds(members);\n const padding = 80;\n const proposed: ViewportRect = {\n MinX: bounds.minX - padding,\n MinY: bounds.minY - padding,\n Width: (bounds.maxX - bounds.minX) + padding * 2,\n Height: (bounds.maxY - bounds.minY) + padding * 2,\n };\n\n // Ensure minimum dimensions\n if (proposed.Width < 200) {\n const cx = proposed.MinX + proposed.Width / 2;\n proposed.Width = 200;\n proposed.MinX = cx - 100;\n }\n if (proposed.Height < 140) {\n const cy = proposed.MinY + proposed.Height / 2;\n proposed.Height = 140;\n proposed.MinY = cy - 70;\n }\n\n this.ViewBox = [proposed.MinX, proposed.MinY, proposed.Width, proposed.Height];\n this.ViewportChanged.emit(proposed);\n this.cdr.detectChanges();\n }\n\n /**\n * Reset the viewport to the default zoom level (full view).\n *\n * @example\n * ```typescript\n * this.scatter.ResetZoom();\n * ```\n */\n public ResetZoom(): void {\n const proposed: ViewportRect = {\n MinX: 0,\n MinY: 0,\n Width: this.defaultWidth,\n Height: this.defaultHeight,\n };\n this.ViewBox = [0, 0, this.defaultWidth, this.defaultHeight];\n this.ViewportChanged.emit(proposed);\n this.cdr.detectChanges();\n }\n\n /**\n * Return all points currently visible within the viewport bounds.\n *\n * @returns Array of `ClusterPoint` objects whose X/Y fall inside the\n * current viewBox.\n */\n public GetVisiblePoints(): ClusterPoint[] {\n const [minX, minY, width, height] = this.ViewBox;\n const maxX = minX + width;\n const maxY = minY + height;\n return this.Points.filter(p =>\n p.X >= minX && p.X <= maxX && p.Y >= minY && p.Y <= maxY\n );\n }\n\n /**\n * Programmatically select points by their `VectorKey` IDs.\n *\n * Merges with the existing selection. Use `ClearSelection()` first\n * to replace the selection entirely.\n *\n * @param ids Array of `VectorKey` strings to select.\n *\n * @example\n * ```typescript\n * this.scatter.SelectPoints(['abc-123', 'def-456']);\n * ```\n */\n public SelectPoints(ids: string[]): void {\n const newSelection = new Set(this.SelectedPointIds);\n for (const id of ids) {\n newSelection.add(id);\n }\n this.SelectedPointIds = newSelection;\n this.SelectionChanged.emit(newSelection);\n this.cdr.detectChanges();\n }\n\n /**\n * Clear all selected points.\n */\n public ClearSelection(): void {\n this.SelectedPointIds = new Set<string>();\n this.HighlightedKey = null;\n this.SelectionChanged.emit(this.SelectedPointIds);\n this.cdr.detectChanges();\n }\n\n /**\n * Export the current scatter plot as an SVG string.\n *\n * The returned string is a self-contained `<svg>` element that can be\n * saved to a file or embedded in HTML.\n *\n * @returns SVG markup string, or empty string if the SVG element is\n * not yet rendered.\n */\n public ExportSVG(): string {\n if (!this.svgRef?.nativeElement) return '';\n const svgElement = this.svgRef.nativeElement;\n const serializer = new XMLSerializer();\n return serializer.serializeToString(svgElement);\n }\n\n /**\n * Visually emphasize all points belonging to a cluster by increasing\n * their radius and dimming non-members.\n *\n * Call `ResetZoom()` or re-assign `Points` to clear the highlight.\n *\n * @param clusterId The numeric cluster ID to emphasize.\n */\n public HighlightCluster(clusterId: number): void {\n const memberKeys = this.Points\n .filter(p => p.ClusterId === clusterId)\n .map(p => p.VectorKey);\n this.SelectedPointIds = new Set(memberKeys);\n this.SelectionChanged.emit(this.SelectedPointIds);\n this.cdr.detectChanges();\n }\n\n // ================================================================\n // Detail Panel Methods\n // ================================================================\n\n /**\n * Close the detail panel.\n */\n public CloseDetailPanel(): void {\n this.ShowDetailPanel = false;\n this.SelectedPoint = null;\n this.DetailEntries = [];\n this.ClusterMembers = [];\n this.cdr.detectChanges();\n }\n\n /**\n * Emit the `OpenRecordRequested` event for the currently selected point\n * (triggered by the \"Open Record\" button in the detail panel).\n */\n public OnOpenRecordClick(): void {\n if (this.SelectedPoint) {\n this.OpenRecordRequested.emit(this.SelectedPoint);\n }\n }\n\n /**\n * Select a point from the cluster members list, updating the detail\n * panel and highlighting the point on the chart.\n *\n * @param point The cluster member point to select.\n */\n public OnClusterMemberClick(point: ClusterPoint): void {\n this.HighlightedKey = point.VectorKey;\n this.selectPointForDetail(point);\n this.cdr.detectChanges();\n }\n\n /**\n * Toggle the cluster members list expansion state.\n */\n public ToggleClusterMembers(): void {\n this.ClusterMembersExpanded = !this.ClusterMembersExpanded;\n }\n\n /**\n * Get the color for a cluster by its ID, used in the detail panel.\n *\n * @param clusterId The cluster ID.\n * @returns CSS color string.\n */\n public GetClusterColor(clusterId: number): string {\n if (clusterId < 0) return '#64748b';\n const cluster = this.Clusters.find(c => c.Id === clusterId);\n if (cluster?.Color) return cluster.Color;\n const palette = this.getActivePalette();\n return palette[clusterId % palette.length];\n }\n\n /**\n * Return the metadata keys that should be hidden in the detail panel.\n */\n private get internalMetadataKeys(): Set<string> {\n return new Set([\n 'Name', 'Entity', 'EntityIcon', 'RecordID',\n 'TemplateID', '__mj_UpdatedAt', '__mj_CreatedAt',\n ]);\n }\n\n /**\n * Select a point and populate the detail panel state.\n */\n private selectPointForDetail(point: ClusterPoint): void {\n this.SelectedPoint = point;\n this.ShowDetailPanel = true;\n this.computeDetailEntries(point);\n this.computeClusterMembers(point);\n }\n\n /**\n * Compute the metadata entries for the detail panel, excluding internal fields.\n */\n private computeDetailEntries(point: ClusterPoint): void {\n const hidden = this.internalMetadataKeys;\n this.DetailEntries = Object.entries(point.Metadata)\n .filter(([key]) => !hidden.has(key))\n .filter(([, value]) => value != null && String(value).trim() !== '')\n .map(([Key, Value]) => ({ Key, Value }));\n }\n\n /**\n * Compute the list of cluster members for the selected point's cluster.\n */\n private computeClusterMembers(point: ClusterPoint): void {\n this.ClusterMembers = this.Points\n .filter(p => p.ClusterId === point.ClusterId)\n .sort((a, b) => a.Label.localeCompare(b.Label));\n }\n\n /** @internal TrackBy function for cluster member loops. */\n public TrackMemberBy(_index: number, point: ClusterPoint): string {\n return point.VectorKey;\n }\n\n // ================================================================\n // Private Methods\n // ================================================================\n\n private onMouseMove(event: MouseEvent): void {\n if (!this.isPanning || !this.svgRef) return;\n const svg = this.svgRef.nativeElement;\n const rect = svg.getBoundingClientRect();\n const scaleX = this.panStartViewBox[2] / rect.width;\n const scaleY = this.panStartViewBox[3] / rect.height;\n\n const dx = (event.clientX - this.panStartX) * scaleX;\n const dy = (event.clientY - this.panStartY) * scaleY;\n\n const proposed: ViewportRect = {\n MinX: this.panStartViewBox[0] - dx,\n MinY: this.panStartViewBox[1] - dy,\n Width: this.panStartViewBox[2],\n Height: this.panStartViewBox[3],\n };\n\n this.ViewBox = [proposed.MinX, proposed.MinY, proposed.Width, proposed.Height];\n this.ViewportChanged.emit(proposed);\n this.cdr.detectChanges();\n }\n\n private onMouseUp(_event: MouseEvent): void {\n this.isPanning = false;\n }\n\n private computeCentroids(): void {\n if (!this.Points.length || !this.Clusters.length) {\n this.ClusterCentroids = [];\n return;\n }\n\n this.ClusterCentroids = this.Clusters.map(cluster => {\n const members = this.Points.filter(p => p.ClusterId === cluster.Id);\n if (members.length === 0) {\n return { ClusterId: cluster.Id, Cx: 500, Cy: 350, R: 80, Color: cluster.Color };\n }\n const cx = members.reduce((s, p) => s + p.X, 0) / members.length;\n const cy = members.reduce((s, p) => s + p.Y, 0) / members.length;\n const maxDist = Math.max(\n ...members.map(p => Math.sqrt((p.X - cx) ** 2 + (p.Y - cy) ** 2)),\n 40\n );\n return {\n ClusterId: cluster.Id,\n Cx: cx,\n Cy: cy,\n R: Math.min(maxDist + 30, 200),\n Color: cluster.Color,\n };\n });\n }\n\n /**\n * Compute the bounding box for a set of points.\n */\n private computeBounds(points: ClusterPoint[]): { minX: number; minY: number; maxX: number; maxY: number } {\n let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;\n for (const p of points) {\n if (p.X < minX) minX = p.X;\n if (p.X > maxX) maxX = p.X;\n if (p.Y < minY) minY = p.Y;\n if (p.Y > maxY) maxY = p.Y;\n }\n return { minX, minY, maxX, maxY };\n }\n\n /**\n * Resolve the effective color palette (custom override or default).\n */\n private getActivePalette(): string[] {\n return this.ColorPalette.length > 0 ? this.ColorPalette : CLUSTER_COLORS;\n }\n\n /**\n * Compute the tooltip entries based on TooltipFields filter.\n */\n private computeTooltipEntries(point: ClusterPoint): void {\n const entries = Object.entries(point.Metadata);\n if (this.TooltipFields.length > 0) {\n this.TooltipEntries = entries\n .filter(([key]) => this.TooltipFields.includes(key))\n .map(([Key, Value]) => ({ Key, Value }));\n } else {\n this.TooltipEntries = entries.map(([Key, Value]) => ({ Key, Value }));\n }\n }\n}\n","<div class=\"scatter-container\">\n <!-- Loading overlay -->\n @if (IsLoading) {\n <div class=\"loading-overlay\">\n <div class=\"loading-spinner\">\n <i class=\"fa-solid fa-circle-nodes fa-spin\"></i>\n <span>Computing clusters...</span>\n </div>\n </div>\n }\n\n <!-- Empty state -->\n @if (!IsLoading && Points.length === 0) {\n <div class=\"empty-state\">\n <i class=\"fa-solid fa-chart-scatter\"></i>\n <p>No data to display</p>\n <p class=\"empty-hint\">Configure and run clustering to see results</p>\n </div>\n }\n\n <!-- SVG scatter plot -->\n @if (Points.length > 0) {\n <svg #svgElement\n class=\"scatter-svg\"\n [class.no-transitions]=\"!AnimateTransitions\"\n [attr.viewBox]=\"GetViewBoxString()\"\n preserveAspectRatio=\"xMidYMid meet\"\n (wheel)=\"OnWheel($event)\"\n (mousedown)=\"OnMouseDown($event)\">\n\n <!-- Gradient definitions for cluster glow -->\n <defs>\n @for (centroid of ClusterCentroids; track TrackCentroidBy($index, centroid)) {\n <radialGradient [attr.id]=\"'cluster-glow-' + centroid.ClusterId\"\n cx=\"50%\" cy=\"50%\" r=\"50%\">\n <stop offset=\"0%\" [attr.stop-color]=\"centroid.Color\" stop-opacity=\"0.12\"/>\n <stop offset=\"100%\" [attr.stop-color]=\"centroid.Color\" stop-opacity=\"0\"/>\n </radialGradient>\n }\n </defs>\n\n <!-- Background grid lines -->\n <g class=\"grid-lines\">\n @for (y of HorizontalGridLines; track y) {\n <line [attr.x1]=\"0\" [attr.y1]=\"y\" [attr.x2]=\"1000\" [attr.y2]=\"y\"/>\n }\n @for (x of VerticalGridLines; track x) {\n <line [attr.x1]=\"x\" [attr.y1]=\"0\" [attr.x2]=\"x\" [attr.y2]=\"700\"/>\n }\n </g>\n\n <!-- Cluster glow circles -->\n @for (centroid of ClusterCentroids; track TrackCentroidBy($index, centroid)) {\n <circle [attr.cx]=\"centroid.Cx\"\n [attr.cy]=\"centroid.Cy\"\n [attr.r]=\"centroid.R\"\n [attr.fill]=\"'url(#cluster-glow-' + centroid.ClusterId + ')'\"/>\n }\n\n <!-- Cluster labels (positioned above centroids) -->\n @for (centroid of ClusterCentroids; track TrackCentroidBy($index, centroid)) {\n @if (GetClusterLabelById(centroid.ClusterId); as label) {\n <text class=\"cluster-label\"\n [attr.x]=\"centroid.Cx\"\n [attr.y]=\"centroid.Cy - centroid.R - 8\"\n text-anchor=\"middle\"\n [attr.fill]=\"centroid.Color\">{{ label }}</text>\n }\n }\n\n <!-- Data points -->\n @for (point of Points; track TrackPointBy($index, point)) {\n <circle class=\"data-point\"\n [attr.cx]=\"point.X\"\n [attr.cy]=\"point.Y\"\n [attr.r]=\"GetPointRadius(point)\"\n [attr.fill]=\"GetPointColor(point)\"\n [attr.opacity]=\"GetPointOpacity(point)\"\n (mouseenter)=\"OnPointMouseEnter(point, $event)\"\n (mouseleave)=\"OnPointMouseLeave()\"\n (click)=\"OnPointClick(point); $event.stopPropagation()\"/>\n }\n\n <!-- Selection rings for selected points -->\n @for (point of Points; track TrackPointBy($index, point)) {\n @if (IsSelected(point)) {\n <circle class=\"selection-ring\"\n [attr.cx]=\"point.X\"\n [attr.cy]=\"point.Y\"\n [attr.r]=\"HighlightRadius + 2\"\n fill=\"none\"\n [attr.stroke]=\"GetPointColor(point)\"\n stroke-width=\"2\"\n opacity=\"0.8\"/>\n }\n }\n\n <!-- Highlight ring for last-clicked point -->\n @if (HighlightedKey) {\n @for (point of Points; track TrackPointBy($index, point)) {\n @if (IsHighlighted(point)) {\n <circle [attr.cx]=\"point.X\"\n [attr.cy]=\"point.Y\"\n [attr.r]=\"HighlightRadius + 4\"\n fill=\"none\"\n [attr.stroke]=\"GetPointColor(point)\"\n stroke-width=\"2.5\"\n opacity=\"0.9\"/>\n }\n }\n }\n </svg>\n }\n\n <!-- Legend overlay -->\n @if (ShowLegend && Clusters.length > 0) {\n <div class=\"legend\">\n <div class=\"legend-title\">Clusters</div>\n @for (cluster of Clusters; track TrackClusterBy($index, cluster)) {\n <div class=\"legend-item\" (click)=\"OnClusterLegendClick(cluster)\">\n <div class=\"legend-dot\" [style.background]=\"cluster.Color\"></div>\n <span class=\"legend-label\">{{ cluster.Label }}</span>\n <span class=\"legend-count\">{{ cluster.MemberCount }}</span>\n </div>\n }\n </div>\n }\n\n <!-- Tooltip -->\n @if (ShowTooltip && HoveredPoint) {\n <div class=\"tooltip\"\n [style.left.px]=\"TooltipX\"\n [style.top.px]=\"TooltipY\">\n <div class=\"tt-header\">\n @if (HoveredPoint.Metadata?.['EntityIcon']) {\n <div class=\"tt-icon\">\n <i [class]=\"HoveredPoint.Metadata['EntityIcon']\"></i>\n </div>\n }\n <div class=\"tt-header-text\">\n <div class=\"tt-name\">{{ HoveredPoint.Label }}</div>\n @if (HoveredPoint.Metadata?.['Entity']) {\n <div class=\"tt-entity\">{{ HoveredPoint.Metadata['Entity'] }}</div>\n }\n </div>\n </div>\n <div class=\"tt-cluster\">\n <i class=\"fa-solid fa-circle\" style=\"font-size: 7px; vertical-align: middle\"></i>\n {{ GetClusterLabel(HoveredPoint) }}\n </div>\n @if (TooltipEntries.length > 0) {\n <div class=\"tt-fields\">\n @for (entry of TooltipEntries; track entry.Key) {\n @if (entry.Key !== 'Entity' && entry.Key !== 'EntityIcon' && entry.Key !== 'RecordID' && entry.Key !== 'Name' && entry.Key !== 'TemplateID' && entry.Key !== '__mj_UpdatedAt') {\n <div class=\"tt-field-row\">{{ entry.Value }}</div>\n }\n }\n </div>\n }\n <div class=\"tt-action\">\n <i class=\"fa-solid fa-arrow-up-right-from-square\"></i> Click to view details\n </div>\n </div>\n }\n\n <!-- Detail Panel (slide-in from right) -->\n <div class=\"detail-panel\" [class.detail-panel-open]=\"ShowDetailPanel && SelectedPoint\">\n @if (SelectedPoint) {\n <!-- Panel Header -->\n <div class=\"detail-header\">\n <div class=\"detail-header-content\">\n @if (SelectedPoint.Metadata?.['EntityIcon']) {\n <div class=\"detail-icon\">\n <i [class]=\"SelectedPoint.Metadata['EntityIcon']\"></i>\n </div>\n }\n <div class=\"detail-header-text\">\n <div class=\"detail-name\">{{ SelectedPoint.Label }}</div>\n @if (SelectedPoint.Metadata?.['Entity']) {\n <span class=\"detail-entity-badge\">{{ SelectedPoint.Metadata['Entity'] }}</span>\n }\n </div>\n </div>\n <button class=\"detail-close\" (click)=\"CloseDetailPanel()\" title=\"Close\">\n <i class=\"fa-solid fa-times\"></i>\n </button>\n </div>\n\n <!-- Cluster badge -->\n <div class=\"detail-cluster\">\n <span class=\"detail-cluster-dot\"\n [style.background]=\"GetClusterColor(SelectedPoint.ClusterId)\"></span>\n {{ GetClusterLabel(SelectedPoint) }}\n </div>\n\n <!-- Metadata fields -->\n @if (DetailEntries.length > 0) {\n <div class=\"detail-fields\">\n @for (entry of DetailEntries; track entry.Key) {\n <div class=\"detail-field\">\n <div class=\"detail-field-key\">{{ entry.Key }}</div>\n <div class=\"detail-field-value\">{{ entry.Value }}</div>\n </div>\n }\n </div>\n }\n\n <!-- Cluster Members -->\n <div class=\"detail-members-section\">\n <button class=\"detail-members-toggle\" (click)=\"ToggleClusterMembers()\">\n <i class=\"fa-solid\"\n [class.fa-chevron-down]=\"ClusterMembersExpanded\"\n [class.fa-chevron-right]=\"!ClusterMembersExpanded\"></i>\n <span>Cluster Members</span>\n <span class=\"detail-members-count\">{{ ClusterMembers.length }}</span>\n </button>\n\n @if (ClusterMembersExpanded) {\n <div class=\"detail-members-list\">\n @for (member of ClusterMembers; track TrackMemberBy($index, member)) {\n <div class=\"detail-member-row\"\n [class.active]=\"member.VectorKey === SelectedPoint.VectorKey\"\n (click)=\"OnClusterMemberClick(member)\">\n @if (member.Metadata?.['EntityIcon']) {\n <i [class]=\"member.Metadata['EntityIcon']\" class=\"member-icon\"></i>\n }\n <span class=\"member-label\">{{ member.Label }}</span>\n </div>\n }\n </div>\n }\n </div>\n\n <!-- Open Record button -->\n <div class=\"detail-footer\">\n <button class=\"detail-open-btn\" (click)=\"OnOpenRecordClick()\">\n <i class=\"fa-solid fa-arrow-up-right-from-square\"></i>\n Open Record\n </button>\n </div>\n }\n </div>\n</div>\n"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import * as i0 from "@angular/core";
|
|
2
|
+
import * as i1 from "./cluster-scatter.component";
|
|
3
|
+
import * as i2 from "./cluster-config-panel.component";
|
|
4
|
+
import * as i3 from "@angular/common";
|
|
5
|
+
import * as i4 from "@angular/forms";
|
|
6
|
+
export declare class ClusteringModule {
|
|
7
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<ClusteringModule, never>;
|
|
8
|
+
static ɵmod: i0.ɵɵNgModuleDeclaration<ClusteringModule, [typeof i1.ClusterScatterComponent, typeof i2.ClusterConfigPanelComponent], [typeof i3.CommonModule, typeof i4.FormsModule], [typeof i1.ClusterScatterComponent, typeof i2.ClusterConfigPanelComponent]>;
|
|
9
|
+
static ɵinj: i0.ɵɵInjectorDeclaration<ClusteringModule>;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=clustering.module.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clustering.module.d.ts","sourceRoot":"","sources":["../../src/lib/clustering.module.ts"],"names":[],"mappings":";;;;;AAOA,qBAca,gBAAgB;yCAAhB,gBAAgB;0CAAhB,gBAAgB;0CAAhB,gBAAgB;CAAG"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { NgModule } from '@angular/core';
|
|
2
|
+
import { CommonModule } from '@angular/common';
|
|
3
|
+
import { FormsModule } from '@angular/forms';
|
|
4
|
+
import { ClusterScatterComponent } from './cluster-scatter.component';
|
|
5
|
+
import { ClusterConfigPanelComponent } from './cluster-config-panel.component';
|
|
6
|
+
import * as i0 from "@angular/core";
|
|
7
|
+
export class ClusteringModule {
|
|
8
|
+
static { this.ɵfac = function ClusteringModule_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || ClusteringModule)(); }; }
|
|
9
|
+
static { this.ɵmod = /*@__PURE__*/ i0.ɵɵdefineNgModule({ type: ClusteringModule }); }
|
|
10
|
+
static { this.ɵinj = /*@__PURE__*/ i0.ɵɵdefineInjector({ imports: [CommonModule,
|
|
11
|
+
FormsModule] }); }
|
|
12
|
+
}
|
|
13
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ClusteringModule, [{
|
|
14
|
+
type: NgModule,
|
|
15
|
+
args: [{
|
|
16
|
+
declarations: [
|
|
17
|
+
ClusterScatterComponent,
|
|
18
|
+
ClusterConfigPanelComponent,
|
|
19
|
+
],
|
|
20
|
+
imports: [
|
|
21
|
+
CommonModule,
|
|
22
|
+
FormsModule,
|
|
23
|
+
],
|
|
24
|
+
exports: [
|
|
25
|
+
ClusterScatterComponent,
|
|
26
|
+
ClusterConfigPanelComponent,
|
|
27
|
+
],
|
|
28
|
+
}]
|
|
29
|
+
}], null, null); })();
|
|
30
|
+
(function () { (typeof ngJitMode === "undefined" || ngJitMode) && i0.ɵɵsetNgModuleScope(ClusteringModule, { declarations: [ClusterScatterComponent,
|
|
31
|
+
ClusterConfigPanelComponent], imports: [CommonModule,
|
|
32
|
+
FormsModule], exports: [ClusterScatterComponent,
|
|
33
|
+
ClusterConfigPanelComponent] }); })();
|
|
34
|
+
//# sourceMappingURL=clustering.module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clustering.module.js","sourceRoot":"","sources":["../../src/lib/clustering.module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7C,OAAO,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAC;AACtE,OAAO,EAAE,2BAA2B,EAAE,MAAM,kCAAkC,CAAC;;AAgB/E,MAAM,OAAO,gBAAgB;iHAAhB,gBAAgB;mEAAhB,gBAAgB;uEARrB,YAAY;YACZ,WAAW;;iFAON,gBAAgB;cAd5B,QAAQ;eAAC;gBACN,YAAY,EAAE;oBACV,uBAAuB;oBACvB,2BAA2B;iBAC9B;gBACD,OAAO,EAAE;oBACL,YAAY;oBACZ,WAAW;iBACd;gBACD,OAAO,EAAE;oBACL,uBAAuB;oBACvB,2BAA2B;iBAC9B;aACJ;;wFACY,gBAAgB,mBAZrB,uBAAuB;QACvB,2BAA2B,aAG3B,YAAY;QACZ,WAAW,aAGX,uBAAuB;QACvB,2BAA2B","sourcesContent":["import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { FormsModule } from '@angular/forms';\n\nimport { ClusterScatterComponent } from './cluster-scatter.component';\nimport { ClusterConfigPanelComponent } from './cluster-config-panel.component';\n\n@NgModule({\n declarations: [\n ClusterScatterComponent,\n ClusterConfigPanelComponent,\n ],\n imports: [\n CommonModule,\n FormsModule,\n ],\n exports: [\n ClusterScatterComponent,\n ClusterConfigPanelComponent,\n ],\n})\nexport class ClusteringModule {}\n"]}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { ClusterConfig, ClusterVisualizationResult, ClusterInputVector } from './clustering.types';
|
|
2
|
+
import * as i0 from "@angular/core";
|
|
3
|
+
export declare class ClusteringService {
|
|
4
|
+
/**
|
|
5
|
+
* Run the full clustering pipeline on pre-fetched vectors:
|
|
6
|
+
* cluster, project to 2D, and return a visualization result.
|
|
7
|
+
*
|
|
8
|
+
* @param vectors Array of vectors with keys, raw number arrays, and optional metadata.
|
|
9
|
+
* @param config Clustering configuration (algorithm, K, epsilon, etc.).
|
|
10
|
+
* @returns A complete visualization result ready for rendering.
|
|
11
|
+
*/
|
|
12
|
+
RunClustering(vectors: ClusterInputVector[], config: ClusterConfig): Promise<ClusterVisualizationResult>;
|
|
13
|
+
/**
|
|
14
|
+
* Reduce an array of high-dimensional vectors to 2D points using UMAP
|
|
15
|
+
* (falls back to PCA if UMAP is unavailable).
|
|
16
|
+
*
|
|
17
|
+
* This is a standalone helper when you only need dimensionality reduction
|
|
18
|
+
* without clustering.
|
|
19
|
+
*
|
|
20
|
+
* @param vectors Array of raw number arrays (each the same length).
|
|
21
|
+
* @returns Array of {x, y} points in the same order as input.
|
|
22
|
+
*/
|
|
23
|
+
ReduceDimensions(vectors: number[][]): Promise<Array<{
|
|
24
|
+
x: number;
|
|
25
|
+
y: number;
|
|
26
|
+
}>>;
|
|
27
|
+
/**
|
|
28
|
+
* Reduce high-dimensional vectors to 2D using UMAP.
|
|
29
|
+
* Falls back to PCA if UMAP is not available.
|
|
30
|
+
*/
|
|
31
|
+
private reduceDimensionsKeyed;
|
|
32
|
+
/** Normalize 2D embedding to [padding, viewBoxSize - padding] range */
|
|
33
|
+
private normalizeAndAssign;
|
|
34
|
+
/** Simple PCA: compute mean, subtract, take first 2 components via power iteration */
|
|
35
|
+
private pcaFallback;
|
|
36
|
+
/** Build a ClusterVisualizationResult from raw data */
|
|
37
|
+
private buildResult;
|
|
38
|
+
/** Build an empty result when no vectors are found */
|
|
39
|
+
private buildEmptyResult;
|
|
40
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<ClusteringService, never>;
|
|
41
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<ClusteringService>;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=clustering.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clustering.service.d.ts","sourceRoot":"","sources":["../../src/lib/clustering.service.ts"],"names":[],"mappings":"AAeA,OAAO,EACH,aAAa,EACb,0BAA0B,EAI1B,kBAAkB,EAErB,MAAM,oBAAoB,CAAC;;AAE5B,qBACa,iBAAiB;IAE1B;;;;;;;OAOG;IACU,aAAa,CACtB,OAAO,EAAE,kBAAkB,EAAE,EAC7B,MAAM,EAAE,aAAa,GACtB,OAAO,CAAC,0BAA0B,CAAC;IAmCtC;;;;;;;;;OASG;IACU,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,GAAG,OAAO,CAAC,KAAK,CAAC;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAmB5F;;;OAGG;YACW,qBAAqB;IA+BnC,uEAAuE;IACvE,OAAO,CAAC,kBAAkB;IA0B1B,sFAAsF;IACtF,OAAO,CAAC,WAAW;IAiDnB,uDAAuD;IACvD,OAAO,CAAC,WAAW;IA6DnB,sDAAsD;IACtD,OAAO,CAAC,gBAAgB;yCA5Pf,iBAAiB;6CAAjB,iBAAiB;CA0Q7B"}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Pure clustering + dimensionality-reduction service.
|
|
3
|
+
*
|
|
4
|
+
* This service is GENERIC — it accepts pre-fetched vectors and returns
|
|
5
|
+
* clustering results. It does NOT fetch data from any database or API.
|
|
6
|
+
*
|
|
7
|
+
* Pipeline:
|
|
8
|
+
* 1. Accept vectors from the caller
|
|
9
|
+
* 2. Run clustering via SimpleVectorService (K-Means or DBSCAN)
|
|
10
|
+
* 3. Reduce dimensions to 2D via UMAP (or PCA fallback)
|
|
11
|
+
* 4. Return ClusterVisualizationResult
|
|
12
|
+
*/
|
|
13
|
+
import { Injectable } from '@angular/core';
|
|
14
|
+
import { SimpleVectorService } from '@memberjunction/ai-vectors-memory';
|
|
15
|
+
import { CLUSTER_COLORS, } from './clustering.types';
|
|
16
|
+
import * as i0 from "@angular/core";
|
|
17
|
+
export class ClusteringService {
|
|
18
|
+
/**
|
|
19
|
+
* Run the full clustering pipeline on pre-fetched vectors:
|
|
20
|
+
* cluster, project to 2D, and return a visualization result.
|
|
21
|
+
*
|
|
22
|
+
* @param vectors Array of vectors with keys, raw number arrays, and optional metadata.
|
|
23
|
+
* @param config Clustering configuration (algorithm, K, epsilon, etc.).
|
|
24
|
+
* @returns A complete visualization result ready for rendering.
|
|
25
|
+
*/
|
|
26
|
+
async RunClustering(vectors, config) {
|
|
27
|
+
const startTime = performance.now();
|
|
28
|
+
if (vectors.length === 0) {
|
|
29
|
+
return this.buildEmptyResult(config, performance.now() - startTime);
|
|
30
|
+
}
|
|
31
|
+
// Step 1: Load into SimpleVectorService and run clustering
|
|
32
|
+
const svc = new SimpleVectorService();
|
|
33
|
+
const entries = vectors.map(v => ({
|
|
34
|
+
key: v.Key,
|
|
35
|
+
vector: v.Vector,
|
|
36
|
+
metadata: v.Metadata ?? {},
|
|
37
|
+
}));
|
|
38
|
+
svc.LoadVectors(entries);
|
|
39
|
+
let clusterResult;
|
|
40
|
+
if (config.Algorithm === 'dbscan') {
|
|
41
|
+
clusterResult = svc.DBSCANCluster(config.Epsilon, config.MinPoints, config.DistanceMetric);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
const k = Math.min(config.K, vectors.length);
|
|
45
|
+
clusterResult = svc.KMeansCluster(k, 100, config.DistanceMetric);
|
|
46
|
+
}
|
|
47
|
+
// Step 2: Reduce dimensions to 2D
|
|
48
|
+
const vectorMap = new Map();
|
|
49
|
+
for (const v of vectors) {
|
|
50
|
+
vectorMap.set(v.Key, v.Vector);
|
|
51
|
+
}
|
|
52
|
+
const projected = await this.reduceDimensionsKeyed(vectorMap);
|
|
53
|
+
// Step 3: Build result
|
|
54
|
+
return this.buildResult(vectors, clusterResult, projected, config, startTime);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Reduce an array of high-dimensional vectors to 2D points using UMAP
|
|
58
|
+
* (falls back to PCA if UMAP is unavailable).
|
|
59
|
+
*
|
|
60
|
+
* This is a standalone helper when you only need dimensionality reduction
|
|
61
|
+
* without clustering.
|
|
62
|
+
*
|
|
63
|
+
* @param vectors Array of raw number arrays (each the same length).
|
|
64
|
+
* @returns Array of {x, y} points in the same order as input.
|
|
65
|
+
*/
|
|
66
|
+
async ReduceDimensions(vectors) {
|
|
67
|
+
if (vectors.length === 0)
|
|
68
|
+
return [];
|
|
69
|
+
// Build temporary keyed map
|
|
70
|
+
const keys = vectors.map((_, i) => String(i));
|
|
71
|
+
const keyedMap = new Map();
|
|
72
|
+
keys.forEach((k, i) => keyedMap.set(k, vectors[i]));
|
|
73
|
+
const projected = await this.reduceDimensionsKeyed(keyedMap);
|
|
74
|
+
return keys.map(k => {
|
|
75
|
+
const [x, y] = projected.get(k) ?? [0, 0];
|
|
76
|
+
return { x, y };
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
// ================================================================
|
|
80
|
+
// Private helpers
|
|
81
|
+
// ================================================================
|
|
82
|
+
/**
|
|
83
|
+
* Reduce high-dimensional vectors to 2D using UMAP.
|
|
84
|
+
* Falls back to PCA if UMAP is not available.
|
|
85
|
+
*/
|
|
86
|
+
async reduceDimensionsKeyed(vectorMap) {
|
|
87
|
+
const keys = Array.from(vectorMap.keys());
|
|
88
|
+
const vectors = keys.map(k => vectorMap.get(k));
|
|
89
|
+
const result = new Map();
|
|
90
|
+
if (vectors.length <= 2) {
|
|
91
|
+
// Trivial case
|
|
92
|
+
keys.forEach((key, i) => {
|
|
93
|
+
result.set(key, [i * 500 + 250, 350]);
|
|
94
|
+
});
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
const { UMAP } = await import('umap-js');
|
|
99
|
+
const umap = new UMAP({
|
|
100
|
+
nComponents: 2,
|
|
101
|
+
nNeighbors: Math.min(15, Math.max(2, Math.floor(vectors.length / 5))),
|
|
102
|
+
minDist: 0.1,
|
|
103
|
+
spread: 1.0,
|
|
104
|
+
});
|
|
105
|
+
const embedding = umap.fit(vectors);
|
|
106
|
+
this.normalizeAndAssign(keys, embedding, result);
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
// PCA fallback: project onto first 2 principal components
|
|
110
|
+
this.pcaFallback(keys, vectors, result);
|
|
111
|
+
}
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
114
|
+
/** Normalize 2D embedding to [padding, viewBoxSize - padding] range */
|
|
115
|
+
normalizeAndAssign(keys, embedding, result) {
|
|
116
|
+
const padding = 60;
|
|
117
|
+
const size = 1000;
|
|
118
|
+
const usable = size - 2 * padding;
|
|
119
|
+
let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
|
|
120
|
+
for (const [x, y] of embedding) {
|
|
121
|
+
if (x < minX)
|
|
122
|
+
minX = x;
|
|
123
|
+
if (x > maxX)
|
|
124
|
+
maxX = x;
|
|
125
|
+
if (y < minY)
|
|
126
|
+
minY = y;
|
|
127
|
+
if (y > maxY)
|
|
128
|
+
maxY = y;
|
|
129
|
+
}
|
|
130
|
+
const rangeX = maxX - minX || 1;
|
|
131
|
+
const rangeY = maxY - minY || 1;
|
|
132
|
+
keys.forEach((key, i) => {
|
|
133
|
+
const nx = padding + ((embedding[i][0] - minX) / rangeX) * usable;
|
|
134
|
+
const ny = padding + ((embedding[i][1] - minY) / rangeY) * usable;
|
|
135
|
+
result.set(key, [nx, ny]);
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
/** Simple PCA: compute mean, subtract, take first 2 components via power iteration */
|
|
139
|
+
pcaFallback(keys, vectors, result) {
|
|
140
|
+
const dim = vectors[0].length;
|
|
141
|
+
const n = vectors.length;
|
|
142
|
+
// Compute mean
|
|
143
|
+
const mean = new Array(dim).fill(0);
|
|
144
|
+
for (const v of vectors) {
|
|
145
|
+
for (let d = 0; d < dim; d++)
|
|
146
|
+
mean[d] += v[d];
|
|
147
|
+
}
|
|
148
|
+
for (let d = 0; d < dim; d++)
|
|
149
|
+
mean[d] /= n;
|
|
150
|
+
// Center data
|
|
151
|
+
const centered = vectors.map(v => v.map((val, d) => val - mean[d]));
|
|
152
|
+
// Power iteration for first 2 components
|
|
153
|
+
const components = [];
|
|
154
|
+
for (let comp = 0; comp < 2; comp++) {
|
|
155
|
+
let w = new Array(dim).fill(0).map(() => Math.random() - 0.5);
|
|
156
|
+
for (let iter = 0; iter < 50; iter++) {
|
|
157
|
+
const newW = new Array(dim).fill(0);
|
|
158
|
+
for (const row of centered) {
|
|
159
|
+
const dot = row.reduce((s, val, d) => s + val * w[d], 0);
|
|
160
|
+
for (let d = 0; d < dim; d++)
|
|
161
|
+
newW[d] += dot * row[d];
|
|
162
|
+
}
|
|
163
|
+
// Deflate by previous components
|
|
164
|
+
for (const prev of components) {
|
|
165
|
+
const proj = newW.reduce((s, val, d) => s + val * prev[d], 0);
|
|
166
|
+
for (let d = 0; d < dim; d++)
|
|
167
|
+
newW[d] -= proj * prev[d];
|
|
168
|
+
}
|
|
169
|
+
// Normalize
|
|
170
|
+
const norm = Math.sqrt(newW.reduce((s, val) => s + val * val, 0)) || 1;
|
|
171
|
+
w = newW.map(val => val / norm);
|
|
172
|
+
}
|
|
173
|
+
components.push(w);
|
|
174
|
+
}
|
|
175
|
+
// Project
|
|
176
|
+
const projections = centered.map(row => [
|
|
177
|
+
row.reduce((s, val, d) => s + val * components[0][d], 0),
|
|
178
|
+
row.reduce((s, val, d) => s + val * (components[1]?.[d] ?? 0), 0),
|
|
179
|
+
]);
|
|
180
|
+
this.normalizeAndAssign(keys, projections, result);
|
|
181
|
+
}
|
|
182
|
+
/** Build a ClusterVisualizationResult from raw data */
|
|
183
|
+
buildResult(vectors, clusterResult, projected, config, startTime) {
|
|
184
|
+
// Build lookup: vectorKey -> clusterId
|
|
185
|
+
const keyToCluster = new Map();
|
|
186
|
+
clusterResult.clusters.forEach((members, clusterId) => {
|
|
187
|
+
for (const key of members) {
|
|
188
|
+
keyToCluster.set(key, clusterId);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
// Mark outliers as cluster -1
|
|
192
|
+
if (clusterResult.outliers) {
|
|
193
|
+
for (const key of clusterResult.outliers) {
|
|
194
|
+
keyToCluster.set(key, -1);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// Build points
|
|
198
|
+
const points = vectors.map(v => {
|
|
199
|
+
const [x, y] = projected.get(v.Key) ?? [500, 350];
|
|
200
|
+
return {
|
|
201
|
+
X: x,
|
|
202
|
+
Y: y,
|
|
203
|
+
ClusterId: keyToCluster.get(v.Key) ?? -1,
|
|
204
|
+
Label: v.Label ?? v.Key,
|
|
205
|
+
VectorKey: v.Key,
|
|
206
|
+
Metadata: v.Metadata ?? {},
|
|
207
|
+
};
|
|
208
|
+
});
|
|
209
|
+
// Build cluster infos
|
|
210
|
+
const clusters = [];
|
|
211
|
+
clusterResult.clusters.forEach((members, clusterId) => {
|
|
212
|
+
clusters.push({
|
|
213
|
+
Id: clusterId,
|
|
214
|
+
Label: `Cluster ${clusterId + 1}`,
|
|
215
|
+
Color: CLUSTER_COLORS[clusterId % CLUSTER_COLORS.length],
|
|
216
|
+
MemberCount: members.length,
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
// Sort by ID
|
|
220
|
+
clusters.sort((a, b) => a.Id - b.Id);
|
|
221
|
+
const outlierCount = clusterResult.outliers?.length ?? 0;
|
|
222
|
+
const metrics = {
|
|
223
|
+
SilhouetteScore: clusterResult.metadata?.silhouetteScore ?? 0,
|
|
224
|
+
ClusterCount: clusters.length,
|
|
225
|
+
ComputationTimeMs: Math.round(performance.now() - startTime),
|
|
226
|
+
RecordCount: vectors.length,
|
|
227
|
+
OutlierCount: outlierCount,
|
|
228
|
+
};
|
|
229
|
+
return { Points: points, Clusters: clusters, Metrics: metrics, Config: config };
|
|
230
|
+
}
|
|
231
|
+
/** Build an empty result when no vectors are found */
|
|
232
|
+
buildEmptyResult(config, elapsedMs) {
|
|
233
|
+
return {
|
|
234
|
+
Points: [],
|
|
235
|
+
Clusters: [],
|
|
236
|
+
Metrics: {
|
|
237
|
+
SilhouetteScore: 0,
|
|
238
|
+
ClusterCount: 0,
|
|
239
|
+
ComputationTimeMs: Math.round(elapsedMs),
|
|
240
|
+
RecordCount: 0,
|
|
241
|
+
OutlierCount: 0,
|
|
242
|
+
},
|
|
243
|
+
Config: config,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
static { this.ɵfac = function ClusteringService_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || ClusteringService)(); }; }
|
|
247
|
+
static { this.ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: ClusteringService, factory: ClusteringService.ɵfac, providedIn: 'root' }); }
|
|
248
|
+
}
|
|
249
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ClusteringService, [{
|
|
250
|
+
type: Injectable,
|
|
251
|
+
args: [{ providedIn: 'root' }]
|
|
252
|
+
}], null, null); })();
|
|
253
|
+
//# sourceMappingURL=clustering.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clustering.service.js","sourceRoot":"","sources":["../../src/lib/clustering.service.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @fileoverview Pure clustering + dimensionality-reduction service.\n *\n * This service is GENERIC — it accepts pre-fetched vectors and returns\n * clustering results. It does NOT fetch data from any database or API.\n *\n * Pipeline:\n * 1. Accept vectors from the caller\n * 2. Run clustering via SimpleVectorService (K-Means or DBSCAN)\n * 3. Reduce dimensions to 2D via UMAP (or PCA fallback)\n * 4. Return ClusterVisualizationResult\n */\n\nimport { Injectable } from '@angular/core';\nimport { SimpleVectorService, VectorEntry, ClusterResult } from '@memberjunction/ai-vectors-memory';\nimport {\n ClusterConfig,\n ClusterVisualizationResult,\n ClusterPoint,\n ClusterInfo,\n ClusterMetrics,\n ClusterInputVector,\n CLUSTER_COLORS,\n} from './clustering.types';\n\n@Injectable({ providedIn: 'root' })\nexport class ClusteringService {\n\n /**\n * Run the full clustering pipeline on pre-fetched vectors:\n * cluster, project to 2D, and return a visualization result.\n *\n * @param vectors Array of vectors with keys, raw number arrays, and optional metadata.\n * @param config Clustering configuration (algorithm, K, epsilon, etc.).\n * @returns A complete visualization result ready for rendering.\n */\n public async RunClustering(\n vectors: ClusterInputVector[],\n config: ClusterConfig,\n ): Promise<ClusterVisualizationResult> {\n const startTime = performance.now();\n\n if (vectors.length === 0) {\n return this.buildEmptyResult(config, performance.now() - startTime);\n }\n\n // Step 1: Load into SimpleVectorService and run clustering\n const svc = new SimpleVectorService();\n const entries: VectorEntry[] = vectors.map(v => ({\n key: v.Key,\n vector: v.Vector,\n metadata: v.Metadata ?? {},\n }));\n svc.LoadVectors(entries);\n\n let clusterResult: ClusterResult;\n if (config.Algorithm === 'dbscan') {\n clusterResult = svc.DBSCANCluster(config.Epsilon, config.MinPoints, config.DistanceMetric);\n } else {\n const k = Math.min(config.K, vectors.length);\n clusterResult = svc.KMeansCluster(k, 100, config.DistanceMetric);\n }\n\n // Step 2: Reduce dimensions to 2D\n const vectorMap = new Map<string, number[]>();\n for (const v of vectors) {\n vectorMap.set(v.Key, v.Vector);\n }\n const projected = await this.reduceDimensionsKeyed(vectorMap);\n\n // Step 3: Build result\n return this.buildResult(vectors, clusterResult, projected, config, startTime);\n }\n\n /**\n * Reduce an array of high-dimensional vectors to 2D points using UMAP\n * (falls back to PCA if UMAP is unavailable).\n *\n * This is a standalone helper when you only need dimensionality reduction\n * without clustering.\n *\n * @param vectors Array of raw number arrays (each the same length).\n * @returns Array of {x, y} points in the same order as input.\n */\n public async ReduceDimensions(vectors: number[][]): Promise<Array<{ x: number; y: number }>> {\n if (vectors.length === 0) return [];\n\n // Build temporary keyed map\n const keys = vectors.map((_, i) => String(i));\n const keyedMap = new Map<string, number[]>();\n keys.forEach((k, i) => keyedMap.set(k, vectors[i]));\n\n const projected = await this.reduceDimensionsKeyed(keyedMap);\n return keys.map(k => {\n const [x, y] = projected.get(k) ?? [0, 0];\n return { x, y };\n });\n }\n\n // ================================================================\n // Private helpers\n // ================================================================\n\n /**\n * Reduce high-dimensional vectors to 2D using UMAP.\n * Falls back to PCA if UMAP is not available.\n */\n private async reduceDimensionsKeyed(vectorMap: Map<string, number[]>): Promise<Map<string, [number, number]>> {\n const keys = Array.from(vectorMap.keys());\n const vectors = keys.map(k => vectorMap.get(k)!);\n const result = new Map<string, [number, number]>();\n\n if (vectors.length <= 2) {\n // Trivial case\n keys.forEach((key, i) => {\n result.set(key, [i * 500 + 250, 350]);\n });\n return result;\n }\n\n try {\n const { UMAP } = await import('umap-js');\n const umap = new UMAP({\n nComponents: 2,\n nNeighbors: Math.min(15, Math.max(2, Math.floor(vectors.length / 5))),\n minDist: 0.1,\n spread: 1.0,\n });\n const embedding = umap.fit(vectors);\n this.normalizeAndAssign(keys, embedding, result);\n } catch {\n // PCA fallback: project onto first 2 principal components\n this.pcaFallback(keys, vectors, result);\n }\n\n return result;\n }\n\n /** Normalize 2D embedding to [padding, viewBoxSize - padding] range */\n private normalizeAndAssign(\n keys: string[],\n embedding: number[][],\n result: Map<string, [number, number]>\n ): void {\n const padding = 60;\n const size = 1000;\n const usable = size - 2 * padding;\n\n let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;\n for (const [x, y] of embedding) {\n if (x < minX) minX = x;\n if (x > maxX) maxX = x;\n if (y < minY) minY = y;\n if (y > maxY) maxY = y;\n }\n const rangeX = maxX - minX || 1;\n const rangeY = maxY - minY || 1;\n\n keys.forEach((key, i) => {\n const nx = padding + ((embedding[i][0] - minX) / rangeX) * usable;\n const ny = padding + ((embedding[i][1] - minY) / rangeY) * usable;\n result.set(key, [nx, ny]);\n });\n }\n\n /** Simple PCA: compute mean, subtract, take first 2 components via power iteration */\n private pcaFallback(\n keys: string[],\n vectors: number[][],\n result: Map<string, [number, number]>\n ): void {\n const dim = vectors[0].length;\n const n = vectors.length;\n\n // Compute mean\n const mean = new Array<number>(dim).fill(0);\n for (const v of vectors) {\n for (let d = 0; d < dim; d++) mean[d] += v[d];\n }\n for (let d = 0; d < dim; d++) mean[d] /= n;\n\n // Center data\n const centered = vectors.map(v => v.map((val, d) => val - mean[d]));\n\n // Power iteration for first 2 components\n const components: number[][] = [];\n for (let comp = 0; comp < 2; comp++) {\n let w = new Array<number>(dim).fill(0).map(() => Math.random() - 0.5);\n for (let iter = 0; iter < 50; iter++) {\n const newW = new Array<number>(dim).fill(0);\n for (const row of centered) {\n const dot = row.reduce((s, val, d) => s + val * w[d], 0);\n for (let d = 0; d < dim; d++) newW[d] += dot * row[d];\n }\n // Deflate by previous components\n for (const prev of components) {\n const proj = newW.reduce((s, val, d) => s + val * prev[d], 0);\n for (let d = 0; d < dim; d++) newW[d] -= proj * prev[d];\n }\n // Normalize\n const norm = Math.sqrt(newW.reduce((s, val) => s + val * val, 0)) || 1;\n w = newW.map(val => val / norm);\n }\n components.push(w);\n }\n\n // Project\n const projections = centered.map(row => [\n row.reduce((s, val, d) => s + val * components[0][d], 0),\n row.reduce((s, val, d) => s + val * (components[1]?.[d] ?? 0), 0),\n ]);\n\n this.normalizeAndAssign(keys, projections, result);\n }\n\n /** Build a ClusterVisualizationResult from raw data */\n private buildResult(\n vectors: ClusterInputVector[],\n clusterResult: ClusterResult,\n projected: Map<string, [number, number]>,\n config: ClusterConfig,\n startTime: number,\n ): ClusterVisualizationResult {\n // Build lookup: vectorKey -> clusterId\n const keyToCluster = new Map<string, number>();\n clusterResult.clusters.forEach((members, clusterId) => {\n for (const key of members) {\n keyToCluster.set(key, clusterId);\n }\n });\n\n // Mark outliers as cluster -1\n if (clusterResult.outliers) {\n for (const key of clusterResult.outliers) {\n keyToCluster.set(key, -1);\n }\n }\n\n // Build points\n const points: ClusterPoint[] = vectors.map(v => {\n const [x, y] = projected.get(v.Key) ?? [500, 350];\n return {\n X: x,\n Y: y,\n ClusterId: keyToCluster.get(v.Key) ?? -1,\n Label: v.Label ?? v.Key,\n VectorKey: v.Key,\n Metadata: v.Metadata ?? {},\n };\n });\n\n // Build cluster infos\n const clusters: ClusterInfo[] = [];\n clusterResult.clusters.forEach((members, clusterId) => {\n clusters.push({\n Id: clusterId,\n Label: `Cluster ${clusterId + 1}`,\n Color: CLUSTER_COLORS[clusterId % CLUSTER_COLORS.length],\n MemberCount: members.length,\n });\n });\n // Sort by ID\n clusters.sort((a, b) => a.Id - b.Id);\n\n const outlierCount = clusterResult.outliers?.length ?? 0;\n\n const metrics: ClusterMetrics = {\n SilhouetteScore: clusterResult.metadata?.silhouetteScore ?? 0,\n ClusterCount: clusters.length,\n ComputationTimeMs: Math.round(performance.now() - startTime),\n RecordCount: vectors.length,\n OutlierCount: outlierCount,\n };\n\n return { Points: points, Clusters: clusters, Metrics: metrics, Config: config };\n }\n\n /** Build an empty result when no vectors are found */\n private buildEmptyResult(config: ClusterConfig, elapsedMs: number): ClusterVisualizationResult {\n return {\n Points: [],\n Clusters: [],\n Metrics: {\n SilhouetteScore: 0,\n ClusterCount: 0,\n ComputationTimeMs: Math.round(elapsedMs),\n RecordCount: 0,\n OutlierCount: 0,\n },\n Config: config,\n };\n }\n}\n"]}
|