d3.js x-coord displaying incorrectly iOS - javascript

Using d3.js the x coordinates of the graph are displaying at x=0 or on the y axis. The x axis represents a date and time and the y axis is the temperature. But this is only on an ipad or iphone. On my own machine, Linux, it displays correctly.
The graphs and all file can be seen at,
http://shanespi.no-ip.biz
The ipad/iphone display
While the correct graph is,
Here is the javascript,
var xScale = d3.scaleTime()
.domain([new Date(datahourly[0].date), d3.max(datahourly, function(d) {return new Date(d.date)})])
.range([0, (w-2*padding)]); // max x screen space is width - twice padding
var yScale = d3.scaleLinear()
.domain([0, d3.max(datahourly, function(d) {return d.temp})])
.range([(h-2*padding), 0]); // max y screen space is height - twice padding
var xAxis = d3.svg.axis(xScale) // d3 v.4
.ticks(9) // specify the number of ticks
/*.ticks(d3.time.days, 1) */
.tickFormat(d3.time.format('%H:00'))
.scale(xScale)
.orient("bottom");
var yAxis = d3.svg.axis(yScale)
.ticks(7)
.scale(yScale)
.orient("left");
var svg = d3.select('#hourly-readings')
.append('svg') // create an <svg> element
.attr('id', 'svgDaily')
.attr('width', w) // set its dimensions
.attr('height', h);
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(" + (2*padding - 15) + "," + (padding - 15) + ")")
.call(yAxis);
svg.append('g') // create a <g> element
.attr('class', 'axis') // specify classes
.attr("transform", "translate(" + (2*padding - 15) + "," + (h - padding - 15) + ")")
.call(xAxis); // let the axis do its thing
var lineFunctionStart = d3.svg.line()
.x(function(d) {return xScale(new Date(d.date)); })
.y(h - 2*padding - 5)
.interpolate("cardinal");
var lineFunction = d3.svg.line()
.x(function(d) {return xScale(new Date(d.date)); })
.y(function(d) {return yScale(d.temp); })
.interpolate("cardinal");
svg.append("path")
.attr('d', lineFunctionStart(datahourly))
.attr('stroke', "grey")
.attr('stroke-width', 1)
.style('fill', "white")
.attr("transform","translate(" + (2*padding - 13) + "," + (padding - 10) + ")")
.transition()
.duration(3000)
.attr('d', lineFunction(datahourly));
//var svg = d3.select('svg');
var svg = d3.select('#svgDaily');
svg.append("text") // text label for the x axis
.attr("x", 310)
.attr("y", h)
.style("font-size", "12")
.style("text-anchor", "middle")
.text("Time (1 hr. intervals)");
svg.append("text") // text label for the x axis
.attr('transform', 'rotate(-90)')
.attr("x", -85) // Because rotate is first x and y coordinates are transaposed
.attr("y", padding-17)
.style("font-size","10")
.style("text-anchor", "middle")
.text("Temp. Celcius");
var rects = svg.selectAll('circle')
.data(datahourly);
var newRects = rects.enter();
newRects.append('circle')
.attr('cx', function(d, i) { return (Math.random() * (w - 2*padding)) })
//.attr('cx', function(d, i) {
//return (5 + xScale(new Date(d.date)));
//})
.attr('cy', (h - (2*padding)))
.attr('r', 5)
.style('fill', "lightblue")
.attr("transform","translate(" + (2*padding - 18) + "," + (padding - 20) + ")")
.transition()
.duration(3000)
.delay(function(d, i) {return i * 300})
.attr('cx', function(d, i) {
return (5 + xScale(new Date(d.date)));
})
.attr('cy', function(d, i) {
return 10 + yScale(d.temp);
});
Here is the 'datahourly' data,
[
{
"date":"2016-12-14 22:01:01.799830",
"temp":"24.04"
},
{
"date":"2016-12-15 00:01:02.362875",
"temp":"23.03"
},
......................
{
"date":"2016-12-15 21:01:01.868593",
"temp":"21.93"
},
{
"date":"2016-12-15 22:01:02.278817",
"temp":"15.9"
},
{
"date":"2016-12-15 23:01:01.963714",
"temp":"21.63"
}
]
I am using Chrome on Linux and Safari on the ipad and iphone. But I did install chrome on the iphone and the graph is still incorrect.
Are there svg issues with iOS?
EDIT:
The main issue was that the time data was not parsed correctly,
This is the correct solution,
var data = [];
$.getJSON("data/data.json",
function(info){
data = info[0].fiveMinReadings;
//console.log(data);
var parseTime = d3.timeParse("%Y-%m-%d %H:%M:%S.%L");
data.forEach(function(d) {
d.date = d.date.slice(0,-3);// remove microseconds
d.date = parseTime(d.date);
d.temp = +d.temp;
});
// Beginning of graph for 5 minute readings
var padding = 25;
var w = 600;
var h = 300;
var xScale = d3.scaleTime()
.domain(d3.extent(data, function(d) { return d.date; }))
.range([0, (w-2*padding)]); // max x screen space is width - twice padding
var yScale = d3.scaleLinear()
.domain([0,d3.max(data, function(d) {return d.temp})])
.range([(h-2*padding), 0]); // max y screen space is height - twice padding
var xAxis = d3.axisBottom(xScale) // d3 v.4
.tickFormat(d3.timeFormat('%H:%M '))
.scale(xScale);
var yAxis = d3.axisLeft(yScale)
.scale(yScale);
var svg = d3.select('#five-min-readings')
.append('svg') // create an <svg> element
.attr('id','svgHourly')
.attr("align","center")
.attr('width', w) // set its dimensions
.attr('height', h);
var valueline = d3.line()
.curve(d3.curveCardinal)
.x(function(d) { return xScale(d.date); })
.y(h - 2*padding - 4);
var valueline2 = d3.line()
.curve(d3.curveCardinal)
.x(function(d) { return xScale(d.date); })
.y(function(d) {return yScale(d.temp); });
svg.append("text") // text label for the x axis
.attr("x", 310)
.attr("y", h)
.style("font-size", "12")
.style("text-anchor", "middle")
.text("Time (5 min. intervals)");
svg.append("text") // text label for the x axis
.attr('transform', 'rotate(-90)')
.attr("x", -85) // Because rotate is first, x and y coordinates are transaposed
.attr("y", padding-17)
.style("font-size","10")
.style("text-anchor", "middle")
.text("Temp. Celcius");
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(" + (2*padding-15) + "," + (padding-15) + ")")
.call(yAxis);
svg.append('g') // create a <g> element
.attr('class', 'axis') // specify class
.attr("transform", "translate(" + (2*padding-15) + "," + (h - padding - 15) + ")")
.call(xAxis); // let the axis do its thing
svg.append('path')
.data([data])
.attr("class","line")
.attr('d', valueline)
.attr('stroke', "grey")
.attr('stroke-width', 1)
.style('fill', "white")
.attr("transform","translate(" + (2*padding - 13) + "," + (padding -10) + ")")
.transition()
.duration(3000)
.attr('d', valueline2 );
var svg = d3.select('#svgHourly');
var rects = svg.selectAll('circle')
.data(data);
var newRects = rects.enter();
newRects.append('circle')
.attr('cx', function(d, i) { return (Math.random() * (w - 2*padding)) })
.attr('cy', h - 2*padding)
.attr('r', 5)
.attr("id", function(d,i){return "circle" + i})
.style('fill', "lightblue")
.attr("transform","translate(" + (2*padding - 18) + "," + (padding - 20) + ")")
.transition()
.duration(3000)
.delay(function(d, i) {return i * 300})
.attr('cx', function(d, i) { return (5 + xScale(d.date)); })
.attr('cy', function(d, i) { return 10 + yScale(d.temp); });
}); // closes getJSON()

