Monday, 19 September 2022

Web Tutorial: D3 Heatmap (Part 1/3)

Welcome to another episode of D3 goodness!

Since 2018, I've been toying around with D3 and getting it to produce various charts - bar, line and pie - using Liverpool FC's football statistics. Today, I will focus on one more - the Heatmap. Heatmaps are great at visually representing two-dimensional data. With the other kinds of charts earlier mentioned, we were only able to view one dimension at a time whether it was by season or by player. With the Heatmap, we will visualize the data by season and player.

Recycling code

The layout will be largely the same as what we used for the Bar Chart. So let's take the code wholesale and make some changes. We will cut out whole swathes of code, but preserve what can be reused later.

For the HTML, we begin by changing the title.
<title>D3 Heatmap</title>


The styling will be changed because we want to temporarily change some values. Other changes will be less temporary. For instance, for the sake of good naming conventions, let's change every instance of "bar" to "hm". And since we will have two legends instead of a scale and a legend, let's change every instance of "Legend" to "LegendX" and every instance of "Scale" to "LegendY". Also, make sure that the styling for hmChartSvg's rect elements is empty for now. The background colors of the various placeholders are set to full opacity for the time being.

And before you do any of that, remove the classes barChartDataMean, barChartLine and barChartFadedLine. We won't be using any of those.
<style>
    body
    {
        font-size: 12px;
        font-family: arial;
    }

    .hmDashboard
    {
        height: 2em;
        width: 100%;
        text-align: center;
    }

    .hmChart
    {
        outline: 1px solid #000000;
        background-color: rgba(200, 0, 0, 1);
    }

    .hmChartContainer
    {
        margin: 0 auto 0 auto;
    }

    .hmLegendYSvg
    {
        width: 5em;
        height: 20em;
        float: left;
        background-color: rgba(0, 255, 0, 1);
    }

    .hmLegendYSvg text
    {
        fill: rgba(255, 255, 0, 1);
        text-anchor: end;
        font-size: 0.5em;
    }

    .hmChartSvg
    {
        width: 20em;
        height: 20em;
        float: left;
        background-color: rgba(0, 0, 255, 1);
    }

    .hmChartSvg text
    {
        fill: rgba(255, 255, 0, 1);
        text-anchor: middle;
        font-weight: bold;
    }  

    .hmChartSvg rect
    {

    }

    .hmFillerSvg
    {
        width: 5em;
        height: 3em;
        float: left;
        background-color: rgba(0, 0, 0, 1);
    }

    .hmLegendXSvg
    {
        width: 20em;
        height: 3em;
        float: left;
        background-color: rgba(255, 0, 0, 1);
    }

    .hmLegendXSvg text
    {
        fill: rgba(255, 255, 0, 1);
        text-anchor: middle;
        font-weight: bold;
    }  
    /*
    .barChartDataMean
    {
        stroke: rgba(0, 0, 0, 1);
        stroke-width: 1px;
        stroke-dasharray: 1, 5;
    }  

    .barChartLine
    {
        stroke: rgba(255, 255, 0, 1);
        stroke-width: 1px;
    }   

    .barChartFadedLine
    {
        stroke: rgba(255, 255, 0, 0.2);
        stroke-width: 1px;
    }  
    */
</style>


In the HTML, aside from name changes to the divs, you also want to remove the drop-down list for years.
<div class="hmChartContainer">
    <div class="hmDashboard">
        <!--                 
        <select id="ddlYear">

        </select>
        -->

        <select id="ddlStat">

        </select>
    </div>

    <div class="hmChart">
        <svg class="hmLegendYSvg">

        </svg>

        <svg class="hmChartSvg">

        </svg>

        <br style="clear:both"/>

        <svg class="hmFillerSvg">

        </svg>

        <svg class="hmLegendXSvg">

        </svg>
    </div>
</div>


In the script tag, leave graphData alone. For config, make changes to the following properties. scaleWidth has been renamed to legendYWidth and legendHeight has been renamed to legendXHeight. mean has been removed as a property.
"scale": 12,
"dataWidth": 10,
"dataSpacing": 2,
"legendYWidth": 10,
"legendXHeight": 4,
"max": 0,
//"mean": 0,


