I am creating a wordcloud by modifying code from : https://github.com/jasondavies/d3-cloud. I can change the size by modifying w & h but I want to scale the word cloud as the browser window changes. What would be the best method to achieve this?
Code also posted at http://plnkr.co/edit/AZIi1gFuq1Vdt06VIETn?p=preview
<script>
myArray = [{"text":"First","size":15},{"text":"Not","size":29},{"text":"Bird","size":80}, {"text":"Hello","size":40},{"text":"Word","size":76},{"text":"Marketplaces","size":75}]
var fillColor = d3.scale.category20b();
var w = 400, // if you modify this also modify .append("g") .attr -- as half of this
h = 600;
d3.layout.cloud().size([w, h])
.words(myArray) // from list.js
.padding(5)
.rotate(0)
.font("Impact")
.fontSize(function(d) { return d.size; })
.on("end", drawCloud)
.start();
function drawCloud(words) {
d3.select("body").append("svg")
.attr("width", w)
.attr("height", h)
.append("g")
.attr("transform", "translate(" + w/2 + "," + h/2 + ")")
.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 fillColor(i); })
.attr("text-anchor", "middle")
.attr("transform", function(d,i) {
return "translate(" + [d.x, d.y] + ")rotate(" + d.rotate + ")";
}
)
.text(function(d) { return d.text; });
}
</script>
Solution # 1:
On line 37:
.style("font-size", function(d) { return (d.size) + "px"; })
Replace
.style("font-size", function(d) { return (d.size/3) + "vh"; }) // "d.size/3" is an assumption use your appropriate relative width or height.
Instead of using px use vw which is view port width. It is a css3 feature that will resize the text according to the viewport. However, you will need to adjust the real width and height properly.
Try reading this article: http://css-tricks.com/viewport-sized-typography/
Solution # 2:
On line 37:
.style("font-size", function(d) { return (d.size) + "px"; })
Use
.attr("class", nameOfClass) // use class names here like 'big-font', 'med-font', 'small-font'
and in the CSS define the styles using media queries, the classes will be assigned depending upon the d.size in the condition so do it like if (d.size > 10) nameOfClass = "big-font" etc.
Instead of giving words width and height using JS, allocate classes to them using media queries breakpoints.
Read : http://www.w3schools.com/cssref/css3_pr_mediaquery.asp
I recommend solution 2 as i believe vw and vh is not supported by all the browsers. http://caniuse.com/#feat=viewport-units. There are some issues reported related to that.
Solution # 3:
To calculate the font-size, you have to create this scale:
var fontSizeScale = d3.scale.pow().exponent(5).domain([0,1]).range([ minFont, maxFont]);
and call it in fontSize function:
var maxSize = d3.max(that.data, function (d) {return d.size;});
.fontSize(function (d) {
return fontSizeScale(d.size/maxSize);
})
To fit the bounds to your screen/div:
in the .on("end", drawCloud) function, call this function:
function zoomToFitBounds() {
var X0 = d3.min( words, function (d) {
return d.x - (d.width/2);
}),
X1 = d3.max( words, function (d) {
return d.x + (d.width/2);
});
var Y0 = d3.min( words, function (d) {
return d.y - (d.height/2);
}),
Y1 = d3.max( words, function (d) {
return d.y + (d.height/2);
});
var scaleX = (X1 - X0) / (width);
var scaleY = (Y1 - Y0) / (height);
var scale = 1 / Math.max(scaleX, scaleY);
var translateX = Math.abs(X0) * scale;
var translateY = Math.abs(Y0) * scale;
cloud.attr("transform", "translate(" +
translateX + "," + translateY + ")" +
" scale(" + scale + ")");
}
Related
I'm newbie in D3 and I'm trying to set a symbol on the left of the text of a legend. The legend is on the right of the graphic and all the texts of the legend are correctly located but I cannot be able to located on their left the symbol which corresponds with the legend.
The function which locate the legend and try to do the same with the symbols are:
setLegend(canvas, symbols, width, offset_right, height) {
canvas
.selectAll("legends")
.data(symbols)
.enter()
.append("text")
.attr("transform", function(d, i) {
let x = width - offset_right + 50;
let y = height / 2 - 100 + i * 24;
return "translate( " + x + "," + y + ")";
})
.attr(
"d",
d3
.symbol()
.type(function(d) {
return d.symbol;
})
.size("75")
)
.style("text-anchor", "left")
.text(d => {
return d.stats;
})
.attr("fill", "#FFFFFF")
.style("font-size", "10pt")
.style("font-weight", "bold"); }
You can check in this screen cap how the legend is correctly located but there are no any symbol on its left.
You can check all the code of the development in codesanbox:
What am I doing wrong?
Right now you're setting an attribute called d to text elements, which has no effect on those texts (only paths have the d attribute). On top of that, you're not appending any path.
A simple and common fix is appending groups in the enter selection, to which you append the paths and texts. Here is an example (I'm setting the x and y positions of the texts so they don't start right over the symbols):
setLegend(canvas, symbols, width, offset_right, height) {
const groups = canvas
.selectAll("legends")
.data(symbols)
.enter()
.append("g")
.attr("transform", function(d, i) {
let x = width - offset_right + 50;
let y = height / 2 - 100 + i * 24;
return "translate( " + x + "," + y + ")";
});
groups.append("path")
.attr("d", d3.symbol().type(function(d) {
return d.symbol;
}).size("75"))
.attr("fill", function(d) {
return d.color;
});
groups.append("text")
.attr("x", 10)
.attr("y", 5)
.style("text-anchor", "left")
.text(d => {
return d.stats;
})
.attr("fill", function(d) {
return d.color;
})
.style("font-size", "10pt")
.style("font-weight", "bold");
}
I have made a violin plot in D3.js with the following code:
<script src="https://d3js.org/d3.v4.js"></script>`
<div id="power"></div>
<script>
var margin = {top: 120, right: 100, bottom: 80, left: 100},
width = 2600 - margin.left - margin.right,
height = 620 - margin.top - margin.bottom;
var svg = d3.select("#power")
.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 + ")");
// Read the data and compute summary statistics for each
d3.csv("static/csv/violinsummary.csv", function (data) {
// Show the X scale
var x = d3.scaleBand()
.range([0, width])
.domain(["2017-09", "2017-10", "2018-02", "2018-03"])
.paddingInner(0)
.paddingOuter(.5);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Show the Y scale
var y = d3.scaleLinear()
.domain([80, 105])
.range([height, 0]);
svg.append("g").call(d3.axisLeft(y));
// Features of density estimate
var kde = kernelDensityEstimator(kernelEpanechnikov(.2), y.ticks(50));
// Compute the binning for each group of the dataset
var sumstat = d3.nest()
.key(function (d) {
return d.DATE;
})
.rollup(function (d) { // For each key..
input = d.map(function (g) {
return g.Power;
});
density = kde(input); // And compute the binning on it.
return (density);
})
.entries(data);
var maxNum = 0;
for (i in sumstat) {
allBins = sumstat[i].value;
kdeValues = allBins.map(function (a) {
return a[1]
});
biggest = d3.max(kdeValues);
if (biggest > maxNum) {
maxNum = biggest
}
}
// The maximum width of a violin must be x.bandwidth = the width dedicated to a group
var xNum = d3.scaleLinear()
.range([0, x.bandwidth()])
.domain([-maxNum, maxNum]);
svg
.selectAll("myViolin")
.data(sumstat)
.enter() // So now we are working group per group
.append("g")
.attr("transform", function (d) {
return ("translate(" + x(d.key) + " ,0)")
}) // Translation on the right to be at the group position
.append("path")
.datum(function (d) {
return (d.value)
}) // So now we are working density per density
.style("opacity", .7)
.style("fill", "#317fc8")
.attr("d", d3.area()
.x0(function (d) {
return (xNum(-d[1]))
})
.x1(function (d) {
return (xNum(d[1]))
})
.y(function (d) {
return (y(d[0]))
})
.curve(d3.curveCatmullRom));
});
function kernelDensityEstimator(kernel, X) {
return function (V) {
return X.map(function (x) {
return [x, d3.mean(V, function (v) {
return kernel(x - v);
})];
});
}
}
function kernelEpanechnikov(k) {
return function (v) {
return Math.abs(v /= k) <= 1 ? 0.75 * (1 - v * v) / k : 0;
};
}
</script>
Data (violinsummary.csv):
Power,DATE
89.29,2017-09
89.9,2017-09
91.69,2017-09
89.23,2017-09
91.54,2017-09
88.49,2017-09
89.15,2017-09
90.85,2017-09
89.59,2017-09
93.38,2017-10
92.41,2017-10
90.65,2017-10
91.07,2017-10
90.13,2017-10
91.73,2017-10
91.09,2017-10
93.21,2017-10
91.62,2017-10
89.58,2017-10
90.59,2017-10
92.57,2017-10
89.99,2017-10
90.59,2017-10
88.12,2017-10
91.3,2017-10
89.59,2018-02
91.9,2018-02
87.83,2018-02
90.36,2018-02
91.38,2018-02
91.56,2018-02
91.89,2018-02
90.95,2018-02
90.15,2018-02
90.24,2018-02
94.04,2018-02
85.4,2018-02
88.47,2018-02
92.3,2018-02
92.46,2018-02
92.26,2018-02
88.78,2018-02
90.13,2018-03
89.95,2018-03
92.98,2018-03
91.94,2018-03
90.29,2018-03
91.2,2018-03
94.22,2018-03
90.71,2018-03
93.03,2018-03
91.89,2018-03
I am trying to make a tooltip for each violin that shows the median and mean upon hover. I cannot figure out how to make the tooltip show up.
I know I need to do something like this with mouseover and mouseout but I'm not sure...
var tooltip = d3.select('#power')
.append('div')
.attr('class', 'tooltip')
.style("opacity", 0);
Any tips/guidance would be very appreciated.
You can implement the tooltip functionality by following two steps.
Step 1:
Initialize the tooltip container which already you did I guess.
var tooltip = svg.append("g")
.attr("class", "tooltip")
.style("display", "none");
tooltip.append("rect")
.attr("width", 30)
.attr("height", 20)
.attr("fill", "white")
.style("opacity", 0.5);
tooltip.append("text")
.attr("x", 15)
.attr("dy", "1.2em")
.style("text-anchor", "middle")
.attr("font-size", "12px")
.attr("font-weight", "bold");
Step 2:
Change the visibility property of the tooltip in the mouseover, mouseout event of the element. In your case, it's myViolin
.on("mouseover", function() {
tooltip.style("display", null);
})
.on("mouseout", function() {
tooltip.style("display", "none");
})
.on("mousemove", function(d) {
var xPosition = d3.mouse(this)[0] - 15;
var yPosition = d3.mouse(this)[1] - 25;
tooltip.attr("transform", "translate(" + xPosition + "," + yPosition + ")");
tooltip.select("text").text(d.y);
});
Here is the implementation of tooltip jsFiddle
Hope it helps :)
I just got the word cloud template from T3. Now I added a youtube link to every word in the word cloud, but I want the video pop up while I clicked the words in the word cloud. How should I modify my code? Thanks a lot.
Here is my Javascript:
<script>
var fill = d3.scale.category20();
var words = [{"text":"Worry", "url":"http://google.com/"},
{"text":"Choices", "url":"http://bing.com/"},
]
var width = 1080;
var height = 500;
for (var i = 0; i < words.length; i++) {
words[i].size = 10 + Math.random() * 90;
}
d3.layout.cloud()
.size([width, height])
.words(words)
.padding(5)
.rotate(function() { return ~~ ((Math.random() * 6) - 3) * 30 + 8; })
.font("Impact")
.fontSize(function(d) { return d.size;})
.on("end", draw)
.start();
function draw(words) {
d3.select("#word-cloud")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate("+ width/2 +","+ height/2 +")")
.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; })
.on("click", function (d, i){
window.open(d.url, "_blank");
});
}
My proposed approach:
Create a modal window
Open the modal when a word is clicked
Dynamically populate the embed code into the modal based on which word is clicked (I didn't do this part for you, but should be easily doable as it's part of the data to access).
I created a JSFiddle that gets you most of the way, with additional work needed here:
.on("click", function (d, i){
// Dynamically populate embed
// Open the modal
document.getElementById('myModal').style.display = "block";
});
I've tried to create a legend using inspiration from http://zeroviscosity.com/d3-js-step-by-step/step-3-adding-a-legend. However, despite having almost the exact same code, the legend isn't visualized. Here's the jsfiddle and the code: http://jsfiddle.net/u5hd25qs/
var width = $(window).width();
var height = $(window).height() / 2;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var legendRectSize = 36; // 18
var legendSpacing = 8; // 4
var recordTypes = []
recordTypes.push({
text : "call",
color : "#438DCA"
});
recordTypes.push({
text : "text",
color : "#70C05A"
});
var legend = svg.selectAll('.legend')
.data(recordTypes)
.enter()
.append('g')
.attr('class', 'legend')
.attr('transform', function (d, i) {
var height = legendRectSize + legendSpacing;
var offset = height * recordTypes.length / 2;
var horz = -2 * legendRectSize;
var vert = i * height - offset;
return 'translate(' + horz + ',' + vert + ')';
});
legend.append('rect')
.attr('width', legendRectSize)
.attr('height', legendRectSize)
.style('fill', function (d) {
return d.color
})
.style('stroke', function (d) {
return d.color
});
legend.append('text')
.attr('x', legendRectSize + legendSpacing)
.attr('y', legendRectSize - legendSpacing)
.text(function (d) {
return d.text;
});
Your code works okay, but this is what you generate:
<g class="legend" transform="translate(-72,-44)">...
Because your translate rule has negative values in it, the legend is positioned outside the screen (it is simply not visible).
Now, the example you're basing your work on has a pie chart that has already been translated to the center of the screen, so negative values are not an issue.
You need to change your math or wrap the legend in some container which you can position in the same way as the pie chart example:
legendContainer
.attr('transform', 'translate(' + (width / 2) + ',' + (height / 2) + ')');
I have the following WordCloud.
The original Code is from Jason Davies D3-JavaScript-WordCloud Example.
https://github.com/jasondavies/d3-cloud/blob/master/examples/simple.html
Most of the Code below is from this really helpfull tutorial (in German):
http://www.advitum.de/blog/2012/04/tagcloud-mit-php-und-javascript-erstellen-word-cloud-d3/
Thank You Lars Ebert!
EDIT: Now I have learned a little bit more. I have cleared nonsensical code.
My goal is to center the first word from array horizontally.
The first word is now centered horizontally, but now there is a gap in the Cloud.
Just at the x,y point where the word would be without positioning.
My new question is: How do I remove this gap?
Thank You.
var wordcloud, size = [800, 800]; //Cloud Size
var fillColor = d3.scale.category20b();
function loaded() {
d3.layout.cloud()
.size(size)
.words(words)
.font("Impact")
.fontSize(function(d) { return d.size;})
.rotate(function() { return ~~(Math.random() * 2) * 90; })
.on("end", draw)
.start();
}
function draw(words) {
wordcloud = d3.select("body")
.append("svg")
.attr("width", size[0])
.attr("height", size[1])
.append("g")
.attr("transform", "translate(" + (size[0]/2) + "," + (size[1]/2) + ")");
wordcloud.selectAll("text")
.data(words)
.enter()
.append("text")
.style("font-size", function(d) { return d.size + "px"; })
.style("fill", function(d) { return fillColor(d.text.toLowerCase()); })
.attr("text-anchor", "middle")
//Edit
.attr("transform", function(d, i) {
if(i == 0){
return "translate(" + [0, 0] + ")rotate(" + 0 + ")"; //handle first element
}else{
return "translate(" + [d.x, d.y] + ")rotate(" + d.rotate + ")"; //handle the rest
}
})
//--------------
.text(function(d) { return d.text; });
}
Use a negative margin equal to one of the size calculations:
wordcloud.selectAll("text")
...
...
...
.style("margin-left", function(d) {return d.size + "px"})