Improve the speed of a realm query in react-native? - react-native

I have something like the following code in my react native app to set up mock/test data for performance tests.
realm.write(() => {
const max = 120;
for(let x=1; x<=max; x++)
{
realm.create('Product', {productId:x});
}
for(let x=1; x<=max; x++)
{
for(let y=x; y<=max; y++)
{
for(let z=y; z<=max; z++)
{
realm.create('Compatibility', {
result: 'Y '+x+' '+y+' '+z,
products: [
realm.objects('Product').filtered('productId = '+x)[0],
realm.objects('Product').filtered('productId = '+y)[0],
realm.objects('Product').filtered('productId = '+z)[0]
]
});
}
}
}
});
class Product {}
Product.schema = {
name: 'Product',
primaryKey:'productId',
properties: {
productId:'int'
}
};
class Compatibility {}
Compatibility.schema = {
name: 'Compatibility',
properties: {
result: {type: 'string'},
products: {type: 'list',objectType:'Product'},
}
};
This means the Products object has 120 records and the Compatibility object has 1.7 million records.
When I run the query realm.objects('Compatibility').filtered(products.productId = 3 AND products.productId = 25 AND products.productId = 97), it takes about 15 seconds to run on my old HTC Desire 510 and my Huawei Nova Plus. This is too slow.
Is there a way to improve the speed of the query? For example, can you index the columns or something?

First of all there is indexing in realm and primaryKeys are indexed already. So indexing in this scenario won't help you. But I think I have an idea of how you could get faster the process.
At the last for loop you are doing 3 queries. 2 of them happens unnecessarily I think since x and y values going to be same for 120 z values. If you implement something like the code below, it might help a little bit with the performance I think.
let productX;
let productY;
let productZ;
for (let x = 1; x <= max; x++)
{
productX = realm.objects('Product').filtered('productId = ' + x)[0];
for (let y = x; y <= max; y++)
{
productY = realm.objects('Product').filtered('productId = ' + y)[0];
for (let z = y; z <= max; z++)
{
productZ = realm.objects('Product').filtered('productId = ' + z)[0];
realm.create('Compatibility',
{
result: 'Y ' + x + ' ' + y + ' ' + z,
products: [ productX, productY, productZ]
});
}
}
}
A second though;
This might be a really bad idea and can be a terrible practice but I'm going to give as a thought practice.
If you are always doing query with 3 different productIds, you can create a string with all tree in a single property and query only that. This way you can use indexing.
Example
class Compatibility {}
Compatibility.schema = {
name: 'Compatibility',
properties: {
result: {type: 'string'},
productQueryHelper: { type: 'string', indexed: true }
products: {type: 'list',objectType:'Product'},
}
};
realm.create('Compatibility',
{
result: 'Y ' + x + ' ' + y + ' ' + z,
productQueryHelper: `${x}&${y}&${z}` // you can use any other separator that isn't possible to be in productId
products: [
realm.objects('Product').filtered('productId = ' + x)[0],
realm.objects('Product').filtered('productId = ' + y)[0],
realm.objects('Product').filtered('productId = ' + z)[0]
]
});
realm.objects('Compatibility').filtered('productQueryHelper = "3&25&97"')

Try to set your primary keys as indexed.
Btw I've never had problems with performance using Realm.
Nowadays I'm using Realm in a scenario to manage my notifications. I have a lot of queries running at some time and this never hurt the performance.
class Product {}
Product.schema = {
name: 'Product',
primaryKey:'productId',
properties: {
productId: { type: 'int', indexed: true }
}
};
class Compatibility {}
Compatibility.schema = {
name: 'Compatibility',
properties: {
result: {type: 'string'},
products: {type: 'list',objectType:'Product'},
}
};

Related

c3.js total count in title of pie chart

