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"])
Related
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.
I'm trying to understand how OpenSeadragon decides upon tile dimensions within a pyramidal .tiff image in order to generate the first layer of tiles. I can't see the correlation between the various sizes defined in the info.json that OpenSeadragon requests (which uses the IIIF Image API), and the tile dimensions.
When I click inside the OpenSeadragon viewer, multiple image requests are sent in order to request high resolution tiles, but the dimensions vary dramatically from image to image, and between click locations within the viewer.
Is it possible to force OpenSeadragon to use the sizes defined in info.json? From what I can see, it isn't.
I've tried looking through the IFFF Image API docs, the OpenSeadragon docs, and the OpenSeadragon.js file, but it's rather hard to follow the flow of execution fully within that.
Here is what the relevant part of the info.json looks like:
"width": 6555,
"height": 6995,
"sizes": [
{
"width": 102,
"height": 109
},
{
"width": 205,
"height": 219
},
{
"width": 410,
"height": 437
},
{
"width": 819,
"height": 874
},
{
"width": 1639,
"height": 1749
},
{
"width": 3278,
"height": 3498
}
],
"tiles": [
{
"width": 6555,
"height": 1664,
"scaleFactors": [
1,
2,
4,
8,
16,
32,
64
]
}
],
"profile": [
"http://iiif.io/api/image/2/level2.json",
{
"formats": [
"tif",
"jpg",
"gif",
"png"
],
"maxArea": 400000000,
"qualities": [
"bitonal",
"default",
"gray",
"color"
],
"supports": [
"sizeByW",
"regionByPx",
"sizeByWhListed",
"cors",
"regionSquare",
"sizeByDistortedWh",
"sizeAboveFull",
"canonicalLinkHeader",
"sizeByConfinedWh",
"sizeByPct",
"jsonldMediaType",
"regionByPct",
"sizeByH",
"rotationArbitrary",
"baseUriRedirect",
"rotationBy90s",
"profileLinkHeader",
"sizeByForcedWh",
"sizeByWh",
"mirroring"
]
}
]
}
{
"objects": [{
"type": "i-text",
"originX": "left",
"originY": "top",
"left": 253.75,
"top": 377.4,
"width": 293.49609375,
"height": 45.199999999999996,
"fill": "#454545",
"stroke": null,
"strokeWidth": 1,
"strokeDashArray": null,
"strokeLineCap": "butt",
"strokeLineJoin": "miter",
"strokeMiterLimit": 10,
"scaleX": 1,
"scaleY": 1,
"angle": 0,
"flipX": false,
"flipY": false,
"opacity": 1,
"shadow": null,
"visible": true,
"clipTo": null,
"backgroundColor": "",
"fillRule": "nonzero",
"globalCompositeOperation": "source-over",
"transformMatrix": null,
"skewX": 0,
"skewY": 0,
"text": "Start typing here",
"fontSize": 40,
"fontWeight": "normal",
"fontFamily": "arial",
"fontStyle": "",
"lineHeight": 1.16,
"textDecoration": "",
"textAlign": "left",
"textBackgroundColor": "",
"charSpacing": 0,
"originalScaleX": 1,
"originalScaleY": 1,
"originalLeft": 253.751953125,
"originalTop": 377.4,
"lockMovementX": false,
"lockMovementY": false,
"lockScalingX": false,
"lockScalingY": false,
"lockUniScaling": false,
"lockRotation": false,
"id": 8454,
"styles": {}
}],
"background": "#ffffff",
"height": 800,
"width": 801,
"originalHeight": 800,
"originalWidth": 801
}
{
"objects": [{
"type": "i-text",
"originX": "left",
"originY": "top",
"left": 253.75,
"top": 377.4,
"width": 293.49609375,
"height": 45.199999999999996,
"fill": "#454545",
"stroke": null,
"strokeWidth": 1,
"strokeDashArray": null,
"strokeLineCap": "butt",
"strokeLineJoin": "miter",
"strokeMiterLimit": 10,
"scaleX": 1,
"scaleY": 1,
"angle": 0,
"flipX": false,
"flipY": false,
"opacity": 1,
"shadow": null,
"visible": true,
"clipTo": null,
"backgroundColor": "",
"fillRule": "nonzero",
"globalCompositeOperation": "source-over",
"transformMatrix": null,
"skewX": 0,
"skewY": 0,
"text": "Start typing here",
"fontSize": 40,
"fontWeight": "normal",
"fontFamily": "arial",
"fontStyle": "",
"lineHeight": 1.16,
"textDecoration": "",
"textAlign": "left",
"textBackgroundColor": "",
"charSpacing": 0,
"originalScaleX": 1,
"originalScaleY": 1,
"originalLeft": 253.751953125,
"originalTop": 377.4,
"lockMovementX": false,
"lockMovementY": false,
"lockScalingX": false,
"lockScalingY": false,
"lockUniScaling": false,
"lockRotation": false,
"id": 6668,
"styles": {}
}],
"background": "#ffffff",
"height": 800,
"width": 801,
"originalHeight": 800,
"originalWidth": 801
}
i have this 2 fabric.js json object which i want to concat and load to canvas by using fabric loadJSON method??
There are many options depending on what Sagar Adke is ultimately trying to achieve. However, I think these might be closer to what he's looking for:
OPTION 1:
Load the canvas with json1 stringify'ing first, clone the object since loading again will clear the canvas, load the canvas with json2 stringify'ing first, and then add the cloned object.
canvas.loadFromJSON(JSON.stringify(json1), canvas.renderAll.bind(canvas));
var item = canvas.item(0).clone();
canvas.loadFromJSON(JSON.stringify(json2), canvas.renderAll.bind(canvas));
// only needed since objects are identical and I wanted to show both objects
item.set({
top: item.top - 70
});
canvas.add(item);
canvas.renderAll();
Working JSFiddle, http://jsfiddle.net/rekrah/wxjnu7pd/.
OPTION 2:
Push json2 object onto json1 object stack, and then load the canvas with json1 stringify'ing first.
json1.objects.push(json2.objects[0]);
// the next line will put both objects on top of each other, since they're identical
canvas.loadFromJSON(JSON.stringify(json1), canvas.renderAll.bind(canvas));
canvas.renderAll();
Working JSFiddle, http://jsfiddle.net/rekrah/okb2uj1m/.
OPTION 3:
No need to stringify json first with this option, just pass an array of fabric objects to enlivenObjects (in fabric.util), and then add each object to the canvas in its callback.
fabric.util.enlivenObjects([json1.objects[0], json2.objects[0]], function(objects) {
var origRenderOnAddRemove = canvas.renderOnAddRemove;
canvas.renderOnAddRemove = false;
objects.forEach(function(o, i) {
// IF only needed since objects are identical and I wanted to show both objects
if (i == 0) o.set({
top: o.top - 70
});
canvas.add(o);
});
canvas.renderOnAddRemove = origRenderOnAddRemove;
canvas.renderAll();
});
Working JSFiddle, http://jsfiddle.net/rekrah/jnkLjrn4/.
(Option 3 credit goes to a FabricJS master, https://stackoverflow.com/a/19364574/3389046)
For this kind of object manipulation, I always have underscore.js ready. It's the first js library I import when starting a new project.
http://underscorejs.org
Take a look at: _.extend (better even, take a look at the whole lib ;))
_.extend({name: 'moe'}, {age: 50});
=> {name: 'moe', age: 50}
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.
I am trying to get a line graph to display correctly on my site, but for some reason it wants to overflow the graph container. I have tried reset the box-sizing to initial, setting overflow hidden on all child elements of the graph and nothing seems to be working. I have no idea why this is happening and was wondering if anyone had come across this issue before themselves?
I've added an image below of what I am currently getting and underneath that, the object that is being used to set up the line graph.
{
"type": "serial",
"theme": "light",
"marginRight": 80,
"autoMarginOffset": 20,
"marginTop": 7,
"dataProvider": queryData.data.result,
"valueAxes": [{
"axisAlpha": 0.2,
"dashLength": 1,
"position": "left"
}],
"mouseWheelZoomEnabled": true,
"graphs": [{
"id": "g1",
"balloonText": "[[value]]",
"bullet": "round",
"bulletBorderAlpha": 1,
"bulletColor": "#FFFFFF",
"hideBulletsCount": 50,
"title": "red line",
"valueField": "value",
"useLineColorForBulletBorder": true,
"balloon": {
"drop": true
}
}],
"chartScrollbar": {
"autoGridCount": true,
"graph": "g1",
"scrollbarHeight": 40
},
"chartCursor": {
"limitToGraph": "g1"
},
"categoryField": "name",
"dataDateFormat": "DD/MM/YYYY HH:NN:SS",
"categoryAxis": {
"parseDates": true,
"axisColor": "#DADADA",
"dashLength": 1,
"minorGridEnabled": true
},
"export": {
"enabled": true
}
}
This might be happening if you are using <base href> directive on your web page. In those cases references to masking filters in SVG do not work properly, hence lines protruding from plot area.
To avoid that simply add the global baseHref setting line to your code:
AmCharts.baseHref = true;
Please note that this must be a standalone line (not a part of chart config) and go before any of the code that creates charts.