I have a project where I am using a Pie/Doughnut chart to visualize my data. I have added the general update pattern in order to create a smooth transition when my data changes/updates.
In order to accomplish this I have followed an example which uses the general update pattern on a pie chart: Bl.ocks example.
The problem I am facing is that the chart doesn't update smoothly when updating the data. The chart instantly swaps from one state into the next.
In this and other examples they define a arcTween
method where d3 interpolates between the previous angles and the angles from the newly updated data:
arcTween(a) {
let i = d3.interpolate(this._current, a);
this._current = i(0);
return t => {
return this.arc(i(t));
};
}
I also have added the code where I join, enter and update my data to the pie chart. Here I first create the group element where the pie is being drawn in. I also define a transition using the 'arcTween' method to transition between the states. And lastly I also define the 'this._current' when the pie is created:
this.g = this.svg
.selectAll("doughnut")
.data(data_ready)
.enter()
.append("g");
this.g
.transition()
.duration(1500)
.attrTween("d", this.arcTween);
this.g
.append("path")
.attr("d", d => {
return this.arc(d);
})
.attr("fill", "#206BF3")
.attr("class", "slice")
.attr("stroke", "#2D3546")
.style("stroke-width", "2px")
.each(d => {
this._current = d;
});
This is my full code. This is being written in Vue.js. I have tried to get this to work inside a snippet. But I couldn't get it to work.
The images that are shown on top of the doughnut slices are locally stored:
<template>
<div class="p-5 flex flex-col h-full">
<h2 class="mb-3"></h2>
<div ref="my_dataviz" class="flex justify-center"></div>
<div class="grid grid-cols-2 gap-7 m-7">
<div v-for="item in data" :key="item.key" class="flex">
<img
:src="require('@/assets/img/doughnut/' + item.icon)"
alt=""
class="doughnutIcon mr-4"
/>
<div class="flex flex-col">
<h3></h3>
<p class="opacity-50">
</p>
<p class="opacity-50"> %</p>
</div>
</div>
</div>
</div>
</template>
<script>
import { converter } from "@/shared";
import * as d3 from "d3";
export default {
name: "DoughnutChartItem",
props: {
title: {
type: String,
required: true
},
data: {
type: Array,
required: true
},
height: {
type: Number,
required: true
},
width: {
type: Number,
required: true
},
unit: {
type: String
}
},
data() {
return {
totalAmount: 0,
svg: undefined,
arc: undefined,
radius: undefined,
g: undefined
};
},
created() {
let total = 0;
this.data.forEach(item => {
total += item.value;
});
this.totalAmount = total;
},
mounted() {
// set the dimensions and margins of the graph
var margin = 1;
// The radius of the pieplot is half the width or half the height (smallest one). I subtract a bit of margin.
this.radius = Math.min(this.width, this.height) / 2 - margin;
// append the svg object to the div called 'my_dataviz'
this.svg = d3
.select(this.$refs.my_dataviz)
.append("svg")
.attr("width", this.width)
.attr("height", this.height)
.append("g")
.attr(
"transform",
"translate(" + this.width / 2 + "," + this.height / 2 + ")"
);
// Compute the position of each group on the pie:
this.pie = d3.pie().value(function(d) {
return d[1];
});
// declare an arc generator function
this.arc = d3
.arc()
.outerRadius(100)
.innerRadius(50);
this.setSlicesOnDoughnut(this.data);
this.addImagesToSlices();
},
methods: {
animateSliceOnHover(radius, path, dir) {
switch (dir) {
case 0:
path
.transition()
.duration(500)
.ease(d3.easeBounce)
.attr(
"d",
d3
.arc()
.innerRadius(100)
.outerRadius(50)
);
path.style("fill", "#206BF3");
break;
case 1:
path.transition().attr(
"d",
d3
.arc()
.innerRadius(50)
.outerRadius(110)
);
path.style("fill", "white");
break;
}
},
percentageOfTotal(amount) {
return Math.round((amount / this.totalAmount) * 100);
},
formatNumberValue(amount) {
return converter.formatNumberValue(amount);
},
setSlicesOnDoughnut(data) {
// Build the pie chart: Basically, each part of the pie is a path that we build using the arc function.
var data_ready = this.pie(
data.map(function(d) {
return [d["key"], d["value"], d["icon"], d["hover"]];
})
);
this.g = this.svg
.selectAll("doughnut")
.data(data_ready)
.enter()
.append("g");
this.g
.transition()
.duration(1500)
.attrTween("d", this.arcTween);
this.g
.append("path")
.attr("d", d => {
return this.arc(d);
})
.attr("fill", "#206BF3")
.attr("class", "slice")
.attr("stroke", "#2D3546")
.style("stroke-width", "2px")
.each(d => {
this._current = d;
});
// Add tooltip
d3.selectAll(".slice")
.on("mouseover", this.mouseover)
.on("mousemove", this.mousemove)
.on("mouseout", this.mouseout);
},
addImagesToSlices() {
var image_width = 20;
var image_height = 20;
this.g.selectAll(".logo").remove();
this.g
.append("svg:image")
.attr("transform", d => {
var x = this.arc.centroid(d)[0] - image_width / 2;
var y = this.arc.centroid(d)[1] - image_height / 2;
return "translate(" + x + "," + y + ")";
})
.attr("class", "logo")
.attr("class", function(d) {
return `${d.data[0]}-logo`;
})
.attr("href", function(d) {
return require("@/assets/img/doughnut/" + d.data[2]);
})
.attr("width", image_width)
.attr("height", image_height);
},
mouseover(event, data) {
//Swap doughnut icon to blue icon
d3.selectAll("." + data.data[0] + "-logo").attr("href", d => {
return require("@/assets/img/doughnut/" + d.data[3]);
});
this.animateSliceOnHover(this.radius, d3.select(event.currentTarget), 1);
const tip = d3.select(".tooltip");
tip
.style("left", `${event.clientX + 15}px`)
.style("top", `${event.clientY}px`)
.transition()
.style("opacity", 0.98);
tip.select("h3").html(`${data.data[0]}`);
tip
.select("h4")
.html(`${this.formatNumberValue(data.data[1])} ${this.unit}`);
},
mousemove(event) {
// Move tooltip
d3.select(".tooltip")
.style("left", `${event.clientX + 15}px`)
.style("top", `${event.clientY}px`);
},
mouseout(event, data) {
//Swap doughnut icon to white icon
d3.selectAll("." + data.data[0] + "-logo").attr("href", function(d) {
return require("@/assets/img/doughnut/" + d.data[2]);
});
// Animate slice
var thisPath = d3.select(event.currentTarget);
this.animateSliceOnHover(this.radius, thisPath, 0);
// if (!thisPath.classed("clicked")) {
// this.animateSliceOnHover(this.radius, thisPath, 0);
// }
// Hide tooltip
d3.select(".tooltip")
.transition()
.style("opacity", 0);
},
arcTween(a) {
let i = d3.interpolate(this._current, a);
this._current = i(0);
return t => {
return this.arc(i(t));
};
}
},
watch: {
data() {
this.setSlicesOnDoughnut(this.data);
this.addImagesToSlices();
}
}
};
</script>
from D3 General update pattern transition not working on pie chart
No comments:
Post a Comment