I have created a bubble chart with zoom feature on JSfiddle
var r = 500,
h = 500,
format = d3.format(",d"),
fill = d3.scale.category20c();
var bubble = d3.layout.pack().sort(null).size([r,h]);
var vis = d3.select("#chart").append("svg")
.attr("class", "bubble")
.call(d3.behavior.zoom().on("zoom", redraw))
.append("g").attr("class", "group2")
d3.json("cantGetRidOfThis", function() {
var node = vis.selectAll("g.node")
.data(bubble.nodes(flat).filter(function(d) {return !d.children;}))
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) {return "translate(" + d.x + "," + d.y + ")";});
node.append("title").text(function(d) {return d.className + ": " + format(d.value);});
node.append("circle")
.attr("r", function(d) {return d.r;})
.attr("class", "nodecircle")
.style("fill", '#ff4719')
.attr("data-classname", function(d) {return d.className;});
node.append("text")
.attr("text-anchor", "middle")
.attr("class", "nodetext")
.attr("data-classname", function(d) {return d.className;})
.attr("style", function(d) {return "font-size:" + d.r/5;})
.attr("data-classname", function(d) {return d.className;})
.each(function(d, i) {
var nm = d.className;
var arr = nm.replace(/[\(\)\\/,-]/g, " ").replace(/\s+/g, " ").split(" "),arrlength = (arr.length > 7) ? 8 : arr.length;
d3.select(this).attr('y',"-" + (arrlength/2) + "em");
//if text is over 7 words then ellipse the 8th
for(var n = 0; n < arrlength; n++) {
var tsp = d3.select(this).append('tspan').attr("x", "0").attr("dy", "1em").attr("data-classname", nm);
if(n === 7) {
tsp.text("...");
} else {
tsp.text(arr[n]);
}
}
});
});
function clickOnCircleFunc(el){
var selection = el.target.__data__.className;
//bubble select
$('.nodecircle').each(function (id, v) {
var $this = $(this),d_nm = $this.attr('data-classname');
if (d_nm === selection && $this.attr('data-selected') !== 'y') {
$this.attr('data-selected','y').css('fill', '#3182bd');
}
else {
$this.attr('data-selected','n').css('fill', '#ff4719');
}
});
}
function redraw() {
vis.attr("transform", "translate(" + d3.event.translate + ")" + " scale(" + d3.event.scale + ")");
}
Above code is working fine with all what needed but one thing which am not able to figure out is to populate the chart in centre of the div id="chart".Currently chart is displayed on left .
How can the chart be populate in centre of the div
Related
I have made a map which shows some circles and on click, the lines are shown. The map is overlayed on a leaflet basemap.
The map works fine. Here is how I projected the map:
d3.csv("going.csv", function(data) {
going = data;
d3.json("lor_migration.geojson", function(json) {
//Projection
transform = d3.geo.transform({
point: projectPoint
});
path = d3.geo.path().projection(transform);
function projectPoint(x, y) {
var point = map.latLngToLayerPoint(new L.LatLng(y, x));
this.stream.point(point.x, point.y);
}
//Bind data and create one path per GeoJSON feature
var feature = g.selectAll("path")
.data(json.features)
.enter()
.append("path")
.attr("id", function(d) {
return d.properties.state;
})
.attr("d", path)
.attr("stroke-width", 0.5)
.attr("fill-opacity", "0.5")
.style("stroke", "#666")
.style("fill", "#fff");
map.on("viewreset", reset);
reset();
//Reset function
function reset() {
var bounds = path.bounds(json);
topLeft = bounds[0],
bottomRight = bounds[1];
svg.attr("width", bottomRight[0] - topLeft[0])
.attr("height", bottomRight[1] - topLeft[1])
.style("left", topLeft[0] + "px")
.style("top", topLeft[1] + "px");
g.attr("transform", "translate(" + -topLeft[0] + "," + -topLeft[1] + ")");
feature.attr("d", path);
}
Here is how the circles are drawn on the map:
//Create circles for net migration
var circles = g.selectAll("circle")
.data(json.features)
.enter().append("circle")
.attr("cx", function(d) {
d.centroid = path.centroid(d);
return d.centroid[0];
})
.attr("cy", function(d) {
return d.centroid[1];
})
.attr("r", function(d) { //circle size
console.log("Value: " + value);
var diff = d.properties.total_imm - d.properties.total_emm;
return circleSize((Math.abs(diff)/Math.PI));
})
.attr("class", "circ")
.attr("id", function(d) {
return d.abbrev;
})
.attr("fill", function(d) { //circle color
var diff = d.properties.total_imm - d.properties.total_emm;
if (diff > 0) {
return "#65a89d";
}
else {
return "#a96a46";
}
})
.attr("fill-opacity", "0.5")
.attr("stroke", "#fff")
.attr("stroke-weight", "0.5")
.on("mouseover", function(d) {
return toolOver(d, this);
})
.on("mousemove", function(d) {
var m = d3.mouse(this);
mx = m[0];
my = m[1];
return toolMove(mx, my, d);
})
.on("mouseout", function(d) {
return toolOut(d, this);
})
.on("click", function(d) {
clicked(d)
});
Here is the onclick function for the lines:
function clicked(selected) {
var selname = selected.id;
var homex = selected.centroid[0];
var homey = selected.centroid[1];
g.selectAll(".goingline")
.attr("stroke-dasharray", 0)
.remove()
g.selectAll(".goingline")
.data(going)
.enter().append("path")
.attr("class", "goingline")
.attr("d", function(d, i) {
var finalval = coming[i][selname] - going[i][selname];
come = coming[i][selname];
go = going[i][selname];
try {
var theState = d3.select("#" + d.abbrev).datum();
if (!isNaN(finalval)) {
var startx = theState.centroid[0];
var starty = theState.centroid[1];
if (finalval > 0)
return "M" + startx + "," + starty + " Q" + (startx + homex) / 2 + " " + (starty + homey) / 1.5 + " " + homex + " " + homey;
else
return "M" + homex + "," + homey + " Q" + (startx + homex) / 2 + " " + (starty + homey) / 5 + " " + startx + " " + starty;
}
}
catch (err) {
console.log(err)
console.log('No datum found for ' + d.abbrev)
}
})
.call(transition)
.attr("stroke-width", function(d, i) {
var finalval = coming[i][selname] - going[i][selname];
return 0.75//(Math.abs(finalval));
})
.attr("stroke", function(d, i) {
var finalval = coming[i][selname] - going[i][selname];
if (finalval > 0) {
return "#65a89d";
}
else {
return "#a96a46";
}
})
.attr("fill", "none")
.attr("opacity", 0.5)
.attr("stroke-linecap", "round")
.on("mouseover", function(d) {
return toolOver2(d, this);
})
.on("mousemove", function(d, i) {
var m = d3.mouse(this);
mx = m[0];
my = m[1];
return toolMove2(mx, my, selname, d.state, coming[i][selname], going[i][selname]);
})
.on("mouseout", function(d) {
return toolOut2(d, this);
});
}
The problem is the projection does not work for circles and the lines. Can you please suggest a solution.
Thanks
EDITED:
So this is an attempt I made on trying to fix the issue: There are still some issues
//Create SVG element
var svg = d3.select(map.getPanes().overlayPane)
.append("svg")
var g = svg.append("g").attr("class", "leaflet-zoom-hide");
var svgCircles = svg.append("g").attr("class", "leaflet-zoom-hide");
var coming, going;
var by_name = {};
//Define the coming and going of csv
d3.csv("coming.csv", function(data) {
coming = data;
});
d3.csv("going.csv", function(data) {
going = data;
d3.json("lor_migration.geojson", function(json) {
//Projection
transform = d3.geo.transform({
point: projectPoint
});
path = d3.geo.path().projection(transform);
/* json.features.forEach(function(d) {
d.LatLng = new L.LatLng(d.geometry.coordinates[1], d.geometry.coordinates[0]);
});*/
function projectPoint(x, y) {
var point = map.latLngToLayerPoint(new L.LatLng(y, x));
this.stream.point(point.x, point.y);
}
for (var i = 0; i < data.length; i++) {
by_name[ data[i].state ] = {};
for (var propt in data[i]) {
by_name[ data[i].state ][propt] = +data[i][propt];
}
by_name[ data[i].state ].abbrev = data[i].abbrev;
by_name[ data[i].state ].state = data[i].state;
}
//Find the corresponding state inside the GeoJSON
json.features.forEach( function (j) {
var jsonState = j.properties.name;
if ( by_name[jsonState] ) {
j.properties.state = by_name[jsonState].state;
j.id = by_name[jsonState].state;
j.abbrev = by_name[jsonState].abbrev;
j.properties.total_imm = by_name[jsonState].total_imm;
j.properties.total_emm = by_name[jsonState].total_emm;
}
else {
console.log('No data for ' + jsonState)
}
})
//Bind data and create one path per GeoJSON feature
var feature = g.selectAll("path")
.data(json.features)
.enter()
.append("path")
.attr("id", function(d) {
return d.properties.state;
})
.attr("d", path)
.attr("stroke-width", 0.5)
.attr("fill-opacity", "0.5")
.style("stroke", "#666")
.style("fill", "#fff");
//Net migration circles
var circles = svgCircles.selectAll("circle")
.data(json.features)
.enter().append("circle")
.attr("cx", function(d) {
d.centroid = path.centroid(d);
return d.centroid[0];
})
.attr("cy", function(d) {
return d.centroid[1];
})
.attr("r", function(d) { //circle size
console.log("Value: " + value);
var diff = d.properties.total_imm - d.properties.total_emm;
// if (diff > 200){
return circleSize((Math.abs(diff)/Math.PI));
// }
})
.attr("class", "circ")
.attr("id", function(d) {
return d.abbrev;
})
.attr("fill", function(d) { //circle color
var diff = d.properties.total_imm - d.properties.total_emm;
if (diff > 0) {
return "#65a89d";
}
else {
return "#a96a46";
}
})
.attr("fill-opacity", "0.5")
.attr("stroke", "#fff")
.attr("stroke-weight", "0.5")
.on("mouseover", function(d) {
return toolOver(d, this);
})
.on("mousemove", function(d) {
var m = d3.mouse(this);
mx = m[0];
my = m[1];
return toolMove(mx, my, d);
})
.on("mouseout", function(d) {
return toolOut(d, this);
})
.on("click", function(d) {
clicked(d)
});
map.on("viewreset", reset);
reset();
//Reset function
function reset() {
var bounds = path.bounds(json);
topLeft = bounds[0],
bottomRight = bounds[1];
svg.attr("width", bottomRight[0] - topLeft[0])
.attr("height", bottomRight[1] - topLeft[1])
.style("left", topLeft[0] + "px")
.style("top", topLeft[1] + "px");
g.attr("transform", "translate(" + -topLeft[0] + "," + -topLeft[1] + ")");
feature.attr("d", path);
circles.attr("cx", function(d) {
return map.latLngToLayerPoint(d.LatLng).x;
});
circles.attr("cy", function(d) {
return map.latLngToLayerPoint(d.LatLng).y;
});
svgCircles.attr("transform", "translate(" + -topLeft[0] + "," + -topLeft[1] + ")");
}
});
});
The problem is if I try to use this function, then nothing shows:
json.features.forEach(function(d) {
d.LatLng = new L.LatLng(d.geometry.coordinates[1],
d.geometry.coordinates[0]);
});
Also I am getting the error:
TypeError: t is undefined, can't access property "lat" of it
Help please
So I fixed the circles at least. The problem was I was not returning the correct attributes. Should'Ve returned the centroids and not log lat. Here is the updated version of my reset function:
//Reset function
function reset() {
//Defining Bounds
var bounds = path.bounds(json);
topLeft = bounds[0],
bottomRight = bounds[1];
svg.attr("width", bottomRight[0] - topLeft[0])
.attr("height", bottomRight[1] - topLeft[1])
.style("left", topLeft[0] + "px")
.style("top", topLeft[1] + "px");
//Recalculating Projections for Json map
g.attr("transform", "translate(" + -topLeft[0] + "," + -topLeft[1] + ")");
feature.attr("d", path);
//Recalculating Projections for Circles
circles.attr("cx", function(d) {
d.centroid = path.centroid(d);
return d.centroid[0];
});
circles.attr("cy", function(d) {
return d.centroid[1];
});
g.attr("transform", "translate(" + -topLeft[0] + "," + -topLeft[1] + ")");
circles.attr("d", path);
}
I still need to figure out the projection of the lines. Any suggestions on that?
I have a line d3.js line graph pictured below:
Two oddities. First notice the vertical mouse line. Notice the hover line at the green line. The data entry 72.3.... is not correct for that date. It is the correct value near 0 on the x axis. The text data is basically reversed to the timeScale(). The green line is plotted correctly.
Second, why does the green line apeear to have shadows? The code for the hover line is below:
var enableMouseOver = function () {
mouseG = chart.append("svg:g")
.attr("class", "mouse-over-effects");
//Vertical line from height to y0
mouseG.append("path")
.attr("class", containerId + "-mouse-line")
.style("stroke", "black")
.style("stroke-width", "1px")
.style("opacity", "0");
lines = document.getElementsByClassName(containerId + "-line");
mousePerLine = mouseG.selectAll('.' + containerId + '-mouse-per-line')
.data(dataset)
.enter()
.append("g")
.attr("class", containerId + '-mouse-per-line');
/*
mousePerLine.append("circle")
.attr("r", 7)
.style("stroke", "black")
.style("fill", "none")
.style("opacity", "0");
*/
mousePerLine.append("text")
.attr("transform", "translate(10,3)");
mouseG.append("svg:rect")
.attr('width', width)
.attr('height', height)
.attr('fill', 'none')
.attr('pointer-events', 'all')
.on("mouseout", function () {
d3.select("." + containerId + "-mouse-line")
.style("opacity", "0");
//d3.selectAll("." + containerId + "-mouse-per-line circle")
// .style("opacity", "0");
d3.selectAll("." + containerId + "-mouse-per-line text")
.style("opacity", "0");
})
.on("mouseover", function () {
d3.select("." + containerId + "-mouse-line")
.style("opacity", "1");
//d3.selectAll("." + containerId + "-mouse-per-line circle")
// .style("opacity", "1");
d3.selectAll("." + containerId + "-mouse-per-line text")
.style("opacity", "1");
})
.on("mousemove", function () {
var mouse = d3.mouse(this);
d3.select("." + containerId + "-mouse-line")
.attr("d", function () {
var d = "M" + mouse[0] + "," + height;
d += " " + mouse[0] + "," + 0;
return d;
});
d3.selectAll("." + containerId + "-mouse-per-line")
.attr("transform", function (d, i) {
var xDate = xScale.invert(mouse[0]),
bisect = d3.bisector(function (d) {
return d.date;
}).right;
var idx = bisect(d.available, xDate);
var beginning = 0,
end = lines[i].getTotalLength(),
target = null;
while (true) {
var pos = lines[i].getPointAtLength(target);
if ((target === end || target === beginning) && pos.x !== mouse[0]) {
break;
}
else if (pos.x > mouse[0]) beginning = target;
else break;
}
var displayFormat;
if (interval === 'weekly' || interval === 'monthly') {
displayFormat = d3.timeFormat("%m/%d");
}
else {
displayFormat = d3.timeFormat("%H:%M:%S");
}
d3.select(this).select('text')
.text(displayFormat(xDate) + " " + yScale.invert(pos.y).toFixed(4))
.attr("font-size", ".5em");
if (i === 1 && (interval === 'daily' || interval === 'hourly')) {
return "translate(" + (mouse[0] - 80) + "," + pos.y + ")";
}
else if (i === 1 && (interval === 'weekly' || interval === 'monthly')) {
return "translate(" + (mouse[0] - 65) + "," + pos.y + ")";
}
else {return "translate(" + mouse[0] + "," + pos.y + ")";}
});
});
}
Thanks in advance!
I created tree layout as in JSFiddle example http://jsfiddle.net/oo66o0q0/15/.
Requirement is path should highlighted in red and with extra width when user click on node's right click menu "Highlight Route" option.
This is working in chrome correctly but in IE highlighted route color becomes black.
If I remove markers then it works in IE as well.
How to resolve this issue in IE but not removing markers?
function treeInitialize(graphData){
diagramLayout = d3.select("#diagramLayout")
.attr("id", "diagramLayout")//set id
.attr("width", width)//set width
.attr("height", height)//set height
.append("g")
.attr("transform", "translate(" + 20 + "," + 20 + ")")
markerRefx = 40;
var data2 = graphData.links.filter(function(l){
if(l.target == undefined && l.source == undefined){
return false;
}else{
return true;
}
});
data2.push(JSON.parse('{"target":"glossforArrow","source":""}'))
var treeData = d3.stratify().id(function(d){ return d.target; }).parentId(function(d) {
return d.source;
})(data2)
nodes = d3.hierarchy(treeData, function(d) {return d.children;});
var levelWidth = [1];
var childCount = function(level, n) {
if(n.children && n.children.length > 0) {
if(levelWidth.length <= level + 1) levelWidth.push(0);
levelWidth[level+1] += n.children.length;
n.children.forEach(function(d) {
childCount(level + 1, d);
});
}
};
childCount(0, nodes);
newHeight = d3.max(levelWidth) * 100;
var tree = d3.tree().size([height, width])
tree.size([newHeight,height/2]);
tree.separation(function (a, b) {
return a.parent == b.parent ? 50 : 100;
});
nodes = tree(nodes);
treeLayout(nodes);
function treeLayout(nodes){
var node = diagramLayout.selectAll(".node");
node = node.data(nodes.descendants());
var link = diagramLayout.selectAll(".link")
.data(nodes.descendants().slice(1))
.enter().append("path")
.attr("class", "link")
.attr("fill", "none")
.attr("stroke", "#000")
.attr("stroke-width", "1px")
.attr("stroke-opacity", "0.3")
.attr("d",connector)
nodes.descendants().slice(1).forEach(function(d) {
var mark = diagramLayout.append("svg:defs").selectAll("marker")//
.data(["start"]) // Different link/path types can be defined here
.enter().append("svg:marker") // This section adds in the arrows
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", -markerRefx)
.attr("refY", 0)
.attr("markerWidth", 5)
.attr("markerHeight", 5)
.attr("orient", "auto")
.attr("stroke", "#000")
.attr("fill", "#000")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5")
.style("stroke-width", "0.3px")
.attr("transform","rotate(180,5, 0)");
});
link.attr("marker-start", "url(#start)")
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("height", nodeHeight)
.attr("width", nodeWidth)
nodeEnter.attr("transform", function(d) {
return "translate(" + project(d.x, d.y) + ")";
}).on('contextmenu', menuCall);
var nodeIcon = nodeEnter.append("rect")
.attr("class", "rect")
.attr("x", -20)
.attr("y", -20)
.attr("rx", 10)
.attr("width", 40)
.attr("height", 40)
.attr("stroke-width", function(d) { return Math.sqrt(2); })
.attr("stroke-opacity", "0.3")
.attr("stroke", "#000" )
.attr("fill", "blue" )
//wrap(nodeText, 8)
}
}
function connector(d) {
return "M" + project(d.x, d.y)
+ "C" + project(d.x, (d.y + d.parent.y) / 2)
+ " " + project(d.parent.x, (d.y + d.parent.y) / 2)
+ " " + project(d.parent.x, d.parent.y);
}
function project(x, y) {
return [x,y];
}
function menuCall(di,i) {
var nodeClicked = d3.select(this);
var menuitems = ["Highlight route"];
d3.selectAll('.context-menu').data([1])
.enter()
.append('div')
.attr('class', 'context-menu');
// close menu
d3.select('body').on('click.context-menu', function() {
d3.select('.context-menu').style('display', 'none');
});
// this gets executed when a contextmenu event occurs
d3.selectAll('.context-menu')
.html('')
.append('ul')
.selectAll('li')
.data(menuitems).enter()
.append('li')
.on('click' , function(d) {
// d3.select('.context-menu').style('display', 'none');
if(d=="Highlight route"){
var id = nodeClicked.datum().data.id;
var node = diagramLayout.selectAll(".node");
var link = diagramLayout.selectAll(".link");
link.style("stroke","black").style("stroke-width", "1.5px")
var linkSelected = link.filter(function (d, i) {
console.log(d.data.id)
console.log(id)
return d.data.id === id;});
linkSelected.style("stroke", "red").style("stroke-width", "5px");
}
d3.select('.context-menu').style('display', 'none');
}).text(function(di) { return di; });
d3.select('.context-menu').style('display', 'none');
// show the context menu
d3.select('.context-menu')
.style('left', (d3.event.pageX - 2) + 'px')
.style('top', (d3.event.pageY - 2) + 'px')
.style('display', 'block');
d3.event.preventDefault();
}
Looks like a bug in IE. If you inspect the DOM after you apply the highlight, IE reports that the inline style has overridden the sheet style (as it should), but the path does not update:
The only fix I can think us is to remove the sheet stroke style on class link
.link {
stroke-opacity: .6;
}
And just apply all the styles in-line.
Updated fiddle.
After upgrading from 3.5.5 to 4.2.8 the flow chart lines connecting steps (represented by boxes) forward progression are gone, while lines to previous steps remain.
What needs to be changed to restore the lines?
This is the code that creates the flow chart.
function VisualizeIt(selectedItem) {
// dataset created using error handling if the data doesn't load it will say why in the console
// the text file contains all the json for the entire tree
d3.json("withdrawal.json", function (error, json) {
if (error) {
console.log(error);
}
else //it worked, so continue with the visualization
{
result = []; //clear the array
resultChildren = []; //clear the array
// grab the result and the child steps from the data
// this fills the above arrays with data
find(json, selectedItem);
//grab the parent of the selected item to display in the left hand box
resultParent = []; //clear the array
// this fills the last array with data
findParent(json, result[0].parentId);
// PARENT step
var parentStep = svg.select('.resultParent').selectAll("rect")
.data(resultParent, function (d) { return d.id; });
parentStep.enter().append("rect")
.attr("x", ParentStepPosition(resultChildren.length)[0])
.attr("y", ParentStepPosition(resultChildren.length)[1])
.attr("width", ParentStepSize(resultChildren.length))
.attr("height", ParentStepSize(resultChildren.length))
.attr("fill", "#003f87")
.attr("onclick", "VisualizeIt(" + result[0].parentId + ")");
parentStep.exit().remove();
var parentStepText = svg.select('.resultParent').selectAll("g")
.data(resultParent, function (d) { return d.id; });
parentStepText
.enter()
.append("g")
.append(function (d, i) {
//console.log("textHeight: " + textHeight + ", lineCount :" + lineCount);
textHeight = 0;
var svgText = createSVGtext("Step Back"
, ParentStepPosition(resultChildren.length)[0] + (ParentStepSize(resultChildren.length) / 2)
, ParentStepPosition(resultChildren.length)[1] + ((ParentStepSize(resultChildren.length) ) / 2) + 4
);
//console.log("textHeight: " + textHeight + ", lineCount :" + lineCount);
return svgText;
})
.attr("font-family", "sans-serif")
.attr("font-size", "12px")
.attr("fill", "white")
.attr("text-anchor", "middle")
.attr("onclick", "VisualizeIt(" + result[0].parentId + ")");
;
parentStepText.exit().remove();
//child connectors
var parentStepLines = svg.select(".result").selectAll("path")
.data( resultParent , function (d) { return d.id; });
// parent steps Lines
parentStepLines
.enter()
.append("path")
.attr("d", function (d, i) {
// format: M 100 350 q 150 -300 300 0
// format: M startPointX startPointY q
var startPointX = ParentStepPosition(resultChildren.length)[0] + ParentStepSize(resultChildren.length); // far right side of the selected
var startPointY = ParentStepPosition(resultChildren.length)[1] + (ParentStepSize(resultChildren.length) / 2); //half the height of the selected
var midPointX = ParentStepPosition(resultChildren.length)[0] + ParentStepSize(resultChildren.length); // far right side of the selected
var midPointY = SelectedStepPosition()[1] + SelectedStepSize() / 2;
var endPointX = SelectedStepPosition()[0];
var endPointY = SelectedStepPosition()[1] + SelectedStepSize() / 2;
return "M" + " " + startPointX + " " + startPointY + " Q " + midPointX + " " + midPointY + " " + endPointX + " " + endPointY;
})
.style("stroke", "#0083d6")
.style("stroke-width", "5")
.style("fill", "none");
parentStepLines.exit().remove();
// CURRENT step
var currentStep = svg.select(".result").selectAll("rect")
.data(result, function (d) { return d.id; });
currentStep.enter().append("rect")
.attr("x", SelectedStepPosition()[0])
.attr("y", SelectedStepPosition()[1])
.attr("width", SelectedStepSize())
.attr("height", SelectedStepSize())
.attr("fill", "#003f87")
.attr("onclick", "")
.text(function (d) { return "id: " + d.id });
currentStep.exit().remove();
var currentStepText = svg.select(".result").selectAll("g")
.data(result, function (d) { return d.id; });
// current step text
currentStepText
.enter()
.append("g")
.append(function (d, i) {
//console.log("textHeight: " + textHeight + ", lineCount :" + lineCount);
textHeight = 0;
var svgText = createSVGtext(d.title
, SelectedStepPosition()[0] + (SelectedStepSize() / 2)
, SelectedStepPosition()[1] + ((SelectedStepSize() - TextHeight(d.title)) / 2) + 4
);
//console.log("textHeight: " + textHeight + ", lineCount :" + lineCount);
return svgText;
})
.attr("font-family", "sans-serif")
.attr("font-size", "12px")
.attr("fill", "white")
.attr("text-anchor", "middle");
currentStepText.exit().remove();
// CHILDREN
// i.e. next available steps
// use the ID as the key when linking the data
var childrenSteps = d3.select(".resultChildren").selectAll("rect")
.data(resultChildren, function (d) { return d.id; });
childrenSteps
.enter()
.append("rect")
.attr("x", function (d, i) { return ChildStepPosition(i, resultChildren.length)[0]; })
.attr("y", function (d, i) { return ChildStepPosition(i, resultChildren.length)[1]; })
.attr("width", SelectedChildStepSize(resultChildren.length)[1])
.attr("height", SelectedChildStepSize(resultChildren.length)[0])
.attr("fill", "#003f87")
.attr("onclick", function (d, i) { return 'VisualizeIt(' + d.id + ')';})
.text(function (d) { return "id: " + d.id });
childrenSteps.exit().remove();
var childrenStepsText = svg.select(".resultChildren").selectAll("g")
.data(resultChildren, function (d) { return d.id; });
// children steps text
childrenStepsText
.enter()
.append("g")
.append(function (d, i) {
//console.log("textHeight: " + textHeight + ", lineCount :" + lineCount);
textHeight = 0;
var svgText = createSVGtext(d.asChildText
, ChildStepPosition(i, resultChildren.length)[0] + (SelectedChildStepSize(resultChildren.length)[1] / 2)
, ChildStepPosition(i, resultChildren.length)[1] + ((SelectedChildStepSize(resultChildren.length)[0] - TextHeight(d.asChildText)) / 2) +4
);
//console.log("textHeight: " + textHeight + ", lineCount :" + lineCount);
return svgText;
})
.attr("font-family", "sans-serif")
.attr("font-size", "12px")
.attr("fill", "white")
.attr("text-anchor", "middle")
.attr("onclick", function (d, i) { return 'VisualizeIt(' + d.id + ')'; });
;
childrenStepsText.exit().remove();
var lineFunction = d3.svg.line();
//child connectors
var childrenStepLines = svg.select(".resultChildren").selectAll("path")
.data(resultChildren, function (d) { return d.id; });
// children steps Lines
childrenStepLines
.enter()
.append("path")
.attr("d", function (d, i) {
// format: M 100 350 q 150 -300 300 0
// format: M startPointX startPointY q
var startPointX = SelectedStepPosition()[0] + SelectedStepSize(); // far right side of the selected
var startPointY = SelectedStepPosition()[1] + (SelectedStepSize() / 2); //half the height of the selected
var midPointX = SelectedStepPosition()[0] + SelectedStepSize(); // far right side of the selected
var midPointY = ChildStepPosition(i, resultChildren.length)[1] + SelectedChildStepSize(resultChildren.length)[0] / 2;
var endPointX = ChildStepPosition()[0];
var endPointY = ChildStepPosition(i, resultChildren.length)[1] + SelectedChildStepSize(resultChildren.length)[0] / 2;
return "M" + " " + startPointX + " " + startPointY + " Q " + midPointX + " " + midPointY + " " + endPointX + " " + endPointY;
})
.style("stroke", "#0083d6")
.style("stroke-width", "5")
.style("fill", "none");
childrenStepLines.exit().remove();
//update the iframe with the correct detailed html
d3.select("iframe").attr("src", "iFrameHTML/" + result[0].url);
};
});
};
thanks
The code was throwing an error at line 250.
TypeError: d3.svg is undefined
var lineFunction = d3.svg.line();
Version 3.5.5 let the error slide. In version 4.2.8 it did not let the error slide.
Once I commented out line 250, the child step line displayed. I do not know the purpose of line 250. a prvious programmer created it. There is no reference in the entire code to a variable called lineFunction.
I'm looking for some hints as to what I am doing wrong with a Sankey diagram I'm creating. I am charting changes in food consumption over time, and using the Sankey layout to visualize how these values changed over a period of forty years.
The bl.ock and small dataset are here. The relevant code:
var margin = {top: 1, right: 1, bottom: 6, left: 1},
width = 1260 - margin.left - margin.right,
height = 1000 - margin.top - margin.bottom;
var formatNumber = d3.format(",.0f"),
format = function(d) { return formatNumber(d) + " TWh"; },
color = d3.scale.category20();
var svg = d3.select("#chart").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var sankey = d3.sankey()
.nodeWidth(15)
.nodePadding(10)
.size([width, height]);
var path = sankey.link();
// ========================== Prepare data ==========================
queue()
.defer(d3.csv, "grains.csv")
.await(ready);
// ========================== Start viz ==========================
function ready(error, csv_data) {
nodes = [];
edges = [];
nodesArray = [];
// Scales
yearScale = d3.scale.linear().domain([1640,1688]).range([20,width -20]);
radiusScale = d3.scale.linear().domain([0,300]).range([2,12]).clamp(true);
chargeScale = d3.scale.linear().domain([0,100]).range([0,-100]).clamp(true);
uniqueValues = d3.set(nodesArray.map(function(d) {return d.name})).values();
colorScale = d3.scale.category20b(uniqueValues);
sortScale = d3.scale.ordinal().domain(uniqueValues).rangePoints([-0.001,.001]);
// Create a JSON link array
// This creates unique nodes for each item and its corresponding date.
// For example, nodes are rendered as "peas-1640," "peas-1641," etc.
csv_data.forEach(function(link) {
key = link.translation + '-' + link.date;
link.source = nodes[key] || (nodes[key] = {name: link.translation, date: link.date, origX: yearScale(parseInt(link.date)), value: link.value || 0});
});
// Build the edgesArray array
// This creates the edgesArray to correspond with unique nodes. We're telling
// items and dates to remain together. So, the code below tells the graph
// layout that `1641` is preceded by `1640` and followed by `1642`, etc.
var y = "→";
for (x in nodes) {
nodesArray.push(nodes[x])
if(nodes[y]) {
nodes[y].date = parseInt(nodes[y].date);
if (nodes[y].name == nodes[x].name) {
var newLink = {source:nodes[y], target:nodes[x]}
edges.push(newLink);
}
}
y = x;
}
sankey
.nodeWidth(10)
.nodePadding(10)
.size([1200, 1200])
.nodes(nodesArray.filter(function(d,i) {return d.date < 1650}))
.links(edges.filter(function(d,i) { return i < 50 && d.source.date < 1650 && d.target.date < 1650} )) // filtering to test a smaller data set
.layout(32);
var link = svg.append("g").selectAll(".link")
.data(edges.filter(function(d,i) { return i < 50 && d.source.date < 1650 && d.target.date < 1650} )) // filtering to test a smaller data set
.enter().append("path")
.attr("class", "link")
.attr("d", path)
.style("stroke-width", function(d) { return Math.max(1, d.dy); })
.sort(function(a, b) { return b.dy - a.dy; });
link.append("title")
.text(function(d) { return d.source.name + " → " + d.target.name + "\n" + format(d.value); });
var node = svg.append("g").selectAll(".node")
.data(nodesArray.filter(function(d,i) {return d.date < 1650})) // filtering to test a smaller data set
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
.call(d3.behavior.drag()
.origin(function(d) { return d; })
.on("dragstart", function() { this.parentNode.appendChild(this); })
.on("drag", dragmove));
node.append("rect")
.attr("height", function(d) { return d.dy; })
.attr("width", sankey.nodeWidth())
.style("fill", function(d) { return d.color = color(d.name.replace(/ .*/, "")); })
.style("stroke", function(d) { return d3.rgb(d.color).darker(2); })
.append("title")
.text(function(d) { return d.name + "\n" + format(d.value); });
node.append("text")
.attr("x", -6)
.attr("y", function(d) { return d.dy / 2; })
.attr("dy", ".35em")
.attr("text-anchor", "end")
.attr("transform", null)
.text(function(d) { return d.name; })
.filter(function(d) { return d.x < width / 2; })
.attr("x", 6 + sankey.nodeWidth())
.attr("text-anchor", "start");
function dragmove(d) {
d3.select(this).attr("transform", "translate(" + d.x + "," + (d.y = Math.max(0, Math.min(height - d.dy, d3.event.y))) + ")");
sankey.relayout();
link.attr("d", path);
}
};
Unfortunately, I'm getting an error as you can see in the bl.ock. The Boss suggested it might be a circular link but I'm at a bit of a loss. Any hints or suggestions?
EDIT: For some clarity, I'm after something like this:
(Source)
From what I can tell, I think I'm building the nodes and edges correctly. If we look at the console for the nodes array and edges array:
It's not like a usual Sankey or alluvial diagram, which, as I've often seen them, shows collapses and expansions of items. In my case the date, food item, and value are all a single stream throughout the length of the visualization but are resized/repositioned based on the value for a given year (like the example image above).