Wie zeichne ich einen Graph in Javascript D3.js?

1 Antwort

Vom Fragesteller als hilfreich ausgezeichnet
Diese ist meine JSON Datei:

Das ist kein JSON, sondern JavaScript-Code, bei dem eine Variable angelegt wird, die auf ein JavaScript-Objekt zeigt. Beachte zudem die Aktualisierung meiner Antwort zu deiner letzten Frage.

Die Skills ("java, python, HTML, json") sind die Knoten und die Indexe (KayO, BenBeck, Borea usw.) sind die Kanten.

Demzufolge müsstest du erst einmal alle Skills ermitteln und zu diesen dann eine Node-Liste erstellen.

let allSkills = [];

for (const person in persona) {
  const skills = persona[person].skills.split(", ");
  allSkills = allSkills.concat(skills.filter(item => allSkills.indexOf(item) < 0));
}

const nodes = allSkills.map(skill => ({ name: skill }));

Die Kanten so zu ermitteln, wird etwas schwieriger. Bereits dein Ausgangsformat ist ungünstig. Besser wäre es, wenn die Skills nicht als Strings gespeichert würden, sondern ebenso schon als Listen.

skills: [ "css", "ruby", "php", "training", /* etc. ... */ ]

Beim Sammeln aller Skills könntest du im gleichen Lauf auch die Kanten schon kreieren. So wie ich es verstehe, müsste jeder Skill durch die Person selbst miteinander verbunden werden.

let allSkills = [];
const allEdges = [];

for (const person in persona) {
  const skills = persona[person].skills.split(", ");
  // ...

  for (let i = 0; i < skills.length - 1; ++i) {
    for (let j = i + 1; j < skills.length; ++j) {
      allEdges.push(({ source: skills[i], target: skills[j], label: person }));
    }
  }
}

Für jedes Kantenobjekt würde ich zugleich ein label-Property hinzufügen. Ein Beispiel für einen Graph mit beschrifteten Kanten findest du hier. Beim Hinzufügen des TextPath muss nur der Rückgabewert für den text-Callback geändert werden.

edgelabels.append('textPath')
  .attr(/* ... */)
  .style(/* ... */)
  .text((d, i) => d.label);
Anschließend möchte ich mit einem Klick auf den Knoten die Liste der Publikationen auf der rechten Seite aufrufen können.

An die Nodes kannst du einen Klick-Handler anhängen.

node.on("click", currentNode => {
  // ...
});

Das currentNode-Objekt sollte auf das angeklickte Node-Objekt aus deinem Graph zeigen. Das heißt, du hättest Zugriff auf das name-Property. Mit Hilfe dessen kannst du dein persona-Objekt filtern.

ashuraRog 
Fragesteller
 16.05.2021, 19:52

super dankeschön

0
ashuraRog 
Fragesteller
 16.05.2021, 21:26

Ich habe deine Methode ausgeführt und es klappt. Nun frage ich mich wie ich die erstellte Methode als Knoten und Kanteausgebe. Wie ich kriege ich das was hier ausgegeben wird :

allEdges.push(({ source: skills[i], target: skills[j], label: person }));

In diesen Graph-Placeholder?

const graph = {nodes: [{id: "a"}, {id: "b"}], links: [{source: "a", target: "b"}]};

Denn wie du oben siehst gibt ja die const-Deklaration von graph die daten aus:


    var link = svg
      .append("g")
      .attr("class", "links")
      .selectAll("line")
      .data(graph.links)
      .enter().append("line")
      .attr("stroke", "#aaa");

    var node = svg.append("g")
      .attr("class", "nodes")
      .selectAll("circle")
      .data(graph.nodes)
      .enter().append("circle")
      .attr("r", 5)
      .call(d3.drag()
        .on("start", dragstarted)
        .on("drag", dragged)
        .on("end", dragended));
0
regex9  17.05.2021, 04:01
@ashuraRog
const graph = {
  nodes: nodes,
  links: allEdges
};
1
ashuraRog 
Fragesteller
 21.05.2021, 20:07
@regex9

Ich habe dies mal versucht doch leider bekomme ich zwei Fehlermeldungen:

Uncaught Error: node not found: java

und

Uncaught TypeError: Cannot create property 'vx' on string 'python'

Ich habe mir mal überbelgt ob ich ein Objekt erstellen soll, wie z.B:

const links = data.links.map(d => Object.create(d));
const nodes = data.nodes.map(d => Object.create(d));

und Data wäre dann:

data = ({nodes: Array.from(new Set(links.flatMap(l => [l.source, l.target])), id => ({id})), links})

DOch leider weiß ich nicht wie ich auf die Werte zugreifen kann welche ich hier ausgebe:

let allSkills = [];
const allEdges = [];
for(const person in persona){
  const skills = persona[person].skills.split(",").map(s => s.trim());
  allSkills = allSkills.concat(skills.filter(item => allSkills.indexOf(item) < 0));
  for (let i = 0; i < skills.length - 1; ++i) {
    for (let j = i + 1; j < skills.length; ++j) {
      allEdges.push(({ source: skills[i], target: skills[j], id: person }));
     }
   }
 }
const nodes = allSkills.map(skill => ({ name: skill }));
0
regex9  23.05.2021, 13:55
@ashuraRog

Ich habe hier:

allEdges.push(({ source: skills[i], target: skills[j], label: person }));

einen Fehler gemacht. Die Indizes sollten für source und target verwendet werden.

allEdges.push(({ source: i, target: j, label: person }));

Demo: https://jsfiddle.net/7vLq04m6/

1
ashuraRog 
Fragesteller
 23.05.2021, 22:29
@regex9

Ich glaub eher, dass der Fehler am Javascript Version liegt. Ich nutze derzeit die Version 6 "https://d3js.org/d3.v6.js". Mit Ihrer Version klappt es voll und ganz. Doch bei mir kommt immer die Fehlermeldung "Uncaught TypeError: Cannot read property 'force' of undefined".

0