charts.js chart showing old data on hover - chart.js

I know this question has been asked a fair bit on here, but the solution that's been going around hasn't been working for me.
My chart renders in real time based on the start/end dates provided on the front-end (HTML date input widget). Since the default start time for that widget is 01/01/2019, charts.js will create a chart for the 01/01/2019 start/end times, and then proceed to produce the chart for the specified start/end dates. The 01/01/2019 chart shows on hover and I'd like to get rid of it.
function dayChart(labels_day, data_day) {
var ctx_day = document.getElementById('day-chart').getContext('2d');
var config = {
type: 'bar',
data: {
labels: labels_day,
// labels: ['Early Morning (00:00-05:59)', 'Morning (06:00-11:59)', 'Afternoon (12:00-18:59)', 'Night (19:00-23:59)'],
datasets: [{
label: '# of Orders by Day of the Week',
data: data_day,
backgroundColor: [
'rgba(255, 99, 132, 0.2)',
'rgba(54, 162, 235, 0.2)',
'rgba(255, 206, 86, 0.2)',
'rgba(75, 192, 192, 0.2)',
'rgba(153, 102, 255, 0.2)',
'rgba(255, 159, 64, 0.2)',
'rgba(255, 99, 132, 0.2)',
],
borderColor: [
'rgba(255, 99, 132, 1)',
'rgba(54, 162, 235, 1)',
'rgba(255, 206, 86, 1)',
'rgba(75, 192, 192, 1)',
'rgba(153, 102, 255, 1)',
'rgba(255, 159, 64, 1)',
'rgba(255, 99, 132, 1)',
],
borderWidth: 1
}]
},
options: {
responsive: true,
title: {
display: true,
text: 'Taxi Orders calculated by day of the week'
},
scales: {
yAxes: [{
id: data_day,
display: true,
ticks: {
stepsize: 500,
min: 0,
}
}]
},
}
}
var chart_day = new Chart (ctx_day, config)
if (chart_day) {
chart_day.destroy();
chart_day = new Chart (ctx_day, config);
chart_day.update();
}
}
form section of html file
<form name="date-input" method="POST" action="/date-input/">
<label for="start">Start date:</label>
<input type="date" id="start" name="start_date" value="2019-01-01" min="2019-01-01" max="2019-06-30">
<label for="end">End date:</label>
<input type="date" id="end" name="end_date" value="2019-01-01" min="2019-01-01" max="2019-07-01">
</form>
async call for start/end dates
<script src="https://code.jquery.com/jquery-3.5.1.js"></script>
<script>
// global variables - so that chartjs doesn't show old data on hover
var chart_day;
var chart_hour;
var chart_payment;
var start_date = Date.parse($("#start").val()) / 1000; // const can't be re-assigned or redeclared. Date() to convert datetime into epoch time
var end_date = Date.parse($("#end").val()) / 1000; // divide by 1000 to get 10 digits (compatible with values in DB)
function ajaxRequest() {
var start_date = Date.parse($("#start").val()) / 1000;
var end_date = Date.parse($("#end").val()) / 1000;
console.log(start_date)
console.log(end_date)
$.ajax({ // initialize an AJAX request
type: "POST",
url: '/get-data/', // calls data from /date-input/ endpoint
data: {
'start_date': start_date, // add the order id to the POST parameters
'end_date': end_date,
'csrfmiddlewaretoken': "{{csrf_token}}",
},
success: function (data) { // `data` is from `homepage` view function
labels_payment = data.labels_payment
data_payment = data.data_payment
labels_hour = data.labels_hour
data_hour = data.data_hour
labels_day = data.labels_day
data_day = data.data_day
// call payment, hour, and dow methods to render in the frontend
setPaymentMethodChart(labels_payment, data_payment)
hourlyChart(labels_hour, data_hour)
dayChart(labels_day, data_day)
// console.log(data)
console.log(data.start_date)
console.log(data.end_date)
console.log(data.labels_payment)
console.log(data.data_payment)
},
error: function (data, xhr, status, error) {
console.log("yikes")
}
});
}

You correctly defined chart_day globally and even commented it as follows:
global variables - so that chartjs doesn't show old data on hover
The problem however lies in your dayChart function. Instead of using the globally defined chart_day variable, you defined an identically named variable in the local scope of the function.
The solution would be to replace the following code block:
var chart_day = new Chart (ctx_day, config)
if (chart_day) {
chart_day.destroy();
chart_day = new Chart (ctx_day, config);
chart_day.update();
}
...with this one:
if (chart_day) {
chart_day.destroy();
}
chart_day = new Chart (ctx_day, config);

