Can't draw barchart using d3.js in React - javascript

I am trying to draw barchart with each bar next to its corresponding checkbox. I have several <div className="geneCountBar">s and I try to select all of them, append the data and then draw bars using the following function:
createBarChart = () => {
let datum = this.props.datum.map((geneObj) => {
return geneObj["values"].length;
});
d3.selectAll("div.geneCountBar")
.data(datum)
.enter()
.append("div")
.attr("class", "bar")
.attr('transform', 'rotate(-90)')
.style("height", function(d) {
var barHeight = d * 5;
return barHeight + "px";
});
}
I just took it from the most basic tutorial on barchart creation with d3:
http://alignedleft.com/tutorials/d3/making-a-bar-chart
Somehow after the function runs - I trigger it with the button when the DOM has already been rendered - no DOM manipulation happens at all. I checked datum and it is correct: just an array of several values. The number of elements in the datum corresponds to the number of the selected divs. I checked whether d3.selectAll(div.geneCountBar) actually selects the right elements and it is. The DOM looks like that:
To further clarify what I actually want to achieve. Here you can see checkboxes:
Next to each one of them I want to draw a bar which would represent the amount of each item present in the dataset.
What am I doing wrong here? Any suggestions will be greatly appreciated.

On this case you are not using svg. Need to Style your divs. Try this:
Remove Transform, style width intead heigh
Style background-color for div,
add & nbsp; to prevent colapse empty div
change
createBarChart = () => {
let datum = this.props.datum.map((geneObj) => {
return geneObj["values"].length;
});
d3.selectAll("div.geneCountBar")
.data(datum)
.enter()
.append("div")
.attr("class", "geneCountBar") // <----CHANGE class
.style("background-color","red")
.style("width", function(d) { return (d*5)+"px";})
.html(" ")
}
Every bar must look like this:
<div class="geneCountBar" style="backgroud-color:red;width:50px;"> </div>
Or, on CSS define .geneCountBar{background-color:red;}
<div class="geneCountBar" style="width:50px;"> </div>
I've not access to your rig. Here a working code.
var myData=[15, 30, 20, 10]
var graph = d3.select("#graph")
graph.selectAll("div#graph")
.data(myData) // maybe this's data(myData), not data(datum)
.enter()
.append("div")
.attr("class", "geneCountBar") // <----CHANGE class
.style("width", function(d) {return (d*5)+"px";})
.html(" ")
.geneCountBar {
background-color: red;
margin:3px}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<div id="graph">
</div>
Hope this help

I ended up drawing bars without the use of d3s entering the data.
I put the following divs on the page:
<div className="geneCountBar"
style={{width: this.getHeightForGeneBar(idx, innerIdx), height:"10px",
backgroundColor: getColorForGene(this.getGeneIdx(idx, innerIdx)),
borderRadius: "2px"}}> </div>
And set up individual bars height and color in code with the two functions: getHeightForGeneBar(idx, innerIdx) and getColorForGene(this.getGeneIdx(idx, innerIdx)).

Related

d3 - Create new element from the dataset for each node in the selection

I'm creating a bar chart in d3 v5, and am trying to add labels inside each bar (the chart is horizontal).
Currently, each bar is contained in a <g> element. My dataset has multiple elements in it (these are simple objects that have a name and a value), so when I do:
g.selectAll('text').data(dataset).enter().append('text')
N <text> elements are added to each <g>. Instead, I would like each <g> to have a single label that uses a single element in the dataset.
Can this be done using d3? Should I do this somewhere else (when I'm creating the bars of the cart, for instance), and how?
not sure where g comes from in your post. With the enter-update-exit, use selectAll to bind data to outer most element that represents data item (<g>), and then use select on the group selection to propagate the bound data item to its (dom) children. Maybe not a best analogy, but if selectAll gives you an array, select performs a map over it
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
<style>
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; }
</style>
</head>
<body>
<script>
// Feel free to change or delete any of the code you see in this editor!
var svg = d3.select("body").append("svg")
.attr("width", 960)
.attr("height", 500)
var data = [{
c: 'green',
l: "Green"
}, {
c: 'red',
l: 'Red'
}];
var g = svg.selectAll("g")
.data(data);
g.exit().remove();
var newG = g.enter()
.append('g');
newG.append('rect')
.attr('width', 100)
.attr('height', 40)
.style('fill', 'none');
newG.append('text')
.attr('y', 30);
// normally I'd reuse the initial `g` variable, but just for clarity
var newGandUpdatedG = newG.merge(g);
newGandUpdatedG.select('text')
.text(d => d.l);
newGandUpdatedG.select('rect')
.style('stroke', d => d.c);
newGandUpdatedG.attr('transform', (d,i) => `translate(${i * 120}, 0)`);
</script>
</body>