I have a question about the pie chart in c3.js.
How can I add the total count of a pie chart in the title??
var title = new Array('data1.sql','data2.sql')
var dtitle = new Array('title1','title2')
var chart = new Array('chart0', 'chart1')
for(var i = 0; i < title.length; i++){
chart[i] = c3.generate({
bindto : '#chart' + i,
size: {
height: 550,
width: 800
},
data : {
url : '/json/sql/data/test/' + title[i],
mimeType : 'json',
type : 'donut'
},
donut: {
title: dtitle[i] + ' - Total:' ,
label: {
format: function(value, ratio, id) {
return value;
}
}
}
});
}
The annoying thing here is that the title option can take a function, but the chart variable is not initialised within it so using the c3 api methods can't be done at this point.
So the best (maybe only) way is to add an onrendered callback that adds up the data as you'd need to anyways and then replace the text in the chart's title text using a spot of d3:
onrendered: function () {
var data = this.api.data();
var total = data.reduce (function (subtotal, t) {
return subtotal + t.values.reduce (function (subsubtotal,b) { return subsubtotal + b.value; }, 0);
}, 0);
d3.select(this.config.bindto + " .c3-chart-arcs-title").text("Total: "+total);
}
Edit: If you want it to keep track of a total as you hide/show series use this
var data = this.api.data.shown.call (this.api);
instead of
var data = this.api.data();

Dynamically update lines in Highcharts time series chart

Here I'm working on Highcharts time series chart with live streaming data, based on the sample jsfiddle. In the fiddle there shows 4 lines named as input1, input2, input3, & input 4 and it is updated with live random data but in my actual project the input values are updated via MQTT. In actual project, sometimes, when we push streaming data, there will be increase or decrease in no: of inputs (such as input5, input6 like wise). So how can we add new line or remove line dynamically in time series chart with streaming data.
javascript code :
$(function() {
$(document).ready(function() {
Highcharts.setOptions({
global: {
useUTC: false
}
});
$('#container').highcharts({
chart: {
type: 'spline',
animation: Highcharts.svg, // don't animate in old IE
marginRight: 10,
events: {
load: function() {
// set up the updating of the chart each second
var series = this.series;
var length = series.length;
setInterval(function() {
var x = (new Date()).getTime(), // current time
a0 = Math.random();
a1 = Math.random();
a2 = Math.random();
series[0].addPoint([x, Math.random()], true, true);
for (var i = 1; i < length; i++) {
series[i].addPoint([x, Math.random()], false, true);
}
}, 1000);
}
}
},
title: {
text: 'Live random data'
},
legend: {
enabled: true
},
xAxis: {
type: 'datetime',
tickPixelInterval: 150
},
yAxis: {
title: {
text: 'Value'
},
plotLines: [{
value: 0,
width: 1,
color: '#808080'
}]
},
tooltip: {
formatter: function() {
return '<b>' + this.series.name + '</b><br/>' +
Highcharts.dateFormat('%Y-%m-%d %H:%M:%S', this.x) + '<br/>' +
Highcharts.numberFormat(this.y, 2);
}
},
exporting: {
enabled: false
},
series: [{
name: 'input1',
data: (function() {
// generate an array of random data
var data = [],
time = (new Date()).getTime(),
i;
for (i = -19; i <= 0; i += 1) {
data.push({
x: time + i * 1000,
y: Math.random()
});
}
return data;
}())
}, {
name: 'input2',
data: (function() {
// generate an array of random data
var data = [],
time = (new Date()).getTime(),
i;
for (i = -19; i <= 0; i += 1) {
data.push({
x: time + i * 1000,
y: Math.random()
});
}
return data;
}())
}, {
name: 'input3',
data: (function() {
// generate an array of random data
var data = [],
time = (new Date()).getTime(),
i;
for (i = -19; i <= 0; i += 1) {
data.push({
x: time + i * 1000,
y: Math.random()
});
}
return data;
}())
}, {
name: 'input4',
data: (function() {
// generate an array of random data
var data = [],
time = (new Date()).getTime(),
i;
for (i = -19; i <= 0; i += 1) {
data.push({
x: time + i * 1000,
y: Math.random()
});
}
return data;
}())
}]
});
});
});