Related

How to write a query to get the number of times a choice is selected by users

I want to count the number of times a choice is selected by users. Here is my model
models.py
from django.db import models
from django.utils import timezone
from django.contrib.auth.models import User
from multiselectfield import MultiSelectField
class MedicalHistory(models.Model):
Anxiety = 'Anxiety'
Arthritis = 'Arthritis'
Asthma = 'Asthma'
Anemia = 'Anemia'
Cancer = 'Cancer'
Corona_virus = 'Corona_virus'
Diabetes = 'Diabetes'
Ebola = 'Ebola'
HIV = 'HIV'
ILLNESS_CHOICES = (
(Anxiety, "Anxiety"),
(Arthritis, "Arthritis"),
(Asthma, "Asthma"),
(Anemia, "Anemia"),
(Cancer, "Cancer"),
(Corona_virus, "Corona_virus"),
(Diabetes, "Diabetes"),
(Ebola, "Ebola"),
(HIV, "HIV"),
)
user = models.ForeignKey(User, on_delete=models.CASCADE)
illness = MultiSelectField(choices=ILLNESS_CHOICES, max_length=50)
symptoms = models.CharField(max_length=100)
additional_info = models.CharField(max_length=100)
disability = models.BooleanField(default=False)
medications = models.BooleanField(default=False)
created_at = models.DateTimeField(default=timezone.now)
def __str__(self):
return f'{self.user.username} Medical History'
Here I have a number of illnesses I want users to select from. A user can select more tan one illness. I want each illness to have a count, and every time the illness is selected it adds to the count. In my view I have
views.py
def pie_chart(request):
labels = []
data = []
queryset = MedicalHistory.objects.values('illness').annotate(count=Sum('user')).order_by('-count')
for entry in queryset:
labels.append(entry['illness'])
data.append(entry['count'])
return JsonResponse(data={
'labels': labels,
'data': data,
})
<QuerySet [{'illness': ['Asthma', 'Diabetes', 'Ebola'], 'count': 3}, {'illness': ['Anemia', 'Covid-19'], 'count': 2}]>
The query doesn't do what I want, as I am trying to plot it in a chart. It groups the illness I think because of the values. My template.html looks like this.
chart.html
{% block content %}
{% include 'sidebar.html' %}
<div id="container" style="width: 85%;">
<canvas id="myChart"></canvas>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js#2.9.3/dist/Chart.min.js"></script>
<script>
var ctx = document.getElementById('myChart');
var myChart = new Chart(ctx, {
type: 'bar',
data: {
labels: {{ labels|safe }},
datasets: [{
label: 'Overview of Medical History',
data: {{ data|safe }},
backgroundColor: [
'rgba(255, 99, 132, 0.2)',
'rgba(54, 162, 235, 0.2)',
'rgba(255, 206, 86, 0.2)',
'rgba(75, 192, 192, 0.2)',
'rgba(153, 102, 255, 0.2)',
'rgba(225, 400, 64, 0.2)'
],
borderColor: [
'rgba(255, 99, 132, 1)',
'rgba(54, 162, 235, 1)',
'rgba(255, 206, 86, 1)',
'rgba(75, 192, 192, 1)',
'rgba(153, 102, 255, 1)',
'rgba(255, 159, 64, 1)'
],
borderWidth: 1
}]
},
options: {
scales: {
yAxes: [{
ticks: {
beginAtZero: true
}
}]
}
}
});
</script>
</div>
<!-- /#page-content-wrapper -->
</div>
<!-- /#wrapper -->
{% endblock %}
Solution:
So I was able to solve this using python. I initialized a dict with the illnesses and setting their initial count as 0. Then I used values() returns a dictionary of illnesses saved in the db. The I looped through the queryset and concatenate 1 to the values of the illnesses. Then i passed the keys and values as labels and data respectively.
views.py
def pie_chart(request):
count = {'Anxiety': 0, 'Arthritis': 0, 'Asthma': 0, 'Anemia': 0, 'Cancer': 0,
'Corona_virus': 0, 'Diabetes': 0, 'Ebola': 0, 'HIV': 0
}
queryset = MedicalHistory.objects.values('illness')
for entry in queryset:
for values in entry['illness']:
count[values] += 1
labels = [*count.keys()]
data = [*count.values()]
return render(request, 'chart.html', {
'labels': labels,
'data': data,
})
You can't do that at the database level if you're using a MultiSelectField because the underlying value is just stored as CharField in the database. So values('illness') will only group by equal strings, i.e. combinations of illnesses.
So either do this in python, after you've fetched all the instances: Initialize a dict of counters, loop through the instances in your queryset and update the counters one by one. Note: This won't scale when the number of MedicalHistory rows in your table becomes large.
Or model your Illness as a separate model (don't use MultiSelectField), use a ManyToMany relationship and count those. Then you can do this in a queryset. This will scale with a very large number of rows.

chart.js - Axis lables

I am trying to figure out how to create some custom axis lables for my data. Im trying to plot time series data. Im using an XY scatter graph to do this. My data collection may be irregular..
My time is recorded as Unix Epoch time. ( ms after 1 Jan 1970 ). I'd like to be able to put human readable dates/times on the x axis. rather than display the epoch time. How would this be best approached?
http://6phrweatherstation.s3-website-ap-southeast-2.amazonaws.com
All you have to do is to add the custom tick callback.
You can go through this in the chartjs documentation. Here have a look: link.
The callback provides you the value of each label. You can edit it and return the edited value.
function renderChart(data, labels) {
var ctx = document.getElementById("myChart").getContext('2d');
var myChart = new Chart(ctx, {
type: 'scatter',
data: {
labels: labels,
datasets: [{
label: '48 Hours Pressure History HPa',
data: data,
borderColor: 'rgba(75, 192, 192, 1)',
backgroundColor: 'rgba(75, 192, 192, 0.2)',
},
]
},
options: {
scales: {
xAxes: [{
ticks: {
callback: function(value) {
return new Date(value).getDate() +"-"+ new Date(value).getMonth() + "-"+new Date(value).getFullYear();
}
}
}]
}
}
});
}

Django and ChartJS

I'm trying to understand if it it's possible to incorporate dynamic data into a Django Chart JS architecture. I went through a couple of tutorials and ultimately got Django to work with ChartJS and it's very good when I'm able to hard code values and then display the related graphs. What I'm ultimately trying to do is this same exercise with dynamic data from my database. I found this identical question in SO, https://stackoverflow.com/questions/47575896/dynamic-chart-using-django-and-chart-js#= but it wasn't answered by anyone. This is exactly what I'm trying to achieve. I've explore serializers a bit, do I need to serialize the data first? Thanks in advance for your thoughts and feedback.
Per the feedback, I have added context to the chart in question but the data is still not coming through. Here is my view:
class ChartView(LoginRequiredMixin, TemplateView):
model = Myobject
template_name = 'director/chart.html'
def get_context_data(self, **kwargs):
context = super(ChartView, self).get_context_data(**kwargs)
myobject = Myobject.objects.filter(field__in=self.request.user.userprofile.field.all())
print(myobject)
context['myobject'] = myobject
return context
I'm just getting a blank screen, no chart at all, suggesting that something is obviously amiss. Do I need to make additional changes to the Javascript in order to tell it about the object? My assumption is no, that I'm passing this information view the context_data.
I'm also using Ajax, so I'm not sure if that is a complicating factor. Here is my javascript.
<script>
var endpoint = '{% url "myobject:chart_data" %}'
var defaultData = [];
var labels = [];
$.ajax({
method: "GET",
credentials: 'same-origin',
url: endpoint,
success: function(data){
labels = data.labels
defaultData = data.default
var ctx = document.getElementById("myChart");
var myChart = new Chart(ctx, {
type: 'pie',
data: {
labels: [{% for i in myobject %}{{ i.labels }},{% endfor %}],
datasets: [{
data: [{% for i in myobject %}{{ i.data }},{% endfor %}]
backgroundColor: [
'rgba(255, 99, 132, 0.2)',
'rgba(153, 102, 255, 0.2)',
],
borderColor: [
'rgba(255,99,132,1)',
'rgba(153, 102, 255, 1)',
],
borderWidth: 1
}]
}
})
},
error: function(error_data){
console.log("error")
console.log(error_data)
}
})
</script>
You don't necessarily need to use serializers to render dynamic data in Chart.js (although you can if you would like). You can just iterate through your Django context variables like normal in the appropriate locations. Below is a line chart example. I would play around with this example, but this should show you how to easily render dynamic data with Django context variables.
...
<canvas id="funchart" width="75" height="50"></canvas>
<script type="text/javascript">
var a = document.getElementById('funchart').getContext('2d');
var myLineChart = new Chart(a, {
type: 'line',
data: {
labels:[{% for i in myobject %}{{ i.labels }},{% endfor %}],
datasets: [{
label:'My Dot',
data: [{% for i in myobject %}{{ i.data }},{% endfor %}]
}]
},
options:{
scales: {
xAxes: [{
display:true
}],
yAxes: [{
ticks: {
beginAtZero:true
}
}]
}
}
});
</script>
so i have been working the same stuff here, i think your label not showing up because the label on chart.js only wants a string, so try this on your labels:
labels: [{% for i in book %}"{{ i.id }}",{% endfor %}]
My code below is what ultimately solved the problem on how to get dynamic user data. I am still trying to figure out how to get the labels to work properly. This solution allows the labels to display with ID numbers, which isn't optimal, but I opened a separate SO for the label issue.
My HTML
{% extends 'base5.html' %}
{% block body_block %}
<div class="box6">
<h1 class="title">Number of Books By Author</h1>
<canvas id="myChart"></canvas>
<div>
<script>
var endpoint = '{% url "Books:chart_data" %}'
var defaultData = [];
var labels = [];
array = {{ procedures3 }}
$.ajax({
method: "GET",
credentials: 'same-origin',
url: endpoint,
success: function(data){
defaultData = data.default
var ctx = document.getElementById("myChart");
var myChart = new Chart(ctx, {
type: 'bar',
data: {
labels: [{% for i in book %}{{ i.id }},{% endfor %}],
datasets: [{
label: "# of Procedures",
data: [{% for j in book_count %}
{{ j }},
{% endfor %}],
backgroundColor: [
'rgba(255, 99, 132, 0.2)',
'rgba(255, 99, 132, 0.2)',
'rgba(54, 162, 235, 0.2)',
'rgba(255, 206, 86, 0.2)',
'rgba(75, 192, 192, 0.2)',
'rgba(153, 102, 255, 0.2)',
'rgba(255, 159, 64, 0.2)'
],
borderColor: [
'rgba(255,99,132,1)',
'rgba(255,99,132,1)',
'rgba(54, 162, 235, 1)',
'rgba(255, 206, 86, 1)',
'rgba(75, 192, 192, 1)',
'rgba(153, 102, 255, 1)',
'rgba(255, 159, 64, 1)'
],
borderWidth: 1
}]
}
})
},
error: function(error_data){
console.log("error")
console.log(error_data)
},
})
</script>
{% endblock %}
Views.py
ChartView
class ChartData(LoginRequiredMixin,APIView):
model = Author
authentication_classes = (SessionAuthentication, BasicAuthentication)
permission_classes = (IsAuthenticated,)
def get(self, request, format=None):
default_items = []
labels = []
data = {
"labels": labels,
"default": default_items,
}
return Response(data)
The ChartView
class ChartView(LoginRequiredMixin, TemplateView): template_name = 'Book/chart.html'
def get_context_data(self, **kwargs):
context = super(ChartView, self).get_context_data(**kwargs)
book = Author.objects.filter(id__in=self.request.user.userprofile.author.all()).order_by('id')
books_count = [ Book.objects.filter(author=cls).count() for cls in book ]
context['book'] = book
context['book_count'] = books_count
return context

updating chart.js in ractive

For some data visualisation I use ractive and chart.js. The initial drawing works great, but I can't find a way to update the chart automatically when my data changes. So far I got (simplified):
const Stats = '<canvas id="myChart" width="400" height="400"></canvas>'
new Ractive ({
el: '#stats',
template: Stats,
magic: true,
modifyArrays: true,
data: {docs}, // <= some JSON Data
computed: {
Data1() {
let tempList = this.get('docs');
// rearrange & filter Data
return tempList ;
},
Data2() {
let tempList2 = this.get('docs');
// rearrange & filter Data
return tempList2 ;
},
Data3() {
let tempList3 = this.get('docs');
// rearrange & filter Data
return tempList3 ;
},
}
},
onrender: function () {
let DataSet1 = this.get('Data1');
let DataSet2 = this.get('Data2');
let DataSet3 = this.get('Data3');
let ctx = document.getElementById("myChart");
var myChart = new Chart(ctx, {
type: 'doughnut',
data: {
labels: ["Data 1", "Data 1", "Data 3"],
datasets: [{
label: 'All my Data',
data: [DataSet1.length, DataSet2.length, DataSet3.length],
backgroundColor: [
'rgba(255, 99, 132, 0.2)',
'rgba(255, 159, 64, 0.2)',
'rgba(75, 192, 192, 0.2)'
],
borderColor: [
'rgba(255,99,132,1)',
'rgba(255, 159, 64, 1)',
'rgba(75, 192, 192, 1)'
],
borderWidth: 1
}]
},
options: {
responsive: false
}
});
},
onchange: function () {
let newData = this.get('docs')
addData(myChart, label, newData)
function addData(chart, label, data) {
chart.data.labels.push(label);
chart.data.datasets.forEach((dataset) => {
dataset.data.push(data);
});
chart.update();
}
}
});
Of Course I get an error in line chart.data.labels.push(label);, and also I'm quite sure that I would need my computed values in the onrenderfunction, not the initial Dataset. But I really have no clue how to get those into the update function, or if this function is the correct approach at all...
If you are working with 3rd party plugins I would recommend to use Ractive's decorators.
This post on ractivejs-and-jquery-plugins shows you a starting point how to implement a decorator based on a fileupload control.
In your case I would recommend to build a decorator with the data as parameter and do not forget to implement the UPDATE function (where in your case you gonna call the update method with the new data for your chart)
As I could not figure out how to write my own decorator for Chart.js I thought I post the solution that worked for me. I do not think it's best practice and for sure decorators would be a better way (therefore I do not mark this solution as correct answer), but it works:
const Stats = '<canvas id="myChart" width="400" height="400"></canvas>'
new Ractive ({
el: '#stats',
template: Stats,
magic: true,
modifyArrays: true,
data: {docs}, // <= some JSON Data
computed: {
Data1() {
let tempList = this.get('docs');
// rearrange & filter Data
return tempList ;
},
Data2() {
let tempList2 = this.get('docs');
// rearrange & filter Data
return tempList2 ;
},
Data3() {
let tempList3 = this.get('docs');
// rearrange & filter Data
return tempList3 ;
},
}
},
onrender: function () {
let DataSet1 = this.get('Data1');
let DataSet2 = this.get('Data2');
let DataSet3 = this.get('Data3');
let ctx = document.getElementById("myChart");
myChart = new Chart(ctx, {
type: 'doughnut',
data: {
labels: ["Data 1", "Data 1", "Data 3"],
datasets: [{
label: 'All my Data',
data: [DataSet1.length, DataSet2.length, DataSet3.length]
}]
}
});
},
onchange: function () {
let changedData1 = this.get('Data1');
let changedData2 = this.get('Data2');
let changedData3 = this.get('Data3');
myChart.data.datasets[0].data[0] = changedData1.length;
myChart.data.datasets[0].data[1] = changedData2.length;
myChart.data.datasets[0].data[2] = changedData3.length;
myChart.update();
}
}
});

