Meteor and ChartJS dynamically create a chart - javascript

I'm currently working on a project with Meteor. It is used to create, edit, delete and vote for polls. Now I'd like to have a page where you can see the results of the answers shown with "ChartJS".
I got following code:
Templates:
<template name="pollAnalysis">
<h3>Auswertung {{title}}</h3>
{{#each questions}}
{{> questionAnalysis}}
{{/each}}
</template>
<template name="questionAnalysis">
<div class="post">
<div class="post-content">
<h3>{{question}}</h3>
{{> analysisChart}}
</div>
</div>
</template>
<template name="analysisChart">
<canvas id="{{_id}}" class="mychart" height="400" width="400"></canvas>
</template>
Helpers:
Template.pollAnalysis.helpers({
questions: function(){
return Questions.find({pollId: this._id});
}
});
First I had the problem that the chartjs didn't get display, I fixed it by doing this (just for a single ID, thats where I get stuck)
Template.analysisChart.rendered = function(){
drawChart();
}
function drawChart(){
var data = [
{
value: 10,
color:"#27AE60",
highlight: "#2ECC71",
label: "trifft zu"
},
{
value: 10,
color: "#16A085",
highlight: "#1ABC9C",
label: "trifft eher zu"
}
]
var ctx = $("#Cb8CdtDpdKA9y4Hij").get(0).getContext("2d");
var myNewChart = new Chart(ctx);
new Chart(ctx).Pie(data);
}
Now I need the drawChart dynamically.
pseudo code:
function drawChart(questionId){
var data = [
{
value: Questions_Users.find({questionId: questionId}, answer: "yes").count(),
color:"#27AE60",
highlight: "#2ECC71",
label: "trifft zu"
},
{
value: Questions_Users.find({questionId: questionId}, answer: "no").count(),
color: "#16A085",
highlight: "#1ABC9C",
label: "trifft eher zu"
}
]
var ctx = $("#"+questionId).get(0).getContext("2d");
var myNewChart = new Chart(ctx);
new Chart(ctx).Pie(data);
}
So I have to give over the questionId from the Template.analysisChart.rendered to the drawChart() function. But how can I get the questionId ("{{_id}}" in analysisChart template) of the current element in the Template.analysisChart.rendered function?
Greetings & happy programming
faebuk

A quick answer would be to use something like
Template.analysisChart.rendered = function(){
Tracker.autorun(function(){
var data = Questions_Users.find();
drawChart();
})
}
Or look into observeChanges, something like:
Template.analysisChart.rendered = function(){
Questions_Users.find().observeChanges({
added:function(id, fields){
//update Chart.datasets
Chart.update()
}
})
}

I have an example project that just implements the update logic with Tracker and an ReactiveArray (tracker is part of Meteor's front-end, ReactiveArray is similar to Minimongo with fetch): https://github.com/Slava/jsconf-asia-demo/blob/master/tracker.html#L47-L61
To adapt it to a Meteor app, you would start an autorun in template's rendered callback and depend on the minimongo query that you fetch and feed to Chart.js. Once you done, tell Chart.js to update itself.

Related

Chart.js does not get the updated value from Flask

Working on a project here where I take values from an API, using Flask. And then adding it to a chart using chart.js
I got it to work somewhat, issue I am having is that the variable I use to add the value to the chart with, but it does not change the variable, I add to the chart, whenever the value changes in the API.
Meaning:
Sell Price: 10.3
sell price change to 10.4
Sell Price: 10.3 <-- It does not change to 10.4
Here is my python code for it:
#app.route('/product/<product>')
def productPage(product):
price = []
data = requests.get(
'https://api.hypixel.net/skyblock/bazaar?key=').json()
sell = data['products'][product]['sell_summary']
for x in sell:
price.append(x['pricePerUnit'])
currentSell = data['products'][product]['sell_summary'][0]['pricePerUnit']
return render_template('product.html', product=product, price=price, currentSell=currentSell)
#app.route('/graph_update/<product>', methods=['GET', 'POST'])
def graph_update(product):
data = requests.get(
'https://api.hypixel.net/skyblock/bazaar?key=').json()
currentSell = data['products'][product]['sell_summary'][0]['pricePerUnit']
return jsonify('', render_template('graph_update.html', currentSell=currentSell))
And here HTML/JS:
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
<div class="container" style="position: relative; height: 40vh; width: 80vw">
<canvas style="width: 25%" id="myChart"></canvas>
</div>
<input
type="button"
value="add data"
style="margin-top: 25%"
onclick="addData()"
/>
<h1 style="padding-top: 25%">{{product}}</h1>
{% for sell in price %}
<p id="sellprice">{{ sell }}</p>
{% endfor %}
<script src="https://cdn.jsdelivr.net/npm/chart.js#2.8.0"></script>
<script>
var sellprice = JSON.parse("{{ currentSell | tojson | safe }}");
var product = "{{ product }}";
let myChart = document.getElementById("myChart").getContext("2d");
let priceChart = new Chart(myChart, {
type: "line", // bar, horizontalBar, pie, line, doughnut, radar, polarArea
data: {
labels: [],
datasets: [
{
label: "Sell Price",
},
],
},
options: {},
});
var getData = function () {
$.ajax({
url: "/graph_update/" + product,
success: function (data) {
// process your data to pull out what you plan to use to update the chart
// e.g. new label and a new data point
// add new label and data point to chart's underlying data structures
var sellprice = JSON.parse("{{ currentSell | tojson | safe }}");
priceChart.data.labels.push(sellprice);
priceChart.data.datasets[0].data.push(sellprice);
// re-render the chart
priceChart.update();
},
});
};
// get new data every 3 seconds
setInterval(getData, 10000);
</script>
Thanks!
The issue is that your python code at the endpoint /graph_update/<product> should return data as JSON and currently it is doing the following:
return jsonify('', render_template('graph_update.html', currentSell=currentSell))
You need to return something like
return jsonify(data) # or subset of data that you need to pass

Django template tag, making my list empty or I'm not pass anything

Thanks for reading my post
okay, I'm currently working on Django project that displays data in a dashboard; I manage to display and draw charts with Chart JS, great but now I need to limited number data in Django database to be displayed on charts and display the most recent data put into the database.
I use Django built-in tag to display the most recently is "last" and limiting the display data, the tag is "length_is"(Solve).
Here are my HTML codes for using the "last" tag and index page
<div class = "containor">
<div class = "float-right my-4 chartjs-render-monitor" id="chartContainerPH" style="width: 49%; height: 400px;display: inline-block; background-color:#FDFDFD;">
<center>
<a class="title-link" href="{%url 'ph' %}">PH:</a>
<p>{% for tank_system in tank %} {{tank_system.ph|last}} {%endfor%}</p>
</center>
{%include 'FrounterWeb/includes/PHchart.html'%}
</div>
This is the result I get Last Tag result in my index
(Solve)
Here' my code for chart HTML which I use the length_is tag
{%block PHchart%}
<canvas class = "my-4 chartjs-render-monitor" id="PHchart" ></canvas>
<script>
var ctx = document.getElementById("PHchart");
var PHchart = new Chart(ctx, {
type: 'line',
data: {
labels: [ {%for tank_system in tank%} "{{tank_system.datetime}}", {%endfor%} ], //x-Axis
datasets: [{ //y-Axis
label: 'PH1',
data: [ {%for tank_system in tank%} {{tank_system.PH|length_is:"3"}}, {%endfor%} ],
backgroundColor: "rgb(249, 24, 24,0.2)",
borderColor: "rgb(249, 24, 24,0.2)",
fill: true,
}]
},
options: {
scales: {
yAxes: [{
ticks: {
beginAtZero:false
}
}]
}
}
});
</script>
</div>{%endblock%}
and the result Length_is on chart
Summary: I can't get the built-in "filter" and "length_is"(Solve) Django tags to work. Could you please share an example or tutorial with me? The Django documentation didn't write many examples.
and here my views codes;
#login_required(login_url='/accounts/login/')
def index(request):
tank = tank_system.objects.all()
args = {'tank':tank}
return render(request,'FrounterWeb/extends/includes.html',args)
and my models' codes;
class tank_system(models.Model):
PH = models.DecimalField(max_digits=3, decimal_places=1)
EC = models.DecimalField(max_digits=3, decimal_places=1)
WaterLevel = models.IntegerField(default=100)
TempWater = models.IntegerField(default=0)
TempRoom = models.IntegerField(default=0)
datetime = models.DateTimeField(default=timezone.now())
Both of these filters are well documented in the django docs. The last filter gets you the last element of a list, and the length_is filter returns True if the list is that length, or False otherwise.
This likely means that there is an issue in your understanding of your code. You'll want to verify the type and the values of tank_system.PH or tank_system.ph (you have both) and the case will matter. One way to debug this is to simply output the value of tank_system.ph to the web page and verify the result.

Can autorun's reactive computation be dependent on part of a collection?

I'd like to create a template in Meteor that has a Tracker.autorun which exclusively runs when part of a document changes --- but not when other parts of the document change.
So here is sample code using a minimongo collection and template.autorun
parent.html
{{#each items}}
{{> child}}
{{/each}}
child.html
<div>{{title}}</div>
<p>{{description}}</p>
Minimongo Collection
LocalProject.findOne() output:
"items": [
{
"title": "hi",
"description": "test"
},
{
"title": "hi 2",
"description": "test 2"
},
],
"otherstuff:{//etc}
child.js
Template.child.onRendered(function(){
this.autorun(function() {
var data = Template.currentData();
doSomething(data.title,data.description)
});
});
addnewitem.js
LocalProject.update(currentEditingProjectID,{ $push: { 'items': newItem }},function(error,result){
if(error){console.log(error)}
});
The problem is, whenever I run addnewitem.js, all of my Template.child autoruns execute even though their reactive data source (Template.currentData()) has not changed unless it was the specific item I updated. Similarly if I want to update an existing item, not just add a new one to the array, all of the autoruns for each item get executed.
So is there a way, using this model, to create a dependency for autorun that is reactively granular to specific portions of a document?
I don't think the way to go is by using an autorun. I would either set up individual reactive dependencies on each item, or use observe/observeChange.
First idea
parent.html:
{{#each items}}
{{> child}}
{{/each}}
parent.js:
Template.parent.helpers({
// Returns only item ids
items: function() {
return Items.find({}, { fields: { _id: 1 } });
}
});
child.html:
{{#each items}}
{{#with item=getItem}}
<div>{{item.title}}</div>
<p>{{item.description}}</p>
{{/with}}
{{/each}}
child.js:
Template.child.helpers({
getItem: function() {
// Get the item and set up a reactive dependency on this particular item
var item = Items.find(this._id);
// After item has been displayed, do something with the dom
Tracker.afterFlush(function () {
doSomething(item.title, item.description);
});
return item;
}
});
Second idea
parent.html:
{{#each items}}
{{> child}}
{{/each}}
parent.js:
function do(item) {
Tracker.afterFlush(function () {
doSomething(item.title, item.description);
});
}
Template.parent.onCreated({
this.items = Items.find({});
this.handle = this.items.observe({
added: function(item) { do(item); },
changed: function(oldItem, newItem) { do(newItem); },
});
});
Template.parent.onDestroyed({
this.handle.stop();
});
Template.parent.helpers({
items: function() {
return Template.instance().items;
}
});
child.html:
{{#each items}}
<div>{{title}}</div>
<p>{{description}}</p>
{{/each}}
There's a tool just for this - 3stack:embox-value provides reactive isolation, and value caching.
Using your example, you could isolate changes to title/description like so:
first up, add the packages
meteor add 3stack:embox-value
meteor add ejson
Then, update your code:
Template.child.onRendered(function(){
// creates a reactive data source, that only triggers "changes"
// when the output of the function changes.
var isolatedData = this.emboxValue(function(){
var data = Template.currentData();
return {title: data.title, description: data.description}
}, {equals: EJSON.equals})
this.autorun(function() {
// This autorun will only execute when title or description changes.
var data = isolatedData()
doSomething(data.title,data.description)
});
});

Parsing ember-leaflet coordinates from json using ember models

I am new to ember and ember-leaflet.js. I am trying to feed data (via an ajax call to a json file) to both my handlebars template and my ember-leaflet map. With my current setup, the data reaches my handlebars template just fine, but doesn't render the coordinates data to the ember-leaflet map.
I am using the two examples listed below as my guides, but have hit a wall because of my lack of experience with ember. Can anyone point me in the right direction please?
Ajax and ember example
Partial example of what I'm trying to accomplish
Handlebars template:
<script type="text/x-handlebars" data-template-name="application">
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="index">
{{view App.MapView id="map"}}
<div id="blog">
<ul>
{{#each item in model}}
<li>{{item.headline}}</li>
{{/each}}
</ul>
</div>
</script>
Ember:
App = Ember.Application.create();
App.Router.map(function() {
// put your routes here
});
App.IndexRoute = Ember.Route.extend({
model: function(){
return App.Item.all();
}
});
App.Item = Ember.Object.extend();
App.Item.reopenClass({
all: function() {
return $.getJSON("js/data/test_e.json").then(function(response) {
var items = [];
response.features.forEach( function (data) {
items.push( App.Item.create(data) );
});
return items;
});
}
});
App.MarkerCollectionLayer =
EmberLeaflet.MarkerCollectionLayer.extend({
locationBinding: 'controller.item.center'});
App.MapView = EmberLeaflet.MapView.extend({
childLayers: [
EmberLeaflet.DefaultTileLayer,
App.MarkerCollectionLayer]
});
App.IndexController =
Ember.Controller.extend({});
JSON file:
{
"features": [
{
"headline": "Docker, the Linux container runtime: now open-source",
"center" : [40.714, -74.000]
},
{
"headline": "What's Actually Wrong with Yahoo's Purchase of Summly",
"center" : [40.714, -73.989]
}
]
}
The main fix needed here is the locationBinding in the MarkerCollectionLayer. The location binding needs to be in the MarkerLayer class. Furthermore, you need to use the EmberLeaflet.computed functions to convert simple lat lng arrays to a Leaflet LatLng object. See this example:
App.MarkerCollectionLayer = EmberLeaflet.MarkerCollectionLayer.extend({
content: Ember.computed.alias('controller'),
itemLayerClass: EmberLeaflet.MarkerLayer.extend({
location: EmberLeaflet.computed.latLngFromLatLngArray('content.center'),
})
});
Check out this JSFiddle with a full working example: http://jsfiddle.net/xALu4/2/

Ember bindings firing when not wanted, and not firing when required

Hey I'm having two different issues in my ember app, both of which involve bindings.
First, I have a binding firing when I don't want it to. Basically what I'm trying to achieve (I'm building a survey creator front-end app) is that when any text is entered into the 'name' field of a question, I want to add a new question object, which will render out another blank question at the end of the list of questions that the user is adding. This has the effect of there always being a new question, so an add question button is not required. The binding is working, and a new object is being added: however, since the binding is from the newest question object, the binding is triggered again when the new object is created, which in turn creates a new object, which triggers the binding again....which obviously eventually crashes the browser. I've tried using the Ember._suspendObserver function, but there isn't a lot of documentation on this, and I think I'm using it wrong - anyhow it isn't suspending the observer or pausing the binding. The observer in the code is around line 27 (contentsNameObserver)
The other issue I'm having -- I have a selection drop down box which selects what type of question the user wants (single answer, multi-choice, etc.) but the binding between the select box and the {{#each}} helper which renders the kind of question isn't triggering. I'm using the Ember.Select view helper, so there shouldn't be any issues with using get/set to fire the binding. I'm using a computed property to return an array of fields for the question type based on the value of the question type id. The computed property is in line 13 (App.SurveyContent.types), and the template templates/step3. Quick heads up that this app may be extended for more than surveys, hence 'questions' are often referred to in the code as 'content'.
I'm pretty new to ember (this is my first real app) so my code most likely has a lot of issues outside of these problems...so any comments on how I've structured my app would be hugely appreciated as well!
Javascript ember app:
App = Ember.Application.create({
rootElement: '#emberContainer'
});
App.SurveyContent = Ember.Object.extend({
name: "",
content_type: 1,
content_pos: 1,
hash: Em.A([]),
types: function() {
alert("redraw");
return App.ContentTypes[this.content_type-1].hash;
}.property()
});
App.Surveys = Ember.Object.create({
name: null,
start: $.datepicker.formatDate('mm/dd/yy' , new Date()),
end: $.datepicker.formatDate('mm/dd/yy' , new Date()),
themeID: 0,
contents: [App.SurveyContent.create()], //Pushing an instance of App.SurveyContent onto this
contentsNameObserver: function() {
context = this;
console.log("entering");
Em._suspendObserver(App.Surveys, "contents.lastObject.name", false, false, function() {
console.log("suspend handler");
context.contents.pushObject(App.SurveyContent.create());
})
}.observes("contents.lastObject.name")
});
App.ContentTypes = [
Ember.Object.create({name: 'Text question', id:1, hash: [Ember.Object.create({name: 'Question', help: 'Enter the question here', type: 'text'})]}),
Ember.Object.create({name: 'Multichoice question', id:2, hash: [Ember.Object.create({name: 'Question', help: 'Enter the question here', type: 'text'}),
Ember.Object.create({name: 'Answer', help: 'Enter possible answers here', type: 'text', multiple: true})]})
];
App.ViewTypeConvention = Ember.Mixin.create({
viewType: function() {
console.log(this);
return Em.get("Ember.TextField");
}.property().cacheable()
});
App.CRMData = Ember.Object.extend();
App.CRMData.reopenClass ({
crm_data: [],
org_data: [],
org_display_data: [],
loadData: function() {
context = this;
context.crm_data = [];
$.getJSON ("ajax/crm_data", function(data) {
data.forEach(function(crm) {
context.crm_data.pushObject(App.CRMData.create({id: crm.crm_id, name: crm.crm_name}));
crm.orgs.forEach(function(org) {
context.org_data.pushObject(App.CRMData.create({id: org.org_id, name: org.org_name, crm_id: crm.crm_id}));
}, context)
}, context)
context.updateOrganisations(5);
});
return this.crm_data;
},
updateOrganisations: function(crm_id) {
context = this;
this.org_display_data.clear();
console.log("clearing the buffer")
console.log(this.org_display_data)
context.org_data.forEach(function(org) {
if(org.crm_id == crm_id) {
context.org_display_data.pushObject(App.CRMData.create({id: org.id, name: org.name}));
}
}, context)
}
});
App.DateField = Ember.TextField.extend({
attributeBindings: ['id', 'class']
});
App.CRMSelect = Ember.Select.extend({
attributeBindings: ['id'],
change: function(evt) {
console.log(evt)
App.CRMData.updateOrganisations($('#crm').val())
}
});
App.ApplicationController = Ember.Controller.extend();
App.Step1Controller = Ember.ArrayController.extend({});
App.Step2Controller = Ember.ArrayController.extend({});
App.Step2Controller = Ember.ArrayController.extend({});
App.ApplicationView = Ember.View.extend({
templateName: 'app'
});
App.Step0View = Ember.View.extend ({
templateName: 'templates/step0'
});
App.Step1View = Ember.View.extend ({
templateName: 'templates/step1'
});
App.Step2View = Ember.View.extend ({
templateName: 'templates/step2',
didInsertElement: function() {
$( ".jquery-ui-datepicker" ).datepicker();
}
});
App.Step3View = Ember.View.extend ({
templateName: 'templates/step3',
});
App.Router = Em.Router.extend ({
enableLogging: true,
root: Em.Route.extend ({
showstep1: Ember.Route.transitionTo('step1'),
showstep2: Ember.Route.transitionTo('step2'),
showstep3: Ember.Route.transitionTo('step3'),
index: Ember.Route.extend({
route: '/',
connectOutlets: function(router){
router.get('applicationController').connectOutlet( 'step0');
}
}),
step1: Ember.Route.extend ({
route: 'step1',
connectOutlets: function(router){
router.get('applicationController').connectOutlet( 'step1', App.CRMData.loadData());
}
}),
step2: Ember.Route.extend ({
route: 'step2',
connectOutlets: function(router) {
router.get('applicationController').connectOutlet('step2')
},
}),
step3: Ember.Route.extend ({
route: 'step3',
connectOutlets: function(router) {
router.get('applicationController').connectOutlet('step3')
},
})
})
});
Ember.LOG_BINDINGS=true;
App.LOG_BINDINGS = true;
App.ContentTypes.forEach(function(object) {
object.hash.forEach(function(hash) {
hash.reopen(App.ViewTypeConvention);
}, this);
}, this);
Html templates (I've got these in haml, so this is just a representation of the important ones)
<script type="text/x-handlebars" data-template-name="app">
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="templates/step3">
<h1> Add content to {{App.Surveys.name}} </h1>
<br>
<div id = "accordion2" class = "accordion">
{{#each content in App.Surveys.contents}}
<div class="accordion-group">
<div class = "accordion-heading">
<a class = "accordion-toggle" data-parent = "#accordion2" data-toggle = "collapse" href = "#collapseOne">
{{content.name}}
</a>
</div>
<div id = "collapseOne" class = "accordion-body collapse in">
{{view Ember.TextField valueBinding="content.name" class="txtName"}}
<form class = "form-horizontal">
<div class = "accordion-inner">
<div class = "control-group">
<label class = "control-label" for ="organisation">
Content Type
<div class = "controls">
{{view Ember.Select contentBinding="App.ContentTypes" optionValuePath="content.id" optionLabelPath="content.name" valueBinding="content.content_type"}}
</div>
</div>
</div>
{{#each item in content.types }}
<div class = "control-group" >
<label class = "control-label" for = "organisation">
{{item.name}}
<div class = "controls">
{{view item.viewType }}
</div>
{{/each}}
</div>
</form>
</div>
{{/each}}
</div>
</div>
<br>
<div class = "btn" {:_action => 'showstep3'}> Next Step > </div>
</script>
I've solved the first issue, although I didn't get the suspendObserver property working I used an if statement to check the previous element, removing the infinite loop.
contentsNameObserver: function() {
context = this;
if(this.get('contents.lastObject').name) {
context.contents.pushObject(App.SurveyContent.create());
}
}.observes("contents.lastObject.name")
Any comments on how to get the _suspendObserver handler working would be appreciated though, it is something that should work but I'm doing something wrong
I've created a stripped down jsfiddle at http://jsfiddle.net/reubenposthuma/sHPv4/
It is set up to go straight to the problem step, step 3, so that I don't need to include all the previous templates.
I'm still stuck on the issue of the binding not firing though. The behaviour I'm expecting is that when the 'Content Type' dropdown box is changed, the text box underneath should change, it should re-render with two text boxes.
I realise this is an old question, but there is no documenation and precious little information I could find searching either, hence sharing what I found worked here.
What I found worked was to call Ember._suspendObserver as follows:
somePropertyDidChange: function(key) {
var that = this;
Ember._suspendObserver(this, key, null,
'somePropertyDidChange', function() {
// do stuff which would normally cause feedback loops
that.set('some.property', 'immune to feedback');
});
}.observes('some.property');
You can also use the multiple observer variant as follows:
somePropertiesDidChange: function(key) {
var that = this;
Ember._suspendObservers(this, ['some.property', 'another.property'],
null, 'somePropertiesDidChange', function() {
// do stuff which would normally cause feedback loops
that.set('some.property', 'immune to feedback');
that.set('another.property', 'also immune to feedback');
});
}.observes('some.property', 'another.property');
In my exact use case I actually called Ember._suspendObservers from an Ember.run.once() function which was setup by the observer since I wanted to make sure a number of dependant properties had settled before doing calculations which in turn would mutate some of those properties.

Categories