django chart.js can't use {{% for loop%}} - django

I am a newbie to django, I don’t know why I can use for loop in javascript in other people’s teaching, but I can’t use for loop in javascript like this. Hope someone can tell me why.
<script>
var ctx = document.getElementById('myChart').getContext('2d');
var myChart = new Chart(ctx, {
type: 'bar',
data: {
labels: [{% for i in myobject %}"{{ labels }}",{% endfor %}],
datasets: [{
label: '# of Votes',
data: '{{ data }}',
}]
},
});
</script>

Related

Charts.js is automatically adding comma as thousands separator when it shouldn't

Charts.js is abusively adding comma separator between thousands while is nowhere set to do this. To all floats. To the axis, labels, tooltips, everywhere possible and on all types of charts.
I am in EU, so the computer, browser, locales have nothing to do with EN/US. Nowhere in the code is specified to add these commas to numbers.
The underlying data does not contain commas either:
[{continent:"Africa",value:14802.32},{continent:"Asia",value:6783.37},...]
This is very annoying, unacceptable for my (French) customer.
Is it a bug ? Or please be kind and provide the way how to remove comma everywhere.
You must be doing something wrong because by default the data is seperated by dots, so you must have specified it somewhere. But you can configure it by using the locale property:
var options = {
type: 'line',
data: {
labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
datasets: [{
label: '# of Votes',
data: [12000.5, 19000, 3000, 5000, 2000, 3000],
borderColor: 'pink'
}]
},
options: {
//locale: 'en-EN' // Uncomment this line for "wrong" options
}
}
var ctx = document.getElementById('chartJSContainer').getContext('2d');
new Chart(ctx, options);
<body>
<canvas id="chartJSContainer" width="600" height="400"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.5.1/chart.js"></script>
</body>

ChartJS doesnt display labels provided by Jinja variable