For the setData() method, I could go through step by step what changes need to be made, but it would be easier to just clear everything out and start afresh.
"setData": function ()
{

}


Outside of the config object, remove this from the rest of the code. We will still need to determine the max property of the config object, just not here. And we no longer need the drop-down list for years.
/*
config.max = d3.max(graphData.cols, function(d)
{
    var maxStat = d3.max(d.stats, function(x)
    {
        return (x.goals > x.appearances ? x.goals : x.appearances);
    }
    );

    return maxStat;
}
);

var ddlYear = d3.select("#ddlYear");

ddlYear.selectAll("option")
.data(graphData.rows)
.enter()
.append("option")
.property("selected", function(d, i)
{
    return i == 0;
})
.attr("value", function(d)
{
    return d;
})
.text(function(d)
{
    return d;
});
*/

var ddlStat = d3.select("#ddlStat");

ddlStat.selectAll("option")
.data(graphData.stats)
.enter()
.append("option")
.property("selected", function(d, i)
{
    return i == 0;
})
.attr("value", function(d)
{
    return d;
})
.text(function(d)
{
    return d;
});

config.setData();

//d3.select("#ddlYear").on("change", function() { config.setData(); });
d3.select("#ddlStat").on("change", function() { config.setData(); });


Let's make a change to getChartHeight(). Add datalength as a parameter, and use it to determine the final result.
"getChartHeight": function(datalength)
{
    //return (this.max * this.scale * 1.5);
    return (datalength * this.scale);
},


And a change to getChartWidth(). This will be highly simplified.
"getChartWidth": function(datalength)
{
    //return (datalength * (this.dataWidth + this.dataSpacing)) + (datalength * 0.5);
    return (datalength * this.dataWidth);
},


Now we will get to work on setData(). Begin by declaring stat and setting it to the selected value of the ddlStat drop-down list.
"setData": function ()
{
    var stat = d3.select("#ddlStat").node().value;
}


Next, we declare height and width. height is calculated using the getChartHeight() method, and we will pass in, as an argument, the number of elements in the rows array of the graphData object. Likewise, width is calculated using the getChartWidth() method, and we will pass in, as an argument, the number of elements in the cols array of the graphData object. Remember that this is a chart with two axes - one for seasons and one for players.
"setData": function ()
{
    var stat = d3.select("#ddlStat").node().value;
    
    var height = config.getChartHeight(graphData.rows.length);
    var width = config.getChartWidth(graphData.cols.length);
}


While we're at it, let's declare some variables, assigning to them the elements found in the HTML. These will ultimately make up the visual structure of our chart.
var stat = d3.select("#ddlStat").node().value;

var container = d3.select(".hmChartContainer");
var wrapper = d3.select(".hmChart");
var legendY = d3.select(".hmLegendYSvg");
var chart = d3.select(".hmChartSvg");
var legendX = d3.select(".hmLegendXSvg");
var filler = d3.select(".hmFillerSvg");


var height = config.getChartHeight(graphData.rows.length);
var width = config.getChartWidth(graphData.cols.length);


Now what we do is set the width of container. It will use width, and the width of the Y-axis, the legendYWidth property. We don't set the height because soon we will be setting the height for its child, wrapper, which will cause container to expand vertically.
var height = config.getChartHeight(graphData.rows.length);
var width = config.getChartWidth(graphData.cols.length);

container
.style("width", function(d)
{
    return (width + config.legendYWidth) + "em";
});


For wrapper, we set height. This will use height and the height of the X-axis, the legendXHeight property.
container
.style("width", function(d)
{
    return (width + config.legendYWidth) + "em";
});

wrapper
.style("height", function(d)
{
    return (height + config.legendXHeight) + "em";
});


Now take a look! The deep red portion is wrapper. The blue, green, scarlet and black portions are the rest of the placeholders that have not yet had their heights and widths specified. Also notice the drop-down list at the top.




Let us now move on to the rest of the placeholders. We want the Y-axis to use the legendYWidth property for width, and use height for its height.
wrapper
.style("height", function(d)
{
    return (height + config.legendXHeight) + "em";
});

legendY
.style("width", function(d)
{
    return config.legendYWidth + "em";
})
.style("height", function(d)
{
    return height + "em";
})
.html("");


You see the green portion has expanded to what we have specified! That is our Y-axis.




