Google Script AddRecord Function and For Loops - for-loop

I am trying to create a for loop using Google Script and having a hard time with adding records. When I add a record, I receive one's email and then 36 questions as such:
function AddRecord(email, q1, q2, q3, q4, q5, q6, q7, q8, q9, q10, q11, q12, q13, q14, q15, q16, q17, q18, q19, q20, q21, q22, q23, q24, q25, q26, q27, q28, q29, q30, q31, q32, q33, q34, q35, q36){
var url ="Name of Url";
var ss= SpreadsheetApp.openByUrl(url);
var ws = ss.getSheetByName("Sheet1");
ws.appendRow([email, q1, q2, q3, q4, q5, q6, q7, q8, q9, q10, q11, q12, q13, q14, q15, q16, q17, q18, q19, q20, q21, q22, q23, q24, q25, q26, q27, q28, q29, q30, q31, q32, q33, q34, q35, q36]);}
I would think the code below would allow me to pass this function correctly by the spread operator. I have tried different things but I am confused with how to proceed dealing with parameters/arrays/etc. How would I go about creating a for loop for this?
const questions = [];
for (let i = 1; i <= 36; i++) {
questions.push(`q${i}`);
}
function AddRecord(email, ... questions){
var url ="Name of Url";
var ss= SpreadsheetApp.openByUrl(url);
var ws = ss.getSheetByName("Sheet1");
ws.appendRow([email, ... questions]);}

Try this:
function AddRecord(email, questions) {
var url = "Name of Url";
var ss = SpreadsheetApp.openByUrl(url);
var ws = ss.getSheetByName("Sheet1");
questions.unshift(email)
ws.appendRow(questions);
}
I had to do this to test it.
function AddRecord() {
const email = 'email';
const questions = [];
for (let i = 1; i <= 36; i++) { questions.push(`q${i}`); }
var id = gobj.globals.testsourceid;//I have this id in globals
var ss = SpreadsheetApp.openById(id);//so I went with openById()
var ws = ss.getSheetByName("Sheet2");
questions.unshift(email)
ws.appendRow(questions);
}
Testing it this way is much better:
function testAddRecord() {
const email = 'email';
const questions = [];
for (let i = 1; i <= 36; i++) { questions.push(`q${i}`); }
AddRecord(email, questions);
}
function AddRecord(email, questions) {
var id = gobj.globals.testsourceid;
var ss = SpreadsheetApp.openById(id);
var ws = ss.getSheetByName("Sheet2");
questions.unshift(email)
ws.appendRow(questions);
}
output:
email
q1
q2
q3
q4
q5
q6
q7
q8
q9
q10
q11
q12
q13
q14
q15
q16
q17
q18
q19
q20
q21
q22
q23
q24
q25
q26
q27
q28
q29
q30
q31
q32
q33
q34
q35
q36

Related

I have a google script that creates QR codes and Barcode. the QR Codes show up twice. Once where its suppose to and then at the end of the document

