Thomas
Thomas

Reputation: 90

How to create a custom element in d3

The code is based on Denise's one on bl.ocks. How can I add to the existing code text and an icon to a node? I have almost got it by appending the needed elements but the problem is that circles appear at (0, 0) coordinate.

A picture is worth a thousand words,

screenshot with problem

and my target is having inside the nodes and where the nodes need to be, a text in the middle and an icon,

screenshot current state

This is my current code, that works perfectly as long as the commented part is not uncommented (that's what I have tried to do)

let translateVar = [0,0];
let scaleVar = 1;
let radius = 50;
function create_pan_zoomable_svg(html_element, width, height) {
    let svg = d3.select("body")
        .append("svg")
        .attr("width", "100%")
        .attr("height", "100%")
        .style("background-color", "#eeeeee")
        .call(_zoom).on("dblclick.zoom", null)
        .append("g");
    d3.select("#zoom_in").on('click', function() { _zoom.scaleBy(svg, 2)});
    d3.select("#zoom_out").on('click', function() { _zoom.scaleBy(svg, 0.5)});
    create_marker(svg);
    initialize_link_node(svg);
    return svg;
}

var _zoom = d3.zoom()
    .on("zoom", function() {
        translateVar[0] = d3.event.transform.x;
        translateVar[1] = d3.event.transform.y;
        scaleVar = d3.event.transform.k;
        svg.attr('transform', 'translate(' + translateVar[0] + ',' + translateVar[1] + ') scale(' + scaleVar + ')');
    });

function create_marker(svg) {
    let defs = svg.append("defs");
    defs.append("marker")
        .attr("id", "arrow")
        .attr("viewBox", "0 -5 10 10")
        .attr("refX", 40)
        .attr("refY", 0)
        .attr("markerWidth", 8)
        .attr("markerHeight", 8)
        .attr("orient", "auto")
        .append("svg:path")
        .attr("d", "M0,-5L10,0L0,5");
}

function getScreenInfo() {
    return {
        width : ($(window).width()-translateVar[0])/scaleVar,
        height : ($(window).height()-translateVar[1])/scaleVar,
        centerx : (($(window).width()-translateVar[0])/scaleVar)/2,
        centery : (($(window).height()-translateVar[1])/scaleVar)/2
    };
}

let link, node, simulation;

function initialize_link_node(svg) {
    let currentScreen = getScreenInfo();
    simulation = d3.forceSimulation()
        .force("link", d3.forceLink()
            .id(function(d) { return d.id; }))
        .force("charge", d3.forceManyBody()
            .strength(function(d) { return -20000;}))
        .force("center", d3.forceCenter(currentScreen.centerx, currentScreen.centery));

    link = svg.append("g").selectAll(".link");
    node = svg.append("g").selectAll(".node");
}


function spawn_nodes(svg, graph, around_node, filtering_options) {
    node = node.data(theData.nodes, function(d) {
        return d.id;
    });

    let newNode = node.enter().append("g");
    newNode = newNode.append("circle")
        .attr("class", "node")
        .attr("r", radius)
        .attr("fill", function(d) {
            return colors[d.type]
        })
        .attr("id", function(d) {
            return d.id
        });
    /****************************************************
    newNode.append("text")
        .text( function(d) {
            return d.id;
        })
        .style("fill", "red")
        .style("text-anchor", "middle");
    newNode.append("svg:image")
            .attr("href", function(d) {
                return dict_type_icon[d.type];
            }).attr("width", "30")
            .attr("height", "30");**************************/

    newNode.call(d3.drag()
        .on("start", dragstarted)
        .on("drag", dragged)
        .on("end", dragended)
    );
    node = node.merge(newNode);

    link = link.data(theData.edges, function(d) {
        return d.id;
    });

    let newLink = link.enter().append("line").attr("class", "link").attr("marker-end", "url(#arrow)");

    newLink.append("title").text(function(d) {
       return d.labeled;
    });

    link = link.merge(newLink);

    node = node.merge(newNode);
    simulation.nodes(theData.nodes).on("tick", ticked);
    simulation.force("link").links(theData.edges);
    simulation.alpha(1).alphaTarget(0).restart();
}

function dragstarted(d) {
    if (!d3.event.active) simulation.alphaTarget(0.3).restart();
    d.fx = d.x;
    d.fy = d.y;
}

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

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

function ticked() {
    let currentScreen = getScreenInfo();
    node
        .attr("cx", function(d) {
            return d.x = Math.max(radius, Math.min(currentScreen.width - radius, d.x));
        })
        .attr("cy", function(d) { return d.y = Math.max(radius, Math.min(currentScreen.height - radius, d.y)); });

    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; });
}

When I uncomment the circles appear at (0,0) point as you may have seen in the image.

I would appreciate if you could help me! Thank you.

Upvotes: 2

Views: 661

Answers (1)

Gerardo Furtado
Gerardo Furtado

Reputation: 102194

Right now, you're turning newNode into a circles' selection:

newNode = newNode.append("circle")
    //etc...

So, in order to keep it as a groups' selection, change that line to:

newNode.append("circle")
    //etc...

Then, uncomment the commented part.

Finally, since that now node.merge(newNode) will be a groups' selection, change the cx and cy in ticked function to translate:

node.attr("transform", function(d) {
    return "translate(" + (d.x = Math.max(radius, Math.min(currentScreen.width - radius, d.x))) +
        "," + (d.y = Math.max(radius, Math.min(currentScreen.height - radius, d.y))) + ")";
});

Upvotes: 2

Related Questions