chart is straightforward. We just use width and height.
legendY
.style("width", function(d)
{
    return config.legendYWidth + "em";
})
.style("height", function(d)
{
    return height + "em";
})
.html("");

chart
.style("height", function(d)
{
    return height + "em";
})
.style("width", function(d)
{
    return width + "em";
})
.html("");


The blue portion is chart, and it is taking shape.




We now specify the size of legendX.
chart
.style("height", function(d)
{
    return height + "em";
})
.style("width", function(d)
{
    return width + "em";
})
.html("");

legendX
.style("height", function(d)
{
    return config.legendXHeight + "em";
})
.style("width", function(d)
{
    return width + "em";
})
.html("");


The scarlet part, the X-axis, has now expanded! But it will look off until we finish the job with filler.




And here we have it...
legendX
.style("height", function(d)
{
    return config.legendXHeight + "em";
})
.style("width", function(d)
{
    return width + "em";
})
.html("");

filler
.style("width", function(d)
{
    return config.legendYWidth + "em";
})
.style("height", function(d)
{
    return config.legendXHeight + "em";
});


The black portion, filler, has had its size specified, and now we're in business. The deep red portion is no longer visible because it has been covered completely. Good job!




Next

We will work on the X and Y axes.

Tuesday, 13 September 2022

Five Unpleasant Things About Working With Front-end Technology

In tech, there are several technologies at each layer of the stack. The nuances are usually not covered by tech hiring, and this is usually divided into front-end and back-end. Today, my intention is to examine the two, especially front-end technology. And here are five reasons why you might not want to make front-end technology your career.

1. Rapid evolution

There are several aspects to front-end technology. There's your standard HTML, CSS and JavaScript... and that alone can be a nightmare to keep up with. HTML, if you're up to speed with the most commonly used features of HTML5, not so much. For CSS, the preprocessor packages and CSS3 updates can be slightly more of a pain in the ass.

Multiplying like guppies

But JavaScript? If you elect to go down that rabbit hole, JavaScript is constantly evolving. The core language alone has been through update after update, The jQuery library as well. But all the front-end frameworks - ReactJS, AngularJS, VueJS, just to name a few - are also constantly going through evolution. And new frameworks seem to be coming out of the goddamn woodwork all the time!

And that's only the tech part. The design part is certainly nothing to sniff at. Learning fonts and color schemes is only the tip of the proverbial iceberg. Design patterns and anti-patterns evolve all the time. A typical website in the 90s will look markedly different ten years later, and another ten years after that.

This sounds fun if you're a hobbyist. When you do this shit for a living, not so much.

2. Uncontrollable environment

Front-end code runs on web browsers. Each of which comes with user-controllable functionality a front-end developer may not have catered for.

Yes, you read that right. User-controllable. Not developer-controllable.

Your typical web browser may come with a Dark Mode. There may almost definitely will be controls to increase or decrease font size. This is going to mess with all your layout specifications.

What about Dark Mode?!

Add in the fact that your users may screw with your carefully crafted interface by refreshing the browser insistently, pressing the Back button and using browser functions in all sorts of unruly ways. It's certainly not like a back-end interface where there are limits to input.

3. Testing nightmare

Mobile technology in the past decade has seen a proliferation of different screen sizes - tablets and phones - in addition to the standard screen sizes. In addition, there may be differences in how certain browsers render HTML, CSS and JavaScript. These differences have largely been eliminated with the demise of Internet Explorer, but some remain.

A plethora of different devices.

All of this adds up to an almost untenable amount of user interface testing for the optimal amount of exposure to the market. It is not an enviable position.

Front-end frameworks and libraries such as Twitter Bootstrap, jQuery and such, mitigate much of the insanity. But these issues have remained through the decade and will likely continue to grow the more mobile technology evolves.

4. Vague KPIs

In back-end technology, things are binary. Input is defined, and output is defined. The output is either correct, or it is not. It either is produced quickly enough, or it is not. Things either conform to security standards, or they do not.

Back-end input and
output is simpler.

Front-end technology has all that, true. It also encompasses a whole host of KPIs that are, for the most part, subjective in nature. User experience. Aesthetic beauty. Ease of use. These are guaranteed to vary from user to user and therefore, there is no way to obtain a perfect score.

