Update JS with data coming in from WebSocket? - javascript

I come from a python background and am now figuring out how to interact with other languages such as JS.
TLDR:
Can I pass data coming in from Websocket into other variable to update a data visualisaton (floating particles) in my frontend not ment to be updated live initially, using JS?
My question:
In Python, I would just make a loop and keep passing delayTimes into particles -> number -> values. But in JS have no clue if this concept actually works out or not. Can I just pass the value and expect it to keep updating, do I need to wrap it in something else?
The whole Situation:
My idea:
Receive data from an API, turn it into an int, send it to a JS file via WebSockets and live update animations (particles) with the incoming data.
For now, I managed to display the changing number live, without needing to refresh my browser window.
Next, I want the data to update a visualization using three.js, particle.js, D3.js, or any kind of JS that would allow me to display particles.
I have already managed to just make those particles and have them floating about, but I actually want to manipulate the code in a way that it displays a particle count based on the incoming data from the WebSocket.
So if WebSocket data = 200, the website shows 200 particles.
If WebSocket data then changes to 130, the website removes 70 particles live without a browser refresh.
I have the JS for my particles and the WebSocket client in one file. Can I somehow store the data coming in and pass it into my particle count data to live update the whole situation?
The code currently looks like this, where "delayTimes" is the integer received and updated via WebSocket, and particles -> number -> value determents how many particles there are in the canvas:
JS:
let ws = new WebSocket("ws://localhost:3000/");
let delayTimeElement = document.getElementById('numberonwebsite')
ws.onmessage = (event) => {
let delayTimes = parseInt(event.data);
delayTimeElement.innerText = delayTimes;
};
particlesJS('particles-js',
{
"particles": {
"number": {
"value": 5,
"density": {
"enable": true,
"value_area": 800
}
},
"color": {
"value": "#000"
},
"shape": {
"type": "circle",
"stroke": {
"width": 0,
"color": "#000000"
},
"polygon": {
"nb_sides": 5
},
"image": {
"src": "img/github.svg",
"width": 100,
"height": 100
}
},
"opacity": {
"value": 0.5,
"random": false,
"anim": {
"enable": false,
"speed": 1,
"opacity_min": 0.1,
"sync": false
}
},
"size": {
"value": 5,
"random": true,
"anim": {
"enable": false,
"speed": 40,
"size_min": 0.1,
"sync": false
}
},
"line_linked": {
"enable": true,
"distance": 150,
"color": "#000",
"opacity": 0.4,
"width": 1
},
"move": {
"enable": true,
"speed": 6,
"direction": "none",
"random": false,
"straight": false,
"out_mode": "bounce",
"attract": {
"enable": false,
"rotateX": 600,
"rotateY": 1200
}
}
},
"interactivity": {
"detect_on": "canvas",
"events": {
"onhover": {
"enable": false,
"mode": "repulse"
},
"onclick": {
"enable": true,
"mode": "push"
},
"resize": true
},
"modes": {
"grab": {
"distance": 400,
"line_linked": {
"opacity": 1
}
},
"bubble": {
"distance": 400,
"size": 40,
"duration": 2,
"opacity": 8,
"speed": 3
},
"repulse": {
"distance": 200
},
"push": {
"particles_nb": 4
},
"remove": {
"particles_nb": 2
}
}
},
"retina_detect": true,
"config_demo": {
"hide_card": false,
"background_color": "#b61924",
"background_image": "",
"background_position": "50% 50%",
"background_repeat": "no-repeat",
"background_size": "cover"
}
}
);
HTML:
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="styles.css">
<meta charset="utf-8" />
</style>
</head>
<body>
<!-- particles.js container -->
<div id="particles-js"></div>
<h1 id="numberonwebsite"> --- </h1>
<!-- scripts -->
<script src="../particles.js"></script>
<script src="js/app.js"></script>
</body>
</html>
src="../particles.js" actually generates the particles but the value in the JS above (app.js) overrides the information.
Further reading:
My project is currently set up in the following way:
A python script that receives data from API calls (looped every 5
seconds), does a bunch of stuff with the data, and then sends this
information (a single integer) out as a client via Websocket.
My Websocket server is set to broadcast, so it sends out the
information received from my python script to all connected clients.
A JS file connects as further WebSocket client receives the
information from my python script via broadcast from the server.
My index HTML is linked to my JS and displays the number coming in
live on my Website frontend.

