switchon@lavalamp.biz
australia flag +61(0) 42 420 8911
south africa flag +27(0) 21 036 1165
Application & software development
Get A Quote

  • Home
  • Services
    • Application & software development
    • Outsourced software development
    • Project based resourcing
    • Digital marketing & consulting
    • Graphic design & consulting
    • UI / UX design & consulting
    • Recruitment services
    • Lease an expert
  • About
    • NBConsult Group
    • Partners
    • Lightbox Digital
  • Blog
  • Join us
  • Contact

  • Home
  • Services
    • Application & software development
    • Outsourced software development
    • Project based resourcing
    • Digital marketing & consulting
    • Graphic design & consulting
    • UI / UX design & consulting
    • Recruitment services
    • Lease an expert
  • About
    • NBConsult Group
    • Partners
    • Lightbox Digital
  • Blog
  • Join us
  • Contact

  • Home
  • Services
    • Application & software development
    • Outsourced software development
    • Project based resourcing
    • Digital marketing & consulting
    • Graphic design & consulting
    • UI / UX design & consulting
    • Recruitment services
    • Lease an expert
  • About
    • NBConsult Group
    • Partners
    • Lightbox Digital
  • Blog
  • Join us
  • Contact
switchon@lavalamp.biz
Get A Quote

  • Home
  • Services
    • Application & software development
    • Outsourced software development
    • Project based resourcing
    • Digital marketing & consulting
    • Graphic design & consulting
    • UI / UX design & consulting
    • Recruitment services
    • Lease an expert
  • About
    • NBConsult Group
    • Partners
    • Lightbox Digital
  • Blog
  • Join us
  • Contact

  • Home
  • Services
    • Application & software development
    • Outsourced software development
    • Project based resourcing
    • Digital marketing & consulting
    • Graphic design & consulting
    • UI / UX design & consulting
    • Recruitment services
    • Lease an expert
  • About
    • NBConsult Group
    • Partners
    • Lightbox Digital
  • Blog
  • Join us
  • Contact
router extra options
Router Extra Options
May 4, 2021
git version control
A Simple And Powerful Git Version Control Workflow
May 31, 2021

Dynamic Doughnut Graph Using Laravel Vue Component

dynamic doughnut graph using laravel vue component
dynamic doughnut graph using laravel vue component

When working on a project, there will come a time in the development process where the client wants their data to be displayed as graph.

There are various ways on approaching graph implementation, depending on the project’s code structure. In this blog, I will be providing a tutorial on how you can implement a basic and dynamic doughnut graph using a Vue component.



Index


  1. Setting Up Components
  2. Component Definition & Data Binding
  3. Parent Components
  4. Child Components
  5. Circle & Shadow
  6. Graph Percentage & Label Position


Setting up Components


Before starting, you will need to install the following if not already included on your project:

  • Chart.js - npm install chart.js --save
  • Vue-chart.js - npm install vue-chartjs –save / npm install vue-chartjs chart.js --save
  • Vue - php artisan ui vue & npm install

When the extensions above have been installed, you will need to create the following main vue component files within resources->js folders

  • DoughnutGraph.vue
  • BookComponent.vue

Ensure that the vue components are properly referenced in the app.js file, which is also located within the resources folder.

dynamic graph: file structure

Component Definition & Data Binding


App.js

require('./bootstrap');

import Vue from 'vue';

Vue.component('DoughnutGraph', require('./components/DoughnutGraph.vue').default);
Vue.component('rate-component', require('./components/RateComponent.vue').default);
Vue.component('book-component', require('./components/BookComponent.vue').default);
Vue.component('rate-per-month-component', require('./components/RatePerMonthComponent.vue').default);

const app = new Vue({
el: '#app',
});

The first file that we need to focus on is the .blade file, where we will be adding the parent components. At this point of development process, you would have already created a controller that retrieves data from the database.

In the following section of code, you will see an example of how you can approach the implementation of the component and the binding of data on the .blade file.


Example.blade

