preface

My last post “Some good D3.js resources from Amway – Niuyi Ancient Willow 2021.06.29” received a good response. I remember a new group member said that the supervisor pushed the article to her and added it, which was also amazing.

Blink of an eye and it hasn’t been updated for another month. In fact, I always wanted to write a simple d3.js introductory article/tutorial, but I always wanted to write it in a comprehensive, detailed, interesting, accessible way, and even if I could mark Daniel Shiffman’s output in Processing, p5.js, etc. It would be nice to really make d3.js visualization a smoother entry for more people. Related reading: With p5.js into the pit creative programming – Niuyi Ancient Willow 2019.06.28

Ideal is full, reality is very skinny. Gu Liu’s own level is not enough to mention, so far has not accumulated many cases to support the realization of the above goal, and often because of a period of time without contact with D3.js, forget all, again use their own difficulties, how to talk about output tutorial?

It was frustrating to say that Gu felt that he had nothing valuable to offer in terms of visualizations, and if he couldn’t even write an introductory tutorial, he really didn’t know what else to do.

But it is not good to have been dragging on, still heart unwilling. Even if you can’t produce a comprehensive and easy-to-understand tutorial right from the start, you may not be able to achieve your goal in many places, but let’s go ahead and see what you can write. Optimization iteration and so on after the output is also too late.

Thus had this article, with the first article in this series, as for this series can write how much, what will be written, old liu also completely don’t count in the heart, let time to tell, while the other is heading for a beginner can easily understand the goals, but really you finish see what it feels like to think, old liu also not clear, so I hope you great feedback, What can be improved in subsequent articles will continue to be improved.

This series of supporting code and the data used will be open source to this warehouse, welcome everyone Star, other questions can be exchanged in the group: github.com/DesertsX/d3…

The body of the

Basic code structure

First, the code structure. The div element with id “chart” will be used for the SVG canvas added later. Introduction of the d3.js library downloaded locally (v5.9); The JS part is the focus of this code and is implemented in the drawChart() function. In addition, THE CSS style is mainly used for the subsequent canvas can be full screen without blank space.

<! DOCTYPEhtml>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
    <title>D3. Js tutorial</title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }

        html.body {
            overflow: hidden;
        }
    </style>
</head>
<body>
    <div id="chart"></div>
    <! -- You can download it locally or reference it online -->
    <script src="./d3.js"></script>
    <! -- < script SRC = "https://cdn.bootcdn.net/ajax/libs/d3/5.9.7/d3.js" > < / script > -- >
    <script>
        function drawChart() {
            // code
        }

        drawChart()
    </script>
</body>
Copy the code

Adding an SVG Canvas

The JS code below is drawn in drawChart().

D3.js for visualization, you can use vector SVG, you can also use scalar graph, pixel canvas, because ancient liu SVG used more, here as an example.

Visualization is simply a process of mapping data into visual elements and placing them on the canvas in a specific way. Visual elements can be circles in scatter plots, rectangles in bars and histograms, lines in line plots, etc. The core of layout is knowing the X /y coordinates of each element, which can be calculated by yourself or generated by the many layout functions that come with D3.js.

Using rectangles as an example, let’s take a look at some uses of d3.js.

The first thing you need is an SVG canvas to hold the following visual elements, in fact titles/axes/legends, etc., which may not be used here, but will be covered later.

Select the chart div element with the id as chart (d3.select()).

Add the SVG element via Append, and set the width and height of the SVG element and the background color. For demonstration purposes, set it to the full height and half width of the browser page window. You can also fill the page window, or use a fixed size such as 900*600, etc., depending on your needs.

const width = window.innerWidth
const height = window.innerHeight

const svg = d3.select('#chart')
    .append('svg')
    .attr('width', width / 2)
    .attr('height', height)
    .style('background'.'#FEF5E5')
Copy the code

Window. innerWidth and window.innerHeight are the width and height of the web window when it is open at a certain size, as shown in the red box, and you can see that the canvas is half the size.

