Categories
Vue and D3

Adding Graphics to a Vue App with D3 — Circle, Ellipse, and Transforms

D3 lets us add graphics to a front-end web app easily.

Vue is a popular front end web framework.

They work great together. In this article, we’ll look at how to add graphics to a Vue app with D3.

Circle Element

We can add a circle with D3 into our Vue app.

For example, we can write:

<template>
  <div id="app">
    <div id="svgcontainer"></div>
  </div>
</template>

<script>
import * as d3 from "d3";
import Vue from "vue";

export default {
  name: "App",
  mounted() {
    Vue.nextTick(() => {
      const width = 300;
      const height = 300;
      const svg = d3
        .select("#svgcontainer")
        .append("svg")
        .attr("width", width)
        .attr("height", height);
      svg
        .append("circle")
        .attr("cx", 200)
        .attr("cy", 50)
        .attr("r", 20)
        .attr("fill", "lightgreen");
    });
  },
};
</script>

to add the circle.

We create the svg element with the d3.select method.

We select the div with ID svgcontainer .

Then we set the width and height with the attr method to set the width and height of the svg element.

Then we add the circle by calling append with the 'circle' argument.

And we set the x and y coordinates of the center of the circle with the attr method.

'cx' sets the x coordinate of the center.

And 'cy' sets the y coordinate of the center.

'fill' sets the background color of the circle.

'r' sets the radius of the circle.

Ellipse Element

To add an ellipse, we can do something similar.

For instance, we can write:

<template>
  <div id="app">
    <div id="svgcontainer"></div>
  </div>
</template>

<script>
import * as d3 from "d3";
import Vue from "vue";

export default {
  name: "App",
  mounted() {
    Vue.nextTick(() => {
      const width = 300;
      const height = 300;
      const svg = d3
        .select("#svgcontainer")
        .append("svg")
        .attr("width", width)
        .attr("height", height);
      svg
        .append("ellipse")
        .attr("cx", 200)
        .attr("cy", 50)
        .attr("rx", 100)
        .attr("ry", 50)
        .attr("fill", "lightgreen");
    });
  },
};
</script>

We changed 'circle' to 'ellipse' .

Then we add the 'rx' and 'ry' attributes instead of 'r' .

'rx' sets the horizontal radius and 'ry' sets the vertical radius.

SVG Transformation

We can transform SVGs with D3.

For example, we can write:

<template>
  <div id="app">
    <div id="svgcontainer"></div>
  </div>
</template>

<script>
import * as d3 from "d3";
import Vue from "vue";

export default {
  name: "App",
  mounted() {
    Vue.nextTick(() => {
      const width = 300;
      const height = 300;
      const svg = d3
        .select("#svgcontainer")
        .append("svg")
        .attr("width", width)
        .attr("height", height);

      const group = svg
        .append("g")
        .attr("transform", "translate(60, 60) rotate(30)");

      const rect = group
        .append("rect")
        .attr("x", 20)
        .attr("y", 20)
        .attr("width", 60)
        .attr("height", 30)
        .attr("fill", "green");
    });
  },
};
</script>

First, we call attr with the 'transform' string to set the transform CSS property of the svg element.

translate(60, 60) moves the SVG 60 pixels right and down respectively.

rotate(30) rotates the SVG by 30 degrees.

Then we call append the rectangle with the append method with the 'rect' as the argument and set all the attributes.

Conclusion

We can add circles and ellipses, and transform shapes with D3 in a Vue app.

Categories
Vue and D3

Adding Graphics to a Vue App with D3 — Transitions and Bar Charts

D3 lets us add graphics to a front-end web app easily.

Vue is a popular front end web framework.

They work great together. In this article, we’ll look at how to add graphics to a Vue app with D3.

Transition

We can use the D3 to apply transitions.

For example, we can write:

<template>
  <div id="app"></div>
</template>

<script>
import * as d3 from "d3";
import Vue from "vue";

export default {
  name: "App",
  mounted() {
    Vue.nextTick(() => {
      const t = d3.transition().duration(2000);
      d3.select("body").transition(t).style("background-color", "lightblue");
    });
  },
};
</script>

to call d3.transition to create our transition object.

Then we set the duration of it with the duration method.

2000 is in milliseconds.

Then we call the transition method with the t method to set the background color of body to lightblue in 2 seconds.

Animation

We can add animations with D3.

For instance, we can write:

<template>
  <div id="app">
    <h3>hello world</h3>
  </div>
</template>

<script>
import * as d3 from "d3";
import Vue from "vue";