Pushing to list in realm

These are my realm objects. I have Hole and Round. I am trying to populate my round with 18 hole objects in a single write but I've been stuck on this for the past few hours and I can't seem to understand where I'm going wrong.
class Hole extends Realm.Object {}
Hole.schema = {
name: 'Hole',
primaryKey: 'id',
properties: {
id: 'int',
fullStroke: 'int',
halfStroke: 'int',
puts: 'int',
firstPutDistance: 'int',
penalties: 'int',
fairway: 'string'
},
};
class Round extends Realm.Object {}
Round.schema = {
name: 'Round',
primaryKey: 'id',
properties: {
id: 'string',
done: 'string',
holes: {type: 'list', objectType: 'Hole'}
},
};
Here is my function that is attempting to push every hole into the hole property of Round. Any help would be greatly appreciated.
exportRound = () => {
let holesObjects = realm.objects('Hole')
if(holesObjects.length < 9){
alert('Enter stats for at least 9 holes please')
}
else{
var sortedHoles = holesObjects.sorted('id')
currentRound = realm.objects('Round').filtered('done == "no"')
for(var i = 1; i < holesObjects.length; i++){
console.log(holesObjects.filtered('id == i'))
realm.write(()=> currentRound.holes.push(holesObjects.filtered('id == {i}')) )
}
}
}
What's error are you facing?
I found some mistake in your code.
The type of currentRound object is Results. It is not Round object until you retrieve each element. So it doesn't have holes property. You should retrieve the element contained by Results, like the following:
var currentRound = realm.objects('Round').filtered('done == "no"')[0]
String interpolation should be `id == ${i}` (Use backtick and ${}). So your query should be:
holesObjects.filtered(`id == ${i}`)
holesObjects.filtered(`id == ${i}`) returns also Results object. You should retrieve an element first.
realm.write(()=> currentRound.holes.push(holesObjects.filtered(`id == ${i}`)[0]))
The whole code that I edited is the following:
exportRound = () => {
let holesObjects = realm.objects('Hole')
console.log(holesObjects);
if(holesObjects.length < 9){
alert('Enter stats for at least 9 holes please')
}
else{
var sortedHoles = holesObjects.sorted('id')
var currentRound = realm.objects('Round').filtered('done == "no"')[0]
console.log(currentRound)
for(var i = 1; i < holesObjects.length; i++){
console.log(holesObjects.filtered(`id == ${i}`))
realm.write(()=> currentRound.holes.push(holesObjects.filtered(`id == ${i}`)[0]))
}
}
}

Create a running sum graph in dc.js

