I am using it to generate a heavy amount of words in my application and I grab data from a json file like below:
var a = [];
for (var i=0; i < a.length; i++){
a.push(a[i].word);
}
And this gives us an array.
a = ["gsad","sagsa","gsag","sagas","gsag","gsagas","yhff","gag"];
I have it display on the screen correctly but since the row is too long it got out from the border and I'd like to give it a link break instead of change the SVG size, How may i do this?
UPDATE:
The code below is how i insert my codes:
var PositiveArr = ["gsad","sagsa","gsag","sagas","gsag","gsagas","yhff","gag"]; //consider the NegativeArr,NeutralArr have the similar contents
var fill = d3.scale.category20();
d3.layout.cloud().size([600, 300])
.words([NegativeArr,NeutralArr,PositiveArr].map(function(d) {
return {text: d, size: 10 + Math.random() * 50};
}))
.rotate(function() { return ~~(Math.random() * 2) * 90; })
.font("Impact")
.fontSize(function(d) { return d.size; })
.on("end", draw)
.start();
function draw(words) {
d3.select("#pre-theme").append("svg")
.attr("width", 600)
.attr("height", 300)
.append("g")
.attr("transform", "translate(300,150)")
.selectAll("text")
.data(words)
.enter().append("text")
.style("font-size", function(d) { return d.size + "px"; })
.style("font-family", "Impact, Arial")
.style("fill", function(d, i) { return fill(i); })
.attr("text-anchor", "middle")
.attr("transform", function(d) {
return "translate(" + [d.x, d.y] + ")rotate(" + d.rotate + ")";
})
.text(function(d) { return d.text; });
}
SOLUTION:
I have found myself a solution, i just merge all my arrays into one using
var allResult = PersonsArr.concat(PlacesArr,PatternsArr,ProductsArr,CompaniesArr);
and insert the to the .map like
.words(entityResult.map(function(d) {
return {text: d, size: 10 + Math.random() * 50};
}))
SOLUTION: I have found myself a solution, i just merge all my arrays into one using
var allResult = PersonsArr.concat(PlacesArr,PatternsArr,ProductsArr,CompaniesArr);
and insert the to the .map like
.words(entityResult.map(function(d) {
return {text: d, size: 10 + Math.random() * 50};
}))
Related
I am new to D3 and trying to dynamically update the chart if the source json is modified. But I am not able to achieve this.
Please check this plunkr
Js:
var width = 500,
height = 500,
radius = Math.min(width, height) / 2;
var x = d3.scale.linear()
.range([0, 2 * Math.PI]);
var y = d3.scale.sqrt()
.range([0, radius]);
var color = d3.scale.category10();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + (height / 2 + 10) + ") rotate(-90 0 0)");
var partition = d3.layout.partition()
.value(function(d) {
return d.size;
});
var arc = d3.svg.arc()
.startAngle(function(d) {
return Math.max(0, Math.min(2 * Math.PI, x(d.x)));
})
.endAngle(function(d) {
return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx)));
})
.innerRadius(function(d) {
return Math.max(0, y(d.y));
})
.outerRadius(function(d) {
return Math.max(0, y(d.y + d.dy));
});
//d3.json("/d/4063550/flare.json", function(error, root) {
var root = initItems;
var g = svg.selectAll("g")
.data(partition.nodes(root))
.enter().append("g");
var path = g.append("path")
.attr("d", arc)
.style("fill", function(d) {
return color((d.children ? d : d.parent).name);
})
.on("click", click)
.each(function(d) {
this.x0 = d.x;
this.dx0 = d.dx;
});
//.append("text")
var text = g.append("text")
.attr("x", function(d) {
return y(d.y);
})
.attr("dx", "6") // margin
.attr("dy", ".35em") // vertical-align
.attr("transform", function(d) {
return "rotate(" + computeTextRotation(d) + ")";
})
.text(function(d) {
return d.name;
})
.style("fill", "white");
function computeTextRotation(d) {
var angle = x(d.x + d.dx / 2) - Math.PI / 2;
return angle / Math.PI * 180;
}
function click(d) {
console.log(d)
// fade out all text elements
if (d.size !== undefined) {
d.size += 100;
};
text.transition().attr("opacity", 0);
path.transition()
.duration(750)
.attrTween("d", arcTween(d))
.each("end", function(e, i) {
// check if the animated element's data e lies within the visible angle span given in d
if (e.x >= d.x && e.x < (d.x + d.dx)) {
// get a selection of the associated text element
var arcText = d3.select(this.parentNode).select("text");
// fade in the text element and recalculate positions
arcText.transition().duration(750)
.attr("opacity", 1)
.attr("transform", function() {
return "rotate(" + computeTextRotation(e) + ")"
})
.attr("x", function(d) {
return y(d.y);
});
}
});
} //});
// Word wrap!
var insertLinebreaks = function(t, d, width) {
alert(0)
var el = d3.select(t);
var p = d3.select(t.parentNode);
p.append("g")
.attr("x", function(d) {
return y(d.y);
})
// .attr("dx", "6") // margin
//.attr("dy", ".35em") // vertical-align
.attr("transform", function(d) {
return "rotate(" + computeTextRotation(d) + ")";
})
//p
.append("foreignObject")
.attr('x', -width / 2)
.attr("width", width)
.attr("height", 200)
.append("xhtml:p")
.attr('style', 'word-wrap: break-word; text-align:center;')
.html(d.name);
alert(1)
el.remove();
alert(2)
};
//g.selectAll("text")
// .each(function(d,i){ insertLinebreaks(this, d, 50 ); });
d3.select(self.frameElement).style("height", height + "px");
// Interpolate the scales!
function arcTween(d) {
var xd = d3.interpolate(x.domain(), [d.x, d.x + d.dx]),
yd = d3.interpolate(y.domain(), [d.y, 1]),
yr = d3.interpolate(y.range(), [d.y ? 20 : 0, radius]);
return function(d, i) {
return i ? function(t) {
return arc(d);
} : function(t) {
x.domain(xd(t));
y.domain(yd(t)).range(yr(t));
return arc(d);
};
};
}
function arcTweenUpdate(a) {
console.log(path);
var _self = this;
var i = d3.interpolate({ x: this.x0, dx: this.dx0 }, a);
return function(t) {
var b = i(t);
console.log(window);
_self.x0 = b.x;
_self.dx0 = b.dx;
return arc(b);
};
}
setTimeout(function() {
path.data(partition.nodes(newItems))
.transition()
.duration(750)
.attrTween("d", arcTweenUpdate)
}, 2000);
In addition to what #Cyril has suggested about removing the following line:
d3.select(self.frameElement).style("height", height + "px");
I made further modifications in your fiddle: working fiddle
The idea used here is to add a function updateChart which takes the items and then generate the chart:
var updateChart = function (items) {
// code to update the chart with new items
}
updateChart(initItems);
setTimeout(function () { updateChart(newItems); }, 2000);
This doesn't use the arcTweenUpdate function you have created but I will try to explain the underlying concept:
First, you will need to JOIN the new data with your existing data:
// DATA JOIN - Join new data with old elements, if any.
var gs = svg.selectAll("g").data(partition.nodes(root));
then, ENTER to create new elements if required:
// ENTER
var g = gs.enter().append("g").on("click", click);
But, we also need to UPDATE the existing/new path and text nodes with new data:
// UPDATE
var path = g.append("path");
gs.select('path')
.style("fill", function(d) {
return color((d.children ? d : d.parent).name);
})
//.on("click", click)
.each(function(d) {
this.x0 = d.x;
this.dx0 = d.dx;
})
.transition().duration(500)
.attr("d", arc);
var text = g.append("text");
gs.select('text')
.attr("x", function(d) {
return y(d.y);
})
.attr("dx", "6") // margin
.attr("dy", ".35em") // vertical-align
.attr("transform", function(d) {
return "rotate(" + computeTextRotation(d) + ")";
})
.text(function(d) {
return d.name;
})
.style("fill", "white");
and, after everything is created/updated remove the g nodes which are not being used i.e. EXIT:
// EXIT - Remove old elements as needed.
gs.exit().transition().duration(500).style("fill-opacity", 1e-6).remove();
This whole pattern of JOIN + ENTER + UPDATE + EXIT is demonstrated in following articles by Mike Bostock:
General Update Pattern - I
General Update Pattern - II
General Update Pattern - III
In side the fiddle the setTimeout is not running because:
d3.select(self.frameElement).style("height", height + "px");
You will get Uncaught SecurityError: Failed to read the 'frame' property from 'Window': Blocked a frame with origin "https://fiddle.jshell.net" from accessing a frame with origin and the setTimeout never gets called.
So you can remove this line d3.select(self.frameElement).style("height", height + "px"); just for the fiddle.
Apart from that:
Your timeout function should look like this:
setTimeout(function() {
//remove the old graph
svg.selectAll("*").remove();
root = newItems;
g = svg.selectAll("g")
.data(partition.nodes(newItems))
.enter().append("g");
/make path
path = g.append("path")
.attr("d", arc)
.style("fill", function(d) {
return color((d.children ? d : d.parent).name);
})
.on("click", click)
.each(function(d) {
this.x0 = d.x;
this.dx0 = d.dx;
});
//make text
text = g.append("text")
.attr("x", function(d) {
return y(d.y);
})
.attr("dx", "6") // margin
.attr("dy", ".35em") // vertical-align
.attr("transform", function(d) {
return "rotate(" + computeTextRotation(d) + ")";
})
.text(function(d) {
return d.name;
})
.style("fill", "white");
}
working fiddle here
for the enter() and transitions to work you need to give d3 a way to identify each item in your data. the .data() function has a second parameter that lets you return something to use as an id. enter() will use the id to decide whether the object is new.
try changing
path.data(partition.nodes(newItems))
.data(partition.nodes(root));
to
path.data(partition.nodes(newItems), function(d){return d.name});
.data(partition.nodes(root), function(d){return d.name});
I'm trying to create a sankey chart using the d3 sankey plugin with dynamic shipping data. It works great most of the time except when I get a data set like this:
[{"DeparturePort":"CHARLESTON","ArrivalPort":"BREMERHAVEN","volume":5625.74},{"DeparturePort":"CHARLESTON","ArrivalPort":"ITAPOA","volume":2340},{"DeparturePort":"PT EVERGLADES","ArrivalPort":"PT AU PRINCE","volume":41.02},{"DeparturePort":"BREMERHAVEN","ArrivalPort":"CHARLESTON","volume":28}]
The key to my issue is the first and last entry in the data set. It seems that having opposite directions in the same sankey chart sends the javascript into an infinite loop and kills the browser. Any ideas on how to prevent this from happening?
Here's my chart code where raw would be the object above:
var data = raw;
var units = "Volume";
var margin = { top: 100, right: 0, bottom: 30, left: 0 },
width = $("#"+divID).width() - margin.left - margin.right,
height = divID == "enlargeChart" ? 800 - margin.top - margin.bottom : 600 - margin.top - margin.bottom;
var formatNumber = d3.format(",.0f"), // zero decimal places
format = function (d) { return ""; },
color = d3.scale.ordinal()
.range(["#0077c0", "#FF6600"]);
// append the svg canvas to the page
var svg = d3.select("#"+divID).append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.style("font-size", "12px")
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
// Set the sankey diagram properties
var sankey = d3.sankey(width)
.nodeWidth(10)
.nodePadding(10)
.size([width, height]);
var path = sankey.link();
// load the data (using the timelyportfolio csv method)
//d3.csv("sankey.csv", function (error, data) {
//set up graph in same style as original example but empty
graph = { "nodes": [], "links": [] };
var checklist = [];
data.forEach(function (d) {
if ($.inArray(d.DeparturePort, checklist) == -1) {
checklist.push(d.DeparturePort)
graph.nodes.push({ "name": d.DeparturePort });
}
if ($.inArray(d.ArrivalPort, checklist) == -1) {
checklist.push(d.ArrivalPort)
graph.nodes.push({ "name": d.ArrivalPort });
}
graph.links.push({
"source": d.DeparturePort,
"target": d.ArrivalPort,
"value": +d.volume
});
});
// return only the distinct / unique nodes
graph.nodes = d3.keys(d3.nest()
.key(function (d) { return d.name; })
.map(graph.nodes));
// 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);
});
//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 };
});
sankey
.nodes(graph.nodes)
.links(graph.links)
.layout(32);
// add in the links
var link = svg.append("g").selectAll(".link")
.data(graph.links)
.enter().append("path")
.attr("class", "link")
.attr("d", path)
.style("stroke-width", function (d) { return Math.max(1, d.dy); })
.sort(function (a, b) { setTimeout(function () { return b.dy - a.dy; }, 10);});
// add the link titles
link.append("title")
.text(function (d) {
return d.source.name + " → " +
d.target.name + "\n" + d.value.toFixed(0) + " TEU";
});
$("#" + divID + " .loading").addClass("hide");
// add in the nodes
var node = svg.append("g").selectAll(".node")
.data(graph.nodes)
.enter().append("g")
.attr("class", "node")
.attr("transform", function (d) {
//setTimeout(function () {
return "translate(" + d.x + "," + d.y + ")";
//}, 10);
})
.call(d3.behavior.drag()
.origin(function (d) { return d; })
.on("dragstart", function () {
this.parentNode.appendChild(this);
})
.on("drag", dragmove));
// add the rectangles for the nodes
node.append("rect")
.attr("height", function (d) { if (d.dy < 0) { d.dy = (d.dy * -1); } return d.dy; })
.attr("width", sankey.nodeWidth())
.style("fill", function (d) {
return d.color = color(d.name);
})
.style("stroke", function (d) {
return d3.rgb(d.color);
})
.append("title")
.text(function (d) {
return d.name + "\n" + format(d.value);
});
// add in the title for the nodes
node.append("text")
.attr("x", -6)
.attr("y", function (d) { return d.dy / 2; })
.attr("dy", ".35em")
.attr("text-anchor", "end")
.style("stroke", function (d) { return "#000000" })
.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");
// the function for moving the nodes
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);
}
}, 0)
It's an issue with the sankey.js script.
See this commit (on a fork of sankey.js) which fixed it:
https://github.com/soxofaan/d3-plugin-captain-sankey/commit/0edba18918aac3e9afadffd4a169c47f88a98f81
while (remainingNodes.length) {
becomes:
while (remainingNodes.length && x < nodes.length) {
That should prevent the endless loop.
I am using the d3.layout.cloud.js to make a word cloud and the source data is a .csv table containing both words and their weight.Now I can show all the words in the cloud but cannot put every word's weight on its own.
Someone help me out here and thank you all very much ;p
<script type="text/javascript">
var fill = d3.scale.category20();
d3.csv("wordTfIdf.csv",function(data){
console.log(data);
var word = [];
var value = [];
for(i in data)
{
word[i] = data[i].word;
value[i] = data[i].tfidf;
}
console.log(word);
console.log(value);
d3.layout.cloud().size([960, 600])
**.words(word.map(function(d) {return {text: d, size: 10 + Math.random() * 50};}))**
.rotate(function() { return ~~(Math.random() * 2) * 90; })
.font("Impact")
.fontSize(function(d) { return d.size; })
.on("end", draw)
.start();
function draw(words) {
d3.select("body").append("svg")
.attr("width", 960)
.attr("height", 600)
.append("g")
.attr("transform", "translate(150,150)")
.selectAll("text")
.data(words)
.enter().append("text")
.style("font-size", function(d) { return d.size + "px"; })
.style("font-family", "Impact")
.style("fill", function(d, i) { return fill(i); })
.attr("text-anchor", "middle")
.attr("transform", function(d) {
return "translate(" + [d.x, d.y] + ")rotate(" + d.rotate + ")";
})
.text(function(d) { return d.text; });
}
});
</script>
You don't include an example of your "wordTfIdf.csv" which would really help to answer this question but I'm from the looks of it it's something like this:
word,tfidf
one, 20
two, 30
three, 40
four, 50
five, 60
six, 70
seven, 80
nine, 90
Where word is the text to display and tfidf is the font-size of the the word.
The first step is to get this into a format that the d3.layout.cloud() likes, mainly an array of objects with text and size properties. We can use d3.csv's parsing to help us.
// give d3.csv two functions
// the first to parse each row
// the second to draw the cloud
d3.csv("wordTfIdf.csv", function(d) {
return { // for each csv row return an object with text and size
text: d.word,
size: +d.tfidf // cast this to a number with +
}
},
function(data) {
d3.layout.cloud().size([960, 600]).words(
data // data is already in the correct format
)
Here's an example.
I'm having some trouble trying to create multiple wordclouds from a single source json using d3.
Edit: Forgot to mention, I'm basing the wordclouds on Jason Davies example, here.
There are various guides for creating small multiples from the same file, but I can't find anything which involves using a layout model for each chart, as is the case for the word-cloud layout.
I want to make a separate wordcloud for each item in the 'results' object:
sourcedata = {
"results":
[
{
"category":"spain",
"words":[["i", 190], ["the", 189], ["it", 139], ["you", 134], ["to", 133], ["a", 131]]
},
{
"category":"england",
"words":[["lol", 37], ["on", 36], ["can", 35], ["do", 35], ["was", 33], ["mike", 33], ["but", 31], ["get", 30], ["like", 30]]
},
{
"category":"france",
"words":[["ve", 18], ["make", 18], ["nick", 18], ["soph", 18], ["got", 18], ["he", 17], ["work", 17]]
},
{
"category":"germany",
"words":[["about", 13], ["by", 13], ["out", 13], ["probabl", 13], ["how", 13], ["video", 12], ["an", 12]]
}
]
}
Since each wordcloud needs it's own layout model, I'm trying to use a forEach loop to go through each category, creating a model and 'draw' callback method for each one:
d3.json(sourcedata, function(error, data) {
if (error) return console.warn(error);
data = data.results;
var number_of_charts = data.length;
var chart_margin = 10;
var total_width = 800;
var chart_width_plus_two_margin = total_width/number_of_charts;
var chart_width = chart_width_plus_two_margin - (2 * chart_margin);
data.forEach(function(category) {
svg = d3.select("body")
.append("svg")
.attr("id", category.name)
.attr("width", (chart_width + (chart_margin * 2)))
.attr("height", (chart_width + (chart_margin * 2)))
.append("g")
.attr("transform", "translate(" + ((chart_width/2) + chart_margin) + "," + ((chart_width/2) + chart_margin) + ")");
d3.layout.cloud().size([chart_width, chart_width])
.words(category.words.map(function(d) {
return {text: d[0], size: 10 + (d[1] / 10)};
}))
.padding(5)
.rotate(function() { return ~~(Math.random() * 2) * 90; })
.font("Impact")
.fontSize(function(d) { return 10 + (d[1] * 10); })
.on("end", drawInner)
.start();
function drawInner(words) {
svg.selectAll("text").data(words)
.enter().append("text")
.style("font-size", function(d,i) { return d.size + "px"; })
.style("font-family", "Impact")
.style("fill", function(d, i) { return fill(i); })
.attr("text-anchor", "middle")
.attr("transform", function(d) {
return "translate(" + [d.x+(chart_width/2), d.y+(chart_width/2)] + ")rotate(" + d.rotate + ")";
})
.text(function(d) { return d.text; });
}
});
});
However, when I run this, I get one set of svg/g tags per category, but each one only contains a single text tag, with just one of the words from that category, and a size of 0.
Any help much appreciated, thanks!
Edit: See my own reply for fixed code. Thanks to Mark.
Stared at this for too long. The problem is this line:
.fontSize(function(d) { return 10 + (d[1] * 10); })
The d here is constructed object from the layout, not your array entry. This fails silently.
Substitute it with:
.fontSize(function(d) { return d.size; })
Also, check your translate math. It seems to be shifting the g elements out of the svg.
Example here.
Another potential pitfall is that since the drawInner is a callback it appears to be called in an async fashion. Because of this you have a potential for the svg variable to be overwritten with multiple calls to drawInner. I would consider moving the svg creation to inside the call back. One way to do this is:
...
.on("end", function(d, i) {
drawInner(d, category.category);
})
.start();
function drawInner(words, name) {
svg = d3.select("body")
.append("svg")
.attr("id", name)
...
So that you can still pass in the category name.
Updated example.
Thanks to Mark's fix - here's the working code:
d3.json('./resources/whatsappList.json', function(error, data) {
if (error) return console.warn(error);
var fill = d3.scale.category20(); //added missing fill scale
data = data.results;
var number_of_charts = data.length;
var chart_margin = 10;
var total_width = 800;
var chart_width_plus_two_margin = total_width/number_of_charts;
var chart_width = chart_width_plus_two_margin - (2 * chart_margin);
data.forEach(function(category) {
d3.layout.cloud().size([chart_width, chart_width])
.words(category.words.map(function(d) {
return {text: d[0], size: 10 + (d[1] / 10)};
}))
.padding(5)
.rotate(function() { return ~~(Math.random() * 2) * 90; })
.font("Impact")
.fontSize(function(d) { return d.size; }) //fixed error on size
.on("end", function(d, i) {
drawInner(d, category.category);
})
.start();
function drawInner(words, name) {
svg = d3.select("body")
.append("svg")
.attr("id", name)
.attr("width", (chart_width + (chart_margin * 2)))
.attr("height", (chart_width + (chart_margin * 2)))
.append("g")
.attr("transform", "translate(0,0)") //neutralised pointless translation
.selectAll("text").data(words)
.enter().append("text")
.style("font-size", function(d,i) { return d.size + "px"; })
.style("font-family", "Impact")
.style("fill", function(d, i) { return fill(i); })
.attr("text-anchor", "middle")
.attr("transform", function(d) {
return "translate(" + [d.x+(chart_width/2), d.y+(chart_width/2)] + ")rotate(" + d.rotate + ")";
})
.text(function(d) { return d.text; });
}
});
});
I'm trying to use the World Cloud Generator : http://www.jasondavies.com/wordcloud/about/
wich is a addon to use with D3.JS.
Here's my code :
var fill = d3.scale.category20();
function draw(words) {
d3.select("body").select("#corps div")
.append("svg")
.attr("width", 900)
.attr("height", 900)
.append("g")
.attr("transform", "translate(450,450)")
.selectAll("text")
.data(words)
.enter().append("text")
.attr("text-anchor", "middle")
.attr("transform", function(d) {
return "translate(" + [d.x, d.y] + ")rotate(" + d.rotate + ")";
})
.style("font-size", function(d) { return d.size + "px"; })
.style("font-family", "Impact")
.style("fill", function(d, i) { return fill(i); })
.text(function(d) { return d.text; });
}
function rotation(){
return ~~(90 * Math.random() *2);
}
var taille = 90;
function motTaille(mot){
taille = taille -2;
return {text: mot, size: taille};
}
var mots = tabMots.map(motTaille);
var layout = d3.layout.cloud()
.timeInterval(10)
.size([900, 900])
.words(words_tab)
.padding(1)
.rotate(function() { return ~~(Math.random() * 2) * 90; })
.font("Impact")
.fontSize(function(d) { return d.size; })
.on("end", draw)
.start();
I already have an array of words : words_tab.
My problem is : In the website, Jason Davies (author) codes a detection of collision. But when I have a lot of words in my array, they overlap each other...
Am i missing something ?
I found the problem... enough simple !
taille = taille -2;
Sizes can't be negatives, so I corrected the problem :
var taille = 90;
var tailleMini = 15;
function motTaille(mot){
if(taille < tailleMini)
return {text: mot, size: tailleMini};
else{
taille = taille -2;
return {text: mot, size: taille};
}
}