这里的交易:我对javascript和编码一般都很陌生。但我仍然需要使用D3.js库提交交互式图形。现在我和我的小说都失去了如何继续。到目前为止,我已经设法使用Json数据(给定代码)将图片作为节点和可折叠强制树制作树图。
This is the treemap with pictures
现在,我的想法是使用这个D3 V3 code并将其转换,以便我可以在新的V4版本中使用它。事实证明这对我来说太难了所以我实际上试图复制过程并编写一个新的工作代码。然而,我已经无法在我的节点中放置图片而不是简单的彩色圆圈。
我还需要做的是将我的照片链接到代码中,并让鼠标悬停在效果上,一旦我点击角色,它就会显示有关它们的更多信息。
所以,如果有人会这么善良并帮助我做到这一点。我会永远感激,或者至少告诉我如何添加图片......这将是我图表正确方向的一大步。
plnkr.co/edit/77iRi22Pl3HBIKQ92F5f?p=preview
这是我目前使用的代码......
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<title>Alliances of the Marvel Characters in the MCU</title>
<script type="text/javascript" src="lib/d3.js"></script>
<style>
/*evtl. raus damit*/
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; }
.node text { font: 16px sans-serif; }
h1 { font-size: 36px; margin: 10px 0; text-transform: uppercase; font-weight: normal;}
h2, h3 { font-size: 18px; margin: 5px 0 ; font-weight: normal;}
header {padding: 20px; position: absolute; top: 0; left: 0;}
.link {
fill: none;
stroke: #ccc;
stroke-width: 3px;
}
</style>
</head>
<body>
<header>
<h1>Marvel Characters within the MCU</h1>
<h2>Click to view their identity</h2>
<h2>And to close the branches!</h2>
</header>
<div class="">
<div id="myGraph"></div>
</div>
<script>
d3.json('data.json', data => {
networkChart = renderChartCollapsibleNetwork()
.svgHeight(window.innerHeight - 30)
.svgWidth(window.innerWidth - 30)
.container('#myGraph')
.data({ root: data })
.debug(true)
.run()
})
</script>
<script>
/*
This code is based on following convention:
https://github.com/bumbeishvili/d3-coding-conventions
*/
function renderChartCollapsibleNetwork(params) {
// exposed variables
var attrs = {
id: 'id' + Math.floor(Math.random() * 1000000),
svgWidth: 960,
svgHeight: 600,
marginTop: 0,
marginBottom: 5,
marginRight: 0,
marginLeft: 30,
nodeRadius: 18,
container: 'body',
distance: 150,
hiddenChildLevel: 1,
//hiddenChildLevel: 5,
nodeStroke: '#41302D',
nodeTextColor: '#ff0000',
linkColor: '#303030',
activeLinkColor: "blue",
hoverOpacity: 0.2,
maxTextDisplayZoomLevel: 1,
textDisplayed: true,
lineStrokeWidth: 1.5,
data: null
};
/*############### IF EXISTS OVERWRITE ATTRIBUTES FROM PASSED PARAM ####### */
var attrKeys = Object.keys(attrs);
attrKeys.forEach(function (key) {
if (params && params[key]) {
attrs[key] = params[key];
}
})
//innerFunctions which will update visuals
var updateData;
var filter;
//main chart object
var main = function (selection) {
selection.each(function scope() {
//calculated properties
var calc = {}
calc.chartLeftMargin = attrs.marginLeft;
calc.chartTopMargin = attrs.marginTop;
calc.chartWidth = attrs.svgWidth - attrs.marginRight - calc.chartLeftMargin;
calc.chartHeight = attrs.svgHeight - attrs.marginBottom - calc.chartTopMargin;
//########################## HIERARCHY STUFF #########################
var hierarchy = {};
hierarchy.root = d3.hierarchy(attrs.data.root);
//########################### BEHAVIORS #########################
var behaviors = {};
behaviors.zoom = d3.zoom().scaleExtent([0.75, 100, 8]).on('zoom', zoomed);
behaviors.drag = d3.drag().on("start", dragstarted).on("drag", dragged).on("end", dragended);
//########################### LAYOUTS #########################
var layouts = {};
// custom radial kayout
layouts.radial = d3.radial();
//########################### FORCE STUFF #########################
var force = {};
force.link = d3.forceLink().id(d => d.id);
force.charge = d3.forceManyBody()
force.center = d3.forceCenter(calc.chartWidth / 2, calc.chartHeight / 2)
// prevent collide
force.collide = d3.forceCollide().radius(d => {
// if parent has many children, reduce collide strength
if (d.parent) {
if (d.parent.children.length > 50) {
// also slow down node movement
slowDownNodes();
return 7;
}
}
// increase collide strength
if (d.children && d.depth > 2) {
return attrs.nodeRadius;
}
return attrs.nodeRadius * 2;
});
//manually set x positions (which is calculated using custom radial layout)
force.x = d3.forceX()
.strength(0.2)
.x(function (d, i) {
// if node does not have children and is channel (depth=2) , then position it on parent's coordinate
if (!d.children && d.depth > 2) {
if (d.parent) {
d = d.parent
}
}
// custom circle projection - radius will be - (d.depth - 1) * 150
return projectCircle(d.proportion, (d.depth - 1) * 150)[0];
});
//manually set y positions (which is calculated using d3.cluster)
force.y = d3.forceY()
.strength(0.5)
.y(function (d, i) {
// if node does not have children and is channel (depth=2) , then position it on parent's coordinate
if (!d.children && d.depth > 2) {
if (d.parent) {
d = d.parent
}
}
// custom circle projection - radius will be - (d.depth - 1) * 150
return projectCircle(d.proportion, (d.depth - 1) * 150)[1];
})
//--------------------------------- INITIALISE FORCE SIMULATION ----------------------------
// get based on top parameter simulation
force.simulation = d3.forceSimulation()
.force('link', force.link)
.force('charge', force.charge)
.force('center', force.center)
.force("collide", force.collide)
.force('x', force.x)
.force('y', force.y)
//########################### HIERARCHY STUFF #########################
// flatten root
var arr = flatten(hierarchy.root);
// hide members based on their depth
arr.forEach(d => {
if (d.depth > attrs.hiddenChildLevel) {
d._children = d.children;
d.children = null;
}
})
//#################################### DRAWINGS #######################
//drawing containers
var container = d3.select(this);
//add svg
var svg = container.patternify({ tag: 'svg', selector: 'svg-chart-container' })
.attr('width', attrs.svgWidth)
.attr('height', attrs.svgHeight)
.call(behaviors.zoom)
//add container g element
var chart = svg.patternify({ tag: 'g', selector: 'chart' })
.attr('transform', 'translate(' + (calc.chartLeftMargin) + ',' + calc.chartTopMargin + ')');
//################################ Chart Content Drawing ##################################
//link wrapper
var linksWrapper = chart.patternify({ tag: 'g', selector: 'links-wrapper' })
//node wrapper
var nodesWrapper = chart.patternify({ tag: 'g', selector: 'nodes-wrapper' })
var nodes, links, enteredNodes;
// reusable function which updates visual based on data change
update();
//update visual based on data change
function update(clickedNode) {
// set xy and proportion properties with custom radial layout
layouts.radial(hierarchy.root);
//nodes and links array
var nodesArr = flatten(hierarchy.root, true)
.orderBy(d => d.depth)
.filter(d => !d.hidden);
var linksArr = hierarchy.root.links()
.filter(d => !d.source.hidden)
.filter(d => !d.target.hidden)
// make new nodes to appear near the parents
nodesArr.forEach(function (d, i) {
if (clickedNode && clickedNode.id == (d.parent && d.parent.id)) {
d.x = d.parent.x;
d.y = d.parent.y;
}
});
//links
links = linksWrapper.selectAll('.link').data(linksArr, d => d.target.id);
links.exit().remove();
links = links.enter()
.append('line')
.attr('class', 'link')
.merge(links).attr('stroke', '#9ecae1');
links.attr('stroke', attrs.linkColor).attr('stroke-width', attrs.lineStrokeWidth)
//node groups
nodes = nodesWrapper.selectAll('.node').data(nodesArr, d => d.id);
var exited = nodes.exit().remove();
var enteredNodes = nodes.enter()
.append('g')
.attr('class', 'node')
//bind event handlers
enteredNodes.on('click', nodeClick)
.on('mouseenter', nodeMouseEnter)
.on('mouseleave', nodeMouseLeave)
.call(behaviors.drag)
//node texts
enteredNodes.append('text').attr('class', 'node-texts')
.attr('x', 60).attr('fill', attrs.nodeTextColor)
.text(d => d.data.name)
.style('display', attrs.textDisplayed ? "initial" : "none")
//channels grandchildren
var channelsGrandchildren = enteredNodes
.append("circle")
.attr('r', 50)
.attr('stroke-width', 2)
.attr('stroke', attrs.nodeStroke)
//merge node groups and style it
nodes = enteredNodes.merge(nodes);
nodes
.attr('fill', d => {
return d._children ? "#3182bd" : d.children ? "#c6dbef" : "#fd8d3c";
})
.style('cursor', 'pointer')
//force simulation
force.simulation.nodes(nodesArr)
.on('tick', ticked);
// links simulation
force.simulation.force("link").links(links).id(d => d.id).distance(100).strength(d => 1);
}
//####################################### EVENT HANDLERS ########################
// zoom handler
function zoomed() {
//get transform event
var transform = d3.event.transform;
attrs.lastTransform = transform;
// apply transform event props to the wrapper
chart.attr('transform', transform)
svg.selectAll('.node').attr("transform", function (d) { return `translate(${d.x},${d.y}) scale(${1 / (attrs.lastTransform ? attrs.lastTransform.k : 1)})`; });
svg.selectAll('.link').attr("stroke-width", attrs.lineStrokeWidth / (attrs.lastTransform ? attrs.lastTransform.k : 1));
// hide texts if zooming is less than certain level
if (transform.k < attrs.maxTextDisplayZoomLevel) {
svg.selectAll('.node-texts').style('display', 'none');
attrs.textDisplayed = false;
} else {
svg.selectAll('.node-texts').style('display', 'initial');
attrs.textDisplayed = true;
}
}
//tick handler
function ticked() {
// set links position
links.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; });
//set nodes position
svg.selectAll('.node').attr("transform", function (d) { return `translate(${d.x},${d.y}) scale(${1 / (attrs.lastTransform ? attrs.lastTransform.k : 1)})`; });
}
//handler drag start event
function dragstarted(d) {
//disable node fixing
nodes.each(d => { d.fx = null; d.fy = null })
}
// handle dragging event
function dragged(d) {
// make dragged node fixed
d.fx = d3.event.x;
d.fy = d3.event.y;
}
//-------------------- handle drag end event ---------------
function dragended(d) {
// we are doing nothing, here , aren't we?
}
//-------------------------- node mouse hover handler ---------------
function nodeMouseEnter(d) {
//get hovered node
var node = d3.select(this);
//get links
var links = hierarchy.root.links();
//get hovered node connected links
var connectedLinks = links.filter(l => l.source.id == d.id || l.target.id == d.id);
//get hovered node linked nodes
var linkedNodes = connectedLinks.map(s => s.source.id).concat(connectedLinks.map(d => d.target.id))
//reduce all other nodes opacity
nodesWrapper.selectAll('.node')
.filter(n => linkedNodes.indexOf(n.id) == -1)
.attr('opacity', attrs.hoverOpacity);
//reduce all other links opacity
linksWrapper.selectAll('.link').attr('opacity', attrs.hoverOpacity);
//highlight hovered nodes connections
linksWrapper.selectAll('.link')
.filter(l => l.source.id == d.id || l.target.id == d.id)
.attr('opacity', 1)
.attr('stroke', attrs.activeLinkColor)
}
// --------------- handle mouseleave event ---------------
function nodeMouseLeave(d) {
// return things back to normal
nodesWrapper.selectAll('.node').attr('opacity', 1);
linksWrapper.selectAll('.link').attr('opacity', 1).attr('stroke', attrs.linkColor)
}
// --------------- handle node click event ---------------
function nodeClick(d) {
//free fixed nodes
nodes.each(d => { d.fx = null; d.fy = null })
// collapse or expand node
if (d.children) {
d._children = d.children;
d.children = null;
update();
force.simulation.restart();
force.simulation.alphaTarget(0.15);
} else if (d._children) {
d.children = d._children;
d._children = null;
update(d);
force.simulation.restart();
force.simulation.alphaTarget(0.15);
} else {
//nothing is to collapse or expand
}
freeNodes();
}
//######################################### UTIL FUNCS ##################################
updateData = function () {
main.run();
}
function slowDownNodes() {
force.simulation.alphaTarget(0.05);
};
function speedUpNodes() {
force.simulation.alphaTarget(0.45);
}
function freeNodes() {
d3.selectAll('.node').each(n => { n.fx = null; n.fy = null; })
}
function projectCircle(value, radius) {
var r = radius || 0;
var corner = value * 2 * Math.PI;
return [Math.sin(corner) * r, -Math.cos(corner) * r]
}
//recursively loop on children and extract nodes as an array
function flatten(root, clustered) {
var nodesArray = [];
var i = 0;
function recurse(node, depth) {
if (node.children)
node.children.forEach(function (child) {
recurse(child, depth + 1);
});
if (!node.id) node.id = ++i;
else ++i;
node.depth = depth;
if (clustered) {
if (!node.cluster) {
// if cluster coordinates are not set, set it
node.cluster = { x: node.x, y: node.y }
}
}
nodesArray.push(node);
}
recurse(root, 1);
return nodesArray;
}
function debug() {
if (attrs.isDebug) {
//stringify func
var stringified = scope + "";
// parse variable names
var groupVariables = stringified
//match var x-xx= {};
.match(/var\s+([\w])+\s*=\s*{\s*}/gi)
//match xxx
.map(d => d.match(/\s+\w*/gi).filter(s => s.trim()))
//get xxx
.map(v => v[0].trim())
//assign local variables to the scope
groupVariables.forEach(v => {
main['P_' + v] = eval(v)
})
}
}
debug();
});
};
//----------- PROTOTYEPE FUNCTIONS ----------------------
d3.selection.prototype.patternify = function (params) {
var container = this;
var selector = params.selector;
var elementTag = params.tag;
var data = params.data || [selector];
// pattern in action
var selection = container.selectAll('.' + selector).data(data)
selection.exit().remove();
selection = selection.enter().append(elementTag).merge(selection)
selection.attr('class', selector);
return selection;
}
// custom radial layout
d3.radial = function () {
return function setProportions(root) {
recurse(root, 0, 1);
function recurse(node, min, max) {
node.proportion = (max + min) / 2;
if (!node.x) {
// if node has parent, match entered node positions to it's parent
if (node.parent) {
node.x = node.parent.x;
} else {
node.x = 0;
}
}
// if node had parent, match entered node positions to it's parent
if (!node.y) {
if (node.parent) {
node.y = node.parent.y;
} else {
node.y = 0;
}
}
//recursively do the same for children
if (node.children) {
var offset = (max - min) / node.children.length;
node.children.forEach(function (child, i, arr) {
var newMin = min + offset * i;
var newMax = newMin + offset;
recurse(child, newMin, newMax);
});
}
}
}
}
//https://github.com/bumbeishvili/d3js-boilerplates#orderby
Array.prototype.orderBy = function (func) {
this.sort((a, b) => {
var a = func(a);
var b = func(b);
if (typeof a === 'string' || a instanceof String) {
return a.localeCompare(b);
}
return a - b;
});
return this;
}
//########################## BOILEPLATE STUFF ################
//dinamic keys functions
Object.keys(attrs).forEach(key => {
// Attach variables to main function
return main[key] = function (_) {
var string = `attrs['${key}'] = _`;
if (!arguments.length) { return eval(` attrs['${key}'];`); }
eval(string);
return main;
};
});
//set attrs as property
main.attrs = attrs;
//debugging visuals
main.debug = function (isDebug) {
attrs.isDebug = isDebug;
if (isDebug) {
if (!window.charts) window.charts = [];
window.charts.push(main);
}
return main;
}
//exposed update functions
main.data = function (value) {
if (!arguments.length) return attrs.data;
attrs.data = value;
if (typeof updateData === 'function') {
updateData();
}
return main;
}
// run visual
main.run = function () {
d3.selectAll(attrs.container).call(main);
return main;
}
main.filter = function (filterParams) {
if (!arguments.length) return attrs.filterParams;
attrs.filterParams = filterParams;
if (typeof filter === 'function') {
filter();
}
return main;
}
return main;
}
</script>
</body>
&#13;
这是链接的Json:
{
"name": "MCU",
"icon": "Logos/Marvel-Cinematic-Universe.png",
"size": 40000,
"children": [
{
"name": "Heroes",
"children": [
{
"name": "Avengers",
"img": "Logos/Avengers.jpg",
"size": 40000,
"children": [
{
"name": "Steven 'Steve' Grant Rogers",
"hero": "Captain America",
"img": "Characters/Avengers/Cap-America.jpg",
"size": 40000
},
{
"name": "Anthony 'Tony' Edward Stark",
"hero": "Iron Man",
"img": "Characters/Avengers/Iron Man.jpg",
"size": 40000
},
{
"name": "Thor Odinson",
"hero": "THOR",
"img": "Characters/Avengers/thor.jpg",
"size": 40000
},
{
"name": "Natalia 'Natasha' Alianova Romanoff",
"hero": "Black Widow",
"img": "Characters/Avengers/Black Widow.jpg",
"size": 40000
},
{
"name": "Clinton 'Clint' Francis Barton",
"hero": "Hawkeye",
"img": "Characters/Avengers/Hawkeye.jpg",
"size": 40000
},
{
"name": "Dr. Bruce Banner",
"hero": "HULK",
"img": "Characters/Avengers/HULK.jpg",
"size": 40000
},
{
"name": "Sam Wilson",
"hero": "Falcon",
"img": "Characters/Avengers/Falcon.jpg",
"size": 40000
},
{
"name": "Wanda Maximoff",
"hero": "Scarlet Witch",
"img": "Characters/Avengers/Scarlet Witch.jpg",
"size": 40000
},
{
"name": "Vision",
"hero": "Vision",
"img": "Characters/Avengers/Vision.jpg",
"size": 40000
},
{
"name": "Collonel James 'Rhodey' Rhodes",
"hero": "Iron Patriot",
"img": "Characters/Avengers/Iron Patriot.jpg",
"size": 40000
}
]
},
{
"name": "Revengers",
"img": "Logos/Revengers.png",
"size": 40000,
"children": [
{
"name": "Thor Odinson",
"hero": "THOR, the God of Thunder",
"img": "Characters/Revengers/thor ragnarok.jpg",
"size": 40000
},
{
"name": "Loki Laufeyson",
"hero": "Loki, the God of Mischief",
"img": "Characters/Revengers/Loki.jpg",
"size": 40000
},
{
"name": "Dr. Bruce Banner / HULK",
"hero": "HULK",
"img": "Characters/Avengers/HULK.jpg",
"size": 40000
},
{
"name": "Valkyrie",
"hero": "Valkyrie",
"img": "Characters/Revengers/Valkyrie.jpg",
"size": 40000
}
]
},
{
"name": "Guardians of the Galaxy",
"img": "Logos/Guardians.png",
"size": 40000,
"children": [
{
"name": "Peter Quill",
"hero": "Star Lord",
"img": "Characters/Guardians/Peter Quill.jpg",
"size": 40000
},
{
"name": "Drax",
"hero": "Drax, the Destroyer",
"img": "Characters/Guardians/Drax.jpg",
"size": 40000
},
{
"name": "Rocket",
"hero": "Rocket Racoon",
"img": "Characters/Guardians/Rocket Racoon.jpg",
"size": 40000
},
{
"name": "Gamora",
"hero": "Gamora",
"img": "Characters/Guardians/Gamora.jpg",
"size": 40000
},
{
"name": "Groot",
"hero": "I am Groot!",
"img": "Characters/Guardians/Groot.jpg",
"size": 40000
}
]
},
{
"name": "Howling Commando",
"img": "Logos/Howling-Commando.png",
"size": 40000,
"children": [
{
"name": "Steven 'Steve' Grant Rogers",
"hero": "Captain America",
"img": "Characters/Avengers/Cap-America.jpg",
"size": 40000
},
{
"name": "James 'Bucky' Buchanan Barnes",
"hero": "Bucky",
"img": "Characters/Howling-Commando/BuckyBarnes.jpg",
"size": 40000
}
]
},
{
"name": "Peter Benjamin Parker",
"hero": "Spiderman",
"img": "Characters/Avengers/Spiderman.jpg",
"size": 40000
},
{
"name": "Dr. Stephen Strange",
"hero": "Doctor Strange",
"img": "Characters/Doctor Strange",
"size": 40000
},
{
"name": "Scott Lang",
"hero": "Ant Man",
"img": "Characters/Avengers/Ant Man.jpg",
"size": 40000
},
{
"name": "T'Challa",
"hero": "Black Panther",
"img": "Characters/Black Panther.jpg",
"size": 40000
}
]
},
{
"name": "Aliances",
"children": [
{
"name": "SHIELD",
"img": "Logos/SHIELD.png",
"size": 40000,
"children": [
{
"name": "Director Nicholas 'Nick' Fury",
"hero": "Nick Fury",
"img": "Characters/SHIELD/Nick Fury.jpg",
"size": 40000
},
{
"name": "Agent Phil Coulson",
"hero": "Coulson",
"img": "Characters/SHIELD/Phil Coulson.jpg",
"size": 40000
}
]
}
]
}
]
}
&#13;