I am trying to create a running sum in crossfilter to use with dc.js.
I have a set of records like the following :
records = [{"date":"2014-01-01","field1":"value1","field2":"value11","value_field":-20},
{"date":"2014-01-02","field1":"value2","field2":"value12","value_field":100},
{"date":"2014-01-03","field1":"value1","field2":"value11","value_field":-10},
{"date":"2014-01-04","field1":"value2","field2":"value12","value_field":150},
]
So far I have created a barGraph which plays nicely with the other dimensions but I would like to be able to show an line graph of the theoretical field runnning_total (along the dimension date).
To have it done in crossfilter would allow me to then group using the fieldx fields and easily get the same running total graph but restricted to a subgroup of values using dc.js.
Any help is welcome.
Since you are grouping across date (as per your date dimension), the reduce() function would be used to perform aggregations grouped by date, as per the highlighted cells in my Mickey Mouse example below:
With a running total you'd need to perform an entirely different operation, looping down the rows:
You can aggregate the data and then append the running total field as follows. I've also included an example of how to calculate an average value, using the reduce function:
records = [{ "date": "2014-01-01", "field1": "value1", "field2": "value11", "value_field": -20 },
{ "date": "2014-01-02", "field1": "value2", "field2": "value12", "value_field": 100 },
{ "date": "2014-01-03", "field1": "value1", "field2": "value11", "value_field": -10 },
{ "date": "2014-01-04", "field1": "value2", "field2": "value12", "value_field": 150 }
];
var cf = crossfilter(records);
var dimensionDate = cf.dimension(function (d) {
return d.date;
});
function reduceAdd(p, v) {
p.total += v.value_field;
p.count++;
p.average = p.total / p.count;
return p;
}
function reduceRemove(p, v) {
p.total -= v.value_field;
p.count--;
p.average = p.count ? p.total / p.count : 0;
return p;
}
function reduceInitial() {
return {
total: 0,
count: 0,
average: 0,
};
}
var average = dimensionDate.group().reduce(reduceAdd, reduceRemove, reduceInitial).all();
var averageWithRunningTotal = appendRuningTotal(average);
function appendRuningTotal(average) {
var len = average.length,
runningTotal = 0;
for (var i = 0; i < len; i++) {
runningTotal += average[i].value.total;
average[i].RunningTotal = runningTotal;
}
return average;
}
And this returns the following:
{"key":"2014-01-01","value":{"total":-20,"count":1,"average":-20},"RunningTotal":-20}
{"key":"2014-01-02","value":{"total":100,"count":1,"average":100},"RunningTotal":80}
{"key":"2014-01-03","value":{"total":-10,"count":1,"average":-10},"RunningTotal":70}
{"key":"2014-01-04","value":{"total":150,"count":1,"average":150},"RunningTotal":220}
Well I know the op already built a solution but after struggling with for a while I was able to crack it, so posting it here if someone else searches for it.
using the cumulative for the following: https://groups.google.com/forum/#!topic/dc-js-user-group/W9AvkP_dZ0U
Running Sum:
var _group = dim.group().reduceSum(function(d) {return 1;});
var group = {
all:function () {
var cumulate = 0;
var g = [];
_group.all().forEach(function(d,i) {
cumulate += d.value;
g.push({key:d.key,value:cumulate})
});
return g;
}
};
for Trailing Twelve Month calculation:
var _group = dateDim.group().reduceSum(function(d) {return d.revenue;});
var group = {
all:function () {
var g = [];
_group.all().forEach(function(d,i) {
var cumulate = 0;
var monthcount =0;
var dt =new Date( d.key);
var ct = new Date(d.key);
ct.setFullYear(ct.getUTCFullYear() -1);
_group.all().forEach(function(d2,i) {
var dt2 = new Date(d2.key);
if(dt2 <= dt && dt2 > ct){
cumulate += d2.value;
monthcount++;
}
})
if(monthcount>=11){
console.log( ' Dt ' + dt + ' ' + cumulate + ' months ' + monthcount);
g.push({key:d.key,value:cumulate})
}
});
return g;
}
};

How to get textbox widget inside grid to work property?