labels inside the heatmap rects

I would like to display the value of heatMap boxes inside the boxes not just in the tool tip.
Looks although this feature has yet to be added according to: https://github.com/dc-js/dc.js/issues/1303 , would this be able to be accomplished with d3.js? Thanks.
HeatMap:
heatMap
.width(1000)
.height(400)
.dimension(dimension)
.group(filtered_heatMap_group)
.margins({
left: 200,
top: 30,
right: 10,
bottom: 50
})
.keyAccessor(function (d) {
return d.key[0];
})
.valueAccessor(function (d) {
return d.key[1];
})
.colorAccessor(function (d) {
return +d.value.color;
})
.title(function (d) {
return "Manager: " + d.key[1] + "\n" +
"FTE: " + d.value.toolTip + "\n" +
"Date: " + d.key[0] + "";
})
.rowOrdering(d3.descending);
https://jsfiddle.net/_M_M_/jvf5ot3p/3/
The devil is in the details.
It's pretty easy to get some text onto the boxes. Getting them nicely positioned and formatted will take some more work. I'll try to get you started anyway.
You can use the renderlet event. pretransition would be nicer, but we will need to steal some attributes from the rects because the chart's scales are not accessible, and these attributes aren't all initialized at pretransition time.
This is a common pattern for annotations in dc.js: wait for a chart event, then select existing elements and add stuff to them. The element selectors are documented here; we want g.box-group.
The rest is standard D3 general update pattern, except that as I mentioned, we don't have access to the scales. Rather than trying to duplicate that language and possibly getting it wrong, we can read attributes like x, y, width, height from the rects that already reside in these gs.
SVG doesn't have multiline text, so we have to create tspan elements inside of text elements.
Putting that all together:
.on('renderlet.label', function(chart) {
var text = chart.selectAll('g.box-group')
.selectAll('text.annotate').data(d => [d]);
text.exit().remove();
// enter attributes => anything that won't change
var textEnter = text.enter()
.append('text')
.attr('class', 'annotate')
textEnter.selectAll('tspan')
.data(d => [d.key[1], d.value.toolTip, d.key[0]])
.enter().append('tspan')
.attr('text-anchor', 'middle')
.attr('dy', 10);
text = textEnter.merge(text);
// update attributes => position and text
text
.attr('y', function() {
var rect = d3.select(this.parentNode).select('rect');
return +rect.attr('y') + rect.attr('height')/2 - 15;
});
text.selectAll('tspan')
.attr('x', function() {
var rect = d3.select(this.parentNode.parentNode).select('rect');
return +rect.attr('x') + rect.attr('width')/2;
})
.text(d => d);
})
Notice that I gave up and nudged the text 15px up... there may or may not be a better way to get vertical centering of multiline text.
The text doesn't quite fit, even without descriptions. I'll let you figure that out.
But it's there and it updates correctly:
Fork of your fiddle.

How to make the circles disappear based on keyboard input?

