Is d3 edge labeling possible? - html

Is it possible to have edge labels in a graph using d3? I've looked over the examples and some of the documentation, and I can see where node labeling is possible, but edge labeling is not explicitly mentioned anywhere (that I can find).

Following other peoples examples I
add a path alongside every edge,
add text to the edges
bind that text to a textpath which is referencing the path along the edge
This example is using the above ideas: http://bl.ocks.org/jhb/5955887

The short answer is "yes", but there's no explicit support for it. You'll have to determine the position of the label yourself. One way of doing this would be to attach the label to the start node and translate it by half the distance to the target node, plus some offset to prevent it overlapping the line. For more elaborate edges (e.g. curves) it would be more difficult.

I tried the text on a path option, but it is quite involved and didn't result in the appearance I wanted. Unfortunately, I didn't check it into the local git repository. This comes from my Eclipse history (thanks Eclipse developers). You'll have to change this code snippet to your own data structure, but I'll hope it is of some help. In particular, notice the adding of the id value to the lines selection and then reusing it in the percentages selection by using the xlink:href attribute. Also, I appended a tspan in the textpath to move the text down by a couple of pixels so it appears over the path instead of on it.
function drawLines(links) {
var diagonal = d3.svg.diagonal();
var format = d3.format(".1%");
var linkKey = function(l) {return l.target.key; };
var lines = linesGroup.selectAll("path").data(links, linkKey);
lines.enter()
.append("path")
.on("mouseover", select)
.on("mouseout", unselect)
.attr("d", diagonal)
.attr("id", function (l) { return "interaction-path-target-" + l.target.key; })
.style("stroke-width", 0.000001);
lines.exit()
.transition().duration(500)
.style("stroke-width", 0.000001)
.remove();
lines.transition()
.delay( function(d, i) { return i * 100; })
.duration(500)
.style("stroke-width", function(d) { return d.weight == 0 ? 0.000001 : d.weight / 1000; })
.attr("d", diagonal);
var percentages = linesGroup.selectAll("text").data(links, linkKey);
percentages.enter()
.append("text")
.attr("opacity", 1)
.append("svg:textPath")
.attr("startOffset", "70%")
.attr("xlink:href",
function(l) {
return "#interaction-path-target-" + l.target.key;
})
.append("svg:tspan")
.attr("dy", 3)
.attr("class", "percentageText")
percentages.exit()
.transition().duration(500)
.attr("opacity", 0)
.remove();
percentages
.transition()
.delay( function(d, i) { return i * 100; })
.duration(500)
.attr("opacity", 1);
percentages.select(".percentageText").text(function(d) {
var newvalue = d.weight ?
d.incomming ? percentageIn(d.weight) : percentageOut(d.weight) : 0;
return format(newvalue);
});
}

Related

CSV Data Fail to Load in IE but loads in Chrome, Firefox, and Edge in my SharePoint Site Using D3

I have created a SharePoint site. In the site I’m using D3 to load data from a csv file. The data loads great in all the browsers except IE (I’m using IE 11). In the console I’m getting the following errors:
SEC7120: Origin https://intelshare.intelink.gov not found in Access-Control-Allow-Origin header.
templates.aspx
SCRIPT5007: Unable to get property 'append' of undefined or null reference
templates.aspx (836,1)
I loaded the CSV file in SharePoint’s assets folder, and I link to the csv inside that folder. Below is my script I used to load the data. I don’t know why IE through the above errors and not load like the other browsers. I appreciate any help/advice you can provide.
jQuery(document).ready(function(){
var width = 1000, height = 650;
var gallery = d3.select(".gridImage").append("div")
.attr('class', 'imgDiv')
.attr("preserveAspectRatio", "xMidYMid")
.attr("viewBox", "0 0 1000 650")
.attr("width", "100%")
.attr("height", "100%");
d3.csv('https://intelshare.intelink.gov/sites/nbib/ep/SiteAssets/SitePages/Test%20Page/data.csv', function(error, data){
if (error){
alert("Data didn't load, Refresh your browser");
}else{
var container = gallery.selectAll('.imgContainer')
.data(data, function(d) { return d.id; });
container.enter()
.append('div')
.attr('class',function(d) { return 'imgContainer imgContainer' + d.id});
container.selectAll('.picture')
.data(function(d) { return [d]; })
.enter()
.append("a")
.attr('class',function(d) { return 'fancybox-thumb hidden iframe fancybox'})
.attr("rel","fancybox-button")
.attr("href",function(d) {return 'https://intelshare.intelink.gov/sites/nbib/ep/SiteAssets/SitePages/Test%20Page/'+ d.sampleImages})
.attr("data-fancybox",function(d) {return d.group})
.attr("title", function(d) {return d.message})
.append('img')
.attr("class", function(d) { return "picture picture" + d.id})
.attr('width', "100%")
.attr('height', "auto")
.attr('src', function(d) { return 'https://intelshare.intelink.gov/sites/nbib/ep/SiteAssets/SitePages/Test%20Page/'+ d.imgsrc; })
}
container.append("text")
.attr("x", (width / 2))
.attr("y", 0 - (300 / 2))
.attr("text-anchor", "middle")
.style("font-size", "12px")
.style("color", "black")
.style("font-family", "Arial, Helvetica, sans-serif")
.text(function(d) { return d.imageTitle; });
container.append("a")
.attr("href",function(d) {return 'https://intelshare.intelink.gov/sites/nbib/ep/SiteAssets/SitePages/Test%20Page/'+ d.download;})
.attr("download",function(d) {return d.download;})
.append("rect")
.attr("class","downLoadBtn")
.style("text-decoration", "none")
.text("Select to download");
});
});
I was able to figure this out. I change my path to the CSV file from and absolute path to an relative path. After I made that change my data loads in all browsers. Below is how I modified my path:
Changed from this: "https://intelshare.intelink.gov/sites/nbib/ep/SiteAssets/SitePages/Test%20Page/data.csv"
Changed to this: "../SiteAssets/SitePages/Test%20Page/data.csv"
I hope this helps someone using d3.csv in sharePoint.

