2024-06-16 06:00:48 +00:00
|
|
|
import { Plugin, ItemView, WorkspaceLeaf, debounce, Notice } from 'obsidian';
|
2024-05-29 15:58:31 +00:00
|
|
|
import * as d3 from "d3";
|
2024-08-09 15:23:34 +00:00
|
|
|
import _ from 'lodash';
|
2024-05-29 15:58:31 +00:00
|
|
|
|
|
|
|
|
const DEFAULT_NETWORK_SETTINGS : any = {
|
2024-06-16 06:00:48 +00:00
|
|
|
relevanceScoreThreshold: 0.5,
|
2024-06-06 06:26:09 +00:00
|
|
|
nodeSize: 4,
|
2024-05-29 15:58:31 +00:00
|
|
|
linkThickness: 0.3,
|
|
|
|
|
repelForce: 400,
|
|
|
|
|
linkForce: 0.4,
|
|
|
|
|
linkDistance: 70,
|
2024-06-02 21:34:48 +00:00
|
|
|
centerForce: 0.1,
|
|
|
|
|
textFadeThreshold: 1.1,
|
|
|
|
|
minLinkThickness: 0.3,
|
2024-06-04 14:13:44 +00:00
|
|
|
maxLinkThickness: 0.6,
|
2024-06-02 21:34:48 +00:00
|
|
|
maxLabelCharacters: 18,
|
|
|
|
|
linkLabelSize: 7,
|
|
|
|
|
nodeLabelSize: 6,
|
2024-06-18 16:47:26 +00:00
|
|
|
connectionType: 'block',
|
|
|
|
|
noteFillColor: '#7c8594',
|
|
|
|
|
blockFillColor: '#926ec9'
|
2024-06-16 06:00:48 +00:00
|
|
|
}
|
|
|
|
|
|
2024-08-09 15:25:50 +00:00
|
|
|
|
2024-06-18 16:47:26 +00:00
|
|
|
/*
|
|
|
|
|
Main Colors
|
|
|
|
|
Menu text: #a3aecb
|
|
|
|
|
HoveredOverNode: #d46ebe
|
|
|
|
|
NormalNode: #926ec9
|
|
|
|
|
centralNode: #7c8594
|
|
|
|
|
Link: #4c7787
|
|
|
|
|
SliderKnob: #f3ee5d
|
|
|
|
|
*/
|
|
|
|
|
|
2024-06-16 06:00:48 +00:00
|
|
|
interface PluginSettings {
|
|
|
|
|
relevanceScoreThreshold: number;
|
|
|
|
|
nodeSize: number;
|
|
|
|
|
linkThickness: number;
|
|
|
|
|
repelForce: number;
|
|
|
|
|
linkForce: number;
|
|
|
|
|
linkDistance: number;
|
|
|
|
|
centerForce: number;
|
|
|
|
|
textFadeThreshold: number;
|
|
|
|
|
minLinkThickness: number;
|
|
|
|
|
maxLinkThickness: number;
|
|
|
|
|
maxLabelCharacters: number;
|
|
|
|
|
linkLabelSize: number;
|
|
|
|
|
nodeLabelSize: number;
|
|
|
|
|
connectionType: string;
|
2024-06-18 16:47:26 +00:00
|
|
|
noteFillColor: string;
|
|
|
|
|
blockFillColor: string;
|
2024-05-29 15:58:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
declare global {
|
|
|
|
|
interface Window {
|
2025-05-30 15:12:13 +00:00
|
|
|
smart_env: any;
|
2024-05-29 15:58:31 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-31 16:05:53 +00:00
|
|
|
class ScGraphItemView extends ItemView {
|
2024-06-16 06:00:48 +00:00
|
|
|
|
|
|
|
|
private plugin: ScGraphView;
|
|
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
currentNoteKey: string;
|
2024-05-30 05:18:02 +00:00
|
|
|
centralNote: any;
|
|
|
|
|
centralNode: any;
|
|
|
|
|
connectionType = 'block';
|
2024-06-02 21:34:48 +00:00
|
|
|
isHovering: boolean;
|
2024-06-06 06:26:09 +00:00
|
|
|
relevanceScoreThreshold = 0.5;
|
|
|
|
|
nodeSize = 4;
|
2024-05-29 15:58:31 +00:00
|
|
|
linkThickness = 0.3;
|
|
|
|
|
repelForce = 400;
|
|
|
|
|
linkForce = 0.4;
|
|
|
|
|
linkDistance = 70;
|
2024-06-06 06:26:09 +00:00
|
|
|
centerForce = 0.3;
|
2024-05-29 15:58:31 +00:00
|
|
|
textFadeThreshold = 1.1;
|
|
|
|
|
minScore = 1;
|
|
|
|
|
maxScore = 0;
|
2024-05-30 08:17:51 +00:00
|
|
|
minNodeSize = 3;
|
|
|
|
|
maxNodeSize = 6;
|
2024-05-29 23:57:01 +00:00
|
|
|
minLinkThickness = 0.3;
|
2024-06-02 21:34:48 +00:00
|
|
|
maxLinkThickness = 0.6;
|
|
|
|
|
nodeSelection: any;
|
|
|
|
|
linkSelection: any;
|
|
|
|
|
linkLabelSelection: any;
|
|
|
|
|
labelSelection: any;
|
|
|
|
|
updatingVisualization: boolean;
|
2024-05-30 08:17:51 +00:00
|
|
|
isCtrlPressed = false;
|
2024-05-30 16:49:19 +00:00
|
|
|
isAltPressed = false;
|
2024-05-30 08:17:51 +00:00
|
|
|
isDragging = false;
|
2024-05-30 16:49:19 +00:00
|
|
|
isChangingConnectionType = true;
|
2024-05-30 08:17:51 +00:00
|
|
|
selectionBox: any;
|
2024-06-02 21:34:48 +00:00
|
|
|
validatedLinks: any;
|
|
|
|
|
maxLabelCharacters = 18;
|
|
|
|
|
linkLabelSize = 7;
|
|
|
|
|
nodeLabelSize = 6;
|
2024-06-18 16:47:26 +00:00
|
|
|
blockFillColor = '#926ec9';
|
|
|
|
|
noteFillColor = '#7c8594';
|
2024-05-30 16:49:19 +00:00
|
|
|
startX = 0;
|
|
|
|
|
startY = 0;
|
2024-06-02 21:34:48 +00:00
|
|
|
nodes : any = [];
|
|
|
|
|
links : any = [];
|
|
|
|
|
connections : any = [];
|
|
|
|
|
svgGroup: d3.Selection<SVGGElement, unknown, null, undefined>;
|
|
|
|
|
svg: d3.Selection<SVGSVGElement, unknown, null, undefined>;
|
|
|
|
|
centerHighlighted = false;
|
|
|
|
|
simulation: any;
|
|
|
|
|
dragging = false;
|
2024-06-06 06:26:09 +00:00
|
|
|
highlightedNodeId = '-1';
|
|
|
|
|
currentNoteChanging = false;
|
2024-06-18 16:47:26 +00:00
|
|
|
isFiltering = false;
|
|
|
|
|
settingsMade = false;
|
2024-05-29 15:58:31 +00:00
|
|
|
|
2024-06-16 06:00:48 +00:00
|
|
|
constructor(leaf: WorkspaceLeaf, plugin: ScGraphView) {
|
2024-05-29 15:58:31 +00:00
|
|
|
super(leaf);
|
|
|
|
|
this.currentNoteKey = '';
|
2024-06-02 21:34:48 +00:00
|
|
|
this.isHovering = false;
|
2024-06-16 06:00:48 +00:00
|
|
|
this.plugin = plugin;
|
|
|
|
|
|
|
|
|
|
// Set the initial values from the loaded settings
|
|
|
|
|
this.relevanceScoreThreshold = this.plugin.settings.relevanceScoreThreshold;
|
|
|
|
|
this.nodeSize = this.plugin.settings.nodeSize;
|
|
|
|
|
this.linkThickness = this.plugin.settings.linkThickness;
|
|
|
|
|
this.repelForce = this.plugin.settings.repelForce;
|
|
|
|
|
this.linkForce = this.plugin.settings.linkForce;
|
|
|
|
|
this.linkDistance = this.plugin.settings.linkDistance;
|
|
|
|
|
this.centerForce = this.plugin.settings.centerForce;
|
|
|
|
|
this.textFadeThreshold = this.plugin.settings.textFadeThreshold;
|
|
|
|
|
this.minLinkThickness = this.plugin.settings.minLinkThickness;
|
|
|
|
|
this.maxLinkThickness = this.plugin.settings.maxLinkThickness;
|
|
|
|
|
this.maxLabelCharacters = this.plugin.settings.maxLabelCharacters;
|
|
|
|
|
this.linkLabelSize = this.plugin.settings.linkLabelSize;
|
|
|
|
|
this.nodeLabelSize = this.plugin.settings.nodeLabelSize;
|
|
|
|
|
this.connectionType = this.plugin.settings.connectionType;
|
2024-06-18 16:47:26 +00:00
|
|
|
this.noteFillColor = this.plugin.settings.noteFillColor;
|
|
|
|
|
this.blockFillColor = this.plugin.settings.blockFillColor;
|
2024-06-16 06:00:48 +00:00
|
|
|
|
2024-05-29 15:58:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getViewType(): string {
|
2024-06-06 19:05:10 +00:00
|
|
|
return "smart-connections-visualizer";
|
2024-05-29 15:58:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getDisplayText(): string {
|
2024-06-06 19:05:10 +00:00
|
|
|
return "Smart connections visualizer";
|
2024-05-29 15:58:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getIcon(): string {
|
2024-05-30 14:29:30 +00:00
|
|
|
return "git-fork";
|
2024-05-29 15:58:31 +00:00
|
|
|
}
|
|
|
|
|
|
2024-05-30 08:17:51 +00:00
|
|
|
updateNodeAppearance() {
|
|
|
|
|
this.nodeSelection.transition().duration(500)
|
2024-06-18 16:47:26 +00:00
|
|
|
.attr('fill', (d: any) => d.fill)
|
2024-06-02 21:34:48 +00:00
|
|
|
.attr('stroke', (d: any) => d.selected ? 'blanchedalmond' : (d.highlighted ? '#d46ebe' : 'transparent'))
|
|
|
|
|
.attr('stroke-width', (d: any) => d.selected ? 1.5 : (d.highlighted ? 0.3 : 0))
|
|
|
|
|
.attr('opacity', (d: any) => this.getNodeOpacity(d));
|
2024-05-30 08:17:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2024-06-18 16:47:26 +00:00
|
|
|
// getNodeFill(d: any) {
|
|
|
|
|
// if (d.id === this.centralNode.id) return '#7c8594';
|
|
|
|
|
// if (d.highlighted && !d.selected) return '#d46ebe';
|
|
|
|
|
// return d.group === 'note' ? '#7c8594' : '#926ec9';
|
|
|
|
|
// }
|
2024-05-30 08:17:51 +00:00
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
getNodeOpacity(d: any) {
|
|
|
|
|
if (d.id === this.centralNode.id) return 1;
|
|
|
|
|
if (d.selected) return 1;
|
|
|
|
|
if (d.highlighted) return 0.8;
|
|
|
|
|
return this.isHovering ? 0.1 : 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
toggleNodeSelection(nodeId: string) {
|
|
|
|
|
const node = this.nodeSelection.data().find((d: any) => d.id === nodeId);
|
|
|
|
|
if (node) {
|
|
|
|
|
node.selected = !node.selected;
|
|
|
|
|
if (!node.selected) {
|
|
|
|
|
node.highlighted = false;
|
2024-05-30 08:17:51 +00:00
|
|
|
}
|
2024-06-02 21:34:48 +00:00
|
|
|
this.updateNodeAppearance();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
clearSelections() {
|
|
|
|
|
this.nodeSelection.each((d: any) => {
|
|
|
|
|
d.selected = false;
|
|
|
|
|
d.highlighted = false;
|
2024-05-30 08:17:51 +00:00
|
|
|
});
|
|
|
|
|
this.updateNodeAppearance();
|
2024-06-02 21:34:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
highlightNode(node: any) {
|
2024-06-06 06:26:09 +00:00
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
if (node.id === this.centralNode.id) {
|
|
|
|
|
this.centerHighlighted = true;
|
|
|
|
|
}
|
2024-06-06 06:26:09 +00:00
|
|
|
|
|
|
|
|
this.highlightedNodeId = node.id;
|
|
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
this.nodeSelection.each((d: any) => {
|
|
|
|
|
if (d.id !== this.centralNode.id) {
|
|
|
|
|
d.highlighted = (d.id === node.id || this.validatedLinks.some((link: any) =>
|
|
|
|
|
(link.source.id === node.id && link.target.id === d.id) ||
|
|
|
|
|
(link.target.id === node.id && link.source.id === d.id)));
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
this.updateNodeAppearance();
|
|
|
|
|
this.updateLinkAppearance(node);
|
2024-06-06 06:26:09 +00:00
|
|
|
this.updateLabelAppearance(node);
|
2024-06-02 21:34:48 +00:00
|
|
|
this.updateLinkLabelAppearance(node);
|
|
|
|
|
}
|
2024-05-30 08:17:51 +00:00
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
|
|
|
|
|
updateHighlight(d: any, node: any) {
|
|
|
|
|
if (d.id !== this.centralNode.id) {
|
|
|
|
|
d.highlighted = (d.id === node.id || this.validatedLinks.some((link: any) =>
|
|
|
|
|
(link.source.id === node.id && link.target.id === d.id) ||
|
|
|
|
|
(link.target.id === node.id && link.source.id === d.id)));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updateLinkAppearance(node: any) {
|
2024-05-30 08:17:51 +00:00
|
|
|
this.linkSelection.transition().duration(500)
|
|
|
|
|
.attr('opacity', (d: any) => (d.source.id === node.id || d.target.id === node.id) ? 1 : 0.1);
|
2024-06-02 21:34:48 +00:00
|
|
|
}
|
|
|
|
|
|
2024-06-06 06:26:09 +00:00
|
|
|
updateLabelAppearance(node: any) {
|
2024-05-30 08:17:51 +00:00
|
|
|
this.labelSelection.transition().duration(500)
|
2024-06-02 21:34:48 +00:00
|
|
|
.attr('opacity', (d: any) => this.getLabelOpacity(d, node))
|
2024-06-06 06:26:09 +00:00
|
|
|
.text((d: any) => d.id === this.highlightedNodeId ? this.formatLabel(d.name, false) : this.formatLabel(d.name, true));
|
2024-06-02 21:34:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getLabelOpacity(d: any, node: any) {
|
|
|
|
|
if (!node) {
|
|
|
|
|
return 1; // Reset to full opacity if no node is highlighted
|
|
|
|
|
}
|
|
|
|
|
return (d.id === node.id || this.validatedLinks.some((link: any) =>
|
2024-06-06 06:26:09 +00:00
|
|
|
(link.source.id === node.id && link.target.id === d.id)) || d.id == this.centralNode.id) ? 1 : 0.1;
|
2024-06-02 21:34:48 +00:00
|
|
|
}
|
2024-05-31 05:43:16 +00:00
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
updateLinkLabelAppearance(node: any) {
|
2024-05-31 05:43:16 +00:00
|
|
|
this.linkLabelSelection.transition().duration(500)
|
2024-06-09 13:05:16 +00:00
|
|
|
.attr('opacity', (d: any) => {
|
|
|
|
|
return (d.source.id === node.id || d.target.id === node.id) ? 1 : 0;
|
|
|
|
|
})
|
2024-05-30 08:17:51 +00:00
|
|
|
}
|
|
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
|
2024-06-06 06:26:09 +00:00
|
|
|
unhighlightNode(node : any) {
|
|
|
|
|
|
|
|
|
|
// Reset highlighted nodeid
|
|
|
|
|
this.highlightedNodeId = '-1';
|
|
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
this.nodeSelection.each((d: any) => {
|
|
|
|
|
if (d.id !== this.centralNode.id) d.highlighted = false;
|
|
|
|
|
});
|
2024-06-06 06:26:09 +00:00
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
this.updateNodeAppearance();
|
|
|
|
|
this.resetLinkAppearance();
|
|
|
|
|
this.resetLabelAppearance();
|
|
|
|
|
this.resetLinkLabelAppearance();
|
2024-06-06 06:26:09 +00:00
|
|
|
this.updateLabelAppearance(null); // Pass false to reset label position
|
2024-06-02 21:34:48 +00:00
|
|
|
}
|
2024-05-30 08:17:51 +00:00
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
|
|
|
|
|
resetLinkAppearance() {
|
2024-05-30 08:17:51 +00:00
|
|
|
this.linkSelection.transition().duration(500).attr('opacity', 1);
|
2024-06-02 21:34:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
resetLabelAppearance() {
|
2024-05-31 05:43:16 +00:00
|
|
|
this.labelSelection.transition().duration(500).attr('opacity', 1)
|
2024-06-02 21:34:48 +00:00
|
|
|
.text((d: any) => this.formatLabel(d.name, true));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
resetLinkLabelAppearance() {
|
2024-05-31 05:43:16 +00:00
|
|
|
this.linkLabelSelection.transition().duration(500).attr('opacity', 0);
|
2024-05-30 08:17:51 +00:00
|
|
|
}
|
2024-05-31 05:43:16 +00:00
|
|
|
|
|
|
|
|
formatLabel(path: string, truncate: boolean = true) {
|
2024-06-02 21:34:48 +00:00
|
|
|
let label = this.extractLabel(path);
|
|
|
|
|
return truncate ? this.truncateLabel(label) : label;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extractLabel(path: string) {
|
2024-06-06 06:26:09 +00:00
|
|
|
let label = path;
|
|
|
|
|
|
|
|
|
|
// Remove the anchor part if it exists
|
|
|
|
|
if (path && path.includes('#')) {
|
2024-06-16 06:00:48 +00:00
|
|
|
|
2024-06-06 06:26:09 +00:00
|
|
|
const parts = path.split('#');
|
2024-06-16 06:00:48 +00:00
|
|
|
|
|
|
|
|
let lastPart = parts[parts.length - 1]; // Take the last part after splitting by '#'
|
|
|
|
|
|
|
|
|
|
// Check if the last part is empty or matches the pattern {number}
|
|
|
|
|
if (lastPart === '' || /^\{\d+\}$/.test(lastPart)) {
|
|
|
|
|
// Concatenate the last two parts
|
|
|
|
|
lastPart = parts[parts.length - 2] + '#' + lastPart;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// // Check if lastPart contains any '/' and if so, take the last part after splitting by '/'
|
|
|
|
|
if (lastPart.includes('/')) {
|
|
|
|
|
lastPart = lastPart.split('/').pop() || lastPart;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
label = lastPart;
|
|
|
|
|
|
2024-06-06 06:26:09 +00:00
|
|
|
} else if (path) {
|
|
|
|
|
label = path.split('/').pop() || label; // Take the last part after splitting by '/'
|
|
|
|
|
} else {
|
|
|
|
|
return '';
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-16 06:00:48 +00:00
|
|
|
|
|
|
|
|
label = label.replace(/[\[\]]/g, '') // Remove brackets if they exist
|
|
|
|
|
.replace(/\.[^/#]+#(?=\{\d+\}$)/, '') // Remove hashtag if it exists
|
|
|
|
|
.replace(/\.[^/.]+$/, ''); // Remove file extension if it exists
|
|
|
|
|
|
2024-06-06 06:26:09 +00:00
|
|
|
|
|
|
|
|
return label;
|
|
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
truncateLabel(label: string) {
|
|
|
|
|
return label.length > this.maxLabelCharacters ? label.slice(0, this.maxLabelCharacters) + '...' : label;
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-03 15:26:29 +00:00
|
|
|
//@ts-ignore
|
2025-02-03 15:21:49 +00:00
|
|
|
get env() { return window.smart_env; }
|
2025-05-30 15:12:13 +00:00
|
|
|
get smartNotes() { return window.smart_env?.smart_sources?.items; }
|
2024-06-02 21:34:48 +00:00
|
|
|
|
2024-05-30 08:17:51 +00:00
|
|
|
|
2024-05-29 15:58:31 +00:00
|
|
|
async onOpen() {
|
2024-05-31 16:05:53 +00:00
|
|
|
this.contentEl.createEl('h2', { text: 'Smart Visualizer' });
|
|
|
|
|
this.contentEl.createEl('p', { text: 'Waiting for Smart Connections to load...' });
|
2024-06-06 19:05:10 +00:00
|
|
|
console.log(this.app);
|
|
|
|
|
|
2024-06-16 15:32:57 +00:00
|
|
|
// Introduce a small delay before rendering to give view time to load
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
this.render();
|
|
|
|
|
}, 500); // Adjust the delay as needed
|
|
|
|
|
|
2024-05-31 16:05:53 +00:00
|
|
|
}
|
2024-06-02 21:34:48 +00:00
|
|
|
|
2024-05-31 16:05:53 +00:00
|
|
|
async render() {
|
|
|
|
|
// wait until this.smartNotes is available
|
2025-01-23 20:00:47 +00:00
|
|
|
while (!this.env?.collections_loaded) {
|
2024-05-31 16:05:53 +00:00
|
|
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
|
|
|
}
|
2024-06-16 15:32:57 +00:00
|
|
|
|
2024-05-31 16:05:53 +00:00
|
|
|
this.contentEl.empty();
|
2024-06-02 21:34:48 +00:00
|
|
|
this.initializeVariables();
|
2024-05-31 16:05:53 +00:00
|
|
|
if (Object.keys(this.smartNotes).length === 0) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2024-06-30 04:27:13 +00:00
|
|
|
this.setupSettingsMenu();
|
2024-06-02 21:34:48 +00:00
|
|
|
this.setupSVG();
|
2024-08-26 23:48:10 +00:00
|
|
|
this.addEventListeners();
|
2024-06-02 21:34:48 +00:00
|
|
|
this.watchForNoteChanges();
|
2024-06-16 15:32:57 +00:00
|
|
|
|
|
|
|
|
// Load latest active file if opening view for first time
|
|
|
|
|
const currentNodeChange = this.app.workspace.getActiveFile();
|
|
|
|
|
if (currentNodeChange && !this.currentNoteChanging) {
|
|
|
|
|
this.currentNoteKey = currentNodeChange.path;
|
|
|
|
|
this.currentNoteChanging = true;
|
|
|
|
|
this.render();
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-04 00:31:18 +00:00
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
this.updateVisualization();
|
|
|
|
|
}
|
2024-05-31 16:05:53 +00:00
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
async waitForSmartNotes() {
|
|
|
|
|
const maxRetries = 10; // Set a max number of retries to avoid infinite loop
|
|
|
|
|
const delay = 2000; // Delay in milliseconds between retries
|
2024-05-31 16:05:53 +00:00
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
2024-06-12 12:45:11 +00:00
|
|
|
console.log(this.env);
|
2025-01-23 20:00:47 +00:00
|
|
|
if (this.env?.collections_loaded) {
|
2024-06-02 21:34:48 +00:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
await new Promise(resolve => setTimeout(resolve, delay));
|
|
|
|
|
}
|
2024-05-31 16:05:53 +00:00
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
// If we reach here, it means the entities are still not loaded
|
|
|
|
|
console.error('Smart notes did not load in time');
|
|
|
|
|
this.contentEl.createEl('p', { text: 'Failed to load Smart Connections.' });
|
|
|
|
|
}
|
2024-05-31 16:05:53 +00:00
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
initializeVariables() {
|
|
|
|
|
this.minScore = 1;
|
|
|
|
|
this.maxScore = 0;
|
|
|
|
|
}
|
2024-05-31 16:05:53 +00:00
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
setupSVG() {
|
2024-05-31 16:05:53 +00:00
|
|
|
const width = this.contentEl.clientWidth;
|
|
|
|
|
const height = this.contentEl.clientHeight;
|
|
|
|
|
|
|
|
|
|
const svg = d3.select(this.contentEl)
|
|
|
|
|
.append('svg')
|
|
|
|
|
.attr('width', '100%')
|
|
|
|
|
.attr('height', '98%')
|
|
|
|
|
.attr('viewBox', `0 0 ${width} ${height}`)
|
|
|
|
|
.attr('preserveAspectRatio', 'xMidYMid meet')
|
|
|
|
|
.call(d3.zoom()
|
|
|
|
|
.scaleExtent([0.1, 10])
|
|
|
|
|
.on('zoom', (event) => {
|
|
|
|
|
svgGroup.attr('transform', event.transform);
|
2024-06-02 21:34:48 +00:00
|
|
|
this.updateLabelOpacity(event.transform.k);
|
2024-05-31 16:05:53 +00:00
|
|
|
}));
|
2024-05-29 23:57:01 +00:00
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
const svgGroup = svg.append('g');
|
|
|
|
|
|
2024-06-30 04:27:13 +00:00
|
|
|
svgGroup.append('g').attr('class', 'smart-connections-visualizer-links');
|
|
|
|
|
svgGroup.append('g').attr('class', 'smart-connections-visualizer-node-labels');
|
|
|
|
|
svgGroup.append('g').attr('class', 'smart-connections-visualizer-link-labels');
|
|
|
|
|
svgGroup.append('g').attr('class', 'smart-connections-visualizer-nodes');
|
2024-06-02 21:34:48 +00:00
|
|
|
|
|
|
|
|
this.svgGroup = svgGroup;
|
|
|
|
|
this.svg = svg;
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-30 16:49:19 +00:00
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
getSVGDimensions() {
|
|
|
|
|
const width = this.contentEl.clientWidth || this.contentEl.getBoundingClientRect().width;
|
|
|
|
|
const height = this.contentEl.clientHeight || this.contentEl.getBoundingClientRect().height;
|
|
|
|
|
return { width, height };
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-31 16:05:53 +00:00
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
createSVG(width: number, height: number) {
|
|
|
|
|
return d3.select(this.contentEl)
|
|
|
|
|
.append('svg')
|
|
|
|
|
.attr('width', '100%')
|
|
|
|
|
.attr('height', '98%')
|
|
|
|
|
.attr('viewBox', `0 0 ${width} ${height}`)
|
|
|
|
|
.attr('preserveAspectRatio', 'xMidYMid meet')
|
2024-06-06 06:26:09 +00:00
|
|
|
.style('background', '#2d3039')
|
2024-06-02 21:34:48 +00:00
|
|
|
.call(d3.zoom().scaleExtent([0.1, 10]).on('zoom', this.onZoom.bind(this)));
|
|
|
|
|
}
|
2024-05-31 16:05:53 +00:00
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
createSVGGroup(svg: any) {
|
|
|
|
|
return svg.append('g');
|
|
|
|
|
}
|
2024-05-31 16:05:53 +00:00
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
onZoom(event: any) {
|
|
|
|
|
d3.select('g').attr('transform', event.transform);
|
|
|
|
|
this.updateLabelOpacity(event.transform.k);
|
|
|
|
|
}
|
2024-05-31 16:05:53 +00:00
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
initializeSimulation(width: number, height: number) {
|
|
|
|
|
this.simulation = d3.forceSimulation()
|
2024-06-06 06:26:09 +00:00
|
|
|
.force('center', d3.forceCenter(width / 2, height / 2).strength(this.centerForce))
|
2024-06-02 21:34:48 +00:00
|
|
|
.force('charge', d3.forceManyBody().strength(-this.repelForce))
|
|
|
|
|
// .force('link', d3.forceLink().id((d: any) => d.id).distance(this.linkDistance).strength(this.linkForce))
|
|
|
|
|
.force('link', d3.forceLink()
|
|
|
|
|
.id((d: any) => d.id)
|
|
|
|
|
.distance((d: any) => this.linkDistanceScale(d.score))
|
|
|
|
|
.strength(this.linkForce))
|
|
|
|
|
.force('collide', d3.forceCollide().radius(this.nodeSize + 3).strength(0.7))
|
|
|
|
|
.on('tick', this.simulationTickHandler.bind(this));
|
|
|
|
|
|
|
|
|
|
// Add the custom force for labels
|
|
|
|
|
this.simulation.force('labels', this.avoidLabelCollisions.bind(this));
|
2024-06-06 06:26:09 +00:00
|
|
|
|
|
|
|
|
// Disable the centering force after the initial positioning
|
|
|
|
|
// this.simulation.on('end', () => {
|
|
|
|
|
// console.log('Simulation ended, center force removed.');
|
|
|
|
|
// this.simulation.force('center', null); // Remove the center force after initial stabilization
|
|
|
|
|
// });
|
|
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
}
|
2024-05-31 16:05:53 +00:00
|
|
|
|
2024-06-18 16:47:26 +00:00
|
|
|
|
|
|
|
|
renderLegend() {
|
|
|
|
|
if (this.validatedLinks.length === 0) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const types = ['block', 'note']; // Connection types
|
|
|
|
|
const counts = types.map(type => this.nodes.filter((node: any) => (node.group === type) && node.id !== this.centralNode.id).length);
|
|
|
|
|
|
|
|
|
|
// Initialize colors with default values
|
|
|
|
|
let colors: { [key: string]: string } = { 'block': DEFAULT_NETWORK_SETTINGS.blockFillColor, 'note': DEFAULT_NETWORK_SETTINGS.noteFillColor };
|
|
|
|
|
|
|
|
|
|
// Iterate over nodes to find the color for each type
|
|
|
|
|
for (let node of this.nodes) {
|
|
|
|
|
if (colors[node.group]) {
|
|
|
|
|
colors[node.group] = node.fill;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Use contentEl to create a table container
|
2024-06-30 04:27:13 +00:00
|
|
|
const tableContainer = this.contentEl.createEl('div', { cls: 'smart-connections-visualizer-legend-container' });
|
2024-06-18 16:47:26 +00:00
|
|
|
|
|
|
|
|
// Create table header
|
2024-06-30 04:27:13 +00:00
|
|
|
const header = tableContainer.createEl('div', { cls: 'smart-connections-visualizer-legend-header' });
|
2024-06-18 16:47:26 +00:00
|
|
|
['Connection Type', 'Count', 'Color'].forEach(headerTitle => {
|
|
|
|
|
|
|
|
|
|
// Assign appropiate class based on column
|
|
|
|
|
switch(headerTitle) {
|
|
|
|
|
case "Connection Type":
|
2024-06-30 04:27:13 +00:00
|
|
|
header.createEl('div', { text: headerTitle, cls: 'smart-connections-visualizer-variable-col' });
|
2024-06-18 16:47:26 +00:00
|
|
|
break;
|
|
|
|
|
case "Count":
|
2024-06-30 04:27:13 +00:00
|
|
|
header.createEl('div', { text: headerTitle, cls: 'smart-connections-visualizer-count-col' });
|
2024-06-18 16:47:26 +00:00
|
|
|
break;
|
|
|
|
|
case "Color":
|
2024-06-30 04:27:13 +00:00
|
|
|
header.createEl('div', { text: headerTitle, cls: 'smart-connections-visualizer-color-col' });
|
2024-06-18 16:47:26 +00:00
|
|
|
break;
|
|
|
|
|
default:
|
2024-06-30 04:27:13 +00:00
|
|
|
header.createEl('div', { text: headerTitle, cls: 'smart-connections-visualizer-variable-col' });
|
2024-06-18 16:47:26 +00:00
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Create rows for each type
|
|
|
|
|
types.forEach((type, index) => {
|
|
|
|
|
if (counts[index] > 0) { // Check if the count is greater than zero
|
2024-06-30 04:27:13 +00:00
|
|
|
const row = tableContainer.createEl('div', { cls: 'smart-connections-visualizer-legend-row' });
|
2024-06-18 16:47:26 +00:00
|
|
|
|
2024-06-30 04:27:13 +00:00
|
|
|
row.createEl('div', { text: this.capitalizeFirstLetter(type), cls: 'smart-connections-visualizer-variable-col' });
|
|
|
|
|
row.createEl('div', { text: `${counts[index]}`, cls: 'smart-connections-visualizer-count-col' });
|
2024-06-18 16:47:26 +00:00
|
|
|
|
2024-06-30 04:27:13 +00:00
|
|
|
const colorCell = row.createEl('div', { cls: 'smart-connections-visualizer-color-col' });
|
|
|
|
|
const colorPicker = colorCell.createEl('input', { type: 'color', value: colors[type as keyof typeof colors], cls: 'smart-connections-visualizer-legend-color-picker' });
|
2024-06-18 16:47:26 +00:00
|
|
|
|
|
|
|
|
colorPicker.addEventListener('change', (e) => this.updateNodeColors(type, (e.target as HTMLInputElement).value));
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
capitalizeFirstLetter(str: string): string {
|
|
|
|
|
if (!str) return str;
|
|
|
|
|
console.log('string: ', str);
|
|
|
|
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updateNodeColors(type: string, color: string) {
|
|
|
|
|
|
|
|
|
|
if (type === 'note' && color !== this.noteFillColor) {
|
|
|
|
|
this.noteFillColor = color;
|
|
|
|
|
this.plugin.settings.noteFillColor = color;
|
|
|
|
|
this.plugin.saveSettings(); // Save the settings
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (type === 'block' && color !== this.blockFillColor) {
|
|
|
|
|
this.blockFillColor = color;
|
|
|
|
|
this.plugin.settings.noteFillColor = color;
|
|
|
|
|
this.plugin.saveSettings(); // Save the settings
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.nodes.forEach((node : any) => {
|
|
|
|
|
if (node.group === type) {
|
|
|
|
|
node.fill = color;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
this.updateNodeFill();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updateNodeFill() {
|
|
|
|
|
// Update the D3 visualization here
|
|
|
|
|
this.nodeSelection.attr('fill', (d: any) => d.fill);
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-06 06:26:09 +00:00
|
|
|
// Ensure node labels dont collide with any elements
|
2024-06-02 21:34:48 +00:00
|
|
|
avoidLabelCollisions() {
|
|
|
|
|
const padding = 5; // Adjust padding as needed
|
|
|
|
|
return (alpha: number) => {
|
2024-06-06 06:26:09 +00:00
|
|
|
const quadtree = d3.quadtree()
|
|
|
|
|
.x((d: any) => d.x)
|
|
|
|
|
.y((d: any) => d.y)
|
|
|
|
|
.addAll(this.labelSelection.data());
|
|
|
|
|
|
|
|
|
|
this.labelSelection.each((d: any) => {
|
|
|
|
|
const radius = d.radius + padding; // Assuming each label has a radius, adjust as necessary
|
|
|
|
|
const nx1 = d.x - radius, nx2 = d.x + radius, ny1 = d.y - radius, ny2 = d.y + radius;
|
|
|
|
|
|
|
|
|
|
quadtree.visit((quad, x1, y1, x2, y2) => {
|
|
|
|
|
if ('data' in quad && quad.data && (quad.data !== d)) {
|
|
|
|
|
let x = d.x - (quad.data as any).x,
|
|
|
|
|
y = d.y - (quad.data as any).y,
|
|
|
|
|
l = Math.sqrt(x * x + y * y),
|
|
|
|
|
r = radius + (quad.data as any).radius;
|
|
|
|
|
if (l < r) {
|
|
|
|
|
l = (l - r) / l * alpha;
|
|
|
|
|
d.x -= x *= l;
|
|
|
|
|
d.y -= y *= l;
|
|
|
|
|
(quad.data as any).x += x;
|
|
|
|
|
(quad.data as any).y += y;
|
|
|
|
|
}
|
2024-06-02 21:34:48 +00:00
|
|
|
}
|
2024-06-06 06:26:09 +00:00
|
|
|
return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
|
|
|
|
|
});
|
|
|
|
|
});
|
2024-06-02 21:34:48 +00:00
|
|
|
};
|
|
|
|
|
}
|
2024-05-31 16:05:53 +00:00
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
addEventListeners() {
|
|
|
|
|
this.setupSVGEventListeners();
|
|
|
|
|
this.setupKeyboardEventListeners();
|
|
|
|
|
}
|
2024-05-31 16:05:53 +00:00
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
setupSVGEventListeners() {
|
|
|
|
|
d3.select('svg')
|
|
|
|
|
.on('mousedown', this.onMouseDown.bind(this))
|
|
|
|
|
.on('mousemove', this.onMouseMove.bind(this))
|
|
|
|
|
.on('mouseup', this.onMouseUp.bind(this))
|
|
|
|
|
.on('click', this.onSVGClick.bind(this));
|
|
|
|
|
}
|
2024-05-31 16:05:53 +00:00
|
|
|
|
2024-06-06 06:26:09 +00:00
|
|
|
// TODO: Add back in when ready for multiselect
|
2024-06-02 21:34:48 +00:00
|
|
|
onMouseDown(event: any) {
|
2024-06-06 06:26:09 +00:00
|
|
|
// if (!event.ctrlKey) this.clearSelections();
|
|
|
|
|
// this.startBoxSelection(event);
|
2024-06-02 21:34:48 +00:00
|
|
|
}
|
2024-05-31 16:05:53 +00:00
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
onMouseMove(event: any) {
|
2024-06-06 06:26:09 +00:00
|
|
|
// event.stopPropagation();
|
|
|
|
|
// this.updateBoxSelection(event);
|
2024-06-02 21:34:48 +00:00
|
|
|
}
|
2024-05-30 05:18:02 +00:00
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
onMouseUp() {
|
2024-06-06 06:26:09 +00:00
|
|
|
// this.endBoxSelection();
|
2024-06-02 21:34:48 +00:00
|
|
|
}
|
2024-05-31 16:05:53 +00:00
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
onSVGClick(event: any) {
|
|
|
|
|
if (!event.defaultPrevented && !event.ctrlKey) this.clearSelections();
|
|
|
|
|
}
|
2024-05-31 16:05:53 +00:00
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
setupKeyboardEventListeners() {
|
|
|
|
|
document.addEventListener('keydown', this.onKeyDown.bind(this));
|
|
|
|
|
document.addEventListener('keyup', this.onKeyUp.bind(this));
|
|
|
|
|
}
|
2024-05-31 05:43:16 +00:00
|
|
|
|
2024-06-30 04:27:13 +00:00
|
|
|
// TODO:: Add back when ready for multiselect
|
2024-06-02 21:34:48 +00:00
|
|
|
onKeyDown(event: any) {
|
2024-06-30 04:27:13 +00:00
|
|
|
// if (event.key === 'Alt' || event.key === 'AltGraph') this.isAltPressed = true;
|
|
|
|
|
// if (event.key === 'Control') {
|
|
|
|
|
// this.isCtrlPressed = true;
|
|
|
|
|
// d3.select('svg').style('cursor', 'crosshair');
|
|
|
|
|
// }
|
2024-06-02 21:34:48 +00:00
|
|
|
}
|
2024-05-30 08:17:51 +00:00
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
onKeyUp(event: any) {
|
2024-06-30 04:27:13 +00:00
|
|
|
// if (event.key === 'Alt' || event.key === 'AltGraph') this.isAltPressed = false;
|
|
|
|
|
// if (event.key === 'Control') {
|
|
|
|
|
// this.isCtrlPressed = false;
|
|
|
|
|
// d3.select('svg').style('cursor', 'default');
|
|
|
|
|
// }
|
2024-06-02 21:34:48 +00:00
|
|
|
}
|
2024-05-31 16:05:53 +00:00
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
setupSettingsMenu() {
|
2024-06-30 04:27:13 +00:00
|
|
|
// Remove any existing settings icon and dropdown menu
|
|
|
|
|
const existingIcon = this.contentEl.querySelector('.smart-connections-visualizer-settings-icon');
|
|
|
|
|
if (existingIcon) {
|
|
|
|
|
existingIcon.remove();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const existingDropdownMenu = this.contentEl.querySelector('.sc-visualizer-dropdown-menu');
|
|
|
|
|
if (existingDropdownMenu) {
|
|
|
|
|
existingDropdownMenu.remove();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create new settings icon and dropdown menu
|
|
|
|
|
this.createSettingsIcon();
|
|
|
|
|
this.createDropdownMenu();
|
|
|
|
|
this.setupAccordionHeaders();
|
|
|
|
|
this.setupSettingsEventListeners();
|
|
|
|
|
}
|
2024-05-31 16:05:53 +00:00
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
createDropdownMenu() {
|
2024-06-23 06:46:41 +00:00
|
|
|
const dropdownMenu = this.contentEl.createEl('div', { cls: 'sc-visualizer-dropdown-menu' });
|
2024-06-04 14:13:44 +00:00
|
|
|
this.buildDropdownMenuContent(dropdownMenu);
|
|
|
|
|
}
|
2024-05-30 05:18:02 +00:00
|
|
|
|
2024-06-04 14:13:44 +00:00
|
|
|
buildDropdownMenuContent(dropdownMenu: HTMLElement) {
|
2024-06-30 04:27:13 +00:00
|
|
|
const menuHeader = dropdownMenu.createEl('div', { cls: 'smart-connections-visualizer-menu-header' });
|
2024-06-06 06:26:09 +00:00
|
|
|
|
|
|
|
|
// Append the refresh icon created by createRefreshIcon
|
|
|
|
|
const refreshIcon = this.createRefreshIcon();
|
2024-06-30 04:27:13 +00:00
|
|
|
refreshIcon.classList.add('smart-connections-visualizer-icon'); // Ensure it has the 'icon' class for styling
|
|
|
|
|
refreshIcon.setAttribute('id', 'smart-connections-visualizer-refresh-icon'); // Set the ID for specific styling or selection
|
2024-06-06 06:26:09 +00:00
|
|
|
menuHeader.appendChild(refreshIcon);
|
|
|
|
|
|
|
|
|
|
// Append the new X icon created by createNewXIcon
|
|
|
|
|
const xIcon = this.createNewXIcon();
|
2024-06-30 04:27:13 +00:00
|
|
|
xIcon.classList.add('smart-connections-visualizer-icon'); // Ensure it has the 'icon' class for styling
|
|
|
|
|
xIcon.setAttribute('id', 'smart-connections-visualizer-close-icon'); // Set the ID for specific styling or selection
|
2024-06-06 06:26:09 +00:00
|
|
|
menuHeader.appendChild(xIcon);
|
|
|
|
|
|
2024-06-04 14:13:44 +00:00
|
|
|
this.addAccordionItem(dropdownMenu, 'Filters', this.getFiltersContent.bind(this));
|
|
|
|
|
this.addAccordionItem(dropdownMenu, 'Display', this.getDisplayContent.bind(this));
|
|
|
|
|
this.addAccordionItem(dropdownMenu, 'Forces', this.getForcesContent.bind(this));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
addAccordionItem(parent: HTMLElement, title: string, buildContent: (parent: HTMLElement) => void) {
|
2024-06-30 04:27:13 +00:00
|
|
|
const accordionItem = parent.createEl('div', { cls: 'smart-connections-visualizer-accordion-item' });
|
|
|
|
|
const header = accordionItem.createEl('div', { cls: 'smart-connections-visualizer-accordion-header' });
|
2024-06-04 14:13:44 +00:00
|
|
|
|
2024-06-30 04:27:13 +00:00
|
|
|
const arrowIcon = header.createEl('span', { cls: 'smart-connections-visualizer-arrow-icon' });
|
2024-06-04 14:13:44 +00:00
|
|
|
arrowIcon.appendChild(this.createRightArrow());
|
|
|
|
|
|
|
|
|
|
header.createEl('span', { text: title });
|
|
|
|
|
|
2024-06-30 04:27:13 +00:00
|
|
|
const accordionContent = accordionItem.createEl('div', { cls: 'smart-connections-visualizer-accordion-content' });
|
2024-06-04 14:13:44 +00:00
|
|
|
buildContent(accordionContent);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getFiltersContent(parent: HTMLElement) {
|
2024-06-30 04:27:13 +00:00
|
|
|
const sliderContainer1 = parent.createEl('div', { cls: 'smart-connections-visualizer-slider-container' });
|
2024-06-04 14:13:44 +00:00
|
|
|
sliderContainer1.createEl('label', {
|
2024-06-06 19:05:10 +00:00
|
|
|
text: `Min relevance: ${(this.relevanceScoreThreshold * 100).toFixed(0)}%`,
|
2024-06-30 04:27:13 +00:00
|
|
|
attr: { id: 'smart-connections-visualizer-scoreThresholdLabel', for: 'smart-connections-visualizer-scoreThreshold' }
|
2024-06-04 14:13:44 +00:00
|
|
|
});
|
2024-06-06 06:26:09 +00:00
|
|
|
|
2024-06-04 14:13:44 +00:00
|
|
|
const relevanceSlider = sliderContainer1.createEl('input', {
|
|
|
|
|
attr: {
|
|
|
|
|
type: 'range',
|
2024-06-30 04:27:13 +00:00
|
|
|
id: 'smart-connections-visualizer-scoreThreshold',
|
|
|
|
|
class: 'smart-connections-visualizer-slider',
|
2024-06-04 14:13:44 +00:00
|
|
|
name: 'scoreThreshold',
|
|
|
|
|
min: '0',
|
|
|
|
|
max: '0.99',
|
|
|
|
|
step: '0.01'
|
|
|
|
|
}
|
|
|
|
|
});
|
2024-06-02 21:34:48 +00:00
|
|
|
|
2024-06-04 14:13:44 +00:00
|
|
|
// Ensure the slider's value is set after it is appended to the DOM
|
|
|
|
|
relevanceSlider.value = this.relevanceScoreThreshold.toString();
|
|
|
|
|
|
2024-06-30 04:27:13 +00:00
|
|
|
parent.createEl('label', { text: 'Connection type:', cls: 'smart-connections-visualizer-settings-item-content-label' });
|
2024-06-04 14:13:44 +00:00
|
|
|
|
2024-06-30 04:27:13 +00:00
|
|
|
const radioContainer = parent.createEl('div', { cls: 'smart-connections-visualizer-radio-container' });
|
2024-05-31 16:05:53 +00:00
|
|
|
|
2024-06-04 14:13:44 +00:00
|
|
|
const radioBlockLabel = radioContainer.createEl('label');
|
|
|
|
|
const blockRadio = radioBlockLabel.createEl('input', {
|
|
|
|
|
attr: {
|
|
|
|
|
type: 'radio',
|
|
|
|
|
name: 'connectionType',
|
|
|
|
|
value: 'block'
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
blockRadio.checked = (this.connectionType === 'block'); // Set checked based on connectionType
|
|
|
|
|
radioBlockLabel.appendText(' Block');
|
|
|
|
|
|
|
|
|
|
const radioNoteLabel = radioContainer.createEl('label');
|
|
|
|
|
const noteRadio = radioNoteLabel.createEl('input', {
|
|
|
|
|
attr: {
|
|
|
|
|
type: 'radio',
|
|
|
|
|
name: 'connectionType',
|
|
|
|
|
value: 'note'
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
noteRadio.checked = (this.connectionType === 'note'); // Set checked based on connectionType
|
|
|
|
|
radioNoteLabel.appendText(' Note');
|
2024-06-16 06:00:48 +00:00
|
|
|
|
|
|
|
|
const radioBothLabel = radioContainer.createEl('label');
|
|
|
|
|
const bothRadio = radioBothLabel.createEl('input', {
|
|
|
|
|
attr: {
|
|
|
|
|
type: 'radio',
|
|
|
|
|
name: 'connectionType',
|
|
|
|
|
value: 'both'
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
bothRadio.checked = (this.connectionType === 'both'); // Set checked based on connectionType
|
|
|
|
|
radioBothLabel.appendText(' Both');
|
2024-06-04 14:13:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
getDisplayContent(parent: HTMLElement) {
|
|
|
|
|
const displaySettings = [
|
2024-06-30 04:27:13 +00:00
|
|
|
{ id: 'smart-connections-visualizer-nodeSize', label: 'Node size', value: this.nodeSize, min: 1, max: 15, step: 0.01 },
|
|
|
|
|
{ id: 'smart-connections-visualizer-maxLabelCharacters', label: 'Max label characters', value: this.maxLabelCharacters, min: 1, max: 50, step: 1 },
|
|
|
|
|
{ id: 'smart-connections-visualizer-linkLabelSize', label: 'Link label size', value: this.linkLabelSize, min: 1, max: 15, step: 0.01 },
|
|
|
|
|
{ id: 'smart-connections-visualizer-nodeLabelSize', label: 'Node label size', value: this.nodeLabelSize, min: 1, max: 26, step: 1 },
|
|
|
|
|
{ id: 'smart-connections-visualizer-minLinkThickness', label: 'Min link thickness', value: this.minLinkThickness, min: 0.1, max: 10, step: 0.01 },
|
|
|
|
|
{ id: 'smart-connections-visualizer-maxLinkThickness', label: 'Max link thickness', value: this.maxLinkThickness, min: 0.1, max: 10, step: 0.01 },
|
|
|
|
|
{ id: 'smart-connections-visualizer-fadeThreshold', label: 'Text fade threshold', value: this.textFadeThreshold, min: 0.1, max: 10, step: 0.01 }
|
2024-06-04 14:13:44 +00:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
displaySettings.forEach(setting => {
|
2024-06-30 04:27:13 +00:00
|
|
|
const sliderContainer = parent.createEl('div', { cls: 'smart-connections-visualizer-slider-container' });
|
2024-06-04 14:13:44 +00:00
|
|
|
sliderContainer.createEl('label', { text: `${setting.label}: ${setting.value}`, attr: { id: `${setting.id}Label`, for: setting.id } });
|
2024-06-30 04:27:13 +00:00
|
|
|
sliderContainer.createEl('input', { attr: { type: 'range', id: setting.id, class: 'smart-connections-visualizer-slider', name: setting.id, min: `${setting.min}`, max: `${setting.max}`, value: `${setting.value}`, step: `${setting.step}` } });
|
2024-06-04 14:13:44 +00:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
getForcesContent(parent: HTMLElement) {
|
|
|
|
|
const forcesSettings = [
|
2024-06-30 04:27:13 +00:00
|
|
|
{ id: 'smart-connections-visualizer-repelForce', label: 'Repel force', value: this.repelForce, min: 0, max: 1500, step: 1 },
|
|
|
|
|
{ id: 'smart-connections-visualizer-linkForce', label: 'Link force', value: this.linkForce, min: 0, max: 1, step: 0.01 },
|
|
|
|
|
{ id: 'smart-connections-visualizer-linkDistance', label: 'Link distance', value: this.linkDistance, min: 10, max: 200, step: 1 }
|
2024-06-04 14:13:44 +00:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
forcesSettings.forEach(setting => {
|
2024-06-30 04:27:13 +00:00
|
|
|
const sliderContainer = parent.createEl('div', { cls: 'smart-connections-visualizer-slider-container' });
|
2024-06-04 14:13:44 +00:00
|
|
|
sliderContainer.createEl('label', { text: `${setting.label}: ${setting.value}`, attr: { id: `${setting.id}Label`, for: setting.id } });
|
2024-06-30 04:27:13 +00:00
|
|
|
sliderContainer.createEl('input', { attr: { type: 'range', id: setting.id, class: 'smart-connections-visualizer-slider', name: setting.id, min: `${setting.min}`, max: `${setting.max}`, value: `${setting.value}`, step: `${setting.step}` } });
|
2024-06-04 14:13:44 +00:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
toggleDropdownMenu() {
|
2024-06-23 06:46:41 +00:00
|
|
|
const dropdownMenu = document.querySelector('.sc-visualizer-dropdown-menu') as HTMLElement;
|
2024-06-04 14:13:44 +00:00
|
|
|
|
|
|
|
|
if (dropdownMenu) {
|
|
|
|
|
dropdownMenu.classList.toggle('visible');
|
|
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
} else {
|
|
|
|
|
console.error('Dropdown menu element not found');
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-06-04 14:13:44 +00:00
|
|
|
|
2024-05-31 16:05:53 +00:00
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
setupAccordionHeaders() {
|
2024-06-30 04:27:13 +00:00
|
|
|
const accordionHeaders = document.querySelectorAll('.smart-connections-visualizer-accordion-header');
|
2024-06-02 21:34:48 +00:00
|
|
|
accordionHeaders.forEach(header => header.addEventListener('click', this.toggleAccordionContent.bind(this)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
toggleAccordionContent(event: any) {
|
|
|
|
|
const content = event.currentTarget.nextElementSibling;
|
2024-06-30 04:27:13 +00:00
|
|
|
const arrowIcon = event.currentTarget.querySelector('.smart-connections-visualizer-arrow-icon');
|
2024-06-02 21:34:48 +00:00
|
|
|
if (content && arrowIcon) {
|
|
|
|
|
content.classList.toggle('show');
|
2024-06-04 14:13:44 +00:00
|
|
|
arrowIcon.innerHTML = ''; // Clear current content
|
|
|
|
|
arrowIcon.appendChild(content.classList.contains('show') ? this.createDropdownArrow() : this.createRightArrow());
|
2024-06-02 21:34:48 +00:00
|
|
|
}
|
|
|
|
|
}
|
2024-06-04 14:13:44 +00:00
|
|
|
|
|
|
|
|
createDropdownArrow() {
|
|
|
|
|
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
2024-06-30 04:27:13 +00:00
|
|
|
svg.setAttribute("class", "smart-connections-visualizer-dropdown-indicator");
|
2024-06-04 14:13:44 +00:00
|
|
|
svg.setAttribute("viewBox", "0 0 16 16");
|
|
|
|
|
svg.setAttribute("fill", "currentColor");
|
|
|
|
|
|
|
|
|
|
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
|
|
|
path.setAttribute("fill-rule", "evenodd");
|
|
|
|
|
path.setAttribute("d", "M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z");
|
|
|
|
|
|
|
|
|
|
svg.appendChild(path);
|
|
|
|
|
return svg;
|
2024-06-02 21:34:48 +00:00
|
|
|
}
|
2024-06-04 14:13:44 +00:00
|
|
|
|
|
|
|
|
createRightArrow() {
|
|
|
|
|
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
2024-06-30 04:27:13 +00:00
|
|
|
svg.setAttribute("class", "smart-connections-visualizer-dropdown-indicator");
|
2024-06-04 14:13:44 +00:00
|
|
|
svg.setAttribute("viewBox", "0 0 16 16");
|
|
|
|
|
svg.setAttribute("fill", "currentColor");
|
|
|
|
|
|
|
|
|
|
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
|
|
|
path.setAttribute("fill-rule", "evenodd");
|
|
|
|
|
path.setAttribute("d", "M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z");
|
|
|
|
|
|
|
|
|
|
svg.appendChild(path);
|
|
|
|
|
return svg;
|
2024-06-02 21:34:48 +00:00
|
|
|
}
|
|
|
|
|
|
2024-06-06 06:26:09 +00:00
|
|
|
createSettingsIcon() {
|
|
|
|
|
// Create the container div for the settings icon
|
|
|
|
|
const settingsIcon = this.contentEl.createEl('div', {
|
2024-06-18 16:47:26 +00:00
|
|
|
cls: ['smart-connections-visualizer-settings-icon', ],
|
2024-06-06 06:26:09 +00:00
|
|
|
attr: { 'aria-label': 'Open graph settings' }
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Create SVG element for settings icon
|
|
|
|
|
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
|
|
|
svg.setAttribute("width", "24");
|
|
|
|
|
svg.setAttribute("height", "24");
|
|
|
|
|
svg.setAttribute("viewBox", "0 0 24 24");
|
|
|
|
|
svg.setAttribute("fill", "none");
|
|
|
|
|
svg.setAttribute("stroke", "currentColor");
|
|
|
|
|
svg.setAttribute("stroke-width", "2");
|
|
|
|
|
svg.setAttribute("stroke-linecap", "round");
|
|
|
|
|
svg.setAttribute("stroke-linejoin", "round");
|
2024-06-30 04:27:13 +00:00
|
|
|
svg.setAttribute("class", "smart-connections-visualizer-svg-icon smart-connections-visualizer-lucide-settings");
|
2024-06-06 06:26:09 +00:00
|
|
|
|
|
|
|
|
// Create path element for settings icon
|
|
|
|
|
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
|
|
|
path.setAttribute("d", "M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z");
|
|
|
|
|
svg.appendChild(path);
|
|
|
|
|
|
|
|
|
|
// Create circle element for settings icon
|
|
|
|
|
const circle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
|
|
|
|
|
circle.setAttribute("cx", "12");
|
|
|
|
|
circle.setAttribute("cy", "12");
|
|
|
|
|
circle.setAttribute("r", "3");
|
|
|
|
|
svg.appendChild(circle);
|
|
|
|
|
|
|
|
|
|
// Append SVG to settings icon container
|
|
|
|
|
settingsIcon.appendChild(svg);
|
|
|
|
|
|
|
|
|
|
settingsIcon.addEventListener('click', this.toggleDropdownMenu);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
createRefreshIcon() {
|
2024-06-30 04:27:13 +00:00
|
|
|
const refreshIcon = this.contentEl.createEl('div', { cls: 'smart-connections-visualizer-refresh-icon' });
|
2024-06-06 06:26:09 +00:00
|
|
|
|
|
|
|
|
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
|
|
|
svg.setAttribute("width", "24");
|
|
|
|
|
svg.setAttribute("height", "24");
|
|
|
|
|
svg.setAttribute("viewBox", "0 0 24 24");
|
|
|
|
|
svg.setAttribute("fill", "none");
|
|
|
|
|
svg.setAttribute("stroke", "currentColor");
|
|
|
|
|
svg.setAttribute("stroke-width", "2");
|
|
|
|
|
svg.setAttribute("stroke-linecap", "round");
|
|
|
|
|
svg.setAttribute("stroke-linejoin", "round");
|
2024-06-30 04:27:13 +00:00
|
|
|
svg.setAttribute("class", "smart-connections-visualizer-svg-icon smart-connections-visualizer-lucide-rotate-ccw");
|
2024-06-06 06:26:09 +00:00
|
|
|
|
|
|
|
|
const path1 = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
|
|
|
path1.setAttribute("d", "M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8");
|
|
|
|
|
svg.appendChild(path1);
|
|
|
|
|
|
|
|
|
|
const path2 = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
|
|
|
path2.setAttribute("d", "M3 3v5h5");
|
|
|
|
|
svg.appendChild(path2);
|
|
|
|
|
|
|
|
|
|
refreshIcon.appendChild(svg);
|
|
|
|
|
|
|
|
|
|
return refreshIcon; // Return the complete icon element
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
createNewXIcon() {
|
2024-06-30 04:27:13 +00:00
|
|
|
const xIcon = this.contentEl.createEl('div', { cls: 'smart-connections-visualizer-x-icon' });
|
2024-06-06 06:26:09 +00:00
|
|
|
|
|
|
|
|
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
|
|
|
svg.setAttribute("width", "24");
|
|
|
|
|
svg.setAttribute("height", "24");
|
|
|
|
|
svg.setAttribute("viewBox", "0 0 24 24");
|
|
|
|
|
svg.setAttribute("fill", "none");
|
|
|
|
|
svg.setAttribute("stroke", "currentColor");
|
|
|
|
|
svg.setAttribute("stroke-width", "2");
|
|
|
|
|
svg.setAttribute("stroke-linecap", "round");
|
|
|
|
|
svg.setAttribute("stroke-linejoin", "round");
|
2024-06-30 04:27:13 +00:00
|
|
|
svg.setAttribute("class", "smart-connections-visualizer-svg-icon smart-connections-visualizer-lucide-x");
|
2024-06-06 06:26:09 +00:00
|
|
|
|
|
|
|
|
const path1 = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
|
|
|
path1.setAttribute("d", "M18 6 6 18");
|
|
|
|
|
svg.appendChild(path1);
|
|
|
|
|
|
|
|
|
|
const path2 = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
|
|
|
path2.setAttribute("d", "m6 6 12 12");
|
|
|
|
|
svg.appendChild(path2);
|
|
|
|
|
|
|
|
|
|
xIcon.appendChild(svg);
|
|
|
|
|
|
|
|
|
|
return xIcon; // Return the complete icon element
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
setupSettingsEventListeners() {
|
|
|
|
|
this.setupScoreThresholdSlider();
|
|
|
|
|
this.setupNodeSizeSlider();
|
|
|
|
|
this.setupLineThicknessSlider();
|
|
|
|
|
this.setupCenterForceSlider();
|
|
|
|
|
this.setupRepelForceSlider();
|
|
|
|
|
this.setupLinkForceSlider();
|
|
|
|
|
this.setupLinkDistanceSlider();
|
|
|
|
|
this.setupFadeThresholdSlider();
|
|
|
|
|
this.setupMinLinkThicknessSlider();
|
|
|
|
|
this.setupMaxLinkThicknessSlider();
|
|
|
|
|
this.setupConnectionTypeRadios();
|
|
|
|
|
this.setupMaxLabelCharactersSlider();
|
|
|
|
|
this.setupLinkLabelSizeSlider();
|
|
|
|
|
this.setupNodeLabelSizeSlider();
|
|
|
|
|
this.setupCloseIcon();
|
|
|
|
|
this.setupRefreshIcon();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setupScoreThresholdSlider() {
|
2024-06-30 04:27:13 +00:00
|
|
|
const scoreThresholdSlider = document.getElementById('smart-connections-visualizer-scoreThreshold') as HTMLInputElement;
|
2024-06-02 21:34:48 +00:00
|
|
|
if (scoreThresholdSlider) {
|
|
|
|
|
scoreThresholdSlider.addEventListener('input', (event) => this.updateScoreThreshold(event));
|
2024-06-06 19:05:10 +00:00
|
|
|
const debouncedUpdate = debounce((event: Event) => {
|
|
|
|
|
this.updateVisualization(parseFloat((event.target as HTMLInputElement).value));
|
|
|
|
|
}, 500, true);
|
2024-06-02 21:34:48 +00:00
|
|
|
scoreThresholdSlider.addEventListener('input', debouncedUpdate);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updateScoreThreshold(event: any) {
|
|
|
|
|
const newScoreThreshold = parseFloat(event.target.value);
|
2024-06-30 04:27:13 +00:00
|
|
|
const label = document.getElementById('smart-connections-visualizer-scoreThresholdLabel');
|
2024-06-16 06:00:48 +00:00
|
|
|
this.plugin.settings.relevanceScoreThreshold = newScoreThreshold; // Update the settings
|
|
|
|
|
this.plugin.saveSettings(); // Save the settings
|
|
|
|
|
if (label) label.textContent = `Min relevance: ${(newScoreThreshold * 100).toFixed(0)}%`;
|
2024-06-02 21:34:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setupNodeSizeSlider() {
|
2024-06-30 04:27:13 +00:00
|
|
|
const nodeSizeSlider = document.getElementById('smart-connections-visualizer-nodeSize') as HTMLInputElement;
|
2024-06-02 21:34:48 +00:00
|
|
|
if (nodeSizeSlider) {
|
|
|
|
|
nodeSizeSlider.addEventListener('input', (event) => this.updateNodeSize(event));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updateNodeSize(event: any) {
|
|
|
|
|
const newNodeSize = parseFloat(event.target.value);
|
2024-06-30 04:27:13 +00:00
|
|
|
const label = document.getElementById('smart-connections-visualizer-nodeSizeLabel');
|
2024-06-16 06:00:48 +00:00
|
|
|
if (label) label.textContent = `Node size: ${newNodeSize}`;
|
|
|
|
|
this.plugin.settings.nodeSize = newNodeSize; // Update the settings
|
|
|
|
|
this.plugin.saveSettings(); // Save the settings
|
2024-06-02 21:34:48 +00:00
|
|
|
this.nodeSize = newNodeSize;
|
|
|
|
|
this.updateNodeSizes();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setupLineThicknessSlider() {
|
2024-06-30 04:27:13 +00:00
|
|
|
const lineThicknessSlider = document.getElementById('smart-connections-visualizer-lineThickness') as HTMLInputElement;
|
2024-06-02 21:34:48 +00:00
|
|
|
if (lineThicknessSlider) {
|
|
|
|
|
lineThicknessSlider.addEventListener('input', (event) => this.updateLineThickness(event));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updateLineThickness(event: any) {
|
|
|
|
|
const newLineThickness = parseFloat(event.target.value);
|
|
|
|
|
const label = document.getElementById('lineThicknessLabel');
|
2024-06-16 06:00:48 +00:00
|
|
|
if (label) label.textContent = `Line thickness: ${newLineThickness}`;
|
|
|
|
|
this.plugin.settings.linkThickness = newLineThickness; // Update the settings
|
|
|
|
|
this.plugin.saveSettings(); // Save the settings
|
2024-06-02 21:34:48 +00:00
|
|
|
this.linkThickness = newLineThickness;
|
|
|
|
|
this.updateLinkThickness();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setupCenterForceSlider() {
|
2024-06-30 04:27:13 +00:00
|
|
|
const centerForceSlider = document.getElementById('smart-connections-visualizer-centerForce') as HTMLInputElement;
|
2024-06-02 21:34:48 +00:00
|
|
|
if (centerForceSlider) {
|
|
|
|
|
centerForceSlider.addEventListener('input', (event) => this.updateCenterForce(event));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updateCenterForce(event: any) {
|
|
|
|
|
const newCenterForce = parseFloat(event.target.value);
|
|
|
|
|
const label = document.getElementById('centerForceLabel');
|
2024-06-16 06:00:48 +00:00
|
|
|
if (label) label.textContent = `Center force: ${newCenterForce}`;
|
|
|
|
|
this.plugin.settings.centerForce = newCenterForce; // Update the settings
|
|
|
|
|
this.plugin.saveSettings(); // Save the settings
|
2024-06-02 21:34:48 +00:00
|
|
|
this.centerForce = newCenterForce;
|
|
|
|
|
this.updateSimulationForces();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setupRepelForceSlider() {
|
2024-06-30 04:27:13 +00:00
|
|
|
const repelForceSlider = document.getElementById('smart-connections-visualizer-repelForce') as HTMLInputElement;
|
2024-06-02 21:34:48 +00:00
|
|
|
if (repelForceSlider) {
|
|
|
|
|
repelForceSlider.addEventListener('input', (event) => this.updateRepelForce(event));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updateRepelForce(event: any) {
|
|
|
|
|
const newRepelForce = parseFloat(event.target.value);
|
2024-06-30 04:27:13 +00:00
|
|
|
const label = document.getElementById('smart-connections-visualizer-repelForceLabel');
|
2024-06-16 06:00:48 +00:00
|
|
|
if (label) label.textContent = `Repel force: ${newRepelForce}`;
|
2024-06-02 21:34:48 +00:00
|
|
|
this.repelForce = newRepelForce;
|
2024-06-16 06:00:48 +00:00
|
|
|
this.plugin.settings.repelForce = newRepelForce; // Update the settings
|
|
|
|
|
this.plugin.saveSettings(); // Save the settings
|
2024-06-02 21:34:48 +00:00
|
|
|
this.updateSimulationForces();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setupLinkForceSlider() {
|
2024-06-30 04:27:13 +00:00
|
|
|
const linkForceSlider = document.getElementById('smart-connections-visualizer-linkForce') as HTMLInputElement;
|
2024-06-02 21:34:48 +00:00
|
|
|
if (linkForceSlider) {
|
|
|
|
|
linkForceSlider.addEventListener('input', (event) => this.updateLinkForce(event));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updateLinkForce(event: any) {
|
|
|
|
|
const newLinkForce = parseFloat(event.target.value);
|
2024-06-30 04:27:13 +00:00
|
|
|
const label = document.getElementById('smart-connections-visualizer-linkForceLabel');
|
2024-06-16 06:00:48 +00:00
|
|
|
if (label) label.textContent = `Link force: ${newLinkForce}`;
|
2024-06-02 21:34:48 +00:00
|
|
|
this.linkForce = newLinkForce;
|
2024-06-16 06:00:48 +00:00
|
|
|
this.plugin.settings.linkForce = newLinkForce; // Update the settings
|
|
|
|
|
this.plugin.saveSettings(); // Save the settings
|
2024-06-02 21:34:48 +00:00
|
|
|
this.updateSimulationForces();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setupLinkDistanceSlider() {
|
2024-06-30 04:27:13 +00:00
|
|
|
const linkDistanceSlider = document.getElementById('smart-connections-visualizer-linkDistance') as HTMLInputElement;
|
2024-06-02 21:34:48 +00:00
|
|
|
if (linkDistanceSlider) {
|
|
|
|
|
linkDistanceSlider.addEventListener('input', (event) => this.updateLinkDistance(event));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updateLinkDistance(event: any) {
|
|
|
|
|
const newLinkDistance = parseFloat(event.target.value);
|
2024-06-30 04:27:13 +00:00
|
|
|
const label = document.getElementById('smart-connections-visualizer-linkDistanceLabel');
|
2024-06-16 06:00:48 +00:00
|
|
|
if (label) label.textContent = `Link distance: ${newLinkDistance}`;
|
2024-06-02 21:34:48 +00:00
|
|
|
this.linkDistance = newLinkDistance;
|
2024-06-16 06:00:48 +00:00
|
|
|
this.plugin.settings.linkDistance = newLinkDistance; // Update the settings
|
|
|
|
|
this.plugin.saveSettings(); // Save the settings
|
2024-06-02 21:34:48 +00:00
|
|
|
this.updateSimulationForces();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setupFadeThresholdSlider() {
|
2024-06-30 04:27:13 +00:00
|
|
|
const fadeThresholdSlider = document.getElementById('smart-connections-visualizer-fadeThreshold') as HTMLInputElement;
|
2024-06-02 21:34:48 +00:00
|
|
|
if (fadeThresholdSlider) {
|
|
|
|
|
fadeThresholdSlider.addEventListener('input', (event) => {
|
|
|
|
|
this.updateFadeThreshold(event);
|
|
|
|
|
this.updateLabelOpacity(d3.zoomTransform(d3.select('svg').node() as Element).k);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updateFadeThreshold(event: any) {
|
|
|
|
|
const newFadeThreshold = parseFloat(event.target.value);
|
2024-06-30 04:27:13 +00:00
|
|
|
const label = document.getElementById('smart-connections-visualizer-fadeThresholdLabel');
|
2024-06-16 06:00:48 +00:00
|
|
|
if (label) label.textContent = `Text fade threshold: ${newFadeThreshold}`;
|
2024-06-02 21:34:48 +00:00
|
|
|
this.textFadeThreshold = newFadeThreshold;
|
2024-06-16 06:00:48 +00:00
|
|
|
this.plugin.settings.textFadeThreshold = newFadeThreshold; // Update the settings
|
|
|
|
|
this.plugin.saveSettings(); // Save the settings
|
2024-06-02 21:34:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setupMinLinkThicknessSlider() {
|
2024-06-30 04:27:13 +00:00
|
|
|
const minLinkThicknessSlider = document.getElementById('smart-connections-visualizer-minLinkThickness') as HTMLInputElement;
|
2024-06-02 21:34:48 +00:00
|
|
|
if (minLinkThicknessSlider) {
|
|
|
|
|
minLinkThicknessSlider.addEventListener('input', (event) => this.updateMinLinkThickness(event));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updateMinLinkThickness(event: any) {
|
|
|
|
|
const newMinLinkThickness = parseFloat(event.target.value);
|
2024-06-30 04:27:13 +00:00
|
|
|
const label = document.getElementById('smart-connections-visualizer-minLinkThicknessLabel');
|
2024-06-16 06:00:48 +00:00
|
|
|
if (label) label.textContent = `Min link thickness: ${newMinLinkThickness}`;
|
2024-06-02 21:34:48 +00:00
|
|
|
this.minLinkThickness = newMinLinkThickness;
|
2024-06-16 06:00:48 +00:00
|
|
|
this.plugin.settings.minLinkThickness = newMinLinkThickness; // Update the settings
|
|
|
|
|
this.plugin.saveSettings(); // Save the settings
|
2024-06-02 21:34:48 +00:00
|
|
|
this.updateLinkThickness();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setupMaxLinkThicknessSlider() {
|
2024-06-30 04:27:13 +00:00
|
|
|
const maxLinkThicknessSlider = document.getElementById('smart-connections-visualizer-maxLinkThickness') as HTMLInputElement;
|
2024-06-02 21:34:48 +00:00
|
|
|
if (maxLinkThicknessSlider) {
|
|
|
|
|
maxLinkThicknessSlider.addEventListener('input', (event) => this.updateMaxLinkThickness(event));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updateMaxLinkThickness(event: any) {
|
|
|
|
|
const newMaxLinkThickness = parseFloat(event.target.value);
|
2024-06-30 04:27:13 +00:00
|
|
|
const label = document.getElementById('smart-connections-visualizer-maxLinkThicknessLabel');
|
2024-06-16 06:00:48 +00:00
|
|
|
if (label) label.textContent = `Max link thickness: ${newMaxLinkThickness}`;
|
2024-06-02 21:34:48 +00:00
|
|
|
this.maxLinkThickness = newMaxLinkThickness;
|
2024-06-16 06:00:48 +00:00
|
|
|
this.plugin.settings.maxLinkThickness = newMaxLinkThickness; // Update the settings
|
|
|
|
|
this.plugin.saveSettings(); // Save the settings
|
2024-06-02 21:34:48 +00:00
|
|
|
this.updateLinkThickness();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setupConnectionTypeRadios() {
|
|
|
|
|
const connectionTypeRadios = document.querySelectorAll('input[name="connectionType"]');
|
|
|
|
|
connectionTypeRadios.forEach(radio => radio.addEventListener('change', (event) => this.updateConnectionType(event)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updateConnectionType(event: any) {
|
|
|
|
|
this.connectionType = event.target.value;
|
|
|
|
|
this.isChangingConnectionType = true;
|
2024-06-16 06:00:48 +00:00
|
|
|
this.plugin.settings.connectionType = this.connectionType; // Update the settings
|
|
|
|
|
this.plugin.saveSettings(); // Save the settings
|
2024-06-02 21:34:48 +00:00
|
|
|
this.updateVisualization();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setupMaxLabelCharactersSlider() {
|
2024-06-30 04:27:13 +00:00
|
|
|
const maxLabelCharactersSlider = document.getElementById('smart-connections-visualizer-maxLabelCharacters') as HTMLInputElement;
|
2024-06-02 21:34:48 +00:00
|
|
|
if (maxLabelCharactersSlider) {
|
|
|
|
|
maxLabelCharactersSlider.addEventListener('input', (event) => this.updateMaxLabelCharacters(event));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updateMaxLabelCharacters(event: any) {
|
|
|
|
|
const newMaxLabelCharacters = parseInt(event.target.value, 10);
|
2024-06-30 04:27:13 +00:00
|
|
|
const label = document.getElementById('smart-connections-visualizer-maxLabelCharactersLabel');
|
2024-06-02 21:34:48 +00:00
|
|
|
if (label) label.textContent = `Max Label Characters: ${newMaxLabelCharacters}`;
|
|
|
|
|
this.maxLabelCharacters = newMaxLabelCharacters;
|
|
|
|
|
this.updateNodeLabels();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setupLinkLabelSizeSlider() {
|
2024-06-30 04:27:13 +00:00
|
|
|
const linkLabelSizeSlider = document.getElementById('smart-connections-visualizer-linkLabelSize') as HTMLInputElement;
|
2024-06-02 21:34:48 +00:00
|
|
|
if (linkLabelSizeSlider) {
|
|
|
|
|
linkLabelSizeSlider.addEventListener('input', (event) => this.updateLinkLabelSize(event));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updateLinkLabelSize(event: any) {
|
|
|
|
|
const newLinkLabelSize = parseFloat(event.target.value);
|
2024-06-30 04:27:13 +00:00
|
|
|
const label = document.getElementById('smart-connections-visualizer-linkLabelSizeLabel');
|
2024-06-02 21:34:48 +00:00
|
|
|
if (label) label.textContent = `Link Label Size: ${newLinkLabelSize}`;
|
|
|
|
|
this.linkLabelSize = newLinkLabelSize;
|
|
|
|
|
this.updateLinkLabelSizes();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setupNodeLabelSizeSlider() {
|
2024-06-30 04:27:13 +00:00
|
|
|
const nodeLabelSizeSlider = document.getElementById('smart-connections-visualizer-nodeLabelSize') as HTMLInputElement;
|
2024-06-02 21:34:48 +00:00
|
|
|
if (nodeLabelSizeSlider) {
|
|
|
|
|
nodeLabelSizeSlider.addEventListener('input', (event) => this.updateNodeLabelSize(event));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updateNodeLabelSize(event: any) {
|
2024-06-30 04:27:13 +00:00
|
|
|
console.log('flounddd');
|
2024-06-02 21:34:48 +00:00
|
|
|
const newNodeLabelSize = parseFloat(event.target.value);
|
2024-06-30 04:27:13 +00:00
|
|
|
const label = document.getElementById('smart-connections-visualizer-nodeLabelSizeLabel');
|
2024-06-02 21:34:48 +00:00
|
|
|
if (label) label.textContent = `Node Label Size: ${newNodeLabelSize}`;
|
|
|
|
|
this.nodeLabelSize = newNodeLabelSize;
|
|
|
|
|
this.updateNodeLabelSizes();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Updated setupCloseIcon method
|
|
|
|
|
setupCloseIcon() {
|
2024-06-30 04:27:13 +00:00
|
|
|
const closeIcon = document.getElementById('smart-connections-visualizer-close-icon');
|
2024-06-02 21:34:48 +00:00
|
|
|
if (closeIcon) closeIcon.addEventListener('click', () => this.toggleDropdownMenu());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
closeDropdownMenu() {
|
2024-06-23 06:46:41 +00:00
|
|
|
const dropdownMenu = document.querySelector('.sc-visualizer-dropdown-menu');
|
2024-06-02 21:34:48 +00:00
|
|
|
if (dropdownMenu) dropdownMenu.classList.remove('open');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setupRefreshIcon() {
|
2024-06-30 04:27:13 +00:00
|
|
|
const refreshIcon = document.getElementById('smart-connections-visualizer-refresh-icon');
|
2024-06-02 21:34:48 +00:00
|
|
|
if (refreshIcon) refreshIcon.addEventListener('click', () => this.resetToDefault());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
resetToDefault() {
|
2024-06-18 16:47:26 +00:00
|
|
|
|
|
|
|
|
// Reset all values to their default
|
2024-06-17 23:20:03 +00:00
|
|
|
this.relevanceScoreThreshold = DEFAULT_NETWORK_SETTINGS.relevanceScoreThreshold;
|
2024-06-02 21:34:48 +00:00
|
|
|
this.nodeSize = DEFAULT_NETWORK_SETTINGS.nodeSize;
|
|
|
|
|
this.linkThickness = DEFAULT_NETWORK_SETTINGS.lineThickness;
|
|
|
|
|
this.repelForce = DEFAULT_NETWORK_SETTINGS.repelForce;
|
|
|
|
|
this.linkForce = DEFAULT_NETWORK_SETTINGS.linkForce;
|
|
|
|
|
this.linkDistance = DEFAULT_NETWORK_SETTINGS.linkDistance;
|
|
|
|
|
this.centerForce = DEFAULT_NETWORK_SETTINGS.centerForce;
|
|
|
|
|
this.textFadeThreshold = DEFAULT_NETWORK_SETTINGS.textFadeThreshold;
|
|
|
|
|
this.minLinkThickness = DEFAULT_NETWORK_SETTINGS.minLinkThickness;
|
|
|
|
|
this.maxLinkThickness = DEFAULT_NETWORK_SETTINGS.maxLinkThickness;
|
|
|
|
|
this.maxLabelCharacters = DEFAULT_NETWORK_SETTINGS.maxLabelCharacters;
|
|
|
|
|
this.linkLabelSize = DEFAULT_NETWORK_SETTINGS.linkLabelSize;
|
|
|
|
|
this.nodeLabelSize = DEFAULT_NETWORK_SETTINGS.nodeLabelSize;
|
2024-06-17 23:20:03 +00:00
|
|
|
this.connectionType = DEFAULT_NETWORK_SETTINGS.connectionType;
|
2024-06-18 16:47:26 +00:00
|
|
|
this.noteFillColor = DEFAULT_NETWORK_SETTINGS.noteFillColor;
|
|
|
|
|
this.blockFillColor = DEFAULT_NETWORK_SETTINGS.blockFillColor;
|
2024-06-17 23:20:03 +00:00
|
|
|
|
2024-06-18 16:47:26 +00:00
|
|
|
// Save plugin settings
|
2024-06-17 23:20:03 +00:00
|
|
|
this.plugin.settings.relevanceScoreThreshold = DEFAULT_NETWORK_SETTINGS.relevanceScoreThreshold;
|
|
|
|
|
this.plugin.settings.nodeSize = DEFAULT_NETWORK_SETTINGS.nodeSize;
|
|
|
|
|
this.plugin.settings.linkThickness = DEFAULT_NETWORK_SETTINGS.lineThickness;
|
|
|
|
|
this.plugin.settings.repelForce = DEFAULT_NETWORK_SETTINGS.repelForce;
|
|
|
|
|
this.plugin.settings.linkForce = DEFAULT_NETWORK_SETTINGS.linkForce;
|
|
|
|
|
this.plugin.settings.linkDistance = DEFAULT_NETWORK_SETTINGS.linkDistance;
|
|
|
|
|
this.plugin.settings.centerForce = DEFAULT_NETWORK_SETTINGS.centerForce;
|
|
|
|
|
this.plugin.settings.textFadeThreshold = DEFAULT_NETWORK_SETTINGS.textFadeThreshold;
|
|
|
|
|
this.plugin.settings.minLinkThickness = DEFAULT_NETWORK_SETTINGS.minLinkThickness;
|
|
|
|
|
this.plugin.settings.maxLinkThickness = DEFAULT_NETWORK_SETTINGS.maxLinkThickness;
|
|
|
|
|
this.plugin.settings.maxLabelCharacters = DEFAULT_NETWORK_SETTINGS.maxLabelCharacters;
|
|
|
|
|
this.plugin.settings.linkLabelSize = DEFAULT_NETWORK_SETTINGS.linkLabelSize;
|
|
|
|
|
this.plugin.settings.nodeLabelSize = DEFAULT_NETWORK_SETTINGS.nodeLabelSize;
|
|
|
|
|
this.plugin.settings.connectionType = DEFAULT_NETWORK_SETTINGS.connectionType;
|
2024-06-18 16:47:26 +00:00
|
|
|
this.plugin.settings.noteFillColor = DEFAULT_NETWORK_SETTINGS.noteFillColor;
|
|
|
|
|
this.plugin.settings.blockFillColor = DEFAULT_NETWORK_SETTINGS.blockFillColor;
|
2024-06-17 23:20:03 +00:00
|
|
|
this.plugin.saveSettings(); // Save the settings
|
|
|
|
|
|
2024-06-18 16:47:26 +00:00
|
|
|
// Update visualization
|
|
|
|
|
this.updateLabelsToDefaults();
|
|
|
|
|
this.updateSliders();
|
|
|
|
|
this.updateNodeSizes();
|
|
|
|
|
this.updateLinkThickness();
|
|
|
|
|
this.updateSimulationForces();
|
|
|
|
|
this.updateVisualization(this.relevanceScoreThreshold);
|
2024-06-30 04:27:13 +00:00
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
}
|
|
|
|
|
|
2024-06-06 06:26:09 +00:00
|
|
|
updateLabelsToDefaults() {
|
|
|
|
|
const labels = {
|
2024-06-30 04:27:13 +00:00
|
|
|
'smart-connections-visualizer-scoreThresholdLabel': `Min relevance: ${(this.relevanceScoreThreshold * 100).toFixed(0)}%`,
|
|
|
|
|
'smart-connections-visualizer-nodeSizeLabel': `Node size: ${this.nodeSize}`,
|
|
|
|
|
'smart-connections-visualizer-maxLabelCharactersLabel': `Max label characters: ${this.maxLabelCharacters}`,
|
|
|
|
|
'smart-connections-visualizer-linkLabelSizeLabel': `Link label size: ${this.linkLabelSize}`,
|
|
|
|
|
'smart-connections-visualizer-smart-connections-visualizer-nodeLabelSizeLabel': `Node label size: ${this.nodeLabelSize}`,
|
|
|
|
|
'smart-connections-visualizer-minLinkThicknessLabel': `Min link thickness: ${this.minLinkThickness}`,
|
|
|
|
|
'smart-connections-visualizer-maxLinkThicknessLabel': `Max link thickness: ${this.maxLinkThickness}`,
|
|
|
|
|
'smart-connections-visualizer-fadeThresholdLabel': `Text fade threshold: ${this.textFadeThreshold}`,
|
|
|
|
|
'smart-connections-visualizer-repelForceLabel': `Repel force: ${this.repelForce}`,
|
|
|
|
|
'smart-connections-visualizer-linkForceLabel': `Link force: ${this.linkForce}`,
|
|
|
|
|
'smart-connections-visualizer-linkDistanceLabel': `Link distance: ${this.linkDistance}`
|
2024-06-06 06:26:09 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for (const [id, text] of Object.entries(labels)) {
|
|
|
|
|
const label = document.getElementById(id);
|
|
|
|
|
if (label) {
|
|
|
|
|
label.textContent = text;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
updateSliders() {
|
2024-06-30 04:27:13 +00:00
|
|
|
const scoreThresholdSlider = document.getElementById('smart-connections-visualizer-scoreThreshold') as HTMLInputElement;
|
|
|
|
|
const nodeSizeSlider = document.getElementById('smart-connections-visualizer-nodeSize') as HTMLInputElement;
|
2024-06-02 21:34:48 +00:00
|
|
|
// const lineThicknessSlider = document.getElementById('lineThickness') as HTMLInputElement;
|
|
|
|
|
// const centerForceSlider = document.getElementById('centerForce') as HTMLInputElement;
|
2024-06-30 04:27:13 +00:00
|
|
|
const repelForceSlider = document.getElementById('smart-connections-visualizer-repelForce') as HTMLInputElement;
|
|
|
|
|
const linkForceSlider = document.getElementById('smart-connections-visualizer-linkForce') as HTMLInputElement;
|
|
|
|
|
const linkDistanceSlider = document.getElementById('smart-connections-visualizer-linkDistance') as HTMLInputElement;
|
|
|
|
|
const fadeThresholdSlider = document.getElementById('smart-connections-visualizer-fadeThreshold') as HTMLInputElement;
|
|
|
|
|
const minLinkThicknessSlider = document.getElementById('smart-connections-visualizer-minLinkThickness') as HTMLInputElement;
|
|
|
|
|
const maxLinkThicknessSlider = document.getElementById('smart-connections-visualizer-maxLinkThickness') as HTMLInputElement;
|
|
|
|
|
const maxLabelCharactersSlider = document.getElementById('smart-connections-visualizer-maxLabelCharacters') as HTMLInputElement;
|
|
|
|
|
const linkLabelSizeSlider = document.getElementById('smart-connections-visualizer-linkLabelSize') as HTMLInputElement;
|
|
|
|
|
const nodeLabelSizeSlider = document.getElementById('smart-connections-visualizer-nodeLabelSize') as HTMLInputElement;
|
2024-06-02 21:34:48 +00:00
|
|
|
|
|
|
|
|
scoreThresholdSlider.value = `${this.relevanceScoreThreshold}`;
|
|
|
|
|
nodeSizeSlider.value = `${this.nodeSize}`;
|
|
|
|
|
// lineThicknessSlider.value = `${this.linkThickness}`;
|
|
|
|
|
// centerForceSlider.value = `${this.centerForce}`;
|
|
|
|
|
repelForceSlider.value = `${this.repelForce}`;
|
|
|
|
|
linkForceSlider.value = `${this.linkForce}`;
|
|
|
|
|
linkDistanceSlider.value = `${this.linkDistance}`;
|
|
|
|
|
fadeThresholdSlider.value = `${this.textFadeThreshold}`;
|
|
|
|
|
minLinkThicknessSlider.value = `${this.minLinkThickness}`;
|
|
|
|
|
maxLinkThicknessSlider.value = `${this.maxLinkThickness}`;
|
|
|
|
|
maxLabelCharactersSlider.value = `${this.maxLabelCharacters}`;
|
|
|
|
|
linkLabelSizeSlider.value = `${this.linkLabelSize}`;
|
|
|
|
|
nodeLabelSizeSlider.value = `${this.nodeLabelSize}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
watchForNoteChanges() {
|
|
|
|
|
this.app.workspace.on('file-open', (file) => {
|
2025-01-23 20:00:47 +00:00
|
|
|
// @ts-ignore
|
2024-08-09 15:23:34 +00:00
|
|
|
if (file && (this.currentNoteKey !== file.path) && !this.isHovering && this.containerEl.children[1].checkVisibility()) {
|
2024-06-02 21:34:48 +00:00
|
|
|
this.currentNoteKey = file.path;
|
2024-06-06 06:26:09 +00:00
|
|
|
this.currentNoteChanging = true;
|
2024-06-02 22:40:43 +00:00
|
|
|
this.render();
|
2024-05-31 16:05:53 +00:00
|
|
|
}
|
2024-06-02 21:34:48 +00:00
|
|
|
});
|
|
|
|
|
}
|
2024-05-31 16:05:53 +00:00
|
|
|
|
2025-01-04 00:31:18 +00:00
|
|
|
async updateVisualization(newScoreThreshold?: number) {
|
2024-06-17 23:20:03 +00:00
|
|
|
|
|
|
|
|
// Only update if we're not already updating
|
2024-06-02 21:34:48 +00:00
|
|
|
if (this.updatingVisualization && !this.isChangingConnectionType) {
|
2024-06-02 22:40:43 +00:00
|
|
|
this.updatingVisualization = false;
|
2024-06-06 06:26:09 +00:00
|
|
|
this.currentNoteChanging = false;
|
2024-06-02 21:34:48 +00:00
|
|
|
return;
|
|
|
|
|
}
|
2024-06-17 23:20:03 +00:00
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
this.isChangingConnectionType = false;
|
|
|
|
|
|
|
|
|
|
if (newScoreThreshold !== undefined) {
|
|
|
|
|
this.relevanceScoreThreshold = newScoreThreshold;
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-04 00:31:18 +00:00
|
|
|
await this.updateConnections();
|
2024-06-17 23:20:03 +00:00
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
const filteredConnections = this.connections.filter((connection: any) => connection.score >= this.relevanceScoreThreshold);
|
|
|
|
|
const visibleNodes = new Set<string>();
|
|
|
|
|
filteredConnections.forEach((connection: any) => {
|
|
|
|
|
visibleNodes.add(connection.source);
|
|
|
|
|
visibleNodes.add(connection.target);
|
|
|
|
|
});
|
2024-06-14 16:38:00 +00:00
|
|
|
// Always include the central node
|
|
|
|
|
visibleNodes.add(this.centralNote.key);
|
2024-06-02 21:34:48 +00:00
|
|
|
const nodesData = Array.from(visibleNodes).map((id: any) => {
|
|
|
|
|
const node = this.nodes.find((node: any) => node.id === id);
|
|
|
|
|
return node ? node : null;
|
|
|
|
|
}).filter(Boolean);
|
2024-06-14 16:38:00 +00:00
|
|
|
|
|
|
|
|
// Ensure the central node is included in nodesData
|
|
|
|
|
if (!nodesData.some((node: any) => node.id === this.centralNote.key)) {
|
|
|
|
|
const centralNode = this.nodes.find((node: any) => node.id === this.centralNote.key);
|
|
|
|
|
if (centralNode) {
|
|
|
|
|
nodesData.push(centralNode);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check and initialize node positions
|
|
|
|
|
nodesData.forEach((node: any) => {
|
|
|
|
|
|
2024-06-17 23:20:03 +00:00
|
|
|
if (!node.x || !node.y) {
|
|
|
|
|
console.warn(`Node with invalid position: ${node.id}`);
|
|
|
|
|
node.x = Math.random() * 1000; // or some default value
|
|
|
|
|
node.y = Math.random() * 1000; // or some default value
|
|
|
|
|
}
|
2024-06-14 16:38:00 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
|
|
|
|
|
this.validatedLinks = filteredConnections.filter((link: any) => {
|
|
|
|
|
const sourceNode = nodesData.find((node: any) => node.id === link.source);
|
|
|
|
|
const targetNode = nodesData.find((node: any) => node.id === link.target);
|
|
|
|
|
if (!sourceNode || !targetNode) {
|
|
|
|
|
console.warn(`Link source or target node not found: ${link.source}, ${link.target}`);
|
2024-05-31 16:05:53 +00:00
|
|
|
}
|
2024-06-02 21:34:48 +00:00
|
|
|
return sourceNode && targetNode;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (nodesData.length === 0 || this.validatedLinks.length === 0) {
|
2024-06-02 22:40:43 +00:00
|
|
|
this.updatingVisualization = false;
|
2024-06-02 21:34:48 +00:00
|
|
|
console.warn('No nodes or links to display after filtering. Aborting update.');
|
2024-06-16 06:00:48 +00:00
|
|
|
new Notice('No nodes or links to display after filtering. Adjust filter settings');
|
|
|
|
|
|
2024-06-06 06:26:09 +00:00
|
|
|
// Clear the existing nodes and links from the visualization
|
2024-06-30 04:27:13 +00:00
|
|
|
this.nodeSelection = this.svgGroup.select('g.smart-connections-visualizer-nodes').selectAll('circle').data([]).exit().remove();
|
|
|
|
|
this.linkSelection = this.svgGroup.select('g.smart-connections-visualizer-links').selectAll('line').data([]).exit().remove();
|
|
|
|
|
this.linkLabelSelection = this.svgGroup.select('g.smart-connections-visualizer-link-labels').selectAll('text').data([]).exit().remove();
|
|
|
|
|
this.labelSelection = this.svgGroup.select('g.smart-connections-visualizer-node-labels').selectAll('text').data([]).exit().remove();
|
2024-06-02 21:34:48 +00:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.updateNodeAndLinkSelection(nodesData);
|
2024-06-14 16:38:00 +00:00
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
|
2024-06-06 06:26:09 +00:00
|
|
|
if (!this.simulation || this.currentNoteChanging || this.isFiltering) {
|
2024-06-02 21:34:48 +00:00
|
|
|
const { width, height } = this.getSVGDimensions();
|
|
|
|
|
this.initializeSimulation(width, height);
|
2024-06-06 06:26:09 +00:00
|
|
|
this.currentNoteChanging = false;
|
|
|
|
|
this.isFiltering = false;
|
2024-06-02 21:34:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.simulation.nodes(nodesData).on('tick', this.simulationTickHandler.bind(this));
|
|
|
|
|
this.simulation.force('link').links(this.validatedLinks)
|
|
|
|
|
.distance((d: any) => this.linkDistanceScale(d.score)); // Ensure the link distance is applied
|
2024-05-31 16:05:53 +00:00
|
|
|
|
2024-06-02 22:40:43 +00:00
|
|
|
this.simulation.alpha(1).restart();
|
2024-06-30 04:27:13 +00:00
|
|
|
|
|
|
|
|
// Stop the simulation after a short delay
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
this.simulation.alphaTarget(0);
|
|
|
|
|
}, 1000); // Adjust the delay as needed
|
2024-06-02 21:34:48 +00:00
|
|
|
|
|
|
|
|
this.updatingVisualization = false;
|
2024-06-18 16:47:26 +00:00
|
|
|
|
|
|
|
|
// TODO: Comment back when pushing legend
|
|
|
|
|
// First, clear the existing legend if it exists
|
|
|
|
|
// const existingLegend = this.contentEl.querySelector('.legend-container');
|
|
|
|
|
// if (existingLegend) {
|
|
|
|
|
// existingLegend.remove();
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// Now, re-render the legend with updated node data
|
|
|
|
|
// this.renderLegend();
|
2024-06-02 21:34:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
simulationTickHandler() {
|
2024-06-06 06:26:09 +00:00
|
|
|
this.nodeSelection.attr('cx', (d: any) => d.x).attr('cy', (d: any) => d.y).style('cursor', 'pointer');
|
|
|
|
|
this.linkSelection.attr('x1', (d: any) => d.source.x || 0).attr('y1', (d: any) => d.source.y || 0).style('cursor', 'pointer')
|
2024-06-02 21:34:48 +00:00
|
|
|
.attr('x2', (d: any) => d.target.x || 0).attr('y2', (d: any) => d.target.y || 0);
|
2024-06-09 13:05:16 +00:00
|
|
|
this.linkLabelSelection.attr('x', (d: any) => ((d.source.x + d.target.x) / 2))
|
|
|
|
|
.attr('y', (d: any) => ((d.source.y + d.target.y) / 2));
|
2024-06-02 21:34:48 +00:00
|
|
|
this.labelSelection
|
2024-06-06 06:26:09 +00:00
|
|
|
.attr('x', (d: any) => d.x)
|
|
|
|
|
.attr('y', (d: any) => d.y);
|
2024-06-02 21:34:48 +00:00
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-01-04 00:31:18 +00:00
|
|
|
async updateConnections() {
|
2024-06-02 21:34:48 +00:00
|
|
|
this.nodes = [];
|
|
|
|
|
this.links = [];
|
|
|
|
|
this.connections = [];
|
|
|
|
|
this.minScore = 1;
|
|
|
|
|
this.maxScore = 0;
|
|
|
|
|
if (!this.currentNoteKey) return;
|
|
|
|
|
this.centralNote = this.smartNotes[this.currentNoteKey];
|
2024-08-09 15:23:34 +00:00
|
|
|
console.log('central note: ', this.centralNote);
|
|
|
|
|
|
|
|
|
|
// console.log('central note connections: ', parse(stringify(this.centralNote.find_connections())));
|
|
|
|
|
|
2025-01-04 00:31:18 +00:00
|
|
|
// Await the asynchronous find_connections method
|
|
|
|
|
const connections = await this.centralNote.find_connections();
|
|
|
|
|
const noteConnections = connections.filter(
|
|
|
|
|
(connection: any) => connection.score >= this.relevanceScoreThreshold
|
|
|
|
|
);
|
2024-06-02 21:34:48 +00:00
|
|
|
this.addCentralNode();
|
|
|
|
|
this.addFilteredConnections(noteConnections);
|
|
|
|
|
const isValid = this.validateGraphData(this.nodes, this.links);
|
|
|
|
|
if (!isValid) console.error('Graph data validation failed.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
addCentralNode() {
|
|
|
|
|
|
|
|
|
|
if (this.centralNote.key && this.centralNote.key.trim() !== '' && !this.nodes.some((node: { id: any; }) => node.id === this.centralNote.key)) {
|
|
|
|
|
|
|
|
|
|
const svg = this.svg.node() as SVGSVGElement;
|
|
|
|
|
const { width, height } = svg.getBoundingClientRect();
|
|
|
|
|
|
|
|
|
|
this.nodes.push({
|
|
|
|
|
id: this.centralNote.key,
|
|
|
|
|
name: this.centralNote.key,
|
|
|
|
|
group: 'note',
|
|
|
|
|
x: width / 2,
|
|
|
|
|
y: height / 2,
|
|
|
|
|
fx: null,
|
|
|
|
|
fy: null,
|
2024-06-18 16:47:26 +00:00
|
|
|
fill: this.noteFillColor,
|
2024-06-02 21:34:48 +00:00
|
|
|
selected: false,
|
|
|
|
|
highlighted: false
|
|
|
|
|
});
|
|
|
|
|
this.centralNode = this.nodes[this.nodes.length - 1];
|
|
|
|
|
} else {
|
|
|
|
|
console.error(`Central node not found or already exists: ${this.centralNote.key}`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
addFilteredConnections(noteConnections: any) {
|
2024-06-16 06:00:48 +00:00
|
|
|
|
|
|
|
|
const filteredConnections = noteConnections.filter((connection: any) => {
|
|
|
|
|
if (this.connectionType === 'both') {
|
|
|
|
|
return true; // return all connections
|
|
|
|
|
} else {
|
2024-08-27 15:30:45 +00:00
|
|
|
// If connectionType is block, return true if connection is a SmartBlock, otherwise return false
|
2024-10-23 17:09:57 +00:00
|
|
|
return (this.connectionType === 'block') === (connection.item instanceof this.env.item_types.SmartBlock);
|
2024-08-27 15:30:45 +00:00
|
|
|
|
2024-06-16 06:00:48 +00:00
|
|
|
}
|
|
|
|
|
}); // console.log('Filtered connections:', filteredConnections);
|
2024-06-02 21:34:48 +00:00
|
|
|
filteredConnections.forEach((connection: any, index: any) => {
|
2024-06-14 16:39:32 +00:00
|
|
|
// console.log('Filtered connection:', connection, 'Index:', index);
|
2024-10-23 17:09:57 +00:00
|
|
|
if (connection && connection.item && connection.item.key) {
|
|
|
|
|
const connectionId = connection.item.key;
|
2024-06-14 16:39:32 +00:00
|
|
|
// console.log('Adding connection node for ID:', connectionId);
|
2024-06-14 16:38:00 +00:00
|
|
|
|
2024-09-02 13:05:08 +00:00
|
|
|
this.addConnectionNode(connectionId, connection);
|
2024-06-14 16:39:32 +00:00
|
|
|
// console.log('Adding connection link for ID:', connectionId);
|
2024-06-14 16:38:00 +00:00
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
this.addConnectionLink(connectionId, connection);
|
|
|
|
|
} else {
|
2024-08-30 16:39:41 +00:00
|
|
|
console.warn(`Skipping invalid connection at index ${index}:`, connection);
|
2024-05-31 16:05:53 +00:00
|
|
|
}
|
2024-06-02 21:34:48 +00:00
|
|
|
});
|
2024-06-14 16:39:32 +00:00
|
|
|
// console.log('Nodes after addFilteredConnections:', this.nodes);
|
|
|
|
|
// console.log('Links after addFilteredConnections:', this.links);
|
2024-06-02 21:34:48 +00:00
|
|
|
}
|
2024-05-31 16:05:53 +00:00
|
|
|
|
2024-09-02 13:05:08 +00:00
|
|
|
addConnectionNode(connectionId: any, connection: any) {
|
|
|
|
|
if (!this.nodes.some((node: { id: string; }) => node.id === connectionId)) {
|
2024-06-02 21:34:48 +00:00
|
|
|
this.nodes.push({
|
2024-09-02 13:05:08 +00:00
|
|
|
id: connectionId,
|
|
|
|
|
name: connectionId,
|
2024-10-23 17:09:57 +00:00
|
|
|
group: (connection.item instanceof this.env.item_types.SmartBlock) ? 'block' : 'note',
|
2024-06-17 23:20:03 +00:00
|
|
|
x: Math.random() * 1000,
|
2024-06-02 21:34:48 +00:00
|
|
|
y: Math.random() * 1000,
|
|
|
|
|
fx: null,
|
|
|
|
|
fy: null,
|
2024-10-23 17:09:57 +00:00
|
|
|
fill: (connection.item instanceof this.env.item_types.SmartBlock) ? this.blockFillColor : this.noteFillColor,
|
2024-06-02 21:34:48 +00:00
|
|
|
selected: false,
|
|
|
|
|
highlighted: false
|
|
|
|
|
});
|
2024-06-14 16:38:00 +00:00
|
|
|
} else {
|
2024-09-02 13:05:08 +00:00
|
|
|
console.log('Node already exists for connection ID:',connectionId);
|
2024-06-02 21:34:48 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
addConnectionLink(connectionId: string, connection: any) {
|
|
|
|
|
const sourceNode = this.nodes.find((node: { id: string; }) => node.id === this.centralNote.key);
|
|
|
|
|
const targetNode = this.nodes.find((node: { id: string; }) => node.id === connectionId);
|
|
|
|
|
|
|
|
|
|
if (!sourceNode) {
|
|
|
|
|
console.error(`Source node not found: ${this.centralNote.key}`);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!targetNode) {
|
|
|
|
|
console.error(`Target node not found: ${connectionId}`);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.links.push({
|
|
|
|
|
source: this.centralNote.key,
|
|
|
|
|
target: connectionId,
|
|
|
|
|
value: connection.score || 0
|
|
|
|
|
});
|
|
|
|
|
this.connections.push({
|
|
|
|
|
source: this.centralNote.key,
|
|
|
|
|
target: connectionId,
|
|
|
|
|
score: connection.score || 0
|
|
|
|
|
});
|
|
|
|
|
this.updateScoreRange(connection.score);
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-31 16:05:53 +00:00
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
updateScoreRange(score: number) {
|
|
|
|
|
if (score > this.maxScore) this.maxScore = score;
|
|
|
|
|
if (score < this.minScore) this.minScore = score;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
validateGraphData(nodes: any[], links: any[]): boolean {
|
|
|
|
|
const nodeIds = new Set(nodes.map(node => node.id));
|
|
|
|
|
let isValid = true;
|
|
|
|
|
links.forEach((link, index) => {
|
|
|
|
|
if (!nodeIds.has(link.source)) {
|
|
|
|
|
console.error(`Link at index ${index} has an invalid source: ${link.source}`);
|
|
|
|
|
isValid = false;
|
2024-05-31 16:05:53 +00:00
|
|
|
}
|
2024-06-02 21:34:48 +00:00
|
|
|
if (!nodeIds.has(link.target)) {
|
|
|
|
|
console.error(`Link at index ${index} has an invalid target: ${link.target}`);
|
|
|
|
|
isValid = false;
|
2024-05-31 16:05:53 +00:00
|
|
|
}
|
2024-06-02 21:34:48 +00:00
|
|
|
});
|
|
|
|
|
nodes.forEach((node, index) => {
|
|
|
|
|
if (!node.hasOwnProperty('id') || !node.hasOwnProperty('name') || !node.hasOwnProperty('group')) {
|
|
|
|
|
console.error(`Node at index ${index} is missing required properties: ${JSON.stringify(node)}`);
|
|
|
|
|
isValid = false;
|
2024-05-31 16:05:53 +00:00
|
|
|
}
|
2024-06-02 21:34:48 +00:00
|
|
|
});
|
|
|
|
|
return isValid;
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-31 16:05:53 +00:00
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
updateNodeAndLinkSelection(nodesData: any) {
|
|
|
|
|
const svgGroup = this.svgGroup;
|
|
|
|
|
|
|
|
|
|
// Update links first
|
2024-06-30 04:27:13 +00:00
|
|
|
this.linkSelection = svgGroup.select('g.smart-connections-visualizer-links').selectAll('line')
|
2024-06-02 21:34:48 +00:00
|
|
|
.data(this.validatedLinks, (d: any) => `${d.source}-${d.target}`)
|
|
|
|
|
.join(
|
|
|
|
|
enter => this.enterLink(enter),
|
|
|
|
|
update => this.updateLink(update),
|
|
|
|
|
exit => exit.remove()
|
|
|
|
|
);
|
|
|
|
|
|
2024-06-09 13:05:16 +00:00
|
|
|
|
2024-06-30 04:27:13 +00:00
|
|
|
this.linkLabelSelection = svgGroup.select('g.smart-connections-visualizer-link-labels').selectAll('text')
|
2024-06-09 13:05:16 +00:00
|
|
|
.data(this.validatedLinks, (d: any) => `${d.source.id}-${d.target.id}`)
|
|
|
|
|
.join(
|
|
|
|
|
enter => this.enterLinkLabel(enter),
|
|
|
|
|
update => this.updateLinkLabel(update),
|
|
|
|
|
exit => exit.remove()
|
|
|
|
|
);
|
2024-06-02 21:34:48 +00:00
|
|
|
|
2024-06-30 04:27:13 +00:00
|
|
|
this.labelSelection = svgGroup.select('g.smart-connections-visualizer-node-labels').selectAll('text')
|
2024-06-02 21:34:48 +00:00
|
|
|
.data(nodesData, (d: any) => d.id)
|
|
|
|
|
.join(
|
|
|
|
|
enter => this.enterLabel(enter),
|
|
|
|
|
update => this.updateLabel(update),
|
|
|
|
|
exit => exit.remove()
|
|
|
|
|
)
|
|
|
|
|
.attr('x', (d: any) => d.x)
|
|
|
|
|
.attr('y', (d: any) => d.y);
|
|
|
|
|
|
|
|
|
|
// Update nodes after links
|
2024-06-30 04:27:13 +00:00
|
|
|
this.nodeSelection = svgGroup.select('g.smart-connections-visualizer-nodes').selectAll('circle')
|
2024-06-14 16:38:00 +00:00
|
|
|
.data(nodesData, (d: any) => {
|
|
|
|
|
return d.id;
|
|
|
|
|
})
|
2024-06-02 21:34:48 +00:00
|
|
|
.join(
|
|
|
|
|
enter => this.enterNode(enter),
|
|
|
|
|
update => this.updateNode(update),
|
|
|
|
|
exit => exit.remove()
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-31 16:05:53 +00:00
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
enterNode(enter: any) {
|
|
|
|
|
const that = this; // Reference to 'this' context for inner functions
|
|
|
|
|
return enter.append('circle')
|
2024-06-30 04:27:13 +00:00
|
|
|
.attr('class', 'smart-connections-visualizer-node')
|
2024-06-02 21:34:48 +00:00
|
|
|
.attr('r', (d: any) => d.id === this.centralNode.id ? this.nodeSize + 2 : this.nodeSize)
|
2024-06-18 16:47:26 +00:00
|
|
|
.attr('fill', (d: any) => d.fill)
|
2024-06-02 21:34:48 +00:00
|
|
|
.attr('stroke', (d: any) => d.selected ? 'blanchedalmond' : 'transparent')
|
|
|
|
|
.attr('stroke-width', (d: any) => d.selected ? 1.5 : 0.3)
|
|
|
|
|
.attr('opacity', 1)
|
|
|
|
|
.attr('cursor', 'pointer')
|
|
|
|
|
.call(d3.drag().on('start', this.onDragStart.bind(this))
|
|
|
|
|
.on('drag', this.onDrag.bind(this))
|
|
|
|
|
.on('end', this.onDragEnd.bind(this)))
|
|
|
|
|
.on('click', this.onNodeClick.bind(this))
|
|
|
|
|
.on('mouseover', this.onNodeMouseOver.bind(this))
|
|
|
|
|
.on('mouseout', this.onNodeMouseOut.bind(this));
|
|
|
|
|
}
|
2024-05-31 05:43:16 +00:00
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
updateNode(update: any) {
|
|
|
|
|
return update.attr('r', (d: any) => d.id === this.centralNode.id ? this.nodeSize + 2 : this.nodeSize)
|
2024-06-18 16:47:26 +00:00
|
|
|
.attr('fill', (d: any) => d.selected ? '#f3ee5d' : d.fill)
|
2024-06-02 21:34:48 +00:00
|
|
|
.attr('stroke', (d: any) => d.selected ? 'blanchedalmond' : 'transparent')
|
|
|
|
|
.attr('stroke-width', (d: any) => d.selected ? 1.5 : 0.3);
|
|
|
|
|
}
|
|
|
|
|
onDragStart(event: any, d: any) {
|
|
|
|
|
if (!event.active) this.simulation.alphaTarget(0.3).restart();
|
|
|
|
|
this.dragging = true;
|
|
|
|
|
d.fx = d.x;
|
|
|
|
|
d.fy = d.y;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onDrag(event: any, d: any) {
|
2024-06-06 06:26:09 +00:00
|
|
|
|
|
|
|
|
// Ensure hovering date isnt active when dragging.
|
|
|
|
|
if(this.isHovering) this.isHovering = false;
|
|
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
d.fx = event.x;
|
|
|
|
|
d.fy = event.y;
|
|
|
|
|
|
2024-06-06 06:26:09 +00:00
|
|
|
// if (d.id === this.centralNode.id) {
|
|
|
|
|
// // Update the position of the node's label immediately during dragging
|
|
|
|
|
// this.labelSelection
|
|
|
|
|
// .filter((node: any) => node.id === d.id)
|
|
|
|
|
// .attr('x', d.x)
|
|
|
|
|
// .attr('y', () => {
|
|
|
|
|
// if (d.highlighted) {
|
|
|
|
|
// return d.y + 8; // Keep label 8px down if node is highlighted
|
|
|
|
|
// }
|
|
|
|
|
// return d.y;
|
|
|
|
|
// });
|
|
|
|
|
|
|
|
|
|
// }
|
|
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
onDragEnd(event: any, d: any) {
|
|
|
|
|
if (!event.active) this.simulation.alphaTarget(0);
|
|
|
|
|
d.fx = null;
|
|
|
|
|
d.fy = null;
|
|
|
|
|
this.dragging = false
|
2024-06-06 06:26:09 +00:00
|
|
|
|
|
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onNodeClick(event: any, d: any) {
|
2024-06-06 06:26:09 +00:00
|
|
|
|
|
|
|
|
// Don't need to touch central since we're in it
|
|
|
|
|
if(d.id === this.centralNode.id) return;
|
|
|
|
|
|
|
|
|
|
this.env.plugin.open_note(d.id, event)
|
|
|
|
|
|
|
|
|
|
// event.stopPropagation();
|
2024-06-02 21:34:48 +00:00
|
|
|
// TODO:: Bring back when ready for selection
|
2024-06-06 06:26:09 +00:00
|
|
|
|
|
|
|
|
// if (!this.isAltPressed) this.clearSelections();
|
2024-06-02 21:34:48 +00:00
|
|
|
// d.selected = !d.selected;
|
|
|
|
|
// if (!d.selected) {
|
|
|
|
|
// d.highlighted = false;
|
|
|
|
|
// }
|
2024-06-06 06:26:09 +00:00
|
|
|
// this.updateNodeAppearance();
|
2024-06-02 21:34:48 +00:00
|
|
|
}
|
2024-05-31 16:05:53 +00:00
|
|
|
|
2024-06-06 06:26:09 +00:00
|
|
|
onNodeMouseOver(event: any, d: any) {
|
|
|
|
|
|
|
|
|
|
// Dont trigger possible highlights if user dragging around nodes quickly for fun
|
|
|
|
|
if(this.dragging) return;
|
|
|
|
|
|
|
|
|
|
// Don't apply hover affect to center node
|
|
|
|
|
if(d.id === this.centralNode.id) return;
|
|
|
|
|
|
|
|
|
|
// Hovering state active
|
2024-06-02 21:34:48 +00:00
|
|
|
this.isHovering = true;
|
2024-06-06 06:26:09 +00:00
|
|
|
|
|
|
|
|
// Highlight node
|
|
|
|
|
this.highlightNode(d);
|
|
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
// Show link labels associated with the node
|
|
|
|
|
this.updateLinkLabelAppearance(d);
|
2024-06-06 06:26:09 +00:00
|
|
|
|
|
|
|
|
// TODO:: Comment back when ready to implement Label Movement animation on hover
|
2024-06-02 21:34:48 +00:00
|
|
|
// console.log(`Hovering over node: ${d.id}, original y: ${d.y}`);
|
|
|
|
|
// this.svgGroup.select(`text[data-id='${d.id}']`).transition().duration(4000).attr('y', d.y + 8); // Animate label down 10 pixels
|
|
|
|
|
|
|
|
|
|
this.app.workspace.trigger("hover-link", {
|
|
|
|
|
event,
|
|
|
|
|
source: 'D3',
|
|
|
|
|
hoverParent: event.currentTarget.parentElement,
|
|
|
|
|
targetEl: event.currentTarget,
|
|
|
|
|
linktext: d.id,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onNodeMouseOut(event: any, d: any) {
|
|
|
|
|
if (this.dragging) return;
|
|
|
|
|
|
|
|
|
|
this.isHovering = false;
|
|
|
|
|
this.centerHighlighted = false;
|
2024-06-06 06:26:09 +00:00
|
|
|
this.unhighlightNode(d);
|
|
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
// Hide link labels associated with the node
|
|
|
|
|
this.updateLinkLabelAppearance({ id: null });
|
|
|
|
|
|
|
|
|
|
// TODO:: Comment back when ready to implement Label MOvement animation on hover
|
|
|
|
|
// console.log(`Mouse out from node: ${d.id}, returning label to y: ${d.y}`);
|
|
|
|
|
// this.svgGroup.select(`text[data-id='${d.id}']`).transition().duration(400).attr('y', d.y); // Animate label back to original position
|
|
|
|
|
}
|
2024-06-09 13:05:16 +00:00
|
|
|
|
|
|
|
|
updateLinkLabelPositions() {
|
|
|
|
|
this.linkLabelSelection
|
|
|
|
|
.attr('x', (d: any) => (d.source.x + d.target.x) / 2)
|
|
|
|
|
.attr('y', (d: any) => (d.source.y + d.target.y) / 2);
|
|
|
|
|
}
|
2024-06-02 21:34:48 +00:00
|
|
|
updateLinkSelection(svgGroup: any) {
|
|
|
|
|
return svgGroup.select('g.links').selectAll('line')
|
|
|
|
|
.data(this.validatedLinks, (d: any) => `${d.source}-${d.target}`)
|
2024-06-06 06:26:09 +00:00
|
|
|
.style('cursor', 'pointer')
|
2024-06-02 21:34:48 +00:00
|
|
|
.join(
|
|
|
|
|
(enter: any) => this.enterLink(enter),
|
|
|
|
|
(update: any) => this.updateLink(update),
|
|
|
|
|
(exit: { remove: () => any; }) => exit.remove()
|
|
|
|
|
);
|
|
|
|
|
}
|
2024-05-31 16:05:53 +00:00
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
enterLink(enter: any) {
|
|
|
|
|
return enter.append('line')
|
2024-06-30 04:27:13 +00:00
|
|
|
.attr('class', 'smart-connections-visualizer-link')
|
2024-06-02 21:34:48 +00:00
|
|
|
.attr('stroke', '#4c7787')
|
|
|
|
|
.attr('stroke-width', (d: any) => this.getLinkStrokeWidth(d))
|
|
|
|
|
.attr('stroke-opacity', 1)
|
|
|
|
|
.attr('opacity', 1);
|
|
|
|
|
}
|
2024-05-31 16:05:53 +00:00
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
updateLink(update: any) {
|
|
|
|
|
return update.attr('stroke', '#4c7787')
|
|
|
|
|
.attr('stroke-width', (d: any) => this.getLinkStrokeWidth(d));
|
|
|
|
|
}
|
2024-05-31 16:05:53 +00:00
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
getLinkStrokeWidth(d: any) {
|
|
|
|
|
return d3.scaleLinear()
|
|
|
|
|
.domain([this.minScore, this.maxScore])
|
|
|
|
|
.range([this.minLinkThickness, this.maxLinkThickness])(d.score);
|
|
|
|
|
}
|
2024-05-31 16:05:53 +00:00
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
updateLinkLabelSelection(svgGroup: any) {
|
|
|
|
|
return svgGroup.append('g')
|
2024-06-30 04:27:13 +00:00
|
|
|
.attr('class', 'smart-connections-visualizer-link-labels')
|
2024-06-02 21:34:48 +00:00
|
|
|
.selectAll('text')
|
|
|
|
|
.data(this.validatedLinks, (d: any) => `${d.source.id}-${d.target.id}`)
|
|
|
|
|
.join(
|
|
|
|
|
(enter: any) => this.enterLinkLabel(enter),
|
|
|
|
|
(update: any) => this.updateLinkLabel(update),
|
|
|
|
|
(exit: { remove: () => any; }) => exit.remove()
|
|
|
|
|
);
|
|
|
|
|
}
|
2024-05-31 16:05:53 +00:00
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
enterLinkLabel(enter: any) {
|
|
|
|
|
return enter.append('text')
|
2024-06-30 04:27:13 +00:00
|
|
|
.attr('class', 'smart-connections-visualizer-link-label')
|
2024-06-02 21:34:48 +00:00
|
|
|
.attr('font-size', this.linkLabelSize)
|
|
|
|
|
.attr('fill', '#bbb')
|
|
|
|
|
.attr('opacity', 0)
|
2024-06-09 13:05:16 +00:00
|
|
|
.attr('x', (d: any) => d.x) // Initialize x position
|
|
|
|
|
.attr('y', (d: any) => d.y) // Initialize y position
|
|
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
.text((d: any) => (d.score * 100).toFixed(1) + '%');
|
|
|
|
|
}
|
2024-05-31 16:05:53 +00:00
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
updateLinkLabel(update: any) {
|
2024-06-09 13:05:16 +00:00
|
|
|
|
|
|
|
|
return update.text((d: any) => (d.score * 100).toFixed(1))
|
|
|
|
|
.attr('x', (d: any) => d.x) // Initialize x position
|
|
|
|
|
.attr('y', (d: any) => d.y) // Initialize y position
|
|
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
}
|
2024-05-31 16:05:53 +00:00
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
enterLabel(enter: any) {
|
|
|
|
|
return enter.append('text')
|
2024-06-30 04:27:13 +00:00
|
|
|
.attr('class', 'smart-connections-visualizer-label')
|
2024-06-02 21:34:48 +00:00
|
|
|
.attr('dx', 0)
|
|
|
|
|
.attr('font-size', this.nodeLabelSize)
|
|
|
|
|
.attr('dy', 12)
|
|
|
|
|
.attr('text-anchor', 'middle')
|
|
|
|
|
.attr('fill', '#bbb')
|
|
|
|
|
.attr('data-id', (d: any) => d.id)
|
|
|
|
|
.attr('opacity', 1)
|
|
|
|
|
.attr('x', (d: any) => d.x) // Initialize x position
|
|
|
|
|
.attr('y', (d: any) => d.y) // Initialize y position
|
|
|
|
|
.text((d: any) => this.formatLabel(d.name));
|
|
|
|
|
}
|
2024-05-29 23:57:01 +00:00
|
|
|
|
|
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
updateLabel(update: any) {
|
|
|
|
|
return update.attr('dx', 0)
|
|
|
|
|
.attr('data-id', (d: any) => d.id)
|
|
|
|
|
.attr('text-anchor', 'middle')
|
2024-06-06 06:26:09 +00:00
|
|
|
.text((d: any) => d.id === this.highlightedNodeId ? this.formatLabel(d.name, false) : this.formatLabel(d.name, true))
|
2024-06-02 21:34:48 +00:00
|
|
|
.attr('fill', '#bbb')
|
|
|
|
|
.attr('font-size', this.nodeLabelSize)
|
|
|
|
|
.attr('x', (d: any) => d.x) // Update x position
|
2024-06-06 06:26:09 +00:00
|
|
|
.attr('y', (d: any) => d.y) // Update y position with offset for highlight
|
2024-06-02 21:34:48 +00:00
|
|
|
.attr('opacity', 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
updateNodeSizes() {
|
|
|
|
|
this.nodeSelection.attr('r', (d: any) => d.id === this.centralNode.id ? this.nodeSize + 3 : this.nodeSize);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updateLinkThickness() {
|
|
|
|
|
const linkStrokeScale = d3.scaleLinear()
|
|
|
|
|
.domain([this.minScore, this.maxScore])
|
|
|
|
|
.range([this.minLinkThickness, this.maxLinkThickness]);
|
|
|
|
|
this.linkSelection.attr('stroke-width', (d: any) => linkStrokeScale(d.score));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updateSimulationForces() {
|
|
|
|
|
if (!this.simulation) {
|
|
|
|
|
console.error('Simulation not initialized');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
this.simulation
|
|
|
|
|
// .force('center', d3.forceCenter(width / 2, height / 2).strength(this.centerForce))
|
|
|
|
|
.force('charge', d3.forceManyBody().strength(-this.repelForce))
|
|
|
|
|
.force('link', d3.forceLink(this.validatedLinks)
|
|
|
|
|
.id((d: any) => d.id)
|
|
|
|
|
.distance((d: any) => this.linkDistanceScale(d.score))
|
|
|
|
|
.strength(this.linkForce))
|
2024-06-06 06:26:09 +00:00
|
|
|
// .force('collide', d3.forceCollide().radius(this.nodeSize + 3).strength(0.7));
|
2024-06-02 21:34:48 +00:00
|
|
|
|
2024-06-30 04:27:13 +00:00
|
|
|
this.simulation.alphaTarget(0.3).restart();
|
|
|
|
|
|
|
|
|
|
// Stop the simulation after a short delay
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
this.simulation.alphaTarget(0);
|
|
|
|
|
}, 1000); // Adjust the delay as needed
|
2024-06-02 21:34:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
normalizeScore(score: number) : number{
|
2024-06-17 23:20:03 +00:00
|
|
|
// When only one link, can't retun 0
|
|
|
|
|
if (this.minScore === this.maxScore) {
|
|
|
|
|
return 0.5; // or any other value in the range [0, 1]
|
|
|
|
|
}
|
2024-06-02 21:34:48 +00:00
|
|
|
return (score - this.minScore) / (this.maxScore - this.minScore);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
linkDistanceScale(score: number) {
|
|
|
|
|
return d3.scaleLinear()
|
|
|
|
|
.domain([0, 1])
|
|
|
|
|
.range([this.linkDistance * 2, this.linkDistance / 2])(this.normalizeScore(score));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
updateLabelOpacity(zoomLevel: number) {
|
|
|
|
|
const maxOpacity = 1;
|
|
|
|
|
const minOpacity = 0;
|
|
|
|
|
const minZoom = 0.1;
|
|
|
|
|
const maxZoom = this.textFadeThreshold; // Use the threshold value from the slider
|
|
|
|
|
|
|
|
|
|
let newOpacity = (zoomLevel - minZoom) / (maxZoom - minZoom);
|
|
|
|
|
if (zoomLevel <= minZoom) newOpacity = minOpacity;
|
|
|
|
|
if (zoomLevel >= maxZoom) newOpacity = maxOpacity;
|
|
|
|
|
|
|
|
|
|
newOpacity = Math.max(minOpacity, Math.min(maxOpacity, newOpacity));
|
2024-05-29 23:57:01 +00:00
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
// Update node labels opacity based on zoom level
|
2024-08-26 23:48:10 +00:00
|
|
|
if(this.labelSelection) {
|
|
|
|
|
this.labelSelection.transition().duration(300).attr('opacity', newOpacity);
|
|
|
|
|
}
|
2024-06-02 21:34:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
updateNodeLabels() {
|
|
|
|
|
this.labelSelection.attr('font-size', this.nodeLabelSize)
|
|
|
|
|
.text((d: any) => this.formatLabel(d.name, true));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updateLinkLabelSizes() {
|
|
|
|
|
|
|
|
|
|
if (this.linkLabelSelection) {
|
|
|
|
|
this.linkLabelSelection.attr('font-size', this.linkLabelSize);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updateNodeLabelSizes() {
|
|
|
|
|
this.labelSelection.attr('font-size', this.nodeLabelSize);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updateNodeLabelOpacity(zoomLevel: number) {
|
|
|
|
|
const maxOpacity = 1;
|
|
|
|
|
const minOpacity = 0;
|
|
|
|
|
const minZoom = 0.1;
|
|
|
|
|
const maxZoom = this.textFadeThreshold; // Use the threshold value from the slider
|
2024-05-29 23:57:01 +00:00
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
let newOpacity = (zoomLevel - minZoom) / (maxZoom - minZoom);
|
|
|
|
|
if (zoomLevel <= minZoom) newOpacity = minOpacity;
|
|
|
|
|
if (zoomLevel >= maxZoom) newOpacity = maxOpacity;
|
|
|
|
|
|
|
|
|
|
newOpacity = Math.max(minOpacity, Math.min(maxOpacity, newOpacity));
|
|
|
|
|
|
|
|
|
|
this.labelSelection.transition().duration(300).attr('opacity', newOpacity);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
startBoxSelection(event: any) {
|
|
|
|
|
if (!this.isCtrlPressed) return;
|
|
|
|
|
this.isDragging = true;
|
|
|
|
|
const [x, y] = d3.pointer(event);
|
|
|
|
|
this.selectionBox = d3.select('svg').append('rect')
|
2024-06-30 04:27:13 +00:00
|
|
|
.attr('class', 'smart-connections-visualizer-selection-box')
|
2024-06-02 21:34:48 +00:00
|
|
|
.attr('x', x)
|
|
|
|
|
.attr('y', y)
|
|
|
|
|
.attr('width', 0)
|
|
|
|
|
.attr('height', 0)
|
|
|
|
|
.attr('stroke', '#00f')
|
|
|
|
|
.attr('stroke-width', 1)
|
|
|
|
|
.attr('fill', 'rgba(0, 0, 255, 0.3)');
|
|
|
|
|
this.startX = x;
|
|
|
|
|
this.startY = y;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updateBoxSelection(event: any) {
|
|
|
|
|
if (!this.isDragging) return;
|
|
|
|
|
const [x, y] = d3.pointer(event);
|
|
|
|
|
const newWidth = x - this.startX;
|
|
|
|
|
const newHeight = y - this.startY;
|
|
|
|
|
this.selectionBox
|
|
|
|
|
.attr('width', Math.abs(newWidth))
|
|
|
|
|
.attr('height', Math.abs(newHeight))
|
|
|
|
|
.attr('x', Math.min(x, this.startX))
|
|
|
|
|
.attr('y', Math.min(y, this.startY));
|
|
|
|
|
this.updateNodeSelectionInBox(newWidth, newHeight);
|
|
|
|
|
this.updateNodeAppearance();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updateNodeSelectionInBox(newWidth: number, newHeight: number) {
|
|
|
|
|
const endX = this.startX + newWidth;
|
|
|
|
|
const endY = this.startY + newHeight;
|
|
|
|
|
const transformedStartX = Math.min(this.startX, endX);
|
|
|
|
|
const transformedStartY = Math.min(this.startY, endY);
|
|
|
|
|
const transformedEndX = Math.max(this.startX, endX);
|
|
|
|
|
const transformedEndY = Math.max(this.startY, endY);
|
|
|
|
|
const transform = d3.zoomTransform(d3.select('svg').node() as Element);
|
|
|
|
|
const zoomedStartX = (transformedStartX - transform.x) / transform.k;
|
|
|
|
|
const zoomedStartY = (transformedStartY - transform.y) / transform.k;
|
|
|
|
|
const zoomedEndX = (transformedEndX - transform.x) / transform.k;
|
|
|
|
|
const zoomedEndY = (transformedEndY - transform.y) / transform.k;
|
|
|
|
|
this.nodeSelection.each((d: any) => {
|
|
|
|
|
const nodeX = d.x;
|
|
|
|
|
const nodeY = d.y;
|
|
|
|
|
d.selected = nodeX >= zoomedStartX && nodeX <= zoomedEndX && nodeY >= zoomedStartY && nodeY <= zoomedEndY;
|
|
|
|
|
});
|
|
|
|
|
}
|
2024-05-29 15:58:31 +00:00
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
endBoxSelection() {
|
|
|
|
|
if (!this.isDragging) return;
|
|
|
|
|
this.isDragging = false;
|
|
|
|
|
this.selectionBox.remove();
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-06 06:26:09 +00:00
|
|
|
|
2024-06-02 21:34:48 +00:00
|
|
|
|
2024-06-06 06:26:09 +00:00
|
|
|
// TODO:: Add back in when ready for toolti
|
|
|
|
|
// showTooltip(event: any, d: any) {
|
|
|
|
|
// const tooltip = d3.select('.tooltip');
|
|
|
|
|
// tooltip.text(d.name)
|
|
|
|
|
// .style('visibility', 'visible');
|
|
|
|
|
// const [x, y] = d3.pointer(event);
|
|
|
|
|
// tooltip.style('top', `${y + 10}px`)
|
|
|
|
|
// .style('left', `${x + 10}px`);
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// hideTooltip() {
|
|
|
|
|
// const tooltip = d3.select('.tooltip');
|
|
|
|
|
// tooltip.style('visibility', 'hidden');
|
|
|
|
|
// }
|
2024-05-29 15:58:31 +00:00
|
|
|
|
|
|
|
|
}
|
2024-06-02 21:34:48 +00:00
|
|
|
|
2024-05-29 15:58:31 +00:00
|
|
|
|
2024-05-30 14:29:30 +00:00
|
|
|
export default class ScGraphView extends Plugin {
|
2024-05-29 15:58:31 +00:00
|
|
|
|
2024-06-16 06:00:48 +00:00
|
|
|
settings: PluginSettings;
|
|
|
|
|
|
2024-05-29 15:58:31 +00:00
|
|
|
async onload() {
|
2024-06-12 12:45:11 +00:00
|
|
|
|
2024-06-16 06:00:48 +00:00
|
|
|
await this.loadSettings();
|
|
|
|
|
|
2024-06-12 12:45:11 +00:00
|
|
|
// Register the new view
|
2024-06-16 06:00:48 +00:00
|
|
|
this.registerView("smart-connections-visualizer", (leaf: WorkspaceLeaf) => new ScGraphItemView(leaf, this));
|
2024-05-29 15:58:31 +00:00
|
|
|
|
2024-09-11 14:17:20 +00:00
|
|
|
// Register hover link source
|
|
|
|
|
this.registerHoverLinkSource('smart-connections-visualizer', {
|
|
|
|
|
display: 'Smart connections visualizer hover link source',
|
|
|
|
|
defaultMod: true
|
|
|
|
|
});
|
2024-05-29 15:58:31 +00:00
|
|
|
|
|
|
|
|
// This creates an icon in the left ribbon.
|
2024-06-06 19:05:10 +00:00
|
|
|
this.addRibbonIcon('git-fork', 'Open smart connections visualizer', (evt: MouseEvent) => {
|
2024-09-11 14:17:20 +00:00
|
|
|
// Check if the view is already open
|
|
|
|
|
const existingLeaf = this.app.workspace.getLeavesOfType("smart-connections-visualizer")[0];
|
|
|
|
|
if (existingLeaf) {
|
|
|
|
|
// If it exists, focus on it
|
|
|
|
|
this.app.workspace.setActiveLeaf(existingLeaf);
|
|
|
|
|
} else {
|
|
|
|
|
// Create a new leaf in the current workspace
|
2025-02-03 15:57:10 +00:00
|
|
|
let leaf = this.app.workspace.getRightLeaf(false);
|
2024-09-11 14:17:20 +00:00
|
|
|
// Set the new leaf's view to your custom view
|
2025-02-03 16:12:06 +00:00
|
|
|
// @ts-ignore
|
2024-09-11 14:17:20 +00:00
|
|
|
leaf.setViewState({
|
|
|
|
|
type: "smart-connections-visualizer",
|
|
|
|
|
active: true,
|
|
|
|
|
});
|
|
|
|
|
}
|
2024-05-29 15:58:31 +00:00
|
|
|
})
|
2024-06-16 06:00:48 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async loadSettings() {
|
|
|
|
|
this.settings = Object.assign({}, DEFAULT_NETWORK_SETTINGS, await this.loadData());
|
|
|
|
|
}
|
2024-05-29 15:58:31 +00:00
|
|
|
|
2024-06-16 06:00:48 +00:00
|
|
|
async saveSettings() {
|
|
|
|
|
await this.saveData(this.settings);
|
2024-05-29 15:58:31 +00:00
|
|
|
}
|
|
|
|
|
|
2024-06-30 04:27:13 +00:00
|
|
|
onunload() {
|
|
|
|
|
|
|
|
|
|
}
|
2024-05-29 15:58:31 +00:00
|
|
|
|
|
|
|
|
}
|