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

Right. so thats why we fix the two points and draw a line between them. or rather, thats what a linearscale does for you in the background.

Thanks again. One more follow-up question on the same topic that I was doing in JSFiddle. Please find my updated JSFiddle here: https://jsfiddle.net/walker123/kb85njwo/36/

In the above fiddle, I’ve made the following changes.

I’ve commented out this line : .attr("width", function (d) { return timeScale(d);}) and currently using this line : .attr("width", '100%')

Reason for this Change:
Since the width was only until the end date and I wanted to display the rectangles all the way through the end, I added width as 100%. But after doing this, did I lose access to the timestamp-based width? I mean let’s say if I want to put a dot (circle) on the first line at the date Tue May 01, 2018, would I be able to do this with this change?

If I did lose access to timestamp width, then maybe I need to do the following things one below another. right?

  d3.select('#wrapper')
	.selectAll('rect')
	.data(myData)
	.join('rect')
	.attr('x', 1)
	.attr('y', function (d,i) { return (i+1)*2; })
        .attr("width", '100%')
       .attr("height", 1);

	.selectAll('rect')
	.data(myData)
	.join('rect')
	.attr('x', 1)
	.attr('y', function (d,i) { return (i+1)*2; })
       .attr("width", function (d) { 	return timeScale(d);})
 	.attr('y', function (d,i) { return (i+1)*2; })
        .attr("height", 1);

So if you want the rectangle to start at a point on graph equivalent to a certain period of time, what you’re actually trying to define is the x position of the rectangle (which defines the left-right position by defining the left position to start drawing the rectangle at). Where is that point? well, thats what your timescale’s for, right? Translating a time into a distance on the graph?

As for the width… well, if the width of the rectangle is to be “from the point it is created, until the end of the graph”, and the whole length of the graph is (as defined by your timescale) 900, then the width of the rectangle must be 900 minus the distance from 0 to the starting point of the rectangle; to put in mathematical terms, if the total width of the graph is 900 pixels, the starting point of the rectangle (in pixels) is s, and the width is w, then s + w = 900. Solve for w.

Ok, so for solving that equation, I think it goes back to the idea you mentioned in this post:

He @m_hutley ,

So here is what I tried - in my new JS Fiddle by starting the date from 5/2/2018 00:00 by introducing var newData = getDaysArray(new Date("5/2/2018 00:00"),new Date("5/5/2018 00:00")); but it seems to have deleted the original rectangular graph which was there before using myData variable.
My goal is to start the blue rectangular graph on top of the existing graph from Wed May 02,2018 and all the way until the end. Similarly I want the other graphs below the first line to start from Wed May 02,2018 but they seem to have shifted forward as well.

. It seems to be behaving like this:

okay, so you want to put a second set of rectangles down.

So you need a new grouping (<g>) element to put them in. #wrapper is full of your black rectangles. Create a new element to house them, select it, and then fill it with your blue rectangles.

.selectAll('rect') will find all rects within the current selection scope, and replace them; so if you want different kinds of rects, you’ll need them grouped independantly so your can select the group, and then modify the rects inside that group.

Okay, so here is my updated JSFiddle where I was able to have blue color rectangles over black ones. another place where I’m stuck is the following:

On first rectangle only, I want to draw the rectangle from the date 5/2/2018 and end at 5/3/2018. So for that, I made some changes:

Change 1:

Defined following variables:

const start = new Date('5/2/2018')
const end = new Date('5/3/2018')
const x1 = timeScale(start)
const x2 = timeScale(end)

In the code snippet below,if I put x1 over here .attr('x', 1) so that it looks like .attr('x', x1) , the whole blue color rectangle starts from x1 for all the dates (for all rectangles) which makes sense but I can’t figure out where to put x2 in this scenario. I’m basically looking to have blue color rectangle on the very first rectangle which should start from 5/2/2018 and end at 5/3/2018 . Basically start at x1 and end at x2

var newData = getDaysArray(new Date("5/1/2018 00:00"),new Date("5/5/2018 00:00"));
 d3.select('#wrappernew')
    .selectAll('rect')
    .data(myData)
    .join('rect')
  .attr('x', 1)
/*  .attr('x', function(d) {
      return timeScale(d);
    }) */
    .attr('y',  function (d,i) {
            console.log("Printing i below");
            console.log(i);
            console.log("Printing d below");
            console.log(d);
            return (i+1)*7; 
        })
  .attr("fill",'blue')
 .attr("width", '100%')
 .attr("height", 1);

Basically, I want the blue color rectangle on the first rectangle only in the highlighted (in yellow color) range shown in the picture below:

Any idea if I’m heading in the wrong direction above?

So, lets think about this idea from a rectangle perspective.

A <rect> is defined by 4 values:
x: The “left” component of the top-left corner.
y: The “top” component of the top-left corner.
w: The width of the rectangle, starting at the top left corner.
h: The height of the rectangle, starting at the top left corner.