Angular- Chart.js, how off opacity in line chart?

guys, how can i change opacity in line chart?? Look at my chart:
red, gray, and blue background. I need make it 100% invisible.
Here is my code:
<canvas id="line" class="chart chart-line" chart-data="data2" chart-labels="labels2" chart-series="series2" chart-options="options2"
chart-dataset-override="datasetOverride2" chart-click="onClick" chart-colours="colours">
</canvas>
And fragment of JS:
$scope.rysujWykres2 = function (daneX, daneY, daneZ, daneA) { //metoda rysujÄ…ca wykres
$scope.labels2 = daneX;
$scope.series2 = ['Ur', 'Ul', 'Uz'];
$scope.data2 = [
daneY,
daneZ,
daneA
];
$scope.datasetOverride2 = [{ yAxisID: 'y-axis-1' }];
$scope.options2 = {
scales: {
yAxes: [
{
id: 'y-axis-1',
type: 'linear',
display: true,
position: 'left'
}
]
},
elements: {
point: {
radius: 1
}
},
colours: [{
fillColor: 'rgba(47, 132, 71, 0.8)',
strokeColor: 'rgba(47, 132, 71, 0.8)',
highlightFill: 'rgba(47, 132, 71, 0.8)',
highlightStroke: 'rgba(47, 132, 71, 0.8)'
}]
};
}
but it doesnt work. Can u help me off this colours? I need only lines.
If you do not want to see the fill under the line, then set the fill property to false (this is part of a line chart dataset configuration). I'm not exactly sure how to do this in angular chart.js, but it looks like you could add it to your datasetOverride2 object.
$scope.datasetOverride2 = [{fill: false}, {fill: false}, {fill: false}];
If this doesn't work then you need to use the property wherever you define each dataset.
My friend,
$scope.datasetOverride2 = [{
yAxisID: 'y-axis-1',
fill: false,
}];
work but only for blue color date, look:
aww, its so easy. Look:
Solution:
$scope.datasetOverride2 = [{fill: false},{fill: false},{fill: false}];
Thanks guys for help. We did it!