Will McLean
Process diary

Colour by location

Last modified: October 8, 2018
Created: August 29, 2018

This is an exercise by Will McLean. View this exercise here or view all exercises here.

Overview

The idea for this exercise came when brainstorming for a client website. GML Heritage is one of Australia’s leading heritage consultancies and carries out projects all over Australia. Each project is defined by its location. Could we turn that location into a part of the brand? Could we generate a colour from that location and thereby create a colour scheme that is truely unique to GML?

Whilst the concept never got picked up this exercise explores the possibility. It can take a list of locations with latitude and longitude and position them relative to a base location around the viewport. They will each be given a programatically generated colour also relative to the base location.

The colours

We use the HSL system to generate our colour. We want to convert our cartesian co-ordinates (Latitude / Longitude ) to polar co-ordinates so we can use an angle for the hue and the distance for the saturation and luminosity.

Luckily someone figured out how to do this quite a while back and we can now use it for our own purposes. One thing to be aware of with the code below is that the result of Math.atan is not always correct for our purpose. If it lands in any quadrant other than the first we need to adjust it to get an angle relative to our base location’s horizontal axis.

// Turn the x,y positions given by longitude and latitude into a degree and a distance relative to the base.
function cartesian2Polar( x, y ) {
    // Distance between two point on a graph is:
    // d = √(x2-x1)² + (y2-y1)²
    let sideA = x - baseX,
        sideB = y - baseY,
        radianCoor = Math.sqrt( Math.pow( sideA, 2 ) + Math.pow( sideB, 2 ) ), // (Distance)
        angularCoor = Math.atan( sideB / sideA ) * ( 180 / Math.PI );
    // Depending on the quadrant with which your point lies away from the base point atan can give incorrect results.
    // Quadrants are: 0-90 = Quad 1, 90-180 = Quad 2, 180-270 = Quad 3, 270-360 = Quad 4
    if ( x < baseX ) {
        // If it is in Quad 2 or 3 then add 180 to the result
        angularCoor += 180;
    } else if ( y < baseY ) {
        // If it is in Quad 4 then add 360 to the result
        angularCoor += 360;
    }
    return { radianCoor: radianCoor, angularCoor: angularCoor };
}

Once you have the polar co-ordinates, setting the colour is easy. Note that we have set some limits on our sat and luminosity in order to keep the range within some boundaries. These can easily be changed.


//Our colour limits
let satMin = 20,
    satMax = 80,
    lumMin = 20,
    lumMax = 80;
function setColour( polarCoords, location ) {
    // Hue directly equals the angular Coordinate
    // Sat and Lum relate to the radian coordinate (distance).
    // The raw radian value is translated to the ranges we have dictated by setting our colour limits
    let hue = polarCoords[ 'angularCoor' ] || 0,
        sat = crossMultiply( polarCoords[ 'radianCoor' ], maxHypotenuse, satMax - satMin ) + satMin,
        lum = crossMultiply( polarCoords[ 'radianCoor' ], maxHypotenuse, lumMax - lumMin ) + lumMin;
    // Set the colour and return it for future use
    location.style.backgroundColor = 'hsla(' + hue + ', ' + sat + '%, ' + lum + '%, 1)';
    return { hue: hue, sat: sat, lum: lum };
}

The positions

For the positions of the locations around the viewport we just turn the latitude and longitude to x and y positions relative to the viewport using cross multiplication. They are all initially stacked on top of the base location in the centre of the viewport and then using css3 transform and it’s translate value we move them out to their correct positions.

// Cross multiplication will allow us to convert a value from one range to the equivalent value from another range.
// Takes an input value and input range maximum along with the maximum from a target range
// ie: What is 2 in a range from 0 to 4  equal to in a range from 0 to 8? Answer is 4.
function crossMultiply( inputValue, inputRangeMax, targetRangeMax ) {
    return targetRangeMax * inputValue / inputRangeMax;
}

function positionLocation( location, locationX, locationY ) {
    let x = locationX - baseX,
        transX = ( x > 0 ? crossMultiply( x, max, 48 ) : crossMultiply( x, max * -1, 48 ) * -1 ),
        y = locationY - baseY,
        transY = ( y > 0 ? crossMultiply( y, max, 48 ) : crossMultiply( y, max * -1, 48 ) * -1 );
    transY = transY * -1;
    location.style.transform = 'translate( ' + transX + posUnit + ', ' + transY + posUnit + ' )';
}

The full excercise can be viewed here. Feel free to inspect the source code on the exercise for more detail. Adapt as you please.

This is a Exercises post.