I would like to use Flask to render a ChartJS graph. This is from my routes.py
#app.route("/graph")
def graph():
d = Eval.query.all()
labels = [i.date.strftime('%d.%m.%Y') for i in d]
data = [i.bw for i in d]
return render_template('chart.html', title='Charts', labels = labels, data = data)
I've the following code in the chart.html:
{% extends "base.html" %}
{% block content %}
<canvas id="myChart" height="400" width="800"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.4.0/Chart.min.js"></script>
<script type="text/javascript">
var ctx = document.getElementById('myChart').getContext('2d');
var chart = new Chart(ctx, {
type: 'line',
data: {
labels: ['16.07.2017', '23.07.2017', '30.07.2017', '06.08.2017', '13.08.2017'],
datasets: [{
label: "My First dataset",
backgroundColor: 'rgb(255, 99, 132)',
borderColor: 'rgb(255, 99, 132)',
data: {{ data }}
}]
},
options: {
responsive:false
}
});
</script>
{% endblock %}
with this code the chart is rendered with the correct labels.
Question:
However once I change the labels to
labels: {{ labels }}
the chart is not rendered at all.
I checked to make sure that the {{ labels }} expression is indeed also a list with the correct formating. Calling {{ labels }} above the script tag shows the following output in the html page:
['16.07.2017', '23.07.2017', '30.07.2017', '06.08.2017', '13.08.2017']
If you are not using safe filter special symbols would be escaped during template parsing process and would corrupt the variable value.
In the case above, final variable value without 'jinja safe' filter would be
["16.07.2017", "23.07.2017"] =>['16.07.2017','23.07.2017']
And that value (['16.07.2017','23.07.2017']) is illegal for JS and you'll be able to see the error in the browser console:
Uncaught SyntaxError: Unexpected token &
In the case above, final variable value with 'jinja safe' filter would be the same
["16.07.2017", "23.07.2017"] =>["16.07.2017", "23.07.2017"]
You can read more about escaping here.
var ctx = document.getElementById('myChart').getContext('2d');
var dates = {{labels|safe}};
var data1 = {{data|safe}}
var chart = new Chart(ctx, {
type: 'line',
data: {
labels: dates,
datasets: [{
label: "My First dataset",
backgroundColor: 'rgb(255, 99, 132)',
borderColor: 'rgb(255, 99, 132)',
data: data1
I added the Jinja expressions as variables inside the script and now the chart renders as expected. Still not sure why ...

Making a row for labels in a Google visualisation

I've got a Google chart timeline showing the start times for various parts of a project and what the lag will be in us being able to measure the effects of the project. Currently my labels are tiny because Google insists on putting the labels to the left, right, or inside the box. I could make my chart a lot clearer if the labels were in a blank row beneath the boxes. Before I hack away adding blank lines then edit it in paint, is there a sensible way to move where the labels appear, and to add blank lines?
Example:
<html>
<head>
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
<script type="text/javascript">
google.charts.load('current', {'packages':['timeline']});
google.charts.setOnLoadCallback(drawChart);
function drawChart() {
var container = document.getElementById('timeline');
var chart = new google.visualization.Timeline(container);
var dataTable = new google.visualization.DataTable();
dataTable.addColumn({ type: 'string', id: 'Project' });
dataTable.addColumn({ type: 'string', id: 'Name' });
dataTable.addColumn({ type: 'date', id: 'Start' });
dataTable.addColumn({ type: 'date', id: 'End' });
dataTable.addRows([
['Project A', 'Start Project', new Date(2017,0), new Date(2017,1)],
['Project B', 'Start Project', new Date(2027,0), new Date(2027,1)],
[ 'Project C', 'Start project', new Date(2018, 3), new Date(2018, 4) ],
[ 'Project C', 'Some stuff happens before the official start of project', new Date(2018,0), new Date(2018,1)],
[ 'Project C' , 'Observable statistic has changed due to project', new Date(2018,7), new Date(2018,8)]
])
var options = {
timeline: { colorByRowLabel: true ,
barLabelStyle: {fontSize:8},
rowLabelStyle: {fontSize:10}
}
};
chart.draw(dataTable, options);
}
</script>
</head>
<body>
<div id="timeline" style="height: 1800px;"></div>
</body>
</html>

EmberJS computed sort property conflicting with jQuery UI Sortable

I am having an issue with getting drag-and-drop sort functionality with EmberJS working.
The tutorials that cover this issue don't provide any help on the initial sort, which I am doing through a computed property.
What I'm encountering seems to be a race condition between ember's rerender when the sortedItems computed property changes and jQueryUI Sortable updating the DOM. List Items get duplicated, or disappear altogether upon sorting.
Route
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return Ember.Object.create({
id: 1,
title: "Title",
subtitle: "Subtitle",
items: [
Ember.Object.create({
name: "Item 2",
sortOrder: 2,
id: 1
}),
Ember.Object.create({
name: "Item 1",
sortOrder: 1,
id: 2
}),
Ember.Object.create({
name: "Item 3",
sortOrder: 3,
id: 3
})
]
});
}
});
Controller
import Ember from 'ember';
export default Ember.ObjectController.extend({
sortProperties: [ 'sortOrder' ],
sortedItems: Ember.computed.sort('model.items', 'sortProperties'),
actions: {
updateSortOrder: function(indexes) {
this.get('items').forEach(function(item) {
var index = indexes[item.get('id')];
item.set('sortOrder', index);
});
}
}
});
View
import Ember from 'ember';
export default Ember.View.extend({
didInsertElement: function() {
var controller = this.get('controller');
this.$(".item-list").sortable({
update: function(event, ui) {
var indexes = {};
$(this).find('li').each(function(index) {
indexes[$(this).data('id')] = index;
});
controller.send('updateSortOrder', indexes);
}
})
}
});
Template
<h1>{{ title }}</h1>
<h2>{{ subtitle }}</h2>
<ul class="item-list">
{{#each item in sortedItems }}
<li data-id="{{ unbound item.id }}">
name: {{ item.name }}<br />
sortOrder: {{ item.sortOrder }}<br />
id: {{ item.id }}
</li>
{{/each}}
</ul>
Here's a Barebones Ember app that reproduces the issue: https://github.com/silasjmatson/test-sortable
Question
Is there a way to avoid this race condition or sort only once when the controller is initialized?
Apologies if there is already an answer on the interwebs for this. I haven't been able to resolve this despite a week of searching/experimenting.
You can sort only once when the controller is initialised:
sortedItems: Ember.computed(function() {
return Ember.get(this, 'model.items').sortBy('sortOrder');
})
Because you're using the computed sort property it is attempting to sort the list for you whenever the sortOrder property changes on any item, which is rerendering the #each and doing something funky.
Just sort once initially using the method above and then let jQuery handle the order of items - rather than jQuery and Ember.

Nested View Not Updating Ember.js

Some background before I get right to the problem. I've got some data (random numbers in this case), and I will need to be able to visualize this data in multiple ways. I've only implemented a table and line view in the fiddle, in prod I will have more ways to visualize the data (pie, bar, etc...), and there will be multiple sections.
Here is the fiddle.
I can correctly change the type I want to display, but I can't seem to get the view to update whenever I update the nested view. I'm probably missing something really easy, so the title of this question maybe loaded. If that's the case I apologize, but I'd greatly appreciate any help.
Handlebars:
<script type="text/x-handlebars" data-template-name="index">
{{#each App.Controller.content}}
{{#view App.ChartTypeContainer contentBinding="this"}}
{{#each chartTypesWithSelected}}
<a href="#" {{action switchChartType this target="view"}}>
{{#if selected}}
<strong>{{display}}</strong>
{{else}}
{{display}}
{{/if}}
</a>
{{/each}}
{{/view}}
{{#if currentView}}
{{view currentView}}
{{/if}}
{{/each}}
</script>
<script type="text/x-handlebars" data-template-name="table">
<table>
<thead>
<tr>
{{#each view.data.headings}}
<th>{{name}}</th>
{{/each}}
</tr>
</thead>
<tbody>
{{#each view.data.data}}
<tr>
{{#each values}}
<td>{{this}}</td>
{{/each}}
</tr>
{{/each}}
</tbody>
</table>
</script>
<script type="text/x-handlebars" data-template-name="line">
</script>
js:
App = Em.Application.create({
getRandomData: function(){
// Generate between 1-3 random headings
var headings=[],
headingCount = (Math.floor(Math.random() * 5) + 1),
data=[];
for(var i = 0; i < headingCount; i++){
headings.push({name: 'heading ' + i});
}
// Generate between 5 and 10 rows of random data
for(var i = 0; i< (Math.floor(Math.random() * 10) + 5);i++){
var values = [];
for(var j=0; j< headingCount;j++){
values.push((Math.floor(Math.random() * 100) + 1));
}
data.push({values: values});
}
return {headings: headings, data: data};
},
ready: function(){
Em.View.create({templateName:'index'}).appendTo('body');
}
});
App.chartFactory = Em.Object.create({
create: function(key, data){
switch(key){
case 'table':
return App.TableView.create({data: data || App.getRandomData()});
case 'line':
return App.LineView.create();
default:
return;
}
}
});
/* MODELS */
App.ChartType = Em.Object.extend({
key: '',
display: ''
});
App.Section = Em.Object.extend({
title: '',
chartTypes: [],
chartTypesWithSelected: function(){
var currentSelected = this.get('selectedChartType');
var types = this.get('chartTypes');
var thing = types.map(function(item){
item.set('selected', item.key === currentSelected);
return item;
});
return thing;
}.property('chartTypes.#each', 'selectedChartType'),
data: {},
selectedChartType: '',
selectedChartTypeObserver: function() {
var selectedChartType = this.get('selectedChartType');
alert('changin chart type to: ' + selectedChartType);
App.chartFactory.create(selectedChartType);
}.observes('selectedChartType'),
currentView: null
});
/* VIEWS */
App.ChartTypeContainer = Em.View.extend({
switchChartType: function(chartType) {
this.get('content').set('selectedChartType', chartType.key);
}
})
App.TableView = Em.View.extend({
templateName: 'table',
data: {}
});
App.LineView = Em.View.extend({
templateName:'line',
data: {},
didInsertElement: function(){
var data = App.getRandomData();
var headings = data.headings.map(function(item){
return item.name;
});
var series = data.data.map(function(item){
return {data: item.values};
});
this.$().highcharts({
title: null,
series: series,
xAxis: {categories: headings},
yAxis: {min: 0, max: 100, title: {text: 'Random Numbers'}}
});
}
})
/* CONTROLLER */
App.Controller = Em.Object.create({
content: [
App.Section.create({
title: 'First Section',
chartTypes: [
App.ChartType.create({key: 'table', display: 'Table Display'}),
App.ChartType.create({key: 'line', display: 'Line Display'})
],
selectedChartType: 'table', // CHANGE HERE TO SEE THE OTHER VIEW, use 'line' or 'table'
currentView: App.chartFactory.create('table') // CHANGE HERE TO SEE THE OTHER VIEW, use 'line' or 'table'
})
]
});
UPDATE:
Setting the newly created view on the next run cycle using Ember.run.next seems to produce the required behavior correctly. Updated Fiddle
I suggest taking a look at the official ember guides. They've gotten really good lately and can shed some light on best practices and ways to not "fight" the framework.
I forked your fiddle and provided something that is closer to "the true ember way" when it comes to showing multiple views for the same underlying data as you are trying to do. In my opinion, manually creating and/or attaching views to the DOM is an anti-pattern in ember.
The basic approach is having a resource represent your data, and then routes under that resource that map to the templates that you want to render. What I've done is provide a template for the resource that merely has links to the two routes beneath it and an {{outlet}} into which those templates will get rendered. I removed your manual creation and swapping of views. By default I have it transition to the chart view so that you don't just see a blank page with links.
As with any framework, if you find yourself writing a ton of boilerplate code and things aren't working properly, that's probably a good sign that you're fighting the framework. Or the framework is bad. :P