Any idea why I can't see two separate rectangles?

Circles are defined by 3 values;

r : the radius of the circle.
cx: the X coordinate of the centerpoint of the circle.
cy: the Y coordinate of the centerpoint of the circle.

This is a false statement based on the fiddle you’re showing me…

   d3.select('#wrapper')  
    .selectAll("circle")
    .data(circle)
    .enter().append("circle")
    .style("stroke", "gray")
    .style("fill", "orange")
    .attr("r", 7)
    .attr("cx",function (d,i) { return (i+1)*24; })
    .attr("cy",function (d,i) { return (i+1)*24; });

cx is defined by the index of the data point.

Instead, define cx by its converted timestamp-to-pixels. (Hint: you want to scale the timestamp to pixels… where in your code have you scaled a time…)

Some things you may want to also consider:

1: You always use the Timestamp as a date. Consider map’ing your data array to transform the Timestamp strings into Dates.
2: Your scale is currently being constructed from fixed date values. You might want to make it scale based off the data in the range. Consider investigating d3.extent().

1 Like

Thanks @m_hutley . Here is my updated fiddle with timestamp-to-pixel conversion.

Changed the code to the following for cx and cy :

.attr("cx",function (d,i) { 
                 let CX = new Date(d.Timestamp)
                 return timeScale(CX);
                  
                  })
    .attr("cy",function (d,i) 
                {
                
                 let CY = new Date(d.Timestamp)
                 return timeScale(CY);
                });

Third circle seems to be completely off which is understandable based on the coordinates (525,525) :
image

Does this mean that I should first think about moving these values on the rectangle and then think about the child-parent relationship based on the Parent Tuple?

You mean something similar to what I’m doing inside this function in my code?

var getDaysArray = function(start, end) {
    for(var arr=[],dt=new Date(start); dt<=new Date(end); dt.setDate(dt.getDate()+1)){
        arr.push(new Date(dt));
    }
    return arr;
};

Kind of. Here’s how i did it in my playing-around with your code: (immediately after the declaration of printObjectsJson)

printObjectsJson = printObjectsJson.map(x => { return {...x, "Timestamp": new Date(x.Timestamp)} });

Which says… “take each object of the array. Return that object (… is a spread, which basically translates as “all of the properties of that object”), and overwrite the Timestamp property with a date made from the current timestamp string.”

Well I would question your choice of changing cy. the timeScale has been calibrated for a range that matches the horizontal stretch of your graph (0,700). You wouldnt want to derive vertical coordinates for it using the same (horizontal) scale.

Consider a chart: (I am grabbing the first thing off Google that demonstrates my point)
image

The scales for X and Y are completely different - X takes a date in, and outputs some coordinate… probably between 20 and 350, would be my guess. Y takes a number in, and outputs some different coordinate, somewhere between 40 and 200, again, if i’m guessing (Actually, in most cases, you’ll flip the Y scale around. In this graph, it would be a range of (200,40), because you want the 0.74 to give you a 200, and the 0.79 to give you a 40, because the way the Y coordinate works in a SVG). Different inputs, different outputs.

Think about your image you showed me.

What’s wrong with the picture?
“The circles are in the wrong place on the line.”
But they’re on the correct line.
“Yes.”
So one of their coordinates is correct; the other is incorrect.

1 Like

Thanks for the info. I think it is working now based on the JSFiddle below:

Following changes were required :

  1. Changed the range from 0-700 to 0-210

  2. Changed the attribute y for rectangle and text to .attr('y', function (d,i) { return timeScale(new Date(d)) }) from .attr('y', function (d,i) { return (i+1)*24; })

  3. Similarly, for circle, the cx and cy defined are as follows:

.attr(“cx”,cir => timeScale(new Date(cir.Timestamp)))
.attr(“cy”,cir => {
const index = printObjectsJson.findIndex(d => d.ID === cir[“Parent tuple”])
return timeScale(new Date(printObjectsJson[index].Timestamp));});

Wanted to double-check with you if everything looks right? I will work on changing the dates to array like you mentioned in your last post. Thanks!

Assuming you actually want your X coordinates restrained to 0-210, and you want the rectangles to be on the same spacing as the circles, then yeah. Without knowing exactly what you want the graph to look like, I can’t definitively say “yes thats right” or not.

So i’ll give you what I was thinking about the dates and your scale extent. You may or may not want to use it, but its probably a good thing to learn about anyway.

Rather than giving concrete dates, we may want to be responsive to the data we receive. The data in your sample, for example, ranges from 5/1/2018 to 5/5/2018. Previously, you’d said your range was until 5/8/2018.

If we wanted to span from the beginning to the end, without knowing what the values are ahead of time, we need to do some digging on our data - the domain, after all, needs a minimum and maximum value.

d3 recognized that this would be a common task, and has implemented methods to extract that information - d3.min(), d3.max(), and d3.extent.

