Artur
Artur

Reputation: 669

How can I add a Bar Chart to my Django based Database?

Dear community members,

I really hope that you will help me to add a Bar Graph to my Django project. 1. I have a Database with around 1000 items. 2. I need to be able visualise a 3 month sales for each item when needed. Not sure what is a correct approach.

here is my models.py:

from django.db import models from math import * from decimal import *

class Itemslist(models.Model):
    item_n = models.CharField(max_length=200)
    sales_this_month = models.DecimalField(blank=True, null=True, max_digits=19, 
    decimal_places=0)
    saleslm = models.DecimalField(blank=True, null=True, max_digits=19, decimal_places=0)
    sales2m = models.DecimalField(blank=True, null=True, max_digits=19, decimal_places=0)
    sales3m = models.DecimalField(blank=True, null=True, max_digits=19, decimal_places=0)


    def __str__(self):
        return self.item_n

here is my views.py file, that as an experiment I have created, using the last solution provided:

def charts(request):
    charts = Itemslist.objects \
        .values('saleslm') \
        .annotate(lm=Count('saleslm')) \
        .annotate(m2=Count('sales2m')) \
        .annotate(3m3=Count('sales3m')) \
        .order_by('saleslm')
    return render(request, 'charts.html', {'charts': charts})

As you can see, this is not a solution I need, I was just trying to come up with at least something , and eaven that has shown me the graph with the same values. here is my hmtl code:

{% extends 'base.html' %}
{% block js %}
{% if user.is_authenticated %}
{% load loads_extra %}
{% load static %}

<br>
<p>Logged in user: {{ user.username }}</p>
<br>


