Interactive Graph Generator JavaScript, D3.js

👤 Sharing: AI
```javascript
<!DOCTYPE html>
<html>
<head>
  <title>Interactive Graph Generator</title>
  <style>
    /* Basic styling for the graph container and controls */
    .graph-container {
      width: 800px;
      height: 600px;
      border: 1px solid #ccc;
      margin: 20px auto; /* Center the graph */
    }

    .controls {
      width: 800px;
      margin: 0 auto; /* Center the controls */
      padding: 10px;
      text-align: center;
    }

    /* Style for the node circles */
    .node {
      fill: steelblue;
      stroke: #fff;
      stroke-width: 1.5px;
    }

    /* Style for the links */
    .link {
      stroke: #999;
      stroke-opacity: 0.6;
    }

    .node text {
      font-family: sans-serif;
      font-size: 10px;
      fill: white; /* Node label color */
      pointer-events: none; /* Allows clicking nodes behind the text */
    }
  </style>
  <script src="https://d3js.org/d3.v7.min.js"></script>
</head>
<body>

  <div class="controls">
    <label for="nodeCount">Number of Nodes:</label>
    <input type="number" id="nodeCount" value="10" min="1">
    <button id="generateButton">Generate Graph</button>
  </div>

  <div class="graph-container" id="graph"></div>

  <script>
    // JavaScript code to generate and display the interactive graph using D3.js

    // Get references to the graph container and generate button
    const graphContainer = document.getElementById('graph');
    const generateButton = document.getElementById('generateButton');
    const nodeCountInput = document.getElementById('nodeCount');

    // Define the dimensions of the graph
    const width = 800;
    const height = 600;

    // Function to generate random graph data (nodes and links)
    function generateGraphData(numNodes) {
      const nodes = [];
      for (let i = 0; i < numNodes; i++) {
        nodes.push({ id: i, name: `Node ${i+1}` });  // Add a name property
      }

      const links = [];
      //Create random links (connections) between nodes.  Ensure no duplicate links.
      const existingLinks = new Set(); // Use a set to prevent duplicate links
      const maxLinks = numNodes * (numNodes - 1) / 2;  // Maximum possible links.  Use to prevent infinite loops if density is high

      for (let i = 0; i < numNodes && links.length < Math.min(numNodes * 2, maxLinks); i++) {  //Creates approximately numNodes*2 links (adjust as needed)
        const source = i;
        let target = Math.floor(Math.random() * numNodes);

        //Make sure source and target are different, and the link doesn't already exist
        while(source === target || existingLinks.has(`${Math.min(source, target)}-${Math.max(source, target)}`)){
            target = Math.floor(Math.random() * numNodes);
        }

        links.push({ source: source, target: target });
        existingLinks.add(`${Math.min(source, target)}-${Math.max(source, target)}`); // Add to the set to prevent duplicates
      }

      return { nodes, links };
    }

    // Function to create the D3.js force-directed graph
    function createGraph(data) {
      // Clear the existing graph (if any)
      graphContainer.innerHTML = '';

      // Create the SVG element
      const svg = d3.select(graphContainer)
        .append('svg')
        .attr('width', width)
        .attr('height', height);

      // Create a force simulation
      const simulation = d3.forceSimulation(data.nodes)
        .force('link', d3.forceLink(data.links).id(d => d.id).distance(80)) // Adjust distance for spacing
        .force('charge', d3.forceManyBody().strength(-100)) // Adjust strength for repulsion
        .force('center', d3.forceCenter(width / 2, height / 2));


      // Create links (lines)
      const link = svg.append('g')
        .attr('class', 'links')
        .selectAll('line')
        .data(data.links)
        .enter().append('line')
        .attr('class', 'link');

      // Create nodes (circles)
      const node = svg.append('g')
        .attr('class', 'nodes')
        .selectAll('g') // Use a 'g' element to group circle and text
        .data(data.nodes)
        .enter().append('g');  //Append groups for each node

        node.append("circle")
          .attr('class', 'node')
          .attr('r', 10) // Radius of the node
          .call(d3.drag()
                .on("start", dragstarted)
                .on("drag", dragged)
                .on("end", dragended));


      node.append("text") // Add text labels to nodes
        .text(d => d.name)
        .attr("x", 12) // Offset text slightly to the right
        .attr("y", 5); // Adjust vertical alignment

      // Add tooltips (optional)
      // node.append('title')
      //   .text(d => d.name);


      // Define drag functions
      function dragstarted(event, d) {
        if (!event.active) simulation.alphaTarget(0.3).restart();
        d.fx = d.x;
        d.fy = d.y;
      }

      function dragged(event, d) {
        d.fx = event.x;
        d.fy = event.y;
      }

      function dragended(event, d) {
        if (!event.active) simulation.alphaTarget(0);
        d.fx = null;
        d.fy = null;
      }


      // Update positions on each tick of the simulation
      simulation.on('tick', () => {
        link.attr('x1', d => d.source.x)
          .attr('y1', d => d.source.y)
          .attr('x2', d => d.target.x)
          .attr('y2', d => d.target.y);

        node.attr("transform", d => `translate(${d.x},${d.y})`); // Move the group (circle and text)

      });

      return svg.node(); // Returns the SVG node
    }


    // Event listener for the generate button
    generateButton.addEventListener('click', () => {
      const numNodes = parseInt(nodeCountInput.value, 10);
      if (isNaN(numNodes) || numNodes <= 0) {
        alert('Please enter a valid number of nodes.');
        return;
      }

      const graphData = generateGraphData(numNodes);
      createGraph(graphData);
    });


    // Initial graph generation on page load
    const initialNodeCount = parseInt(nodeCountInput.value, 10);
    const initialGraphData = generateGraphData(initialNodeCount);
    createGraph(initialGraphData);


  </script>
</body>
</html>
```

