Display only values from the data set into X axis ticks - javascript
I'm having trouble on this.. I'm working on a line chart using d3.js. I'm having trouble to display the X axis tick text that uses date values ONLY in the data spreadsheet. When I develop the line chart, I saw it automatic generated the date values between the values from the Data spreadsheet. Here is a quick example of the data.
date,close
16-Dec-12,53.98
16-Dec-12,67.00
16-Dec-12,89.70
16-Dec-12,99.00
16-Dec-12,130.28
23-Dec-12,166.70
23-Dec-12,234.98
23-Dec-12,345.44
23-Dec-12,443.34
23-Dec-12,543.70
23-Dec-12,580.13
30-Dec-12,605.23
30-Dec-12,622.77
30-Dec-12,626.20
30-Dec-12,628.44
30-Dec-12,636.23
30-Dec-12,633.68
So in this dataset, it has 3 different date values.
In the D3 line chart, I want to display those only the 3 different date values which are Weeks in the x axis tick text. However, the chart is generated other dates in between those data date values. Example below.
I'm trying to display like this that only display the date values from the Dataset.
I hope this makes sense. Is this possible? I tried to use .tick() but it only display '16-Dec-12' which it confuses me. I'm pretty new into line charts for d3.js =/
here is my snippet code. I hope this helps.
function getExtent(member) {
var extents = [];
dataset.forEach(function(arry){
extents = extents.concat(d3.extent(arry,function(d){return d[member]}));
});
return d3.extent(extents);
}
var xScale = d3.time.scale().domain(getExtent('x')).range([0,width]);
var yScale = d3.scale.linear().domain(getExtent('y')).range([height,0]);
var xAxis = d3.svg.axis()
.scale(xScale)
.orient('bottom');
xAxis.scale(xScale)
.tickFormat(d3.time.format('%b %d'));
var yAxis = d3.svg.axis()
.scale(yScale)
.orient('left');
var lineFunc = d3.svg.line()
.x(function(d){return xScale(d.x)})
.y(function(d){return yScale(d.y)})
.interpolate('linear');
var g = svg.append('g')
.attr('width',width)
.attr('height',height)
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
// Use this group for drawing the lines
g.append('g')
.attr('class', 'line-group');
// Axes
g.append('g')
.attr('class', 'usps-multiline axis axis--x')
.attr('transform', 'translate(0,' + height + ')')
.call(xAxis)
.selectAll("text")
.attr("transform", "translate(-40,20) rotate(315)");
g.append('g')
.attr('class', 'usps-multiline axis axis--y')
.call(yAxis);
**Please let me know if you can view the sample pic.
This is the expected behaviour for a time scale. In D3, the axis is automatically generated, you don't have much control on the ticks.
The easiest alternative seems to be passing an array of the dates you have in your data to tickValues:
var axis = d3.axisBottom(scale)
.tickValues(uniqueValues);
Here, uniqueValues is an array with the dates you have in your CSV, filtered to only unique dates (otherwise you'll have several ticks in the same position).
here is the demo with the CSV you shared:
var svg = d3.select("svg");
var csv = `date,close
16-Dec-12,53.98
16-Dec-12,67.00
16-Dec-12,89.70
16-Dec-12,99.00
16-Dec-12,130.28
23-Dec-12,166.70
23-Dec-12,234.98
23-Dec-12,345.44
23-Dec-12,443.34
23-Dec-12,543.70
23-Dec-12,580.13
30-Dec-12,605.23
30-Dec-12,622.77
30-Dec-12,626.20
30-Dec-12,628.44
30-Dec-12,636.23
30-Dec-12,633.68`;
var data = d3.csvParse(csv, function(d) {
d.date = d3.timeParse("%d-%b-%y")(d.date);
return d
});
var uniqueValues = [...new Set(data.map(function(d) {
return d.date.getTime()
}))].map(function(d) {
return new Date(d);
});
var scale = d3.scaleTime()
.range([30, 570])
.domain(d3.extent(data, function(d) {
return d.date
}));
var axis = d3.axisBottom(scale)
.tickValues(uniqueValues);
var gX = svg.append("g")
.attr("transform", "translate(0,50)")
.call(axis);
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="600" height="100"></svg>
PS: I'm using D3 v4 in the demo, but the principle is the same.
If .ticks(3) doesn't work, you can pass a custom function into .ticks to ensure you get the ticks you want.
Here is a fairly comprehensive axis tutorial.
Related
d3.js Time Scale Axis
I have a scatter plot created using d3.js that I am trying to add an x axis to which will range from 12AM to 12AM i.e. spanning 24 hours. The nature of the graph is that the data will change depending on user input however I keep receiving the same error for however I try to append the axis. I receive the error: d3.v4.min.js:2 Error: attribute transform: Expected number, "translate(NaN,0)". Here is my code in which I have tried to include only that which is important for my query. (timeLog is my array of data) var parseTime = d3.utcParse("%H:%M"); var midnight = parseTime("00:00"); var height = $("#scatter").height(); var width = $("#scatter").width(); var max = Math.max(timeLog); var min = Math.min(timeLog); var yScale = d3.scale.linear() .domain([0,d3.max(timeLog)]) .range([0,height]); var xScale = d3.scaleBand() .domain(d3.range(0,timeLog.length)) .range([0,width]); d3.select("#scatter").append('svg') .attr('width',width) .attr('height',height) .style('background', '#f4f4f4') .selectAll('circle') .data(timeLog) .enter().append('circle') .style('fill', 'black') .style('stroke','none') .attr('cx',function(d, i){ return xScale(i); }) .attr('cy',function(d){ return height - yScale(d); }) .attr('r',2); var hScale = d3.scaleUtc() .domain([midnight,d3.time.day.offset(midnight,1)]) .range([0,width]); var xAxis = d3.axisBottom() .scale(hScale) .tickFormat(d3.time.format.utc("%I %p")) var xGuide = d3.select('svg') .append('g') .attr("class", "axis axis--x") xAxis(xGuide) xGuide.attr('transform','translate(0,' + height + ')') xGuide.selectAll('path') .style('fill','black') .style('stroke','black') xGuide.selectAll('line') .style('stroke','black') I am fairly new to d3.js and am attempting to teach myself the basics so any feedback that would help me identify the cause of the error would be greatly appreciated. Edit I have made some changes after discovering some of my syntax was outdated and updated the above code, I am now not receiving any error messages however the axis is still not displaying. Is there perhaps some attribute I am missing? Thanks for any feedback.
can't get .exit() and .enter() to work properly when updating data in d3.js
I'm currently trying to make a simple series of bar charts from a CSV file. The CSV file is divided into columns, each of which represents a question on a survey. Each row represents a survey respondent, and each cell is a response for a particular question from a particular. The idea is to have a two arrow buttons at the top that allow me to change the chart so that it represents the next question in the survey. I can get any given chart to load when I first load the page, but then when I try to use the buttons, I get very strange behaviors with some bars not disappearing, and some bars doubling up with other bars. So for example, it might start off like this: And after a few presses of the button end up like this with both axes and bars doubled up: I suspect that I'm assigning keys badly so that d3 doesn't know what it needs to remove, but I can't figure out much beyond that. Here is the code: <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <script src="http://d3js.org/d3.v3.min.js"></script> <style> </style> <script type="text/javascript"> function draw(data) { "use strict"; // Sets up initial question var current_question = [1]; /*sets up the canvas for the visualization*/ var margin = 75, width = 1400 - margin, height = 700 - margin; /*Adds title*/ d3.select("body") .append("h2") .text("Environmental Attitudes"); //Adds space for updated question text later var question_text = d3.select("body") .append('div') .attr('class','question_text'); /*Adds buttons for question selection*/ var buttons =d3.select("body") .append('div') .attr('class','question_selection_buttons'); // y scale var y = d3.scale.linear() .range([height,0]); // x scale var x = d3.scale.ordinal() .rangeRoundBands([0, width], .1); //x axis var xAxis = d3.svg.axis() .scale(x) .orient("bottom"); //y axis var yAxis = d3.svg.axis() .scale(y) .orient("left"); /*Adds the SVG element that will house everything else*/ var svg = d3.select("body") .append("svg") .attr("id", "svg_main") .attr("width", width + margin) .attr("height", height + margin); // list of variables var questions = d3.keys(data[0]).filter(function(d){ var x = "cluster"; if (d.indexOf(x) === -1 && d !== 'ids') { return d ; } }); var num_questions = questions.length; function update_chart(question) { /* d3.selectAll("svg > *") .remove(); */ // filters out NaN observations from data from the question "question" var new_data = data.filter(function(d){ if(isNaN(+d[question])){ return false; } return true; }); // rolls up data by answer in "question" var nested = d3.nest() .key(function(d){return d[question]; }) .rollup(function(leaves){ var total = data.length var responses = leaves.length; return { 'responses' : responses, 'percent' : responses/total }; }) .entries(new_data) var elem = svg.selectAll('rect') .data(nested) .exit() .remove(); //sets the domain of x by passing it the range of possible values x.domain(nested.map(function (d) { return d.key; })); //sets the domain of y by passing it the range of possible values y.domain([0, d3.max(nested, function (d) { return +d.values['percent'].toPrecision(3); })]); //draws x axis svg.append("g") .attr('class','x axis') .attr('transform', 'translate(0,' + height + ')') .call(xAxis); //draws y axis svg.append('g') .attr("class", 'y axis') .call(yAxis); svg.selectAll('g') .data(nested) .enter().append('g') .attr('class','response') .attr('transform', function(d){ return "translate (" + x(d.key) + ",0)"; }); svg.selectAll('rect') .data(nested) .enter().append("rect") .attr("width", x.rangeBand()) .attr('height', function(d){ return height-y(+d.values['percent'].toPrecision(3)); }) .attr("y", function(d){return y(+d.values['percent'].toPrecision(3))}) .attr('transform', function(d){ return "translate (" + x(d.key) + ",0)"; }); }; var left = buttons.append('div') .selectAll('div') .data(current_question) .enter() .append('div') .attr("class", "button") .text("<") .attr("id", "left-button"); var right = buttons.append('div') .selectAll('div') .data(current_question) .enter() .insert('div') .attr("class", "button") .text(">") .attr("id", "right-button"); left.on("click", function(d) { current_question-- if (current_question < 0) { current_question = num_questions-1; }; update_chart(questions[current_question]) }); right.on("click", function(d) { current_question++ if (current_question >= num_questions) { current_question = 0 } update_chart(questions[current_question]); }); update_chart(questions[current_question[0]]); } </script> </head> </body> <script type="text/javascript"> d3.csv("clusterData.csv", draw) </script> </body> </html> Just one more note. I have gotten the chart to refresh properly by clearing all the contents of the svg element on every update. That code is commented out in the code above. I'd like instead to do this by using .exit().remove(). Here is a sample of the data Here's a smaller snippet of data in CSV format: ids,ldcgrn,othssame,chemgen,natchld,natpark,sex,nukegen,harmgood,topprob1,drivless,grnmoney,topprob2,peopgrn,natsci,grngroup,redcehme,grntaxes,grncon,govdook,knowsol,chemfree,ihlpgrn,tempgen1,recycle,grndemo,impgrn,scigrn,h2oless,carsgen,indusgen,grnprice,grwthelp,nobuygrn,grneffme,helpharm,grnprog,toodifme,grnsign,harmsgrn,grnexagg,popgrwth,watergen,natenrgy,busgrn,natsoc,privent,grnsol,natroad,age,usdoenuf,grwtharm,econgrn,race,polgreed,polviews,grnintl,knwcause,grnecon,2_clusters,3_clusters,4_clusters,5_clusters,6_clusters,7_clusters,8_clusters,9_clusters, 1269,2,3,3,2,2,Female,1,4,Poverty,NaN,0,Health care,More information and education for people about the advantages of protecting the environment,2,0,1,4,3,4,3,1,4,3,NaN,0,3,3,1,3,2,3,3,1,NaN,4,4,4,0,4,3,4,3,2,More information and education for businesses about the advantages of protecting the environment,2,NaN,5,2,71,2,3,3,Black,4,6,3,3,3,1,0,1,0,5,6,2,0, 1403,4,2,3,1,1,Male,3,2,Health care,2,0,The environment,Heavy fines for people who damage the environment,2,0,2,3,5,3,4,2,4,3,2,0,4,4,2,1,1,2,4,4,2,3,4,5,0,4,4,4,1,2,NaN,2,4,3,2,18,2,2,4,Black,2,4,5,5,2,1,1,2,3,0,0,3,7, 1868,4,4,2,1,1,Male,1,2,The economy,NaN,0,Health care,More information and education for people about the advantages of protecting the environment,2,0,2,2,5,NaN,1,1,2,3,3,0,4,4,2,1,1,2,4,2,4,4,4,4,0,4,4,4,2,3,Heavy fines for businesses that damage the environment,2,5,4,2,51,1,2,4,White,4,4,4,1,3,1,1,1,3,0,0,3,7, 1296,2,2,2,1,1,Female,NaN,4,Health care,2,0,Immigration,More information and education for people about the advantages of protecting the environment,NaN,0,2,4,5,1,3,3,5,3,4,0,3,3,2,4,3,2,3,3,4,4,NaN,2,0,3,3,4,1,1,Heavy fines for businesses that damage the environment,1,3,2,NaN,41,1,3,NaN,White,4,4,4,3,2,0,1,2,2,3,2,7,4, 1256,2,4,2,1,2,Male,5,2,The economy,NaN,0,Poverty,More information and education for people about the advantages of protecting the environment,2,0,3,5,5,4,2,3,4,4,2,0,4,3,3,2,3,4,5,3,4,4,5,1,0,5,3,3,3,1,More information and education for businesses about the advantages of protecting the environment,1,3,5,1,51,2,2,4,Black,2,5,4,3,5,1,0,1,3,1,3,4,2, 1943,2,4,3,1,2,Female,2,1,Immigration,2,0,The economy,More information and education for people about the advantages of protecting the environment,1,0,2,5,5,2,2,2,4,3,3,0,3,2,3,1,1,2,4,2,5,4,2,4,0,4,2,4,1,1,More information and education for businesses about the advantages of protecting the environment,1,2,2,2,52,1,3,3,White,4,7,4,2,1,0,1,2,2,3,2,7,4, 1940,2,2,3,2,2,Male,3,2,Terrorism,3,0,Poverty,Use the tax system to reward people who protect the environment,1,0,3,3,4,2,2,3,4,1,4,0,2,2,2,2,2,3,4,2,2,4,4,4,0,4,2,4,1,1,Use the tax system to reward businesses that protect the environment,1,2,5,2,47,2,2,4,White,3,4,4,2,2,0,1,2,2,3,3,7,2, 1941,2,4,4,1,2,Male,4,2,Health care,1,0,Crime,More information and education for people about the advantages of protecting the environment,2,0,2,5,1,4,2,3,4,1,3,0,4,2,2,1,2,4,4,2,2,2,2,4,0,2,4,4,1,2,Heavy fines for businesses that damage the environment,2,2,5,2,75,1,2,4,White,2,3,4,2,4,1,0,1,0,5,6,2,2,
Couple problems: 1.) You are re-appending your axis on every update. Append a blank one then update it each time. 2.) Your rects aren't following enter, update, exit pattern at all. See below for how I handle the enter, update and exit all independently of each other. // set x domain to new data x.domain(nested.map(function (d) { return d.key; })); // set y domain to new data y.domain([0, d3.max(nested, function (d) { return +d.values['percent'].toPrecision(3); })]); // select your rects and bind your data // note how I'm giving it a "key" function to guarantee the join is computed properly (https://github.com/mbostock/d3/wiki/Selections#data) var rects = svg.selectAll('rect') .data(nested, function(d){ return d.key; }); // handle those elements entering rects .enter().append("rect"); // handle the update rects .attr("width", x.rangeBand()) .attr('height', function(d){ return height-y(+d.values['percent'].toPrecision(3)); }) .attr("y", function(d){return y(+d.values['percent'].toPrecision(3))}) .attr('x', function(d){ return x(d.key); }); // handle the exit rects.exit().remove(); // just update an already existing axis svg.select('g.x.axis') .call(xAxis); svg.select('g.y.axis') .call(yAxis); Here's a working example where I've cleaned up your code a bit.
D3 line chart axis text labels in multi line
I have a line chart built in d3.js. I needed some help with some customisation. I am looking to split x-axis text labels in two lines. I want the date in one line and the month in another. The present chart has "14 Dec" in one line. The present chart: The x-axis labels are split into 2 lines here. Date and month in 2 different lines. Expected x-axis: Codepen link var xScale = d3.time.scale().domain([data[0][xkeyVal], data[data.length - 1][xkeyVal]]).range([margin.left, width]); var yScale = d3.scale.linear().domain([0, d3.max(data, function(d) { return d[ykeyVal]; })]).range([height, margin.left]); var xAxisGen = d3.svg.axis() .scale(xScale) .orient("bottom") .ticks(_config.keys.xAxis.ticks) .tickFormat(d3.time.format("%d %b")) .tickSize(0); var yAxisGen = d3.svg.axis() .scale(yScale) .orient("left") .tickValues(_config.keys.yAxis.tickValues.length > 0 ? _config.keys.yAxis.tickValues : 1) .tickSize(0);
I'd do it after generating the axis: svg.append("svg:g") .attr("class", "x axis") .attr("transform", "translate(0," +height + ")") .call(_config.xAxisGen) .selectAll('.x .tick text') // select all the x tick texts .call(function(t){ t.each(function(d){ // for each one var self = d3.select(this); var s = self.text().split(' '); // get the text and split it self.text(''); // clear it out self.append("tspan") // insert two tspans .attr("x", 0) .attr("dy",".8em") .text(s[0]); self.append("tspan") .attr("x", 0) .attr("dy",".8em") .text(s[1]); }) }); Updated example.
D3: tickformat method to only show year
My dates for my d3 chart are month/day/year, for example: "10/1/2013" And I'm parsing them this way: d3.time.format("%x").parse; But how do I write a .tickFormat() method on my axis to only show the year (full year, with century)?
Just change %x to %Y in your parsing snippet. You can find the full documentation at https://github.com/mbostock/d3/wiki/Time-Formatting So something like this: tickFormat: function(d) { var dx = data[0].values[d]; return dx ? d3.time.format('%Y')(new Date(dx)) : ''; } Of course your specifics of where to get your data etc will be different.
When defining your x axis you can modify it with tickformat and pass in a function to return the year. Here is a full example. var width = 200, height = 200; var data = ["10/1/2013", "10/1/2014"].map(d3.time.format("%x").parse) var xDomain = [Math.min.apply(null, data), Math.max.apply(null, data)]; var x = d3.time.scale().range([0, width]).domain(xDomain) var xAxis = d3.svg.axis() .scale(x).tickFormat(function(time, index) { return time.getUTCFullYear()); }) var svg = d3.select("body").append("svg") .attr('width', 200) .attr('height', 200); svg.append("g") .attr("class", "x axis") .attr("transform", "translate(0," + 180 + ")") .call(xAxis); The key line to note would be the place where we specify the tickformat of the xAxis: var xAxis = d3.svg.axis() .scale(x).tickFormat(function(time, index) { return time.getUTCFullYear()); }) Note that we specify the domain of var x using the parsed times in xDomain.
How to use tickFormat to only show the year (as century and last two digits)
Here is my code including, JSON example and how I'm parsing my dates. Right now the dates are showing up like: 10/1/2013 for example How can I write a tickFormat function for the x axis that returns the full year (with century) for the first year specified in the tickValues function and '14 (for 2014 for example) for all the other years? JSON structure: var shortData5= [ {"date":"10/1/2013","shortFig":12}, {"date":"11/1/2013","shortFig":34}, {"date":"12/1/2013","shortFig":-25}] //fyi- var shortData5 is passed in my chart building function as "thedata" var parseDate = d3.time.format("%x").parse; thedata.forEach(function(d) { parseDate(d.date); }); //Set up the X scales //for bars var xScaleOrdinal = d3.scale.ordinal() .rangeRoundBands([0, width], .1) .domain(thedata.map(function(d) { return d.date; })); //With the X scales, set up the X axis var xAxis= d3.svg.axis() .orient("bottom") .tickFormat() //need function here; if(theDiv=="#contructionSingleMulti"|| theDiv=="#volumeExistingLong"|| theDiv=="#volumeNewLong"){ xAxis.scale(xScaleOrdinal) .tickValues([thedata[4].date,thedata[16].date,thedata[28].date,thedata[40].date,thedata[52].date,thedata[64].date,thedata[76].date,thedata[88].date,thedata[100].date,thedata[112].date]); }else{ xAxis.scale(xScaleOrdinal) .tickValues([thedata[0].date,thedata[3].date]); } //Call the X axis baseGroup.append("g") .attr("class", "xaxis") .attr("transform", "translate(0," + height + ")") .call(xAxis);
I'm not sure how you want to format your tick values from way that you posed your question. Perhaps an example would be more helpful. You can create a custom tick formatting function to pass to the scale's .tickFormat accessor method. You could do something like this : scale.tickFormat(myCustomTickFormatter); function myCustomTickFormatter(datum, index) { return /** some manipulation of datum **/ } See also http://bl.ocks.org/mbostock/4149176 for a more complex example of defining your own custom tick formatter.