Your statement to me is “I want a rectangle between this X value (x1) and this other X value (x2)”.
Keep in mind that all X values are defined from 0 on the left; what is the value of:
the start point of your rectangle?
the width of your rectangle?
Answer using only the values x1 and x2. They are all you need to define these two values.

If it’s not intuitive, try this:

If X1 is 12, and X2 is 18, how wide is my rectangle? How did you get that value?

So in this case, since the start point is 5/1/2018, x value is zero here. I verified it by using the following :

const test = new Date ('5/1/2018 00:00');
const testX = timeScale(test);
console.log(testX);

Similarly, I verified x1 and x2 by printing the values fr dates 5/2/2018 and 5/3/2018 respectively:

console.log(x1); // Prints 175
console.log(x2); // Prints 350

Since I wanted to start my rectangle from x1, the start value of rectangle is 175 and end value is 350.

So 350 - 175 will give me the width of the rectangle which is 175, right?

The width of the rectangle in your example will be 18-12 = 6.

Does this look correct?

I slightly modified my JSFiddle to start the first rectangle from the date 5/02/2018 as shown in my updated JSFiddle here

This is the part that I modified for the colored rectangle group in the above updated JS Fiddle :

.attr('x', function(d,i) {
               if (i == 0){
                  return x1
                 }
                 else
                 return 1;
	})

Since I have to stop it at x2, which is 350 in value, is there a way I can involve x2 inside the above function? That’s confusing me. One thing that comes to my mind is to modify the .data(myData) part for only scenario where i==0 such that it only accepts the values until the date where x2 is defined and for rest of the rectangles below, it used the original dates. But can I modify the data inside above function?

x2 does not relate to the x value of the rectangle, so it wont go into the function for declaring the x value of the rects.

x2 relates to the width of the rect, width, in that w = x2 - x1 .

image

Your timescale function will give you pixel coordinates for a given input; so it’s giving you X values.

Thanks. This finally worked for me . Updated JS Fiddle : https://jsfiddle.net/walker123/qs93yzec/116/

