Skip to content
Advertisement

d3.js x-coord displaying incorrectly iOS

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

enter image description here

While the correct graph is,

enter image description here

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()

Advertisement

Answer

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