Using a time scale instead of normal numbers

For this graph

The x axis goes from 0 to 360 and so does the random data.

I want to change it to a time scale representing a year with the 12 months and if you zoom in you see the days and finally the time in minutes.

The book I have gives me a few examples but the data also consists of dates. Do I have to convert my random numbers into date formats or create random time/date data to make it work?

        let xScale = d3.scaleTime().domain([Date(2022,1,1) , Date.now]).range([0, width]).nice(),
            yScale = d3.scaleLinear().domain([0, 210]).range([height, 0]).nice(),
            newX = xScale,
            newY = yScale;

        var axBot = g.append("g")
         .attr("transform", "translate(0," + height + ")")
         .attr('id', 'x-axis')
         .call(d3.axisBottom().scale(xScale).tickSize(25));

Created a new Codepen for that:

1 Like

Well if you want a time-based scale… it will need some time-based data to work off of :stuck_out_tongue:
Technically, numbers can be time data (Timestamps), but lets look at your scale:
let xScale = d3.scaleTime().domain([Date(2022,1,1) , Date.now]).range([0, width]).nice(),
If you generate random numbers between 0 and 360 and interpolate them as dates, they will come out as random times in the first 6 minutes of January 1, 1970. Timestamp 0 is Midnight, and every number counts as a single second.

Spitballing a random date generator in the range Midnight January 1, 2022 to Now… and making it readable instead of my one-line normality…

//Create 360 date points using the function...
let randDates = Array.from({length: 360}, () => {
  let Jan1 = new Date(new Date().getFullYear(),0,1);  //Needed for a bottom of our random pool.
//Get a random number of seconds between 0 and Now's Number of Seconds of the Year, then add January 1, 2022 to it.
  let RandTime = Math.floor(Math.random()*(Date.now()-Jan1))+Jan1.getTime(); 
//Make it a date and spit it out.
  return new Date(RandTime);
});

Thanks will try that out.

Also found this in the book:

        let xScale = d3.scaleTime().domain([new Date(2022,1,1) , new Date.now]).range([0, width]).nice(),
            yScale = d3.scaleLinear().domain([0, 210]).range([height, 0]).nice(),
            newX = xScale,
            newY = yScale;

        console.log(xScale(new Date('2022-1-1')));
        console.log(xScale.invert(400));

What is the difference in putting “new” in front of date? In my case with “new” I get nothing, without at least some errors and pics.

Where exactly do I place your code? Before Step 4, the scaling?

        // Step 3
        var svg = d3.select("svg"),
            margin = 200,
            width = svg.attr("width") - margin, //1700
            height = svg.attr("height") - margin //700

            const offsetX = margin/2 + width/2;
            const offsetY = margin/2 + height/2;

        //Create 360 date points using the function...
        let randDates = Array.from({length: 360}, () => {
            let Jan1 = new Date(new Date().getFullYear(),0,1);  //Needed for a bottom of our random pool.
            //Get a random number of seconds between 0 and Now's Number of Seconds of the Year, then add January 1, 2022 to it.
            let RandTime = Math.floor(Math.random()*(Date.now()-Jan1))+Jan1.getTime(); 
            //Make it a date and spit it out.
            return new Date(RandTime);
        });

        // Step 4 
        let xScale = d3.scaleTime().domain([Jan1 , RandTime]).range([0, width]).nice(),
            yScale = d3.scaleLinear().domain([0, 210]).range([height, 0]).nice(),
            newX = xScale,
            newY = yScale;

        console.log(xScale(new Date('2022-1-1')));
        console.log(xScale.invert(400));

Line Chart Zoom - Zeitachse.html:147 Uncaught ReferenceError: Jan1 is not defined

Date is an object. To create a new Date object, you have to specify the new keyword. If you just say Date() Javascript is looking for a function called Date. If you tell it new Date Javascript goes and finds the class definition for a Date object and calls its constructor.

My code in the previous post generates a dataset. You would need to zip together the dates and some Y value, as you have with your other data.

Mmmh… not quite.

You can pull the Jan1 out of the function if you like, and do something like
let xScale = d3.scaleTime().domain([Jan1 , new Date]).range([0, width]).nice(),

(new Date will default to the current datetime)

You mean your code generates the plotted data which go into an array. I use three lines, two linear, up and down and one curvy.

