I am trying to use D3.js on an html page and fill in the node circles with a .jpeg image that is in base64 format and is ** not a URL **. I have found lots of examples with URLs and have made a working Network Force Graph with URLs but I need to be able to just pass base64 in my json file.
Can anyone help me? Thank you.
Here is the relevant snippet. d.img contains the base64 text.:
var images = node.append("svg:image")
.attr("xlink:href", "data:image/jpeg;base64," + function(d) { return "data:image/jpeg;base64," + d.img;})
//.attr("xlink:href", function(d) { return "data:image/jpeg;base64," + "data:image/jpeg;base64," + d.img;})
.attr("x", function(d) { return -25;})
.attr("y", function(d) { return -25;})
.attr("height", 50)
.attr("width", 50);
Here is the full javascript file:
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<meta name="description" content="">
<title>8.6 - Node-link diagrams</title>
<!-- Bootstrap Core CSS -->
<link rel="stylesheet" href="css/bootstrap.min.css">
<!-- Custom CSS -->
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<nav class="navbar navbar-default"></nav>
<svg width="600" height="600"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height"),
circleRadius = 30,
chargeStrength = -5000; // -50
var color = d3.scaleOrdinal(d3.schemeCategory10);
// Add "forces" to the simulation here
var simulation = d3.forceSimulation()
.force("center", d3.forceCenter(width / 2, height / 2))
.force("charge", d3.forceManyBody().strength(chargeStrength))
.force("collide", d3.forceCollide(circleRadius).strength(0.9))
.force("link", d3.forceLink().id(function(d) { return d.id; }));
// For each id.
d3.json("data/force2.json", function(error, graph) {
if (error) throw error;
console.log(graph);
// Add lines for every link in the dataset
var link = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(graph.links)
.enter().append("line")
.attr("stroke-width", function(d) { return
Math.sqrt(d.value); });
// Add circles for every node in the dataset
var node = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(graph.nodes)
.enter()
.append("g")
.attr("class", "node");
var circles = node.append("circle")
.attr("r", circleRadius)
.attr("fill", function(d) { return color(d.group); })
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended)
);
// Add Supervisory Org Label
var text2 = node.append("text")
.style("text-anchor", "middle")
.attr("y", 40)
.text(function(d) {return d.org });
// Add id (name) label
var text = node.append("text")
.style("text-anchor", "middle")
.attr("y", 55)
.text(function(d) {return d.id });
var images = node.append("svg:image")
.attr("xlink:href", "data:image/jpeg;base64," +
function(d) { return "data:image/jpeg;base64," + d.img;})
//.attr("xlink:href", function(d) { return d.img;})
.attr("x", function(d) { return -25;})
.attr("y", function(d) { return -25;})
.attr("height", 50)
.attr("width", 50);
// make the image grow a little on mouse over and add the text
details on click
var setEvents = images
.on( 'mouseenter', function() {
// select element in current context
d3.select( this )
.transition()
.attr("x", function(d) { return -60;})
.attr("y", function(d) { return -60;})
.attr("height", 100)
.attr("width", 100);
})
// set back
.on( 'mouseleave', function() {
d3.select( this )
.transition()
.attr("x", function(d) { return -25;})
.attr("y", function(d) { return -25;})
.attr("height", 50)
.attr("width", 50);
});
// Basic tooltips
node.append("title")
.text(function(d) { return d.id; });
// Attach nodes to the simulation, add listener on the "tick" event
simulation
.nodes(graph.nodes)
.on("tick", ticked);
// Associate the lines with the "link" force
simulation.force("link")
.links(graph.links)
// Dynamically update the position of the nodes/links as time passes
function ticked() {
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; });
node.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
}
var legend = svg.selectAll(".legend")
//.data(color.domain())
.data([1,2])
.enter().append("g")
.attr("class", "legend")
//.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("rect")
.attr("x", width - 100)
.attr("y", height - 90)
.attr("width", 18)
.attr("height", 18)
.style("fill", function(d) { return color(1) });
legend.append("rect")
.attr("x", width - 100)
.attr("y", height - 113)
.attr("width", 18)
.attr("height", 18)
.style("fill", function(d) { return color(2) });
legend.append("text")
.attr("x", width - 78)
.attr("y", height - 80)
.attr("dy", ".35em")
.style("text-anchor", "begin")
.text("MatchUP")
//.text("MatchUPXXXXXXXXXXXXXXXXXXXX");
legend.append("text")
.attr("x", width - 78)
.attr("y", height - 103)
.attr("dy", ".35em")
.style("text-anchor", "begin")
.text("Connection")
});
// Change the value of alpha, so things move around when we drag a node
function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(0.7).restart();
d.fx = d.x;
d.fy = d.y;
}
// Fix the position of the node that we are looking at
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
// Let the node do what it wants again once we've looked at it
function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
</script>
</body>
the function must be:
function(d) { return "data:image/jpeg;base64," + d.img; }
otherwise you concatenate a string and a function which results in a string and you therefore losse the function.
I am having trouble figuring out how to change the colors of the links between nodes. Well, I know how to change the color straight up, but can't seem to figure out how put an if statement inside of the js code for the Sankey.
I am needing an if statement to make any "values" above 30 to have red links, and if they are below 30, then be grey, or #000.
Here is the red color I am looking for when values are above 30:
Here is the code, and the values I am talking about are on the lines that resemble:
{"source":0,"target":2,"value":33}
var svg = d3.select("svg").attr("style", "outline: thin solid grey;"),
width = +svg.attr("width"),
height = +svg.attr("height");
var formatNumber = d3.format(",.0f"),
format = function(d) { return formatNumber(d) + " TWh"; },
color = d3.scaleOrdinal(d3.schemeCategory10);
var school = {"nodes": [
{"name":"High School"}, // 0
{"name":"Community College"}, // 1
{"name":"Finance"}, // 2
{"name":"Accounting"}, // 3
{"name":"ITS"}, // 4
{"name":"Marketing"}, // 5
{"name":"Analytics"}, // 6
{"name":"Security"}, // 7
{"name":"Consulting"}, // 8
{"name":"Banking"}, // 9
{"name":"Internal"}, // 10
{"name":"Securities"}, // 11
{"name":"Public"}, // 12
{"name":"Audting"}, // 13
{"name":"Internal"}, // 14
{"name":"Retail"}, // 15
{"name":"Technology"}, // 16
{"name":"Strategy"} // 17
],
"links":[
// From HS
{"source":0,"target":2,"value":33},
{"source":0,"target":3,"value":42},
{"source":0,"target":4,"value":74},
{"source":0,"target":5,"value":60},
// From Community College
{"source":1,"target":2,"value":7},
{"source":1,"target":3,"value":13},
{"source":1,"target":4,"value":11},
{"source":1,"target":5,"value":9},
// From Finance
{"source":2,"target":9,"value":16},
{"source":2,"target":10,"value":14},
{"source":2,"target":11,"value":10},
// From Accounting
{"source":3,"target":12,"value":20},
{"source":3,"target":13,"value":12},
{"source":3,"target":7,"value":8},
{"source":3,"target":14,"value":15},
// From Marketing
{"source":5,"target":6,"value":30},
{"source":5,"target":15,"value":39},
// From ITS
{"source":4,"target":8,"value":19},
{"source":4,"target":6,"value":40},
{"source":4,"target":7,"value":20},
{"source":4,"target":12,"value":6},
// From ITS Consulting to Tech and Strat
{"source":8,"target":16,"value":10},
{"source":8,"target":17,"value":9},
]};
var sankey = d3.sankey()
.nodeWidth(15)
.nodePadding(10)
.extent([[1, 1], [width - 1, height - 6]])
.nodeAlign(d3.sankeyLeft);
var link = svg.append("g")
.attr("class", "links")
.attr("fill", "none")
//.attr("stroke", "#000")
.attr("stroke", function(d) {
if (d3.value > 30)
{return "red"}
else {return "#000"};
})
.attr("stroke-opacity", 0.2)
.selectAll("path");
var node = svg.append("g")
.attr("class", "nodes")
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.selectAll("g");
sankey(school);
link = link
.data(school.links)
.enter().append("path")
.attr("d", d3.sankeyLinkHorizontal())
.attr("stroke-width", function(d) { return Math.max(1, d.width); });
// link hover values
link.append("title")
.text(function(d) { return d.source.name + " → " + d.target.name + "\n" + format(d.value); });
node = node
.data(school.nodes)
.enter().append("g");
node.append("rect")
.attr("x", function(d) { return d.x0; })
.attr("y", function(d) { return d.y0; })
.attr("height", function(d) { return d.y1 - d.y0; })
.attr("width", function(d) { return d.x1 - d.x0; })
.attr("fill", function(d) { return color(d.name.replace(/ .*/, "")); })
.attr("stroke", "#000");
node.append("text")
.attr("x", function(d) { return d.x0 - 6; })
.attr("y", function(d) { return (d.y1 + d.y0) / 2; })
.attr("dy", "0.35em")
.attr("text-anchor", "end")
.text(function(d) { return d.name; })
.filter(function(d) { return d.x0 < width / 2; })
.attr("x", function(d) { return d.x1 + 6; })
.attr("text-anchor", "start");
svg.append("text")
.attr("x", 10)
.attr("y", 30)
.attr("class", "graphTitle")
.text("STUDENT CHOICES");
svg.append("text")
.attr("x", width - 80)
.attr("y", height - 10)
.attr("class", "footnote")
.text("data is fictitious");
You should be able to just add a stroke color with a function when you define the links:
link = link
.data(school.links)
.enter().append("path")
.attr("d", d3.sankeyLinkHorizontal())
.attr("stroke-width", function(d) { return Math.max(1, d.width); })
.attr("stroke", d => d.value > 30 ? 'red': 'gray')
Here's a working snippet:
var width = 500,
height =300
var xscale = d3.scaleLinear()
.range([0, width])
.domain([0, 100])
var yscale = d3.scaleLinear()
.range([height, 0])
.domain([0, 6])
var data = [
{student_percentile: 90, rank:1},
{student_percentile: 40, rank:3},
{student_percentile: 30, rank:4},
{student_percentile: 20, rank:5}
]
var svg = d3.select("#chart")
.append("svg")
.attr("style", "outline: thin solid grey;")
.attr("width", width)
.attr("height", height)
.append("g")
/*
var svg = d3.select("svg").attr("style", "outline: thin solid grey;"),
width = +svg.attr("width"),
height = +svg.attr("height");
*/
var formatNumber = d3.format(",.0f"),
format = function(d) { return formatNumber(d) + " TWh"; },
color = d3.scaleOrdinal(d3.schemeCategory10);
var school = {"nodes": [
{"name":"High School"}, // 0
{"name":"Community College"}, // 1
{"name":"Finance"}, // 2
{"name":"Accounting"}, // 3
{"name":"ITS"}, // 4
{"name":"Marketing"}, // 5
{"name":"Analytics"}, // 6
{"name":"Security"}, // 7
{"name":"Consulting"}, // 8
{"name":"Banking"}, // 9
{"name":"Internal"}, // 10
{"name":"Securities"}, // 11
{"name":"Public"}, // 12
{"name":"Audting"}, // 13
{"name":"Internal"}, // 14
{"name":"Retail"}, // 15
{"name":"Technology"}, // 16
{"name":"Strategy"} // 17
],
"links":[
// From HS
{"source":0,"target":2,"value":33},
{"source":0,"target":3,"value":42},
{"source":0,"target":4,"value":74},
{"source":0,"target":5,"value":60},
// From Community College
{"source":1,"target":2,"value":7},
{"source":1,"target":3,"value":13},
{"source":1,"target":4,"value":11},
{"source":1,"target":5,"value":9},
// From Finance
{"source":2,"target":9,"value":16},
{"source":2,"target":10,"value":14},
{"source":2,"target":11,"value":10},
// From Accounting
{"source":3,"target":12,"value":20},
{"source":3,"target":13,"value":12},
{"source":3,"target":7,"value":8},
{"source":3,"target":14,"value":15},
// From Marketing
{"source":5,"target":6,"value":30},
{"source":5,"target":15,"value":39},
// From ITS
{"source":4,"target":8,"value":19},
{"source":4,"target":6,"value":40},
{"source":4,"target":7,"value":20},
{"source":4,"target":12,"value":6},
// From ITS Consulting to Tech and Strat
{"source":8,"target":16,"value":10},
{"source":8,"target":17,"value":9},
]};
var sankey = d3.sankey()
.nodeWidth(15)
.nodePadding(10)
.extent([[1, 1], [width - 1, height - 6]])
.nodeAlign(d3.sankeyLeft);
var link = svg.append("g")
.attr("class", "links")
.attr("fill", "none")
//.attr("stroke", "#000")
.attr("stroke", function(d) {
if (d3.value > 30)
{return "red"}
else {return "#000"};
})
.attr("stroke-opacity", 0.2)
.selectAll("path");
var node = svg.append("g")
.attr("class", "nodes")
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.selectAll("g");
sankey(school);
link = link
.data(school.links)
.enter().append("path")
.attr("d", d3.sankeyLinkHorizontal())
.attr("stroke-width", function(d) { return Math.max(1, d.width); })
.attr("stroke", d => d.value > 30 ? 'red': 'gray')
// link hover values
link.append("title")
.text(function(d) { return d.source.name + " → " + d.target.name + "\n" + format(d.value); });
node = node
.data(school.nodes)
.enter().append("g");
node.append("rect")
.attr("x", function(d) { return d.x0; })
.attr("y", function(d) { return d.y0; })
.attr("height", function(d) { return d.y1 - d.y0; })
.attr("width", function(d) { return d.x1 - d.x0; })
.attr("fill", function(d) { return color(d.name.replace(/ .*/, "")); })
.attr("stroke", "#000");
node.append("text")
.attr("x", function(d) { return d.x0 - 6; })
.attr("y", function(d) { return (d.y1 + d.y0) / 2; })
.attr("dy", "0.35em")
.attr("text-anchor", "end")
.text(function(d) { return d.name; })
.filter(function(d) { return d.x0 < width / 2; })
.attr("x", function(d) { return d.x1 + 6; })
.attr("text-anchor", "start");
svg.append("text")
.attr("x", 10)
.attr("y", 30)
.attr("class", "graphTitle")
.text("STUDENT CHOICES");
svg.append("text")
.attr("x", width - 80)
.attr("y", height - 10)
.attr("class", "footnote")
.text("data is fictitious");
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://unpkg.com/d3-sankey#0"></script>
<div id="chart"></div>
I am pretty sure that this used to work. I wanted to be able to sort by values when bar was clicked and then by name if bar was clicked again.
http://jsbin.com/qimitedohe/edit?js,output
Right now I am getting only names sorting at the bottom but bars are not being selected. What did I screw up?
Thanks!
Ah my css was missing a class name that I used to identify individual bars. I just added an "id" property to all bars and then collected them by that.
barChart.selectAll("#bar")
.data(data)
.enter().append("rect")
.attr("id", "bar")
.attr("fill", "grey")
.attr("x", function (d) { return x(d.name); })
.attr("width", x.rangeBand())
.attr("y", function (d) { return y(d.value); })
.attr("height", function (d) { return height - y(d.value); })
.on("click", function() {sortBars();})
.on("mouseover", function(d){
var delta = d.value;
var xPos = parseFloat(d3.select(this).attr("x"));
var yPos = parseFloat(d3.select(this).attr("y"));
var height = parseFloat(d3.select(this).attr("height"));
var width = parseFloat(d3.select(this).attr("width"));
d3.select(this).attr("fill", "orange");
barChart.append("text")
.attr("x", xPos)
.attr("y", yPos - 3)
.attr("font-family", "sans-serif")
.attr("font-size", "10px")
.attr("font-weight", "bold")
.attr("fill", "black")
.attr("text-anchor", "middle")
.attr("id", "tooltip")
.attr("transform", "translate(" + width/2 + ")")
.text(d.name +": "+ d.value);
})
.on("mouseout", function(){
barChart.select("#tooltip").remove();
d3.select(this).attr("fill", "grey");
});
var sortOrder = true;
var sortBars = function() {
//Flip value of sortOrder
sortOrder = !sortOrder;
var x0 = x.domain(data.sort(sortOrder ? function(a, b) { return b.value - a.value; }
: function(a, b) { return d3.ascending(a.name, b.name); })
.map(function(d) { return d.name; }))
.copy();
barChart.selectAll("#bar")
.sort(function(a, b) { return x0(a.name) - x0(b.name); });
var transition = barChart.transition().duration(750),
delay = function(d, i) { return i * 50; };
transition.selectAll("#bar")
.delay(delay)
.attr("x", function(d) { return x0(d.name); });
transition.select(".x.axis")
.call(xAxis)
.selectAll("g")
.delay(delay);
};
function type(d) {
d.value = +d.value;
return d;
}
I want to display one <circle> and <text> for each node. My code looks like this, having added the suggested code from the answer below. Please note the different
var width = 960,
height = 500;
var color = d3.scale.category10();
var nodes = [],
links = [];
var force = d3.layout.force()
.nodes(nodes)
.links(links)
.charge(-250)
.linkDistance(25)
.size([width, height])
.on("tick", tick);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var node = svg.selectAll(".node")
.data(force.nodes(), function(d) { return d.id;}),
link = svg.selectAll(".link");
var text=svg.selectAll("text") //simply add text to svg
.data(force.nodes())
.enter()
.append("text")
.attr("class", "nodeText")
.attr("font-family", "sans-serif")
.attr("font-size", "10px")
.attr("fill", "red");
function start() {
link = link.data(force.links(), function(d) { return d.source.id + "-" + d.target.id; });
link.enter().insert("line", ".node")
.attr("class", function(d) { return "link " + d.edgeType; })
.attr("id", function(d) { return d.source.id + "-" + d.target.id; });
link.exit().remove();
v1: <line>s exist and are displayed, no <circle>s or <text> exist
var g = node.enter().append("g");
g.append("circle")
.attr("class", function(d) { return "node " + d.id; })
.attr("id", function(d) { return d.id; })
.attr("r", 8)
.on("click", nodeClick);
g.append("text")
.text(function(d) {return d.id; });
/v1
v2: <line>s and <circle>s exist and are displayed. <text>s exist within <circle>s but aren't displayed
node = node.data(force.nodes(), function(d) { return d.id;});
node.enter()
.append("circle")
.attr("class", function(d) { return "node " + d.id; })
.attr("id", function(d) { return d.id; })
.attr("r", 8)
.on("click", nodeClick);
node.append("text")
.text(function(d) {return d.id; });
/v2
node.exit().remove();
force.start();
}
function nodeClick() {
var node_id = event.target.id;
handleClick(node_id, "node");
}
function tick() {
text.attr("dx", function(d) { return d.x+5; })
.attr("dy", function(d) { return d.y+5; })
.text(function(d){return d.id});
node.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return 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; });
}
Simply add text element as following in your js
var text=svg.selectAll("text") //simply add text to svg
.data(Your_nodes_array)
.enter()
.append("text")
.attr("class", "nodeText")
.attr("font-family", "sans-serif")
.attr("font-size", "10px")
.attr("fill", "red");
And inside tick do this :
text.attr("dx", function(d) { return d.x+5;}) //to keep it away from node
.attr("dy", function(d) { return d.y+5; })
.text(function(d){return d.id});
This will solve circle and text problems as this time we are adding text directly now