As you might expect, min() finds the smallest value in the data; max finds the largest. Extent(), however, finds both - the minimum, and the maximum, and returns them as a 2-item array.

We wouldn’t think we can feed it the whole object - it doesnt know how to compare

  { "ID":"7",
		"s1": "Rectangle 4",
    "Parent tuple": "2",
    "Relationship": "has_rectangle",
    "Timestamp": "5/4/2018 00:00",
	}

to

   { "ID":"8",
		"s1": "Rectangle 5",
    "Parent tuple": "1",
    "Relationship": "has_rectangle",
    "Timestamp": "5/5/2018 00:00",
	}

(maybe we want to compare by ID, or Parent, or s1…?)
We could do some fancy work with map again… but d3 has us covered already. extent (and min and max, for that matter!) can take a second parameter; a function that defines how to access the data object, so d3 knows what to use for a comparison.

In our case, we would want the Timestamp. Let’s for the moment assume we have converted the strings into Dates already.

extent can find our max and min values in a single command:
d3.extent(circles, d => d.Timestamp)

Which would return the min and max values in an array:
d3.extent(circles, d => d.Timestamp) << [Tue May 01 2018 00:00:00 GMT-0400 (Eastern Daylight Time), Fri May 04 2018 00:00:00 GMT-0400 (Eastern Daylight Time)]

Which, as it turns out, is exactly what .domain() is expecting… so…

  var timeScale = d3.scaleTime()
	.domain([new Date("5/1/2018 00:00"), new Date("5/5/2018 00:00")])
 	.range([0, 220]); 

=>

  var timeScale = d3.scaleTime()
	.domain(d3.extent(circle, d => d.Timestamp))
 	.range([0, 220]); 

And suddenly your scale will adjust itself to fit any timespan data you happen to ingest…(Note: Because this is only based off of the circles, you will actually make your range until 5/4, meaning that the last rectangle disappears; so you might want to do the timescale on your whole data set, rather than the filtered ones)

I would actually not want it to be restrictive to 0-210. I would like it to look like the following:

Width wise I want some space betwee nthe dates. But if I go on increasing the height to 700 and then range to 0-600, the width between rectangles is increasing as shown here:

So… lets set aside the code for a moment, and go back a step. or 3.

Let’s talk design specifications.

In english, tell me what you want the chart to do. What you want it to show. How wide should it be? How tall? Where should the things in the graph be?

From your picture, I might come up with a design specification something like…

“I want a 700 by 200 chart, that is broken up vertically by date (there should be 5 dates shown at a time), and as many lines as I have records in that time period, each with a circle on it marking which day the record falls on.”

Sure. So, in simple words, I would want the chart to look like the following ( you summarized it well after looking at the picture):

  1. The chart must have 5 dates and as many lines as I’ve records.
  2. I want the dates on the chart which are broken vertically by date to be at some distance which are easy to read and not shrunk on the left side, just like it happened in a scenario of range 0-210. I don’t want too much space between the rectangles/lines.
  3. Each lines must have a circle on it marking which day the record falls on just like you said.
  4. I’m not very sure about 700 by 200 part though.

Did I answer your specification question properly? Let me know if I’m missing anything.

One another thing I had to do to fix this space issue is the following:

Introduced an additional scale for X-axis and used the width as the range and then used that in my x attribute callback like this attr("cx",cir => timeScaleX(new Date(cir.Timestamp))). I hope it’s not redundant to introduce additional scale just for handling spacing issue?

[So, I swear I had pushed Post on this yesterday, but apparently nope, i wrote it all out and didn’t bother to push the button. Good job me.]

okay, so that tells me a few things about what is variable and what is not. Let’s reflect on a few things.

  1. The char must have 5 dates and as many lines as I have records.
    So, two parts to this:
    A) 5 dates: Is the code meant to be filtering out data outside that range? What qualifies as the starting point? Is it fixed, or should it come from the data?
    B) as many lines as record: Either your chart will need to have a variable height, and fixed width of record, or a fixed height, and variable width of record.

  2. This should be simple, as we’ve fixed the number of dates at 5, and we can control the font of the text to ensure that this is done.

  3. So let’s start thinking about the display of the data of a record;
    We want a circle (a marker) on the line for the record’s date.
    We want a line extending the full width of the chart’s display
    We want a text which gets its value from the record.

So, at minimum, we want the record to be tall enough to fit those elements.

It’s not redundant, because you have two different scales for different data. At the moment, you’re using both as a time scale, but i’m trying to get you to see that your Y scale isnt a time scale. :wink:

No problem and thanks for your valuable input. I will look into the usage of d3.min, max and extent like you said and I think I should start using them anyways going forward.

And like you mentioned in your post : “Let’s for the moment assume we have converted the strings into Dates already.”

Is it mandatory for the conversion to happen to strings for this to work? You mentioned before as well to convert dates/timestamp to strings. Just curious to know about it. Thanks!

