Find min date within data to draw svg using d3.js - javascript

Im using d3.js to visualise some data, and I'm brand new to both Javascript and SVG etc.
I've got the following code which does work
var margin = {top: 20, right: 30, bottom: 30, left: 140},
width = pbi.width - margin.left - margin.right,
height = pbi.height - margin.top - margin.bottom;
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.ordinal()
.rangeRoundBands([0, height], 0.1, 0.2);
var svg = d3.select("#chart")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var header = svg.append("g")
.attr("id", "header");
// Title
header.append("text")
.attr("class", "title")
.attr("x", 10)
.attr("y", 15)
.text("The visual");
pbi.dsv(function(Data) {
var mindate = new Date('01-01-1905');
var maxdate = new Date();
x.domain([mindate, maxdate]).nice(d3.time.month);
y.domain(Data.map(function(d) { return d.milestone; }));
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.svg.axis().scale(x).orient("bottom"));
svg.append("g")
.attr("class", "y axis")
.call(d3.svg.axis().scale(y).orient("left"));
svg.append("g")
.selectAll(".circle")
.data(Data)
.enter().append("circle")
.attr("cx", function(d) {return x(new Date(d.date))} )
.attr("cy", function(d) {return y(d.milestone)+33; })
.attr("r", 10);
});
What I want to do next is alter the mindate line so it dynamically finds the lowest date in the dataset. As of now, its hardcoded.
Something like var mindate = new Date(d3.min(d.date)) but that definitely doesn't work
My data is formatted as follows:
{reportingyear:'2016', owner:'***', thing:'a-thing', milestone:'start-date', date:'01/12/10'},
{reportingyear:'2017', owner:'***', thing:'a-thing', milestone:'start-date', date:'01/12/10'},
{reportingyear:'2020', owner:'***', thing:'a-thing', milestone:'start-date', date:'01/12/10'}
etc. (De-identified for security reasons!)
Any tips to point me in the right direction is greatly appreciated.
Thank you.
After applying the help below, I know am running into the following error
This code is being developed for use in PowerBI in the d3.js plug in, so the data is actually going to be pulled from the data model, the list data is just to replicate the svg in browser to edit.

First, if you've got data represented as a list of objects with a date field expressed as a string, you should consider converting those strings to Date objects. It's not immediately clear from your example what the date format is but, assuming it's month/day/year, you could perform that task as
data = data.map(function (o) {
o.date = new Date(o.date);
return o;
})
Then, you could compute the min as
d3.min(data, o => o.date)
Here's complete code:
let data = [
{
reportingyear: "2016",
owner: "***",
thing: "a-thing",
milestone: "start-date",
date: "01/12/10"
},
{
reportingyear: "2017",
owner: "***",
thing: "a-thing",
milestone: "start-date",
date: "01/12/10"
},
{
reportingyear: "2020",
owner: "***",
thing: "a-thing",
milestone: "start-date",
date: "01/12/10"
}
].map(function (o) {
o.date = new Date(o.date);
return o;
});
let min_date = d3.min(data, o => o.date)
console.log(min_date)
<script src="https://d3js.org/d3.v3.min.js"></script>

Related

Histogram with values from csv