I didn’t think of modifying width parameter of `rectangle as I was getting confused with an x-xoordinate for the end point.

Quick question on the above function. Does it only accept d (which is for the date) and i (which is for the rows) or any more parameters as well?

So what it does is a foreach, feeding the data point (d) and index (i) to the function. That function is scoped, but you can reference any variables from above that scope as per normal. You dont need to pass them as parameters.

Note the example on the D3.js - Data-Driven Documents (d3js.org) homepage at the bottom:

d3.selectAll("circle").transition()
    .duration(750)
    .delay(function(d, i) { return i * 10; })
    .attr("r", function(d) { return Math.sqrt(d * scale); });

scale is a variable outside the scope of the function, so it will rely on a scale being defined elsewhere in the code before that point.

1 Like

Hey @m_hutley ,

I am working on a slight variation of the JSFiddle now and have a question for you. I’ve put all of my d3 related stuff inside a makeTimeLine function and I’m passing a variable in the function function makeTimeLine (text). The value is the value from the JSON data for each s1 key as shown in the JSFiddle here

In the following code snippet of fiddle:

I want to print Object1 on the first rectangle, Object2 on the second rectangle etc until Object5 on the 5th rectangle. But the problem I see here is that I’m getting the values from the JSON object one by one since it’s coming through a for loop which is outside the function. I was attempting to do the following, however, putting .data(text.s1) doesn’t seem to work. In case of rectangles, we have the myData variable as an array but here we are getting the values one by one. How should I go about handling this scenario?

Here is my code snippet from JS Fiddle.

//Start: Attempt to put text on each rectangle


 d3.select('#wrapper')
  .selectAll('text')
  .data(text.s1)
  //.data(myData)
  .join('text')
  .attr('x', function(d) {
    return timeScale(d);
  })
  .attr('y', function (d,i) { return (i+1)*24; })
  
 /*   .text(function(d) {
    return d.toDateString();
     });   */
 //End: Attempt to put text on each rectangle

Thanks for your time.

So, again, keep in mind that the idea is to walk through your data exactly once.

What you’re currently doing is wrapping the chart creator in a for loop thats trying to take the array and run the chart creator once for each key.

Instead, pass the entire array into your function, and iterate through it using d3. (PS: Your JSON isnt a JSON at this point - it’s a standard Array of Objects.

for (var key of Object.keys( printObjectsJson)) {
     makeTimeLine(printObjectsJson[key])
}

=>

     makeTimeLine(printObjectsJson)

and

 d3.select('#wrapper')
  .selectAll('text')
  .data(text)
  ... (etc)
  .text(function (d) { return d.s1; })

Note that each iteration is handed the full object, so we need to return the text of the object (s1).

Is this json going to be coming from an external source? If not, you might want to simplify and combine your data sources in your code.

Also; you cant put comments in the middle of your D3 chain. If you put a comment in the middle, javascript has a wobbly and refuses to process.

Thanks. It’s working in my updated JSFiddle. I cleaned up the comments as well. I had the feeling of passing all at once but I was not sure about this part .text(function (d) { return d.s1; }), Thanks for clarifying.

Yes, at some point. For now, in my actual code, I have a json file which I’m importing it into my code.

One more important question:

Is it possible to have pagination kind of feature using D3 or with JavaScript & D3? For example, I may have 100 or more timestamp based rectangles and I don’t want to show all at once but maybe first 10 or 20 so can there be something like next button feature which will take me to next set of rectangles? Does something like this exist?

Well, the simplest way would be to handle that by trimming your data set.

So lets say you’ve got 100 data points in an array.

If you hand the whole thing to d3 and say “here’s my data, go for it”, it’ll render all 100… rects, or circles, or whatever you’re drawing.

Lets look at your example currently.

var printObjectsJson=[{
		"s1": "Object1"
	},
	{
		"s1": "Object2"
	},
	{
		"s1": "Object3"
	},
	{
		"s1": "Object4"
	},
  {
		"s1": "Object5"
	}
]

Lets imagine there’s a lot more data there. But its in the object.

Right now, you say:

makeTimeLine(printObjectsJson)

“Here, have the whole thing, make a timeline out of it.”

What if, instead, I said…

makeTimeLine(printObjectsJson.slice(1,3)) ?

Now i’m only handing the 1th (yes, 1th, remember an array is 0-indexed) and 2th elements of the big dataset to the function. So if your function builds the graph based on the data its been handed, it will only show that portion of the data.

Thanks. So makeTimeLine(printObjectsJson.slice(1,3)) and other variations will only give me control over displaying number of s1 values. In this scenario, the rectangles are still showing. I was wondering if rectangles can also be controlled in the similar manner.

Well, lets think it through.

Right now, your rects are being created by myData.

But they dont have to be.
Look at your rects right now:

d3.select('#wrapper')
  .selectAll('rect')
  .data(myData)
  .join('rect')
  .attr('x', 1)
  .attr('y', 
       function (d,i) {
             return (i+1)*24; 
        })
 .attr("width", '100%')
 .attr("height", 1); 

The only bit of myData you’re using to create the rects, is the index, or the number of items in myData.

If you want there to be as many rects as there are elements handed to the function… base your rects off of that instead.

Thanks, @m_hutley. I’m working on a different scenario/version of the problem now based on the updated JS Fiddle here. My JSON data has changed to the following now:

var printObjectsJson=[{
		"ID":"1",
    "s1": "Rectangle 1",
    "Parent tuple": "0",
    "Relationship": "has_rectangle",
    "Timestamp": "5/1/2018 00:00",
	},
	{ "ID":"2", 
		"s1": "Rectangle 2",
    "Parent tuple": "1",
    "Relationship": "has_rectangle",
    "Timestamp": "5/2/2018 00:00", 
	},
	{ "ID":"3", 
		"s1": "I'm a Circle 1 ",
    "Parent tuple": "6",
    "Relationship": "has_value",
    "Timestamp": "5/1/2018 00:00", 
  },
	{ "ID":"4",
		"s1": "I'm a Circle 2",
    "Parent tuple": "7",
    "Relationship": "has_value",
    "Timestamp": "5/2/2018 00:00", 
  },
  { "ID":"5",
		"s1": "I'm a Circle 3",
    "Parent tuple": "8",
    "Relationship": "has_value",
    "Timestamp": "5/4/2018 00:00", 
	},
  { "ID":"6",
		"s1": "Rectangle 3",
    "Parent tuple": "1",
    "Relationship": "has_rectangle",
    "Timestamp": "5/3/2018 00:00",
	},
  { "ID":"7",
		"s1": "Rectangle 4",
    "Parent tuple": "2",
    "Relationship": "has_rectangle",
    "Timestamp": "5/4/2018 00:00",
	},
   { "ID":"8",
		"s1": "Rectangle 5",
    "Parent tuple": "1",
    "Relationship": "has_rectangle",
    "Timestamp": "5/5/2018 00:00",
	}
  
]

The JSON data for ID 3, 4, and 5 are basically for plotting circles on the graph and the location of the circle will be determined based on the Timestamp field and the rectangle on which it needs to be present is determined based on the Parent tuple value of the data. The value of Parent tuple corresponds to the value ID. For example, in the following data:

{ “ID”:“3”,
“s1”: "I’m a Circle 1 ",
“Parent tuple”: “6”,
“Relationship”: “has_value”,
“Timestamp”: “5/1/2018 00:00”,
},

Since it says Parent tuple: 6, the circle belongs to the rectangle where ID is 6. So in the above example, the circle must be drawn on the rectangle with following data:

{ “ID”:“6”,
“s1”: “Rectangle 3”,
“Parent tuple”: “1”,
“Relationship”: “has_rectangle”,
“Timestamp”: “5/3/2018 00:00”,
},

I’ve been able to draw the circle based on the filteredCircle data. However, it is coming out like this as shown in the JSFiddle above:

Above circles are only getting plotted based on the Timestamp value of the filteredCircle data. How can I plot it on the rectangle where it actually belongs? Please let me know if I can clarify anything. Thanks for your time.