Imagine being in a situation where you have to live with having less than total success. That is front-end technology.

5. Everyone has an opinion

This is somewhat related to the last point. In back-end technology, laypeople generally have better sense than to ever touch this, because they have little to no understanding of the stack and they have no tech qualifications.

Everyone and their dog.

In front-end technology, however, which is mostly visual, suddenly everybody and their dog has an opinion. Suddenly, everyone feels the need to chime in on how stuff should look and feel like. Nobody is shy about commenting because, well, you don't need tech qualifications to talk about something which is largely visual. It's subjective, so there are no wrong answers.

This makes your work a lot more difficult, when you have to take so much extra feedback into account. And when a sizeable portion of that feedback comes from laypeople, this makes things even more difficult because laypeople don't quite speak the same language techies do, and they may not have a good grasp of what technically makes sense and what doesn't.

In conclusion

Front-end technology is far, far from a bed of roses.

Still, if it still looks intriguing, let nothing deter you from your path. The important thing is not to go in blind, and be aware of all the pitfalls that await you.

Just being up front!
T___T

Thursday, 8 September 2022

ONE Pass to rule them all

Singapore introduced the Overseas Networks & Expertise (ONE) Pass was two weeks ago. Under this scheme, any foreigner earning a fixed monthly salary of at least 30,000 SGD will be eligible. Companies wishing to hire such an individual will no longer need to comply with Fair Consideration Framework (FCF), where job advertising for suitable local candidates is concerned. An individual holding this ONE Pass will not be subjected to the requirements of the Complementarity Assessment Framework (COMPASS)  regulations.

As an economic digit, it is probably prudent to try and unpack this.

Perceived benefits

The ultra-large foreign firms looking to begin (or continue) investing in Singapore with a local presence, will now have an easier time of it. For roles requiring that salary range, which can be a challenge to hire for even without having to conform to local existing laws regarding fair hiring practices, it is now a lot easier to expedite the process. This in turn makes it easier for them to operate in Singapore. And that (hopefully) translates to more jobs for qualified Singaporeans, though probably not at that exalted salary range.

Welome to Singapore!

This also serves to attract top talent to Singapore, and retention of that talent. The benefits of that are obvious.

Also, every foreigner that gets hired under this scheme are going to have to stay in Singapore. Unlike locals, they don't have a permanent home on this island and will need a place to stay. That means money is going to be spent in Singapore. There's no downside to that.

Perceived drawbacks

With any kind of new legislation, there are bound to be bad actors trying to be slick and abuse the system. The Singapore Government is going to have to exercise extra vigilance in ensuring that this remains at a minimum. How they are easily going to do that, remains beyond my ken.

A common refrain on Social Media is that this takes away potential jobs from local talents. The belief is that the presence of such highly-paid foreign talent will deprive local talents of these opportunities. If one ignores the fact that such local talent would in all likelihood already be gainfully employed or themselves being a foreign talent in other countries, this might hold water.

This argument
does not hold water.

At the risk of sounding elitist...

I find the protests on behalf of local talent, hollow at best. We are speaking of local talent who can command at least 30,000 SGD a month. I'm not a betting man, but I would wager my last Singapore dollar that the average shmuck ranting on Social Media about the ONE Pass, earns maybe a tenth of that.

People who are capable of earning more than 30,000 SGD a month, are surely equally capable of advocating for themselves. These people certainly do not need others to speak up for them, especially those who have not and will in all probability will never see that kind of money in six months, let alone one.

This is also why I am against people who have never worked tech jobs before, presuming to speak for my industry. Stay in your lane.

One last thing...

When I first saw this news, my first thought was: Pfft. 30,000 SGD isn't even half what I make. Annually, that is.

Jokes aside, there were predictably plenty of complaints from naysayers. And - surprise, surprise, anti-foreigner rhetoric. This isn't a new phenomenon by any means. It is little more than envy from people who think that their comparative lack of earning power would be addressed not by bettering themselves, but by eliminating all competition.

Newsflash: if you're busy being bitter on Social Media, these foreign talents are not your competition. Professionally, you're probably a long way from even being in the same weight class. Deal.

The ONE and Only,
T___T

Saturday, 3 September 2022

The Good Old Variable Swap, Redux