Canvas set, can add visual elements inside, just like many tools/software comes with some basic graphic elements, SVG has circle/the rect/the ellipse/polygon/line/common elements, such as the path/text and each element can set corresponding properties, Such as position, width, height, radius, color, stroke, transparency, etc. (images taken from Fullstack D3), are not complicated.

Now we will draw a rectangle /rect on the canvas, again append with the name of the element, and set the x/ Y position (the top left corner of the rectangle, not the center), width and height (the number is in pixels, for example 100 is 100px) and color.

It should be noted that the origin of the cartesian coordinate system is in the upper left corner of the web page window, the horizontal right is the positive X-axis, and the vertical down is the positive Y-axis.

svg.append('rect')
    .attr('x'.30)
    .attr('y'.50)
    .attr('width'.50)
    .attr('height'.100)
    .attr('fill'.'#00AEA6')
Copy the code

The corresponding HTML generated in the browser looks like this.

<svg width="518.5" height="680" style="background: rgb(254, 245, 229);">
    <rect x="30" y="50" width="50" height="100" fill="#00AEA6"></rect>
</svg>
Copy the code

If the rectangle is drawn at the edge of the canvas, it is not visible beyond the canvas. So if you have too much data, you need to wrap it, and I’ll show you how to do that later.

svg.append('rect')
    .attr('x', width / 2 - 25)
    .attr('y'.50)
    .attr('width'.50)
    .attr('height'.100)
    .attr('fill'.'#EB5C36')
Copy the code

The above shows how to add one element, but more often we need to add multiple elements based on the data set. How do we do this?

It might occur to someone to iterate over the loop data to add elements…… Well, it’s not all bad.

Constructing simple data

Here d3.range(20) is used to simply construct an array containing 0-19 numbers — [0, 1, 2…, 19] — as the demonstration data set;

const dataset = d3.range(20)
console.log(dataset) // [0, 1, 2... 19]

const colors = ['#00AEA6'.'#DB0047'.'#F28F00'.'#EB5C36'.'# 242959'.'#2965A7']
Copy the code

Six colors are prepared to simulate the visualization of mapping a certain type of attribute to different colors. Color matching from this figure, very nice, but the ancient willow meditation selected!

For example, if the first color is colors[0], the index starts at 0 and ends with the array length reduced by 1, i.e. Color.length-1, the corresponding color is colors[color.length-1]. Are relatively basic JS.

Iterate over the data to add elements

This can be done by iterating through the data to add an element, or by using a for loop. Here, we can simply iterate through each element with forEach. D is each number from 0 to 19. I can’t show it all because it’s going to be out of the canvas.

dataset.forEach(d= > {
    svg.append('rect')
        .attr('x'.20 + d * 70)
        .attr('y'.20)
        .attr('width'.50)
        .attr('height'.100)
        .attr('fill', colors[d % colors.length])
})
Copy the code

Each rectangle color is indexed by the remainder of the length of the color array, and then the color is taken from the color array. It’s a very useful operation to take a number and take a mod, and it’s going to happen a lot. Here are some examples of how to take a mod.

0 % 6= >0 => colors[0]
1 % 6= >1 => colors[1]
2 % 6= >2 => colors[2]...5 % 6= >5 => colors[5]
6 % 6= >0 => colors[0]
7 % 6= >1 => colors[1]...19 % 6= >1 => colors[1]
Copy the code

D3.js adds elements based on data

Returning to the blank canvas, the code below achieves the same effect as the loop above.

While it is sometimes possible to iterate over data to add elements, it is not usually done this way. A more general, d3.js approach is to add elements based on data with aset of commands.selectall (‘rect’).data(dataset).join(‘rect’).

const rects = svg.selectAll('rect')
    .data(dataset)
    .join('rect')
    .attr('x'.d= > 20 + d * 70)
    .attr('y'.20)
    .attr('width'.50)
    .attr('height'.100)
    .attr('fill'.d= > colors[d % colors.length])
Copy the code

I think a lot of people, when they first come into contact with this approach, will find it strange? To draw a rectangle with data, selectAll(‘rect’) is used to selectAll rectangles. .data(dataset) binds the dataset to the selected element. .join(‘rect’) is the operation that actually adds elements.

