Wednesday, 16 August 2017

Handicap curves from Archers Mate

I've been doing a little work on investigating archery handicaps for Witchford Archers, and it's been a fascinating journey into statistics. The Club Secretary, God-bless-him, has passed me all sorts of facts and figures and I've been looking at the code on the Archer's Mate website. We'd also attended a meeting of the CAA and discovered that working out your Handicap was a useful thing to do (especially if you enter competitions - for me, less so).

On the site, there is a simple form which allows a user to enter the round they are shooting, the type of bow they are using, the type of target they are shooting at, their gender and their score.

I was only really interested in the Portsmouth handicaps, so I was a wee bit of a pain and hit the server with lots and lots of requests (I did it during odd hours, so hopefully it didn't impact things too much).

Anyway, I discovered that the handicaps didn't differ between men and women but that the curve varied between the different bows and targets. I guess the main take away was that the user shooting a compound at a full target started off the same as someone shooting a recurve bow at the same target, as their score increased though, they began to follow the handicaps of those shooting a compound bow at a triple target.

Hopefully, the graph above illustrates what I mean. I'm not going to hit the server anymore, but I think I'll do the same to the handicaps published by Archery GB and see if there's any correlation.

There did seem to be a difference in the classifications between men and women though... not sure what that's all about.

Tuesday, 8 August 2017

Complex object creation in JavaScript

In JavaScript you can get a little spoilt with the initialisation of variables. For example I might have something like this:

const start = {
    "x": 20,
    "y": 20
}, end = {
    "x": 40,
    "y": 60
}, width = end.x - start.x, height = end.y - start.y;

Where width and height rely on the previously set variables.

Today, at work, I was asked if there was a way of doing something similar regarding initializing an object. This pseudo code illustrates what I mean:

const psuedoObj = {
    "start": [20, 20],
    "end": [40, 60],
    "width": this.end[0] - this.start[0],
    "height": this.end[1] - this.start[1],
};

console.log(psuedoObj);

The thing is, that doesn't work.

So we got to looking and found this answer on Stack Overflow. To get around the issue we could then do this:

const psuedoObj = {
    "start": [20, 20],
    "end": [40, 60],
    "init": function(){
        this.x = this.start[0];
        this.y = this.start[1];
        this.width = this.end[0] - this.start[0],
        this.height = this.end[1] - this.start[1],
        delete this.init
        return this;
    }
}.init();

console.log(psuedoObj);

Nice ehh? And we even tidy up after ourselves!

Wednesday, 2 August 2017

Some thoughts on pagination

I've written an adaptation of DataTables Pagination for use with MDB before but I've never really had to think about pagination much until, that is, a recent project at work came up. I was laid-up at home with a cold, so I used my time to think about it in some depth. For my use case, I wanted a set of buttons starting with a First and Previous button and ending with Next and Last buttons. I spent some time looking around, and it seemed as though the best arrangement of buttons for individual pages had an odd number.

This, then, was my desired output:

1 2 3 4 5

With the currently active page disabled for it to be distinguishable (and immune to clicking - I'm not sure why that's as important as it is to me, but it always pleases me if I'm unable to link back to the page I'm on from the page I'm on). I guessed that I'd also need to disable the First and Previous buttons when the first page was active and the Next and Last when the last page was active.

This then meant that the as far as possible the active number should be in the centre of the range of numbers. In the example above then when three was active I'd be happy. However, should four be the active page then the range would start at two and end at six. When one is the active page then it is not in the middle of the range; so we don't need to show numbers which are impossible to display (we really can't get to the page with the number minus one).

If we had a range of seven pages of data to display, this is what we should see while paging through the data:

Active: one

1 2 3 4 5

Active: two

1 2 3 4 5

Active: three

1 2 3 4 5

Active: four

2 3 4 5 6

Active: five

3 4 5 6 7

Active: six

3 4 5 6 7

Active: seven

3 4 5 6 7

This, then presented me with some considerable confusion, not least because I had trouble thinking clearly - but after far too many hours of pondering I can up with this spreadsheet and started reasoning about the logic behind calculating what I needed to happen.

After playing with the sheet, I clocked the algorithm required needed to check a number of different figures in order to decide where to start and end the range:

=IF(LTE(A3, CEILING(DIVIDE(D2, 2))),
    1,
    IF(GT(ADD(A3, FLOOR(DIVIDE(D2, 2))), A14),
        ADD(MINUS(A14, D2), 1),
        MINUS(A3, FLOOR(DIVIDE(D2,2)))))

Bonkers ehh?

I wrote the first line and clocked what was going on and decided to cheat after that, so I wrote a JSFiddle that generated the correct Google Sheet code for me, which I could then use to create the finished product. The final bit of code is rather simple, and this is it:

const generateCell = (text, active) => {
    const cell = document.createElement("td");
    cell.textContent = text;
    active && cell.classList.add("active");
    return cell;
}
const generateRow = (f, a, b) => {
    const row = document.createElement("tr");
    row.append(generateCell("", a === 1));
    row.append(generateCell("", a === 1));
    for(let i = f; i < (f + b); i++){
        row.append(generateCell(i, i === a));
    }
    row.append(generateCell("", a === total));
    row.append(generateCell("", a === total));
    return row;
};
const total = 12;
const table = document.getElementById("body");
const buttons = 5;
for(let active = 1; active <= total; active++){
    let first = (active <= Math.ceil(buttons / 2))
        ? 1
        : (active + Math.floor(buttons / 2) > total)
            ? (total - buttons) + 1
            : active - Math.floor(buttons / 2);
    table.append(generateRow(first, active, buttons));
}

I'm sort of ashamed that it took quite as long as it did, but I’m rather pleased with the result.

Friday, 14 July 2017

d3.json and Data URLs

I've been playing with d3 quite a bit lately and I know there's a load of stuff I need to learn more about but I thought I'd share this little trick associated with d3.json.

I've had a couple of occasions where I needed to pre-process the json sent to a d3 script. Each time I've been stumped by ds.json requiring an URL, I'm not going to process the data and then send it back to the server in order to get it again, so I started thinking about Data URLs and base64 encoding (as is my wont) and this sorts it out a treat:

let url = "data:application/json;charset=utf-8;base64,";
url += btoa(JSON.stringify(processedObject));
d3.html(url, function(error, graph) {
    if (error) throw error;
    // do cool things with the data...
});

Anyway, I hope it helps someone (and yes, I do know you don’t need to do it, it’s just nice not to change the original scripts too much!).

Tuesday, 13 June 2017

JavaScript - shorthand if statement (sans else)

I'm all in favour of shorthand statements and I often visit this Sitepoint page. I'm also keen on ES6 destructuring as I seem to spend a lot of time thinking about the shape and patterns of data anyway.

One shorthand I've always like is the if using &&:

Given these constants:

const thing  = "fred";

const helloWorld = () => {
    console.log("hello");
    console.log("world");
};

We can do this:

thing === "fred" ? helloWorld() : null;  // hello\nworld

thing === "Fred" ? helloWorld() : null;  // nowt

We can also do this:

thing === "fred" && helloWorld(); // hello\nworld

thing === "Fred" && helloWorld(); // nowt

But, even better, we can do this:

thing === "fred" && (() => {
    console.log("hello");
    console.log("world");
})();  // hello\nworld

thing === "Fred" && (() => {
    console.log("hello");
    console.log("world");
})();  // nowt

How cool is that? JSFiddle here.

Wednesday, 26 April 2017

For loop -vs- Map

I got asked last week about getting the index of an object in an array which had the earliest date, so that this array:

let objArr = [
    {
        "date": "2011-08-12T20:17:46.384Z"
    }, {
        "date": "2012-08-12T20:17:46.384Z"
    }, {
        "date": "2013-08-12T20:17:46.384Z"
    }, {
        "date": "2014-08-12T20:17:46.384Z"
    }, {
        "date": "2010-08-12T20:17:46.384Z"
    }, {
        "date": "2009-08-12T20:17:46.384Z"
    }
];

Produces 5 as that element has the earliest day.

I pondered using map but then decided to go all old school and used a for loop like this:

let boringParse = i => parseInt(moment.utc(i.date).format("x"), 10);
let forLoopInt = objA => {
    let min, i, objALength = objA.length;
    for (let y = 0; y < objALength; y++) {
        let now = boringParse(objA[y])
        if (!min || now < min) {
            min = now, i = y;
        }
    }
    return i;
};

Once I got back to my desk I decided to talk it over with James and came up with this:

let boringParse = i => parseInt(moment.utc(i.date).format("x"), 10);
let mapOne = objA => {
    let datesArr = objA.map(boringParse);
    return datesArr.indexOf(Math.min(...datesArr));
};

Turns out that the for loop is the faster, but only just!

Saturday, 22 April 2017

WebSQL bulk insert

I've been playing with WebSQL of late (after all, why wouldn't you play with a deprecated standard?). And I'm loving it for inserting data via Papa Parse, the thing is, it was proving to be really, really slow... right up until I clocked that I was doing it wrong!

The thing is, I was iterating over my data and firing a transaction with an SQL payload for each row of the CSV file I was parsing where I should've been firing a transaction and then iterating over my data and firing off multiple SQL payloads within the single transaction.

This was my first effort:

for (let record of results.data) {
    mydb.transaction(
        t => {
            t.executeSql(`
                INSERT INTO
                  accounts (
                    Date,
                    Company,
                    Account,
                    Amount
                  )
                VALUES (
                  ?,
                  ?,
                  ?,
                  ?
                )`,
                [
                    record.Date,
                    record.Company,
                    record.Account,
                    record.Amount
                ],
                t => {
                    let query = "SELECT COUNT(*) AS total FROM accounts";
                    t.executeSql(
                        query,
                        null,
                        (t, result) => {
                            if (~~result.rows[0].total === tableMetaData.total) {
                                createTable();
                            }
                        },
                        null
                    );
                },
                null
            );
        }
    );
}

Pretty, eh? But it took silly amounts of time to execute and I was planning on inserting 250,000 records. It simply wasn't practical to do it that way as it took something like 20 seconds to insert 1000 rows:

RowsTime
100.218
1001.711
100021.111
500090.703

Changing the code to this:

mydb.transaction(t => {
    for (let record of results.data) {
            t.executeSql(`
                INSERT INTO
                  accounts (
                    Date,
                    Company,
                    Account,
                    Amount
                  )
                VALUES (
                  ?,
                  ?,
                  ?,
                  ?
                )`,
                [
                    record.Date,
                    record.Company,
                    record.Account,
                    record.Amount
                ],
                null,
                null
            );
        }
    },
    error => {
        console.log("error", error);
    },
    () => {
        createTable();
    }
);
Produced these results:
RowsTime
100.081
1000.058
10000.083
50000.223

Not only quicker but I've also halved the number of queries as I don't need to check that all the transactions have completed after each insert, sweet eh? After figuring this out I came across a number of posts where the same thing had been discovered by others. Including RICAL and Gavin Pickin. Just a shame it's deprecated.