Calculating beer color in JS

Beer. Is there anybody who doesn't like it? Light and dark, hoppy and wheat, bitter and sweet. Everyone can find something for themselves.

I'm a home-brewer. Each time I brew, I make notes about the ingredients I used, mashing temperatures and hopping additions. I keep all my recipes on a webpage where I also calculate parameters like percentage of alcohol, bitterness and efficiency.

Recently I added automatic calculation of beer color, based on types of malts added. The result of this calculation is a HEX value which I put as a background color of a div containing a transparent beer glass. It looks like this:

Now, let's calculate the color of one of Saisons I made.


Saison color calculation

Each malt has it's own color defined in SRM unit. The range is from 1 to 40, the lower the value is, the lighter the color.

Based on that I created a color mapper with HEX value for each SRM color:

const COLOR_MAP = {  
        1: '#FFE699', 2: '#FFD878',
        3: '#FFCA5A', 4: '#FFBF42',
        5: '#FBB123', 6: '#F8A600',
        7: '#F39C00', 8: '#EA8F00',
        ...,
        39: '#3A070B', 40: '#36080A',
        'max': '#030403'
    };

The base pattern for calculating beer SRM color is:

SRM = 1.4922 * (MCU ^ 0.6859)  

where MCU is Malt Color Unit.

To calculate MCU of the whole beer, we need to use this pattern:

MCU = (Weight of grains in lbs) * (Color of grains) / (Batch volume in gallons)  

As you can see, the above pattern uses lb and gallon units, and I mostly use Polish kilograms and litres. So I had to write converters for them:

const CONVERTERS = {  
    'kg2lbs': 2.20462262,
    'g2lbs': 0.00220462262,
    'l2gal': 0.264172052
};

Taking all this into account, we can create a prototype of our main function:

function getBeerColor(batchSize, grains) {  
    // calculation of MCU from all used grains
    let mcu = calculateMCU(grains);

    // converting batch size from litres to gallons
    let batchSize = getBatchSizeInGallons(batchSize);

    // calculating color
    let color = calculateColor(mcu, batchSize);

    // converting SRM color into HEX value
    let hex = convertSrmToHex(color);

    return hex;
}

Malt Color Units calculation

To calculate MCU for this beer, we have to create an array of malts, where each malt should be an object with 2 keys - amount and color. To have this info, we need to take a deeper look into the recipe:

2.1 kg     Pilzen malt   [srm: 3]  
1.0 kg     Pale Ale      [srm: 3]  
0.6 kg     Wheat malt    [srm: 2]  
0.41 kg    Munich malt   [srm: 9]  
0.2 kg     Carahell      [srm: 20]  
0.325 kg   Caramel 30L   [srm: 30]  

So our grain collection looks like this:

var grains = [  
    {amount: 2.1,   srm: 3},
    {amount: 1.0,   srm: 3},
    {amount: 0.6,   srm: 2},
    {amount: 0.41,  srm: 9},
    {amount: 0.2,   srm: 20},
    {amount: 0.325, srm: 30}
];

Now we can create a function which calculates MCU for our beer:

function calculateMCU(grains) {  
    let mcu = 0,
        amount,
        color;

    let factor = CONVERTERS['kg2lbs'];
    for (let i = 0; i < grains.length; i++) {
        amount = grains[i].amount * factor;
        color = grains[i].color || 0;
        mcu += amount * color;
    }
    return mcu;
}

Please note, that in the original MCU pattern the grain-color value is divided by batch size in gallons. At this point I don't have this value calculated, so I will do it later.


Batch size conversion

Recipe is calculated for 21 litres. It means that we have to convert this value into gallons, to make further calculations. To convert it I'll use mapper which I created earlier:

function getBatchSizeInGallons(amount) {  
    let factor = CONVERTERS[`l2gal`];
    return amount * factor;
}

Half of the work done. Let's go further.


Calculating SRM color

Having needed 2 values for calculating SRM color of the beer, we can write a function for it:

function calculateColor(mcu, batchSize) {  
    let color = 0;
    if (mcu && batchSize) {
        color = 1.4922 * Math.pow(mcu / batchSize, 0.6859);
    }
    return color;
}

As you can see, here is the place where I put batchSize instead of MCU calculation which is in the original pattern. As a result of this function, we receive SRM color of this beer, which parsed to integer gives us 12. The only thing we have to do now, is to convert it into HEX.


Converting color

For SRM to HEX conversion we will use the mapper I created eariler. The function just takes SRM color as a parameter and converts it. If SRM is higher than 40, it takes maximum value from COLOR_MAP object.

function convertSrmToHex(srm) {  
    let color = Math.floor(srm);
    let hex = color && color > 40 ? COLOR_MAP.max : COLOR_MAP[color];
    return hex || '';
}

Result

We now have all the needed parts of color calculation. The result of executing function getBeerColor is HEX value #cf6900. All we need to do now is to put this color as a background of a glass. And it looks like this:

Nice, huh?


Closing words

There are a lot of fun calculations during homebrewing. You can calculate bitterness (IBU), your efficiency, percentage of alcohol and many more.
And of course while doing that, you can drink something that you created by yourself.

Cheers!

Mariusz Wróbel

Senior Frontend Developer, Homebrewer, Offroader

Subscribe to Webinterpret Tech

Get the latest posts delivered right to your inbox.

or subscribe via RSS with Feedly!