Home Reference Source Test

src/Gisplay/Legend.js

/**
 * This class represents the Map Legend. 15/03
 * @see Diogo's thesis page 66/67 + 58/59 + 69(Figures)
 */
export class Legend {
    /**
     * Creates an instance of Legend class.
     * @param {number} id -  The id of the legend.
     * @param {string} title - The title for the legend.
     * 
     * @memberOf Legend
     */
    constructor(id, title) {
        /**
         * Title of the Gisplay map Legend.
         * @type {string}
         */
        this.title = title;
        /**
         * The div that contains the Legend.
         * @type {HTMLDivElement}
         */
        this.legendDiv = null;
        /**
         * The table element where the legend elements will be apppended.
         * @type {HTMLTableElement}
         */
        this.table = null;
        /**
         * Class name for the Legend? Not used?
         * @type {string}
         */
        this.className = null;
        /**
         * The last div to insert in the legend. Only for PSymbol Maps.
         * @type {HTMLDivElement}
         */
        this.lastdiv = undefined;
        /**
         * If it is the first insertion or not. Only for PSymbol Maps.
         * @type {boolean}
         */
        this.firstInsertion = false;
        /**
         * Constant to use when it's a Polygon.
         * @constant {number} POLYGON
         */
        this.POLYGON = 1;
         /**
         * Constant to use when it's a Point.
         * @constant {number} POLYGON
         */
        this.POINT = 2;

        this.init(id, null);
    }

    /**
     * Initialise Legend. Creates a table element and appends title, color element and value element.
     * @param {number} id - The id of this Legend.
     * @param {string} classname - The class name to be usde by the Legend.
     * @memberOf Legend
     */
    init(id, classname) {
        //const mapCanvas = document.getElementById(`mapCanvas${id}`); //@TODO: Remove
        this.legendDiv = document.createElement('div');
        if (classname != undefined && classname != null)
            this.legendDiv.className = classname;
        else
            this.legendDiv.className = '_gisplaylegendBR';

        this.legendDiv.id = `legendDiv${id}`;

        this.table = document.createElement('table');
        this.table.style.zIndex = "2000";
        const thvalue = document.createElement('th');
        const thcolor = document.createElement('th');
        thcolor.style.align = "center";

        this.table.appendChild(thcolor);
        this.table.appendChild(thvalue);

        const titlerow = document.createElement('tr');
        const titletd = document.createElement('td');
        titletd.colSpan = 2;
        titletd.style.textAlign = 'center';
        titletd.style.width = 100;
        const titletext = document.createTextNode(this.title);
        titletd.appendChild(titletext);
        titlerow.appendChild(titletd);
        this.table.appendChild(titlerow);
    }

    /**
     * Inserts one row for the Aesthetic object.
     * Used in Dot Maps.
     * @param {Aesthetic} currentaes - The Aesthetic object 
     * @param {Map} mapobj - The Map object where the point row will be added. 
     * @see Diogo's thesis Page 69 5.1c
     * @memberOf Legend
     */
    insertPointRow(currentaes, mapobj) {
        this.insertRow(currentaes, mapobj, this.POINT);
    }

    /**
     * Inserts one polygon row into the Legend. Used for area based Maps (Choropleth and Chorocromatic).
     * @param {Aesthetic} currentaes  - The Aesthetic object 
     * @param {Map} mapobj - The map where to insert a polygon row.
     * @see Diogo's thesis Page 69 5.1b
     * @memberOf Legend
     */
    insertPolygonRow(currentaes, mapobj) {
        this.insertRow(currentaes, mapobj, this.POLYGON);
    }