You can clearly see that your circles are getting the correct y ("cy") value, the error lies in the x ("cx") value.
The problem seems to be the use of new Date() in Safari with this pattern: yyyy-MM-dd.
In your code, given your data structure, you'll end up having something like this in the line generator:
.x(function(d) {
return xScale(new Date("2016-12-15 23:01:01.963714"));
})//the first date in your data ---^
And the same for your circles:
.attr('cx', function(d, i) {
return (5 + xScale(new Date("2016-12-15 23:01:01.963714")));
})
Apparently, this is supported by Chrome and Firefox, but not by Safari. Curiously, the pattern (yyyy-MM-dd) is included in the ECMA standard, so this is probably a Safari specific issue.
According to this answer, it will work if you include a T (I didn't test it):
.x(function(d) {
return xScale(new Date("2016-12-15T23:01:01.963714"));
})
Alternatively, remove the new Date() and parse the dates using D3 (d3.timeParse() in d3 v4.x and format.parse() in d3 v3.x).
EDIT: Summarising, you have two possible solutions:
Solution 1: Remove all new Date functions (both on the line generator and in all scales that use it) and parse the date using d3. You said that you're using d3 v3.x, but your code is using d3 v4.x instead. Nevertheless, here is how to do it using d3 v3.x:
var date = "2016-12-14 22:01:01.799830".slice(0,-3);
var format = d3.time.format("%Y-%m-%d %H:%M:%S.%L");
var myDate = format.parse(date);
console.log(myDate);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Note: I'm removing the last 3 digits because d3 can parse onli miliseconds, not microseconds.
Solution 2: Keep your new Date functions, but add a T as already discussed above:
var date = "2016-12-14 22:01:01.799830".replace(/\s/, 'T');
console.log(date);
console.log(new Date(date));

Seems there's a issue with compatibility between dc.js 1.7.5 the stable version and d3.js v4. dc.js compatibility with v4 of d3.js and my new post on this https://stackoverflow.com/questions/41389307/dc-js-1-7-5-not-compatible-with-d3-js-v4

Wow, this method works commonly in both android and iOS, saved my hours of browsing about this issue
var commonDate = item.created_at.replace(/\s/, "T");
var date = new Date(commonDate);
Long story short: I have used d3 in the ionic angular version and had the same issue in iOS alone

Related

How to zoom in a graph in JavaScript D3

I am trying to get Zoom to work, I am new to D3 and I find it very abstract and not intuitive. I recently finished a beginners course in JavaScript but D3 feels like a completely new language.
I found this topic which might help a bit.
D3 Zooming in graph
I also found the following code that created the graph on the web, the simplest I could find and I don't understand all of it. Now I wanna zoom in and used code that I also found on the web but which has to be adapted. I understood that much that the zoom variable at the top is calling a function called NeuerChart which has the actual zooming behaviour in it. It needs to zoom the graph and the axes when I spin the mousewheel.
In the end I need to implement this into a real problem, thanks. Using D3.v5.
<script>
let zoom = d3.zoom()
.scaleExtent([0.5, 10])
.extent([[0, 0], [width, height]])
.on('zoom', NeuerChart);
// Step 1
let min = 0;
let max = 100;
let x_arr = [];
let y_arr = [];
let s_arr = [];
let z_arr = [];
for (let i = 0; i < 360; i++) {
var r = Math.round(Math.random() * (max - min)) + min;
x_arr[i]= i;
y_arr[i]= r;
z_arr.push([x_arr[i],y_arr[i]]);
}
s_arr = y_arr.sort(function(a, b){return a - b});
let neu_arr = [];
let zz_arr = [];
for (let i = 0; i < 360; i++) {
neu_arr[i]= i;
zz_arr.push([neu_arr[i], s_arr[i]]);
}
console.log(z_arr);
console.log(zz_arr);
var dataset1 = zz_arr;
// Step 3
var svg = d3.select("svg"),
margin = 200,
width = svg.attr("width") - margin, //1700
height = svg.attr("height") - margin //700
// Step 4
var xScale = d3.scaleLinear().domain([0 , 365]).range([0, width]),
yScale = d3.scaleLinear().domain([0, 105]).range([height, 0]);
var g = svg.append("g")
.attr("transform", "translate(" + 100 + "," + 100 + ")");
// Step 5
// Title
svg.append('text')
.attr('x', width/2 + 100)
.attr('y', 100)
.attr('text-anchor', 'middle')
.style('font-family', 'Helvetica')
.style('font-size', 20)
.text('Line Chart');
// X label
svg.append('text')
.attr('x', width/2 + 100)
.attr('y', height - 15 + 150)
.attr('text-anchor', 'middle')
.style('font-family', 'Helvetica')
.style('font-size', 12)
.text('Zeitachse');
// Y label
svg.append('text')
.attr('text-anchor', 'middle')
.attr('transform', 'translate(60,' + 500 + ')rotate(-90)')
.style('font-family', 'Helvetica')
.style('font-size', 12)
.text('Wert');
// Step 6
g.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(xScale).ticks(7).tickValues([0, 60, 120, 180, 240, 300, 360]));
g.append("g")
.call(d3.axisLeft(yScale));
// Step 7
svg.append('g')
.selectAll("dot")
.data(dataset1)
.enter()
.append("circle")
.attr("cx", function (d) { return xScale(d[0]); } )
.attr("cy", function (d) { return yScale(d[1]); } )
.attr("r", 3)
.attr("transform", "translate(" + 100 + "," + 100 + ")")
.style("fill", "#CC0000");
// Step 8
var line = d3.line()
.x(function(d) { return xScale(d[0]); })
.y(function(d) { return yScale(d[1]); })
.curve(d3.curveMonotoneX)
svg.append("path")
.datum(dataset1)
.attr("class", "line")
.attr("transform", "translate(" + 100 + "," + 100 + ")")
.attr("d", line)
.style("fill", "none")
.style("stroke", "#CC0000")
.style("stroke-width", "2")
svg.append("rect")
.attr("width", width)
.attr("height", height)
.style("fill", "none")
.style("pointer-events", "all")
.attr("transform", "translate(" + 100 + "," + 100 + ")")
.call(zoom);
function NeuerChart () {
// recover the new scale
var newX = d3.event.transform.rescaleX(xScale);
var newY = d3.event.transform.rescaleY(yScale);
// update axes with these new boundaries
xAxis.call(d3.axisBottom(newX))
yAxis.call(d3.axisLeft(newY))
}
</script>
I added the code here in Codepen:
https://codepen.io/Dvdscot/pen/zYjpzVP
This is how it should work:
https://codepen.io/Dvdscot/pen/BaxJdKN
Problem is solved, see the code at Codepen:
`
Reset zoom
`https://codepen.io/Dvdscot/pen/zYjpzVP

