I am trying to turn this graph https://bl.ocks.org/scresawn/0e7e4cf9a0a459e59bacad492f73e139, into a react component. So far what I have is not rendering anything to the screen. If someone can take a look and make suggestions on improving the code that would be great. Also, I will be passing data to the component from another component instead fetching a CSV file. Any help will be greatly appreciated.
class ScaleTime extends Component {
constructor(){
super();
this.state = {
data: []
};
};
componentDidMount() {
fetch(data)
.then( (response) => {
return response.json() })
.then( (json) => {
this.setState({data: json});
});
};
//What is happening here?
componentWillMount(){
d3.csv("phage-history.csv", function (error, data) {
svgEnter = svg.selectAll("rect")
.data(data)
.enter();
svgEnter.append("rect")
.attr("rx", 25)
.attr("x", function (d) {
x = xScale(new Date(d.start));
return x;
})
.attr("y", function(d, i) { return 400 - (d.count*30); })
.attr("width", 25)
.attr("height", 25)
.attr("fill", "green")
.attr("stroke-width", "1px")
.attr("stroke", "black")
.on("mouseover", function (d) {
rect = d3.select(this);
rect.transition()
.duration(500)
.attr("y", function(d, i) {
console.log(this);
var x = rect.x;
return 20; })
.transition()
.duration(500)
.attr("rx", 2)
.attr("width", 300)
.attr("height", 100)
.attr("fill", "skyblue");
tooltip.html(d.authors + "<br>" + d.description);
tooltip
.style("top", 30)
.style("left",function () {
console.log("x", x);
return d3.event.pageX;
})
setTimeout(function () {
tooltip.style("visibility", "visible");
}, 1500);
})
.on("mouseout", function (d) {
d3.select(this)
.transition()
.duration(500)
.attr("rx", 25)
.attr("width", 25)
.attr("height", 25)
.transition()
.duration(500)
.delay(500)
.attr("y", function(d, i) { return 400 - (d.count*30); })
.attr("fill", "green");
//tooltip.text(d.authors);
return tooltip.style("visibility", "hidden");
});
});
}//componentWillMount
componentDidUpdate() {
var tooltip = d3.select("body")
.append("div")
.style("font-size", "12px")
.style("width", 285)
.style("position", "absolute")
.style("z-index", "10")
.style("visibility", "hidden");
var svg = d3.select("body").append("svg")
.attr("width", 960)
.attr("height", 500);
var xScale = d3.scaleTime()
.domain([new Date("January 1, 1940 00:00:00"), new Date("January 4, 1980 00:00:00")])
.range([20, 900]);
var xAxis = d3.axisBottom(xScale);
}
render() {
return (
<div className="ScaleTime">
<svg width="960" height="500" />
svg.append("g")
.attr("transform", "translate(0,450)")
.call(xAxis);
</div>
);
}
}
Related
My question is- I have generated a scatter plot using D3 version 4. Now, I need to add tooltip to points, on mouseover. But, I am getting an error message- "setAttribute is not a function" Can Someone provide me a clue for the same, and check if the function declaration is right?
https://jsfiddle.net/kunal16/be37wmaj/
var width = 700;
var height = 700;
var padding = 70;
var myData = [12, 15, 20, 9, 17, 25, 30];
var errData = [6, 7.5, 10, 4.5, 8.5, 12.5, 15];
var svg = d3.select("body").
append("svg")
.attr("width", width)
.attr("height", height);
var yScale = d3.scaleLinear().domain([0, d3.max(myData)]).range([height/2, 0]);
var xScale = d3.scaleLinear().domain([0, d3.max(myData)]).range([0, width - 200]);
var y_ErScale = d3.scaleLinear().domain([0, d3.max(errData)]).range([height/2, 0]);
var x_ErScale = d3.scaleLinear().domain([0, d3.max(errData)]).range([0, width - 200]);
// var div = d3.select("body").append("svg")
// .attr("class", "tooltip")
// .style("opacity", 0);
var eBar = d3.select("body").append("svg");
//var x_min =
var x_axis = d3.axisBottom()
.scale(xScale);
var y_axis = d3.axisLeft()
.scale(yScale);
svg.append("g")
.attr("transform", "translate(50, 10)")
.call(y_axis);
var xAxisTranslate = height/2 + 10;
svg.append("g")
.attr("transform", "translate(50, " + xAxisTranslate +")")
.call(x_axis);
svg.append("g")
.selectAll("scatter-dots")
.data(myData)
.enter().append("svg:circle")
.attr("cx", function (d,i) { return xScale(myData[i]); } )
.attr("cy", function (d) { return yScale(d); } )
.attr("r", 3)
.style("opacity", 0.8);
// var errorBar = eBar.append("path")
// .attr("d", yScale(errData))
// .attr("stroke", "red")
// .attr("stroke-width", 1.5);
// svg.append("g")
// .selectAll("error-bars")
// .data(errData)
// .enter().append("svg:path")
// .attr("cx", function (d,i) { return x_ErScale(errData[i]); } )
// .attr("cy", function (d) { return y_ErScale(d); } )
// .attr("stroke", "red")
// .attr("stroke-width", 1.5);
svg.append("g")
.selectAll("line")
.data(errData)
.enter()
.append("line")
.attr("class", "error-line")
.attr("x1", function(d) {
return x_ErScale(d);
})
.attr("y1", function(d) {
return y_ErScale(d) + 30;
})
.attr("x2", function(d) {
return x_ErScale(d);
})
.attr("y2", function(d) {
return y_ErScale(d) + 2;
});
svg.append("g").selectAll("line")
.data(errData).enter()
.append("line")
.attr("class", "error-cap")
.attr("x1", function(d) {
return x_ErScale(d) - 20;
})
.attr("y1", function(d) {
return y_ErScale(d) - 30;
})
.attr("x2", function(d) {
return x_ErScale(d) + 20;
})
.attr("y2", function(d) {
return y_ErScale(d) - 30;
});
svg.append("g")
.selectAll("line")
.data(errData)
.enter()
.append("line")
.attr("class", "error-line")
.attr("x1", function(d) {
return x_ErScale(d);
})
.attr("y1", function(d) {
return y_ErScale(d) - 30;
})
.attr("x2", function(d) {
return x_ErScale(d);
})
.attr("y2", function(d) {
return y_ErScale(d) - 2;
});
// Add Error Bottom Cap
svg.append("g").selectAll("line")
.data(errData).enter()
.append("line")
.attr("class", "error-cap")
.attr("x1", function(d) {
return x_ErScale(d) - 20;
})
.attr("y1", function(d) {
return y_ErScale(d) + 30;
})
.attr("x2", function(d) {
return x_ErScale(d) + 20;
})
.attr("y2", function(d) {
return y_ErScale(d) + 30;
});
// console.log(svg.append("g").selectAll("scatter-dots"));
var div = svg.append("g").selectAll("scatter-dots")
.data(myData)
.enter()
// .append("circle")
.attr("class", "tooltip")
.style("opacity", 0)
.on("mouseover", function(d)
{
div
.transition()
.duration(200)
.html(myData)
.style("opacity", 0.8);
})
.on("mouseout", function(d)
{
div
.transition()
.duration(500)
.style("opacity", 0);
});
Please refer the above fiddle.Thanks!
Adding divs to each circle is unneccessary once you have joined the data to create the circles. You can put listeners on the circle with mouseover and mouseout functions.
https://jsfiddle.net/xqqfq9hf/
var tooltip = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
svg.append("g")
.selectAll("scatter-dots")
.data(myData)
.enter().append("svg:circle")
.attr("cx", function (d,i) { return xScale(myData[i]); } )
.attr("cy", function (d) { return yScale(d); } )
.attr("r", 3)
.style("opacity", 0.8)
.on("mouseover", function(d) {
tooltip.transition()
.duration(200)
.style("opacity", .9);
tooltip.html(d)
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
tooltip.transition()
.duration(500)
.style("opacity", 0);
});
I am trying to add a tooltip to this simple bubblechart that displays the key/value pair on mouseover of each bubble. I'm also having trouble adding a border to individual bubbles on mouseover as well, another styling component I'd like to add to this visualization.
Here is my code:
var width = 400,
height = 400;
var svg = d3.select("#borough")
.append("svg")
.attr("height", height)
.attr("width", width)
.append("g")
.attr("transform", "translate(0,0)")
var toolTip = d3.select('body').append('div').attr('class',
'tooltipbor').style('opacity', 0)
var simulation = d3.forceSimulation()
.force("x", d3.forceX(width / 2).strength(0.05))
.force("y", d3.forceY(height / 2).strength(0.05))
.force("collide", d3.forceCollide(function(d) {
return radiusScale(d.number)+ 1;
}))
var radiusScale = d3.scaleSqrt().domain(["0.26", "1.07"]).range([5,60])
d3.queue()
.defer(d3.csv, "borough.csv")
.await(ready)
function ready (error, datapoints) {
var circles = svg.selectAll(".rate")
.data(datapoints)
.enter().append("circle")
.attr("class", "rate")
.attr("r", function(d) {
return radiusScale(d.number)
})
.attr("fill", function (d) {
if (d.borough === "StatenIsland") {
return "#EAC435"
} else if (d.borough === "Queens") {
return "#345995"
} else if (d.borough === "Manhattan") {
return "#03CEA4"
} else if (d.borough === "Brooklyn") {
return "#FA7921"
} else if (d.borough == "Bronx") {
return "#E40066"
}
})
.on ('mouseover', function (d) {
div.style("display", "inline")
})
.on('mousemove', function (d) {
toolTip.transition()
.duration(200)
.style('opacity', 0.9)
toolTip.html(`${d.borough} <br/>${d.number}`)
.style('left', (d3.event.pageX + 10) + 'px')
.style('top', (d3.event.pageY + 10) + 'px')
})
.on('mouseout', function (d) {
toolTip.transition()
.duration(500)
.style('opacity', 0)
})
simulation.nodes(datapoints)
.on('tick', ticked)
function ticked() {
circles
.attr("cx", function(d) {
return d.x
})
.attr("cy", function(d) {
return d.y
})
}
}
borough.csv :
borough,number
Bronx,1.07
Brooklyn,0.59
Manhattan,1.025
Queens,0.40
StatenIsland,0.26
Thank you in advance! Probably needless to say, but I am very new to JavaScript much less d3!
I want to update the bar chart with new data and I looked over this question:
How to update d3.js bar chart with new data
But it is not working for me. Probably because I don't know where to put the exit().remove() functions within my code. I tried putting this line
svg.selectAll("rect").exit().remove();
below the create bars section but it just removes all of the labels. Then, if I put it right after the create labels portion it removes the chart entirely. How can I get the update button change the chart with new data?
function draw(data) {
//Width and height
var w = 250;
var h = 250;
var xScale = d3.scale.ordinal()
.domain(d3.range(data.length))
.rangeRoundBands([0, w], 0.05);
var yScale = d3.scale.linear()
.domain([0, d3.max(data)])
.range([0, h]);
//Create SVG element
var svg = d3.select("#chart")
.append("svg")
.attr("width", w)
.attr("height", h);
//Create bars
svg.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("x", function(d, i) {
return xScale(i);
})
.attr("y", function(d) {
return h - yScale(d);
})
.attr("width", xScale.rangeBand())
.attr("height", function(d) {
return yScale(d);
})
.attr("fill", "steelblue");
//Create labels
svg.selectAll("text")
.data(data)
.enter()
.append("text")
.text(function(d) {
return d;
})
.attr("text-anchor", "middle")
.attr("x", function(d, i) {
return xScale(i) + xScale.rangeBand() / 2;
})
.attr("y", function(d) {
return h - yScale(d) + 14;
})
.attr("font-family", "sans-serif")
.attr("font-size", "11px")
.attr("fill", "white");
};
function update() {
var data = [ 25, 22, 18, 15, 13 ];
draw(data);
}
var data = [ 21, 3, 5, 21, 15 ];
window.onload = draw(data);
With d3v3 (as I can see from your code you use this version) you should update your chart this way:
In update function set the new domain for yScale:
function update(newData) {
yScale.domain([0, d3.max(newData)]);
After that, apply new selection with selectAll("rect").data(newData), store selection in rects variable and set new value for appropriate attributes (if you do not want animation effect, remove .transition() .duration(300)):
var rects = d3.select("#chart svg")
.selectAll("rect")
.data(newData);
// enter selection
rects
.enter().append("rect");
// update selection
rects
.transition()
.duration(300)
.attr("y", function(d) {
return h - yScale(d);
})
.attr("height", function(d) {
return yScale(d);
})
Exit selection with exit method:
rects
.exit().remove();
Do the same way with text. I rewrite your code, look at the example in a hidden snippet below:
var myData = [21, 3, 5, 21, 15];
//Width and height
var w = 250;
var h = 250;
var yScale = null;
function draw(initialData) {
var xScale = d3.scale.ordinal()
.domain(d3.range(initialData.length))
.rangeRoundBands([0, w], 0.05);
yScale = d3.scale.linear()
.domain([0, d3.max(initialData)])
.range([0, h]);
//Create SVG element
var svg = d3.select("#chart")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("rect")
.data(initialData)
.enter()
.append("rect")
.attr("x", function(d, i) {
return xScale(i);
})
.attr("y", function(d) {
return h - yScale(d);
})
.attr("width", xScale.rangeBand())
.attr("height", function(d) {
return yScale(d);
})
.attr("fill", "steelblue");
svg.selectAll("text")
.data(initialData)
.enter()
.append("text")
.text(function(d) {
return d;
})
.attr("text-anchor", "middle")
.attr("x", function(d, i) {
return xScale(i) + xScale.rangeBand() / 2;
})
.attr("y", function(d) {
return h - yScale(d) + 14;
})
.attr("font-family", "sans-serif")
.attr("font-size", "11px")
.attr("fill", "white");
}
function update(newData) {
yScale.domain([0, d3.max(newData)]);
var rects = d3.select("#chart svg")
.selectAll("rect")
.data(newData);
// enter selection
rects
.enter().append("rect");
// update selection
rects
.transition()
.duration(300)
.attr("y", function(d) {
return h - yScale(d);
})
.attr("height", function(d) {
return yScale(d);
})
// exit selection
rects
.exit().remove();
var texts = d3.select("#chart svg")
.selectAll("text")
.data(newData);
// enter selection
texts
.enter().append("rect");
// update selection
texts
.transition()
.duration(300)
.attr("y", function(d) {
return h - yScale(d) + 14;
})
.text(function(d) {
return d;
})
// exit selection
texts
.exit().remove();
}
window.onload = draw(myData);
setInterval(function() {
var data = d3.range(5).map(function() {
return parseInt(Math.random() * 20 + 1);
});
update(data);
}, 3000)
<div id="chart"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.js"></script>
I have been trying to project a heat map with data loaded from csv onto a orthogonal projection on D3. While rotating the earth (i.e. D3, orthogonal projection), the points/circles remain static. I have tried many combinations but failed to figure out what is missing.
Basically, i need the small circles move along the path of countries.
Here is the complete code :
<script>
var width = 600,
height = 500,
sens = 0.25,
focused;
//Setting projection
var projection = d3.geo.orthographic()
.scale(245)
.rotate([0,0])
.translate([width / 2, height / 2])
.clipAngle(90);
var path = d3.geo.path()
.projection(projection);
//SVG container
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
// Define the gradient
var gradient = svg.append("svg:defs")
.append("svg:linearGradient")
.attr("id", "gradient")
.attr("x1", "0%")
.attr("y1", "0%")
.attr("x2", "100%")
.attr("y2", "100%")
.attr("spreadMethod", "pad");
// Define the gradient colors
gradient.append("svg:stop")
.attr("offset", "0%")
.attr("stop-color", "#FFFF00")
.attr("stop-opacity", 0);
gradient.append("svg:stop")
.attr("offset", "100%")
.attr("stop-color", "#FF0000")
.attr("stop-opacity", 1);
//Adding water
svg.append("path")
.datum({type: "Sphere"})
.attr("class", "water")
.attr("d", path)
var countryTooltip = d3.select("body").append("div").attr("class", "countryTooltip"),
countryList = d3.select("body").append("select").attr("name", "countries");
queue()
.defer(d3.json, "world-110m.json")
.defer(d3.tsv, "world-110m-country-names.tsv")
.await(ready);
//Main function
function ready(error, world, countryData) {
var countryById = {},
countries = topojson.feature(world, world.objects.countries).features;
//Adding countries to select
countryData.forEach(function(d) {
countryById[d.id] = d.name;
option = countryList.append("option");
option.text(d.name);
option.property("value", d.id);
});
//circles for heatmap are coming from the csv below
d3.csv("cities.csv", function(error, data) {
svg.selectAll("circle")
.data(data)
.enter()
.append("a")
.attr("xlink:href", function(d) {
return "https://www.google.com/search?q="+d.city;}
)
.append("circle")
.attr("cx", function(d) {
return projection([d.lon, d.lat])[0];
})
.attr("cy", function(d) {
return projection([d.lon, d.lat])[1];
})
.attr("r", 5.5)
.attr('fill', 'url(#gradient)');
var world = svg.selectAll("path.circle")
.data(countries) //countries from the tsc file is used to populate the names
.enter().append("path")
.attr("class", "land")
.attr("d", path)
//.attr('fill', 'url(#gradient)')
//Drag event
.call(d3.behavior.drag()
.origin(function() { var r = projection.rotate(); return {x: r[0] / sens, y: -r[1] / sens}; })
.on("drag", function() {
var rotate = projection.rotate();
projection.rotate([d3.event.x * sens, -d3.event.y * sens, rotate[2]]);
svg.selectAll("path.land").attr("d", path);
svg.selectAll(".focused").classed("focused", focused = false);
}))
//Mouse events
.on("mouseover", function(d) {
countryTooltip.text(countryById[d.id])
.style("left", (d3.event.pageX + 7) + "px")
.style("top", (d3.event.pageY - 15) + "px")
.style("display", "block")
.style("opacity", 1);
})
.on("mouseout", function(d) {
countryTooltip.style("opacity", 0)
.style("display", "none");
})
.on("mousemove", function(d) {
countryTooltip.style("left", (d3.event.pageX + 7) + "px")
.style("top", (d3.event.pageY - 15) + "px");
});
});//closing d3.csv here
//Country focus on option select
d3.select("select").on("change", function() {
var rotate = projection.rotate(),
focusedCountry = country(countries, this),
p = d3.geo.centroid(focusedCountry);
svg.selectAll(".focused").classed("focused", focused = false);
//Globe rotating
(function transition() {
d3.transition()
.duration(2500)
.tween("rotate", function() {
var r = d3.interpolate(projection.rotate(), [-p[0], -p[1]]);
return function(t) {
projection.rotate(r(t));
svg.selectAll("path").attr("d", path)
.classed("focused", function(d, i) { return d.id == focusedCountry.id ? focused = d : false; });
//svg.selectAll("circle").attr("d", data)
//.classed("focused", function(d, i) { return d.id == focusedCountry.id ? focused = d : false; });
};
})
})();
});
function country(cnt, sel) {
for(var i = 0, l = cnt.length; i < l; i++) {
if(cnt[i].id == sel.value) {return cnt[i];}
}
};
};
</script>
Please help.
Thank you in advance
Here is an option, using point geometries:
.enter().append('path')
.attr('class', 'circle_el')
.attr('fill', function(d) {return d.fill; })
.datum(function(d) {
return {type: 'Point', coordinates: [d.lon, d.lat], radius: some_radius};
})
.attr('d', path);
This is cool because you will update the circles simultaneously with a path redraw. And in addition it will account for the projection as a sphere, not showing circles that should be on the non-visible side of the sphere. I got the idea from this post by Jason Davies.
I'm making a line chart using d3.js. A tooltip at a point is displayed on mouseover,and disappears on mouseout. I want to display all the tooltips together permanently when the chart is created. Is there a way to do it?
My javascript-
var x = d3.time.scale().range([0, w]);
var y = d3.scale.linear().range([h, 0]);
x.domain(d3.extent(data, function(d) { return d.x; }));
y.domain(d3.extent(data, function(d) { return d.y; }));
var line = d3.svg.line()
.x(function(d) {
return x(d.x);
})
.y(function(d) {
return y(d.y);
})
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
var graph = d3.select("#graph").append("svg:svg")
.attr("width", 900)
.attr("height", 600)
.append("svg:g")
.attr("transform", "translate(" + 80 + "," + 80 + ")");
var xAxis = d3.svg.axis().scale(x).ticks(10).orient("bottom");
var yAxisLeft = d3.svg.axis().scale(y).ticks(10).orient("left");
var area = d3.svg.area()
.x(function(d) { return x(d.x); })
.y0(h)
.y1(function(d) { return y(d.y); });
graph.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("class", "circle")
.attr("cx", function (d) { return x(d.x); })
.attr("cy", function (d) { return y(d.y); })
.attr("r", 4.5)
.style("fill", "black")
.on("mouseover", function(d) {
div.transition()
.duration(200)
.style("opacity", .9);
div
.html(d.y) + "<br/>" + d.x)
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 30) + "px");
})
.on("mouseout", function(d) {
div.transition()
.duration(500)
.style("opacity", 0);
});
To label each data point, you can add text elements at the appropriate positions. The code would look something like this.
graph.selectAll("text").data(data).enter()
.append("text")
.attr("x", function (d) { return x(d.x); })
.attr("y", function (d) { return y(d.y) - 10; })
.text(function(d) { return d.value; });