I am trying to create a simple histogram with values stored in a csv (that I will be modifying through the time).
The code I am using now is: (edited code!)
var values = []
d3.csv('../static/CSV/Chart_data/histogram_sub.csv?rnd='+(new Date).getTime(),function(data){
values = Object.keys(data).map(function(k){ return data[k]['Calculus I']});
var color = "steelblue";
// Generate a 1000 data points using normal distribution with mean=20, deviation=5
// A formatter for counts.
var formatCount = d3.format(",.0f");
var margin = {top: 20, right: 30, bottom: 30, left: 30},
width = 800 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
var max = d3.max(values);
var min = d3.min(values);
var x = d3.scale.linear()
.domain([min, max])
.range([0, width]);
// Generate a histogram using twenty uniformly-spaced bins.
var data = d3.layout.histogram()
.bins(x.ticks(20))
(values);
var yMax = d3.max(data, function(d){return d.length});
var yMin = d3.min(data, function(d){return d.length});
var colorScale = d3.scale.linear()
.domain([yMin, yMax])
.range([d3.rgb(color).brighter(), d3.rgb(color).darker()]);
var y = d3.scale.linear()
.domain([0, yMax])
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var svg = d3.select("#Histogram2").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 + ")");
var bar = svg.selectAll(".bar")
.data(data)
.enter().append("g")
.attr("class", "bar")
.attr("transform", function(d) { return "translate(" + x(d.x) + "," + y(d.y) + ")"; });
bar.append("rect")
.attr("x", 1)
.attr("width", (x(data[0].dx) - x(0)) - 1)
.attr("height", function(d) { return height - y(d.y); })
.attr("fill", function(d) { return colorScale(d.y) });
bar.append("text")
.attr("dy", ".75em")
.attr("y", -12)
.attr("x", (x(data[0].dx) - x(0)) / 2)
.attr("text-anchor", "middle")
.text(function(d) { return formatCount(d.y); });
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
});
And my csv file looks like this:
Calculus I
5.0
5.1
5.7
...
And I am getting errors that I think refer to data[0]:
Uncaught TypeError: Cannot read property 'dx' of undefined
Any help? Thanks in advance!
Here's a plunkr using d3.csv and fetching data from the file:
http://plnkr.co/edit/2xCvrwiXWzrS6gtbmIU7?p=preview
And please go through the docs for d3.csv
Using the same, here are the relevant changes to the code:
Added a new file test.csv with the content.
Fetched the file using d3.csv:
d3.csv("test.csv", parse, function(error, data) {
console.log(data);
});
The parse that you see above is a accessor function that receives every row from the csv and I'm using it to parse the integer value.
function parse(row) {
row['Calculus I'] = +row['Calculus I'];
return row;
}
And as you were assuming values to be array of integers, I'm mapping the fetched data in the same format as desired using map
values = data.map(function(d) { return d['Calculus I']; });
Hope this helps.
What you need to do is first read the csv using d3.csv and then convert it to an array of values
var values = []
d3.csv("**csv file path**",function(data){
//This will internally convert csv to a json and then we can extract all values and transform it into an array
values = Object.keys(data).map(function(k){ return data[k]['Calculus I']});
//If the above code is too complex for you
//for(i in data){
// values.push(data[i]['Calculus I']
//}
});
//Rest of the chart rendering code goes here

I am trying to visualize my json object with D3. I want date to be the x axis and y to be sales. number values stored a string