Trouble Binding JSON subarrays in d3.js

I guess anyone who monitors the d3 questions is getting used to seeing me this week (this is my first time using d3) so here goes another question. I am fetching data via php/ajax but I have placed a sample as hard code below. I wish to bind the fineBinSummary data to a bar chart but can't seem to figure out how. When I inspect the html it makes it as far as setting up the svg and then stops when I start to try to bind data to it.
I successfully bound the detail data (code not shown, just the JSON) but can't get the fineBinSummary data to bind in the same manner. I also will need to extract the length of the fineBinSummary array (you can see that I've just made it the width / 5 but there won't always be 5 bars in the chart. Any assistance is much appreciated.
(document).ready(function() {
var data = [
{"Id":"93439440411",
"detail":[{"fineBin":"UNCLASSIFIED","x":"-142000.0","y":"-21000.0","imageId":null,"serverId":null,"fileSpec":null,"color":"HotPink"},
{"fineBin":"UNCLASSIFIED","x":"-142000.0","y":"16000.0","imageId":null,"serverId":null,"fileSpec":null,"color":"HotPink"},
{"fineBin":"UNCLASSIFIED","x":"-141000.0","y":"-15000.0","imageId":null,"serverId":null,"fileSpec":null,"color":"HotPink"},
{"fineBin":"UNCLASSIFIED","x":"-141000.0","y":"24000.0","imageId":null,"serverId":null,"fileSpec":null,"color":"HotPink"},
{"fineBin":"UNCLASSIFIED","x":"-141000.0","y":"27000.0","imageId":null,"serverId":null,"fileSpec":null,"color":"HotPink"},
{"fineBin":"UNCLASSIFIED","x":"-140000.0","y":"-15000.0","imageId":null,"serverId":null,"fileSpec":null,"color":"HotPink"}],
"fineBinSummary":[{"fineBin":"UNCLASSIFIED","count":8212},{"fineBin":"SMALL_PARTICLE","count":104},{"fineBin":"RESIDUE","count":68},
{"fineBin":"FALSE","count":16},{"fineBin":"258","count":12}]},
{"Id":"93439440419",
"detail":[{"fineBin":"UNCLASSIFIED","x":"-142839.40900000001","y":"20448.394","imageId":null,"serverId":null,"fileSpec":null,"color":"HotPink"},
{"fineBin":"UNCLASSIFIED","x":"-142546.65599999999","y":"26731.720000000001","imageId":null,"serverId":null,"fileSpec":null,"color":"HotPink"},
{"fineBin":"UNCLASSIFIED","x":"-142499.136","y":"-24443.516","imageId":null,"serverId":null,"fileSpec":null,"color":"HotPink"},
{"fineBin":"UNCLASSIFIED","x":"-142267.68799999999","y":"32411.870999999999","imageId":null,"serverId":null,"fileSpec":null,"color":"HotPink"},
{"fineBin":"UNCLASSIFIED","x":"-142000.0","y":"-33000.0","imageId":null,"serverId":null,"fileSpec":null,"color":"HotPink"}],
"fineBinSummary":[{"fineBin":"UNCLASSIFIED","count":8212},{"fineBin":"SMALL_PARTICLE","count":104},{"fineBin":"RESIDUE","count":68},
{"fineBin":"FALSE","count":16},{"fineBin":"258","count":12}]}]
//Constants
var squareSide = 400;
var height = squareSide / 2.0;
var barWidth = squareSide / 5;
//begin building bootstrap courousel stack
var slides = d3.select(".carousel-inner").selectAll('div')
.data(data)
.enter()
.append("div")
.attr("class","item")
.append("div")
.attr("class","container-fluid")
.append("div")
.attr("class","row");
//Make the first slide the active slide
d3.selectAll(".item").classed('active', function(d, i) { return i == 0; });
//Build the image side of the slide
var imageSide = slides.append("div")
.attr("class","col-lg-6 wafer-and-pareto");
//build final location for div that will hold the histogram
var paretoBox = imageSide.append("div")
.attr("class","row")
.append("div")
.attr("class","col-lg-12")
.append("div")
.attr("class","pareto-box")
.append("svg")
.attr("class","chart")
.attr("width",squareSide)
.attr("height",squareSide / 2.0);
Seems to be working up to this point based on reviewing the html but does not successfully execute the code below:
var bar = paretoBox.selectAll("g")
.data(function(d) {return data.fineBinSummary})
.enter()
.append("g")
.attr("transform",function(d,i) {
return "translate(" + i * barWidth + ",0)";
});
bar.append("rect")
.attr("y",function(d) {
return y(d.count);
})
.attr("height",function(d) {
return height - y(d.count)
})
.attr("width",barWidth - 1);
var y = d3.scale.linear()
.range([height,0])
.domain([0,d3.max(data.fineBinSummary,function(d) {
return d.count;
})]);
})
There are just two small problems with your code. First, in your nested selection, you are referencing the original data instead of the part bound to the current element. Instead of
var bar = paretoBox.selectAll("g")
.data(function(d) {return data.fineBinSummary})
use
var bar = paretoBox.selectAll("g")
.data(function(d) {return d.fineBinSummary})
Second, the max element for your scale is determined incorrectly -- data doesn't have a fineBinSummary member. The code should look like this:
var y = d3.scale.linear()
.range([height,0])
.domain([0, d3.max(data, function(d) {
return d3.max(d.fineBinSummary, function(e) { return e.count; });
})]);