D3 chart: While using d3.axisLeft(y) method, how do I prevent Y-Axis grid-lines from using the entire chart width

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);
});
}

d3js updates only once

I have a visualization task that I need to make it done with d3.js. Here's my code.
var w = 700;
var h = 500;
var offset = 100;
var padding = 20;
var colors = d3.scale.category10();
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
var texts = function(ds,ds2){
var stack = d3.layout.stack();
stack_data = stack(ds);
var xScale = d3.scale.ordinal()
.domain(d3.range(ds[0].length))
.rangeRoundBands([0, w-offset], 0.50);
var yScale = d3.scale.linear()
.domain([0,
d3.max(stack_data, function(d) {
return d3.max(d, function(d) {
return d.y0 + d.y -20;
});
})
])
.range([padding, h-50]);
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.ticks(ds[0].length);
gs = svg.selectAll("g").data(stack_data);
for (var i5 = 0; i5 < ds.length; i5++) {
gs.enter()
.append("g")
.attr("class", "stacked_bars")
.attr("fill", function(d, i) {
return colors(i);
});
asd = gs.selectAll("rect").data(function(d) { return d; });
asd.enter().append("rect");
asd.transition()
.duration(1000)
.attr("x", function(d, i) {
return xScale(i);
})
.attr("y", function(d) {
return h - yScale(d.y0) - yScale(d.y);
})
.attr("height", function(d) {
return yScale(d.y);
})
.attr("width", xScale.rangeBand())
.attr("class", "rectbar");
};
gs.append("g") // add a group element to the svg
.attr("class", "axis") //Assign class "axis" to group
.attr("transform", "translate(0," + (h - padding) + ")") // shift the axis to bottom
.call(xAxis); // call the axis function
gs.exit().remove();
}
res = dataGenerator("Europe");
dataset = res[0];
dataset2 = res[1];
texts(dataset,dataset2);
d3.select("#selector").on("change", function() {
cont = d3.select(this).property('value');
res = dataGenerator(cont)
dataset = res[0]
dataset2 = res[1]
//svg.selectAll(".sym").remove()
texts(dataset,dataset2);
});
It basically gets the data and generates stacked bars. When user uses the select element on the page, it updates the data and generates new results. It successfully gets the first results and when user selects another option, it makes it happen also. But when user tries to use select part once again. It only generates bars for dataset's first item.
So, in this particular case I have countries and their data as numbers, at first load and first update it successfully shows all but when it comes to second update, it only generate bars for first country in dataset. It's been hours that I'm trying to fix this. I know I only have a little mistake but couldn't make it to solve.
Also here's the jsfiddle of the code: https://jsfiddle.net/510ep9ux/4/
Since I'm new at d3.js, I may not understand the update concept well.
So, any guesses?
Solved, using two separate functions textsInit and textsUpdate :
https://jsfiddle.net/qxqdp36x/2/
Essentially you need to separate initialization and update logic, and avoid re-creating elements when updating, that causes unintended behaviours.
Also, the variables gs and asd needs to be global to be accessible to both functions.
var textsInit = function(ds, ds2) {
var stack = d3.layout.stack();
stack_data = stack(ds);
var xScale = d3.scale.ordinal()
.domain(d3.range(ds[0].length))
.rangeRoundBands([0, w - offset], 0.50);
var yScale = d3.scale.linear()
.domain([0,
d3.max(stack_data, function(d) {
return d3.max(d, function(d) {
return d.y0 + d.y - 20;
});
})
])
.range([padding, h - 50]);
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.ticks(ds[0].length);
gs = svg.selectAll("g").data(stack_data);
bars = gs.enter();
bars.append("g")
.attr("class", "stacked_bars")
.attr("fill", function(d, i) {
return colors(i);
});
asd = gs.selectAll("rect").data(function(d) {
return d;
});
asd.enter().append("rect");
asd.transition()
.duration(1000)
.attr("x", function(d, i) {
return xScale(i);
})
.attr("y", function(d) {
return h - yScale(d.y0) - yScale(d.y);
})
.attr("height", function(d) {
return yScale(d.y);
})
.attr("width", xScale.rangeBand())
.attr("class", "rectbar");
gs.append("g") // add a group element to the svg
.attr("class", "axis") //Assign class "axis" to group
.attr("transform", "translate(0," + (h - padding) + ")") // shift the axis to bottom
.call(xAxis); // call the axis function
}
And:
var textsUpdate = function(ds, ds2) {
var stack = d3.layout.stack();
stack_data = stack(ds);
var xScale = d3.scale.ordinal()
.domain(d3.range(ds[0].length))
.rangeRoundBands([0, w - offset], 0.50);
var yScale = d3.scale.linear()
.domain([0,
d3.max(stack_data, function(d) {
return d3.max(d, function(d) {
return d.y0 + d.y - 20;
});
})
])
.range([padding, h - 50]);
gs.data(stack_data);
asd = gs.selectAll("rect").data(function(d) { return d; });
asd.transition()
.duration(1000)
.attr("x", function(d, i) {
return xScale(i);
})
.attr("y", function(d) {
return h - yScale(d.y0) - yScale(d.y);
})
.attr("height", function(d) {
return yScale(d.y);
})
.attr("width", xScale.rangeBand())
.attr("class", "rectbar");
}
Edited to fix a small bug, updating the asd selection's data.
I made 2 simple but crucial changes to your code.
https://jsfiddle.net/guanzo/510ep9ux/6/
From
gs = svg.selectAll("g").data(stack_data);
to
gs = svg.selectAll("g.stacked_bars").data(stack_data);
The axis is also contained in a g element, so you have to ensure you're only selecting elements that are used for your data, and not unrelated elements.
From
gs.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + (h - padding) + ")")
.call(xAxis);
to
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + (h - padding) + ")")
.call(xAxis);
If you go into the browser inspector you'll see that you have an axis element for EVERY stacked_bars element, you only need 1 obviously. It only looks like there's 1 axis because they're absolutely positioned and stacked on top of each other.
I changed it so that the axis is appended when the svg is created, and every time new data is selected, the axis will update itself.