I have a json object that I am trying to visualize with D3.js. I want the x axis to represent the date in the json object which is stored as a string and the y axis to represent sales projections which is also a number in a string i.e "85,000.00"
example of my json object:
[{"Num":78689,"Client":"Health Services" ,"TotalEstSales":"85,000,000.00","Date ":"2/15/2015","RFP Receipt Date":null,"Exp. Proposal Due Date":"3/6/2015","Proposal Submission Date":null,"estAwardDate":"4/15/2015","Procurement Type":"New - Incumbent","Bid Type":"Standalone Contract"}]
and my d3 code:
// Various accessors that specify the four dimensions of data to visualize.
function x(d) { return d.date; }
function y(d) { return d.TotalEstSales; }
function radius(d) { return parseFloat(d.TotalEstSales);}
function color(d) { return d.region; }
function key(d) { return d.Title;}
// Chart dimensions.
var margin = {top: 19.5, right: 19.5, bottom: 19.5, left: 39.5},
width = 960 - margin.right,
height = 500 - margin.top - margin.bottom;
// Various scales. These domains make assumptions of data, naturally.
var xScale = d3.scale.log().domain([300, 1e5]).range([0, width]),
yScale = d3.scale.linear().domain([10000, 85000000]).range([height, 0]),
radiusScale = d3.scale.sqrt().domain([0, 5e8]).range([0, 40]),
colorScale = d3.scale.category10();
// The x & y axes.
var xAxis = d3.svg.axis().orient("bottom").scale(xScale).ticks(12, d3.format(",d")),
yAxis = d3.svg.axis().scale(yScale).orient("left");
// Create the SVG container and set the origin.
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 + ")");
// 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);
// Add an x-axis label.
svg.append("text")
.attr("class", "x label")
.attr("text-anchor", "end")
.attr("x", width)
.attr("y", height - 6)
.text("Data of RFP");
// Add a y-axis label.
svg.append("text")
.attr("class", "y label")
.attr("text-anchor", "end")
.attr("y", 6)
.attr("dy", ".75em")
.attr("transform", "rotate(-90)")
.text("Award amount");
// Add the year label; the value is set on transition.
var label = svg.append("text")
.attr("class", "year label")
.attr("text-anchor", "end")
.attr("y", height - 24)
.attr("x", width)
.text(2015);
// Load the data.
d3.json("rfpdata.json", function(data) {
// A bisector since many nation's data is sparsely-defined.
// var bisect = d3.bisector(function(d) { return d[0]; });
// Add a dot per nation. Initialize the data at 1800, and set the colors.
var dot = svg.append("g")
.attr("class", "dots")
.selectAll(".dot")
.data(data)
.enter().append("circle")
.attr("class", "dot")
.style("fill", function(d) { return colorScale(color(d)); })
.call(position)
.sort(order);
// Add a title.
dot.append("title")
.text(function(d) { return d.Client; })
// Positions the dots based on data.
function position(dot) {
dot .attr("cx", function(d) { return xScale(x(d)); })
// .attr("cy", function(d) { return yScale(y(d)); })
.attr("r", function(d) { return radiusScale(radius(d)); });
}
// Defines a sort order so that the smallest dots are drawn on top.
function order(a, b) {
return radius(b) - radius(a);
}
// After the transition finishes, you can mouseover to change the year.
function enableInteraction() {
var yearScale = d3.scale.linear()
.domain([1800, 2009])
.range([box.x + 10, box.x + box.width - 10])
.clamp(true);
// Cancel the current transition, if any.
function mouseover() {
label.classed("active", true);
}
function mouseout() {
label.classed("active", false);
}
function mousemove() {
displayYear(yearScale.invert(d3.mouse(this)[0]));
}
}
// this is the function needed to bring in data
// Interpolates the dataset for the given (fractional) year.
function interpolateData(date) {
return data.map(function(d) {
return {
title: d.Title,
client: d.Client,
sales: parseFloat(d.TotalEstSales),
sales: interpolateValues(d.TotalEstSales, date),
};
});
}
// Finds (and possibly interpolates) the value for the specified year.
function interpolateValues(values, date) {
var i = bisect.left(values, date, 0, values.length - 1),
a = values[i];
if (i > 0) {
var b = values[i - 1],
t = (date - a[0]) / (b[0] - a[0]);
return a[1] * (1 - t) + b[1] * t;
}
return a[1];
}
});
I am not sure what I am doing wrong but the data is not displaying? Am i properly parsing the date string? This was a graph available on the d3 site. I want a bubble graph where the radius changes depending on the size of the sale and the date is on the x axis.
#all Update:
I was able to make the proper adjustment for date on the xaxis here:
var xAxis = d3.svg.axis().orient("bottom").scale(xScale).tickFormat(d3.time.format("%m/%d")),
yAxis = d3.svg.axis().scale(yScale).orient("left").ticks(23, d3.format(" ,d"));
d3.time.format was what I was looking for. Once data was loaded I needed to parse the date:
month = data.Date;
parseDate = d3.time.format("%m/%d/%Y").parse;
data.forEach(function(d) {
d.Date = parseDate(d.Date);
});
// update Dates here when new report comes in monthly
xScale.domain([parseDate("1/1/2015"),parseDate("6/1/2015")]);
obviously, using "Date" as a name column in the excel file was not idea for "Date" in js(because it is an oject).

Can't display a single bar in D3

