Ich baue gerade ein Diagramm der Beziehung zwischen Forschungsarbeiten in D3.js. Momentan erlaubt mir mein Code ein kraftgerichtetes Diagramm zu erstellen. Ich kann den Graphen zoomen und ziehen, und vorläufig "hässliche" Tooltips zeigen Knoteninformationen auf "mouseover" an (aber das ist für diese Frage irrelevant).Aufbau eines kraftgerichteten konzentrischen Kreisdiagramms mit Knotenanordnung von innen nach außen in D3.V4
Ich bin auf der Suche nach der besten Möglichkeit zur Visualisierung des Artikels Netzwerk auf der Grundlage des Erscheinungsjahres. Ich glaube, dass der beste Weg, dies zu tun ist, um die Knoten von Jahr in einem konzentrischen Kreismuster wie diese, anzuzeigen:
Simple representation of the expected result of a concentric circle force-directed graph
wie in meinem Code Knoten gefärbt ist, basierend auf Jahr im Bild.
Hier ist meine zupfen Link: http://plnkr.co/edit/RCzGe0OFaQNnI32kBuSn?p=preview
Und hier ist mein Code: HTML:
<!DOCTYPE html>
<html>
<head>
<script src="https://d3js.org/d3.v4.min.js"></script>
<link rel="stylesheet" href="style.css">
</head>
<body>
<script src="script.js"></script>
</body>
</html>
Style.css:
/* Styles go here */
.links line {
stroke: #999;
stroke-opacity: 0.6;
}
.nodes circle {
stroke: #fff;
stroke-width: 1.5px;
}
div.tooltip {
position: absolute;
text-align: center;
padding: 2px;
font: 12px sans-serif;
background: lightsteelblue;
border: 0px;
border-radius: 8px;
pointer-events: none;
}
Test-data.JSON:
{
"papers":[
{
"id":"1",
"title":"Title 1",
"year":"2016",
"authors":["A1","A2"],
"problematic":"",
"solution":"",
"references":["2","3"]
},
{
"id":"2",
"title":"Title 2",
"year":"2015",
"authors":["A2","A3"],
"problematic":"",
"solution":"",
"references":["4","5"]
},
{
"id":"3",
"title":"Title 3",
"year":"2015",
"authors":["A4","A5"],
"problematic":"",
"solution":"",
"references":["4"]
},
{
"id":"4",
"title":"Title 4",
"year":"2014",
"authors":["A1","A3"],
"problematic":"",
"solution":"",
"references":[]
},
{
"id":"5",
"title":"Title 5",
"year":"2013",
"authors":["A6","A7"],
"problematic":"",
"solution":"",
"references":[]
}
]
}
script.js:
/* ------ DESCRIPTION ------
Properties of the graph:
BASIC:
✓ Graph represents all papers and relationships in RTB research
✓ Graph is force dynamic
✓ Nodes are coloured by publishing year
✓ Graph is draggable
✓ Graph is zoomable
X Graph is "tree like" where the nodes are "ordered" by publishing year, the oldest being at the bottom
~ Hovering over a Node will display it's info
- Clicking a node will allow to visualize it's direct or most important connections
ADVANCED:
- Display papers graph
- Display authors graph
- Search for paper based on info: id, title, author, year, ...
- Add new paper to graph and modify and save JSON file
- Open PDF File in new Tab
*/
// ----- GLOBAL VARIABLES ------
var w = window.innerWidth;
var h = window.innerHeight;
var svg = d3.select("body").append("svg")
.attr("width",w)
.attr("height",h)
.style("cursor","move");
var g = svg.append("g");
// NODE COLORS
var color = d3.scaleOrdinal(d3.schemeCategory20);
// FORCE SIMULATION
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) { return d.id; }))
.force("charge", d3.forceManyBody().strength(-100))
.force("center", d3.forceCenter(w/2, h/2))
.force("collide", d3.forceCollide(10));
// ZOOM PARAMETERS
var min_zoom = 0.1;
var max_zoom = 7;
var zoom = d3.zoom()
.scaleExtent([min_zoom,max_zoom])
.on("zoom", zoomed);
svg.call(zoom);
var transform = d3.zoomIdentity
.translate(w/6, h/6)
.scale(0.5);
svg.call(zoom.transform, transform);
// BASIC NODE SIZE
var nominal_stroke = 1.5;
var nominal_node_size = 8;
// ----- GLOBAL FUNCTIONS -----
function dragStart(d){
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragging(d){
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function dragEnd(d){
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
function zoomed() {
g.attr("transform", d3.event.transform);
// Manually offsets the zoom to compensate for the initial position. Should get fixed asap or the position variables made global.
//svg.attr("transform", "translate(" + (d3.event.transform.x + 400) + "," + (d3.event.transform.y + 325) + ")scale(" + d3.event.transform.k + ")");
}
function isInList(el, list){
for (var i = 0; i < list.length; i++){
if (el == list[i]) return true;
}
return false;
}
// builds a graph dictionary based on paper references
function referencesGraph(file_data){
var nodes = [];
var links = [];
// we use these to add nodes to references that are missing as nodes
var node_ids = [];
var ref_ids = [];
// for each paper in graph create a node and append result to node list
for (var i = 0; i < file_data.length; i++){
var node = {
"id":file_data[i].id,
"title":file_data[i].title,
"year":file_data[i].year,
"authors":file_data[i].authors
};
node_ids.push(file_data[i].id);
nodes.push(node);
// for each referenced paper in graph create a link and append result to link list
for (var j = 0; j < file_data[i].references.length; j++){
var link = {
"source":file_data[i].id,
"target":file_data[i].references[j]
};
ref_ids.push(file_data[i].references[j]);
links.push(link);
}
}
//check if all referenced elements have a node associated
for (var i = 0; i < ref_ids.length; i++){
if (!isInList(ref_ids[i],node_ids)){
var node = {
"id":ref_ids[i],
"title":ref_ids[i],
"year":""
}
nodes.push(node);
}
}
var graph = {
"nodes":nodes,
"links":links
};
return graph;
}
// builds a graph dictionary based on author collaboration
function authorsGraph(data){
}
// DEAL WITH MISSING DATA TO BE WORKED
// ----- MANAGE JSON DATA -----
d3.json("test-data.json",function(error,graph){
if (error) throw error;
// Read the JSON data and create a dictionary of nodes and links based on references
var paper_graph_data = referencesGraph(graph.papers);
//var authors_graph_data; //function not implemented yet
// INITIALIZE THE LINKS
var link = g.append("g")
.attr("class","links")
.selectAll("line")
.data(paper_graph_data.links)
.enter()
.append("line")
.attr("stroke-width",function(d){return nominal_stroke})
/* FUNCTION THAT CREATES DIV ELEMENT TO HOLD NODE INFORMATION
[ PAPER TITLE ]
[ PUBLISHING YEAR ][ PERSONAL RATING ]
[ AUTHORS & LINKS ]
[ PROBLEMATIC ]
[ SOLUTION ]
[OPEN PDF FILE]
*/
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
function createTooltip(d){
//get node data, manage missing values
div.transition()
.duration(200)
.style("opacity", .9);
div.html("<table><tr><td>" + d.title + "</td></tr><tr><td>" + d.year + "</td></tr><tr><td>" + d.authors + "</td></tr><tr><td>" + d.problematic + "</td></tr><tr><td>" + d. solution + "</td></tr></table>")
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
}
// INITIALIZE THE NODES
var node = g.append("g")
.attr("class","nodes")
.selectAll("circles")
.data(paper_graph_data.nodes)
.enter()
.append("circle")
.attr("r",nominal_node_size)
.attr("fill",function(d){return color(d.year);})
.style("cursor","pointer")
.on("mouseover",createTooltip)
.on("mouseout",function(d){
div.transition()
.duration(500)
.style("opacity", 0);
})
.call(d3.drag()
.on("start", dragStart)
.on("drag", dragging)
.on("end", dragEnd));
simulation.nodes(paper_graph_data.nodes)
.on("tick",ticked);
simulation.force("link")
.links(paper_graph_data.links);
// function to return link and node position when simulation is generated
function ticked(){
// Each year is placed on a different level to get chronological order of paper network
/*
switch(d.source.year){
case "2016":
return 40;
case "2015":
return 80;
case "2014":
return 120;
case "2013":
return 160;
case "2012":
return 200;
case "2011":
return 240;
case "2010":
return 280;
case "2009":
return 320;
case "2008":
return 360;
case "2007":
return 400;
default:
return 600;
}
*/
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("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
}
function ticked_advanced(){
link
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) {
switch(d.source.year){
case "2016":
return 40;
case "2015":
return 80;
case "2014":
return 120;
case "2013":
return 160;
case "2012":
return 200;
case "2011":
return 240;
case "2010":
return 280;
case "2009":
return 320;
case "2008":
return 360;
case "2007":
return 400;
default:
return 600;
}
})
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) {
switch(d.target.year){
case "2016":
return 40;
case "2015":
return 80;
case "2014":
return 120;
case "2013":
return 160;
case "2012":
return 200;
case "2011":
return 240;
case "2010":
return 280;
case "2009":
return 320;
case "2008":
return 360;
case "2007":
return 400;
default:
return 600;
}
});
node
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) {
switch(d.year){
case "2016":
return 40;
case "2015":
return 80;
case "2014":
return 120;
case "2013":
return 160;
case "2012":
return 200;
case "2011":
return 240;
case "2010":
return 280;
case "2009":
return 320;
case "2008":
return 360;
case "2007":
return 400;
default:
return 600;
}
});
}
});
Ich stelle mir vor, dass ich die Zecke Funktion ändern müssen, um x und y zufällige Koordinaten innerhalb jeder „Jahr-Zone“ zurückzukehren, aber nicht wissen, wie diese zu berechnen.
Irgendwelche Ideen, wie man das macht? Danke vielmals.
Hinweis:
ich diese Antwort fand eine Zufallszahl in einem Ring zu erzeugen, die gleichmäßig bezieht sich auch eine Zufallszahl in einem Kreis zu erzeugen:
Generate a uniformly random point within an annulus (ring)
Dies ist in der Tat die richtige Antwort, hatte ich das gleiche gefunden. Allerdings würde ich gerne Erklärungen zu den Gründen für die Funktion constrain für diejenigen hinzufügen, die nicht mit D3.js oder der Mathematik vertraut sind. Und warum kommentieren Leute ihren Code nicht? – David