    /**
     * Adds one row to the Legend and attaches an on click event to said row.
     * @param {Aesthetic} currentaes  - The Aesthetic object.
     * @param {Map} mapobj - The map where to insert a polygon row.
     * @param {number} type - The type of row to insert. 1=Polygon, 2=Point. @TODO: Should be constant
     * @memberOf Legend
     */
    insertRow(currentaes, mapobj, type) {
        const row = document.createElement('tr');
        const value = document.createElement('td');
        const color = document.createElement('td');
        let text;
        if (typeof currentaes.range[0] === 'number') {
            const mininput = currentaes.range[0] != null ? currentaes.range[0] : mapobj.min;
            const maxinput = currentaes.range[1] != null ? currentaes.range[1] : mapobj.max;
            if (!currentaes.isOuter())
                text = document.createTextNode(`[${mininput}, ${maxinput}[`);
            else
                text = document.createTextNode(`[${mininput}, ${maxinput}]`);
        }
        else
            text = document.createTextNode(currentaes.range[0]);

        value.appendChild(text);
        const colorDiv = document.createElement('div');
        colorDiv.style.position = 'relative';

        const [r, g, b, a] = currentaes.getFillColor();
        const rgba = `rgba(${r},${g},${b},${a})`;
        colorDiv.style['backgroundColor'] = rgba;
        if (type === this.POLYGON) {//polygon
            colorDiv.style.height = 25;
            colorDiv.style.width = 80;
            if (currentaes.getStrokeColor() != null) {//&& currentaes != undefined
                const [r, g, b, a] = currentaes.getStrokeColor();
                colorDiv.style['borderColor'] = `rgba(${r},${g},${b},${a})`;
            }
            colorDiv.className = '_gisplayrectangle';
        }
        else if (type === this.POINT) {//point
            let size;
            if (currentaes.getPointSize() != null)
                size = Math.max(currentaes.getPointSize(), 5);
            else
                size = 25;
            colorDiv.style.height = size;
            colorDiv.style.width = size;
            colorDiv.className = '_gisplaycircle';
        }

        color.appendChild(colorDiv);
        row.appendChild(color);
        row.appendChild(value);

        row.onclick = function () {
            if (mapobj.gisplayOptions.legendToggle) {
                const toFade = !currentaes.enableDisable();
                if (toFade)
                    this.className += " _gisplayfade";
                else
                    this.className = this.className.replace(/(?:^|\s)_gisplayfade(?!\S)/g, '');
            }
            if (mapobj.gisplayOptions.legendOnClickFunction != null && mapobj.gisplayOptions.legendOnClickFunction != undefined)
                mapobj.gisplayOptions.legendOnClickFunction(currentaes);
            mapobj.draw();
        };
        this.table.appendChild(row);
    }

    /**
     * Inserts one Proportional symbols legend element.
     * Used by PSymbols Maps.
     * @param {Aesthetic} currentaes  - The Aesthetic object.
     * @param {Map} mapobj - The map where to insert the proportion symbols element.
     * @param {number} numLegendItems - Number of items to be created.
     * @see Diogo's thesis Page 69 5.1a
     * @memberOf Legend
     */
    insertProportionalSymbols(currentaes, mapobj, numLegendItems) {
        if (this.lastdiv === undefined) {//First insertion
            var row = document.createElement('tr');
            var value = document.createElement('td');
            value.colSpan = 2;
            value.style.textAlign = 'center';
            this.firstInsertion = true;
        }
        else
            this.firstInsertion = false;

        let strokecolor;
        let [sr, sg, sb, sa] = currentaes.getStrokeColor();//console.log("PropSymbols Insert >>>", sr, sg, sb, sa);
        let [r, g, b, a] = currentaes.getFillColor(); //console.log("PropSymbols Insert >>>", r, g, b, a);
        const rgba = `rgba(${r},${g},${b},${a})`;// const rgbc = `rgba(${currentaes.fillColor[0]},${currentaes.fillColor[1]},${currentaes.fillColor[2]},${1})`;

        if (currentaes.getStrokeColor() != null && currentaes != undefined) //TODO: Remove && curr..
            strokecolor = `rgba(${sr},${sg},${sb},${sa})`;
        else
            strokecolor = `rgba(${0},${0},${0},${1})`;

        for (let i = numLegendItems - 1; i >= 0; i--) {
            const current = document.createElement('div');
            let propvalue;
            if (!this.firstInsertion && i === (numLegendItems - 1) || numLegendItems === 1)
                propvalue = currentaes.range[1];
            else
                propvalue = mapobj.min + i / (numLegendItems - 1) * (mapobj.max - mapobj.min);

            const text = document.createTextNode(Math.round(propvalue)); //TODO: this.round(propValue) ?
            current.appendChild(text);
            const colorDiv = document.createElement('div');
            colorDiv.style.position = 'relative';
            colorDiv.style.backgroundColor = rgba;
            colorDiv.className = '_gisplayproportionalcircle';
            colorDiv.style.borderColor = strokecolor;
            const temppointsize = ((mapobj.gisplayOptions.maxPointSize - mapobj.gisplayOptions.minPointSize) / (mapobj.max - mapobj.min)) * (propvalue - mapobj.min);
            const size = Math.max(temppointsize, 7.5);
            colorDiv.style.height = size;
            colorDiv.style.width = size;
            colorDiv.style.inherit = false;

            colorDiv.onclick = function (e) {
                if (mapobj.gisplayOptions.legendToggle) {
                    const toFade = !currentaes.enableDisable();
                    if (toFade)
                        this.className += " _gisplayfade";
                    else
                        this.className = this.className.replace(/(?:^|\s)_gisplayfade(?!\S)/g, '');
                }
                if (mapobj.gisplayOptions.legendOnClickFunction != null && mapobj.gisplayOptions.legendOnClickFunction != undefined)
                    mapobj.gisplayOptions.legendOnClickFunction(currentaes);
                mapobj.draw();

                /*if (!e)
                    var e = window.event; //TODO: Remove??*/
                e.cancelBubble = true;
                if (e.stopPropagation)
                    e.stopPropagation();
            };

            current.appendChild(colorDiv);
            if (this.lastdiv != undefined) {
                this.lastdiv.appendChild(current);
                this.lastdiv = colorDiv;
            }
            else {//1st insertion
                value.appendChild(current);
                this.lastdiv = colorDiv;
            }
        }

        if (this.firstInsertion) {//1st insertion
            row.appendChild(value);
            this.table.appendChild(row);
        }
    }

