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

Ok, I will try to play with fewer data. Also, I was wondering what is controlling the color of the rectangle? Is it because I haven’t specified the color attribute while defining the rectangle, it’s taking a default color of black?

an SVG rect can take a fill attribute.

1 Like

A slightly different question which I’m trying to understand.

Can you help me understand how myScale(2) is generating 120 in the example shown below. Similarly I don’t get myScale (3) = 180 etc. How are they plugging in the data values 0,2,3,5 etc in the myScale function?

Pasting relavent snippet from their documentation:

For example, suppose you have some data:

[ 0, 2, 3, 5, 7.5, 9, 10 ]

you can create a scale function using:

let myScale = d3.scaleLinear()
  .domain([0, 10])
  .range([0, 600]);

D3 creates a function myScale which accepts input between 0 and 10 (the domain) and maps it to output between 0 and 600 (the range).

You can use myScale to calculate positions based on the data:

myScale(0);   // returns 0
myScale(2);   // returns 120
myScale(3);   // returns 180
...
myScale(10);  // returns 600

So if the domain (input) of the scale is 0…10, and the range (output) of the scale is 0…600, and the scale is Linear, then you can write this scale as a function:

for 0 <= X <= 10:
scale(x) = 60x

so scale(2) is 120. scale(2.5) is 150. scale(0.3) is 20.

A linear scale is just that. For a given input (x…f), and a given output (y…g), you can construct a line from the points (x,y) (f,g); the equation of that line defines all points between x and f. d3 will also use that function to scale beyond the scope of your domain, though its not recommended you do; it’s also why the clamp option is available for your scale object.

How are they plugging in the data values 0,2,3,5 etc in the myScale function?
Well, in the example, they’re explicitly calling it. scaleLinear returns a function (sort of. its more like an executable object, but… confusion lies that way.), which contains the parameters necessary to evaluate a numerical input.

How would you apply it in most circumstances? Go back to your previous example, and look at how they calculate the width…

Coming back to scaleTime example that they have:

scaleTime

scaleTime is similar to scaleLinear except the domain is expressed as an array of dates. (It’s very useful when dealing with time series data.)

timeScale = d3.scaleTime()
  .domain([new Date(2016, 0, 1), new Date(2017, 0, 1)])
  .range([0, 700]);

timeScale(new Date(2016, 0, 1));   // returns 0
timeScale(new Date(2016, 6, 1));   // returns 348.00...
timeScale(new Date(2017, 0, 1));   // returns 700

My Observation:

Domain of input is new Date(2016, 0, 1)new Date(2017, 0, 1) and the range (output) of the scale is 0…700, and the scale is similar to scaleLinear, I could write it like the following?

for new Date(2016, 0, 1) <= X new Date(2017, 0, 1)
timeScale(x) = 700X

So how are they coming with timeScale(new Date(2016, 6, 1)); = 348.00 here? Is it like the new Date(2016, 6, 1)); gets converted to milliseconds first, then gets multiple by 700? It’s confusing.

So… it… depends a bit on what their unit of measure is, but we could derive it.

my money would be on a timestamp (seconds since epoch). but lets see if we put it in terms of days:

We take for granted that our domain starts at 0. We can arbitrarily say that January 1, 2016 = 0.

If January 1, 2016 is 0, what is January 1, 2017? Well 2016 was a leap year, so given the previous value is 0, January 1, 2017 would be 366.

The range is specified to be 0…700.

So we have two points: 0,0 and 366,700.

It may have been a while since i did line calculations, but the slope of the line would be 700/366 ((y2-y1)/(x2-x1)), or 1.912568306.

Our Y-intercept is 0 (because we have a point at 0,0), so the equation y = mx + b of our line is 1.912568306x + 0, or just 1.912568306x.

if we take the date provided, July 1st, and translate it to our days-since system, it would be 182. If we put 182 into the equation for our line, we get 1.912568306 * 182 = 348.087431692.

May I ask why did you go this route of calculating slope and then substituting its value in the line equation? Based on my previous example understanding of scaleTime, shouldn’t it be straightforward to do timeScale(x) = 700X and calculate the answer?

Thanks for your great explanation above.

the idea is that when you put the highest value of the domain into the equation, you get the highest value in the range.

if the equation is 700x, and my domain is 0…366… what value do i get when i put the highest value in?

That would be 700 * 366?

and does that (256200) fit with the range of 0…700?

Yeah, that’s way out of that range.

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?