Then the attribute of each element is set through the callback function, where D is the data of each item in the dataset. Properties with fixed values can be written to death without function writing.

I don’t want to explain too much here, but the real reason is that guliu can’t explain it well, and it involves a set of concepts such as enter-update-exit (also taken from fullstack D3). Many people are probably confused by these concepts when they get started, so just remember that this is a common operation and important. Bind data to draw elements frequently, and remember these three sentences.

.enter().append() is the old version of d3.js. Use.join() instead. Function () {} => function() {} =>

const rects = svg.selectAll('rect')
    .data(dataset)
    .enter()
    .append('rect')
    .attr('x'.function (d) {
        return 20 + d * 70
    })
    .attr('y'.20)
    .attr('width'.50)
    .attr('height'.100)
    .attr('fill'.function (d) {
        return colors[d % colors.length]
    })
Copy the code

Adjust layout, line feed display

In the example above, the rectangles are arranged in a single line, and the data will exceed the canvas as much as possible. Then adjust the layout to make the display look like a line break.

The formula for x-coordinate is 20 + d * 70, and we want the last rectangle in each row to be inside the canvas, so the x-coordinate plus the width of the rectangle should be less than the width of the canvas. So we can figure out how many rectangles we can put in a row, so we’ll call it col_num, and notice that the NTH element here is actually n-1 for d, because d starts at 0, and the element really starts at the first element.

/ / formula
20 + (col_num - 1) * 70 + 50 <= witdh / 2

/ / is equivalent to
col_num <= witdh / 2 / 70
Copy the code

Because the division has decimals, we need to take an integer down here, either math.floor () or parseInt.

const col_num = parseInt(width / 2 / 70)
// const col_num = Math.floor(width / 2 / 70)
console.log(col_num)
Copy the code

Once you have the number of columns, you can go ahead and compute the x/y coordinates of each element using the lump-mod operation mentioned above, which essentially means that you need to know which column and which row each element is in.

const dataset = d3.range(50)

const rects = svg.selectAll('rect')
    .data(dataset)
    .join('rect')
    .attr('x'.d= > 20 + d % col_num * 70)
    .attr('y'.d= > 20 + Math.floor(d / col_num) * 120)
    .attr('width'.50)
    .attr('height'.100)
    .attr('fill'.d= > colors[d % colors.length])
Copy the code

For example, the position of the element in each row is obtained by d % col_num and calculated to the x coordinate. The position of the element in each column is rounded by math.floor (d/col_num) and computed to the y coordinates. If you’re a beginner, you can sort it out again.

It should be noted that the dataset is changed above to generate 50 data from 0 to 49, so as to fill the canvas as much as possible. So far, all the data has been well drawn on the canvas by using the mod and round operation.

But what happens when there is more data and the maximum height is exceeded?

Maybe you can reduce the width and height of the rectangles and adjust the spacing step by step. (Here gu Liu is not adjusted, mainly to introduce the question)

const dataset = d3.range(100)

const rects = svg.selectAll('rect')
    .data(dataset)
    .join('rect')
    .attr('x'.d= > 20 + d % col_num * 70)
    .attr('y'.d= > 20 + Math.floor(d / col_num) * 120)
    .attr('width'.50 / 2)
    .attr('height'.100 / 2)
    .attr('fill'.d= > colors[d % colors.length])
Copy the code

But can you automatically calculate the width, height and spacing of each RECT based on the data size and canvas width, and then automatically lay it out?

Just as Gu Liu before eating the Atlantic codex visualization source code to see how to solve the above problems, will share with you in the next article, more d3.js content will continue to explain in the next article, please look forward to.

Related reading: The Most complex visualization ever reproduced: Codex Atlanticus (PART 1) – Niuyi Ancient Liu 2021.06.17

In the future, welcome to “visual communication group” to communicate together, Jia Gu Liu wechat “xiaoaizhj” remarks “visual plus group” pull you into the group ha!

Welcome to follow gu Liu’s official account “Niu Yi Gu Liu”, and set a star tag so that you can receive updates as soon as possible.