So the scale code is correct?

        //Create 360 date points using the function...
        let randDates = Array.from({length: 360}, () => {
            let Jan1 = new Date(new Date().getFullYear(),0,1);  //Needed for a bottom of our random pool.
            //Get a random number of seconds between 0 and Now's Number of Seconds of the Year, then add January 1, 2022 to it.
            let RandTime = Math.floor(Math.random()*(Date.now()-Jan1))+Jan1.getTime(); 
            //Make it a date and spit it out.
            return new Date(RandTime);
        });

        let Jan1 = new Date(new Date().getFullYear(),0,1);

        // Step 4
        let xScale = d3.scaleTime().domain([Jan1 , new Date]).range([0, width]).nice(),
            yScale = d3.scaleLinear().domain([0, 210]).range([height, 0]).nice(),
            newX = xScale,
            newY = yScale;

        console.log(xScale(new Date('2022-1-1')));
        console.log(xScale.invert(400));

This gives me a time scale, zooming to 10080 gets me as close to 5 min intervals. Now I wanna change it from am pm to 24h style.

Will try this: https://stackoverflow.com/questions/15654000/d3-js-default-axis-tickformat-to-24-hour-clock

And need to plot data since that is gone.

Why two “new date” after the other?

For January 1? They’re not actually beside each other, one is inside the other:

let Jan1 = new Date(     new Date().getFullYear()    ,0,1);

When you call new Date, there are a lot of overriding options for going into the constructor; in this case, i’m passing 3 numerical parameters to the outer constructor; this means i’m going into the constructor method:

Date(int Year, int Month, int Day)

The month and Day are fairly trivial - Month 0 is January (months are 0-indexed!), and Day 1 is… well, Day 1.

The first parameter, where we want the Year, I put new Date().getFullYear()
Remember that i said new Date will default to the current datetime? so, I call new Date(), which gives me a date with the current timestamp, and then getFullYear from that date object, which will give me the current year - in this case, 2022.

There are other ways this could have been done… mathematical ways, functional ways… this was the way i did it.

Just discovered that time.format has been deprecated.
https://splunktool.com/how-to-format-time-on-xaxis-use-d3js

So will try the other options.

Everything else is correct so far?

Need to update my data generating code next to get actual data. 270 days (until September) * 24 hours * 60 minutes = 388.800. Every minute a new data point.

Well the code i provided is generating random data points, not a data point every minute.

If you want a data point exactly every minute…spitballing…

let Jan1 = new Date(new Date().getFullYear(),0,1);
let x_date_values = Array.from({length: 388800}, (e,i) => new Date(Jan1.getTime()+(i*60000)));

You mean your one line of code creates one random dataset and replaces a third of this code?
Used 6480 since the other took too long.

        // Step 1

        let min = 0;
        let max = 200;
        let x_arr = [];
        let y_arr = [];
        let s_arr = [];
        let z_arr = [];

        for (let i = 0; i < 6480; 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 < 6480; i++) {
            neu_arr[i]= i;
            zz_arr.push([neu_arr[i], s_arr[i]]);
        }

        // Zweiter und dritter Datensatz

        let x2_arr = [];
        let y2_arr = [];
        let s2_arr = [];
        let neu2_arr = [];
        let zz2_arr = [];

        for (let i = 0; i < 6480; i++) {
            var r = Math.round(Math.random() * (max - min)) + min;
            x2_arr[i]= i;
            y2_arr[i]= r;
        }

        s2_arr = y2_arr.sort(function(a, b){return b - a});

        for (let i = 0; i < 6480; i++) {
            neu2_arr[i]= i;
            zz2_arr.push([neu2_arr[i], s2_arr[i]]);
        }

        console.log(zz2_arr);

        let x3_arr = [];
        let y3_arr = [];
        let s3_arr = [];
        let neu3_arr = [];
        let zz3_arr = [];

        for (let i = 0; i < 6480; i++) {
            var r = Math.round(Math.pow(Math.random(), 3) * (max - min)) + min;
            x3_arr[i]= i;
            y3_arr[i]= r;
        }

        s3_arr = y3_arr.sort(function(a, b){return a - b});

        for (let i = 0; i < 6480; i++) {
            neu3_arr[i]= i;
            zz3_arr.push([neu3_arr[i], s3_arr[i]]);
        }

        console.log(zz3_arr);

        var dataset1 = zz_arr;
        var dataset2 = zz2_arr;
        var dataset3 = zz3_arr;