How to improve this data-visualisation?

I have a d3.js plot that I want to improve it but I can't figure out how to do it!
This is my plot:
Mainly I am trying to change axis and add a little legend so I can get something like this (with x and y zeros centered in the plot ):
This is how I define x and y axis in my d3.js/JavaScript code
var xScale = d3.scale.linear().domain([d3.min(dataset, function(d) { return d[0]; }), d3.max(dataset, function(d) { return d[0]; })]).range([padding, w - padding]);
var yScale = d3.scale.linear().domain([d3.min(dataset, function(d) { return d[1]; }), d3.max(dataset, function(d) { return d[1]; })]).range([h - padding, padding]);
// Create axis
var xAxis = d3.svg.axis().scale(xScale).orient("bottom").ticks(5);
//Define Y axis
var yAxis = d3.svg.axis().scale(yScale).orient("left").ticks(5);
//Create SVG element
var svg = d3.select("#mydiv").append("svg").attr("width", w).attr("height", h);
svg.append("g").attr("class", "axis").attr("transform", "translate(0," + (h - padding) + ")").call(xAxis);
svg.append("g").attr("class", "axis").attr("transform", "translate(" + padding + ",0)").call(yAxis);
Thanks in advance:
If you provide your actual dataset I can give you more exact code for your case, without it the best we can really do is give you examples from the docs.
How to create a legend:
// draw legend
var legend = svg.selectAll(".legend")
.data(color.domain())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
// draw legend colored rectangles
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", color);
// draw legend text
legend.append("text")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) { return d;})
How to hardcode max/min on your axes:
var yScale = d3.scale.linear().domain([0,6]).range([h - padding, padding]);
How to add gridlines:
var yAxisGrid = yAxis
.tickSize(width, 0)
.tickFormat("")
.orient("right")
var xAxisGrid = xAxis
.tickSize(-height, 0)
.tickFormat("")
.orient("top")
svg.append("g")
.classed('y', true)
.classed('axis', true)
.call(yAxisGrid)
svg.append("g")
.classed('x', true)
.classed('axis', true)
.call(xAxisGrid)