I'm trying to display single bars in D3. I have a data of the type:
data = [
"value1": 1,
"value2": 2,
"value3": 3,
]
Because the y scale is not the same, I'm trying to display three different bar-charts, each one of them just with a bar. I don't need x-axis.
As you can see in this fiddle: http://jsfiddle.net/GPk7s/, The bar is not showing up, although if you inspect the source code, it has been added. I think it is because I'm not providing a x range, but I don't know how, because I don't really have one.
I just want a bar whose height is related to the range I provide (in the fiddle example this is [10, 30]).
I copy here the code just in case:
var margin = {
top: 50,
right: 0,
bottom: 100,
left: 30
},
width = 200 - margin.left - margin.right,
height = 200 - margin.top - margin.bottom;
var data = [{ "Value": 22.5 } ];
var yRange = d3.scale.linear().range([height, 0]);
yRange.domain([10, 30]);
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 + ")");
svg.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("class", "bar")
.attr("y", function (d) {
return yRange(d.Value);
})
.attr("height", function (d) {
return height - yRange(d.Value);
})
.attr("y", function (d) {
return yRange(d.Value) + 3;
})
.attr("dy", ".75em")
.text(function (d) {
return d.Value;
});
Thanks for your help!
There are two problems with what you are doing:
1) Your rectangle doesn't have a width. I added this:
...
.attr("class", "bar")
.attr("width", 10)
.attr("y", function (d) {
...
2) Your data is not an array. D3 expects arrays of data to be provided with the .data() method, and you have data = {Value : 22.5} (effectively). I changed it to this:
...
var data = [{'Value' : 22.5}];
...
Updated fiddle is here.

D3.js moving a line and a circle after a button press

The JSFiddle of my code is at this URL http://jsfiddle.net/b8eLJ/1/
Within the code, I have implemented a back button. The objective is that the calendar on x-axis should go back in time. As this happens, the lines should also move back in time.
I have the x-axis working correctly with the new domain - but am struggling with the lines and circles.
I have also looked at the following example http://bl.ocks.org/mbostock/1166403
I am a little confused about how to either "select the line and move it" or "destroy the line and recreate it". I have tried both and neither seem to work.
function startFunction() {
console.log("start");
endDate.setDate(endDate.getDate() - 7);
startDate.setDate(startDate.getDate() - 7);
//change the domain to reflect the new dates
xScale.domain([startDate, endDate]);
var t = svg.transition().duration(750);
t.select(".x.axis").call(xAxis);
//???
var pl = svg.selectAll("path.line");
pl.exit().remove();
}
// INPUT
dataset2 = [{
movie: "test",
results: [{
week: "20140102",
revenue: "5"
}, {
week: "20140109",
revenue: "10"
}, {
week: "20140116",
revenue: "17"
}, ]
}, {
movie: "test",
results: [{
week: "20140206",
revenue: "31"
}, {
week: "20140213",
revenue: "42"
}]
}];
console.log("1");
var parseDate = d3.time.format("%Y%m%d").parse;
var lineFunction = d3.svg.line()
.x(function (d) {
return xScale(parseDate(String(d.week)));
})
.y(function (d) {
return yScale(d.revenue);
})
.interpolate("linear");
console.log("2");
var endDate = new Date();
var startDate = new Date();
startDate.setDate(startDate.getDate() - 84);
//SVG Width and height
var margin = {
top: 20,
right: 10,
bottom: 20,
left: 40
};
var w = 750 - margin.left - margin.right;
var h = 250 - margin.top - margin.bottom;
//X SCALE AND AXIS STUFF
//var xMin = 0;
//var xMax = 1000;
var xScale = d3.time.scale()
.domain([startDate, endDate])
.range([0, w]);
console.log(parseDate("20130101"));
console.log("3");
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.ticks(12);
console.log("4S");
//Y SCALE AND AXIS STUFF
//max and min test
var minY = d3.min(dataset2, function (kv) {
return d3.min(kv.results, function (d) {
return +d.revenue;
})
});
var maxY = d3.max(dataset2, function (kv) {
return d3.max(kv.results, function (d) {
return +d.revenue;
})
});
console.log("min y " + minY);
console.log("max y " + maxY);
var yScale = d3.scale.linear()
.domain([minY, maxY])
.range([h, 0]);
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left")
.ticks(10);
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w + margin.left + margin.right)
.attr("height", h + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
console.log("4S1");
//CREATE X-AXIS
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + (h) + ")")
.call(xAxis);
//Create Y axis
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(,0)")
.call(yAxis);
//create circle
var movie_groups = svg.selectAll("g.metric_group")
.data(dataset2).enter()
.append("g")
.attr("class", "metric_group");
var circles = movie_groups.selectAll("circle")
.data(function (d) {
return d.results;
});
svg.selectAll("g.circle")
.data(dataset2)
.enter()
.append("g")
.attr("class", "circle")
.selectAll("circle")
.data(function (d) {
return d.results;
})
.enter()
.append("circle")
.attr("cx", function (d) {
// console.log(d[0]);
console.log(parseDate(d.week));
return xScale(parseDate(d.week));
})
.attr("cy", function (d) {
return yScale(d.revenue);
})
.attr("r", 3);
//create line
//create line
var lineGraph = svg.selectAll("path.line")
.data(dataset2).enter().append("path")
.attr("d", function (d) {
return lineFunction(d.results);
})
.attr("class", "line");
To clarify, lines moving back in time will actually translate to lines moving forward to the right since their date is not changing and the time axis is moving to the right. I hope I am thinking clear here...it is late.
Importantly, your data is not changing between clicks so there will be no new data to be bound in the enter() selection after the initial call. Lines will be drawn once and only once.
Since your axis is the movable part, one quick solution would be to keep removing the lines and rebuilding them again inside startFunction, like this:
var lineGraph = svg.selectAll("path.line").remove();
lineGraph = svg.selectAll("path.line")
.data(dataset2);
I tried this and it works. Note however that I am not removing the lines off of the exit selection and thus not truly leveraging the EUE pattern (Enter/Update/Exit). But, since your data is not really changing, I am not sure what you could add to it between clicks that could be used as the key to selection.data. I hope this helps...
NOTE: I am not particularly fond of this solution and considered writing it as a comment but there is too much verbiage. For one thing, object constancy is lost since we veered away from the EUE pattern and the "graph movement" is ugly. This can be mitigated some by adding a transition delay in the drawing of lines (path) as shown below...but still...
lineGraph.enter().append("path").transition().delay(750)
.attr("d", function (d) {
return lineFunction(d.results);
})

