erdogant / d3graph

Creation of interactive networks using d3 Javascript
https://erdogant.github.io/d3graph
Other
175 stars 25 forks source link

Feature Request: Select Marker Shape for Nodes/Edges #10

Open JohnOmernik opened 2 years ago

JohnOmernik commented 2 years ago

Via Node/Edge, it would be nice to be able to select a few more marker shapes. I am not strong enough in JS to understand this, but I believe it is possible with SVG markers based on this link:

http://bl.ocks.org/dustinlarimer/5888271

I think this is where directed edge arrows come from, and it would be great if we could select from a few basic shapes like circle (Default as it currently is), Square, Triangle, Diamond, Star. This would give us the ability to have a great selection of nodes and edge shapes to work with, without having to do custom images. (While interesting, SVG shapes makes everything play well I think).

A stretch goal for this request would be the ability to select a Marker and Color and create a definition that can be displayed as a Key. It wouldn't need to be complicated, it could work something like this:

d3_toc = {
                  ('circle', 'black'): "Actors",
                  ('triangle', 'blue'): "Movies",
                  ('square', ''yellow'): "TV Series"
}

and then that could be passed to an optional ToC that gets displayed on the HTML. In addition to the markers shape/color the ability to put just lines of text would be good. You could have the ToC and then you could say "Node Sizes indicate popularity" or "Edge Widths determine number of roles". It would be a manual thing, but the ability to have a few extra shapes, as well as some notes on what we are seeing would look fantastic.

erdogant commented 2 years ago

I am going to look into this.

erdogant commented 2 years ago

I added the feature and it is now possible to change the marker-end! See docs here.

Note that the marker-start and marker-colors are also in this version but are still under development.

JohnOmernik commented 2 years ago

This looks pretty great, if I am reading the docs correct at this point, it only applies to Edges correct? Are Node shapes (other than Circle) going to be something that we could change too? Thank you!

erdogant commented 2 years ago

Correct. Edges can now have the marker-end: arrow, stub, circle and square.

JohnOmernik commented 1 year ago

Would it be possible to use a basic set of shapes for nodes? Are we locked into circles?

erdogant commented 1 year ago

I did another attempt.. For some reason, it keeps failing when I implement something else and than circles.

jjon commented 1 year ago

I've only just discovered your d3graph project and I'm hoping it will turn out to be the tool I've needed.

With respect to node shapes: I've recently been trying to learn to use d3.js And I too have wanted to use something other than circles for force directed graphs. I've had some success with this ellipse-force module. It hasn't been updated in a long while, and there's a problem with displaying markers, but I'm keen on ellipses, and hope to expand on their use in directed graphs. Here you can see a toy example of my progress so far.

erdogant commented 1 year ago

This is very cool @jjon! Can you explain how you created the function to drag a node and freeze it (until click again)? Or would it be possible to add this functionality here too? That would help to fix issue #18 :-)

jjon commented 1 year ago

@erdogant The click and drag behavior of nodes was the product of a lot of naive trial and error and some "monkey see monkey do" from the observablehq.com example mentioned in the comment, so I can't claim to really understand what's going on. I had to do quite a bit of futzing about to get old examples to work with d3.v7; but, this works in the toy example I mentioned:

the d3 for the nodes includes a .call(d.drag()) and looks like this:

var node = svg.append("g")
  .attr("class", "node")
  .selectAll("ellipse")
  .data(dnodes)
  .join(enter => enter.append("ellipse"))
    .attr("rx", function(d) { return d.rx; })
    .attr("ry", function(d) { return d.ry; })
    .attr("fill", function(d) { return type_attributes[d.types[0]].color })
    .call(d3.drag()
        .on("start", dragstarted)
        .on("drag", dragged)
        .on("end", dragended))
    .on("click", clicked)
    .on("contextmenu", cmdclick);

and the drag handlers look like this:

//getting rid of d3.event.active fixed the dragging for v7
//see:https://observablehq.com/@d3/d3v6-migration-guide#events
function dragstarted(event, d) {
  if (!event.active) simulation.alphaTarget(0.05).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 = event.x;
  d.fy = event.y;
}

this fixes the node at the drag "end" location: d.fx = event.x

and the .on("click", clicked) handler looks like this:

function clicked(event, d) {
    d.fx = null;
    d.fy = null;
}

setting the fixed node location (d.fx & d.fy) back to null

The result is that you can make nodes stay where you drag them until they're clicked. A nice side-effect is that my metadata table, generated in the cmdclick handler, stays with the node when you drag it.

If you come up with any insights on how to make the markers respect the perimeter of the ellipses, do let me know.

Jon

erdogant commented 1 year ago

Thanks! I experimented with your code but I can not get the nodes sticky. I must be missing something here.

jjon commented 1 year ago

@erdogant can you show me the code you're working with?

erdogant commented 1 year ago

See here. Look at the "view page source". If you search for "STICKY NODES" you will find your functions. I needed to change some of the function names. Perhaps it is because I use d3.v3.js whereas you d3.v7? I guess the latter is the issue.

jjon commented 1 year ago

I'm sure that's it. d3's gone through a lot of breaking changes since d3v3. The observable document here describes how the handling of mouse events has changed since v6. I think, however, that d3.event was available in v3. In v7, the event is passed as a parameter to the drag handlers, so your dragged_node(event, d), for example, won't work under v3. But, it might be as simple as revising it something like this:

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

But of course, that's probably just wishful thinking. Have a look at Bostock's 'Les Miserables' example that uses d3.v4. There he codes the drag handlers thus:

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;
}
erdogant commented 1 year ago

Here is another great example with sticky nodes and with a legend :D