D3 add graticule and geojson

When adding D3 graticule first and then calling d3.json , the very first feature within json doesn't show on the map. If I add d3.graticule within d3.json after appending paths, graticule will be drawn on top of the features, which is not what I wanted. Here is my JavaScript file:
$(document).ready(function (){
$('.aToolTip').tooltip();
$('.aPopOver').popover();
// D3 Mapping Starts Here
var h = $('#myMap').height();
var w = $('#myMap').width();
console.log("h = " + h , ", w = " + w);
$('svg').attr('width', w)
.attr('height', h);
var aSVG = d3.select("#aSVGElement");
var aProjection = d3.geo.mercator()
.rotate([-135, 24])
.scale(600)
.translate([w/2, h/2]);
var path = d3.geo.path()
.projection(aProjection);
var graticule = d3.geo.graticule()
.step([10, 10]);
aSVG.append("path")
.datum(graticule)
.attr("class", "graticule")
.attr("d", path);
d3.json("js/Australia.json", function(error, json){
aSVG.selectAll("path")
.data(json.features)
.enter()
.append("path")
.attr("d", path);
}); // end d3.json
});// end document ready
The first feature doesn't show up if you're drawing the grid first because you're selecting paths, of which the grid is one. This means that the first data element (i.e. the first feature) is matched to the existing path and not part of the .enter() selection.
To fix this, simply assign a class to your feature paths and select accordingly:
aSVG.selectAll("path.feature")
.data(json.features)
.enter()
.append("path")
.attr("class", "feature")
.attr("d", path);

d3.js - Plotting points on zoomable, clickable map