export default {
  name: "App",
  mounted() {
    Vue.nextTick(() => {
      d3.selectAll("h3")
        .transition()
        .style("font-size", "28px")
        .delay(2000)
        .duration(2000);
    });
  },
};
</script>

We get the h3 and then make the font-size 28px after 2 seconds.

Then we change the size to 28px in 2 seconds.

Drawing Charts

D3 is useful for creating charts.

We can use it to create bar charts, circle charts, pie charts, donut, charts, etc.

Bar Charts

We can create bar charts with D3.

For example, we can write:

<template>
  <div id="app">
    <div id="svgcontainer"></div>
  </div>
</template>

<script>
import * as d3 from "d3";
import Vue from "vue";

export default {
  name: "App",
  mounted() {
    Vue.nextTick(() => {
      const data = [13, 6, 11, 20, 13];

      const width = 500;
      const scaleFactor = 20;
      const barHeight = 30;

      const graph = d3
        .select("#svgcontainer")
        .append("svg")
        .attr("width", width)
        .attr("height", barHeight * data.length);

      const bar = graph
        .selectAll("g")
        .data(data)
        .enter()
        .append("g")
        .attr("transform", (d, i) => {
          return "translate(0," + i * barHeight + ")";
        });
      bar
        .append("rect")
        .attr("width", (d) => {
          return d * scaleFactor;
        })
        .attr("height", barHeight - 1)
        .attr("fill", "green");

      bar
        .append("text")
        .attr("x", (d) => {
          return d * scaleFactor;
        })
        .attr("y", barHeight / 2)
        .attr("dy", ".35em")
        .text((d) => {
          return d;
        })
        .attr("fill", "red");
    });
  },
};
</script>

We sett the constants and the dimensions for our bar chart in the first few lines.

Then we create the g elements with:

const bar = graph
  .selectAll("g")
  .data(data)
  .enter()
  .append("g")
  .attr("transform", (d, i) => {
    return "translate(0," + i * barHeight + ")";
  });

to house the bars.

Next, we create the rectangles for the bar chart with:

bar
  .append("rect")
  .attr("width", (d) => {
    return d * scaleFactor;
  })
  .attr("height", barHeight - 1)
  .attr("fill", "green");

d has the number in the data array.

We multiply that by scaleFactor to get the width of the bars.

We set the height to barHeight — 1 to set the height and add a gap between the bars.

And the 'fill' is set to 'green' .

Then we add the next to the right of the bars by writing:

bar
  .append("text")
  .attr("x", (d) => {
    return d * scaleFactor;
  })
  .attr("y", barHeight / 2)
  .attr("dy", ".35em")
  .text((d) => {
    return d;
  })
  .attr("fill", "red");

The 'x' and 'y' attributes set the location of the text.

x comes after the bar and y is barHeight / 2 .

We also have to shift the y coordinate to that the text is aligned with the bar.

So we set the dy attribute to .35em .

Then we return the number and set that as the text with the text method.

And then we call attr with the 'fill' set to 'red' .

Conclusion

We can add transitions and bar charts with D3 into our Vue app.

Categories
Modern JavaScript

Best of Modern JavaScript — __proto__ and Descriptors

Since 2015, JavaScript has improved immensely.

It’s much more pleasant to use it now than ever.

In this article, we’ll look at new OOP features in JavaScript.

The proto Property

We can use the __proto__ property to get and set the prototype of an object easily.

For example, we can write:

Object.getPrototypeOf({ __proto__: null })

Then it’ll return null since we set the __proto__ property to null .

This doesn’t work with the string version of the key as it creates its own property:

Object.getPrototypeOf({ ['__proto__']: null })

It’ll return Object.prototype .

And Object.keys({ [‘__proto__’]: null }) returns the array[ ‘__proto__’ ] .

Also, if we define our own property named __proto__ , it also won’t work.

For instance, we can write:

const obj = {};
Object.defineProperty(obj, '__proto__', { value: 'bar' })

Objects that don’t have Object.prototype as a Prototype

We can use the Object.create method with the argument null to create an object with no prototype.

For example, we can write:

const obj = Object.create(null);

Then obj has no __proto__ property.

This method makes it easy to create dictionary objects where we don’t want any inherited properties from it.

proto and JSON

__proto__ may be stringified with JSON.stringify .

For example, we can write:

JSON.stringify({['__proto__']: 'foo' })

And then that returns:

"{"__proto__":"foo"}"

Detecting support for ES6-style proto

There are several ways to detect the ES6 style __proto__ property.

We can check it with the Object.prototype.__proto__ property and the __proto__ in the object literals.

For example, we can write:

const supported = ({}).hasOwnProperty.call(Object.prototype, '__proto__');