For scaleTime to work, the values for the domain must be Dates or Date-Coercible (domain will coerce things fed to it to Dates). Your current strings should be coercible, but if the data you receive isnt, you might end up in trouble, so keep it in mind when you unleash the code on the real data :wink:

It expects Date data, to be put on a time scale. Not entirely surprising. If it was just numbers, you’d use scaleLinear instead.

I just dont like the object to contain a bunch of strings-that-arent-strings.

I’ve had a play around with your code. I’ve also flexed a bit more of the strength of some of D3’s inbuilt functionality, to show what you might be able to do…

That looks great. I will definitely try to use that in my real data. My requirement is to use text on the rectangle so that can be done easily based on the x-coordinate.

I’m trying to solve a slightly different variation of the problem as described below:

Noticed an issue when I started working on large data and I’ve created a fiddle with my deidentified data. The problem is mainly related to plotting text when the timestamp is not in the proper sequence. Here is my JSFiddle with how I want the data to look:

Fiddle1:

But as you notice in the line 644 and 654, I’m using .attr('y', function (d,i) { return (i+1)*20; }) which I don’t want to use and if I use the following : .attr('y', myD => timeScale(new Date(myD)))
on line 644 and 654, the data looks like the following:

Fiddle 2:

If I’m understanding it correctly, this is happening because the timestamp is not in sequence in the problems JSON data, I mean it’s not starting from 4/1/2021 and ending in 4/30/2021 and hence the following code :

d3.select(‘#wrapper’)
.selectAll(‘text’)
.data(problems)
.join(‘text’)
.attr(‘x’,5)
.attr(‘y’, txt => timeScale(new Date(txt.Timestamp)))
.text(function (d) { return d.Object.toUpperCase(); })`

is using the timestamp from the problems data and behaving as shown in Fiddle 2 above.

Question:

I would like to know, if in D3, is there a way to use the timestamp from variable myData which will be basically (var myData = getDaysArray(new Date("4/1/2021 00:00"), new Date("4/30/2021 00:00"));) instead of problems for the following line of code .attr('y', txt => timeScale(new Date(txt.Timestamp))) in the above code snippet?

However, for the following line, I would still want to use the problems data:

.text(function (d) { return d.Object.toUpperCase(); })

Otherwise, I won’t be able to retrieve the text from the problem data.

D3 and this dataset… are you taking a Masters degree in Data Science? :stuck_out_tongue:

So what i’m trying to get you to see is that you should scale the coordinates based on the type of data you have on that scale.

Let me give you some examples, and how their scales would be described in D3 terms.To Google!


This graph has an X scale based on Time, specifically, a 24 hour timescale. It would use a scaleTime.
This graph has a Y scale based on a continuous number scale. It would use a scaleLinear.


This graph has an X scale based on color, which is a categorical dataset (or a discrete set of values). This would use a scaleOrdinal.
This graph has a Y scale based on a continuous number scale (even though it will only ever have whole-number data, the scale is continuous). It would use a scaleLinear.

There are other types of scale in D3, but those are the most common. A scale is simply this: For any given valid input, what is the corresponding output. It translates input to an output; most commonly, the coordinate in the graph to correspond to that value. Each point (or box, or… whatever) will have two coordinates that it bases itself off of in a two-dimensional plot: an X and a Y.

So look at your data, and what you want your scales to be. What should your X scale be? What should your Y scale be?

Haha, not really in Data Science. I did graduate in computer science 10 years back but this is my first encounter with D3 and this much data for visualization :smile:

Thanks for the examples. So based on the examples, in my scenario, In my 2nd Fiddle I’m using scaleTime for .attr('y', txt => timeScale(new Date(txt.Timestamp))) and just using .attr('x',5) for X. Is that what I should not be using or is that the direction you were referring me to?

my question to you would be:

  .attr('x',5)
  .attr('y', txt => timeScale(new Date(txt.Timestamp)))
  .text(function (d) { return d.Object.toUpperCase(); })

What does the string “constipation” have to do with the timescale?

Right now, your code is telling me that X and Y are both timescales. They’re both the same time scale. Which would possibly make sense if you were trying to plot something in two dimensions of time; but you’ve only got 1 dimension of time in your data (Timestamp). What’s the other dimension for your graph?

Look back at the previous examples; each axis represents 1 element of the data from each object; in the case of the stock price, you’ve got the “Time”, and “Price” elements. In the colour one, you’ve got “color”, and a “count”.

Your code keeps trying to tell me “Timestamp” and “Timestamp”. Which… doesnt make sense for a graph.

Eventually, I will be plotting the circles just like I did in the JSFiddle of this post: Any idea why I can't see two separate rectangles? - #54 by Jack_Tauson_Sr

In order to do that, I need two different timeScale as I did in the above post’s JSFiddle.

To answer your question, I think I would continue to use both X and Y as timescales.

Are you referring to this line of my code .text(function (d) { return d.Object.toUpperCase(); }) by saying string constipation?