I have to create scatter plot with quadrants. I have looked at libraries like d3.js , high charts, nvd3 but I found only normal scatter charts.
Can someone suggest which js library will help me achieve this?
Thanks
D3.js allows this feature if you simply add negative values for the coordinates in the scatterplot. Just off the top of my hat, you could give the points their regular coordinates, just set the domains of your d3.scale function as to allow negative values. Just an example would be
var x = d3.scale.linear().range([0, width]).domain([d3.min(data), d3.max(data)]);
This sets the range of your scatterplot to the width you have selected, but allows all values to be accepted into the plot, regardless of them being positive or negative. As is very well explained here, scales fit to the size of the range, spreading the contents of the domain over the range. It is, however, not a requirement that said domain is completely positive.
If you check for the biggest absolute number in your data, you can format the domain likewise, therefore having the axes in the center of your plot, instead of them being misaligned.
Next, just add your axes like normal, only shift them to the middle of your canvas using x and y attributes.
Here is the upated Plunker link for creating scatter plot charts with quadrants through d3.js:-
http://plnkr.co/edit/yEfkN0tn7DPAypAvyWjD?p=preview
Code:
<script>
var svg = d3.select("#scatter"),
margin = {top: 20, right: 20, bottom: 30, left: 50},
width = +svg.attr("width"),
height = +svg.attr("height"),
domainwidth = width - margin.left - margin.right,
domainheight = height - margin.top - margin.bottom;
var x = d3.scaleLinear()
.domain(padExtent([1,5]))
.range(padExtent([0, domainwidth]));
var y = d3.scaleLinear()
.domain(padExtent([1,5]))
.range(padExtent([domainheight, 0]));
var g = svg.append("g")
.attr("transform", "translate(" + margin.top + "," + margin.top + ")");
g.append("rect")
.attr("width", width - margin.left - margin.right)
.attr("height", height - margin.top - margin.bottom)
.attr("fill", "#F6F6F6");
d3.json("data.json", function(error, data) {
if (error) throw error;
data.forEach(function(d) {
d.consequence = +d.consequence;
d.value = +d.value;
});
g.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("class", "dot")
.attr("r", 7)
.attr("cx", function(d) { return x(d.consequence); })
.attr("cy", function(d) { return y(d.value); })
.style("fill", function(d) {
if (d.value >= 3 && d.consequence <= 3) {return "#60B19C"} // Top Left
else if (d.value >= 3 && d.consequence >= 3) {return "#8EC9DC"} // Top Right
else if (d.value <= 3 && d.consequence >= 3) {return "#D06B47"} // Bottom Left
else { return "#A72D73" } //Bottom Right
});
g.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + y.range()[0] / 2 + ")")
.call(d3.axisBottom(x).ticks(5));
g.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + x.range()[1] / 2 + ", 0)")
.call(d3.axisLeft(y).ticks(5));
});
function padExtent(e, p) {
if (p === undefined) p = 1;
return ([e[0] - p, e[1] + p]);
}
</script>
Related
Dear stack overflow community,
I've been trying to create a bar chart using d3v7 for one of my Uni projects, following the _Blocks_ example. However, I am getting an error as mentioned in this question title. Apparently, my code has a problem reading my data but I can't figure out how to fix this. It would be really helpful if you could help me here :)
Here is my code:
var margin = {top: 5, right:10, bottom:5, left:10},
width = 575 - margin.left - margin.right,
height = 200 - margin.top - margin.bottom;
// set the ranges
var x = d3.scaleBand()
.range([0, width])
.padding(0.1);
var y = d3.scaleLinear()
.range([height, 0]);
// append the svg object to the body of the page
// append a 'group' element to 'svg'
// moves the 'group' element to the top left margin
var svg = d3.select("#bar").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 + ")");
// get the data
d3.csv("canton_pop.csv").then(function(data) {
// format the data
data.forEach(function(d) {
d.Population = +d.Population;
});
// Scale the range of the data in the domains
x.domain(data.map(function(d) { return d.Canton; }));
y.domain([0, d3.max(data, function(d) { return d.Population; })]);
// append the rectangles for the bar chart
svg.selectAll(".bar_chart")
.data(data)
.enter().append("rect")
.attr("class", "bar_chart")
.attr("x", function(d) { return x(d.Canton); })
.attr("width", x.bandwidth())
.attr("y", function(d) { return y(d.Population); })
.attr("height", function(d) { return height - y(d.Population); });
// add the x Axis
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// add the y Axis
svg.append("g")
.call(d3.axisLeft(y));
});
Here is what my CSV file looks like:
Canton;Population
Zürich;1553423
Bern;1043132
Luzern;416347
Uri;36819
Schwyz;162157
Obwalden;38108
Nidwalden;43520
Glarus;40851
Zug;128794
Fribourg;325496
Solothurn;277462
Basel-Stadt;196735
Basel-Landschaft;290969
Schaffhausen;83107
Appenzell Ausserrhoden;55309
Appenzell Innerrhoden;16293
St. Gallen;514504
Graubünden;200096
Aargau;694072
Thurgau;282909
Ticino;350986
Vaud;814762
Valais;348503
Neuchâtel;175894
Genève;506343
Jura;73709
When running my code I am able to visualize the graphics axis but no bar appears and the axis doesn't have any graduation (below is an image of the result I get and of the error that appears in my console). Hopefully, someone can help me here.
Blank bar chart:
The error message that appears in my console:
Okay so I just realized the problem was coming from my CSV file, indeed I was using ";" as separation when I needed to use ","
First of all, I apologize for such a long post and my absolute beginner knowledge about D3 framework.
I have managed to draw the following chart using D3 framework.
However, I am not been able to accomplish the following things:
1) Cannot draw Y-Axis vertical sideline
2) Cannot make the Y-Axis ticks to align centered. At the moment they all are left-aligned
3) Cannot make the color of X-Axis baseline same as Y-Axis gridlines
4) Cannot make the label "Growth Target (7)" center-aligned Right now it is left-aligned
1) Y-Axis Vertical Sideline:
The text Growth Target (7) is within the chart. In order to make the gridlines to stop before this, I have used this (approach 1)
// ******************* << Custom Y axis grid lines Begins
var yGrid = svg.selectAll(".hgrid")
.data(yTicks)
.enter()
.append("g")
.attr("transform", function(d){
return "translate(0, " + y(d) + ")"
});
yGrid.append("line")
.attr("class", "hgrid")
.attr("x1", 0)
// shorten growth target line width to give room for the label
.attr("x2", width - growth_target_width_adjusted);
yGrid.append("text")
.text(function(d){return d; })
.attr("class", "hgrid-label")
// update X and Y positions of the label
.attr("x", -15)
.attr("y", "0.3em");
d3.selectAll(".hgrid-label")
.each(function (d, i) {
d3.select(this).style("font-size", 13);
});
// ******************* Custom Y axis grid lines Ends >>
Rather than this (approach 2):
// gridlines in y axis function
function draw_yAxis_gridlines() {
return d3.axisLeft(y)
.tickValues(yTicks);
}
// add the Y grid lines
svg.append("g")
.attr("class", "grid axis yAxis")
.call(draw_yAxis_gridlines()
.tickSize(-width)
);
The above approach 2, however, creates the Y-Axis baseline but the gridlines are using the entire chart area and overlapping the text "Growth Target (7)".
Is there any way that I can control the width of each Y-Axis line using approach 2? If not, how can I draw the Y-Axis baseline using approach 1?
2) Y-Axis ticks to align center
Currently, the Y-Axis tick values (0, 5, 10...) are left-aligned. Is there a way to make them centered?
3) Color of X-Axis baseline same as Y-Axis gridlines
In my chart above, the X-Axis color is black while the gridline colors are light gray. I need to make the X-Axis color light gray as well. What is the right way to do this?
4) Center aligning the label "Growth Target (7)"
What is the right way to make the two lines of the label centered-aligned? Right now the lines are aligned left to each other.
The desired output needs to be like this:
Here is the complete chart script that I have now:
function barColor(data_month, current_month) {
if( parseInt(data_month) >= current_month)
return "#008600";
else
return "#c4c4c4";
}
function draw_gp_chart(data_str) {
var chart_container = jQuery("#ecbg_unitary");
jQuery(chart_container).html("");
var w = parseInt(jQuery(chart_container).width());
var h = 375;
var barPadding = 2;
// set the dimensions and margins of the graph
var margin = {top: 30, right: 20, bottom: 30, left: 40};
var width = w - margin.left - margin.right;
var height = h - margin.top - margin.bottom;
var growth_target_width_adjusted = margin.left + margin.right;
var svg = d3.select("#ecbg_unitary")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + ", 10)");
var data = data_str;
// set the ranges (less width of chart to allow room for Growth Target label)
var x = d3.scaleBand().range([0, width - growth_target_width_adjusted]).padding(0.2);
var y = d3.scaleLinear().range([height, 0]);
// Scale the range of the data in the domains
x.domain(data.map(function (d) {
return d.month;
}));
var y_domain_upperBound = d3.max(data, function (d) {
return d.points;
});
y_domain_upperBound = Math.round(y_domain_upperBound / 10) * 10 + 10;
y.domain([0, y_domain_upperBound]);
// Create Y-Axis tick array to draw grid lines
var yTicks = [];
var tickInterval = 5;
for (var i = 0; i <= y_domain_upperBound; i = i + tickInterval) {
yTicks.push(i);
}
// gridlines in y axis function
function draw_yAxis_gridlines() {
return d3.axisLeft(y)
.tickValues(yTicks);
}
// ******************* << Growth Target line Begins
var targetGoalArr = [parseInt(jQuery("#ecbg_unitary_growth_target").val())];
var target = svg.selectAll(".targetgoal")
.data(targetGoalArr)
.enter()
.append("g")
.attr("transform", function(d){
return "translate(0, " + y(d) + ")"
});
target.append("line")
.attr("class", "targetgoal")
.attr("x1", 0)
// shorten growth target line width to give room for the label
.attr("x2", width - growth_target_width_adjusted);
// Adding two SVG text elements since SVG text element does not support text-wrapping.
// ref: https://stackoverflow.com/a/13254862/1496518
// Option 2: may be tried later - https://bl.ocks.org/mbostock/7555321
// Text element 1
target.append("text")
.text(function(d){return "Growth " })
.attr("class", "tglebal")
// update X position of the label
.attr("x", width - growth_target_width_adjusted + 5)
.attr("y", "-0.3em");
// Text element 2
target.append("text")
.text(function(d){return "Target (" + d + ")" })
.attr("class", "tglebal")
// update X position of the label
.attr("x", width - growth_target_width_adjusted + 5)
.attr("y", "1em");
d3.selectAll(".tglebal")
.each(function (d, i) {
d3.select(this).style("font-size", 12);
});
// ******************* Growth Target line Ends >>
// ******************* << Custom Y axis grid lines Begins
var yGrid = svg.selectAll(".hgrid")
.data(yTicks)
.enter()
.append("g")
.attr("transform", function(d){
return "translate(0, " + y(d) + ")"
});
yGrid.append("line")
.attr("class", "hgrid")
.attr("x1", 0)
// shorten growth target line width to give room for the label
.attr("x2", width - growth_target_width_adjusted);
yGrid.append("text")
.text(function(d){return d; })
.attr("class", "hgrid-label")
// update X and Y positions of the label
.attr("x", -15)
.attr("y", "0.3em");
d3.selectAll(".hgrid-label")
.each(function (d, i) {
d3.select(this).style("font-size", 13);
});
// ******************* Custom Y axis grid lines Ends >>
// append the rectangles for the bar chart
svg.selectAll("rect")
.data(data)
.enter().append("rect")
.attr("x", function (d) {
return x(d.month);
})
.attr("width", x.bandwidth())
.attr("y", function (d) {
return y(d.points);
})
.attr("height", function (d) {
return height - y(d.points);
})
//.attr("height", function(d) {return d.points;})
.attr("fill", function (d) {
return barColor(d.data_month_number, d.current_month_number)
});
// column labels
svg.selectAll(".colvalue")
.data(data)
.enter()
.append("text")
.attr("class", "colvalue")
.text(function (d) {
return d.points;
})
.attr("text-anchor", "middle")
.attr("x", function (d, i) {
return x(d.month) + x.bandwidth() / 2;
})
//.attr("x", function(d) { return x(d.month); })
.attr("y", function (d) {
//return h - (d.points * 4) - 10;
return y(d.points) - 10;
})
.attr("font-family", "Roboto")
.attr("font-size", "13px")
.attr("font-weight", "bold")
.attr("fill", "#606668");
// add the x Axis
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// text label (axis name) for the x axis
var xAxisNameHeightFromTop = height + margin.bottom + 20;
svg.append("g")
.attr("class", "x-axis-name")
.append("text")
.attr("transform", "translate(" + width / 2 + ", " + xAxisNameHeightFromTop + ")")
.style("text-anchor", "middle")
.text("MONTH");
// text label for the y axis
svg.append("g")
.attr("class", "y-axis-name")
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0 - margin.left)
.attr("x", 0 - (height / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.text("SALES UNITS");
// add the Y grid lines
/*svg.append("g")
.attr("class", "grid axis yAxis")
.call(draw_yAxis_gridlines()
.tickSize(-width)
);*/
d3.selectAll(".yAxis>.tick>text")
.each(function (d, i) {
d3.select(this).style("font-size", 13);
});
}
I had a D3js code which produces bar graphs and works fine with version 3.x. I wanted to upgrade the code to version 5 in the interest of being updated. Upon doing so I was able to correct a number of syntax updates such as scaleLinear, scaleBand, etc. The data is imported via tsv. The code is able to show the graph on the page with the correct x axis widths for the bars. However, the yAxis bars go out of bounds and the scale on the y-axis is very short. For example, the data shows the maximum value of the data to be 30000, but the yaxis is only from 0-90. Upon further investigation the d.RFU values from which the y data is generated seems to be not converted from string to integers. In the v3 code, I had a function at the end which converted the type of d.RFU to integer using the unary operator
d.RFU = +d.RFU
However, it seems to be not working in v5. Could this be due to the promises implementation in replacement of the asynchronous code?
Any solutions on how to fix this in version 5?
Please let me know if you need any more information and forgive me if I have missed out anything as I am new to programming and this website. Any help is appreciated.
Here is parts of the code which I have right now:
//set dimensions of SVG and margins
var margin = { top: 30, right: 100, bottom: 50, left: 100, },
width = divWidth - margin.left - margin.right,
height = 250 - margin.top - margin.bottom,
x = d3.scaleBand()
.range([0, width - 20], 0.1),
y = d3.scaleLinear()
.range([height,0]);
//setup the axis
var xAxis = d3.axisBottom(x);
var yAxis = d3.axisLeft(y);
var svg = d3.select("#bargraphID")
.append("svg")
.attr("width", width + margin.left + margin.right - 100)
.attr("height", height + margin.top + margin.bottom - 10)
.append("g")
.attr("transform", "translate (" + margin.left + "," + margin.top + ")");
d3.tsv(filename).then(function(data) {
// get x values from the document id
x.domain(data.map(function(d) {
return d.ID;
}));
yMax = d3.max(data, function(d) {
return d.RFU;
});
// get the y values from the document RFU tab
y.domain([0, yMax]);
//create the x-axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate (0, " + height + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "middle")
.attr("dx", "0em")
.attr("dy", "-0.55em")
.attr("y", 30)
.attr("class", "x-axisticks");
//create the y-axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
//add the data as bars
var bar = svg.selectAll("bar")
.data(data)
.enter()
.append("rect")
.style("fill", barColor)
.attr("fill-opacity", "0.3")
.attr("x", function(d) {
return x(d.ID);
})
.attr("width", x.bandwidth())
//set initial coords for bars for animation.
.attr("y", height)
.attr("height", 0)
//animate bars to final heights
.transition()
.duration(700)
.attr("y", function(d) {
return y(d.RFU);
})
.attr("height", function(d) {
return height - y(d.RFU);
})
.attr("fill-opacity", "1.0")
.attr("class", "y-data");
});
//convert RFU to integers
function type(d) {
d.RFU = +d.RFU;
return d;
}
Just like with the old v3 and v4 versions, you have to pass the row conversion function to d3.tsv in D3 v5:
d3.tsv(filename, type)
Then, use the promise with then. Have in mind that d3.tsv always return strings (be it D3 v3, v4 or v5), because:
If a row conversion function is not specified, field values are strings.
Here is the demo with fake data:
var tsv = URL.createObjectURL(new Blob([
`name RFU
foo 12
bar 42
baz 17`
]));
d3.tsv(tsv, type).then(function(data) {
console.log(data)
})
function type(d) {
d.RFU = +d.RFU;
return d;
}
<script src="https://d3js.org/d3.v5.min.js"></script>
PS: Since SO snippet may have a problem loading that blob in some browsers, here is the same code in JSFiddle, check the console: https://jsfiddle.net/kv0ea0x2/
I wish to create a line chart with transition. So for the first step, I wish to simply draw the axis and move the x-axis. The x-axis has 0-15 as values, and I want these values to keep on moving in a loop.. I took help from this code which I got through stackoverflow: http://jsfiddle.net/aggz2qbn/
Here is the code I have:
var t = 1, maxval;
var i, n = 40;
var duration = 750;
function refilter(){
var margin = {top: 10, right: 10, bottom: 20, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.linear()
.domain([1, 15])
.range([0, width]);
var y = d3.scale.linear()
.domain([0, maxval])
.range([height, 0]);
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
var g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// extra svg to clip the graph and x axis as they transition in and out
var graph = g.append("svg")
.attr("width", width)
.attr("height", height + margin.top + margin.bottom);
var xAxis = d3.svg.axis().scale(x).orient("bottom");
var axis = graph.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(x.axis=xAxis);
g.append("g")
.attr("class", "y axis")
.call(d3.svg.axis().scale(y).orient("left"));
tick();
function tick() {
t++;
if(t>=15)
t=1;
else if(t<=15)
{
x.domain([t,15]);
axis.transition()
.duration(500)
.ease("linear")
.call(xAxis)
.each("end", tick);
}
// slide the x-axis left
}
}
The values on x-axis do move, but not in a repetitive way, instead they simply stretch frm 1 to 15. Can anybody help me out?
Your tick function is setting the domain as:
[1,15]
then
[2,15]
then
[3,15]
etc...
This is more a zoom towards the end. What you want is a rolling effect (say with an N of 5 ticks):
[1,5]
then
[2,6]
then
[3,7]
etc...
So:
function tick() {
var curD = x.domain(); // get current domain
if (curD[1] >= 15){ // at 15 reset to 1,5
curD[0] = 0;
curD[1] = n-1;
}
x.domain([curD[0]+1,curD[1]+1]); // increase both sides by one
// slide the x-axis left
axis.transition()
.duration(1500)
.ease("linear")
.call(xAxis)
.each("end", tick);
}
Example here.
EDITS FOR COMMENT
Datetimes can be handled the same way, but do the math different. For instance, scrolling my months:
function tick() {
var curD = x.domain();
var newD = [curD[0].setMonth(curD[0].getMonth() + 1),
curD[1].setMonth(curD[1].getMonth() + 1)]
x.domain(newD);
// slide the x-axis left
axis.transition()
.duration(1500)
.ease("linear")
.call(xAxis)
.each("end", tick);
}
Updated example.
I am trying to draw a simple line chart in D3. I am able to draw the data line with a linear scale on the Y-axis (ignore the fact that there are no ticks):
Here is the code and a relevant JSFiddle for this:
var margin = { top: 10, right: 245, bottom: 30, left: 100 },
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// Set the X and Y scale (e.g., linear vs. log) and the domains
var x = d3.scale.linear().domain([-95,-74]).range([0, width]);
// ******* CHANGE linear() to log() *************************** //
var y = d3.scale.linear().domain([0.000010,1]).range([height, 0]);
// Set the locations of the axis (e.g., bottom and left)
var xAxis = d3.svg.axis().scale(x).orient("bottom");
var yAxis = d3.svg.axis().scale(y).orient("left").ticks(0, ".1");;
// Set up some lines and related colors
var color = d3.scale.category10();
var line = d3.svg.line().x(function (d) { return x(d.power); }).y(function (d) { return y(d.ber); });
// Set the graph to be drawn in the respective div (grapharea), and set dimensions
var svg = d3.select("body").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 + ")");
// Append the X and Y axis to the actual SVG graph
svg.append("g").attr("class", "y axis").call(yAxis);
svg.append("g").attr("class", "x axis").attr("transform", "translate(0," + height + ")").call(xAxis);
// Parse the JSON data response
var jsonString = '[{"power":-91.0,"ber":0.0},{"power":-92.0,"ber":0.0},{"power":-93.0,"ber":1E-07},{"power":-94.0,"ber":6.5E-06},{"power":-95.0,"ber":0.000147}]';
var jsonData = JSON.parse(jsonString);
jsonData.forEach(function (d) {
d.ber = d.ber;
d.power = d.power;
});
// Set the data properly
x.domain(d3.extent(jsonData, function(d) { return d.power; }));
y.domain(d3.extent(jsonData, function(d) { return d.ber; }));
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("BER");
svg.append("path")
.datum(jsonData)
.attr("class", "line")
.attr("d", line);
If all I change is linear() to log() on the variable y, then I get a resulting log scale graph but my data line no longer shows up (relevant JSFiddle):
Does anyone know what I am doing wrong for the data line to not show up on the log scale graph?
You have zeros in your data. The log of zero is not defined. You'll need to use a different type of scale.