Related

get the range values from AmCharts

I have an AmCharts working, this is the code:
var chart = AmCharts.makeChart("chartdiv", {
"type": "serial",
"theme": "light",
"precision":"3",
"decimalSeparator": ",",
"thousandsSeparator": ".",
"legend": {
"horizontalGap": 10,
"maxColumns": 3,
"position": "top",
"useGraphSettings": false,
"markerSize": 10,
"marginTop": 20,
"autoMargins":true,
"forceWidth":true,
"valueAlign":"right",
"labelWidth":85,
"valueWidth":85
},
"dataProvider": response,
"synchronizeGrid":true,
"valueAxes": [{
"axisAlpha": 0,
"position": "left",
"title": "Activos"
}],
/*"startDuration": 0.5,*/
"graphs": graphs,
"chartScrollbar": {},
"chartCursor": {
"cursorPosition": "mouse"
},
"categoryField": "diahora",
"categoryAxis": {
/*"parseDates": true,*/
"gridPosition": "start",
"axisColor": "#DADADA",
/*"minorGridEnabled": true*/
},
"export": {
"enabled": true,
"position": "top-right",
"class": "export-main",
"menu": [ {
"title": "Exportar el gráfico a archivo de imagen",
"label": "Descargar",
"menu": [ "PNG", "JPG", "PDF" ]
}]
}
});
I'm calling it with ajax with:
$.get(url, function(response){
So, I need to get the 2 values of the range of X of the graph that is being rendered on the browser after a zoom, any idea?
For example, when the graph starts, the range of the axis x are 0 and 100. If I zoom into some range, for example 40 and 60, I need to retrieve those values (40 and 60) because I will use them for another things in the page.
So, how could I save those ranges after every ajax call?
AmCharts provides the zoomed event, which gives you the start and end indices/values. You can use that and store the values for whatever you need, e.g.
AmCharts.makeChart("chartdiv", {
// ...
listeners: [{
event: "zoomed",
method: function(e) {
//e.startIndex and e.endIndex gives you the indicies in the dataProvider
//e.startValue and e.endValue gives you the start and end values
//e.startDate and e.endDate gives you the start and end dates if you're using parseDates
}
}]
});
Note that zoomed is also called on the initial load. If you don't want to capture the initial start/end values, you might need to keep track of the first load before storing those values on subsequent zoom events.

Amchart Dont show data where value =0

Im working in a graph where data comes from server like Object's Array.
I dont need some data to show in my graph, data where number=0, and i want to know if amchart have some function to dont show this.
Here is an example of the code:
var data =[
{'number':1, 'date':'11:00-11:59', 'duration': 3},
{'number':2, 'date':'12:00-12:59', 'duration':6},
{'number':4, 'date':'13:00-13:59', 'duration':12},
{'number':8, 'date':'14:00-14:59', 'duration':8},
{'number':0, 'date':'14:00-14:59', 'duration':0}
];
var chart = AmCharts.makeChart("chartdiv", {
"theme": "light",
"type": "serial",
"startDuration": 2,
"dataProvider": data,
"valueAxes": [{
"id": "number",
"position": "left",
"title": "N Llamadas"
},{
"id": "durationAxis",
"position": "right",
"title": "duration"
}],
"graphs": [{
"balloonText": "[[category]]: <b>[[value]]</b>",
"fillColorsField": "color",
"fillAlphas": 1,
"lineAlpha": 0.1,
"type": "column",
"valueField": "number"
},{
"bullet": "square",
"bulletBorderAlpha": 1,
"bulletBorderThickness": 1,
"dashLengthField": "dashLength",
"legendValueText": "[[value]]",
"title": "duration",
"fillAlphas": 0,
"valueField": "duration",
"valueAxis": "durationAxis"
}],
"depth3D": 20,
"angle": 30,
"chartCursor": {
"categoryBalloonEnabled": false,
"cursorAlpha": 0,
"zoomable": false
},
"categoryField": "date",
"categoryAxis": {
"gridPosition": "start",
"labelRotation": 0
},
"export": {
"enabled": true
}
});
In this case i dont want to show the last item of the array because number is 0, i shouldnt create a new array for reasons project.
Thanks in advance
Not showing a graph object when a value is 0 is pretty much impossible without modifying the array due to the bullets of the line chart (you can hide a column using alphaField, but you can't remove the bullet). You can write an init handler that nulls out zero values so that the chart doesn't show the point or column at all upon chart initialization:
//Nulls out any field that contains a zero, effectively hiding the point/column from the chart.
//Note that the category will still be visible if both points are null unless you null out that category as well.
AmCharts.addInitHandler(function(chart) {
var valueFields = chart.graphs.map(function(graph) {
return graph.valueField;
});
chart.dataProvider.forEach(function(dataItem) {
valueFields.forEach(function(valueField) {
if (dataItem[valueField] === 0) {
dataItem[valueField] = null;
}
})
})
}, ["serial"]);
Note that this will effect the data export (csv, xlsx, json) as well. If you want to re-add those zero values to the data export, you can use the processData callback in the export plugin to modify your data to re-add the zeroes:
"export": {
"enabled": true,
//re-add nulled out values as zeroes for data export:
"processData": function(data, cfg) {
var valueFields = this.setup.chart.graphs.map(function(graph) {
return graph.valueField;
});
data.forEach(function(dataItem) {
valueFields.forEach(function(valueField) {
if (dataItem[valueField] == null) {
dataItem[valueField] = 0;
}
});
});
return data;
}
}
Demo

Lines Graph are rendering as straight Lines in AM Charts

I am trying to draw Line Graph but these are rendering as a straight line in the bottom of graph area.I have added columns graphs too in it. they are showing properly but Lines Graph are not showing correct just a straight line. I have added the screenshot in the end. Please help.
This is my graph header
var header_graph = [{"balloonText":"[[title]] of [[category]]:[[value]]",
"fillAlphas":"1",
"id":"ProductA",
"title":"Produc A ",
"type":"SmoothedLine",
"valueField":"column2"}];
This is my graph data values
var data_graph = [{"category":"Jan",
"column1":"85", "column2":"24", "column3":"343",
"column4":"85", "column5":"31", "column6":"267",
"column7":"85", "column8":"19", "column9":"439"},......]
This is the whole script to generate the chart
var chart = AmCharts.makeChart("chartdiv", {
"type": "serial",
"depth3D": 2,
"startDuration": 1,
"theme": "light",
"precision": 2,
"valueAxes": [{
"id": "v1",
"title": "",
"position": "left",
"autoGridCount": false,
}, {
"id": "v2",
"title": "",
"gridAlpha": 0,
"position": "right",
"autoGridCount": false
}],
"graphs": header_graph ,
"chartScrollbar": {
"graph": "column2",
"oppositeAxis": false,
"offset": 30,
"scrollbarHeight": 50,
"backgroundAlpha": 0,
"selectedBackgroundAlpha": 0.1,
"selectedBackgroundColor": "#888888",
"graphFillAlpha": 0,
"graphLineAlpha": 0.5,
"selectedGraphFillAlpha": 0,
"selectedGraphLineAlpha": 1,
"autoGridCount": true,
"color": "#AAAAAA"
},
"chartCursor": {
"pan": true,
"valueLineEnabled": true,
"valueLineBalloonEnabled": true,
"cursorAlpha": 0,
"valueLineAlpha": 0.2
},
"categoryField": "category",
"categoryAxis": {
"parseDates": false,
"dashLength": 1,
"minorGridEnabled": true,
"gridPosition": "start"
},
"legend": {
"useGraphSettings": true,
"position": "bottom"
},
"balloon": {
"borderThickness": 1,
"shadowAlpha": 0
},
"export": {
"enabled": false
},
"dataProvider": data_graph
});
The chart is drawing your line at scale with respect to your chart's range of values and it looks like your values for the line are very small compared to the rest of your columns. It will look like a straight line near the bottom as a result of this.
You can use a valueScrollbar and let your users zoom in on the valueAxis/Y-axis so they can see the line better. It takes the same properties as a chartScrollbar as you can see in this demo from AmCharts:
"valueScrollbar": {
"autoGridCount": true,
"color": "#000000",
"scrollbarHeight": 50
},
Outside of that or making your valueAxis maximum smaller, there's not much else you can do.
(Off topic, you should make your values numbers, not strings.)
Edit
You can dynamically set the chart's maximum and minimum using addInitHandler to have the chart determine these value upon chart initialization like so:
//get the maximum and minimum values from the dataProvider
AmCharts.addInitHandler(function(chart) {
var maximum = Number.MIN_VALUE;
var minimum = Number.MAX_VALUE;
chart.dataProvider.forEach(function(data) {
//for each dataProvider element, check the properties that correspond to the chart graphs' valueFields.
chart.graphs.forEach(function(graph) {
if (data[graph.valueField] != null) {
maximum = Math.max(maximum, data[graph.valueField]);
minimum = Math.min(minimum, data[graph.valueField]);
}
});
});
chart.valueAxes[0].maximum = maximum;
chart.valueAxes[0].minimum = minimum;
chart.valueAxes[0].strictMinMax = true;
}, ["serial"]);
Demo

Ammaps legend only appears on window resize

I'm using ammaps to create a map and trying to create a legend. I'm running in to a weird problem where the legend doesn't appear on initial load but only appears on window resize. Regardless of the window size, on any resize, the legend becomes visible (VERY WEIRD RIGHT?).
Code:
var icon = "M9.875,0.625C4.697,0.625,0.5,4.822,0.5,10s4.197,9.375,9.375,9.375S19.25,15.178,19.25,10S15.053,0.625,9.875,0.625";
var map = AmCharts.makeChart( "mapdiv", {
/**
* this tells amCharts it's a map
*/
"type": "map",
/**
* create data provider object
*/
"dataProvider": {
"mapURL": "https://raw.githubusercontent.com/ggwc82/amcharts/master/unitedKingdomLow.svg",
"getAreasFromMap": false,
"images": [ {
"latitude": 51.5074,
"longitude": 0.1278,
"svgPath": icon,
"scale": 0.7,
"label": "Dagenham Factory",
"labelBackgroundColor": "#ffffff",
"labelColor": "#696D6E",
"labelFontSize": 14,
"labelShiftY": 00,
"color": "#D30000",
"title": "1 Warning",
"url": "http://www.google.co.uk",
"description": "DRM with id 09 is offline"
},
{
"latitude": 53.4808,
"longitude": -2.2426,
"svgPath": icon,
"scale": 0.7,
"label": "Manchester Factory",
"labelBackgroundColor": "#ffffff",
"labelColor": "#696D6E",
"labelFontSize": 14,
"labelShiftY": 0,
"color": "#40D300",
"title": "No Issues",
"url": "http://www.google.co.uk",
"description": ""
},
{
"latitude": 54.9783,
"longitude": -1.6178,
"svgPath": icon,
"scale": 0.7,
"label": "Newcastle Factory",
"labelBackgroundColor": "#ffffff",
"labelColor": "#696D6E",
"labelFontSize": 14,
"labelShiftY": 0,
"color": "#D3D000",
"title": "2 Alerts",
"url": "http://www.google.co.uk",
"description": "DRM with id 23 is inactive. DRM with id 25 is inactive."
}
],
},
/**
* create areas settings
* autoZoom set to true means that the map will zoom-in when clicked on the area
* selectedColor indicates color of the clicked area.
*/
"areasSettings": {
"autoZoom": true,
"unlistedAreasColor": "#C8E1D6",
"selectedColor": "#CC0000"
},
"zoomControl": {
"zoomControlEnabled": false,
"homeButtonEnabled": true,
},
"dragMap": false,
"showDescriptionOnHover": true,
"allLabels": [
{
"text": "Default Factory View - UK Sites",
"bold": true,
"size": 20,
"color": "#696D6E",
"align": "center",
"y": 100
}
],
} );
var legend = new AmCharts.AmLegend();
console.log("hello");
map.addLegend(legend,"legenddiv");
legend.data = [{title:"first", color:"#CC0000", markerType: "circle"},
{title:"second", color:"#00CC00", markerType: "circle"},
{title:"second", color:"#ffff00", markerType: "circle"}]
You're using the makeChart function. This function is an helper which allow you to create in a single call a chart / map, configure it using JSON, display it in the container div passed as first argument and get back the instance previously created.
You're adding the legend to your instance but it's already rendered by the makeChart helper when you're hitting your code adding the legend. So, when adding the legend to an already rendered chart, it'll only be visible when re-rendered which happens when you resize your window.
As specified in the documentation, the AmMap class can not be instantiated explicitly so the use of the makeChart method is mandatory but you can also configure your legend in the JSON config instead of doing it later.
/**
* Legend
*/
"legend": {
"width": 400,
"backgroundAlpha": 1,
"backgroundColor": "#fff",
"borderColor": "#000",
"borderAlpha": 1,
"bottom": 15,
"right": 15,
"horizontalGap": 10,
"data": [{
"title": "first",
"color": "#CC0000",
"markerType": "circle"
}, {
"title": "second",
"color": "#00CC00",
"markerType": "circle"
}, {
"title": "third",
"color": "#ffff00",
"markerType": "circle"
}]
},
I've put a little fiddle based on your question including a legend configured in JSON. The legend is displayed at the same time of the map, no resize or anything required to show it.

Construct image from Fabric.js JSON on python server

Using python, is it possible to reconstruct an image from the json representation of a fabric.js canvas?
It's easy to do with Node.js but I am using django so I prefer not having to run a separate node.js server just to create these images to send them to my django server.
No.
I don't see how it's possible without writing Python port of Fabric (or a custom parser+renderer).
Here's why:
JSON data string contains custom representation of various Fabric shapes, which — during parsing and interpretation by Fabric's loadFromJSON — are all loaded and rendered onto canvas. The canvas could then be used to generate an image.
You can parse data in python, since it's just JSON. You can walk over it and analyze it. You'll even be able to "see" the kinds of shapes that need to be loaded onto canvas. But you'll still need the main thing — creating visual representation of each of those shapes.
Unless you do that manually (using some kind of graphics methods in Python; which sounds like a whole lot of work), I don't think there's much else that can be done.
You don't have to run a separate node server. But you do need a node runtime with the modules - 'canvas' and 'fabric' installed. With that done, you can trivially call the nodejs script from your python code
Here is a sample js script which will read the JSON input and generate a PNG output file
var fabric = require('fabric').fabric,
fs = require('fs'),
out = fs.createWriteStream(process.argv[3]);
var canvas = fabric.createCanvasForNode(530, 630);
canvas.loadFromJSON(process.argv[2], function() {
canvas.renderAll();
var stream = canvas.createPNGStream();
stream.on('data', function(chunk) {
out.write(chunk);
});
});
And here is how you can call it in your python code
import subprocess
fabric_json = u'{"objects": [{"opacity": 1, "strokeMiterLimit": 10, "isMulticolor": false, "height": 203, "visible": true, "stroke": null, "filters": [], "hexColors": ["#085a68", "#ca350a", "#d8ec73"], "fill": "rgb(0,0,0)", "angle": 0, "fillRule": "nonzero", "flipX": false, "flipY": false, "top": 205.78, "scaleX": 0.46, "scaleY": 0.46, "strokeLineJoin": "miter", "width": 200, "backgroundColor": "", "clipTo": null, "type": "image", "strokeLineCap": "butt", "strokeDashArray": null, "strokeWidth": 1, "originY": "top", "originX": "left", "globalCompositeOperation": "source-over", "designId": 2825, "alignY": "none", "alignX": "none", "shadow": null, "crossOrigin": "", "src": "http://localhost.com/designs/sampleimage.png", "meetOrSlice": "meet", "designFileName": "sampleimage.png", "left": 208.68}], "backgroundImage": {"opacity": 1, "strokeMiterLimit": 10, "height": 630, "visible": true, "stroke": null, "filters": [], "fill": "rgb(0,0,0)", "angle": 0, "fillRule": "nonzero", "flipX": false, "flipY": false, "top": 0, "scaleX": 1, "scaleY": 1, "strokeLineJoin": "miter", "width": 530, "backgroundColor": "", "clipTo": null, "type": "image", "strokeLineCap": "butt", "strokeDashArray": null, "strokeWidth": 1, "originY": "top", "originX": "left", "globalCompositeOperation": "source-over", "alignY": "none", "alignX": "none", "shadow": null, "crossOrigin": "", "src": "http://localhost.com:5000/static/images/fabric/ts_rne_front.png", "meetOrSlice": "meet", "left": 0}, "background": "#bf1515"}'
subprocess.call(["node", "fabricImageGenerator.js", fabric_json, "outputImage.png"])

Categories