I have placed a textbox widget inside grid cell by using formatter. However, I cannot move my cursor around nor select text inside the textbox.
E.g.
http://jsfiddle.net/g33m9/69/
Does anyone know how to fix this?
Thanks
You need to set the column as 'editable' so that the Grid component will know how to handle keypressed events. So a modification to the layout is in order
from
var layout = [[
{name: 'Column 1', field: 'col1'},
{name: 'Column 2', field: 'col2', width:'200px', formatter: func}
]];
to
var layout = [[
{name: 'Column 1', field: 'col1'},
{name: 'Column 2', field: 'col2', width:'200px', formatter: func, editable: true}
]];
Edit state activates by doubleclick.
Now, OP wants it to be a fully bloated widget, popping up in the editable state. For this to be scaleable up with any number of rows/columns i will restrict this to the edit state, so that the value simply shows text but once double-clicked it will pop a FilteringSelect. Same principle goes with the dijit widget ValidationTextBox.
Currently (1.7.2) the possible celltypes are:
dojox.grid.cells.Bool
dojox.grid.cells.ComboBox
dojox.grid.cells.DateTextBox
dojox.grid.cells.Select
Catch me SEO:
example of custom dojox.grid cellType widget - semi-programmatic
First step - create some data
var i = 0,
data = {
identifier: 'id',
items: [
{ id: i, value: 'val'+i++},
{ id: i, value: 'val'+i++},
{ id: i, value: 'val'+i++},
{ id: i, value: 'val'+i++}
]
},
// The item label which holds visible value and which holds the value to represent
searchAttr = 'value',
valueAttr = data.identifier,
// The store to use for select widget
store = new dojo.data.ItemFileReadStore({ data: data }),
// And the options, reassembling the valid options we will present in dropdown
// Used when cellType is dojox.grid.cells.Select to name the allowable options
options = [];
dojo.forEach(data.items, function(it) { options.push(it[searchAttr])});
Tricky part - Define a cellType
Lets extend the existing dojox.grid.cells.Cell, it has two key features - an edit-state-formatter and the default-formatter. The default would work just fine. Last but not least, we'll override the '_finish' function allthough allow the Cell to process its own definition too.
var whenIdle = function( /*inContext, inMethod, args ...*/ ) {
setTimeout(dojo.hitch.apply(dojo, arguments), 0);
};
var FilteringSelectCell = declare("dojox.grid.cells.FilteringSelect", [dojox.grid.cells.Cell], {
options: null,
values: null,
_destroyOnRemove: true,
constructor: function(inCell){
this.values = this.values || this.options;
},
selectMarkupFactory: function(cellData, rowIndex) {
var h = ['<select data-dojo-type="dijit.form.FilteringSelect" id="deleteme' + rowIndex + '" name="foo">'];
for (var i = 0, o, v;
((o = this.options[i]) !== undefined) && ((v = this.values[i]) !== undefined); i++) {
v = v.replace ? v.replace(/&/g, '&').replace(/</g, '<') : v;
o = o.replace ? o.replace(/&/g, '&').replace(/</g, '<') : o;
h.push("<option", (cellData == v ? ' selected' : ''), ' value="' + v + '"', ">", o, "</option>");
}
h.push('</select>');
return h;
},
textMarkupFactory: function(cellData, rowIndex) {
return ['<input class="dojoxGridInput" id="deleteme' + rowIndex + '" data-dojo-type="dijit.form.ValidationTextBox" type="text" value="' + cellData + '">']
},
// #override
formatEditing: function(cellData, rowIndex) {
this.needFormatNode(cellData, rowIndex);
var h = (cellData == "W1")
? this.textMarkupFactory(cellData, rowIndex)
: this.selectMarkupFactory(cellData, rowIndex);
// a slight hack here, i had no time to figure out when the html would actually be inserted to the '<td>' so.. Use 'debugger' statement and track function to hook into
whenIdle(function() {
dojo.parser.parse(dojo.byId('deleteme' + rowIndex).parentNode);
var w = dijit.byId('deleteme' + rowIndex);
w.focus()
});
return h.join('');
},
// clean up avoiding multiple widget definitions 'hanging'
_finish: function(inRowIndex) {
this.inherited(arguments)
dijit.byId('deleteme' + inRowIndex).destroy();
},
// needed to read the value properly, will work with either variant
getValue: function(rowIndex) {
var n = this.getEditNode(rowIndex);
n = dijit.getEnclosingWidget(n);
return n.get("value");
}
});
Last bit, a new layout
var layout = [[
{ name: 'Column 1', field: 'col1' },
{ name: 'Column 2', field: 'col2',
cellType: FilteringSelectCell, options: options, editable: true
}
]];
Running sample here http://jsfiddle.net/dgbxw/1/