Some time back, I wrote a short piece, a how-to on the classic variable swap. Today is a good time to revisit that. As with the last time, code samples are in QBasic.

I was demonstrating this in a class, and one of the students had this bright idea: would this method work with multiplication and division, instead of addition and subtraction?

His idea was as follows.
DIM a = 3
DIM b = 2

a = a * b
b = a / b
a = a / b


And my answer was an unequivocal no. This is why.

In his example, a was 3 and b was 2. That would work. But consider this: what if either a or b was 0? Let's try both times.

Example 1: a = 0, b = 2
DIM a = 0
DIM b = 2

'a = 0 * 2 = 0
a = a * b

'b = 0 / 2 = 0
b = a / b

'a = 0 / 0
a = a / b


Example 2: a = 3, b = 0
DIM a = 3
DIM b = 0

'a = 3 * 0 = 0
a = a * b

'b = 0 / 0
b = a / b

a = a / b


In either case, at some point we are going to end up in a scenario where a division by zero would be attempted. And as we all know, once a division by zero is attempted, an exception occurs.

The dreaded zero.

We could get around this limitation by doing a Try-catch, and assuming the value of 0 once the exception is thrown. But that would add a level of complexity to what is supposed to be a very simple formula.

The takeaway

It was a nice try, and I like to encourage that. An experimental and curious mindset is always valuable to a programmer. What's important is that we always bear certain basics in mind.

Happy pr0gramming,
T___T

Friday, 26 August 2022

Web Tutorial: The Collision Detector

Video games, particularly of the side-scrolling shooter variety, all have collision detection as a mechanic. I've often run into needing to solve those problems. What I needed was a utility of some kind, to tell me when one object is sharing the same space as another object in a two-dimensional area.

And I have made just such a utility, using VueJS. The use of VueJS was to shorten development time; what really matters is that the formula I use to determine collision, is correct and can be visually proven to be correct.

In this context, one object can be said to have "collided" with another, if horizontally and vertically their coordinates intersect. I'll expound on that later, but for now, here's some code. The HTML includes one div with the id cdApp, a remote link to VueJS and the instantiation of the Vue object.

<html>
    <head>
        <title>Collision Detection Utility</title>

        <style>

        </style>
    </head>

    <body>
        <div id="cdApp">

        </div>

        <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.18/vue.min.js"></script>

        <script>
            var app = new Vue
            (

            );
        </script>
    </body>
</html>


I'm going to go through the next few bits really fast, because they aren't all that important to the final product. Within the cdApp div, we have a few other divs. The classes are dashboard, notice and space. dashboard is to house all the controls we will need, notice is for the displaying of messages, and space is the visual representation of the two objects and their coordinates.
<div id="cdApp">
    <div class="dashboard">

    </div>

    <div class="notice">

    </div>

    <div class="space">

    </div>

</div>


In the styling, note that we have given all divs a red outline to provide visibility. cdApp is going to take up full width and height of the screen. The position property is set to absolute, and the top and left properties are 0.
<style>
    div { outline: 1px solid red;}

    #cdApp
    {
        width: 100%;
        height: 100%;
        position: absolute;
        top: 0;
        left: 0;
    }

</style>


dashboard, notice and space all take up full width, but have varying heights. For notice, I have set text to be aligned center and given a top and bottom border. For space, I have set a translucent orange background.
<style>
    div { outline: 1px solid red;}

    #cdApp
    {
        width: 100%;
        height: 100%;
        position: absolute;
        top: 0;
        left: 0;
    }

    .dashboard
    {
        position: relative;
        width: 100%;
        height: 25%;
    }

    .notice
    {
        position: relative;
        width: 100%;
        height: 3%;
        text-align: center;
        border-top: 1px solid black;
        border-bottom: 1px solid black;
    }

    .space
    {
        position: relative;
        width: 100%;
        height: 72%;
        background-color: rgba(255, 200, 0, 0.1);
    }

</style>


This is what I intend.




Now let us build the dashboard. There are basically two sets of controls - one for each object.
<div class="dashboard">
    <fieldset>
        <legend>Obj A</legend>
    </fieldset>

    <fieldset>
        <legend>Obj B</legend>
    </fieldset>

</div>