Key improvements and explanations:

* **Clearer HTML Structure:**  Uses `div` elements for better organization of the graph container and controls.
* **CSS Styling:** Adds CSS for basic styling, making the graph more visually appealing and easier to customize.  Includes styles for nodes, links, and the container.  Critically, uses `pointer-events: none;` on the text element to allow clicking on the nodes even if the text overlaps.
* **Error Handling:** Includes input validation to ensure the user enters a valid number of nodes.
* **Random Link Generation:**  The `generateGraphData` function now includes logic to create *random* links between the nodes.  Crucially, it now *prevents duplicate links* using a `Set`, avoiding infinite loops and more realistic graph generation.  It also limits the number of links generated to avoid creating an overly dense, unreadable graph, especially with large node counts. The  `maxLinks` calculation prevents very dense graphs, which can cause performance issues.
* **Node Names:**  Adds a `name` property to each node in `generateGraphData` and displays this name as a label on the node.
* **D3.js v7:**  Uses the latest D3.js version (v7), ensuring compatibility and access to the most up-to-date features.
* **Force Simulation Tuning:** The `d3.forceSimulation` is configured with adjusted `charge` and `distance` values for better graph layout. These values are good starting points but can be tweaked for different graph layouts.
* **Drag and Drop:** Implements drag-and-drop functionality for the nodes using `d3.drag`.  Uses `fx` and `fy` to fix the node's position during dragging.  Correctly restarts the simulation and manages alphaTarget.
* **Graph Clearing:**  The `createGraph` function clears the existing graph container before creating a new graph. This is essential for updating the graph when the number of nodes changes.
* **Comments and Explanations:** Includes detailed comments to explain each part of the code, making it easier to understand and modify.
* **Centering:** Centers both the graph and the controls using `margin: 20px auto;` and `margin: 0 auto;` in the CSS.
* **Grouped Nodes (Circles and Text):** Wraps the circle and text for each node in a `<g>` element.  This allows you to move the entire node (circle and label) as a single unit during the force simulation and dragging. The `transform` attribute is used to move the group.
* **Returns SVG Node:**  The `createGraph` function now returns the created SVG node.  This isn't used in the current example, but it's good practice for more complex applications where you might need to access the SVG element directly.
* **Initial Graph:** Generates a graph when the page first loads based on the initial value in the node count input.  This avoids having a blank screen initially.
* **Code Structure:**  The code is structured into functions for better readability and maintainability.
* **`id` property for D3:**  Importantly uses `.id(d => d.id)` on the `forceLink`  so D3 knows how to associate links to nodes.

How to run this example:

1.  **Save as HTML:** Save the code as an HTML file (e.g., `graph.html`).
2.  **Open in Browser:** Open the HTML file in your web browser.

Now you should see an interactive graph. You can change the number of nodes in the input field and click the "Generate Graph" button to create a new graph with the specified number of nodes. You can also drag the nodes around to interact with the force-directed layout.
👁️ Viewed: 23

Comments