D3 Multi-series transition overlapping data

I have a graph that correctly plots several lines based on a sensor's serial number. The problem is that I need it to transition to a new dataset and the data will overlap. Here's what I have so far......
var thresholdTemp = 72;
var minutes = 5;
var transInterval;
//Main function to create a graph plot
function plot(date1, date2, interval) {
var data;
//If we define a date search parameter then we don't want to have it load interactive
if (date1 == undefined && date2==undefined) {
data = loadMinutesJSON(minutes);
} else {
data = searchJSON(date1, date2, interval);
}
var margin = {top: 20, right: 80, bottom: 60, left: 50},
width = 960 - margin.left - margin.right ,
height = 500 - margin.top - margin.bottom;
//Re-order the data to be more usable by the rest of the script
var parseDate = d3.time.format("%Y-%m-%d %H:%M:%S").parse;
//Set the X domain to exist within the date and times given
var x = d3.time.scale().domain([getMinDate(data), getMaxDate(data)]).range([0, (width)]);
//Y Axis scale set to start at 45 degrees and go up to 30 degrees over highest temp
var y = d3.scale.linear()
.domain([
45,
getMaxTemp(data) + 10
])
.range([height, 0]);
//Set up the line colors based on serial number, this generates a color based on an ordinal value
var color = d3.scale.category20().domain(d3.keys(data).filter(function(key) { return key;}));
//.domain(d3.keys(data[0]).filter(function(key) { return key === 'serial';}));
//Define where the X axis is
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickFormat(d3.time.format("%b %d %H:%M:%S"));
//Define where the Y axis is
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
//When called creates a line with the given datapoints
var line = d3.svg.line()
.interpolate("basis")
.x(function(d) { return x(d.date);})
.y(function(d) { return y(d.reading); });
//An extra line to define a maximum temperature threshold
var threshold = d3.svg.line()
.x(function(d) { return x(d.date);})
.y(function(d) { return y(thresholdTemp); });
//Append the SVG to the HTML element
var svg = d3.select("#plot").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 + ")");
//Define the clipping boundaries
svg.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", (width + margin.left + margin.right))
.attr("height", height + margin.top + margin.bottom);
//Add the X axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform" , function (d) {return "rotate(-35)"});
//Add the Y axis and a label denoting it as temperature in F
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("Temperature (F)");
//Create the lines based on serial number
var serial = svg.selectAll(".serial")
.data(data);
var serials = serial.enter().append("g")
.attr("class", "serial");
//Add the extra line for a threshold and align it with the current time
var threshElement = svg.selectAll(".thresh")
.data(data)
.enter().append("g")
.attr("class", ".thresh");
//Add the path to draw lines and clipping so they stay within bounds
var path = serial.append("path")
.attr("clip-path", "url(#clip)")
.attr("class", "line")
.attr("d", function (d) {return line(d.values);})
.style("stroke", function(d,i) {return color(i);});
//Custom path to add a line showing the temperature threshold
var threshpath = threshElement.append("path")
.attr("clip-path", "url(#clip)")
.attr("class", "line")
.attr("d", function(d) { return threshold(d.values);})
.style("stroke", "red");
//Add a label to the end of the threshold line denoting it as the threshold
threshElement.append("text")
.attr("transform", "translate(" + x(getMaxDate(data)) + "," + y(thresholdTemp) + ")")
.attr("x", 3)
.attr("dy", ".35em")
.text("Threshold");
serial.exit().remove();
//Add the legend
//plotLegend(data);
//Load in the new data and add it to the SVG
function transition() {
data = loadMinutesJSON(minutes);
x.domain([getMinDate(data), getMaxDate(data)]);
y.domain([45, getMaxTemp(data) + 10]);
d3.select(".x.axis")
.transition()
.duration(500).call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform" , function (d) {return "rotate(-35)"});
d3.select(".y.axis").transition().duration(500).call(yAxis);
serial.data(data).enter().append("g").attr("class", "serial");
d3.selectAll("path")
.attr("class", "line")
.attr("d", function(d) { return line(d.values);})
.style("stroke", function(d,i) { return color(i);});
}
if(date1 == undefined && date2 == undefined) {
//Set the transition loop to run every 30 seconds
transInterval = setInterval(transition,30000);
}
}
//Set the new time in minutes and re-draw the graph
function setMinutes(newminutes) {
if (transInterval) {
clearInterval(transInterval);
}
d3.selectAll("svg").remove();
console.log("Setting new minutes " + newminutes);
minutes=newminutes;
plot();
}
//Search the database for data between 2 dates and create a new plot of it
function searchDB(date1, date2, intervalInMinutes) {
if (transInterval) {
clearInterval(transInterval);
}
d3.selectAll("svg").remove();
plot(date1, date2, intervalInMinutes);
console.log("Processing Search ");
}
//Quick function to determine the maximum date in a dataset
function getMaxDate(data) {
var arr = [];
for (x in data) {
arr.push(d3.max(data[x].values, function(d) { return d.date;}));
}
return d3.max(arr);
//return d3.max(data[0].values, function(d) { return d.date;});
}
//Calculate the minimum data
function getMinDate(data) {
var arr = [];
for (x in data) {
arr.push(d3.min(data[x].values, function(d) { return d.date;}));
}
return d3.min(arr);
//return d3.min(data[0].values, function(d) { return d.date;});
}
//Calculate the upper maximum temperature
function getMaxTemp(data) {
var arr = [];
for (x in data) {
arr.push(d3.max(data[x].values, function(d) { return d.reading;}));
}
return d3.max(arr);
//return d3.max(data, function(d) { return d3.max(data.values, function(d) {return d.reading})});
}
Here's examples of the data:
First
[{"serial":"2D0008017075F210","values":[{"date":"2013-08-23T20:43:46.000Z","reading":76.1,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:44:16.000Z","reading":76.1,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:44:46.000Z","reading":76.1,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:45:16.000Z","reading":76.1,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:45:47.000Z","reading":76.1,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:46:17.000Z","reading":76.1,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:46:47.000Z","reading":76.1,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:47:17.000Z","reading":76.1,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:47:47.000Z","reading":76.1,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:48:17.000Z","reading":76.1,"elevation":null,"room":null,"system":null}]},{"serial":"1D00080170496D10","values":[{"date":"2013-08-23T20:43:46.000Z","reading":73.4,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:44:16.000Z","reading":73.4,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:44:46.000Z","reading":73.4,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:45:16.000Z","reading":73.4,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:45:47.000Z","reading":73.4,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:46:17.000Z","reading":73.4,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:46:47.000Z","reading":73.4,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:47:17.000Z","reading":73.4,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:47:47.000Z","reading":73.4,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:48:17.000Z","reading":73.4,"elevation":null,"room":null,"system":null}]},{"serial":"380008017037ED10","values":[{"date":"2013-08-23T20:43:46.000Z","reading":74.3,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:44:16.000Z","reading":75.2,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:44:46.000Z","reading":74.3,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:45:16.000Z","reading":74.3,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:45:47.000Z","reading":74.3,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:46:17.000Z","reading":74.3,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:46:47.000Z","reading":75.2,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:47:17.000Z","reading":74.3,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:47:47.000Z","reading":74.3,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:48:17.000Z","reading":74.3,"elevation":null,"room":null,"system":null}]}]
And then transitioning to:
[{"serial":"2D0008017075F210","values":[{"date":"2013-08-23T20:44:46.000Z","reading":76.1,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:45:16.000Z","reading":76.1,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:45:47.000Z","reading":76.1,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:46:17.000Z","reading":76.1,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:46:47.000Z","reading":76.1,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:47:17.000Z","reading":76.1,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:47:47.000Z","reading":76.1,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:48:17.000Z","reading":76.1,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:48:47.000Z","reading":76.1,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:49:17.000Z","reading":76.1,"elevation":null,"room":null,"system":null}]},{"serial":"1D00080170496D10","values":[{"date":"2013-08-23T20:44:46.000Z","reading":73.4,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:45:16.000Z","reading":73.4,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:45:47.000Z","reading":73.4,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:46:17.000Z","reading":73.4,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:46:47.000Z","reading":73.4,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:47:17.000Z","reading":73.4,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:47:47.000Z","reading":73.4,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:48:17.000Z","reading":73.4,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:48:47.000Z","reading":73.4,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:49:17.000Z","reading":73.4,"elevation":null,"room":null,"system":null}]},{"serial":"380008017037ED10","values":[{"date":"2013-08-23T20:44:46.000Z","reading":74.3,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:45:16.000Z","reading":74.3,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:45:47.000Z","reading":74.3,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:46:17.000Z","reading":74.3,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:46:47.000Z","reading":75.2,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:47:17.000Z","reading":74.3,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:47:47.000Z","reading":74.3,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:48:17.000Z","reading":74.3,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:48:47.000Z","reading":74.3,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:49:17.000Z","reading":74.3,"elevation":null,"room":null,"system":null}]}]
UPDATE:
I've modified the code quite a bit, but the jsfiddle is here http://jsfiddle.net/8cguF/1/
I'm not sure why the fiddle doesn't work, I tested it on my webserver and it works fine. But what I'm trying to do is construct a graph with the data1 variable in there and then transition it to the data2 variable.

Categories