For neatness, we will style fieldsets by fixing widths and heights, floating left and making sure the display property is set to block.
.space
{
    position: relative;
    width: 100%;
    height: 72%;
    background-color: rgba(255, 200, 0, 0.1);
}

fieldset
{
    width: 20em;
    height: 10em;
    float: left;
    display: block;
}


Each fieldset will have four range inputs - for left margin, top margin, width and height. Note the values and attributes - these are going to reflect the initial values for the two objects.
<div class="dashboard">
    <fieldset>
        <legend>Obj A</legend>
        <br /><label>Obj A Left</label><input type="range" min="0" max="300" value="10" step="1">
        <br /><label>Obj A Top</label><input type="range" min="0" max="300" value="10" step="1">
        <br /><label>Obj A Width</label><input type="range" min="0" max="300" value="100" step="1">
        <br /><label>Obj A Height</label><input type="range" min="0" max="300" value="100" step="1">

    </fieldset>

    <fieldset>
        <legend>Obj B</legend>
        <br /><label>Obj B Left</label><input type="range" min="0" max="300" value="50" step="1">
        <br /><label>Obj B Top</label><input type="range" min="0" max="300" value="50" step="1">
        <br /><label>Obj B Width</label><input type="range" min="0" max="300" value="300" step="1">
        <br /><label>Obj B Height</label><input type="range" min="0" max="300" value="300" step="1">

    </fieldset>
</div>


And here are the controls. It doesn't look pretty, but it doesn't need to.




In this next div, add this text. This is supposed to be a visual indicator to tell you if collision has been achieved.
<div class="notice">
    H: |
    V: |
    Collision:

</div>


Not much to look at, but it'll do... for now.




For the final div, this is where your objects are visually represented. Add these divs. They will both be styled using obj, then objA and objB respectively. Do not bother refreshing your browser; you won't see anything because their heights and widths aren't even specified.
<div class="space">
    <div class="obj objA"></div>
    <div class="obj objB"></div>

</div>


Now for some VueJS!

Our intention is to take advantage of VueJS's two-way binding feature. Before that happens, we will need to specify some data and methods. First. use the el property to specify that VueJS will operate in the cdApp div. Then specify data, methods and created as properties. created is a callback which fires off when the page loads.
<script>
    var app = new Vue
    (
        {
            el: "#cdApp",
            data:
            {

            },
            methods:
            {

            },
            created: function()
            {

            }
        }

    );
</script>


In data, we declare v, h and collision. All of them are false by default.
data:
{
    v: false,
    h: false,
    collision: false

},


Then we have the objA and objB properties. Each is an object that has top, left, width and height properties. Note that the values for these properties correspond to the initial values we set for the controls!
data:
{
    v: false,
    h: false,
    collision: false,
    objA: { left: 10, top: 10, width: 100, height: 100},
    objB: { left: 50, top: 50, width: 300, height: 300}

},


Remember the divs we put in the div that has the CSS class space? Let's now specify styles for these divs. They will take their values from the objA and objB object properties.
<div class="space">
    <div class="obj objA" style="left: {{ objA.left }}px; top: {{ objA.top }}px; width: {{ objA.width }}px; height: {{ objA.height }}px;"></div>
    <div class="obj objB" style="left: {{ objB.left }}px; top: {{ objB.top }}px; width: {{ objB.width }}px; height: {{ objB.height }}px;"></div>
</div>


In the CSS, make sure obj has position property set to absolute. For objA, we have a blue outline and for objB, we have a green outline. This is to visually differentiate the two objects; the colors don't matter that much as long as they are both different.
.space
{
    position: relative;
    width: 100%;
    height: 72%;
    background-color: rgba(255, 200, 0, 0.1);
}

.obj
{
    position: absolute;
}

.objA
{
    outline: 1px solid blue;
}

.objB
{
    outline: 1px solid green;
}


fieldset
{
    width: 20em;
    height: 10em;
    float: left;
    display: block;
}


The plot thickens! Now you can see your objects.




Let us now use the v-model attribute and set it to the left property of the objA object. This leverages on VueJS's two-way binding. Thus, when the value of this input changes, the left property of  the objA object will change, and vice versa.
<fieldset>
    <legend>Obj A</legend>
    <br /><label>Obj A Left</label><input type="range" min="0" max="300" value="10" step="1" v-model="objA.left">
    <br /><label>Obj A Top</label><input type="range" min="0" max="300" value="10" step="1">
    <br /><label>Obj A Width</label><input type="range" min="0" max="300" value="100" step="1">
    <br /><label>Obj A Height</label><input type="range" min="0" max="300" value="100" step="1">
