i2djs for dummies

i2djs for dummies

Table of contents

No heading

No headings in the article.

Disclaimer: This post is mainly intended for people who are somewhat familiar with web dev in general as well as integral components of web graphics like svg, canvas, etc.

For introduction, i2djs is a library which helps in rendering different kinds of elements from svg, canvas and webgl land in your browser window. With the help of i2djs, it is very easy to switch between canvas, svg and webgl contexts. i2djs gives you native svg elements like rect, circle etc. for all the three different contexts. It also supports events and behaviours for all the three different contexts to create more interactive visualisations.

In this post, my main goal is to make you familiar about different methods available in i2djs through a simple example as well as show how easy it is to get started and maybe in my next post, I will try to make that same replica of whatever we do in canvas today both in svg and webgl as well.

So, let us get started. If you would like to see the final output, I would suggest you to checkout the codepen, otherwise follow along with me so that we can look into what i2djs has to offer.

First of all, we need to create a basic html page with some css sprinkled in it and point it to i2djs, you can just copy the below snippet

<!DOCTYPE html>
<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>barchart</title>

    <script defer src="https://i2djs.github.io/I2Djs/dist/i2d.js"></script>
    <script defer src="index.js"></script>
    <style>
      *,
      *::after,
      *::before {
        margin: 0;
        padding: 0;
        font-family: "Courier New", Courier, monospace;
      }
      html,
      body {
        height: 100%;
        width: 100%;
      }
      #myCanvas {
        height: 100vh;
        width: 100%;
        background-color: black;
      }
    </style>
  </head>
  <body>
    <div id="myCanvas"></div>
  </body>
</html>

As you can see in the above snippet, I have added a script tag pointing to an index.js file where we will be adding our code related to i2djs and in html we have added a div with the id attribute as myCanvas where i2djs will attach a container like canvas in our case. Eventually, whatever we do will be rendered in that container element (canvas in our case).

So let us now create a canvas layer where our elements will reside. You might be thinking "why do we need a layer in the first place?". Well, the thing is that we can create different kinds of layer based on the use cases, we can put one layer on top of the other. We can combine multiple layers of different contexts to build composite scenes and the layers will be stacked in the order of their creation. i2djs at present provides three layers i.e canvasLayer, svgLayer and webglLayer which respectively represents canvas, svg and webgl semantics (duh). So, now just copy the below code and paste it in index.js file.

let renderer = i2d.canvasLayer("#myCanvas", {}, {});

So, what we are doing in the above code is that we are telling i2djs about the type of layer which we are gonna use as well as the containerId (#myCanvas) where we want the canvas to be rendered.

Similar to how d3.js works with data, i2djs also supports data binding for rendering graphical elements. So, let us first add some custom data, just copy and paste the below code in index.js file after renderer declaration.

let data = [
  {
    id: 1,
    value: 23
  },
  {
    id: 2,
    value: 30
  },
  {
    id: 3,
    value: 40
  }
];

So now since we have the data, we can use that to create and interact with graphical element like circle in the canvas layer using data join actions (enter, exit and update) provided by i2djs. Before that we need to write some code which will help i2djs to uniquely identify a part of data and bind it to a graphical element. So, just copy and paste the below code after data declaration.

let joinRef = renderer.join(data, "circle", {
  joinOn: function (d, i, n) {
    return d.id;
  },
  action: {
    enter: function (data) {},
    exit: function (nodes) {},
    update: function (nodes) {}
  }
});

So, what we are doing in the above code is that we are telling i2djs about the data as well as the elements which needs to be bound with that data. In our case, circle element will be bound with the individual data elements (objects) and since i2djs needs a unique key to identify and bind data to the graphical element, so to indicate the uniqueness in our data in this example, we will be using id inside the joinOn function, to help i2djs do its magic. Along with that, we have added an action object which contains enter, exit and update functions. These functions helps us to render elements in the canvas. I will be explaining their use cases soon.

After seeing the black screen for so long, finally let us add some circles. Copy the below code and paste it inside the enter function.

      this.createEls(data.circle, {
        el: "circle",
        attr: {
          cx: function (d) {
            return d.id * 100;
          },
          cy: renderer.height / 2,
          r: 20
        },
        style: {
          fillStyle: "orange"
        }
      });

Hopefully, at present, you will be able to see some orange circles in your screen. As a short explanation, we are creating circle elements based on the data that we have specified earlier. We are calculating individual circle's attributes as well as giving it some styles inside the enter function.

I feel that the code is somewhat intuitive and I would suggest you to tinker the code by modifying the attributes and styles of the circle and see how it affects the output in the browser.

Now, just trust me and modify the update and exit functions with the below code. I will show their usecase soon.

    exit: function (nodes) {
      nodes.circle.remove();
    },
    update: function (nodes) {
      nodes.circle.setStyle("fillStyle", "red");
    }

Now, just copy the below code and paste it below the place where you declared joinRef

setTimeout(() => {
  joinRef.push({ id: 4, value: 70 });
}, 1000);

At present, you might be able to see a new circle in an orange colour after a delay of atleast 1 second. So what happened?? Well, i2djs saw that we had pushed some new data element and hence it triggered the entry action with the data which we added, so we got a new circle added to the dom.

Now, copy the below code.

setTimeout(() => {
  joinRef.pop();
}, 2000);

The above code should remove the newly inserted data element which you had just inserted. What happened behind the scene was that the exit function was triggered which consequently deleted the node representing the respective circle from the dom.

Now, would you like to maybe make the circle bigger, or maybe change its radius, you can do all these stuffs in update action and trigger it using the below code. But for now, we are just changing the colour to red

setTimeout(() => {
  joinRef.update();
}, 3000);

Now, suppose if you would like to remove multiple elements from the dom, which is not possible using pop, for those scenarios you can just refer the below code.

setTimeout(() => {
  joinRef.remove([data[1], data[2]]);
}, 4000);

What we are doing in the above code is that we are removing two elements based on their index in our provided data. As an important thing to keep in mind is that we need to provide elements which we are gonna remove through reference and not by value.

As an example, below code is not gonna work.

setTimeout(() => {
  joinRef.remove([
    {
      id: 2,
      value: 30
    },
    {
      id: 3,
      value: 40
    }
  ]);
}, 4000);

Now, let us suppose, we want to manipulate the data i.e. remove some objects and add some other objects to the data array.

setTimeout(() => {
  data = [
    {
      id: 4,
      value: 50
    },
    {
      id: 5,
      value: 60
    },
    {
      id: 6,
      value: 70
    }
  ];

}, 5000);

Unsurprisingly, our elements will not be updated. So, to tell i2djs to re-render the elements based on the updated data, we need to write the below code after data.

joinRef.join(data);

So, hopefully, we will be able to see new orange circles.

The final code for this post can be found in the below codepen link.

And if you would like to see multiple elements, bound with the same data, you can check out the below codepen. In that codepen, I have created a text which is bound to the value key in the data we have provided.

That's it for now. We will catch up very soon with a new post. And if you all had anything to add which could help me improve my technical writing skills. Do comment it. I am always open for constructive criticism. Thanks for reading till here😁.

Reference links: