Related
I have a problem displaying data into a mixed bar and line chart from an API designed using the Django Rest Framework. Here is the model for the readings from the pressure and flowrate sensors
models.py
from django.db import models
# Create your models here.
class ReadingQuerySet(models.QuerySet):
pass
class ReadingManager(models.Manager):
def get_queryset(self):
return ReadingQuerySet(self.model, using=self._db)
class Reading(models.Model):
date = models.DateField(auto_now=True)
pressure =models.IntegerField(default=0)
flowrate =models.IntegerField(default=0)
timestamp =models.TimeField(auto_now_add=True)
objects = ReadingManager()
def __str__(self):
return str(self.timestamp) + ' ' + 'Pressure: ' + str(self.pressure) + ' ' + 'Flowrate: ' + str(self.flowrate)
The code for the serializer is here.
serializers.py
from rest_framework import serializers
from app.models import Reading
class ReadingSerializer(serializers.ModelSerializer):
class Meta:
model = Reading
fields = ['id','date','timestamp','pressure','flowrate']
the serializer is in an api folder i placed within a django app. the code for the APIview is below.
views.py
from rest_framework import generics, mixins
from rest_framework.response import Response
from .serializers import ReadingSerializer
from app.models import Reading
class ReadingChartAPIView(mixins.CreateModelMixin, generics.ListAPIView):
permission_classes = []
authentication_classes = []
serializer_class = ReadingSerializer
def get(self, request, *args, **kwargs):
qs = Reading.objects.all()
data = {
'timestamp':qs.values('timestamp'),
'pressure':qs.values('pressure'),
'flowrate':qs.values('flowrate'),
'update':qs.values('pressure', 'flowrate').last()
}
return Response(data)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
and this is the data i receive from the APIview
{"timestamp":[{"timestamp":"12:44:11.487901"},{"timestamp":"14:12:58.410520"},{"timestamp":"01:56:56.485449"},{"timestamp":"01:57:14.184429"},{"timestamp":"01:57:34.366559"},{"timestamp":"13:12:43.469454"}],"pressure":[{"pressure":45},{"pressure":35},{"pressure":79},{"pressure":52},{"pressure":89},{"pressure":27}],"flowrate":[{"flowrate":56},{"flowrate":67},{"flowrate":83},{"flowrate":14},{"flowrate":74},{"flowrate":46}],"update":{"pressure":27,"flowrate":46}}
The issue is in my html file when i am extracting this data. Here is my HTML.I am stuck and open to any solutions. i am trying to push the data labels for to datasets every after 5 seconds starting from the first one.
{% extends "layouts/base.html" %}
{%load static %}
{% block title %} Dashboard {% endblock %}
<!-- Specific Page CSS goes HERE -->
{% block stylesheets %}{% endblock stylesheets %}
{% block content %}
{% block jquery %}
<script type="text/javascript">
var counter = 0
var limit = 6
var chart = $('#myChart')
var endpoint = '/api/chart'
var pressures = []
var flowrates = []
var timestamps = []
var pressure_new = []
var flowrate_new = []
$.ajax({
method: 'GET',
url: endpoint,
success: function(data){
console.log(data)
}
})
var getData =
$.ajax({
method: 'GET',
url: endpoint,
success: function(data){
timestamps = data.timestamp.map(b => b.timestamp);
pressures = data.pressure.map(a => a.pressure);
flowrates = data.flowrate.map(c => c.flowrate);
myChart.data.labels.concat(timestamps[counter]);
myChart.data.datasets[0].data.concat(pressures[counter]);
myChart.data.datasets[1].data.concat(flowrates[counter]);
counter++;
myChart.update();
}
});
setInterval(getData, 3000)
</script>
{% endblock %}
<div class="container-fluid">
<div class="page-header row no-gutters py-4">
<div class="col-12 col-sm-4 text-center text-sm-left mb-0">
<span class="text-uppercase page-subtitle">Dashboard</span>
<h3 class="page-title">Overview</h3>
</div>
</div>
<div class="row">
<div class="col-lg-8 col-md-12 col-sm-12 mb-4">
<div class="card" width = '18rem' height = '10rem'>
<div class="card-header border-bottom">
<h5 class="card-title">Pressure and Flowrate</h5>
<h6 class="card-subtitle mb-1 text-muted">Real-time Readings of Pressure and Flowrate</h6>
</div>
<div class="card-body">
<canvas id="myChart"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.js"></script>
<script>
function setChart(){
var ctx = document.getElementById('myChart');
var myChart = new Chart(ctx, {
type: 'bar',
data: {
labels: labels,
datasets: [{
label: 'Pa',
data:[],
backgroundColor:'rgba(255, 99, 132, 0.2)',
borderColor:'rgba(255, 99, 132, 1)',
borderWidth: 1
}, {
label: 'cm3/min',
data:[],
backgroundColor:'rgba(54, 162, 235, 0.2)',
borderColor:'rgba(54, 162, 235, 1)',
type: 'line',
}]
},
options: {
scales: {
yAxes: [{
ticks: {
beginAtZero: true
}
}]
}
}
});
};
setChart()
</script>
</div>
</div>
</div>
Trying to make some of my tables work with Ajax pagination
Actually created a clean empty template with just one table. Just for testing.
{% extends 'base.html' %}
{% load static %}
{% block title %}
TEST Skaters averages - NHL stats tracker
{% endblock title %}
{% block styles %}
<link rel="stylesheet" href="{% static 'players/tablesorter.css' %}">
{% endblock styles %}
{% block content %}
<!-- SKATERS -->
<table class="tablesorter">
<thead>
<tr class="tablesorter-ignoreRow">
<td class="pager" colspan="5">
<button type="button" class="btn first"><i class="small material-icons">first_page</i></button>
<button type="button" class="btn prev"><i class="small material-icons">navigate_before</i></button>
<span class="pagedisplay"></span>
<button type="button" class="btn next"><i class="small material-icons white">navigate_next</i></button>
<button type="button" class="btn last"><i class="small material-icons">last_page</i></button>
<select class="pagesize">
<option value="25">25</option>
</select>
</td>
</tr>
<tr>
<th>1</th>
<th>2</th>
<th>3</th>
</thead>
<tbody>
</tbody>
</table>
{% endblock content %}
{% block scripts %}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.tablesorter/2.31.1/js/jquery.tablesorter.js"></script>
<!-- Widgets -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.tablesorter/2.31.1/js/jquery.tablesorter.widgets.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.tablesorter/2.31.1/js/extras/jquery.tablesorter.pager.min.js"></script>
<script src="{% static 'players/sorting_averages.js' %}"></script>
{% endblock scripts %}
sorting_averages.js
$("table")
.tablesorter({
});
.tablesorterPager({
container: $(".pager"),
ajaxUrl: 'http://127.0.0.1:8000/skaters_averages_json/{page}',
});
In players.views I'm making slices with JSON data for every page.
def skaters_averages_json(request, page):
start = utils.PAGE_SIZE_2*(page - 1)
end = start + utils.PAGE_SIZE_2
skaters = Skater.objects.select_related('team')
one_page_slice = skaters.order_by('-points', 'games', '-goals')[start:end]
skaters_json = json.loads(serializers.serialize('json', one_page_slice))
data = {}
data["total_rows"] = utils.PAGE_SIZE_2
data["headers"] = ["ID", "Name", "Points"]
data["rows"] = []
for index, skater in enumerate(skaters_json):
data["rows"].append({})
data["rows"][index][data["headers"][0]] = skater['fields']['nhl_id']
data["rows"][index][data["headers"][1]] = skater['fields']['name']
data["rows"][index][data["headers"][2]] = skater['fields']['points']
return JsonResponse(data, safe=False)
urls.py
urlpatterns = [
path('skaters_averages_json/<int:page>', views.skaters_averages_json,
name='skaters_averages_json'),
]
For example, 127.0.0.1:8000/skaters_averages_json/1 shows this JSON output
{
"total_rows":25,
"headers":[
"ID",
"Name",
"Points"
],
"rows":[
{
"ID":8476453,
"Name":"Nikita Kucherov",
"Points":128
},
{
"ID":8478402,
"Name":"Connor McDavid",
"Points":116
},
{
"ID":8474141,
"Name":"Patrick Kane",
"Points":110
},
{
"ID":8477934,
"Name":"Leon Draisaitl",
"Points":105
},
{
"ID":8473419,
"Name":"Brad Marchand",
"Points":100
},
{
"ID":8471675,
"Name":"Sidney Crosby",
"Points":100
},
{
"ID":8477492,
"Name":"Nathan MacKinnon",
"Points":99
},
{
"ID":8476346,
"Name":"Johnny Gaudreau",
"Points":99
},
{
"ID":8474564,
"Name":"Steven Stamkos",
"Points":98
},
{
"ID":8477493,
"Name":"Aleksander Barkov",
"Points":96
},
{
"ID":8478483,
"Name":"Mitchell Marner",
"Points":94
},
{
"ID":8478010,
"Name":"Brayden Point",
"Points":92
},
{
"ID":8476456,
"Name":"Jonathan Huberdeau",
"Points":92
},
{
"ID":8471218,
"Name":"Blake Wheeler",
"Points":91
},
{
"ID":8471214,
"Name":"Alex Ovechkin",
"Points":89
},
{
"ID":8475166,
"Name":"John Tavares",
"Points":88
},
{
"ID":8478420,
"Name":"Mikko Rantanen",
"Points":87
},
{
"ID":8478550,
"Name":"Artemi Panarin",
"Points":87
},
{
"ID":8473512,
"Name":"Claude Giroux",
"Points":85
},
{
"ID":8476460,
"Name":"Mark Scheifele",
"Points":84
},
{
"ID":8478427,
"Name":"Sebastian Aho",
"Points":83
},
{
"ID":8470613,
"Name":"Brent Burns",
"Points":83
},
{
"ID":8478403,
"Name":"Jack Eichel",
"Points":82
},
{
"ID":8477497,
"Name":"Sean Monahan",
"Points":82
},
{
"ID":8473548,
"Name":"Phil Kessel",
"Points":82
}
]
}
As I read in the docs linked above, now ajaxProcessing isn't necessary
OBJECT returned
In v2.11, the ajaxProcessing can just return the above object directly
(or don't even bother setting an ajaxProcessing function).
So, if an object is returned by the ajaxProcessing function, the data
is stored in table.config.pager.ajaxData
It seems to me that I misunderstood how to pass a page number parameter in .tablesorterPager. Tried a couple of different ways. For now, I would like the table to show the first page on the initial load. And then paginate back and forth using AJAX. Now it loads just the headers of the table. Not headers from JSON.
In this case, you will need to include an ajaxProcessing function.
If you want to completely leave out the ajaxProcessing function, then the JSON expected from the server should look like this (comments aren't allowed in JSON of course):
{
total: 100,
filteredRows: 100, // needed if using the filter widget
headers: [...], // optional
rows: [...]
}
or this
[
100, // total
[
[ "row1cell1", "row1cell2", ... "row1cellN" ],
[ "row2cell1", "row2cell2", ... "row2cellN" ],
...
[ "rowNcell1", "rowNcell2", ... "rowNcellN" ]
],
[ "header1", "header2", ... "headerN" ] // optional
]
#Mottie posted a great answer. And I'd like to add a couple of things that were wrong in my code.
In my players.views I didn't expect to get a value of the parameter 'page' = 0.
I forgot that tablesorter's rows indexation starts from 0. Then when pager made AJAX call with page=0 variable start was equal -25. It's obviously wrong. With utils. PAGE_SIZE_2 = 25 start = 25*(0 - 1). If page = 1 it start = 0. So I've just replaced page with page+1 and it works as expected.
The semicolon in this tablesorter initialization caused the SyntaxError: expected expression, got '.' So nothing in this file could be executed. With the semicolon removed it works.
$("table").tablesorter({
});
i'm working in Django, to draw some graph in function of my modules. ie: if i have 2 modules, i want 2 charts , if 8 modules, 8 charts.
Modules are integreted in a plugin. so i can list all modules found in a plugin. i did this in Django:
def plugin_graph(request, hub_uid, plugin_uid):
request.session['is_admin'] = True
hub = Hub.objects.get(mac=hub_uid)
fields = []
if request.method == 'GET':
if 'field' in request.GET:
fields.append(request.GET['field'])
plugin = Plugin.objects.get(uid=plugin_uid, hub=Hub.objects.get(mac=hub_uid))
#on recupere lensemble desmodules de ce plug
modules = plugin.get_modules_list()
#pour chak module, tracer son graph
for m in modules:
modules_list = {'uid':m.uid,
'name':m.name,
'version':m.version,
'type':m.type,
'time':m.last_time(),
'rssi':m.rssi(),
'status_icon':m.status_icon()}
module = Module.objects.get(uid=m.uid, plugin=Plugin.objects.get(uid=plugin_uid, hub=Hub.objects.get(mac=hub_uid)))
historic = sorted(ModuleDocument.objects.filter(module=module), key=getKey)
values = get_graph_values(historic=historic, dates=None, fields=fields)
print values
field = None if len(fields) < 1 else fields[0]
return render(request, "admin/graph2.html",
{
'values': values,
'hub': hub_uid,
'plugin': plugin_uid,
'uid': m.uid,
'module': module,
'fields': module.get_number_fields(),
'field': field,
'level': 'module',
}
)
After recovering all my modules i draw the charts like that in javascript:
<script>
var ctx = document.querySelector("#chart");
var data = JSON.parse('{{ values }}'.replace(/"/g, '"'));
var labels = [];
var values = [];
var beginAtZero = true;
for (obj in data) {
labels.push(data[obj].x);
values.push(data[obj].y);
if (data[obj].y < 0) {
beginAtZero = false;
}
}
var lineChart = new Chart(ctx, {
type: 'line',
data: {
labels: labels,
datasets: [{
label: "{{field}}",
data: values,
borderColor: '#97168F'
}]
},
options: {
scales: {
xAxes: [{
time: {
unit: 'day'
}
}],
yAxes: [{
ticks: {
beginAtZero:beginAtZero
}
}]
}
}
});
</script>
My problem is just one chart is printed. i would like to put it in a for loop to recuperate all data & labels of each module to draw normally all charts i want.
Thank u for your help
You need to utilise the forloop.counter method in the django template to dynamically name a number of items, otherwise the charts will just get overwritten each time and you are just left with the last chart. Its worth noting that I include this code in the block content not a specific js block.
In the example below, the items that are dynamically named are;
The container div
<div id = 'emg-{{forloop.counter}}'>
The canvas
<canvas id="myChart-{{forloop.counter}}">
The chart JS function name
var func_name = "container-{{forloop.counter}}";
func_name = new Chart(
document.getElementById('myChart-{{forloop.counter}}'),{
type: 'scatter',
I've included the full code below. I appreciate my example is different from your posted code, but it demonstrates the concept of dynamically creating certain elements.
{% for time, name_list, value_list, latitude_list, longitude_list, polyline_list in data %}
{% with forloop.counter as track_object_counter %}
<div id = 'emg-{{forloop.counter}}'>
{% for item in emg_data %}
{% if item.loop_id == track_object_counter %}
<div id = "emg-plot" style="height:15rem; padding-top: 1rem;">
<canvas id="myChart-{{forloop.counter}}">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
var y_data = {{item.data|strip_apostrophe}}
var x_data = '{{item.name}}'
var func_name = "container-{{forloop.counter}}";
func_name = new Chart(
document.getElementById('myChart-{{forloop.counter}}'),{
type: 'scatter',
data: {
datasets: [{
showLine: true,
label: x_data,
data: y_data,
backgroundColor: 'rgba(141,49,123,0.7)',
borderWidth: 2,
borderColor: 'rgba(141,49,123,0.7)'
}],
},
});
</script>
</canvas>
</div>
{% endif %}
{% endfor %}
</div>
{% endwith %}
{% endfor %}
I want to display some charts with Highcharts in my Symfony3 app.
To do this, I have to get some data from my DB and send it to Highcharts. Here comes the problem : I don't know how to do this. I created an array with all the data needed but I can't use it just by setting up a twig variable.
How can I send these datas ?
Here's my code :
Controller
$repartitionArray = [];
$statsArray = [];
for ($i = 0 ; $i < count($statscolle) ; $i++) {
$groupe[$i] = $statscolle[$i]->getGroupes()->getName();
$repartitionsColles[$i] = $em->getRepository(RepartitionColle::class)->findBy(['idStatColle' => $statscolle], ['note' => 'ASC']);
for ($j = 0; $j < count($repartitionsColles[$i]); $j++){
$note[$j] = $repartitionsColles[$i]->getNote();
$nombre[$j] = $repartitionsColles[$i]->getNombre();
$repartitionArray[$i] = [$note[$j], $nombre[$j]];
}
$statsArray[] = ["name" => $groupe[$i], "data" => $repartitionArray[$i]];
}
$series = $statsArray;
return $this->render("PACESStatistiqueBundle:Default:collapsideColle.html.twig", ["titre" => $colle->getNom(),
"data" => $series]);
Twig :
<div class="center">
<div id="repNotesColles" style="width:100%; height:400px;"></div>
</div>
{% block javascripts %}
<script src="http://code.highcharts.com/highcharts.js"></script>
<script>
$(document).ready(function () {
var repartitionNotesOfColle = Highcharts.chart('repNotesColles', {
chart: {
type: 'column'
},
title: {
text: {{ titre }}
},
xAxis: {
title: {
text: 'Notes'
}
},
yAxis: {
title: {
text: 'Effectif'
}
},
series:
});
})
</script>
{% endblock %}
Twig malforms my array keys and adds " " brackets to my array keys passed from a symfony2 controller.
The array is passed to a Javascript graphics library which requires:
[{x:"disabled test object",y:"17",label:"disabled test object"}]
Instead {{ array|json_encode|raw }} as suggested by the Twig docs and other SO questions I've read through returns the unreadable:
[{"x":"disabled test object","y":"17","label":"disabled test object"}]
I figure this should not be complicated to achieve but going through the json_encode options so far has not resulted in a clear answer. I am unsure whether there is something I can do from PHP so I've added the tag for now.
EDIT:
I am now attempting to step through the array manually using Twig. {{dump(points)}} confirms it is filled properly
{% set points = chart.dataPoints|json_encode|raw %} <=== this was the problem
dataPoints:
{% for point in points %}
{{ dump(point) }}
{ x: {{ point.x }}, y: {{ point.y }}, label: {{ point.label }} }
{% if loop.lastIndex != true %}
,
{% endif %}
{% endfor %}
But this does not work either as the dump is never reached. Is this the correct way of trying to access objects in a foreach via Twig though? This code is an amalgamation of several Twig docs tutorials.
EDIT 2, the solution:
The line {% set points = chart.dataPoints|json_encode|raw %} turned the whole array into a single string, making it impossible for javascript to interpret as an array. After removing this, all that was left was to make sure the query result had the proper array keys and to transform the X-axis data before passing it to Twig.
$i = 0;
$points = array();
/** #var array $query_result*/
foreach($query_result as &$row) {
foreach($row as $value) {
$point[] = [
'x' => ($i)*10,
'y' => $value['y'],
'label' => $value['label']
];
$points[$i] = $point;
$i++;
}
}
Quotes are not a problem for CanvasJS. As you can see in the example below, "x": works (I took this example):
window.onload = function () {
var chart = new CanvasJS.Chart("chartContainer",
{
title:{
text: "Top Oil Reserves"
},
data: [{
dataPoints: [
{ x: 1, y: 297571, label: "Venezuela"},
{ "x": 2, "y": 267017, label: "Saudi" }
]
}]
});
chart.render();
}
<!DOCTYPE HTML>
<html>
<head>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/canvasjs/1.7.0/canvasjs.min.js"></script>
</head>
<body>
<div id="chartContainer" style="height: 200px; width: 100%;"></div>
</body>
</html>
So we need to provide a JSON array for dataPoints:
Define the PHP array in your Controller's function and pass it to the template:
public function myAction()
{
// …
$json = array(
array(
'x' => 1,
'y' => 297571,
'label' => 'Venezuela',
),
array(
'x' => 2,
'y' => 267017,
'label' => 'Saudi',
),
);
return array(
// …
'json' => $json,
);
}
Display the array in the template and pass it to dataPoints :
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/canvasjs/1.7.0/canvasjs.min.js"></script>
<script>
var json = {{ json|json_encode|raw }};
window.onload = function () {
var chart = new CanvasJS.Chart("chartContainer",
{
title:{
text: "Top Oil Reserves"
},
data: [{
dataPoints: json
}]
});
chart.render();
}
</script>
<div id="chartContainer" style="height: 300px; width: 100%;"></div>
The rendered output will be:
// …
<script>
var json = [{"x":1,"y":297571,"label":"Venezuela"},{"x":2,"y":267017,"label":"Saudi"}];
// …
CanvasJS will be able to read this JavaScript array and display the chart.
It looks like the first one is a JavaScript object, and the second one is JSON, try running JSON.parse on the string like this to convert it back in to an object:
var fixed = JSON.parse('[{"x":"disabled test object","y":"17","label":"disabled test object"}]');
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse