How to make friends Fabric.js and Redux? - fabricjs

Is there any approach to use Fabric.js and Redux together? Fabric.js state should be used as part of store, but it isn't immutable and can mutate itself by user canvas interaction. Any idea? Thanks.

I have extracted small example from my implementation of React-Redux and Fabric.js.
It works by simply getting whole fabric object by fabric.toObject(), saving it into state and revoking by fabric.loadFromJSON(). You can play around by using Redux DevTools and traveling through the state.
For any case, there is also jsfiddle available: https://jsfiddle.net/radomeer/74t5y1r0/
// don't be scared, just some initial objects to play with (fabric's serialized JSON)
const initialState = {
canvasObject: {
"objects": [{
"type": "circle",
"originX": "center",
"originY": "center",
"left": 50,
"top": 50,
"width": 100,
"height": 100,
"fill": "#FF00FF",
"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,
"radius": 50,
"startAngle": 0,
"endAngle": 6.283185307179586
}, {
"type": "rect",
"originX": "center",
"originY": "center",
"left": 126,
"top": 210,
"width": 100,
"height": 100,
"fill": "#FF0000",
"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,
"radius": 50,
"startAngle": 0,
"endAngle": 6.283185307179586
}, {
"type": "triangle",
"originX": "center",
"originY": "center",
"left": 250,
"top": 100,
"width": 100,
"height": 100,
"fill": "#00F00F",
"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,
"radius": 50,
"startAngle": 0,
"endAngle": 6.283185307179586
}],
"background": ""
}
};
// Redux part
const canvasObjectReducer = function(state = initialState, action) {
switch (action.type) {
case "OBJECTS_CANVAS_CHANGE":
return Object.assign({}, state, {
canvasObject: action.payload.canvasObject,
selectedObject: action.payload.selectedObject
});
default:
return state
}
return state;
}
// standard react-redux boilerplate
const reducers = Redux.combineReducers({
canvasObjectState: canvasObjectReducer
});
const { createStore } = Redux;
const store = createStore(reducers, window.devToolsExtension && window.devToolsExtension());
const { Provider } = ReactRedux;
const { Component } = React;
const MyProvider = React.createClass({
render: function() {
return (
<div>
<Provider store={store}>
<FabricCanvasReduxed/>
</Provider>
</div>
);
}
});
// Fabric part
var fabricCanvas = new fabric.Canvas();
// class which takes care about instantiating fabric and passing state to component with actual canvas
const FabricCanvas = React.createClass({
componentDidMount() {
// we need to get canvas element by ref to initialize fabric
var el = this.refs.canvasContainer.refs.objectsCanvas;
fabricCanvas.initialize(el, {
height: 400,
width: 400,
});
// initial call to load objects in store and render canvas
this.refs.canvasContainer.loadAndRender();
fabricCanvas.on('mouse:up', () => {
store.dispatch({
type: 'OBJECTS_CANVAS_CHANGE',
payload: {
// send complete fabric canvas object to store
canvasObject: fabricCanvas.toObject(),
// also keep lastly active (selected) object
selectedObject: fabricCanvas.getObjects().indexOf(fabricCanvas.getActiveObject())
}
});
this.refs.canvasContainer.loadAndRender();
});
},
render: function() {
return (
<div>
{/* send store and fabricInstance viac refs (maybe not the cleanest way, but I was not able to create global instance of fabric due to use of ES6 modules) */}
<CanvasContainer ref="canvasContainer" canvasObjectState={this.props.objects} fabricInstance={fabricCanvas}/>
</div>
)
}
});
const mapStateToProps = function(store) {
return {
objects: store.canvasObjectState
};
};
// we can not use export default on jsfiddle so we need react class with mapped state in separate constant
const FabricCanvasReduxed = ReactRedux.connect(mapStateToProps)(FabricCanvas);
const CanvasContainer = React.createClass({
loadAndRender: function() {
var fabricCanvas = this.props.fabricInstance;
fabricCanvas.loadFromJSON(this.props.canvasObjectState.canvasObject);
fabricCanvas.renderAll();
// if there is any previously active object, we need to re-set it after rendering canvas
var selectedObject = this.props.canvasObjectState.selectedObject;
if (selectedObject > -1) {
fabricCanvas.setActiveObject(fabricCanvas.getObjects()[this.props.canvasObjectState.selectedObject]);
}
},
render: function() {
this.loadAndRender();
return (
<canvas ref="objectsCanvas">
</canvas>
);
}
});
var App = React.createClass({
render: function() {
return (
<div>
<MyProvider/>
</div>
);
}
});
ReactDOM.render( <App/>, document.getElementById('container'));
<!--
Please use Redux DevTools for Chrome or Firefox to see the store changes and time traveling
https://github.com/zalmoxisus/redux-devtools-extension
Inspired by https://jsfiddle.net/STHayden/2pncoLb5/
-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.6.4/fabric.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/4.4.5/react-redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/3.6.0/redux.js"></script>
<div id="container">
</div>

I found some solution. I try to describe, but sorry for english. Because there is no immutabity in Fabric.js it's hard to implement state management with redux. As far I understand default solution is to use fabric.loadFromJson function for push new state and serialization for pull and store for next manipulations such as actions history. But in this case JSON parsing will be bottleneck if you want to work with images, because they will be stored in Base64 data-uri.
The way is a bit hacky, but it works for me. I was replacing inner array of objects of fabric.js (fabric._objects) and invoking render everytime when something happens on canvas, e.g. moving objects by mouse.
First of all, my state is immutable now via Immutable.js, i.e. i have to return immutable List in my reducers. But elements of these lists are not immutable, it is just fabric.js objects stored in order that they should render. My state consist of objects list, selection list and several helpers objects that represent e.g viewport state (zoom, panning). Object state list keys used as ID of objects in actions. There is structure of my root scene reducer.
const sceneReducer = composeReducers(
whetherRecordCurrentState,
combineReducers({
project: undoable(
composeReducers(
projectActions,
combineReducers({
objects,
params,
counters
}),
),
{
limit: historyLimit,
filter: combineFilters(
recordFilter,
excludeAction([
'CREATE_SELECTION',
'CLEAR_SELECTION',
'SET_WORKSPACE_NAME',
'SET_WORKSPACE_ID',
'SET_WORKSPACE_TYPE',
'SET_TAGS',
]),
)
}
),
selection,
meta,
viewport,
recording
}),
selectJustCreatedObject
);
It implements any fabric.js possibilities including async functions such as applying filters. Also I use redux-undoable package and it allows implement unlimitted undo/redo history. It also allows implement not stored actions, such as opacity changing by slider (all intermediate states will be not stored). Since I use immutability I can push new history state with only one changed object to save memory. There is my state
https://i.gyazo.com/fcef421e9ccfa965946a6e5930e42edf.png
See how it works: in fabric.js I handle event with new object state. Then I dispatch action with that state as payload. In my actions I can create new fabric objects or pass updated objects. All async operations (filtering, changing image source) performing in actions and pass to reducer ready new object. In reducers there is access to my fabric.js objects factory that creates deep copy of object with one distinction. I patched fabric.js (monkey patching, but you can use prototype extending) and it does not serialize images to base64 anymore. I implement it by overriding method Object.toDatalessObject(), that returns same json without images data. Instead source data-uri image data it storing link to HTMLElement object by manually setting Image._element. I.e. after changing images coordinates new image object will have same _element. It allows to save memory and accelerate application.
After all, my container for fabric.js is React component. It connects with redux and after commiting change invokes componentWillRecievProps method. In method I catch new state, create copy with my factory (yes, there is double copying, it should be optimized, but it works fine for me) and pass it to fabric._objects and then I invoke render.
I hope it helps.

Related

LLRP for Zebra FX7500 with llrpjs doesn't read tags

Using the llrpjs library for Node.js, we are attempting to read tags from the Zebra FX7500 (Motorola?). This discussion points to the RFID Reader Software Interface Control Guide pages 142-144, but does not indicate potential values to set up the device.
From what we can gather, we should issue a SET_READER_CONFIG with a custom parameter (MotoDefaultSpec = VendorIdentifier: 161, ParameterSubtype: 102, UseDefaultSpecForAutoMode: true). Do we need to include the ROSpec and/or AccessSpec values as well (are they required)? After sending the SET_READER_CONFIG message, do we still need to send the regular LLRP messages (ADD_ROSPEC, ENABLE_ROSPEC, START_ROSPEC)? Without the MotoDefaultSpec, even after sending the regular LLRP messages, sending a GET_REPORT does not retrieve tags nor does a custom message with MOTO_GET_TAG_EVENT_REPORT. They both trigger a RO_ACCESS_REPORT event message, but the tagReportData is null.
The README file for llrpjs lists "Vendor definitions support" as a TODO item. While that is somewhat vague, is it possible that the library just hasn't implemented custom LLRP extension (messages/parameters) support, which is why none of our attempts are working? The MotoDefaultSpec parameter and MOTO_GET_TAG_EVENT_REPORT are custom to the vendor/chipset. The MOTO_GET_TAG_EVENT_REPORT custom message seems to trigger a RO_ACCESS_REPORT similar to the base LLRP GET_REPORT message, so we assume that part is working.
It is worth noting that Zebra's 123RFID Desktop setup and optimization tool connects and reads tags as expected, so the device and antenna are working (reading tags).
Could these issues be related to the ROSPEC file we are using (see below)?
{
"$schema": "https://llrpjs.github.io/schema/core/encoding/json/1.0/llrp-1x0.schema.json",
"id": 1,
"type": "ADD_ROSPEC",
"data": {
"ROSpec": {
"ROSpecID": 123,
"Priority": 1,
"CurrentState": "Disabled",
"ROBoundarySpec": {
"ROSpecStartTrigger": {
"ROSpecStartTriggerType": "Immediate"
},
"ROSpecStopTrigger": {
"ROSpecStopTriggerType": "Null",
"DurationTriggerValue": 0
}
},
"AISpec": {
"AntennaIDs": [1, 2, 3, 4],
"AISpecStopTrigger": {
"AISpecStopTriggerType": "Null",
"DurationTrigger": 0
},
"InventoryParameterSpec": {
"InventoryParameterSpecID": 1234,
"ProtocolID": "EPCGlobalClass1Gen2"
}
},
"ROReportSpec": {
"ROReportTrigger": "Upon_N_Tags_Or_End_Of_ROSpec",
"N": 1,
"TagReportContentSelector": {
"EnableROSpecID": true,
"EnableAntennaID": true,
"EnableFirstSeenTimestamp": true,
"EnableLastSeenTimestamp": true,
"EnableSpecIndex": false,
"EnableInventoryParameterSpecID": false,
"EnableChannelIndex": false,
"EnablePeakRSSI": false,
"EnableTagSeenCount": true,
"EnableAccessSpecID": false
}
}
}
}
}
For anyone having a similar issue, we found that attempting to configure more antennas than the Zebra device has connected caused the entire spec to fail. In our case, we had two antennas connected, so including antennas 3 and 4 in the spec was causing the problem.
See below for the working ROSPEC. The extra antennas in the data.AISpec.AntennaIDs property were removed and allowed our application to connect and read tags.
We are still having some issues with llrpjs when trying to STOP_ROSPEC because it sends an RO_ACCESS_REPORT response without a resName value. See the issue on GitHub for more information.
That said, our application works without sending the STOP_ROSPEC command.
{
"$schema": "https://llrpjs.github.io/schema/core/encoding/json/1.0/llrp-1x0.schema.json",
"id": 1,
"type": "ADD_ROSPEC",
"data": {
"ROSpec": {
"ROSpecID": 123,
"Priority": 1,
"CurrentState": "Disabled",
"ROBoundarySpec": {
"ROSpecStartTrigger": {
"ROSpecStartTriggerType": "Null"
},
"ROSpecStopTrigger": {
"ROSpecStopTriggerType": "Null",
"DurationTriggerValue": 0
}
},
"AISpec": {
"AntennaIDs": [1, 2],
"AISpecStopTrigger": {
"AISpecStopTriggerType": "Null",
"DurationTrigger": 0
},
"InventoryParameterSpec": {
"InventoryParameterSpecID": 1234,
"ProtocolID": "EPCGlobalClass1Gen2",
"AntennaConfiguration": {
"AntennaID": 1,
"RFReceiver": {
"ReceiverSensitivity": 0
},
"RFTransmitter": {
"HopTableID": 1,
"ChannelIndex": 1,
"TransmitPower": 170
},
"C1G2InventoryCommand": {
"TagInventoryStateAware": false,
"C1G2RFControl": {
"ModeIndex": 23,
"Tari": 0
},
"C1G2SingulationControl": {
"Session": 1,
"TagPopulation": 32,
"TagTransitTime": 0,
"C1G2TagInventoryStateAwareSingulationAction": {
"I": "State_A",
"S": "SL"
}
}
}
}
}
},
"ROReportSpec": {
"ROReportTrigger": "Upon_N_Tags_Or_End_Of_AISpec",
"N": 1,
"TagReportContentSelector": {
"EnableROSpecID": true,
"EnableAntennaID": true,
"EnableFirstSeenTimestamp": true,
"EnableLastSeenTimestamp": true,
"EnableTagSeenCount": true,
"EnableSpecIndex": false,
"EnableInventoryParameterSpecID": false,
"EnableChannelIndex": false,
"EnablePeakRSSI": false,
"EnableAccessSpecID": false
}
}
}
}
}

Read BLOB from Oracle Database in Node.js

I am trying to read a BLOB data type from Oracle using a nodejs from npm package called oracledb and this is my select statement, it hold picture in it:
select media from test_data
I have tried using a few solution but it doesn't work.
Here is my response from the res.json:
{
"MEDIA": {
"_readableState": {
"objectMode": false,
"highWaterMark": 16384,
"buffer": {
"head": null,
"tail": null,
"length": 0
},
"length": 0,
"pipes": [],
"flowing": null,
"ended": false,
"endEmitted": false,
.....
},
How do I get the hex value of it?
You are seeing an instance of the node-oracledb Lob class which you can stream from.
However, it can be easiest to get a Buffer directly. Do this by setting oracledb.fetchAsBuffer, for example:
oracledb.fetchAsBuffer = [ oracledb.BLOB ];
const result = await connection.execute(`SELECT b FROM mylobs WHERE id = 2`);
if (result.rows.length === 0)
console.error("No results");
else {
const blob = result.rows[0][0];
console.log(blob.toString()); // assuming printable characters
}
Refer to the documentation Simple LOB Queries and PL/SQL OUT Binds.

Chart JS change pointBackgroundColor based on data value

So I have created a basic line chart using Chartjs. How would I go about changing the color of the points (pointBackgroundColor) depending on the value of the data? For example, if the data point is less than 10 it changes to red, or if the data point is between 10 and 20 it changes to blue?
const CHART = document.getElementById("lineChart");
let lineChart = new Chart(CHART, {
type: 'line',
data: {
labels: ["5/10/2010", "5/11/2010", "5/12/2010", "5/13/2010", "5/14/2010", "5/15/2010", "5/16/2010"],
datasets: [
{
label: "Theta",
fill: false,
lineTension: 0,
backgroundColor: "rgba(75,192,192,0.4)",
borderColor: "rgba(9,31,62)",
borderCapStyle: 'butt',
borderDash: [],
borderDashOffset: 0.0,
borderJoinStyle: 'miter',
pointBorderColor: "rgba(0,191,255)",
pointBackgroundColor: "rgba(0,191,255)",
pointBorderWidth: 5,
pointBorderRadius: 5,
pointHoverBackgroundColor: "rgba(75,192,192,1)",
pointHoverBorderColor: "rgba(220,220,220,1)",
pointHoverBorderWidth: 2,
pointRadius: 1,
pointHitRadius: 10,
data: [15, 28, 11, 3, 34, 65, 20],
}
]
},
options: {
maintainAspectRatio: false,
responsive: true,
legend: {
display: false,
},
scales: {
yAxes:[{
ticks: {
fontColor: "#091F3e",
beginAtZero: true,
steps: 10,
stepSize: 10,
max: 100
},
gridLines: {
display: false
}
}],
xAxes:[{
ticks: {
fontColor: "#091F3e",
fontSize: "10",
},
gridLines: {
display: false
}
}]
}
}
});
You can use a closure on any option and manipulate the returned value according to the context. In the example bellow I'm the pointBackgroundColor is red when the current value is greater then 0 and blue otherwise.
pointBackgroundColor: function (context) {
let value = context.dataset.data[context.dataIndex];
return value > 0
? 'red'
: 'blue';
},
Here is another thing that may help you Change bar color depending on value.
Its original answer from Tot Zam
Adding the code sample in case the link doesn't work.
var colorChangeValue = 50; //set this to whatever is the decidingcolor change value
var dataset = myChart.data.datasets[0];
for (var i = 0; i < dataset.data.length; i++) {
if (dataset.data[i] > colorChangeValue) {
dataset.backgroundColor[i] = chartColors.red;
}
}
myChart.update();
Its about bars background, but you can try work around and find same solution.

Alter SVG After Highcharts is Redrawn

I'm trying to specifically target the first and last x-axis labels, which are text elements, and alter their attributes.
To preface this issue, I was trying to keep the first and last x-axis labels within the confines of the Highcharts container. Unfortunately, they are cutting off in the UI.
This is my configuration:
xAxis: [{
type: 'datetime',
showFirstLabel: true,
showLastLabel: true,
labels: {
step: 3,
formatter: function(){
return options.labels[this.value];
},
style: {
fontSize: '10px',
color: '#9c9c9c'
},
// x: 5,
y: 10
},
startOnTick: true,
// lineColor: 'transparent',
tickLength: 0,
minPadding: 0,
maxPadding: 0
}],
The step key is causing this I realize. Otherwise, I would be able to use overflow:justify, but I wanted the step. So, I then decided that perhaps altering the SVG would be a good solution. It worked when the chart is first loaded, but it does not work when being redrawn.
This was my solution on 'load', which I'd like to replicate on 'redraw':
Highcharts.Chart.prototype.callbacks.push(function(chart){
$('.highcharts-axis-labels.highcharts-xaxis-labels text').first().attr('x', 25);
$('.highcharts-axis-labels.highcharts-xaxis-labels text').last().attr('x', 813);
});
You should configure these callbacks in the options of your chart. It's cleaner that way:
function moveLabels(){
$('.highcharts-axis-labels.highcharts-xaxis-labels text').first().attr('x', 25);
$('.highcharts-axis-labels.highcharts-xaxis-labels text').last().attr('x', 813);
}
$('#container').highcharts({
chart: {
events: {
load: moveLabels
redraw: moveLabels
}
}
},
...

Search is not working with filter toolbar in JQGrid

I am facing issue while loaidng the data in JQGrid at a later stage in place of at the time of ceating grid. I am using filter toolbar for search.
following is the code I am using:
Creating Grid
jQuery("#list").jqGrid({
datatype: 'local',
colNames: [my col names],
colModel: [my col model],
jsonReader: {
root: "rows",
page: "page",
total: "total",
//records: "records",
repeatitems: false
},
height: 300,
viewrecords: true,
gridComplete: this.onGridComplete,
ondblClickRow: this.rowDblClick,
onSelectRow: this.selectRow,
headertitles: false,
loadtext: "Loading...",
sortable: true,
altRows: true,
loadonce: true,
rowNum: 100,
pager: '#pager',
root: "rows",
rowList: [100, 200, 300],
pagination: true,
ignoreCase: true
})
Load data at later stage
if(gridDataStr != "none") // gridDatStr has data
{
grid.initialize(); // create the grid
var myjsongrid = JSON.parse(gridDataStr);
grid.table[0].addJSONData(myjsongrid);
grid.table.jqGrid('setGridParam',{datatype:'json', data:myjsongrid}).trigger('reloadGrid');
if (myjsongrid["rows"].length > 1)
{
grid.table.filterToolbar({
stringResult: true,
searchOnEnter: false
});
}
}
However if I load the same data at the time of creating the grid with datatype:json and using some valid url, searching is working well.
Any suggestions?
The method addJSONData can not be used to work with the jqGrid having the 'local' datatype.
You can use addRowData and to use localReader instead of jsonReader or set data parameter of jqGrid with respect of setGridParam method and then call jQuery("#list")[0].refreshIndex() (see here) and reload the grid.

Resources