I am trying to adapt the map found at http://bl.ocks.org/mbostock/2206340 and plot some points on it. I have the data I am trying to use (latitudes and longitudes) in a csv file called data.csv, which is formatted like this:
lon_0,lat_0,lon_1,lat_1
-122.1430195,37.4418834,-122.415278,37.778643
-122.1430195,37.4418834,-122.40815,37.785034
-122.4194155,37.7749295,-122.4330827,37.7851673
-122.4194155,37.7749295,-122.4330827,37.7851673
-118.4911912,34.0194543,-118.3672828,33.9164666
-121.8374777,39.7284944,-121.8498415,39.7241178
-115.172816,36.114646,-115.078011,36.1586877
The modification to the code I made is contained in the following snippet
d3.json("us-states.json", function(json) {
d3.csv("data.csv", function(data) {
dataset = data.map(function(d) { return [ +d["lat_0"], +d["lon_0"] ]; });
console.log(data)
states.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cx", function(d) {
return projection([d["lon_0"], d["lat_0"] ])[0];
})
.attr("cy", function(d) {
return projection([d["lon_0"],d["lat_0"] ])[1];
})
.attr("r", 5)
.style("fill", "red");
});
states.selectAll("path")
.data(json.features)
.enter().append("path")
.attr("d", path)
.on("click", click);
});
The map renders fine, but the plots are not shown. Any idea what could be wrong here? I've tried a bunch of different things, I'm not sure if it is a matter of the states map covering the points, or if something is wrong with the projection...
The paths drawn in the example are filled, i.e. anything behind them will be obscured. SVG elements are drawn in the order they are specified. In your case, the circles are drawn first and then the paths for the states, thereby obscuring the circles.
It works when you draw the state paths before drawing the circles.

Sankey diagram transition

I would like to know if there is an easy way to modify the Sankey diagram example so that there is smooth transition to new data. For example, imagine I have different datafiles (energy1.json, energy2.json ...) how could d3 plot a Sankey diagram for the first dataset, then waits and later on rearanges the boxes disposition to represent the second dataset?
This is possible. Here's one approach using a csv file. Working sankey here: https://www.betterment.com/resources/investment-strategy/portfolio-management/portfolio-diversification/
Define a global array outside of your d3.csv call.
var portfolioValues = [];
When parsing the csv to create the node/link structure, push values to your global array.
d3.csv("etf-geo.csv", function(error, data) {
graph = {"nodes" : [], "links" : []};
data.forEach(function (d, i) {
var item = { source: d.source, target: d.target, values: [] };
for (var j=0; j < 101; j++) {
item.values.push(d['value'+j.toString()]);
}
portfolioValues.push(item);
graph.nodes.push({ "name": d.source });
graph.nodes.push({ "name": d.target });
graph.links.push({
source: portfolioValues[i].source,
target: portfolioValues[i].target,
value: portfolioValues[i].values[startingAllocation]
});
});
//this handy little function returns only the distinct / unique nodes
graph.nodes = d3.keys(
d3.nest()
.key(function (d) { return d.name; })
.map(graph.nodes)
);
// it appears d3 with force layout wants a numeric source and target
// so loop through each link replacing the text with its index from node
graph.links.forEach(function (d, i) {
graph.links[i].source = graph.nodes.indexOf(graph.links[i].source);
graph.links[i].target = graph.nodes.indexOf(graph.links[i].target);
portfolioValues[i].source = graph.links[i].source;
portfolioValues[i].target = graph.links[i].target;
});
// now loop through each nodes to make nodes an array of objects
// rather than an array of strings
graph.nodes.forEach(function (d, i) {
graph.nodes[i] = { "name": d };
});
// construct sankey
sankey
.nodes(graph.nodes)
.links(graph.links)
.layout();
Listen for a change and pass user input to your update function.
$(".sankey-slider").bind("slider:changed", function (event, data) {
slideValue = data.value;
updateData(parseInt(slideValue));
});
Create a temporary array and retrieve the correct values from the global array. Call the sankey functions to recalculate the layout.
var newLinks = [];
portfolioValues.forEach(function(p, i) {
newLinks.push({
source: p.source,
target: p.target,
value: p.values[allocation]
});
});
graph.links = newLinks;
sankey
.nodes(graph.nodes)
.links(graph.links)
.size([width, height])
.layout();
Select each element that needs to be changed and pass the new data values.
d3.selectAll(".link")
.data(graph.links)
.attr("d", path)
.attr("id", function(d,i){
d.id = i;
return "link-"+i;
})
.style("stroke-width", function(d) { return Math.max(1, d.dy); })
.sort(function(a, b) { return b.dy - a.dy; });
d3.selectAll(".node").attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")"; });
d3.selectAll("rect")
.attr("height", function(d) { return d.dy; })
.on("mouseover",highlight_node_links)
.on("mouseout",onNodeMouseout);
Working sankey here:
https://www.betterment.com/resources/investment-strategy/portfolio-management/portfolio-diversification/
Since the automatic positioning of nodes includes a part which tries to minimize link distance in a connected graph which is an np optimization problem, any kind of optimizer can potentially jump from one minimum to another leading to a jump in layout. So a guaranteed smooth transition wont be possible.
The closest possible solution would probably be to linearly interpolate between the two input data sets and thereby generate a series of graphs which (depending on the data) more or less smoothly transition from one two the other.
Hope this helps.