I tried to figure out the difference between 'd3.event.pageX' & 'd3.mouse(this)[0]'.
I guessed both are same but,
when I console.log both,
the value was different by '8' in my code.
var height=600;
var width=600;
var graphgap=60;
d3.csv('./details.csv').then(function(data){
var svg =d3.select('section').append('svg')
.attr('width',600).attr('height',600)
.on('mousemove',mousemove)
drawrect(data);
})
function drawrect(data){
let bars=d3.select('svg').selectAll('rect').data(data);
bars.enter().append('rect').classed('bargraph',true)
.attr('x',function(d,i){return (i+1)*graphgap})
.attr('y',function(d){return height-(d.Age)*5})
.attr('width',55)
.attr('height',function(d){return (d.Age)*(5)})
}
function mousemove(){
let mouselocation =[];
d3.select('svg').append('text')
.text(d3.event.pageX)
.attr('x',d3.event.pageX)
.attr('y',d3.event.pageY)
console.log(d3.event.pageX)
console.log(d3.mouse(this)[0])
}
So, I think these two are two different things.
Can anyone let me know why it makes a difference?
The reason why I tried to figure this out is because I was re-writing the code below.
<script>
// set the dimensions and margins of the graph
var margin = {top: 10, right: 30, bottom: 30, left: 60},
width = 460 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3.select("#my_dataviz")
.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
d3.csv("https://raw.githubusercontent.com/holtzy/D3-graph-gallery/master/DATA/data_IC.csv",function(data) {
// Add X axis --> it is a date format
var x = d3.scaleLinear()
.domain([1,100])
.range([ 0, width ]);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add Y axis
var y = d3.scaleLinear()
.domain([0, 13])
.range([ height, 0 ]);
svg.append("g")
.call(d3.axisLeft(y));
// This allows to find the closest X index of the mouse:
var bisect = d3.bisector(function(d) { return d.x; }).left;
// Create the circle that travels along the curve of chart
var focus = svg
.append('g')
.append('circle')
.style("fill", "none")
.attr("stroke", "black")
.attr('r', 8.5)
.style("opacity", 0)
// Create the text that travels along the curve of chart
var focusText = svg
.append('g')
.append('text')
.style("opacity", 0)
.attr("text-anchor", "left")
.attr("alignment-baseline", "middle")
// Create a rect on top of the svg area: this rectangle recovers mouse position
svg
.append('rect')
.style("fill", "none")
.style("pointer-events", "all")
.attr('width', width)
.attr('height', height)
.on('mouseover', mouseover)
.on('mousemove', mousemove)
.on('mouseout', mouseout);
// Add the line
svg
.append("path")
.datum(data)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-width", 1.5)
.attr("d", d3.line()
.x(function(d) { return x(d.x) })
.y(function(d) { return y(d.y) })
)
// What happens when the mouse move -> show the annotations at the right positions.
function mouseover() {
focus.style("opacity", 1)
focusText.style("opacity",1)
}
function mousemove() {
// recover coordinate we need
var x0 = x.invert(d3.mouse(this)[0]);
var i = bisect(data, x0, 1);
selectedData = data[i]
focus
.attr("cx", x(selectedData.x))
.attr("cy", y(selectedData.y))
focusText
.html("x:" + selectedData.x + " - " + "y:" + selectedData.y)
.attr("x", x(selectedData.x)+15)
.attr("y", y(selectedData.y))
}
function mouseout() {
focus.style("opacity", 0)
focusText.style("opacity", 0)
}
})
</script>
In documentation is written:
While you can use the native event.pageX and event.pageY, it is often
more convenient to transform the event position to the local
coordinate system of the container that received the event using
d3.mouse, d3.touch or d3.touches.
d3.event
d3.mouse - uses local coordinate (without margin (60px))
d3.event.pageX - uses global coordinate (with margin (60px))
But local cordinate start on 68px. I guess 8 pixels is used to describe the y-axis.
Related
Hi i have a heatmap here that im trying to give color to. Right now its all over red but I want to use the d3.interpolateRdYlBu, i want my values that are lower to be the blue and the higher be the red so i would like it to gel nicely. I know that its reading correctly since i get the red and no other errors in my console but Im not doing something right that it doesnt take my value into account and do accordingly. Any help would be appreciated!
<!DOCTYPE html>
<meta charset="utf-8">
<!-- Load d3.js -->
<script src="https://d3js.org/d3.v4.js"></script>
<!-- Create a div where the graph will take place -->
<div id="my_dataviz"></div>
<!-- Load color palettes -->
<script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script>
<script>
// set the dimensions and margins of the graph
var margin = {top: 80, right: 25, bottom: 30, left: 40},
width = 1000 - margin.left - margin.right,
height = 1000 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3.select("#my_dataviz")
.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
d3.csv("https://raw.githubusercontent.com/Nataliemcg18/Data/master/NASA_Surface_Temperature.csv", function(data) {
// Labels of row and columns -> unique identifier of the column called 'group' and 'variable'
var myGroups = d3.map(data, function(d){return d.group;}).keys()
var myVars = d3.map(data, function(d){return d.variable;}).keys()
// Build X scales and axis:
var x = d3.scaleBand()
.range([ 0, width ])
.domain(myGroups)
.padding(0.05);
svg.append("g")
.style("font-size", 15)
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).tickSize(0))
.select(".domain").remove()
// Build Y scales and axis:
var y = d3.scaleBand()
.range([ height, 0 ])
.domain(myVars)
.padding(0.05);
svg.append("g")
.style("font-size", 15)
.call(d3.axisLeft(y).tickSize(0))
.select(".domain").remove()
// Build color scale
var myColor = d3.scaleSequential()
.interpolator( d3.interpolateRdYlBu)
.domain([1,100])
// create a tooltip
var tooltip = d3.select("#my_dataviz")
.append("div")
.style("opacity", 0)
.attr("class", "tooltip")
.style("background-color", "white")
.style("border", "solid")
.style("border-width", "2px")
.style("border-radius", "5px")
.style("padding", "5px")
// Three function that change the tooltip when user hover / move / leave a cell
var mouseover = function(d) {
tooltip
.style("opacity", 1)
d3.select(this)
.style("stroke", "green")
.style("opacity", 1)
}
var mousemove = function(d) {
tooltip
.html("The exact value of this cell is: " + d.value, )
.style("left", (d3.mouse(this)[0]+70) + "px")
.style("top", (d3.mouse(this)[1]) + "px")
}
var mouseleave = function(d) {
tooltip
.style("opacity", 0)
d3.select(this)
.style("stroke", "none")
.style("opacity", 0.8)
}
// add the squares
svg.selectAll()
.data(data, function(d) {return d.group+':'+d.variable;})
.enter()
.append("rect")
.attr("x", function(d) { return x(d.group) })
.attr("y", function(d) { return y(d.variable) })
.attr("rx", 4)
.attr("ry", 4)
.attr("width", x.bandwidth() )
.attr("height", y.bandwidth() )
.style("fill", function(d) { return myColor(d.value)} )
.style("stroke-width", 4)
.style("stroke", "none")
.style("opacity", 0.8)
.on("mouseover", mouseover)
.on("mousemove", mousemove)
.on("mouseleave", mouseleave)
})
// Add title to graph
svg.append("text")
.attr("x", 0)
.attr("y", -50)
.attr("text-anchor", "left")
.style("font-size", "22px")
.text("A d3.js heatmap");
// Add subtitle to graph
svg.append("text")
.attr("x", 0)
.attr("y", -20)
.attr("text-anchor", "left")
.style("font-size", "14px")
.style("fill", "grey")
.style("max-width", 400)
.text("A short description of the take-away message of this chart.");
</script>
In your colour scale, you've set the domain as [0,100]. Your values however are between 0 and 1.5, and you want them reversed
so, this should fix it:
// Build color scale
var myColor = d3.scaleSequential()
.interpolator( d3.interpolateRdYlBu)
.domain([1.3,0])
To be even more thorough, you can use the d3.max and d3.min functions to work out the max in min for you:
var myColor = d3.scaleSequential()
.interpolator( d3.interpolateRdYlBu)
.domain([d3.max(data, d=>d.value),d3.min(data, d=>d.value)])
Heres a jsFiddle with this working: https://jsfiddle.net/x8zyud5t/
I have a bar chart graph, created with d3 v5, which needs to have a straight line as a "limit" depending on a specific y value.
This is the graph I currently have and the line which should be created (added on paint)
Here is the code in order to create the graph
(async ()=> {
const response = await fetch('https://api);
const myJson = await response.json();
//need myJson.DailyDelvs to be the y value of the line
// set the dimensions and margins of the graph
var margin = {top: 30, right: 30, bottom: 70, left: 60},
width = 400 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom
tip = d3.tip()
.attr('class', 'd3-tip')
.html(function(d) { return "DAY: "+d.DIA+"<br/>PO: "+d.PO_ID })
// append the svg object to the body of the page
var svg = d3.select("#dailyDeliveryVolume")
.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 + ")")
.call(tip)
var datos
// get the data
d3.json("https://api2").then(function(data) {
datos = data
d3.select("#dailyDeliveryVolume_spinner").remove();
var x = d3.scaleBand()
.range([ 0, width ])
.domain([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31])
.padding(0.05);
var xAxis = d3.axisBottom(x)
.tickValues(x.domain().filter(function(d,i){ return !(i%2)}));
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
var y = d3.scaleLinear()
.domain([0, d3.max(data.STD, function(d) { return +d.PO_ID })])
.range([ height, 0]);
svg.append("g")
.call(d3.axisLeft(y))
// Bars
svg.selectAll("mybar")
.data(data.STD)
.enter()
.append("rect")
.attr("x", function(d) { return x(d.DIA); })
.attr("y", function(d) { return y(d.PO_ID); })
.attr("width", x.bandwidth())
.attr("height", function(d) { return height - y(d.PO_ID); })
.attr("fill", "#69b3a2")
.attr("border-color", "black")
.on("mouseover", tip.show)
.on("mouseleave", tip.hide )
})
})()
In order to create the line, I tried the following code, right after I append the bars, which is causing an error
var linea = drawLine(svg, x, y, data.STD);
var drawLine = function(svg, x, y, data) {
var lineFunc = d3.line()
.x(function(obj) {
return x(obj.DIA);
})
.y(function(obj) {
return y(obj.PO_ID);
});
var linea = svg.append("linea") //SVG Paths represent the outline of a shape that can be stroked, filled, used as a clipping path, or any combination of all three. We can draw rectangles, circles, ellipses, polylines, polygons, straight lines, and curves through path
.attr("d", lineFunc(data))
.attr("stroke", '#87CEEB')
.attr("stroke-width", 3)
.attr("fill", "black");
return linea;
};
dailyDeliveryVolume.js:64 Uncaught (in promise) TypeError: drawLine is not a function at dailyDeliveryVolume.js:64
I also tried to directly append a line to the svg with fixed attributes x1, x2, y1 and y2 but seems it is done base on the entire container and can't get to suit the x and y axis values.
The goal is that myJson.DailyDelvs is the y value (based on the y scale values) and then is just stright thru all width.
One issue is that you need to call the function (var linea = drawLine(...)) after defining the function (var drawLine = ...).
I think you can also simplify what you're doing by drawing a <line> directly:
svg.append("line")
.attr("x1", x(0))
.attr("x2", x(31))
.attr("y1", y(obj.PO_ID))
.attr("y2", y(obj.PO_ID))
.attr("stroke", '#87CEEB')
.attr("stroke-width", 3)
.attr("fill", "black");
(I'm not sure which value you are trying to use for the y-axis, but it seems like it should be similar to above...)
I am trying to make simple chart right now importing data from a CSV. Everything on the chart is working great except for the labels. In element inspect I can see that they are being appended and that their x and y coordinates are even correct, but for some reason they are all trapped in the top left corner in the SVG itself.
I have tried changing the x placement function at first because I thought it just wasn't giving the labels a x position, but upon further inspection the labels have the correct metadata.
//Graph Dimensions
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 1000 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
//Set Ranges
var x_scale = d3.scaleBand()
.range([0, width])
.padding(0.1);
var y_scale = d3.scaleLinear()
.range([height, 0]);
//Create SVG object
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 + ")");
//Retrieve data
d3.csv('sales.csv').then(function(data){
//Set domains based on data
x_scale.domain(data.map(function(d) { return d.month; }));
y_scale.domain([0, d3.max(data, function(d) { return d.sales; })]);
//Create bars
svg.selectAll("rect")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) { return x_scale(d.month); })
.attr("width", x_scale.bandwidth())
.attr("y", function(d) { return y_scale(d.sales); })
.attr("height", function(d) { return height - y_scale(d.sales); });
//Create labels
svg.selectAll('text')
.data(data)
.enter().append('text')
.attr('class', 'label')
.attr("x", function(d) { return x_scale(d.month); })
.attr("y", function(d) { return y_scale(d.sales); })
.attr( 'font-size', 14 )
.attr( 'fill', '#555555' )
.attr( 'text-anchor', 'middle' );
//Add Axes
svg.append("g") //X Axis
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x_scale));
svg.append("g") //Y Axis
.call(d3.axisLeft(y_scale));
})
The only thing im looking for is the labels actually appearing. I can change their location later if needed.
I create a scatterplot which is defined on the following data (note that only first two fields are currently using for plotting):
var data = [[5,3,"{'text':'word1',size:4},{'text':'word2','size':1}"],
[3,5,"{'text':'word3',size:5},{'text':'word4','size':4}"],
[1,4,"{'text':'word1',size:3},{'text':'word2','size':5},{'text':'word3','size':2}"],
[2,3,"{'text':'word2',size:1},{'text':'word3','size':5}"]];
Next, when we click on each particular point in the scatterplot the application should attach a wordcloud which is defined from words stored in the 3rd field of the data variable. I use Jason Davies's implementation of wordcloud. Currently (for demo purposes), the wordcloud is generating onlyfrom the static data stored in variable frequency_list. The current code is also stored on JSFiddle.
Any idea how to proceed?
var data = [[5,3,"{'text':'word1',size:4},{'text':'word2','size':1}"],
[3,5,"{'text':'word3',size:5},{'text':'word4','size':4}"],
[1,4,"{'text':'word1',size:3},{'text':'word2','size':5},{'text':'word3','size':2}"],
[2,3,"{'text':'word2',size:1},{'text':'word3','size':5}"]];
var margin = {top: 20, right: 15, bottom: 60, left: 60},
width = 500 - margin.left - margin.right,
height = 250 - margin.top - margin.bottom;
var x = d3.scale.linear()
.domain([0, d3.max(data, function(d) { return d[0]; })])
.range([ 0, width ]);
var y = d3.scale.linear()
.domain([0, d3.max(data, function(d) { return d[1]; })])
.range([ height, 0 ]);
var chart = d3.select('body')
.append('svg:svg')
.attr('width', width + margin.right + margin.left)
.attr('height', height + margin.top + margin.bottom)
.attr('class', 'chart')
var main = chart.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
.attr('width', width)
.attr('height', height)
.attr('class', 'main')
// Draw the x axis
var xAxis = d3.svg.axis()
.scale(x)
.orient('bottom');
main.append('g')
.attr('transform', 'translate(0,' + height + ')')
.attr('class', 'main axis date')
.call(xAxis);
// draw the y axis
var yAxis = d3.svg.axis()
.scale(y)
.orient('left');
main.append('g')
.attr('transform', 'translate(0,0)')
.attr('class', 'main axis date')
.call(yAxis);
var g = main.append("svg:g");
g.selectAll("scatter-dots")
.data(data)
.enter().append("svg:circle")
.attr("cx", function (d,i) { return x(d[0]); } )
.attr("cy", function (d) { return y(d[1]); } )
.attr("r", 5)
.on("mouseover", function(){d3.select(this).style("fill", "red")})
.on("mouseout", function(){d3.select(this).style("fill", "black")});
// FUNCTION TO DISPLAY CIRCLE
g.on('mouseover', function(){
div.style("display", "block")
d3.select("krog").style("fill", "orange");
generate();
});
g.on('mouseout', function(){
//div.style("display", "none")
div.select("svg").remove();
});
var div = d3.select("body")
.append("div")
.attr("class", "tooltip")
.style("display", "none");
// Functions to draw wordcloud
var frequency_list = [{"text":"study","size":40},{"text":"motion","size":15},{"text":"forces","size":10},{"text":"electricity","size":15},{"text":"movement","size":10},{"text":"relation","size":5},{"text":"things","size":10},{"text":"force","size":5},{"text":"ad","size":5}];
var color = d3.scale.linear()
.domain([0,1,2,3,4,5,6,10,15,20,100])
.range(["#ddd", "#ccc", "#bbb", "#aaa", "#999", "#888", "#777", "#666", "#555", "#444", "#333", "#222"]);
// Generates wordcloud
function generate(){
d3.layout.cloud().size([800, 300])
.words(frequency_list)
.rotate(0)
.fontSize(function(d) { return d.size; })
.on("end", draw)
.start();
}
function draw(words) {
d3.select("div").append("svg")
.attr("width", 850)
.attr("height", 350)
.attr("class", "wordcloud")
.append("g")
// without the transform, words words would get cutoff to the left and top, they would
// appear outside of the SVG area
.attr("transform", "translate(320,200)")
.selectAll("text")
.data(words)
.enter().append("text")
.style("font-size", function(d) { return d.size + "px"; })
.style("fill", function(d, i) { return color(i); })
.attr("transform", function(d) {
return "translate(" + [d.x, d.y] + ")rotate(" + d.rotate + ")";
})
.text(function(d) { return d.text; });
}
You have a couple of problems here.
First, your data has strings for the words. I changed that for an array of objects:
var data = [[5,3,[{'text':'word1',size:4},{'text':'word2','size':1}]],
[3,5,[{'text':'word3',size:5},{'text':'word4','size':4}]],
[1,4,[{'text':'word1',size:3},{'text':'word2','size':5},{'text':'word3','size':2}]],
[2,3,[{'text':'word2',size:1},{'text':'word3','size':5}]]];
After that, I changed the function draw: instead of appending a new div every time you hover a circle, it just change the div content:
div.append("svg")
.attr("width", 300)
.attr("height", 300)
.attr("class", "wordcloud")
.append("g")
But now comes the most important change:
You are displaying the wordcloud every time the user hover a circle, but you're calling the mouseover for the group element. That way, we cannot access the data bound to each specific circle.
Instead of that, we'll set a variable for the circles:
var circle = g.selectAll("scatter-dots")
.data(data)
.enter()
.append("svg:circle");
Thus, we can get the data for each hovered circle, which is the third element in the array:
circle.on('mouseover', function(d){
div.style("display", "block")
d3.select("krog").style("fill", "orange");
generate(d[2]);//here, d[2] is the third element in the data array
});
And we pass this third element (d[2]) to the function generate as a parameter named thisWords:
function generate(thisWords){
d3.layout.cloud().size([800, 300])
.words(thisWords)
.rotate(0)
.fontSize(function(d) { return d.size; })
.on("end", draw)
.start();
}
here is your fiddle: https://jsfiddle.net/jwrbps4j/
PS: you'll have to improve the translate for that words.
I have a bar chart displaying data on which you can filter through different years with the press of a button. I want the chart to transition from the current value to the new value, but now it starts at the bottom each time you press a button. How can I fix this?
Thanks!
var margin = {top: 20, right: 20, bottom: 30, left: 20},
height = 500 - margin.top - margin.bottom,
width = 600 - margin.left - margin.right
function bars(data) {
max = d3.max(data)
var yScale = d3.scale.linear()
.domain([0, d3.max(data)])
.range([0, height])
var xScale = d3.scale.ordinal()
.domain(d3.range(0, data.length))
.rangeBands([0, width], .2)
var myChart = d3.select("#chart")
var bars = myChart.selectAll("rect.bar")
.data(data)
//enter
bars.enter()
.append("svg:rect")
.attr("class", "bar")
.attr("fill", "#800")
//apply to everything (enter and update)
bars.style('fill', '#C64567')
.attr('width', xScale.rangeBand())
.attr('x', function(d,i){
return xScale(i);
})
.attr('height', 0)
.attr('y', height)
bars.transition()
.attr('height', function(d){
return yScale(d);
})
.attr('y', function(d){
return height - yScale(d);
})
.duration(1000)
.ease('elastic')
}
function init() {
//setup the svg
var svg = d3.select("#svg")
.style('background', '#000')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append("svg:g")
.attr("id", "chart")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
//UI
d3.select("#button1")
.on("click", function (d, i) {
bars(j1996);
})
d3.select("#button2")
.on("click", function (d, i) {
bars(j1997);
})
d3.select("#button3")
.on("click", function (d, i) {
bars(j1998);
})
//draw the bars
bars(j1996);
}
A fresh new look at it and I managed to find the solution. I removed the tag bars in this line:
//apply to everything (enter and update)
bars.style('fill', '#C64567')
So all the attributes are now set on the bar.enter() command, this way d3.js makes the transitions automatically from the last value