The code below generates a label that has a QR Code and barcode. Currently I am trying to get both to work. They both show up in the label but when it does the QR code will be added in the correct spot but also will add extra qr codes on the last label generated. The two qr codes at the end are itemnum qr codes and they are not suppose to be there. It normally puts them ontop of each other i moved them so people could see. The barcodes generate just fine but they don't scan
TEST SCRIPT here is a link to test it.. This is a test script and not part of a business so its easy to do. Basically what needs done is click on My Custom Menu and select reset labels. Once the labels are created you can go to the labels tab and select a few labels with serial numbers then go back to the menu and select create labels for selected items.
//####################################### Create Zebra Label #########################
function createZebraLabel(monthlySheet, newSheetName) {
var parentFolder = DriveApp.getFolderById('1mUPLw1PSi-guH19uJPCPWmogkpdckLcW');
var createdoc = DocumentApp.create('Zebra Label').getId();
doc = DocumentApp.openById(createdoc);
var id = createdoc;
var docFile = DriveApp.getFileById(doc.getId());
var target = parentFolder;
var file1 = DriveApp.getFileById(id);
var mkfile1 = file1.makeCopy(newSheetName, target).getId();
doc = DocumentApp.openById(mkfile1);
docFile.setTrashed(true);
//325
var body = doc.getBody().setPageWidth(325).setPageHeight(180).setMarginTop(.2).setMarginBottom(.2);
// var body = doc.getBody().setPageWidth(144).setPageHeight(144).setMarginTop(.2).setMarginBottom(.2);
var sourceSpreadSheet = SpreadsheetApp.openById(monthlySheet);
var ss = sourceSpreadSheet.getSheetByName('Sheet1');
var startRow = 1; // First row of data to process
var numRows = 250; // Number of rows to process
var startColumn = 1; // A=1 B=2
var numColumns = 5; // Number of columns to process
var dataRange = ss.getRange(startRow, startColumn, numRows, numColumns);
var data = dataRange.getValues();
for (var i = 1; i < data.length; ++i) {
var row = data[i];
var actualrow = i;
var itemnum = row[0]; // A column
var desc = row[1]; // B column
var serial = row[2]; // D column
var bin = row[3]; // D column
var qty = row[4];
var styleLeft = {};
styleLeft[DocumentApp.Attribute.HORIZONTAL_ALIGNMENT] = DocumentApp.HorizontalAlignment.LEFT;
styleLeft[DocumentApp.Attribute.FONT_FAMILY] = 'Calibri';
styleLeft[DocumentApp.Attribute.FONT_SIZE] = 14;
styleLeft[DocumentApp.Attribute.BOLD] = true;
var styleRight = {};
styleRight[DocumentApp.Attribute.HORIZONTAL_ALIGNMENT] = DocumentApp.HorizontalAlignment.RIGHT;
styleRight[DocumentApp.Attribute.FONT_FAMILY] = 'Calibri';
styleRight[DocumentApp.Attribute.FONT_SIZE] = 14;
styleRight[DocumentApp.Attribute.BOLD] = true;
var itemBarcode = {};
itemBarcode[DocumentApp.Attribute.HORIZONTAL_ALIGNMENT] = DocumentApp.HorizontalAlignment.LEFT;
itemBarcode[DocumentApp.Attribute.FONT_FAMILY] = 'Libre Barcode 39 Extended Text';
itemBarcode[DocumentApp.Attribute.FONT_SIZE] = 25;
itemBarcode[DocumentApp.Attribute.BOLD] = false;
var serialBarcode = {};
serialBarcode[DocumentApp.Attribute.HORIZONTAL_ALIGNMENT] = DocumentApp.HorizontalAlignment.CENTER;
serialBarcode[DocumentApp.Attribute.FONT_FAMILY] = 'Libre Barcode 39 Extended Text';
serialBarcode[DocumentApp.Attribute.FONT_SIZE] = 20;
serialBarcode[DocumentApp.Attribute.BOLD] = false;
var nospace = {};
nospace[DocumentApp.Attribute.HORIZONTAL_ALIGNMENT] = DocumentApp.HorizontalAlignment.RIGHT;
nospace[DocumentApp.Attribute.FONT_FAMILY] = 'Calibri';
nospace[DocumentApp.Attribute.FONT_SIZE] = 1;
nospace[DocumentApp.Attribute.BOLD] = true;
var text = body.editAsText();
var itemqrcode = UrlFetchApp.fetch('https://chart.googleapis.com/chart?chs=70x70&cht=qr&chl=' + itemnum);
var serialqrcode = UrlFetchApp.fetch('https://chart.googleapis.com/chart?chs=70x70&cht=qr&chl=' + serial);
if (itemnum != '') {
//#################### SERIAL ###################
if (serial != '') { // SERIAL NUMBER
body.appendParagraph('').setAttributes(nospace);
var paragraph = body.appendParagraph('Item Number: ' + itemnum).setAttributes(styleLeft);
paragraph.addPositionedImage(itemqrcode.getBlob()).setLayout(DocumentApp.PositionedLayout.WRAP_TEXT)
body.appendParagraph('*' + itemnum + '*').setAttributes(itemBarcode);
body.appendParagraph('\n\n').setAttributes(nospace).appendHorizontalRule();
//description
body.appendParagraph(desc).setAttributes(styleLeft);
body.appendParagraph('').setAttributes(nospace);
//serials
body.appendParagraph('').setAttributes(nospace).appendHorizontalRule();
var paragraph = body.appendParagraph('Serial Number: ' + serial).setAttributes(styleRight);
body.appendParagraph('*' + serial + '*').setAttributes(serialBarcode);
paragraph.addPositionedImage(serialqrcode.getBlob()).setLayout(DocumentApp.PositionedLayout.WRAP_TEXT)
body.appendParagraph('').setAttributes(nospace);
} else if (serial == '') { // NO SERIAL NUMBER
var styleCenter = {};
styleCenter[DocumentApp.Attribute.HORIZONTAL_ALIGNMENT] = DocumentApp.HorizontalAlignment.CENTER;
styleCenter[DocumentApp.Attribute.FONT_FAMILY] = 'Calibri';
styleCenter[DocumentApp.Attribute.FONT_SIZE] = 18;
styleCenter[DocumentApp.Attribute.BOLD] = true;
var itemBarcode = {};
itemBarcode[DocumentApp.Attribute.HORIZONTAL_ALIGNMENT] = DocumentApp.HorizontalAlignment.CENTER;
itemBarcode[DocumentApp.Attribute.FONT_FAMILY] = 'Libre Barcode 39 Extended Text';
itemBarcode[DocumentApp.Attribute.FONT_SIZE] = 35;
itemBarcode[DocumentApp.Attribute.BOLD] = false;
var styleLocation = {};
styleLocation[DocumentApp.Attribute.HORIZONTAL_ALIGNMENT] = DocumentApp.HorizontalAlignment.LEFT;
styleLocation[DocumentApp.Attribute.FONT_FAMILY] = 'Calibri';
styleLocation[DocumentApp.Attribute.FONT_SIZE] = 12;
styleLocation[DocumentApp.Attribute.BOLD] = true;
body.appendParagraph('').setAttributes(nospace);
var paragraph = body.appendParagraph('Item Number: ' + itemnum).setAttributes(styleLeft);
body.appendParagraph('*' + itemnum + '*').setAttributes(itemBarcode);
body.appendParagraph('\n').setAttributes(nospace).appendHorizontalRule();
//description
body.appendParagraph(desc).setAttributes(styleLeft);
body.appendParagraph('').setAttributes(nospace).appendHorizontalRule();
body.appendParagraph('Location: ' + bin + ' Qty: ' + qty).setAttributes(styleLocation);
body.appendParagraph('\n').setAttributes(nospace);
}
body.appendPageBreak();
}
}
var googledoc = doc.getUrl();
FinishedGoogleDoc(googledoc);
}
Below is my solution that does work.
/
/####################################### Create Zebra Label #########################
function createZebraLabel(monthlySheet,newSheetName){
var parentFolder=DriveApp.getFolderById('1mUPLw1PSi-guH19uJPCPWmogkpdckLcW');
var createdoc = DocumentApp.create('Zebra Label').getId();
doc = DocumentApp.openById(createdoc);
var id = createdoc;
var docFile = DriveApp.getFileById(doc.getId());
var target = parentFolder;
var file1 = DriveApp.getFileById(id);
var mkfile1 = file1.makeCopy(newSheetName, target).getId();
doc = DocumentApp.openById(mkfile1);
docFile.setTrashed(true);
//325
var body = doc.getBody().setPageWidth(325).setPageHeight(180).setMarginTop(.02).setMarginBottom(.02);
// var body = doc.getBody().setPageWidth(144).setPageHeight(200).setMarginTop(.01).setMarginBottom(.01);
var sourceSpreadSheet = SpreadsheetApp.openById(monthlySheet);
var ss = sourceSpreadSheet.getSheetByName('Sheet1');
var startRow = 1; // First row of data to process
var numRows = 250; // Number of rows to process
var startColumn = 1; // A=1 B=2
var numColumns = 5; // Number of columns to process
var dataRange = ss.getRange(startRow, startColumn, numRows, numColumns);
var data = dataRange.getValues();
for (var i = 1; i < data.length; ++i) {
var row = data[i];
var actualrow=i;
var itemnum = row[0]; // A column
var desc = row[1]; // B column
var serial = row[2]; // D column
var bin = row[3]; // D column
var qty =row[4];
if (itemnum != ''){
var styleITEMNUM = {};
styleITEMNUM[DocumentApp.Attribute.HORIZONTAL_ALIGNMENT] = DocumentApp.HorizontalAlignment.RIGHT;
styleITEMNUM[DocumentApp.Attribute.VERTICAL_ALIGNMENT] =DocumentApp.VerticalAlignment.CENTER;
styleITEMNUM[DocumentApp.Attribute.FONT_FAMILY] = 'Calibri';
styleITEMNUM[DocumentApp.Attribute.FONT_SIZE] = 14;
styleITEMNUM[DocumentApp.Attribute.BOLD] = true;
var styleDESC = {};
styleDESC[DocumentApp.Attribute.HORIZONTAL_ALIGNMENT] = DocumentApp.HorizontalAlignment.LEFT;
styleDESC[DocumentApp.Attribute.FONT_FAMILY] = 'Calibri';
styleDESC[DocumentApp.Attribute.FONT_SIZE] = 10;
styleDESC[DocumentApp.Attribute.BOLD] = true;
var styleSERIAL = {};
styleSERIAL[DocumentApp.Attribute.HORIZONTAL_ALIGNMENT] = DocumentApp.HorizontalAlignment.RIGHT;
styleSERIAL[DocumentApp.Attribute.VERTICAL_ALIGNMENT] =DocumentApp.VerticalAlignment.CENTER;
styleSERIAL[DocumentApp.Attribute.FONT_FAMILY] = 'Calibri';
styleSERIAL[DocumentApp.Attribute.FONT_SIZE] = 12;
styleSERIAL[DocumentApp.Attribute.BOLD] = true;
var nospace = {};
nospace[DocumentApp.Attribute.HORIZONTAL_ALIGNMENT] = DocumentApp.HorizontalAlignment.RIGHT;
nospace[DocumentApp.Attribute.FONT_FAMILY] = 'Calibri';
nospace[DocumentApp.Attribute.FONT_SIZE] =-1;
nospace[DocumentApp.Attribute.BOLD] = true;
var text = body.editAsText();
var paraStyle = {};
paraStyle[DocumentApp.Attribute.PADDING_BOTTOM] = 0;
paraStyle[DocumentApp.Attribute.PADDING_TOP] = 0;
paraStyle[DocumentApp.Attribute.MARGIN_TOP] = 0;
paraStyle[DocumentApp.Attribute.SPACING_BEFORE] = 0;
paraStyle[DocumentApp.Attribute.SPACING_AFTER] = 0;
paraStyle[DocumentApp.Attribute.LINE_SPACING] = 0;
var itemqrcode = UrlFetchApp.fetch('https://chart.googleapis.com/chart?chs=80x80&cht=qr&chl='+itemnum);
var serialqrcode = UrlFetchApp.fetch('https://chart.googleapis.com/chart?chs=80x80&cht=qr&chl='+serial);
//#################### ITEM WITH SERIAL NUMBER ###################
if (itemnum != '' && serial != ''){ // SERIAL NUMBE
body.appendParagraph('').setAttributes(nospace);
var table = body.appendTable().setBorderWidth(0).setAttributes(paraStyle);
var tr = table.appendTableRow().setAttributes(paraStyle);
var td = tr.appendTableCell('').setWidth(80).setPaddingTop(0).setPaddingBottom(0).appendImage(itemqrcode.getBlob()).setAttributes(paraStyle);
var td = tr.appendTableCell('Item Number\n' +itemnum).setPaddingTop(0).setPaddingBottom(0).setAttributes(styleITEMNUM);
body.appendHorizontalRule().setAttributes(nospace);
body.appendParagraph(desc).setAttributes(styleDESC);
body.appendParagraph('').setAttributes(nospace).appendHorizontalRule().setAttributes(nospace);
body.appendParagraph('').setAttributes(nospace);
var table = body.appendTable().setBorderWidth(0).setAttributes(paraStyle);
var tr = table.appendTableRow().setAttributes(paraStyle);
var td = tr.appendTableCell('').setWidth(80).setPaddingTop(0).setPaddingBottom(0).appendImage(serialqrcode.getBlob()).setHeight(80);
var td = tr.appendTableCell('Serial Number\n ' +serial).setPaddingTop(0).setPaddingBottom(0).setAttributes(styleSERIAL);
}
else if (itemnum != '' && serial == ''){ // NO SERIAL NUMBER
var styleLocation = {};
styleLocation[DocumentApp.Attribute.HORIZONTAL_ALIGNMENT] = DocumentApp.HorizontalAlignment.LEFT;
styleLocation[DocumentApp.Attribute.FONT_FAMILY] = 'Calibri';
styleLocation[DocumentApp.Attribute.FONT_SIZE] =12;
styleLocation[DocumentApp.Attribute.BOLD] = true;
body.appendParagraph('').setAttributes(nospace);
var table = body.appendTable().setBorderWidth(0).setAttributes(paraStyle);
var tr = table.appendTableRow().setAttributes(paraStyle);
var td = tr.appendTableCell('').setWidth(80).setPaddingTop(0).setPaddingBottom(0).appendImage(itemqrcode.getBlob()).setAttributes(paraStyle);
var td=tr.appendTableCell('Item Number\n' +itemnum).setAttributes(styleITEMNUM);
body.appendParagraph('\n').setAttributes(nospace).appendHorizontalRule();
body.appendParagraph(desc).setAttributes(styleDESC);
body.appendParagraph('\n').setAttributes(nospace).appendHorizontalRule();
body.appendParagraph('').setAttributes(nospace);
var table = body.appendTable().setBorderWidth(0).setAttributes(paraStyle);
var tr = table.appendTableRow().setAttributes(paraStyle);
var td=tr.appendTableCell('Location:'+bin).setWidth(200).setPaddingTop(0).setPaddingBottom(0).setAttributes(styleLocation);
var td=tr.appendTableCell('Qty: ' +qty).setAttributes(styleLocation);
}
body.appendPageBreak();
}
}
var googledoc = doc.getUrl();
FinishedGoogleDoc(googledoc);
}

