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

Please find my JSFiddle here:

I’m trying to display two rectangles one below another and my ultimate goal is to display them dynamically

2 Likes

Well right now, you’re just telling all the rects to be at coordinates 1,10.

Where do you want them to be?

EDIT: Let me phrase that better.

Give me an example, on a coordinate plane, where you want the first… 5 values of your data to lie, keeping in mind what x and y mean for a rect.

1 Like

Hi @m_hutley ,

Thanks for your reply. So right now one of the coordinate is 1,8 and another is 1,10 which I believe is wrong?

Let’s say the first rectangle is the one at the top and I want another one below it at a distance. For example something like this as shown in the image below. Coordinates can be anything such that it shows like the following. I intend not to show dates like that but I will show dots later one corresponding to the timestamp.

1 Like

So I think what you’ve got here is a fundamental misunderstanding of the .data command.

d3.select('#wrapper')
	.selectAll('rect')
	.data(myData)
	.join('rect')
	.attr('x', 1)
	.attr('y', 8)
 .attr("width", function (d) { 	return timeScale(d);})
 .attr("height", 10);

So what this says is "select the rects inside #wrapper. Transform that set, so that it matches the following:

  1. Walk through the set of information myData. All elements of the data (and only elements of that data) should be represented.
  2. If you need to create a new element for one of these data points, make it a rect.
  3. All rects should have x coordinate 1.
  4. All rects should have y coordinate 8.
  5. Each rect should determine its width based on the function provided, with d as its associated data point.
  6. All rects should have height 10.

This code, then, is looping through your data, applying these transformations to all data points.

So, rather than executing the code multiple times, we need to execute it exactly once (or once for the rects and once for the text, but you get the idea), but position the elements properly according to your want. This will almost certainly require a function, either for the X or Y coordinate. This is why I asked for what you would want the first 5 data points of your rects to be.

Thanks for the detailed explanation. I’m new to D3 so that definitely helps me in understanding it better.

So for the coordinates, I believe the following might produce enough distance between each other. For example,

Since I want the rectangles on top of each other, only y coordinate should change and x can remain as it is.

Let’s say 1st x,y coordinate is : (1,8)
2nd x,y coordinate is : (1,16)
3rd x,y coordinate is : (1,24)
4th x,y coordinate is : (1,32)
5th x,y coordinate is : (1,40)

I want the width and height of the rectangle to remain the same. Please let me know if that’s what you were looking for and what I mentioned above makes sense. Thanks again !

So that makes sense.
I will say keep in mind 2 things:

  • your rects are 10 pixels in height, and you’re telling me you want their top left corners spaced 8 pixels apart, so you’re overlapping a bit.
  • your overall SVG is only 80 pixels high, and your wrapper is translated 40,40, so you’re going to run out of space to see your rectangles very quickly. Like… after 3 of them.

So, we need a function for Y, such that it starts at 8, and increases by 8 each time.

Second little tidbit about the functions you can supply to attrs; they have a second optional parameter, index. So, to give you exactly what you want, the following would be the solution:

  1. Remove the “second time” code entirely. We only need 1 execution to do everything we want.
  2. Set the y attribute to the following: function (d,i) { return (i+1)*8; } (Indexing starts at 0, so we need to add 1).

Applying this function to your code, and looking at the source of the result, I see the following:

Thanks. I made that change and it is looking like the following now. I am actually using 2 in the function like this : function (d,i) { return (i+1)*2; }) and also reduced the height to 5 in a hope it would look somewhat better.

If I have to achieve something like the one I mentioned above , do I need to make changes to my SVG width and height?:

Also, how far i is running in the function? For example, I would like to limit the equally spaced number of rectangles based on the value provided by the user, so it could be 5 or 10 or more, etc. Is the function going to handle this?

.data() runs across the entire set provided to it inside the parenthesis. So if your line is .data(myData), the code will run until i = myData.length - 1.

If you want the data to fit in your SVG, you have a few options:

  1. limit the amount of data in myData.
  2. resize the SVG to accomodate the size of myData * height + whatever margin you apply in the form of translating your wrapper
  3. reduce the height of the rects to fit the data into the current available size of the SVG.

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.