Not quite, I replaced dataset1 with x_date_values but there is this error message:
d3.v5.min.js:2 Error: attribute d: Expected number, “MNaN,NaNCNaN,NaN,…”.

Well yeah, because you replaced a 2-dimensional dataset of x,y data with a 1-dimensional array of dates :wink:

I wasnt going to comment on the data generation schema you’ve got going there, but if you want to tidy that up we can.

Yes, I noticed that myself. Will try and insert it into the 2 dimensional array as before.

Your function or code only gives me values of the 1.1.2022:

  1. 6464: Sat Jan 01 2022 01:47:44 GMT+0100 (Mitteleuropäische Normalzeit) {}
  2. 6465: Sat Jan 01 2022 01:47:45 GMT+0100 (Mitteleuropäische Normalzeit) {}
  3. 6466: Sat Jan 01 2022 01:47:46 GMT+0100 (Mitteleuropäische Normalzeit) {}
  4. 6467: Sat Jan 01 2022 01:47:47 GMT+0100 (Mitteleuropäische Normalzeit) {}
  5. 6468: Sat Jan 01 2022 01:47:48 GMT+0100 (Mitteleuropäische Normalzeit) {}
  6. 6469: Sat Jan 01 2022 01:47:49 GMT+0100 (Mitteleuropäische Normalzeit) {}
  7. 6470: Sat Jan 01 2022 01:47:50 GMT+0100 (Mitteleuropäische Normalzeit) {}
  8. 6471: Sat Jan 01 2022 01:47:51 GMT+0100 (Mitteleuropäische Normalzeit) {}

I need random values from 1.1.2022 until today. Maybe the y scale is random and it all happens every hour.

