MFColumnGradient / beta / js / mf-column-gradient.js
file last updated : 2023-09-05
/*! MFColumnGradient.js
 *
 * Version: beta
 * Licence: MIT
 */

class MFColumnGradient {
    constructor(tableId, lowColor, highColor) {
        if ( lowColor==undefined ) console.error('No MFColumnGradient low color given');
        if ( highColor==undefined ) console.error('No MFColumnGradient high color given');
        this.table = document.getElementById(tableId);
        this.lowColor = lowColor;
        this.highColor = highColor;
        this.doAutoForeground = false;
        this.lightBgColFg = 'white';
        this.darkBgColFg = 'black';
        this.dpChr = '.';
        this.trace = true;
    }


    _getValue(inputStr, dpChr) {
        // Replace the specified character with a period
        const pattern = new RegExp(`[^0-9${this.dpChr}-]`, 'g');

        // Replace all characters that are not 0-9, dpChr, or '-' with an empty string
        const cleanedStr = inputStr.replace(pattern, '').replace(this.dpChr, '.');

        // Parse the cleaned string as a float (assuming it represents a decimal number)
        const numericValue = parseFloat(cleanedStr);

        // Check if the numericValue is a valid number, if not, return NaN
        if (isNaN(numericValue)) {
            return NaN;
        }

        // Return the numeric value
        return numericValue;
    }


    // Function to change cell color based on value and thresholds
    _changeCellColor(cell, lowThreshold, highThreshold, colorScale) {
        let value;
        if ( cell ) {
            if ( cell.getAttribute('data-cgvalue') ) value=parseFloat(cell.getAttribute('data-cgvalue'));
            else value = parseFloat(this._getValue(cell.textContent, this.dpChr));
        }
        if ( cell && this.trace ) console.log(cell.textContent + ' = '+value);

        if (!isNaN(value)) {
            let adjustedValue = value;

            if (adjustedValue < lowThreshold) {
                adjustedValue = lowThreshold;
            } else if (adjustedValue > highThreshold) {
                adjustedValue = highThreshold;
            }

            const ratio = (adjustedValue - lowThreshold) / (highThreshold - lowThreshold);
            const cellColor = this._interpolateColor(this.lowColor, this.highColor, ratio, colorScale);
            if ( this.trace ) console.log('adjusted color for '+value+' / '+adjustedValue+' = '+ratio);
            cell.style.backgroundColor = cellColor;
            cell.title=cellColor;

            if ( this.doAutoForeground ) cell.style.color = this._getTextColorBasedOnBackground(cellColor);
        }
    }


    // Function to interpolate between two colors
    _interpolateColor(color1, color2, ratio, colorScale) {
        const index = Math.round(ratio * (colorScale.length - 1));
        return colorScale[index];
    }


    // Public method to set column gradient
    setColumnGradient(colN, minThreshold, maxThreshold) {
        let minValue = Infinity;
        let maxValue = -Infinity;

        // Iterate through rows to find the minimum and maximum values
        for (let r = 0; r < this.table.rows.length; r++) {
            const row = this.table.rows[r];
            const cell = row.cells[colN];
            if (cell) {
                const value = parseFloat(this._getValue(cell.textContent, this.dpChr));
                if (!isNaN(value)) {
                    minValue = Math.min(minValue, value);
                    maxValue = Math.max(maxValue, value);
                }
            }
        }

        let lowThreshold, highThreshold;

        if ( minThreshold && minValue<minThreshold ) lowThreshold=minThreshold;
        else lowThreshold=minValue;

        if ( maxThreshold && maxValue>maxThreshold ) highThreshold=maxThreshold;
        else highThreshold=maxValue;

        const colorScale = this._generateColorScale(this.lowColor, this.highColor, 100);

        if ( this.trace ) console.log('Gradients from '+lowThreshold+' to '+highThreshold+' ('+this.lowColor+' to '+this.highColor+')');

        for (let r = 0; r < this.table.rows.length; r++) {
            const row = this.table.rows[r];
            this._changeCellColor(row.cells[colN], lowThreshold, highThreshold, colorScale);
        }
    }


    setDecimalPlaceChar(dpChr) {
        this.dpChr=dpChr;
    }


    setAutoForeground(lightBgCol, darkBgCol) {
        this.doAutoForeground = true;
        this.lightBgColFg=lightBgCol;
        this.darkBgColFg=darkBgCol;
    }


    _getTextColorBasedOnBackground(backgroundColor) {
        // Calculate the relative luminance of the background color
        const r = parseInt(backgroundColor.slice(1, 3), 16) / 255;
        const g = parseInt(backgroundColor.slice(3, 5), 16) / 255;
        const b = parseInt(backgroundColor.slice(5, 7), 16) / 255;

        const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;

        // Define a brightness threshold (you can adjust this value as needed)
        const brightnessThreshold = 0.5;

        // Determine the text color based on the luminance
        if (luminance > brightnessThreshold) {
            return this.lightBgColFg; // Use black text on light backgrounds
        } else {
            return this.darkBgColFg; // Use white text on dark backgrounds
        }
    }


    _generateColorScale(color1, color2, numSteps) {
        // Parse the input colors
        const r1 = parseInt(color1.slice(1, 3), 16);
        const g1 = parseInt(color1.slice(3, 5), 16);
        const b1 = parseInt(color1.slice(5, 7), 16);

        const r2 = parseInt(color2.slice(1, 3), 16);
        const g2 = parseInt(color2.slice(3, 5), 16);
        const b2 = parseInt(color2.slice(5, 7), 16);

        // Calculate step values for each color component
        const stepR = (r2 - r1) / numSteps;
        const stepG = (g2 - g1) / numSteps;
        const stepB = (b2 - b1) / numSteps;

        // Generate the color scale
        const colorScale = [];

        for (let i = 0; i <= numSteps; i++) {
            const r = Math.round(r1 + stepR * i);
            const g = Math.round(g1 + stepG * i);
            const b = Math.round(b1 + stepB * i);

            // Convert to hexadecimal format and push to the scale
            const color = `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
            colorScale.push(color);
        }

        return colorScale;
    }
}

About

License

Latest Release

Version 1.02024-05-08