javascriptcssd3.jssemantic-ui-css

How to apply a CSS style sheet to a D3 force-directed graph?


EDIT: It seems it is not easily possible to apply SemanticUI styles to SVG elements. The accepted answer provides corrections of the original code and some ideas for a workaround.

I'm quite new to web development. I'm trying to create a graph using d3.js, and I want the nodes to be styled according to the "ui button tiny blue" classes of the SemanticUI stylesheet. However, when I add this class to the nodes in the svg, they are not displayed all. Inspecting them in the browser tells me that the style has been applied properly, but the content box for some reason has negative height and width. The "TEST_NODE" outside the svg gets displayed as intended. What am I doing wrong?

The html document with inline js-script:

<!DOCTYPE html>
<meta charset="utf-8">
<link href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.4.1/semantic.min.css" rel="stylesheet" />
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<script src="https://d3js.org/d3.v2.min.js?2.9.3"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.4.1/semantic.min.js"></script>
<style>

    .link {
        stroke: #aaa;
    }

</style>
<body>

<div class="content">
    <div class="ui button tiny blue">TEST_NODE</div>
</div>


<script>
    $(document).ready(function (){
        var width = 960,
            height = 500

        var svg = d3.select("body").append("svg")
            .attr("width", width)
            .attr("height", height);

        var force = d3.layout.force()
            .gravity(.05)
            .distance(100)
            .charge(-100)
            .size([width, height]);

        d3.json("d3_test.json", function(json) {
            force
                .nodes(json.nodes)
                .links(json.links)
                .start();

            var link = svg.selectAll(".link")
                .data(json.links)
                .enter().append("line")
                .attr("class", "link");

            var node = svg.selectAll(".node")
                .data(json.nodes)
                .enter()
                .append("g")
                .call(force.drag);

            node.append("div")
                .attr("class", "ui button tiny blue")
                .text("NODE")


            force.on("tick", function() {
                link.attr("x1", function(d) { return d.source.x; })
                    .attr("y1", function(d) { return d.source.y; })
                    .attr("x2", function(d) { return d.target.x; })
                    .attr("y2", function(d) { return d.target.y; });

                node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
            });
        });
    });

</script>

</body>

Solution

  • Your code has 2 errors:

    1. Class is missing for the node elements. Fix it by:
    const node = svg.selectAll(".node")
      .data(json.nodes)
      .enter()
      .append("g")
      .classed("node", true)
      .call(force.drag);
    
    1. You cannot create a <div> under <g>. You can append a <text>:
    node.append('rect')
      .attr('x', -50)
      .attr('y', -30)
      .attr('width', 100)
      .attr('height', 60)
      .style('fill', 'blue');
    
    node.append('text')
      .classed('ui ...', true)
      .text('NODE')
      .attr('alignment-baseline', 'middle')
      .attr('text-anchor', 'middle')
      .style('fill', 'white');
    
    

    ... or insert a <div> under a <foreignObject>;