How to speed up creating Waypoints?

I am attaching a waypoint to 2000 elements on the page, because I want to trigger a different event for each one. I have tried the noframework way as well as using jquery, and each one adds about 20 seconds to the loading of my page. Is there a technique I can use to attach waypoints to a lot of elements without it slowing down my page load? Thanks.
Jquery version (call once):
var waypoints = $('[data-waypointidentifier="iswaypoint"]').waypoint({
handler: function (direction) {
waypointHandler(direction, this);
}
});
No Framework version (called once for each of the 2000 elements):
var waypoint = new Waypoint({
document.getElementById('element-waypoint1'),
handler: function (direction) {
waypointHandler(direction, this);
}
});
This is not really an answer BUT does demonstrate the issue in near lowest form.
EDIT: This is perhaps a bit of a workaround hack now. Modified to simply attach in chunks.
NOTE: you will have to modify the last chunk - mine had even numbers at 2000 which I "chunked" in groups and then set a bound timeout to attach them. It still takes just as long but makes the situation slightly less visible while/until they all attach.
// approx times to attach (not definitive)
// - note each increment nearly doubles times, not linear
// super simple test, chrome, prob more on most computers
// var punchCount = 250; // 150ms
// var punchCount = 500; // 500ms
// var punchCount = 1000; // 2350ms
// var punchCount = 1500; // 4700ms
var punchCount = 2000; // 8750ms
function initme() {
var addme = $('<div class="facepunch" data-iam="-1">hi</div>');
var addgroup = $('<div>start</div>');
for (var i = punchCount; i > 0; i--) {
addme = $('<div class="facepunch">hi</div>').data('iam', i);
addme.appendTo(addgroup);
}
$('#punchme').append(addgroup);
}
var t0 = performance.now();
console.log('init 1');
initme();
var t1 = performance.now();
console.log("Call to initme took " + (t1 - t0) + " milliseconds.");
console.log('before 1');
function waypointHandler(direction, me) {
// NOOO... this will kill your perf console.log(direction, me);
var newt = "howdy " + $(me.element).data('iam');
$(me.element).text(newt + " " + direction);
}
t0 = performance.now();
console.log('before 2');
var waypoints = {};
function createWaysChunk(begin, end) {
console.log(begin, end, $('.facepunch').slice(begin, end).length);
$('.facepunch').slice(begin, end).waypoint({
// enabled: false,//no perf benefit
handler: function(direction) {
waypointHandler(direction, this); // "this.element" is the element
}
});
}
function createWays() {
t0 = performance.now();
var chunksize = 40;
var y = punchCount / chunksize; // 250
var x = 0;
for (var i = 0; i < chunksize; ++i) {
begin = x;
end = y * i;
setTimeout(createWaysChunk.bind(null, begin, end), 100);
x = end;
}
t1 = performance.now();
console.log("Call to createways = took " + (t1 - t0) + " milliseconds.");
}
// time out just moves the "freeze" to after the page loads
//var timeoutID0 = window.setTimeout(createWays, 2000);
window.onload = createWays;
// createWays();
t1 = performance.now();
console.log("Call to waypoints = took " + (t1 - t0) + " milliseconds.");
// Test with .enableAll
//t0 = performance.now();
//console.log('after 1');
// time out just moves the "freeze" to after the page loads
//var timeoutID = window.setTimeout(Waypoint.enableAll, 2000);
//initial enabled = false then enable is way slow, slower than true overall by 15000+ ms
//Waypoint.enableAll();
//console.log('after 2');
//t1 = performance.now();
//console.log("Call to enableAll took " + (t1 - t0) + " milliseconds.");
.facepunch {
height: 3em;
border: solid 1px lime;
margin: 0.25em;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/waypoints/4.0.1/jquery.waypoints.min.js" integrity="sha256-jDnOKIOq2KNsQZTcBTEnsp76FnfMEttF6AV2DF2fFNE=" crossorigin="anonymous"></script>
<div id="punchme"></div>

{d3.js} migration v3 to v4: code work on v3 (d3.layout.stack()) error v4 (d3.stack())

I'm trying to migrate D3 from v3 to v4:
Read:
https://github.com/d3/d3/blob/master/CHANGES.md#shapes-d3-shape
See:
d3.layout.stack ↦ d3.stack
I changed my working code:
Code working on v3: (d3.layout.stack())
Code producing error in v4: (d3.stack())
V4:
var dvstack = d3.stack();
var layers = dvstack(d3.range(nLocal).map(function(d,i) { ...
console.log(dvstack);
function stack(data) {
var kz = keys.apply(this, arguments),
i,
m = data.length,
n = kz.length,
sz = new Array(n),
oz;
for (i = 0; i < n; ++i) {
for (var ki = kz[i], si = sz[i] = new Array(m), j = 0, sij; j < m;++j) {
si[j] = sij = [0, +value(data[j], ki, j, data)];
sij.data = data[j];
}
si.key = ki;
}
for (i = 0, oz = order(sz); i < n; ++i) {
sz[oz[i]].index = i;
}
offset(sz, oz);
return sz; }
layers[c].dvnum = c;
Error:
SCRIPT5007: Unable to set property 'dvnum' of undefined or null reference
V3:
var stack = d3.layout.stack();
var layers = stack(d3.range(nLocal).map(function(d,i) { ...
console.log(stack);
function stack(data, index) {
if (!(n = data.length)) return data;
var series = data.map(function(d, i) {
return values.call(stack, d, i);
});
var points = series.map(function(d) {
return d.map(function(v, i) {
return [ x.call(stack, v, i), y.call(stack, v, i) ];
});
});
var orders = order.call(stack, points, index);
series = d3.permute(series, orders);
points = d3.permute(points, orders);
var offsets = offset.call(stack, points, index);
var m = series[0].length, n, i, j, o;
for (j = 0; j < m; ++j) {
out.call(stack, series[0][j], o = offsets[j], points[0][j][1]);
for (i = 1; i < n; ++i) {
out.call(stack, series[i][j], o += points[i - 1][j][1], points[i][j][1]);
}
}
return data; }
layers[c].dvnum = c;
Screenshot of the working code in v3:
Screenshot of the working code (D3 v3) console.log(layers)
Screenshot of the (D3 v4) console.log(layers)
Turns out, it's actually quite easy.
You simply want to transpose your matrix so that it's looks like somethings which is close to the array of objects the new stack function is waiting for:
var n = 4, // number of layers
m = 58, // number of samples per layer
stack = d3.stack().keys([0,1,2,3]);
stack.value(function (d, key) {
return d[key].y;
});
var layers = stack(d3.transpose(d3.range(n).map(function() { return bumpLayer(m, .1); }))),
Then it's a simple matter of modifying the names according to the new syntax.
I updated your fiddle so that it works for v4.
see:
https://jsfiddle.net/9y2g65qc/20/

Is there any solution to Firefox 37's poor Canvas rendering performance

I've deliberately provided the version number in the question as this is the kind of question that will go out of date at some point.
Here is a simple animation that will run perfectly smoothly in Chrome and Safari, but will be very jerky in Firefox:
function lerp(a,b,λ) {
return a + λ*(b-a);
}
function random(a,b) {
return lerp( a, b, Math.random() );
}
// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
$(document).ready( function()
{
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
var balls = new Array(12);
for( var i=0; i<balls.length; i++ )
{
while(true)
{
var density = Math.sqrt( random(1,100) );
var r = random(5, 30);
var x = random( r+1, canvas.width-1 -(r+1) ),
y = random( r+1, canvas.height-1 -(r+1) );
var overlap = false;
for( var j=0; j<i; j++ )
{
var _x = balls[j].x - x,
_y = balls[j].y - y,
d2 = _x*_x + _y*_y,
_r = balls[j].r + r;
if( d2 < _r*_r )
overlap = true;
}
if( overlap )
continue;
balls[i] = {
color : d3.hsl( lerp(0,240,density/10), random(.3,.7), random(.3,.7) ).toString(),
x : x,
y : y,
vx : random(0, 0.2),
vy : random(0, 0.2),
r : r,
advance: function(t) {
this.x += t * this.vx;
this.y += t * this.vy;
}
};
break;
}
};
window.requestAnimationFrame(vsync);
var t_last;
function vsync(t)
{
if( t_last )
render( t - t_last );
t_last = t;
window.requestAnimationFrame(vsync);
}
function render(t_frame)
{
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
ctx.fillStyle="gray";
ctx.fillRect(0,0, canvas.width, canvas.height);
function advance_all(t) {
balls.forEach( function(b) {
b.advance(t);
});
}
var t_remaining = t_frame;
while(true) {
var hit = get_next_collision( balls, canvas.width, canvas.height );
if( t_remaining < hit.t )
break;
advance_all( hit.t );
t_remaining -= hit.t;
collide_wall( balls[hit.i], hit.wall );
balls[hit.i].advance(.001);
};
advance_all( t_remaining );
// draw balls
balls.forEach( function(ball) {
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.r, 0, Math.PI*2, true);
ctx.closePath();
ctx.fillStyle = ball.color;
ctx.fill();
});
}
});
// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
function get_next_collision(balls,W,H)
{
var winner;
// ball-wall
balls.forEach( function(ball,i)
{
var t = [];
t['L'] = (ball.r - ball.x) / ball.vx; // s.x + t v.x = r
t['T'] = (ball.r - ball.y) / ball.vy;
t['R'] = (W-1-ball.r - ball.x) / ball.vx; // s.x + t v.x = (W-1)-r
t['B'] = (H-1-ball.r - ball.y) / ball.vy;
// get index of smallest positive t
var LR = t['L'] >= 0 ? 'L' : 'R',
TB = t['T'] >= 0 ? 'T' : 'B',
wall = t[LR] < t[TB] ? LR : TB;
if( ! winner || ( t[wall] <= winner.t ) )
winner = {
t : t[wall],
i : i,
wall: wall
};
});
return winner;
}
function collide_wall( A, wall )
{
if( wall == 'L' || wall == 'R' )
A.vx *= -1;
else
A.vy *= -1;
}
html, body {
width: 100%;
height: 100%;
margin: 0px;
}
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/mathjs/1.5.1/math.min.js"></script>
<script src="http://d3js.org/d3.v3.min.js"></script>
<canvas id="myCanvas">
<!-- Insert fallback content here -->
</canvas>
Why is Firefox performing significantly worse than its competitors?
If I take the number of balls to 500 Chrome is still smooth, Firefox is seriously choppy.
If I take the number of balls down to 1 Firefox is still burring it.
Another experiment I did was applying a fixed velocity increment each frame, so that the smoothness of the animation accurately reflects the evenness of the render callback. This showed Firefox to be all over the place. Chrome on the other hand was smooth.
If there is sufficient interest, I can provide a snippet for that also and maybe tidy up the first one so that it offers a slider to modify the ball count.
As far as I can see, (1) Firefox definitely isn't giving us a genuine VSYNC callback, I suspect it is just using a timer, (2) even correcting for that by manually calculating elapsed time for each frame, it burrs the animation, maybe suggesting that it sometimes the callback fires in time to catch the VSYNC and sometimes it misses the boat, (3) there is an additional compositing hit that is disproportionate compared with Chrome.
Is there anything to be done about this?
EDIT: I found this question from four years ago! Poor performance of html5 canvas under firefox -- please don't mark this question as the duplicate unless it is certain that the answer to that question is still the only relevant answer four years later. I've deliberately included the version number in the question, as the question pertains specifically to the current version.

Amchart with two lines

i have an amchart with two lines:
<script type="text/javascript">
var chart;
var graph;
var chartData2 = [
{"year":"2014-10-04 23:30:03", "v1":9.1, "v2":0},
{"year":"2014-10-04 23:45:02", "v1":8.8, "v2":86.2},
...
{"year":"2014-10-05 23:30:02", "v1":8.7, "v2":98.7},
{"year":"2014-10-05 23:45:02", "v1":8.5, "v2":98.7},
{"year":"2014-10-06 00:00:01", "v1":8.4, "v2":98.5},
{"year":"2014-10-06 00:15:02", "v1":8.5, "v2":98.3},
{"year":"2014-10-06 00:30:01", "v1":8.4, "v2":97.9}
];
AmCharts.ready(function () {
// SERIAL CHART
chart = new AmCharts.AmSerialChart();
chart.pathToImages = "../amcharts/images/";
chart.dataProvider = chartData2;
chart.marginLeft = 10;
chart.categoryField = "year";
chart.dataDateFormat = "JJ:NN:SS";
//chart.addListener("dataUpdated", zoomChart);
// AXES
// category
var categoryAxis = chart.categoryAxis;
categoryAxis.parseDates = true;
categoryAxis.minPeriod = "100";
categoryAxis.dashLength = 3;
categoryAxis.minorGridEnabled = true;
categoryAxis.minorGridAlpha = 0.1;
// value
var valueAxis = new AmCharts.ValueAxis();
valueAxis.id = "ax1";
valueAxis.axisAlpha = 0;
valueAxis.inside = true;
valueAxis.dashLength = 3;
valueAxis.position = "right";
chart.addValueAxis(valueAxis);
var valueAxis2 = new AmCharts.ValueAxis();
valueAxis2.id = "ax2";
valueAxis2.axisAlpha = 0;
valueAxis2.inside = true;
valueAxis2.dashLength = 3;
valueAxis2.baseValue = -20;
valueAxis2.position = "left";
chart.addValueAxis(valueAxis2);
// GRAPH
graph = new AmCharts.AmGraph();
graph.valueaxis = "ax1";
graph.type = "smoothedLine";
graph.lineColor = "#d1655d";
graph.negativeLineColor = "#637bb6";
graph.bullet = "square";
graph.bulletSize = 8;
graph.bulletBorderColor = "#FFFFFF";
graph.bulletBorderAlpha = 1;
graph.bulletBorderThickness = 2;
graph.lineThickness = 2;
graph.valueField = "v1";
graph.title = "title v1";
graph.balloonText = "[[category]]<br><b><span style='font-size:14px;'>[[value]]</span></b>";
chart.addGraph(graph);
graph2 = new AmCharts.AmGraph();
graph2.valueAxis = "ax2";
graph2.type = "smoothedLine";
graph2.lineColor = "#d1655d";
graph2.negativeLineColor = "#637bb6";
graph2.bullet = "round";
graph2.bulletSize = 8;
graph2.bulletBorderColor = "#FFFFFF";
graph2.bulletBorderAlpha = 1;
graph2.bulletBorderThickness = 2;
graph2.lineThickness = 2;
graph2.valueField = "v2";
graph2.title = "title v2";
graph2.balloonText = "[[category]]<br><b><span style='font-size:14px;'>[[value]]</span></b>";
chart.addGraph(graph2);
// CURSOR
var chartCursor = new AmCharts.ChartCursor();
chartCursor.cursorAlpha = 0;
chartCursor.cursorPosition = "mouse";
chartCursor.categoryBalloonDateFormat = "JJ:NN:SS";
chart.addChartCursor(chartCursor);
// SCROLLBAR
var chartScrollbar = new AmCharts.ChartScrollbar();
chart.addChartScrollbar(chartScrollbar);
chart.creditsPosition = "bottom-right";
// WRITE
chart.write("chartdiv2");
});
I can see the x-and y-axis, but no lines with values. Can anyone please help me?
Two problems:
dataDateFormat is incorrect, should be: chart.dataDateFormat = "YYYY-MM-DD JJ:NN:SS";
minPeriod is incorrect, should be: categoryAxis.minPeriod = "ss";

Resources