We check if the Object.prototype.__proto__ property exists.

We can also check that getter and setter of __proto__ are functions to make sure that we can get and set the value of this property.

For example, we can write:

const desc = Object.getOwnPropertyDescriptor(Object.prototype, '__proto__');
const supported = (
  typeof desc.get === 'function' && typeof desc.set === 'function'
);

And we can also use the Object.getPrototypeOf method to do the check by passing in an object with the __proto__ property set to null .

Then we can use that to check if the prototype returned is null .

For example, we can write:

const supported = Object.getPrototypeOf({__proto__: null}) === null;

Enumerability in ES6

In JavaScript, each object can have zero or more p[roperties.

Each property has a key and 3 or more descriptors.

All properties have the enumerable and configurable attributes

enumerable means that whether we show the property everywhere.

configurable set to false disables property value changes and prevents deletion.

Properties have the value and writable attributes.

value has the value of the property.

And writable controls whether we can change the value of the property.

Properties also have the getter get accessor and setter set accessor.

We can use the Object.getOwnPropertyDescriptor method to get the property descriptors.

For example, we can write:

const obj = { foo: 'bar' };
consr desc = Object.getOwnPropertyDescriptor(obj, 'foo')

Then desc is:

{
  value: "bar",
  writable: true,
  enumerable: true,
  configurable: true
}

Conclusion

Object properties have property descriptors.

Also, the __proto__ property acts differently in different contexts.

We can check if the property is available in different ways.

Categories
JavaScript Basics

Removing Duplicate Objects in a JavaScript Array

Removing duplicate objects in an array can take some effort.

It’s not as simple as removing duplicate primitive values from an array.

In this article, we’ll look at how to remove duplicate objects from an array.

Eliminating Duplicates Values from Arrays

Removing duplicate values is simple.

We just convert it to a set and then spread it back to an array.

For instance, we can write:

const arr = ['foo', 'bar', 'foo'];
const uniqueArr = [...new Set(arr)];

Then we get [“foo”, “bar”] as the value of uniqueArr .

This won’t work with objects since only objects with the same reference are considered the same.

Create New Array without Duplicates

One way to remove duplicates is to create a new array without the duplicate entries.

For example, if we have the following array:

const persons = [{
    firstName: 'jane',
    lastName: 'snith',
    age: 20
  },
  {
    firstName: 'bob',
    lastName: 'jones',
    age: 33,
  },
  {
    firstName: 'jane',
    lastName: 'snith',
    age: 20
  },
];

Then we can check each object for existence in the array with the unique entries.

If it doesn’t already exist, we put it into the array with unique values.

To do that, we can write the following code:

const uniquePersons = [];

for (const p of persons) {
  if (!contains(uniquePersons, p)) {
    uniquePersons.push(p);
  }
}

function contains(arr, person) {
  return arr.find(
    (a) => a.firstName === person.firstName && a.lastName === person.lastName && a.age === person.age);
}

We have the uniquePersons array to hold the array entries with the unique entries from the persons array.

The contains function returns if the item exists in the arr array.

person is the object from persons we wan to check.

We call arr.find with the condition that we want to return to check for the properties of person to determine existence.

In the for-of loop, we only call push on uniquePersons to add items from persons that doesn’t already exist.

In the end, uniquePersons is:

[
  {
    "firstName": "jane",
    "lastName": "snith",
    "age": 20
  },
  {
    "firstName": "bob",
    "lastName": "jones",
    "age": 33
  }
]

Using filter()

Another way to remove duplicate objects from an array is to use the filter method to check if the object is the first instance of the object.

To do that, we can write:

const uniquePersons = persons.filter(
  (m, index, ms) => getFirstIndex(ms, m) === index);

function getFirstIndex(arr, person) {
  return arr.findIndex(
    (a) => a.firstName === person.firstName && a.lastName === person.lastName && a.age === person.age);
}

given that we have

const persons = [{
    firstName: 'jane',
    lastName: 'snith',
    age: 20
  },
  {
    firstName: 'bob',
    lastName: 'jones',
    age: 33,
  },
  {
    firstName: 'jane',
    lastName: 'snith',
    age: 20
  },
];

as the value of persons .

We created the getFirstIndex method that takes an arr array and the person object.

Inside the function, we call findIndex to return the first index with of the array entry with the given value of person.firstName , person.lastName , and person.age .

Then in the filter callback, we can call getFirstIndex to compare the index with the returned index.

If they’re different, then we know the object is a duplicate, so it’ll be discarded.

In the end, we get the same result.

Create a Map and Convert it Back to an Array

Another way to remove the entries is to create a map from the array and then convert it back to an array.

For instance, we can write:

const map = new Map();for (const p of persons) {
  map.set(JSON.stringify(p), p);
}

const uniquePersons = [...map.values()]

given that person is the same as the other examples.

We just add the items into the map with the set method. The stringified version of the object if the key and the non-stringified version is the value.

Since strings can be compared with === , duplicates will be overwritten.

Then we can spread the items back to an array by getting them with map.values() and using the spread operator.

Conclusion

Removing duplicate objects takes more work than removing duplicate primitive values.

Array methods and maps make this easier.

Categories
Vue

Vue Konva — Animation and Caching

We can make working with the HTML canvas easier in Vue apps with the Vue Konva library.

In this article, we’ll take a look at how to use Vue Konva to make working with the HTML canvas easier in a Vue app.

Animation

We can animation shapes with the Konva.Animation constructor.

For example, we can write:

<template>
  <v-stage ref="stage" :config="stageSize">
    <v-layer ref="layer">
      <v-rect
        ref="rect"
        @dragstart="changeSize"
        @dragend="changeSize"
        :config="{
          width: 50,
          height: 50,
          fill: 'green',
          draggable: true,
        }"
      />
      <v-regular-polygon
        ref="octagon"
        :config="{
          x: 200,
          y: 200,
          sides: 8,
          radius: 20,
          fill: 'red',
          stroke: 'black',
          strokeWidth: 4,
        }"
      />
    </v-layer>
  </v-stage>