<html>
<head>
<meta charset="utf-8">
<title>Django Highcharts Example</title>
</head>
<body>
<div id="container"></div>
{
<script src="https://code.highcharts.com/highcharts.src.js"></script>
<script>
  Highcharts.chart('container', {
      chart: {
          type: 'column'
      },
      title: {
          text: 'Sales previous 3 months'
      },
      xAxis: {
          categories: ['sales']
      },
      series: [{
          name: '3mBack',
          data: [ {% for entry in charts %}{{ entry.m3 }}{% endfor %} ]
      }, {
          name: '2mBack',
          data: [ {% for entry in charts %}{{ entry.m2 }}{% endfor %} ]
      }, {
          name: 'Lmonth',
          data: [ {% for entry in charts %}{{ entry.lm }}{% endfor %} ]
      }, ]

  });
</script>

</body>
</html>

{% endif %}
{% endblock %}

<!-- charting tutorial to follow :  https://simpleisbetterthancomplex.com/tutorial/2018/04/03/how-to-integrate-highcharts-js-with-django.html -->

I have to create a request button for charts and then chart has to be generated with a right parameters.

Have looked at this question: Displaying multiple bar graph in django

Also have searched through this solution https://code.djangoproject.com/wiki/Charts

And looked at this article https://simpleisbetterthancomplex.com/tutorial/2018/04/03/how-to-integrate-highcharts-js-with-django.html

The last article was the clearest one, and you can see, that I have just copy pasted the solution from there, with small changes. here is a script that I have placed in my base.html file:

<script src="https://code.highcharts.com/highcharts.src.js"></script>

and that's the chart that I have finally got displayed:

Highchart

But still can’t find how to deal with it in my situation.

All this solutions, as far as I can see, are showing how to implement charting to one array, or to sum or self generated array. But I want to be able to chose, when to show a graph and for which Item.

the button is placed on this html file:

{% extends 'base.html' %}
{% block js %}
{% if user.is_authenticated %}
{% load loads_extra %}
{% load static %}

<br>
<p>Logged in user: {{ user.username }}</p>
<br>


    <body>
            <table id="example" class="table table-striped table-bordered dt-responsive nowrap" style="width:100%">
            <thead>
            <tr>
              <th>SUP:</th>
              <th>Item N.:</th>
              <th>SKU</th>
              <th>Description</th>
              <th>3mBack</th>
              <th>2mBack</th>
              <th>Lmonth</th>
              <th>CMonth</th>
              <th>Nmonth</th>
              <th>N2Month</th>
              <th>N3month</th>
              <th></th>

            </tr>
            </thead>
            <tbody>
                        {% for records in sorted %}
                <tr>
                <td>{{ records.sup }}</td>
                <td>{{ records.item_n }}</td>
                <td>{{ records.sku }}</td>
                <td>{{ records.description }}</td>
                <td>{{ records.sales3m }}</td>
                <td>{{ records.sales2m }}</td>
                <td>{{ records.saleslm }}</td>
                <td>{{ records.sales_this_month }}</td>
                <td>{{ records.m1predicted }}</td>
                <td>{{ records.m2predicted }}</td>
                <td>{{ records.m3predicted }}</td>

                <td>
                  <a href="{% url 'edit' records.id %}" class="btn btn-secondary">Edit</a>
                </td>

                            </tr>
                        {% endfor %}

            </tbody>
        </table>

        <script>
        $(document).ready(function() {
            var table = $('#example').DataTable( {
            fixedColumns: true,
                lengthChange: true,
                buttons: [ 'copy', 'excel', 'csv', 'pdf', 'colvis' ]
            } );

            table.buttons().container()
                .appendTo( '#example_wrapper .col-md-6:eq(0)' );
        } );
         </script>
    </body>
</html>
{% endif %}
<div></div>
{% endblock js %}

This is my first question in this community, so if something is not clear, please help me to correct it in a right way.

waiting for any helpful answers!!!

Upvotes: 2

Views: 4813

Answers (2)

Artur
Artur

Reputation: 669

Good News community.

I have been offered a very good solution, and now my question is completely sorted.

At least for my needs, this is a perfect solution.

instead of highcharts, I have been offered to use d3js.org, which is absolutely fine for me.

The logic behind a code is that you request data from the values displayed, hopefully it will be clear, when you will go through the code with comments.

as it's a django project, here's the heading of my html file:

{% extends 'base.html' %}
{% block js %}
{% if user.is_authenticated %}
{% load loads_extra %}
{% load static %}

<br>
<p>Logged in user: {{ user.username }}</p>
<br>

here is a part of my html code that displays table/value:

<table id="example" class="table table-striped table-bordered dt-responsive nowrap" style="width:100%">
            <thead>
            <tr>
              <th>SUP:</th>
              <th>Item N.:</th>
              <th>SKU</th>
              <th>Description</th>
              <th>6mBack</th>
              <th>5mBack</th>
              <th>4mBack</th>
              <th>3mBack</th>
              <th>2mBack</th>
              <th>Lmonth</th>
              <th>CMonth</th>
              <th>Nmonth</th>
              <th>N2Month</th>
              <th>N3month</th>
              <th>AVGrowth</th>
              <th></th>
              <!-- This is header for new button to draw the Bar Charts -->
              <th></th>

            </tr>
            </thead>
            <tbody>
              {% for records in sorted %}
                <tr>
                <td>{{ records.sup }}</td>
                <td>{{ records.item_n }}</td>
                <td>{{ records.sku }}</td>
                <td>{{ records.description }}</td>
                <td>{{ records.sales6m }}</td>
                <td>{{ records.sales5m }}</td>
                <td>{{ records.sales4m }}</td>
                <td>{{ records.sales3m }}</td>
                <td>{{ records.sales2m }}</td>
                <td>{{ records.saleslm }}</td>
                <td>{{ records.sales_this_month }}</td>
                <td>{{ records.m1predicted }}</td>
                <td>{{ records.m2predicted }}</td>
                <td>{{ records.m3predicted }}</td>
                <td>{{ records.avgrowths }}</td>

                <td>
                  <a href="{% url 'edit' records.id %}" class="btn btn-secondary">Edit</a>
                </td>
                <!-- Add new button for drawing Bar Charts -->
                <td>
                  <button class="btn btn-secondary" onclick="draw_chart(this)" data-toggle="modal" data-target="#myModal">Chart</button>
                </td>

            </tr>
          {% endfor %}

         </tbody>
        </table>

please read comments to understand what is what.

and here is the rest of html, that is generating a chart on a topup window,by pressing a button in front of an array that has to be visualised:

<!-- Modal which Bar Chart will be placed -->
        <div id="myModal" class="modal fade" role="dialog">
            <div class="modal-dialog" style="max-width: 900px !important">
            <!-- Modal content-->
                <div class="modal-content">
                    <div class="modal-header">
                        <button type="button" class="close" data-dismiss="modal">&times;</button>
                    </div>
                    <div class="modal-body">
                        <!-- <svg> element which will contains the Bar Chart -->
                        <svg width="1000" height="500"></svg>
                    </div>
                    <div class="modal-footer">
                        <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
                    </div>
                </div>
            </div>
        </div>
        <!-- Insert D3.js Library -->
        <script src="https://d3js.org/d3.v5.min.js"></script>
        <script>
        $(document).ready(function() {
            var table = $('#example').DataTable( {
            fixedColumns: true,
                lengthChange: true,
                buttons: [ 'copy', 'excel', 'csv', 'pdf', 'colvis' ]
            } );

            table.buttons().container()
                .appendTo( '#example_wrapper .col-md-6:eq(0)' );
        } );
        // Main functions for Drawing Bar chart using D3.js
        function draw_chart(item){
            // `item` is the current clicked button element
            // `row_ele` is the parent <tr> element of the current clicked button element
            row_ele = item.closest('tr');
            // Get the value from the <td> element using nth-child()
            val_6mBack = row_ele.querySelector("td:nth-child(5)");
            val_5mBack = row_ele.querySelector("td:nth-child(6)");
            val_4mBack = row_ele.querySelector("td:nth-child(7)");
            val_3mBack = row_ele.querySelector("td:nth-child(8)");
            val_2mBack = row_ele.querySelector("td:nth-child(9)");
            val_Lmonth = row_ele.querySelector("td:nth-child(10)");
            val_CMonth = row_ele.querySelector("td:nth-child(11)");
            val_Nmonth = row_ele.querySelector("td:nth-child(12)");
            val_N2Month = row_ele.querySelector("td:nth-child(13)");
            val_N3month = row_ele.querySelector("td:nth-child(14)");

            // `data` is variable which store the data for Bar Charts
            data = []
            // Pushing data as key/value type objects into the `data` variable
            data.push({'label':'6mBack', 'value': val_6mBack.innerHTML})
            data.push({'label':'5mBack', 'value': val_5mBack.innerHTML})
            data.push({'label':'4mBack', 'value': val_4mBack.innerHTML})
            data.push({'label':'3mBack', 'value': val_3mBack.innerHTML})
            data.push({'label':'2mBack', 'value': val_2mBack.innerHTML})
            data.push({'label':'Lmonth', 'value': val_Lmonth.innerHTML})
            data.push({'label':'CMonth', 'value': val_CMonth.innerHTML})
            data.push({'label':'Nmonth', 'value': val_Nmonth.innerHTML})
            data.push({'label':'N2Month', 'value': val_N2Month.innerHTML})
            data.push({'label':'N3month', 'value': val_N3month.innerHTML})


            // Set <svg> element's width and height
            var svg = d3.select("svg"),
                        margin = 200,
                        width = svg.attr("width") - margin,
                        height = svg.attr("height") - margin
            // Remove the old contents of the <svg> element
            svg.selectAll('*').remove()
            // Initialize X-axis and Y-axis for Bar Chart
            var xScale = d3.scaleBand().range([0, width]).padding(0.4),
                yScale = d3.scaleLinear().range([height, 0]);

            // Set all group which is placed in the <svg>element
            // transform to (50,100) on <svg> area , margint in svg has been changed to 300 and instead 50/100, changed to 100/300, but then back.
            var g = svg.append("g")
                        .attr("transform", "translate(" + 50 + "," + 100 + ")");


            xScale.domain(data.map(function(d) { return d.label; }));
            // If all values of data will be zero, we will fix the range of the Y-axis
            if(d3.max(data, function(d) { return d.value; }) == 0){
                yScale.domain([0, 10]);
            }else{
                // If all is not zero, we will set Y-axis from 0 to maximum value.
                yScale.domain([0, d3.max(data, function(d) { return Number(d.value); })]);
            }

            // Set X- axis
            g.append("g")
             .attr("transform", "translate(0," + height + ")")
             .call(d3.axisBottom(xScale));
             // Set Y-axis using ticket
            g.append("g")
             .call(d3.axisLeft(yScale).tickFormat(function(d){
                 return d;
             }).ticks(10));

             console.log(data)
            // Draw Bar Chart using <rect> element by data which is stored in the `data` variable
            g.selectAll(".bar")
             .data(data)
             .enter().append("rect")
             .attr("class", "bar")
             .attr("x", function(d) { return xScale(d.label); })
             .attr("y", function(d) { return yScale(d.value); })
             .attr("width", xScale.bandwidth())
             .attr("height", function(d) { return height - yScale(d.value); })
             .style('fill','#899da6');
        }
        </script>
    </body>
{% endif %}
{% endblock js %}

so finally here is how my html looks like live:

enter image description here

and here is a graph, that I get by pressing chart button:

enter image description here

in order to help community, please ask questions, and please add/correct if there is a better way.

Thank you very much for your help and attention!!!

Upvotes: 2

Kapil Lamichhane
Kapil Lamichhane

Reputation: 169

views.py You have an error on annotate(3m3 its cannot start with 3 here. You also have same function name and variable name def charts and charts inside. Python might be smart enough to figure it out but try to avoid doing this. I am also against naming variables saleslm and sales2m be explicit here.

def charts(request):
    return render(request, 'charts.html')


def charts_ajax(request):
    charts_data = Itemslist.objects \
        .values('saleslm') \
        .annotate(lm=Count('saleslm')) \
        .annotate(m2=Count('sales2m')) \
        .annotate(m3=Count('sales3m')) \
        .order_by('saleslm')
    return JsonResponse({'charts': list(charts_data)})

url.py

path('charts/', views.charts, name='charts_home'),
path('charts_ajax/', views.charts_ajax, name='render_charts_ajax')

html You have multiple issues in your html file.

</html>
{% endif %}
<div></div>
{% endblock js %}

You have div after the html has ended. You should not have anything after html ends. Few other organizational issues here. I usually would have a block content and then block js while you have everything inside block js. I would clean those. Now that you added datables also. You can add buttons as mentioned here. https://datatables.net/extensions/buttons/examples/initialisation/custom.html but if i were you i would try to make it work with simple table and move on to datables.

<html>
<head>
<meta charset="utf-8">
<title>Django Highcharts Example</title>
</head>
<body>
<div id="container">
</div>
<button id="render_chart" type="button">Render Chart</button>

<script
  src="https://code.jquery.com/jquery-3.4.1.min.js"
  integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo="
  crossorigin="anonymous"></script>
<script src="https://code.highcharts.com/highcharts.src.js"></script>
<script>
$(document).ready(function(){
   $('#render_chart').on('click', function(e){
       $.ajax({
           url: "{% url 'render_charts_ajax'%}",
           type: "GET",
           dataType: "json",
           success: function (data) {
               console.log(data.charts[0].saleslm)
               Highcharts.chart('container',{
                   chart:{
                       type:'bar'
                   },
                   title:{
                     text:"Sales Last Month"
                   },
                   series:[{
                       name:"Sales LM",
                       data:[parseInt(data.charts[0].saleslm)]
                   }]
               })
           }
       })
   })
});
</script>

</body>
</html>

Your json might need some processing before the chart comes out right.

Upvotes: 1

Related Questions