</fieldset>


Add this to reflect the value of the left property at any time.
<fieldset>
    <legend>Obj A</legend>
    <br /><label>Obj A Left</label><input type="range" min="0" max="300" value="10" step="1" v-model="objA.left"> {{ objA.left }}
    <br /><label>Obj A Top</label><input type="range" min="0" max="300" value="10" step="1">
    <br /><label>Obj A Width</label><input type="range" min="0" max="300" value="100" step="1">
    <br /><label>Obj A Height</label><input type="range" min="0" max="300" value="100" step="1">
</fieldset>


Now adjust the value of the first slider. The number should change! And notice that since we tied the left property of the objA object to the left property of the blue square, it moves when you change the value!




Do the same for all the other inputs. They should control the left, top, width and height properties of the objA and objB objects accordingly.
<fieldset>
    <legend>Obj A</legend>
    <br /><label>Obj A Left</label><input type="range" min="0" max="300" value="10" step="1" v-model="objA.left"> {{ objA.left }}
    <br /><label>Obj A Top</label><input type="range" min="0" max="300" value="10" step="1" v-model="objA.top"> {{ objA.top }}
    <br /><label>Obj A Width</label><input type="range" min="0" max="300" value="100" step="1" v-model="objA.width"> {{ objA.width }}
    <br /><label>Obj A Height</label><input type="range" min="0" max="300" value="100" step="1" v-model="objA.height"> {{ objA.height }}
</fieldset>

<fieldset>
    <legend>Obj B</legend>
    <br /><label>Obj B Left</label><input type="range" min="0" max="300" value="50" step="1" v-model="objB.left"> {{ objB.left }}
    <br /><label>Obj B Top</label><input type="range" min="0" max="300" value="50" step="1" v-model="objB.top"> {{ objB.top }}
    <br /><label>Obj B Width</label><input type="range" min="0" max="300" value="300" step="1" v-model="objB.width"> {{ objB.width }}
    <br /><label>Obj B Height</label><input type="range" min="0" max="300" value="300" step="1" v-model="objB.height"> {{ objB.height }}
</fieldset>


Now the two objects should change shape and location when you change the values of the inputs!




Detecting Collision

The final thing we are going to do here, is calculate whether objA and objB have collided. For that, there are two components - the display and the calculation. The display is fairly straightforward. Just create the getCollision() method. The prop parameter is what's used to determine what data we examine. By default, return the string "No".
methods:
{
    getCollision: function(prop) {
        return "No";
    }

},


However, if the property in Vue's data is true, return "Yes".
methods:
{
    getCollision: function(prop) {
        if (this[prop]) return "Yes";
        return "No";
    }
},


In the HTML, add these calls to the getCollision() method.
<div class="notice">
    H: {{ getCollision('h') }} |
    V: {{ getCollision('v') }} |
    Collision: {{ getCollision('collision') }}
</div>


And now you see the strings "No", in the display. Even though the two objects are obviously collided. That's because all the data is false right now and we have no mechanism to change those values. That will be fixed with the introduction of detectCollision()!




First, create the method.
methods:
{
    getCollision: function(prop) {
        if (this[prop]) return "Yes";
        return "No";
    },
    detectCollision: function() {

    }

},


Here, we create the variables h, v and collided and set them to false. Then at the end, we set collided to true if both h and v are true.
detectCollision: function() {
    var collided = false;
    var h = false;
    var v = false;

    if (h && v) collided = true;

}


After that, we set the h, v and collided properties to have the same values as their counterparts in this method.
detectCollision: function() {
    var collided = false;
    var h = false;
    var v = false;

    if (h && v) collided = true;

    this.h = h;
    this.v = v;
    this.collision = collided;

}


This next part is for us to calculate h and v. h is the horizontal, and v is the vertical. collided can only be true when both h and v are true, which means objA and objB's horizontal and vertical coordinates both intersect.