</template>

<script>
import Konva from "konva";
const width = window.innerWidth;
const height = window.innerHeight;

export default {
  data() {
    return {
      stageSize: {
        width: width,
        height: height,
      },
    };
  },
  methods: {
    changeSize(e) {
      e.target.to({
        scaleX: Math.random() + 0.8,
        scaleY: Math.random() + 0.8,
        duration: 0.2,
      });
    },
  },
  mounted() {
    const vm = this;
    const amplitude = 100;
    const period = 5000;
    const centerX = vm.$refs.stage.getNode().getWidth() / 2;
    const octagon = this.$refs.octagon.getNode();
    const anim = new Konva.Animation((frame) => {
      octagon.setX(
        amplitude * Math.sin((frame.time * 2 * Math.PI) / period) + centerX
      );
    }, octagon.getLayer());
    anim.start();
  },
};
</script>

We call the Konva.Animation constructor with a callback to change the x coordinate of the item with setX .

Then we call anim.start to start the animation.

The changeSize method is called when we’re dragging the rectangle.

It changes the position when we drag on the object.

Cache

We can cache the items rendered with the cache method.

For example, we can write:

<template>
  <div>
    <v-stage ref="stage" :config="stageConfig">
      <v-layer ref="layer">
        <v-group ref="group">
          <v-star
            v-for="item in list"
            :key="item.id"
            :config="{
              x: item.x,
              y: item.y,
              rotation: item.rotation,
              id: item.id,
              numPoints: 5,
              innerRadius: 30,
              outerRadius: 50,
              fill: 'lightgreen',
              opacity: 0.8,
              shadowColor: 'black',
              shadowBlur: 10,
              shadowOpacity: 0.6,
              scaleX: item.scale,
              scaleY: item.scale,
            }"
          />
        </v-group>
      </v-layer>
    </v-stage>
    <div class="cache">
      <input type="checkbox" @change="handleCacheChange" /> cache shapes
    </div>
  </div>
</template>

<script>
const width = window.innerWidth;
const height = window.innerHeight;
export default {
  data() {
    return {
      list: [],
      dragItemId: null,
      stageConfig: {
        width: width,
        height: height,
        draggable: true,
      },
    };
  },
  methods: {
    handleCacheChange(e) {
      const shouldCache = e.target.checked;
      if (shouldCache) {
        this.$refs.group.getNode().cache();
      } else {
        this.$refs.group.getNode().clearCache();
      }
    },
  },
  mounted() {
    for (let n = 0; n < 300; n++) {
      this.list.push({
        id: n.toString(),
        x: Math.random() * width,
        y: Math.random() * height,
        rotation: Math.random() * 180,
        scale: Math.random(),
      });
    }
  },
};
</script>

<style>
body {
  margin: 0;
  padding: 0;
}

.cache {
  position: absolute;
  top: 0;
  left: 0;
}
</style>

to add 300 stars.

In the handleCacheChange method, we call cache on the v-group ‘s ref to cache the content if the checkbox is checked.

Otherwise, we clear the cache with clearCache .

Conclusion

We can animate shapes and cache them with Vue Konva.