If put together with the index array:

  1. 2724: (2) [2724, Sat Jan 01 2022 00:45:24 GMT+0100 (Mitteleuropäische Normalzeit)]

  2. 2725: (2) [2725, Sat Jan 01 2022 00:45:25 GMT+0100 (Mitteleuropäische Normalzeit)]

  3. 2726: (2) [2726, Sat Jan 01 2022 00:45:26 GMT+0100 (Mitteleuropäische Normalzeit)]

  4. 2727: (2) [2727, Sat Jan 01 2022 00:45:27 GMT+0100 (Mitteleuropäis

         //Create 360 date points using the function...
         let randDates = Array.from({length: 6480}, () => {
             let Jan1 = new Date(new Date().getFullYear(),0,1);  //Needed for a bottom of our random pool.
             //Get a random number of seconds between 0 and Now's Number of Seconds of the Year, then add January 1, 2022 to it.
             let RandTime = Math.floor(Math.random()*(Date.now()-Jan1))+Jan1.getTime(); 
             //Make it a date and spit it out.
             return new Date(RandTime);
         });
    
         let Jan1 = new Date(new Date().getFullYear(),0,1);
         let x_date_values = Array.from({length: 6480}, (e,i) => new Date(Jan1.getTime()+(i*1000)));
    
         console.log(x_date_values);
    
         let neud_arr = [];
         let zzd_arr = [];
    
         for (let i = 0; i < 6480; i++) {
             neud_arr[i]= i;
             zzd_arr.push([neud_arr[i], x_date_values[i]]);
         }
    
         var dataset1 = zzd_arr;
    
         console.log(dataset1);
    

So if you’ve sliced off the first 6480 records of a function that generates a point per minute… you’ve sliced 6480 minutes… which would be 108 hours… or 4 and a half days. So yes, i would expect slicing 6480 records from that function to give you a data point every minute until Noon on January 5th.

Okay, so now we’re changing the specification :wink:

Lets… address this whole… data generation… mess you’ve got going on. No offense, but it is messy.

Do you want the X values to be…
1 point every hour? That will have a variable number of points as time goes on… more hours, more points.
6480 points, evenly divided across the timescale? That will have a fixed number of points, but as time goes on, those points will get further and further apart.

From what I can see in your code, the Y values of the three lines are:
Red: An increasing data set of random linear values between 0 and 200.
Green: A decreasing data set of random linear values between 200 and 0.
Blue: An increasing data set of random exponential values, with a power of 3, between 0 and 200.

1 Like

I want all those those data points (could be a lot more, like a few thousand) ranging from 0 to 200 being displayed on a time scale representing this year instead of 0 to 360 as discrete numbers. Three different graphs, can look the same as now. I can already zoom far in enough to see a 5 min gap.

When this works, I wanna change the time format to 24h european system, not a.m., p.m. but 18:00 or so.

When this works, I later want to use and display different y axes, one on the right, one on the left and one somewhere else, one for each data set, cause they are different. If on the same scale one would be very small and the other stretched, e.g. 0 to 200, 0 to 500, 600 to 2000. I have literature to add one more scale.

The tooltip should still work on this. Final step would be adding a vertical line wherever my mouse is and when this line hits a circle (data point) it gives me the tooltip.

Right. So what i hear you say is you want a variable but definable number of points, spread across the year.

//Define some values...
//Our y-scale boundries...
const min = 0;
const max = 200;
//Number of points to create on the graph...
const points = 6480;
//I'm going to getTime here, because i only use Jan1 as its time form.
const Jan1 = new Date(new Date().getFullYear(),0,1).getTime(); 
//down to the millisecond, how wide is the gap between our datapoints?
const gap = Math.floor((new Date().getTime() - Jan1)/points); 

//Data
//The X values are common.
const x_values = Array.from({length: points}, (e,i) => new Date(Jan1+(i*gap)));
//The Y values aren't.
const y_red = Array.from({length: points}, () => Math.round(Math.random() * (max - min)) + min)
y_red.sort((a,b) => a-b);
//I'm just going to steal the same values from the red set and flip them. I'm lazy. You can do whatever.
const y_green = [...y_red]; 
y_green.sort((a,b) => b-a);
//Blue doin exponential things.
const y_blue = Array.from({length: points}, () => Math.round(Math.pow(Math.random(), 3) * (max - min)) + min);
y_blue.sort((a,b) => a-b);

//Combine the datasets.
const dataset1 = y_red.map((y,i) => [x_values[i],y]);
const dataset2 = y_green.map((y,i) => [x_values[i],y]);
const dataset3 = y_blue.map((y,i) => [x_values[i],y]);

On in compact form, without the comments, and how i’d probably do it in my own code because i’m a bad programmer:

const min = 0;
const max = 200;
const points = 6480;
const Jan1 = new Date(new Date().getFullYear(),0,1).getTime(); 
const gap = Math.floor((new Date().getTime() - Jan1)/points); 

const x_values = Array.from({length: points}, (e,i) => new Date(Jan1+(i*gap)));
const dataset1 = Array.from({length: points}, () => Math.round(Math.random() * (max - min)) + min).sort((a,b) => a-b).map((y,i) => [x_values[i],y]);
const dataset2 = Array.from({length: points}, () => Math.round(Math.random() * (max - min)) + min).sort((a,b) => b-a).map((y,i) => [x_values[i],y]);
const dataset3 = Array.from({length: points}, () => Math.round(Math.pow(Math.random(), 3) * (max - min)) + min).sort((a,b) => a-b).map((y,i) => [x_values[i],y]);

So this can go away?

        //Create 360 date points using the function...
        let randDates = Array.from({length: 6480}, () => {
            let Jan1 = new Date(new Date().getFullYear(),0,1);  //Needed for a bottom of our random pool.
            //Get a random number of seconds between 0 and Now's Number of Seconds of the Year, then add January 1, 2022 to it.
            let RandTime = Math.floor(Math.random()*(Date.now()-Jan1))+Jan1.getTime(); 
            //Make it a date and spit it out.
            return new Date(RandTime);
        });

        let Jan1 = new Date(new Date().getFullYear(),0,1);
        let x_date_values = Array.from({length: 6480}, (e,i) => new Date(Jan1.getTime()+(i*1000)));

        console.log(x_date_values);

        let neud_arr = [];
        let zzd_arr = [];

        for (let i = 0; i < 6480; i++) {
            neud_arr[i]= i;
            zzd_arr.push([neud_arr[i], x_date_values[i]]);
        }

// var dataset1 = zzd_arr;

        console.log(dataset1);

This would replace lines 1-76 of the code in your codepen.

EDIT: Oh, i see where you’ve stuck that bit of code… right… it would replace lines 1-76 AND 87-111. Stick it at the top and call it Step 1. Or… 1 and 2? i dont know where Step 2 went.