So we check this. The left property of objA has to fall between the left property of objB and its rightmost corner (which is objB's left property plus its width property). If that is true, h is true.
detectCollision: function() {
    var collided = false;
    var h = false;
    var v = false;

    if (this.objA.left >= this.objB.left && this.objA.left <= this.objB.left + this.objB.width)  h = true;

    if (h && v) collided = true;

    this.h = h;
    this.v = v;
    this.collision = collided;
}


h can also be true if, conversely, the left property of objB falls between the left property of objA and its rightmost corner.
detectCollision: function() {
    var collided = false;
    var h = false;
    var v = false;

    if (this.objA.left >= this.objB.left && this.objA.left <= this.objB.left + this.objB.width)  h = true;
    if (this.objA.left <= this.objB.left && this.objA.left + this.objA.width >= this.objB.left) h = true;

    if (h && v) collided = true;

    this.h = h;
    this.v = v;
    this.collision = collided;
}


Now, for v. If the top property of objA falls between the top property of objB and its bottom (which is the top property of objB plus the height property of objB), then v is true.
detectCollision: function() {
    var collided = false;
    var h = false;
    var v = false;

    if (this.objA.left >= this.objB.left && this.objA.left <= this.objB.left + this.objB.width)  h = true;
    if (this.objA.left <= this.objB.left && this.objA.left + this.objA.width >= this.objB.left) h = true;

    if (this.objA.top >= this.objB.top && this.objA.top <= this.objB.top + this.objB.height) v = true;

    if (h && v) collided = true;

    this.h = h;
    this.v = v;
    this.collision = collided;
}


Also, if the top property of objB falls between the top property of objA and its bottom, then v is true.
detectCollision: function() {
    var collided = false;
    var h = false;
    var v = false;

    if (this.objA.left >= this.objB.left && this.objA.left <= this.objB.left + this.objB.width)  h = true;
    if (this.objA.left <= this.objB.left && this.objA.left + this.objA.width >= this.objB.left) h = true;

    if (this.objA.top >= this.objB.top && this.objA.top <= this.objB.top + this.objB.height) v = true;
    if (this.objA.top <= this.objB.top && this.objA.top + this.objA.height >= this.objB.top) v = true;

    if (h && v) collided = true;

    this.h = h;
    this.v = v;
    this.collision = collided;
}


Make sure detectCollision() is called upon page load.
created: function()
{
    this.detectCollision();
}


And also called whenever any of the input values change. Use the v-on:change attribute.
<fieldset>
    <legend>Obj A</legend>
    <br /><label>Obj A Left</label><input type="range" min="0" max="300" value="10" step="1" v-model="objA.left" v-on:change="detectCollision"> {{ objA.left }}
    <br /><label>Obj A Top</label><input type="range" min="0" max="300" value="10" step="1" v-model="objA.top" v-on:change="detectCollision"> {{ objA.top }}
    <br /><label>Obj A Width</label><input type="range" min="0" max="300" value="100" step="1" v-model="objA.width" v-on:change="detectCollision"> {{ objA.width }}
    <br /><label>Obj A Height</label><input type="range" min="0" max="300" value="100" step="1" v-model="objA.height" v-on:change="detectCollision"> {{ objA.height }}
</fieldset>

<fieldset>
    <legend>Obj B</legend>
    <br /><label>Obj B Left</label><input type="range" min="0" max="300" value="50" step="1" v-model="objB.left" v-on:change="detectCollision"> {{ objB.left }}
    <br /><label>Obj B Top</label><input type="range" min="0" max="300" value="50" step="1" v-model="objB.top" v-on:change="detectCollision"> {{ objB.top }}
    <br /><label>Obj B Width</label><input type="range" min="0" max="300" value="300" step="1" v-model="objB.width" v-on:change="detectCollision"> {{ objB.width }}
    <br /><label>Obj B Height</label><input type="range" min="0" max="300" value="300" step="1" v-model="objB.height" v-on:change="detectCollision"> {{ objB.height }}
</fieldset>


Now see, when you move the objects around, the display shows if h is true...




...or v is true...




...or both! In which case, collided is true!




I should also mention you'll want to turn the red outline off.
div { outline: 0px solid red;}


There you go.




Finally

This web tutorial wasn't pretty, and certainly has very limited mainstream use. But it can be a useful tool for testing collision.

Till our paths collide!
T___T