@extends('layouts.app')</p>
<p>@section('content')</p>
<div class="container container-dashboard">
<div class="row justify-content-center">
<div class="col-md-4">
<div class="card">
                <book-component :book-pending={{$counts&#091;'in_progress'&#093;}} :book-completed={{$counts&#091;'complete'&#093;}} ><br />
</book-component></div>
</div>
<div class="col-md-4">
<div class="card">
                <rate-component :rate-first-amount={{$maximum_amount }} :rate-second-amount={{$current_amount }} ><br />
</rate-component></p>
</div>
</div>
<div class="col-md-4">
<div class="card">
                <rate-per-month-component :fixed-rate={{$subsciption_table->fixed_subscription_rate}}<br />
:first-rate={{$subsciption_table->cost_per_item * $rate_one}}<br />
:second-rate={{$subsciption_table->cost_per_item * $rate_two }}<br />
:next-monthly-rate={{$subsciption_table->fixed_subscription_rate + $subsciption_table->cost_per_item * $rate_two + $subsciption_table->cost_per_item * $rate_one}}<br />
><br />
</rate-per-month-component></div>
</div>
</div>
</div>
<p>@endsection

Parent Components


Before creating the parent components, I will briefly explain what type of data will be displaying for these examples.

  1. For BookComponent, it will be counting the number of books that consist the Completed and Pending status on the database.
  2. RateComponent will be used to display the total amount rates that have been defined and summed up in the controller.
  3. MonthlyRateComponent will display the fixed rate for Rate 1, the cost per rate for both Rate 2 and 3, and the total rate for all three combined.

BookComponent.vue

<template></p>
<div id="app">
    <DoughnutGraph :graphText="graphTextContent" :data="chartData" :options="chartOptions" class="doughnut_graph"></DoughnutGraph></p>
<div class="col-10 ml-3">
<p><b class="graph_total_option_1">Total Books Completed:</b> {{BookCompleted}}</p>
<p><b class="graph_total_option_1">Total Books Reading:</b> {{BookPending}}</p>
<p>&nbsp;</p>
</div>
</div>
<p></template></p>
<p><script>
import DoughnutGraph from './DoughnutGraph'
export default {
    name: "BookComponent",
    components: {
        DoughnutGraph
    },</p>
<p>    props: ["BookCompleted", "BookPending"],</p>
<p>    data() {
        return {
            graphTextContent: 'Books Completed\n vs \nBooks In Progress',
            chartOptions: {
                hoverBorderWidth: 20
            },
            chartData: {
                hoverBackgroundColor: "red",
                hoverBorderWidth: 10,
                labels: ["Completed", "Still Reading"],
                datasets: [
                    {
                        label: "Data One",
                        backgroundColor: ["#20bde9", "#e3342f"],
                        data: [this.BookCompleted, this.BookPending, ]
                    }
                ]
            }
        };
    },
};
</script>

The code layout for these components are the same. The only difference between the three components are the props, graph colour, data and graphTextContent.

To ensure that text is displayed on the next line, /n is used within graphTextContent. You can also display dynamic values within the circle in case you want the total amount to display as the example below.


MonthlyRateComponent.vue

<template></p>
<div id="app">
    <DoughnutGraph :graphText="graphTextContent" :data="chartData" :options="chartOptions" class="doughnut_graph"></DoughnutGraph></p>
<div class="col-10 ml-3">
<p><b class="graph_total_option_1">Total Rate 1:</b> $ {{FixedRate}}</p>
<p><b class="graph_total_option_1">Total Rate 2:</b> $ {{FirstRate}}</p>
<p><b class="graph_total_option_1">Total Rate 3:</b> $ {{SecondRate}}</p>
</div>
</div>
<p></template></p>
<p><script>
import DoughnutGraph from './DoughnutGraph'
export default {
  name: "RatePerMonthComponent",
  components: {
    DoughnutGraph
  },
  props: ["FixedRate", "FirstRate", "SecondRate", "NextMonthlyRate"],
    data() {
        return {
            graphTextContent: 'Next Monthly Rate\n $'+this.NextMonthlyRate,
            chartOptions: {
                hoverBorderWidth: 20
            },
            chartData: {
                hoverBackgroundColor: "red",
                hoverBorderWidth: 10,
                labels: ["Rate 1", "Rate 2", "Rate 3"],
                datasets: [
                {
                    label: "Data One",
                    backgroundColor: ["#99d795", "#34ac2b", "#52c84a"],
                    data: [this.FixedRate, this.FirstRate, this.SecondRate]
                }
                ]
            }
        };
    },
};
</script>

Child Components


DoughnutGraph.vue is used to fully display the graph, the circle centred within the graph, a shadow and the centered.


DoughnutGraph.vue

<script>
import { Doughnut, mixins } from "vue-chartjs";
export default {
  extends: Doughnut,
  props: ["data", "options", "graphText"],</p>
<p>  mounted() {
      var plugin = (chart)=> {</p>
<p>        var width = chart.chart.width;
        var height = chart.chart.height;
        var ctx = chart.chart.ctx;
        const min = Math.min(chart.chart.height, chart.chart.width);</p>
<p>        drawShadow((chart.chart.width/2),(chart.chart.height/2.19),(min * 0.25));
        function drawShadow(cx,cy,r){
            ctx.save();
            ctx.shadowColor='black';
            ctx.shadowBlur=40;
            //
            ctx.beginPath();
            ctx.arc(cx,cy,r,0,2 * Math.PI);
            ctx.fill();
            ctx.restore();
        }</p>
<p>        var innerCircleX = Math.round((width - 3) / 2);
        var innerCircleY = height / 2;</p>
<p>        ctx.fillStyle = '#f9f9f9';
      // ctx.fillStyle = '#106DBD';
        ctx.beginPath();
        ctx.arc((chart.chart.width/2), (chart.chart.height/2.19), (min * 0.28), 0, 2 * Math.PI);
        ctx.fill();</p>
<p>        ctx.restore();</p>
<p>        var fontSize = (height / 324).toFixed(2);
        ctx.font = fontSize + "Roboto";
        ctx.textBaseline = "middle";</p>
<p>        var text = this.graphText;
        ctx.textAlign = "center";</p>
<p>        var textX =  Math.round((width - ctx.measureText(text).width) / 2);
        var textY = Math.round((height - ctx.measureText(text).height) / 2);
        var lineheight = 20;
        var lines = text.split('\n');</p>
<p>        for (var i = 0; i<lines.length; i++)
            ctx.fillText(lines&#091;i&#093;, (chart.chart.width/2), (chart.chart.height/2.4) + (i*lineheight) );

         //ctx.fillText(text, textX, textY);
         ctx.fillStyle = '#777777';

         ctx.save();
}

    this.addPlugin({
        id: 'my-plugin',
        beforeDraw: plugin
    })

    this.renderChart(this.chartdata, this.options);

    // this.chartData is created in the mixin.
    // If you want to pass options please create a local options object
    this.renderChart(this.data, {
        borderWidth: "10px",
        hoverBackgroundColor: "red",
        hoverBorderWidth: "10px",
        cutoutPercentage: "75",
        tooltips: {
            callbacks: {
                label: function(tooltipItem, data) {
                    var dataset = data.datasets&#091;tooltipItem.datasetIndex&#093;;
                var total = dataset.data.reduce(function(previousValue, currentValue, currentIndex, array) {
                    return previousValue + currentValue;
                });
                var currentValue = dataset.data&#091;tooltipItem.index&#093;;
                var precentage = Math.floor(((currentValue/total) * 100)+0.5);
                return data&#091;'labels'&#093;&#091;tooltipItem&#091;'index'&#093;&#093; + ': ' + precentage + "%";
                }
            }
        },
        legend: {
            display: true,
            position: 'bottom',
            align: 'center',
            fullWidth: true,
            reverse: false,
            weight: 1000,
            labels: {
                usePointStyle:true,
                boxWidth: 5,
            }
        }
    });

  }
}
</script>

The first section of the code is responsible for the import and rendering of the doughnut graph. Data and options props is used to pass data from the parent component.

Properties like legend, cutoutpercentage and hoverborderwidth is responsible for the graph’s appearance. For instance, the child properties used within legend is used for positioning and styling and cutoutpercentage is used to determine how thick the graph should be and how much space there needs to be in the middle.

And voila, a working basic doughnut graph.

dynamic doughnut graphs

Of course, you don’t necessarily need to include all of the code above, since every project might have a different design. Let me briefly explain what each additional section of code is doing.



Circle & Shadow


To ensure that a circle can be placed in the middle of the doughnut graph, we will need to draw the circle on the canvas.


DoughnutGraph.vue

 mounted() {
      var plugin = (chart)=> {

        var width = chart.chart.width;
        var height = chart.chart.height;
        var ctx = chart.chart.ctx;
        const min = Math.min(chart.chart.height, chart.chart.width);

        drawShadow((chart.chart.width/2),(chart.chart.height/2.24),(min * 0.28));
        function drawShadow(cx,cy,r){
            ctx.save();
            ctx.shadowColor='black';
            ctx.shadowBlur=10;
            //
            ctx.beginPath();
            ctx.arc(cx,cy,r,0,2 * Math.PI);
            ctx.fill();
            ctx.restore();
        }

        var innerCircleX = Math.round((width - 3) / 2);
        var innerCircleY = height / 2;

        ctx.fillStyle = '#f9f9f9';
      // ctx.fillStyle = '#106DBD';
        ctx.beginPath();
        ctx.arc((chart.chart.width/2), (chart.chart.height/2.24), (min * 0.28), 0, 2 * Math.PI);
        ctx.fill();
        ctx.restore();
        ctx.save();
}

    this.addPlugin({
        id: 'my-plugin',
        beforeDraw: plugin
    })

Var width, height and ctx is used to define the variables, which determines the location and creation of the circle. By using ctx.arc((chart.chart.width/2), (chart.chart.height/2.24), (min * 0.28), 0, 2 * Math.PI);, we are ensuring that the circle is staying in the centre of the graph and that it stays in the position when testing responsiveness.

The drawShadow function is also using a similar method that would ensure that it stays in the centre and draws a shadow. Placing this function on top of the circle code allows the shadow to be behind the drawn circle.

I previously mentioned that you can add text in the centre of the circle. This is made possible with with the graphText prop, which can be easily spotted in the parent component. This section of code allows you to display text above the drawn circle we previously discussed.

var fontSize = (height / 324).toFixed(2);
        ctx.font = fontSize + "Roboto";
        ctx.textBaseline = "middle";

        var text = this.graphText;
        ctx.textAlign = "center";

        var textX =  Math.round((width - ctx.measureText(text).width) / 2);
        var textY = Math.round((height - ctx.measureText(text).height) / 2);
        var lineheight = 20;
        var lines = text.split('\n');

        for (var i = 0; i 	 	 	 	 	 	 	 	 	 	 	 	 	 	
		

You will also need to determine the size of the text and font-family. This is how we ensure that custom text can be added to each component without having the same text on all three components.

The text’s position is determined by the value of the canvas’s width and height, using a similar method used by the circle and shadow.



Graph Percentage & Label Position


There will be some cases where your graph’s legend and tooltip need to match a client’s vision. The following code is used to determine the position of your legend and the displaying of the graph’s tooltip:

</p>
<pre class="line-numbers">
this.renderChart(this.data, {
        borderWidth: "10px",
        hoverBackgroundColor: "red",
        hoverBorderWidth: "10px",
        cutoutPercentage: "75",
        tooltips: {
            callbacks: {
                label: function(tooltipItem, data) {
                    var dataset = data.datasets[tooltipItem.datasetIndex];
                var total = dataset.data.reduce(function(previousValue, currentValue, currentIndex, array) {
                    return previousValue + currentValue;
                });
                var currentValue = dataset.data[tooltipItem.index];
                var precentage = Math.floor(((currentValue/total) * 100)+0.5);
                return data['labels'][tooltipItem['index']] + ': ' + precentage + "%";
                }
            }
        },
        legend: {
            display: true,
            position: 'bottom',
            align: 'center',
            fullWidth: true,
            reverse: false,
            weight: 1000,
            labels: {
                usePointStyle:true,
                boxWidth: 5,
            }
        }
    });
</pre>
<p>

As already discussed, the DoughnutGraph component is responsible for the graph’s functionality and appearance.

The tooltip property is responsible for displaying the graph’s data value. In some examples, like Chart.js’s Doughnut Graph, you will see that the tooltip is displaying the actual data value.

I hope that this tutorial aids you in your coding journey.

graphs value tooltip
graphs percentage tooltip

In the code above, you will see that the graph is displaying the data in percentages. That code is used to calculate the percentage of the total amount of the data in comparison with the other data value and convert the actual value into a percentage value.

The legend property is used to change the appearance of the legend and where it’s position should be. You can play around with the code and see what is to your liking.

I hope that this tutorial aids you in your coding journey.

Contact us


    Related posts:

    laravel livewireLaravel Livewire 7 design principles for web design7 Design Principles For Web Design angularUpdating A Primitive Type Variable From A Service In Angular how to deploy a backend api to azureHow To Deploy A Backend API To Azure
    Share
    29
    Henlo Neethling
    Henlo Neethling
    Henlo is a junior developer, learning to become a jack of all trades for front-end development, UI/UX design and backend development. Has an interest in learning techniques to make the development process go smoother.

    Leave a Reply Cancel reply

    Your email address will not be published. Required fields are marked *

    lava lamp lab secondary logo white

    Like technology, a lava lamp constantly changes form, producing new conditions with every passing moment


    lava lamp lab facebook   lava lamp lab twitter   lava lamp lab linkedin   lava lamp lab instgram

    Services

    • Application & software development
    • Outsourced software development
    • Project based resourcing
    • Digital marketing & consulting
    • Graphic design & consulting
    • UI/UX design & consulting
    • Recruitment services
    • Lease an expert

    Contact Us

    south africa+27(0) 83 419 4851

    south africa+27(0) 21 036 1165

    australia+61(0) 42 420 8911


    switchon@lavalamp.biz


    Lava Lamp Lab,
    Unit 1 Monaco Square, 14 Church Street, Durbanville, Cape Town, 7550

    NBConsult Group

    nbconsult
    designer needed
    © 2023 Lava Lamp Lab (Pty) Ltd | All Rights Reserved | Privacy Policy
        Business Automation

        Business-specific workflows assist our clients with Disaster recovery, time tracking and invoice generation, to mention a few. If you choose to outsource your enterprise app development, we work with you to understand your business and assist to deliver automation for great business efficiency.

        eCommerce Solutions

        We are industry experts in Fintech and eCommerce, Lava Lamp Lab will help you with custom software development to provide you with the quality software services for creating online shops, Fintech mobile apps and web presence for your business of any size, allowing you to engage, sell, support and collect payments.

        Startup & Innovation

        Lava Lamp Lab believes in helping you grow your idea into a business. Our team has provided outsourced services to multiple tech start-ups and investors over the last decade, delivering innovative solutions. We have been trusted over other software development companies to build their entire technology stack.

        Data Mining

        Key to any business is the processing of websites and social network APIs, aligning the information gathered to information collected from internal enterprise systems. This big data can be rendered on reporting dashboards to create Business Intelligence. You can entrust Lava Lamp Lab to reliably deliver on this.

        Contact us now

          Application & software development

            Outsourced software development

              Project based resourcing

                Digital marketing & consulting

                  Graphic design & consulting

                    UI/UX design & consulting

                      Lease an expert

                        Recruitment services

                          We are using cookies to give you the best experience on our website.

                          You can find out more about which cookies we are using or switch them off in .

                          Lava Lamp Lab
                          Powered by  GDPR Cookie Compliance
                          Privacy Overview

                          This website uses cookies so that we can provide you with the best user experience possible. Cookie information is stored in your browser and performs functions such as recognising you when you return to our website and helping our team to understand which sections of the website you find most interesting and useful.

                          Strictly Necessary Cookies

                          Strictly Necessary Cookie should be enabled at all times so that we can save your preferences for cookie settings.

                          If you disable this cookie, we will not be able to save your preferences. This means that every time you visit this website you will need to enable or disable cookies again.

                          3rd Party Cookies

                          This website uses Google Analytics to collect anonymous information such as the number of visitors to the site, and the most popular pages.

                          Keeping this cookie enabled helps us to improve our website.

                          Please enable Strictly Necessary Cookies first so that we can save your preferences!

                          Cookie Policy

                          More information about our Cookie Policy