    /**
     * Appends the legend div element to the map container. Used by all Maps.
     * @param {BGMapWrapper} bgMap - The background map where the legend will be appended to. 
     */
    insertLegend(bgMap) {
        this.legendDiv.appendChild(this.table);
        bgMap.getContainer().appendChild(this.legendDiv);
    }

    /**
     * Inserts in the Legend one gradient according to the Aesthetic object.
     * Use in Change Maps. 
     * @param {Map} mapobj - The map where to insert the gradient row.
     * @param {number} left - Left value of the Legend (minimum change).
     * @param {number} middle - Value at the center of the change(aka break point).
     * @param {number} right - Right value of the legend (maximum value).
     * @see Diogo's thesis page 69 Figure 5.1d
     * @deprecated Not used, since now Change Maps use classes instead of gradient.
     * @memberOf Legend
     */
    insertGradient(mapobj, left, middle, right) {
        const row = document.createElement('tr');
        const value = document.createElement('td');
        const valueDiv = document.createElement('div');
        value.colSpan = 2;
        value.style.textAlign = 'center';
        let numberofAesthetics = 5;
        if (mapobj.aesthetics.length > 5)
            numberofAesthetics = mapobj.aesthetics.length;

        let strcolor = '';
        for (let i = 0; i < numberofAesthetics; i++) {
            let [r, g, b] = mapobj.fcolor(i / numberofAesthetics).rgb();
            let [roundR, roundG, roundB] = [Math.round(r), Math.round(g), Math.round(b)];
            strcolor += `,rgba(${roundR},${roundG},${roundB},${mapobj.gisplayOptions.alpha})`;
        }
        valueDiv.style.background = `-webkit-linear-gradient(left${strcolor})`;

        valueDiv.style.height = 25;
        valueDiv.style.width = 130;

        const row2 = document.createElement('tr');
        const value2 = document.createElement('td');
        const divleft = document.createElement('div');

        value2.colSpan = 2;
        divleft.style.textAlign = 'left';
        divleft.style.width = '33%';
        divleft.style.display = "inline-block";
        const lefttext = document.createTextNode(left);
        const divmid = document.createElement('div');
        divmid.style.textAlign = 'center';
        divmid.style.width = '33%';
        divmid.style.display = "inline-block";
        const text = document.createTextNode(middle);
        const divright = document.createElement('div');
        divright.style.textAlign = 'right';
        divright.style.width = '33%';
        divright.style.display = "inline-block";
        const righttext = document.createTextNode(right);

        divleft.appendChild(lefttext);
        divmid.appendChild(text);
        divright.appendChild(righttext);
        value2.appendChild(divleft);
        value2.appendChild(divmid);
        value2.appendChild(divright);
        value.appendChild(valueDiv);
        row2.appendChild(value2);
        row.appendChild(value);
        this.table.appendChild(row);
        this.table.appendChild(row2);
    }

}