I have a csv file "data_good.csv" with the simple format : date,value ("date" here is just a number, not date format.)
I've just started using d3js and here is the code i'm using for the graph :
// set the dimensions and margins of the graph
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = screen.width - 200,
height = 200;
// 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("#body_d3_graph").append("svg").attr("class","my_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("tmp/data_good.csv", function(error, data) {
if (error) throw error;
// format the data
data.forEach(function(d) {
d.value = +d.value;
});
// Scale the range of the data in the domains
x.domain(data.map(function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.value; })]);
// append the rectangles for the bar chart
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.style("fill", "green")
.attr("x", function(d) { return x(d.date); })
.attr("width", x.bandwidth())
.attr("y", function(d) { return y(d.value); })
.attr("height", function(d) { return height - y(d.value); });
// 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));
});
It's working as expected with several green bars.
But now i have another csv file ("tmp/data_bad.csv") and i would like to have it as a red bar on top of the green one. According to their key "date". (There is no date without value : data_good.csv and data_bad.csv have the same number of lines.)
I think that you should separate logic which draws chart and logic which loads a file.
Also you have to group bars and separate drawing axes from bars.
// set the dimensions and margins of the graph
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = screen.width - 200,
height = 200;
// 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("#body_d3_graph").append("svg").attr("class","my_svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
var drawAxes = function(data) {
// 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));
}
var drawChart = function(data) {
// Scale the range of the data in the domains
x.domain(data.map(function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.value; })]);
// append the rectangles for the bar chart
svg
.append('g')
.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.style("fill", "green")
.attr("x", function(d) { return x(d.date); })
.attr("width", x.bandwidth())
.attr("y", function(d) { return y(d.value); })
.attr("height", function(d) { return height - y(d.value); });
}
// get the data
d3.csv("tmp/data_good.csv", function(error, data) {
if (error) throw error;
// format the data
data.forEach(function(d) {
d.value = +d.value;
});
drawChart(data);
});
Then if you want to preserve domain scaling to be defined only by "good data" you can abstract it too and use only in "good data reader".
Remember to draw axes when changing scales.
// set the dimensions and margins of the graph
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = screen.width - 200,
height = 200;
// 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("#body_d3_graph").append("svg").attr("class","my_svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
var drawAxes = function(data) {
// 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));
}
var setDomains = function(data) {
// Scale the range of the data in the domains
x.domain(data.map(function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.value; })]);
drawAxes();
}
var drawChart = function(data) {
// append the rectangles for the bar chart
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.style("fill", "green")
.attr("x", function(d) { return x(d.date); })
.attr("width", x.bandwidth())
.attr("y", function(d) { return y(d.value); })
.attr("height", function(d) { return height - y(d.value); });
// 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));
}
// get the data
d3.csv("tmp/data_good.csv", function(error, data) {
if (error) throw error;
// format the data
data.forEach(function(d) {
d.value = +d.value;
});
setDomains(data);
drawChart(data);
});
d3.csv("tmp/data_bad.csv", function(error, data) {
if (error) throw error;
// format the data
data.forEach(function(d) {
d.value = +d.value;
});
drawChart(data);
});
Related
When building an area chart in D3.js, when you have only a single value the chart does not render.
For demonstration purposes, I modified the following example: https://d3-graph-gallery.com/graph/area_basic.html to illustrate the problem.
<script>
// set the dimensions and margins of the graph
var margin = {top: 10, right: 30, bottom: 30, left: 50},
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/data_to_viz/master/Example_dataset/3_TwoNumOrdered_comma.csv",
// When reading the csv, I must format variables:
function(d){
return { date : d3.timeParse("%Y-%m-%d")(d.date), value : d.value }
},
// Now I can use this dataset:
function(data) {
data = [data[0]]
// Add X axis --> it is a date format
var x = d3.scaleTime()
.domain(d3.extent(data, function(d) { return d.date; }))
.range([ 0, width ]);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add Y axis
var y = d3.scaleLinear()
.domain([0, d3.max(data, function(d) { return +d.value; })])
.range([ height, 0 ]);
svg.append("g")
.call(d3.axisLeft(y));
// Add the area
svg.append("path")
.datum(data)
.attr("fill", "#cce5df")
.attr("stroke", "#69b3a2")
.attr("stroke-width", 1.5)
.attr("d", d3.area()
.x(function(d) { return x(d.date) })
.y0(y(0))
.y1(function(d) { return y(d.value) })
)
})
</script>
I would expect that chart to look something like:
If you inspect the element the path element you can see it is rendering, just 0 width/height:
I would like to know what i am doing wrong i think its in the axis as the stacked data as well as well as the rect elements are showing. Been stuck now for 2 hours.
var margin = {top: 10, right: 30, bottom: 20, left: 50},
width = 760 - margin.left - margin.right,
height = 600 - margin.top - margin.bottom;
var svg = d3.select("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
innerheight=height + margin.top + margin.bottom
d3.csv("data.csv", function (error, data) {
if (error) {
throw error;
}
// var subgroups = data.columns.slice(1);
// console.log(subgroups);
var subgroups = d3.map(data, function(d){ return(d.Segment)}).keys();
var groups = d3.map(data, function(d){ return(d.Category)}).keys();
console.log(groups);
// Add X axis
var x = d3.scaleBand()
.domain(groups)
.range([0, width])
.padding([0.2])
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).tickSizeOuter(0));
// Add Y axis
var y = d3.scaleLinear()
.domain([0, d3.max(data, function(d) { return d.Sales; })])
.range([ height , 0 ]);
console.log(y.domain())
console.log(y.range())
svg.append("g")
.call(d3.axisLeft(y));
var color = d3.scaleOrdinal()
.domain(subgroups)
.range(['#e41a1c','#377eb8','#4daf4a']);
ymax= d3.max(data, function(d) { return d.Sales; });
console.log(subgroups);
var stackedData = d3.stack().keys(subgroups);
stackedData = stackedData(data)
console.log(stackedData);
svg.append("g")
.selectAll("g")
// Enter in the stack data = loop key per key = group per group
.data(stackedData)
.enter().append("g")
.attr("fill", function (d) { return (color(d.key)); })
.selectAll("rect")
// enter a second time = loop subgroup per subgroup to add all rectangles
.data(function (d) { return d; })
.enter().append("rect")
.attr("x", function (d) { return x(d.data.Category); })
.attr("y", function (d) { return y(d.data.Sales); })
.attr("height", function (d) { return (height - y(d.data.Sales)) ; })
.attr("width", x.bandwidth())
});
csv is in this format
Row ID,Segment,Category,Sales,Total
1,Consumer,Accessories,84754.742,1007859.426
2,Consumer,Appliances,42852.68,1007859.426
31,Corporate,Phones,89019.204,588163.8014
34,Corporate,Tables,57226.0385,588163.8014
37,Home Office,Art,4799.502,373365.8573
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.
In Bellow code I am trying to use separate charts (i.e. Bar and line chart) and getting Error: attribute d: Expected number, "MNaN,116LNaN,117L…"
Tried with using different SVG elements for both charts. Give some suggestions if I am missing something.
If I commenting Bar chart code then Line chart works properly and same works with the Bar chart but both at a same time doesn't work.
<div>
<div>Simple Bar Chart</div>
<div class="simpleBar">
<svg width="960" height="500"></svg>
</div>
</div>
<div>
<div>Line Chart</div>
<div id="lineGraph"></div>
</div>
<script>
// --------------- Bar chart code starts -----------------------
var svg = d3.select(".simpleBar > svg"),
margin = {top: 20, right: 20, bottom: 30, left: 50},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom;
var tooltip = d3.select("body").append("div").attr("class", "toolTip");
var x = d3.scaleBand().rangeRound([0, width]).padding(0.1),
y = d3.scaleLinear().rangeRound([height, 0]);
var colours = d3.scaleOrdinal()
.range(["#6F257F", "#CA0D59"]);
var g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.json("dataSample.json", function(error, data) {
if (error) throw error;
x.domain(data.map(function(d) { return d.Timestamp; }));
y.domain([0, d3.max(data, function(d) { return d.DayTotal; })]);
g.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
g.append("g")
.attr("class", "axis axis--y")
.call(d3.axisLeft(y).ticks(5).tickFormat(function(d) { return parseInt(d) + "Rs"; }).tickSizeInner([-width]))
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 1)
.attr("dy", "0.71em")
.attr("text-anchor", "end")
.attr("fill", "#5D6971")
.text("Average Expence(Rs)");
g.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("x", function(d) { return x(d.Timestamp); })
.attr("y", function(d) { return y(d.DayTotal); })
.attr("width", x.bandwidth())
.attr("height", function(d) { return height - y(d.DayTotal); })
.attr("fill", function(d) { return colours(d.Timestamp); })
.on("mousemove", function(d){
tooltip
.style("left", d3.event.pageX - 50 + "px")
.style("top", d3.event.pageY - 70 + "px")
.style("display", "inline-block")
.html((d.Timestamp) + "<br>" + "Rs" + (d.DayTotal));
})
.on("mouseout", function(d){ tooltip.style("display", "none");});
});
// --------------- Line chart code starts -----------------------
// parse the Timestamp / time
var parseTime = d3.timeParse("%d-%b-%y");
// set the ranges
var xLine = d3.scaleTime().range([0, width]);
var yLine = d3.scaleLinear().range([height, 0]);
// define the line
var valueline = d3.line()
.x(function(d) { return x(d.Timestamp); })
.y(function(d) { return y(d.DayTotal); });
var svgLine = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// append the svg obgect to the body of the page
// appends a 'group' element to 'svg'
// moves the 'group' element to the top left margin
var svgLine = d3.select("#lineGraph").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.json("dataSample.json", function(error, data) {
if (error) throw error;
// format the data
data.forEach(function(d) {
d.Timestamp = parseTime(d.Timestamp);
d.DayTotal = +d.DayTotal;
});
// Scale the range of the data
xLine.domain(d3.extent(data, function(d) { return d.Timestamp; }));
yLine.domain([0, d3.max(data, function(d) { return d.DayTotal; })]);
// Add the valueline path.
svgLine.append("path")
.data([data])
.attr("class", "line")
.attr("d", valueline);
// Add the X Axis
svgLine.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(xLine).ticks(5));
// Add the Y Axis
svgLine.append("g")
.call(d3.axisLeft(yLine));
});
</script>
and bellow is the Json data format:
[
{
"DayTotal": "418",
"Timestamp": "15-Mar-18"
},{...}
]
You are supposed to use xLine, yLine in your d3 line function whereas you used the ones(x, y) that you meant for bar charts.
var valueline = d3.line()
.x(function(d) { return xLine(d.Timestamp); })
.y(function(d) { return yLine(d.DayTotal); });
Solved JSFiddle
Here is my entire JS code, I'm really just getting started based on some example.
I would like to be able to catch a click event, wether on the whole drawing, wether on the chart line and get y value for it.
onMouseOver and onMouseOut work as intended for now.
Thank you.
//Example may be interesting: http://bl.ocks.org/byrongibson/5232838
var parseDate = d3.time.format("%d-%b-%y").parse;
var svg, d3, x, y, valueline, xAxis, yAxis, width, height;
function CreateSvg()
{
// Set the dimensions of the canvas / graph
var margin = {top: 30, right: 20, bottom: 30, left: 50};
width = 600 - margin.left - margin.right;
height = 270 - margin.top - margin.bottom;
// Set the ranges
x = d3.time.scale().range([0, width]);
y = d3.scale.linear().range([height, 0]);
// Define the axes
xAxis = d3.svg.axis().scale(x)
.orient("bottom").ticks(5);
yAxis = d3.svg.axis().scale(y)
.orient("left").ticks(5);
// Define the line
valueline = d3.svg.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.close); });
//var rect = d3.selectAll("rect");
// Adds the svg canvas
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 + ")").on("click", click)
;
}
function GetData(data)
{
data.forEach(function(d)
{
d.date = parseDate(d.date);
d.close = +d.close;
});
data.reverse();
data.push({date:parseDate('3-May-12'), close:Math.random() * 1200});
data.push({date:parseDate('4-May-12'), close:Math.random() * 1200});
data.reverse();
return data;
}
function Draw()
{
d3.csv("data.csv", function(error, data) {
data = GetData(data);
// Scale the range of the data
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.close; })]);
svg.text("");
// Add the valueline path.
svg.append("path")
.attr("class", "line")
.attr("d", valueline(data))
.on("mouseover", onMouseOver)
.on("mouseout", onMouseOut)
.on("click", click)
;
// Add the X Axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Add the Y Axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
});
}
function click() {
alert("onclick");
}
function onMouseOver(){
alert("mouseOver");
};
function onMouseOut(){
alert("mouseOut");
};
CreateSvg();
Draw();
Draw();