Getting key and value of object

While I'm trying to access the key and value of an object, it's giving undefined. Below is my code
<script type="text/javascript">
var data;
var xAxisName;
var yAxisName;
var jso;
function getX(d) {
return d[xAxisName];
}
function getY(d) {
return d[yAxisName];
}
d3.json("response.json", function (json) {
console.log("hi");
console.log(json); //getting the values
console.log("this " +json.users); //getting the values
xAxisName = json.attribute1.;
console.log("xAxisName=" + xAxisName); //Not getting the values
yAxisName = json.attribute2;
console.log("yAxisName=" + yAxisName); //Not getting the values
data = json.users;
alert(data);
data.map(function(d) { console.log(getX(d));});
data.map(function(i) {console.log(i);});
visualize(data); //then start the visualization
});
function visualize (data) {
var padding = 40;
var margin = {top:30, right: 30, bottom: 30, left:100};
var w = 700 - margin.left - margin.right;
var h = 400 - margin.top - margin.bottom;
//the svg
var svg = d3.select("#container")
.append("svg")
.attr("class", "chart")
.attr("width", w + margin.left + margin.right)
.attr("height", h + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
//the scales
var xScale = d3.scale.ordinal()
.domain(d3.range(data.length))
.rangeRoundBands([0, w], 0.04);
var yScale = d3.scale.linear()
.domain([d3.max(data, getY), 0])
.range([0, h]);
//the axes
var xAxis = d3.svg.axis().scale(xScale).orient("bottom");
var yAxis = d3.svg.axis().scale(yScale).orient("left");
//add the data and bars
svg.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("x", function(d, i) { return xScale(i);})
.attr("y", function(d) { return yScale(getY(d));})
.attr("width", xScale.rangeBand())
.attr("height", function(d) {
return h - yScale(getY(d));})
.attr("class", "bar");
//create axes
svg.append("g").attr("class", "x axis")
.attr("transform", "translate(0," + h + ")").call(xAxis);
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(yAxisName);
}
alert("done");
</script>
It's giving undefined for the xAxisName and yAxisName. In svg.selectAll("rect") y and height giving NaN.
My JSON is
{
"users": [
{
"name": "warchicken",
"score": 30
},
{
"name": "daydreamt",
"score": 100
},
{
"name": "Anas2001",
"score": 30
},
{
"name": "ocjojo",
"score": 30
},
{
"name": "joklawitter",
"score": 30
}
]
}
It looks likes you want to extract property names from the user objects. To do that, you can either use Object.keys() or iterate over the object with for...in (related question: How do I enumerate the properties of a JavaScript object?).
var keys = Object.keys(json.users[0]);
xAxisName = keys[0];
yAxisName = keys[1];
Beware though that object properties are not ordered. You might end up with xAxisName being "score" and vice versa.
If you need xAxisName to be a certain value, you either have to hardcode it, or add the information to the JSON you return from the server. For example:
{
"axes": ["name", "score"],
"users": [...]
}
Then you get it with
xAxisName = json.axes[0];
// ...
Side note: Choosing json as variables name for an object is not optimal because it suggests that the variables holds a string containing JSON, while it actually holds an object. How about chartData instead?

Categories

Resources