based on this example: http://bl.ocks.org/d3noob/10633704 i wish to make an input on my keyboard (a number) and make the circles disappear with the help of array.slice(). Unfortunally it did not worked well. In my code, i created some circles based on the values of the array days. With the HTML part i am able to create a button, where i can make a number input. With the last part days.slice(nValue) i want that the input number is the same like the number inside the brackets of the slice() function, so the array days is getting shorter and automatically let circles based on the value of the array disappear. But unfortunally there is a mistake i made in this code. Can someone maybe be so kind and help? I am using D3 to solve this problem.
Thanks
<!DOCTYPE html>
<meta charset="utf-8">
<title>Input (number) test</title>
<p>
<label for="nValue"
style="display: inline-block; width: 120px; text-align: right">
angle = <span id="nValue-value"></span>
</label>
<input type="number" min="0" max="360" step="4" value="0" id="nValue">
</p>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var width = 600;
var height = 300;
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var days = [7, 12, 20, 31, 40, 50];
console.log(days);
var circle = svg.selectAll("circle")
.data(days)
.enter().append("circle")
.attr("cy", 60)
.attr("cx", function(d, i) { return i * 100 + 40; })
.attr("r", function(d) { return Math.sqrt(d); });
d3.select("#nValue").on("input", function() {
update(+this.value);
});
// Initial update value
update(0);
function update(nValue) {
days.slice(nValue);
}
It took me a while to see what you're after here, and I might still be off a bit in my understanding.
The Problem
As I see understand it, you are modifying an array of data (with a select menu in this case), but the modified array does not appear to modify your visualization. Essentially, as "the array days is getting shorter ... let circles based on the value[s] of the array disappear."
Updating the visualization
To update the visualization you need to bind the new data to your selection. After this you can remove unneeded elements in the visualization, add new ones (not relevant to this question), or modify existing elements. Changing the data array by itself will not update the visualization. To have the visualization utilize the new information you need to bind that data to the selection:
circle.data(data);
Then you can remove the old items:
circle.exit().remove();
Then you can modify properties of the old items:
circle.attr('cx',function(d,i) {...
Your update function needs to at least update the data and remove unneeded elements.
Changing the Array
In the following snippet I append both a select menu and the circles with d3 based on the data in the array. Selecting an item in the menu will remove a circle:
var data = [10,20,30,40,50,60,70,80,90,100];
var color = d3.schemeCategory10; // color array built in
//// Add the select and options:
var select = d3.select('body')
.append('select')
.on('change',function() { update(this.value) });
var start = select.append('option')
.html("select: ");
var options = select.selectAll('.option')
.data(data)
.enter()
.append('option')
.attr('class','option')
.attr('value',function(d,i) { return i; })
.html(function(d) { return d; });
//// Add the circles (and svg)
var svg = d3.selectAll('body')
.append('svg')
.attr('width',500)
.attr('height',200);
var circles = svg.selectAll('circle')
.data(data)
.enter()
.append('circle')
.attr('cx',function(d,i) { return i * 30 + 50; })
.attr('cy',50)
.attr('r',10)
.attr('fill',function(d,i) { return color[i]; });
// Update everything:
function update(i) {
data.splice(i,1); // remove that element.
// Update and remove option from the select menu:
options.data(data).exit().remove();
// Remove that circle:
circles.data(data).exit().remove();
circles.attr('cx',function(d,i) { return i * 30 + 50; })
.attr('fill',function(d,i) { return color[i]; });
// reset the select menu:
start.property('selected','selected');
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.5.0/d3.min.js"></script>
There is a problem here, only the last circle and menu item is removed each time. Why? Imagine a four element array, if you remove the second item, d3 does not know that you removed the second item, you might have modified elements two and three and removed element four.
Since all your items are appended with their increment (which position they are in the array), and this doesn't account for holes that were created when other items were removed, you need to change the approach a little.
A solution
Instead of relying on the increment of an item in the array (as this will change every time an element that is before another element is removed from the array), you could use an id property in your data.
This would require restructuring you data a little. Something like:
var data = [ {id:1,value:1},{id2....
As the id property won't change, this makes a better property to set attributes. Take a look at the following snippet:
var data = [{id:0,value:10},{id:1,value:20},{id:2,value:23},{id:3,value:40},{id:4,value:50},{id:5,value:60},{id:6,value:70},{id:7,value:77},{id:8,value:86},{id:9,value:90}];
var color = d3.schemeCategory10; // color array built in
//// Add the select and options:
var select = d3.select('body')
.append('select')
.on('change',function() { update(this.value); } ); // add an event listener for changes
// append a default value:
var start = select.append('option')
.html("Select:");
var options = select.selectAll('.option')
.data(data)
.enter()
.append('option')
.attr('class','option')
.attr('value',function(d,i) { return i; })
.html(function(d) { return d.value; });
//// Add the circles (and svg)
var svg = d3.selectAll('body')
.append('svg')
.attr('width',500)
.attr('height',200);
var circles = svg.selectAll('circle')
.data(data)
.enter()
.append('circle')
.attr('cx',function(d) { return d.id * 30 + 50; })
.attr('cy',50)
.attr('r',10)
.attr('fill',function(d) { return color[d.id]; });
// Update everything:
function update(i) {
data.splice(i,1); // remove the element selected
// Update and remove option from the select menu:
options.data(data).exit().remove();
// Remove that circle:
circles.data(data).exit().remove();
// update the options (make sure each option has the correct attributes
options.attr('value',function(d,i) { return i; })
.html(function(d) { return d.value; })
// Make sure circles are in the right place and have the right color:
circles.attr('cx',function(d) { return d.id * 30 + 50; })
.attr('fill',function(d) { return color[d.id]; });
// reset the default value so the change will work on all entries:
start.property('selected', 'selected');
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.5.0/d3.min.js"></script>
Try changing your update function to this:
function update(nValue) {
days = days.slice(nValue);
}

How to display names next to bubbles in d3js motion chart

In Mike Bostocks example http://bost.ocks.org/mike/nations/ there is so much data that putting the names of the countries there would make it chaotic, but for a smaller project I would like to display it.
I found this in the source:
var dot = svg.append("g")
.attr("class", "dots")
.selectAll(".dot")
.data(interpolateData(2004))
.enter().append("circle")
.attr("class", "dot")
.style("fill", function(d) { return color(d); })
.text(function(d) { return d.name; })
.call(position)
.sort(order);
dot.append("title")
.text(function(d) { return d.name; });
But somehow a title never shows up. Does anybody have an idea, how to display the name, next to the bubble?
As the other answer suggests, you need to group your elements together. In addition, you need to append a text element -- the title element only displays as a tooltip in SVG. The code you're looking for would look something like this.
var dot = svg.append("g")
.attr("class", "dots")
.selectAll(".dot")
.data(interpolateData(2004))
.enter()
.append("g")
.attr("class", "dot")
.call(position)
.sort(order);
dot.append("circle")
.style("fill", function(d) { return color(d); });
dot.append("text")
.attr("y", 10)
.text(function(d) { return d.name; });
In the call to position, you would need to set the transform attribute. You may have to adjust the coordinates of the text element.
Unfortunately grouping the text and circles together will not help in this case. The bubbles are moved by changing their position attributes (cx and cy), but elements do not have x and y positions to move. They can only be moved with a transform-translate. See: https://www.dashingd3js.com/svg-group-element-and-d3js
Your options here are:
1) rewrite the position function to calculate the position difference (change in x and change in y) between the elements current position and its new position and apply that to the . THIS WOULD BE VERY DIFFICULT.
or 2) Write a parallel set of instructions to setup and move the tags. Something like:
var tag = svg.append("g")
.attr("class", "tag")
.selectAll(".tag")
.data(interpolateData(2004))
.enter().append("text")
.attr("class", "tag")
.attr("text-anchor", "left")
.style("fill", function(d) { return color(d); })
.text(function(d) { return d.name; })
.call(tagposition)
.sort(order);
You will need a separate tagposition function since text needs 'x' and 'y' instead of 'cx', 'cy', and 'r' attributes. Don't forget to update the "displayYear" function to change the tag positions as well. You will probably want to offset the text from the bubbles, but making sure the text does not overlap is a much more complicated problem: http://bl.ocks.org/thudfactor/6688739
PS- I called them tags since 'label' already means something in that example.
you have to wrap the circle element and text together , it should look like
<country>
<circle ></circle>
<text></text>
</country>

How to make axis tick labels hyperlinks in D3.js

In D3.js, how do I assign HTML elements/attributes to tick labels? I'm specifically interested in making them hyperlinks, but it could be generalized to making them images, or something strange like alternating div classes.
var data = [{"count": 3125,"name": "aaa"}, {"count": 3004,"name": "bbb"}...
y = d3.scale.ordinal()
.rangeRoundBands([0, height], 0.1)
.domain(data.map(function (d) {
return d.name;
}))
yAxis = d3.svg.axis().scale(y)
vis.append("g")
.attr("class", "y axis")
.call(yAxis)
http://jsfiddle.net/SFDrv/
So in the JSfiddle, how would I link to www.example.com/aaa, www.example.com/bbb, www.example.com/ccc, etc?
For the JSfiddle you posted, you can create a selection of all text that are strings (these are the y-axis ticks), and then use .on("click", function), to link each label. Here's a working example:
d3.selectAll("text")
.filter(function(d){ return typeof(d) == "string"; })
.style("cursor", "pointer")
.on("click", function(d){
document.location.href = "http://www.example.com/" + d;
});
I forked your JSFiddle and have the whole example there: http://jsfiddle.net/mdml/Qm9U7/.
A better solution would be to have an array of y-axis values and to use those to filter the text elements in the document, instead of testing whether each text element's data is a string. The best way to do that depends on the rest of the code, however, so it may differ from application to application.
To create axis with images you need to create them yourself and not use the d3.svg.axis(). This creates floated li tags with certain width...
// generate axis
x = d3.scale.linear().range([min, max]).domain([minValue, maxScaleValue]);
xAxis = d3.scale.identity().domain([minValue, maxScaleValue]);
var ticks = xAxis.ticks(10);
if (ticks.length) {
var tickDiff = Math.abs(ticks[1] - ticks[0]);
scope.legend
.selectAll('li')
.data(ticks)
.enter()
.append('li')
.text(xAxis)
.style('width', x(tickDiff));
}
scope.legend
.selectAll("li")
.data(ticks)
.exit()
.remove();
Also there is useful article about d3 axis that helped me a lot.

Categories