I have a series of paired xy coordinates that create 58 lines. I want to plot them on a Cartesian graph, values are between -5 and 5 on both axis, essentially making a scatter plot of lines. I have made something similar in matplotlib using the quiver function, but I want to be able to do this in D3. I would also like to be able to label each line, or each line that meets a length threshold. The code I have come up with below. Thanks.
var lisa = [["Eloy",0.0169808,-0.695317,-0.0510301,-0.6995938],
["Florence",-0.3465685,-0.6790588,-0.5869514,-0.6762134],
["Phoenix",0.677068,-0.5754814,-0.6052215,-0.6158059],
["Tucson",-0.663848,0.4111043,-0.6722116,0.011639]]
var w = 200;
var h = 200;
//create the svg element and set the height and width parameters
var svg = d3.select("div").select("div")
.append("svg")
.attr("height",h)
.attr("width", w)
.style("border", "1px solid black");
//Create the scale for the scatter plot
var xScale = d3.scale.linear()
.domain([d3.min(dataset, function(d) { return d[0];}),d3.max(dataset, function(d) { return d[0];})])
.range([-1,1]);
var yScale = d3.scale.linear()
.domain([d3.min(dataset, function(d) { return d[1];}),d3.max(dataset, function(d) { return d[1];})])
.range([-1,1]);
//This is the function that creates the SVG lines
var line = svg.selectAll("line")
.data(lisa)
.enter()
.append("line");
//This gets the cooresponding x,y cordinates from the dataset
line.attr("x1", function(d) {
return xScale(d[0]);
})
.attr("y1", function(d) {
return yScale(d[1]);
})
.attr("x2", function(d) {
return xScale(d[2]);
})
.attr("y2", function(d) {
return yScale(d[3]);
})
.attr("stroke", "black");
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Your code has some problems:
First, your range right now ([-1, 1]) makes no sense. This should be the domain instead (I changed the ranges to [0, w] and [0, h]).
In your real code, the domain should be [-5, 5] and the range should be the limits of the plot, something like [leftLimit, rightLimit] and [topLimit, bottomLimit] (have in mind that, in an SVG, the 0 position for the y axis is the top, not the bottom).
Second, given this array:
["Tucson",-0.663848,0.4111043,-0.6722116,0.011639]
your x and y positions should be the indices 1,2,3 and 4, not 0, 1, 2 and 3.
Besides that changes, I added the labels:
var text = svg.selectAll(".text")
.data(dataset)
.enter()
.append("text");
text.attr("font-size", 10)
.attr("x", function(d) {
return xScale(d[1]);
})
.attr("y", function(d) {
return yScale(d[2]);
})
.text(d => d[0]);
Here is the demo with the corrections:
var dataset = [["Eloy",0.0169808,-0.695317,-0.0510301,-0.6995938],
["Florence",-0.3465685,-0.6790588,-0.5869514,-0.6762134],
["Phoenix",0.677068,-0.5754814,-0.6052215,-0.6158059],
["Tucson",-0.663848,0.4111043,-0.6722116,0.011639]];
var color = d3.scale.category10();
var w = 400;
var h = 300;
//create the svg element and set the height and width parameters
var svg = d3.select("body")
.append("svg")
.attr("height",h)
.attr("width", w)
.style("border", "1px solid black");
//Create the scale for the scatter plot
var xScale = d3.scale.linear()
.domain([-1,1])
.range([0,w]);
var yScale = d3.scale.linear()
.domain([-1,1])
.range([0,h]);
//This is the function that creates the SVG lines
var line = svg.selectAll("line")
.data(dataset)
.enter()
.append("line");
//This gets the cooresponding x,y cordinates from the dataset
line.attr("x1", function(d) {
return xScale(d[1]);
})
.attr("y1", function(d) {
return yScale(d[2]);
})
.attr("x2", function(d) {
return xScale(d[3]);
})
.attr("y2", function(d) {
return yScale(d[4]);
})
.attr("stroke-width", 2)
.attr("stroke", (d,i)=>color(i));
var text = svg.selectAll(".text")
.data(dataset)
.enter()
.append("text");
text.attr("font-size", 10)
.attr("x", function(d) {
return xScale(d[1])+2;
})
.attr("y", function(d) {
return yScale(d[2]) + 4;
})
.text(d=>d[0]);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Related
This is my first time using d3.js, so please bear with me. I am implementing this inside of a vue.js file as pure javascript.
I am trying to make a scatter plot with zooming capabilities. So far I have everything nearly working, but when I zoom I notice that the x-axis isn't scaling properly, but the y-axis is working properly. For instance, when looking at the original plot, a point may be at around 625 on the x-axis, but after zooming in the same point will be less than 600. This is not happening with the y-axis - those points scale properly. I am assuming that something is wrong with the scaling of the x-axis in my zoom function, but I just can't figure it out. Please take a look, and let me know if you can see where I went wrong.
Edit: I should mention that this is using d3.js version 7.4.4
<template>
<div id="reg_plot"></div>
</template>
<script>
import * as d3 from 'd3';
export default {
name: 'regCamGraph',
components: {
d3
},
methods: {
createSvg() {
// dimensions
var margin = {top: 20, right: 20, bottom: 30, left: 40},
svg_dx = 1400,
svg_dy =1000,
chart_dx = svg_dx - margin.right - margin.left,
chart_dy = svg_dy - margin.top - margin.bottom;
// data
var y = d3.randomNormal(400, 100);
var x_jitter = d3.randomUniform(-100, 1400);
var d = d3.range(1000)
.map(function() {
return [x_jitter(), y()];
});
// fill
var colorScale = d3.scaleLinear()
.domain(d3.extent(d, function(d) { return d[1]; }))
.range([0, 1]);
// y position
var yScale = d3.scaleLinear()
.domain(d3.extent(d, function(d) { return d[1]; }))
.range([chart_dy, margin.top]);
// x position
var xScale = d3.scaleLinear()
.domain(d3.extent(d, function(d) { return d[0]; }))
.range([margin.right, chart_dx]);
console.log("chart_dy: " + chart_dy);
console.log("margin.top: " + margin.top);
console.log("chart_dx: " + chart_dx);
console.log("margin.right: " + margin.right);
// y-axis
var yAxis = d3.axisLeft(yScale);
// x-axis
var xAxis = d3.axisBottom(xScale);
// zoom
var svg = d3.select("#reg_plot")
.append("svg")
.attr("width", svg_dx)
.attr("height", svg_dy);
svg.call(d3.zoom().on("zoom", zoom)); // ref [1]
// plot data
var circles = svg.append("g")
.attr("id", "circles")
.attr("transform", "translate(200, 0)")
.selectAll("circle")
.data(d)
.enter()
.append("circle")
.attr("r", 4)
.attr("cx", function(d) { return xScale(d[0]); })
.attr("cy", function(d) { return yScale(d[1]); })
.style("fill", function(d) {
var norm_color = colorScale(d[1]);
return d3.interpolateInferno(norm_color)
});
// add y-axis
var y_axis = svg.append("g")
.attr("id", "y_axis")
.attr("transform", "translate(75,0)")
.call(yAxis).style("font-size", "20px")
// add x-axis
var x_axis = svg.append("g")
.attr("id", "x_axis")
.attr("transform", `translate(${margin.left}, ${svg_dy - margin.bottom})`)
.call(xAxis).style("font-size", "20px")
function zoom(e) {
// re-scale y axis during zoom
y_axis.transition()
.duration(50)
.call(yAxis.scale(e.transform.rescaleY(yScale)));
// re-scale x axis during zoom
x_axis.transition()
.duration(50)
.call(xAxis.scale(e.transform.rescaleX(xScale)));
// re-draw circles using new y-axis scale
var new_xScale = e.transform.rescaleX(xScale);
var new_yScale = e.transform.rescaleY(yScale);
console.log(d);
x_axis.call(xAxis.scale(new_xScale));
y_axis.call(yAxis.scale(new_yScale));
circles.data(d)
.attr('cx', function(d) {return new_xScale(d[0])})
.attr('cy', function(d) {return new_yScale(d[1])});
}
}
},
mounted() {
this.createSvg();
}
}
</script>
Interestingly enough, after I set the clip region to prevent showing points outside of the axes the problem seemed to resolve itself. This is how I created the clip path:
// clip path
var clip = svg.append("defs").append("svg:clipPath")
.attr("id", "clip")
.append("svg:rect")
.attr("id", "clip-rect")
.attr("x", "0")
.attr("y", "0")
.attr('width', chart_dx)
.attr('height', chart_dy);
And I then added that attribute to the svg when plotting the data like this:
svg.append("g").attr("clip-path", "url(#clip)")
Updated clip path with plot data section:
// clip path
var clip = svg.append("defs").append("svg:clipPath")
.attr("id", "clip")
.append("svg:rect")
.attr("id", "clip-rect")
.attr("x", "0")
.attr("y", "0")
.attr('width', chart_dx)
.attr('height', chart_dy);
// plot data
var circles = svg.append("g")
.attr("id", "circles")
.attr("transform", "translate(75, 0)")
.attr("clip-path", "url(#clip)") //added here
.selectAll("circle")
.data(d)
.enter()
.append("circle")
.attr("r", 4)
.attr("cx", function(d) { return xScale(d[0]); })
.attr("cy", function(d) { return yScale(d[1]); })
.style("fill", function(d) {
var norm_color = colorScale(d[1]);
return d3.interpolateInferno(norm_color)
});
I ended up resolving this issue. I have updated the original post to show what worked for me.
Basically, after adding the clip region things started to work properly.
// clip path (this is the new clip region that I added. It prevents dots from being drawn outside of the axes.
var clip = svg.append("defs").append("svg:clipPath")
.attr("id", "clip")
.append("svg:rect")
.attr("id", "clip-rect")
.attr("x", "0")
.attr("y", "0")
.attr('width', chart_dx)
.attr('height', chart_dy);
// plot data
var circles = svg.append("g")
.attr("id", "circles")
.attr("transform", "translate(75, 0)")
.attr("clip-path", "url(#clip)") //added clip region to svg here
.selectAll("circle")
.data(d)
.enter()
.append("circle")
.attr("r", 4)
.attr("cx", function(d) { return xScale(d[0]); })
.attr("cy", function(d) { return yScale(d[1]); })
.style("fill", function(d) {
var norm_color = colorScale(d[1]);
return d3.interpolateInferno(norm_color)
});
I have a scatter plot made in d3.v3 and no matter how large i increase the width and height variables it does not take up more screen space.
var w = 700;
var h = 700;
var dataset = [
"Over 50 pairs of coordinates that look like [0,1][0,43],
];
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d) {
return d[0];
})
.attr("y", function(d) {
return d[1];
})
.attr("width",5)
.attr("height",5);
There are more than 50 coordinates in my dataset and i want to be able to display them well so that is why i want this to take up more screen space. Currently there is nothing in my html, and no css for this. How can i adjust this so that the scatter plot takes more screen space?
The code you show doesn't place data points with any consideration of width or height, it places data points based on the values in the data:
.attr("x", function(d) {
return d[0];
})
.attr("y", function(d) {
return d[1];
})
The x and y attributes, without any SVG transformation, expect pixel values. If a point has the datum [25,25] it will be placed 25 pixels from the top and left. Height and width of the svg do not matter - you are stating the pixel position based on the data, not based on the svg dimensions in combination with the data.
Scales are a fundamental part of d3 - you can scale the x and y values of your data points across a range of pixel values. To do this we need to know the domain of the input data - the extent of input values - and the range of the output values - the extent of output values.
There are a number of scales built into D3, including power, logarithmic and linear. I'll demonstrate a linear scale below, but the form is very similar for other continuous scales, such as those noted above.
var xScale = d3.scaleLinear() // create a new linear scale
.domain([0,50]) // map values from 0 to 50 to:
.range([0,width]) // to pixel values of 0 through width
var yScale = d3.scaleLinear() // create a new linear scale
.domain([0,50]) // map values from 0 to 50 to:
.range([height,0]) // to pixel values of height through 0
Since in SVG coordinate space y=0 is the top of the SVG, and normally we want data with y=0 to be displayed at the bottom of the graph, we use a range of [height,0] rather than [0,height]
With the scales set up, to return the pixel value for a given data value we use:
xScale(d[0]); // assuming d[0] holds the x value
or
yScale(d[1]); // assuming d[1] holds the y value
Together this gives us:
var w = 700;
var h = 700;
var dataset = d3.range(50).map(function(d) {
return [Math.random()*50,Math.random()*50];
})
var x = d3.scaleLinear()
.domain([0,50]) // input extent
.range([0,w]) // output extent
var y = d3.scaleLinear()
.domain([0,50])
.range([h,0])
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d) {
return x(d[0]);
})
.attr("y", function(d) {
return y(d[1]);
})
.attr("width",5)
.attr("height",5);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
Of course we might not know the domain of the data if its dynamic, so d3 has a few built in helpers including d3.min, d3.max, d3.extent.
d3.min and d3.max iterate through an array to find the minimum and maximum values of some property of each item in the data array:
d3.min(dataArray, function(d) {
return d.property;
})
d3.max(dataArray, function(d) {
return d.property;
})
D3.extent does both at the same time returning an array containing min and max:
d3.extent(dataArray, function(d) {
return d.property;
})
We can plug those into the scales too:
var w = 700;
var h = 700;
var dataset = d3.range(50).map(function(d) {
return [Math.random()*50,Math.random()*50];
})
var x = d3.scaleLinear()
.domain([0,d3.max(dataset, function(d) { return d[0]; }) ]) // input extent
.range([0,w]) // output extent
var y = d3.scaleLinear()
.domain( d3.extent(dataset, function(d) { return d[1]; }) )
.range([h,0])
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d) {
return x(d[0]);
})
.attr("y", function(d) {
return y(d[1]);
})
.attr("width",5)
.attr("height",5);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
The below works perfectly fine for me. The things you might need to check is :
The coordinates range is more than 5. (or you might need to use scale and axis)
Is there a overriding styles to your g
var w = 700;
var h = 700;
var dataset = [[10,10],[10,30],[30,50],[30,70]];
//Create SVG element
var svg = d3.select("#scatterplot")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d) {
return d[0];
})
.attr("y", function(d) {
return d[1];
})
.attr("width",5)
.attr("height",5);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id='scatterplot'></div>
I have created the stacked bar chart by using d3.js.In that I would like to display a single bar with different colors to highlight the data for particular x axis value like below.
The script i have used to plot stacked chart is below:
// Set the dimensions of the canvas / graph
var svg = d3.select("#svgID"),
margin = {top: 80, right: 140, bottom: 100, left: 100},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var padding = -100;
//set the ranges
var x = d3.scaleBand()
.rangeRound([0, width])
.paddingInner(0.20)
.align(0.1);
var y = d3.scaleLinear()
.rangeRound([height, 0]);
var z = d3.scaleOrdinal()
.range(["#008000", "#C00000", "#404040", "#4d4d4d"]);
var data = $("#svgID").data("values");
var keys = ["Pass", "Fail", "Average", "Worst"];
var legendKeysbar = ["Pass", "Fail", "Average", "Worst"];
var legendColorsbar = d3.scaleOrdinal()
.range(["#008000", "#C00000", "#404040", "#4d4d4d"]);
// Scale the range of the data
x.domain(data.map(function (d) {
return d.year;
}));
y.domain([0, d3.max(data, function (d) {
return d.total;
})]).nice();
z.domain(keys);
// add the Y gridlines
g.append("g").selectAll(".hline").data(y.ticks(10)).enter()
.append("svg:line")
.attr("x1", 0)
.attr("y1", function(d){ return y(d);})
.attr("x2", width)
.attr("y2", function(d){ return y(d);})
.style("stroke", "white")
.style("stroke-width", 1);
// append the rectangles for the bar chart
g.append("g")
.selectAll("g")
.data(d3.stack().keys(keys)(data))
.enter().append("g")
.attr("fill", function (d) {
return z(d.key);
})
.selectAll("rect")
.data(function (d) {
return d;
})
.enter().append("rect")
.attr("x", function (d) {
return x(d.data.year);
})
.attr("y", function (d) {
return y(d[1]);
})
.attr("height", function (d) {
return y(d[0]) - y(d[1]);
})
Can you help me to update colors for single bar? is that possible by d3.js
Create a second color scale, then in the method where you assign color, perform a check to determine which color scale to use, e.g.,:
var z2 = d3.scaleOrdinal().range(<your color array here>)
...
.attr("fill", function (d) {
return d.data.year === "Dec" ? z2(d.key) : z(d.key);
})
i want to add a multidimensional array of lines to my SVG with the D3 library. But somehow the lines don´t show up. There is no error message from javascript so i guess i can not be totally wrong but something is missing. I tried to use the description of Mike Bostock as an example http://bost.ocks.org/mike/nest/
I have an array of lines that looks like that:
var datasetPolylines = [[[-10849.0, 1142.0, -10720.0, 454.0],[x1, y1, x2, y2],[x1, y1, x2, y2]...],[polyline],[polyline]...];
For every Line there are 4 points in the array for x and y values of the line.
Now i try to add them to my mainsvg like that:
d3.select("#mainsvg").selectAll("g")
.data(datasetPolylines)
.enter()
.append("g")
.selectAll("line")
.data(function (d) {return d;})
.enter()
.append("line")
.attr("x1", function(d, i) {
return xScale(i[0]);
})
.attr("y1", function(d, i) {
return yScale(i[1]);
})
.attr("x2", function(d, i) {
return xScale(i[2]);
})
.attr("y2", function(d, i) {
return yScale(i[3]);
})
.attr("stroke-width", 2)
.attr("stroke", "blue")
.attr("fill", "none");
I´m very thankful for every hint on where I´m wrong. I´m on this stuff now for some days and just don´t get it :(
Everything works fine if i just draw one polyline with many lines and use the .data attribute just once. I cannot merge the lines to one path also, because they are not always connected and must be drawn seperately.
The complete (and now thanks to Christopher and Lars also working) code example looks like that:
var datasetLines = [];
var datasetPolylines = [];
/**Add a line to the dataset for lines*/
function addLineToDataset(x1, y1, x2, y2){
var newNumber1 = x1;
var newNumber2 = y1;
var newNumber3 = x2;
var newNumber4 = y2;
datasetLines.push([newNumber1, newNumber2, newNumber3, newNumber4]);
}
/**Add polyline to the dataset for polylines*/
function addPolyline(){
var polyline = [];
for (i in datasetLines) {
polyline[i] = datasetLines[i];
}
datasetPolylines.push(polyline);
}
/**Draw all polylines from the polylinearray to the svg*/
function showPolylineArray(){
//Create scale functions for x and y axis
var xScale = d3.scale.linear()
.domain([d3.min(outputRange, function(d) { return d[0]; }), d3.max(outputRange, function(d) { return d[0]; })])//get minimum and maximum of the first entry of the pointarray
.range([padding, w - padding]); //w, the SVGs width. without padding 0,w
var yScale = d3.scale.linear()
.domain([d3.min(outputRange, function(d) { return d[1]; }), d3.max(outputRange, function(d) { return d[1]; })])
.range([h - padding, padding]); //without padding h,0
d3.select("#mainsvg").selectAll("g")
.data(datasetPolylines)
.enter()
.append("g")
.selectAll("line")
.data(function (d) {return d;})
.enter()
.append("line")
.attr("x1", function(d) {
return xScale(d[0]);
})
.attr("y1", function(d) {
return yScale(d[1]);
})
.attr("x2", function(d) {
return xScale(d[2]);
})
.attr("y2", function(d) {
return yScale(d[3]);
})
.attr("stroke-width", 2)
.attr("stroke", "blue")
.attr("fill", "none")
.on('mouseover', function(d){ d3.select(this).style({stroke: 'red'}); })
.on('mouseout', function(d){ d3.select(this).style({stroke: 'blue'}); })
.append("title")
.text("Polyline");
}
Your code is almost working, except for the referencing of the data (as pointed out by Christopher). You didn't show us the complete code, but the bit you have should work fine with these modifications.
Complete example here.
I'm trying to make a d3 Bar Graph animate from the x axis up upon load. Here is my code so far:
$(document).ready(function(){
setTimeout(function(){
var t = 0, // start time (seconds since epoch)
v = 70, // start value (subscribers)
data = d3.range(4).map(next); // starting dataset
function next() {
return {
value: v = dataArray[t].length,
time: ++t
};
}
var w = 30,
h = 80;
var x = d3.scale.linear()
.domain([0, 1])
.range([0, w]);
var y = d3.scale.linear()
.domain([0, 100])
.rangeRound([0, h]);
var barPadding = 5;
var chart = d3.select("#revenue").append("svg")
.attr("class", "chart")
.attr("width", w * data.length + (data.length-1)*8)
.attr("height", h);
var rects = chart.selectAll("rect")
.data(data)
.enter().append("rect")
.attr("x", function(d, i) { return i*35; })
.attr("y", function(d) { return h - y(d.value) - .5; })
.attr("width", w)
.attr("fill", "white")
.attr("height", function(d) { return 0; });
rects.data(data)
.transition().duration(2000).delay(200)
.attr("height", function(d) { return y(d.value); })
.attr("y", function(d) {return h-y(d.value); });
},2000);
});
I know the answer lies within animating the Y position at the same time so that it gives the appearance of growing upwards, but no matter what I change, I can't get it to animate from the bottom up.
graph.selectAll("rect")
.append("animate")
.attr("attributeName","y")
.attr("attributeType","XML")
.attr("begin","0s")
.attr("dur","1s")
.attr("fill","freeze")
.attr("from",yaxis_offset)
.attr("to",function(d){return yscale(d.value);} );
graph.selectAll("rect")
.append("animate")
.attr("attributeName","height")
.attr("attributeType","XML")
.attr("begin","0s")
.attr("dur","1s")
.attr("fill","freeze")
.attr("from",0)
.attr("to",function(d){return yaxis_offset-yscale(d.value);});
This is the code which worked for me to animate the barchart as you said... change the variable according to your need..