Source: weightedAdjacencyMatrix.js

/*=============== Weighted Adjacency Matrix Graph ====================*/

"use strict";

/**
 * The color coded weighted adjacency matrix attached to the graphicalModel.
 */
class WeightedAdjacencyMatrix {

    /**
     * Creates a color coded adjacency matrix contained in a WeightedAdjMat object.
     * @param {string} divID - the id of the div that contains the graph, e.g "#id".
     * @param {object} matrixConfiguration - The configurations of the adjacency matrix. It can be obtained via Config.getAdjacencyMatrixConfig().
     */
    constructor(divID, matrixConfiguration) {

        /**
         * This matrix's configuration.
         * @memberof WeightedAdjacencyMatrix.
         * @type {object}
         */
        this.config = matrixConfiguration;

        this._divID = divID;

        this._adjMatData = []; // Contains the matrix cell data and cell label data

        this._rowLabel = []; // Contains the row labels in string. e.g["square", "circle"]

        this._colLabel = []; // Contains the column labels in string. e.g["square", "circle"]

        this._svg = d3.select(this._divID)
            .append('svg')
            .attr("class", "weightedAdjMat")
            .attr('width', this.config.transform.width)
            .attr('height', this.config.transform.height)
            .append('g')
            .attr("transform", "translate(" + this.config.transform.x + "," + this.config.transform.y + ")");

        // Create the background wrapper for color theme
        this._svg.append("rect")
            .attr("class", "background")
            .attr("width", this.config.transform.width)
            .attr("height", this.config.transform.height)
            .style("fill", this.config.background.color);
    }


    /**
     * Darkens or lightens hex color value.
     * @private
     * @param {string} colorHex - a hex color string. ie. "#63c59b".
     * @param {number} percentage - shading ranges form -100(dark) to +100(light).
     */
    _shadeColor(colorHex, percent) {

        let R = parseInt(colorHex.substring(1, 3), 16);
        let G = parseInt(colorHex.substring(3, 5), 16);
        let B = parseInt(colorHex.substring(5, 7), 16);

        R = parseInt(R * (100 + percent) / 100);
        G = parseInt(G * (100 + percent) / 100);
        B = parseInt(B * (100 + percent) / 100);

        R = (R < 255) ? R : 255;
        G = (G < 255) ? G : 255;
        B = (B < 255) ? B : 255;

        let RR = ((R.toString(16).length === 1) ? "0" + R.toString(16) : R.toString(16));
        let GG = ((G.toString(16).length === 1) ? "0" + G.toString(16) : G.toString(16));
        let BB = ((B.toString(16).length === 1) ? "0" + B.toString(16) : B.toString(16));

        return "#" + RR + GG + BB;
    }


    /** 
     * Draws the adjancy matrix based on the property of each cell.
     */
    redrawMatrix() {

        d3.selectAll(this._divID + " g .cell").remove();
        d3.selectAll(this._divID + " g .label").remove();

        // Each cell group holds 
        let cell = this._svg.selectAll('g')
            .data(this._adjMatData).enter()
            .append("g")
            .attr("class", "cell")
            .attr("transform", d => "translate(" + d.x + "," + d.y + ")");

        // Each rect is a matrix cell
        cell.append("rect")
            .attr("transform", "translate(" + -this.config.matrix.cellDim / 2 + "," + -this.config.matrix.cellDim / 2 + ")")
            .attr('width', this.config.matrix.cellDim)
            .attr('height', this.config.matrix.cellDim)
            .attr('fill', d => {
                // Add color to the cell matrix
                if (d.type === "cellData") {
                    return this._shadeColor(this.config.matrix.color, -d.colorWeight * 5);
                }
                if (d.type === "cellLabel") {
                    return this.config.background.color;
                }
            });


        // Add cell weight text
        cell.append("text")
            .attr("font-size", this.config.matrix.cellDim * this.config.weight.size)
            .attr("text-anchor", this.config.weight.anchor)
            .attr("alignment-baseline", this.config.weight.alignment)
            .attr("fill", this.config.weight.color)
            .text(d => {
                if (d.type === "cellData") {
                    return d.weight;
                }
            });

        // Add cell label text
        cell.append("text")
            .attr("font-size", this.config.matrix.cellDim * this.config.label.size)
            .attr("text-anchor", this.config.label.anchor)
            .attr("alignment-baseline", this.config.label.alignment)
            .attr("fill", this.config.label.color)
            .text(d => {
                if (d.type === "cellLabel") {
                    return d.label;
                }
            });
    }

    /** 
     * Creates an adjacency matrix based on the row and column labels.
     * @param {array} rowLab - An array of labels that represent the row of the matrix.
     * @param {array} colLab - An array of labels that represent the column of the matrix.
     */
    createMatrix(rowLab, colLab) {

        this._rowLabel = rowLab; // Update the labels
        this._colLabel = colLab; // Update the labels

        // Calculate the dimension of each block and other matrix config properties
        this.config.matrix.cellDim = this.config.matrix.cellDim * Array.min([this.config.transform.width, this.config.transform.height]) / Array.max([this._rowLabel.length, this._colLabel.length]);
        this.config.matrix.cellSpacing *= this.config.matrix.cellDim / 10;
        this.config.matrix.x *= this.config.transform.width;
        this.config.matrix.y *= this.config.transform.height;

        // Populate adjacency matrix data
        let id = 0;
        let x;
        let y;
        for (let i = 0; i < this._rowLabel.length; i++) {
            for (let j = 0; j < this._colLabel.length; j++) {
                x = (this.config.matrix.cellDim + this.config.matrix.cellSpacing) * (j + 1 / 2) + this.config.matrix.x;
                y = (this.config.matrix.cellDim + this.config.matrix.cellSpacing) * (i + 1 / 2) + this.config.matrix.y;
                this._adjMatData.push({
                    type: "cellData",
                    id: id,
                    x: x,
                    y: y,
                    weight: 0,
                    colorWeight: 0
                });
                id++;
            }
        }

        // Add column labels to the adjacent matrix.
        for (let i = 0; i < this._colLabel.length; i++) {
            // Add column labels
            x = (this.config.matrix.cellDim + this.config.matrix.cellSpacing) * (i + 1 / 2) + this.config.matrix.x;
            y = (this.config.matrix.cellDim + this.config.matrix.cellSpacing) * (-1 / 2) + this.config.matrix.y;
            this._adjMatData.push({
                type: "cellLabel",
                label: this._colLabel[i],
                x: x,
                y: y,
            });
        }

        // Add row labels to the adjacent matrix.
        for (let i = 0; i < this._rowLabel.length; i++) {
            // Add row labels
            x = (this.config.matrix.cellDim + this.config.matrix.cellSpacing) * (-1 / 2) + this.config.matrix.x;
            y = (this.config.matrix.cellDim + this.config.matrix.cellSpacing) * (i + 1 / 2) + this.config.matrix.y;
            this._adjMatData.push({
                type: "cellLabel",
                label: this._rowLabel[i],
                x: x,
                y: y,
            });
        }

        this.redrawMatrix();
    }


    /** 
     * Increases the matix cell weight by label pair.
     * @param {Array} cell - The cell to increase weight is represented by a coordinate pair using labels, ie. cell = (rowLabel, colLabel).
     * @param {Integer} weight - Used to increase the weight of the cell.
     */
    increaseCellWeight(cell, weight) {
        let row = this._rowLabel.indexOf(cell[0]);
        let col = this._colLabel.indexOf(cell[1]);

        if (row < 0 || col < 0) throw new Error("updateMatrix(cell): the element updated does not exist in the adjacency matrix.");

        let elementIndex = row * this._colLabel.length + col;
        this._adjMatData[elementIndex].weight += weight;
    }


    /** 
     * Increases the matix cell color weight and darkens the color by label pair.
     * @param {Array} cell - The cell to increase weight is represented by a coordinate pair using labels, ie. cell = (rowLabel, colLabel).
     * @param {Integer} weight - Used to increase the color weight of the cell.
     */
    increaseCellColor(cell, colorWeight) {
        let row = this._rowLabel.indexOf(cell[0]);
        let col = this._colLabel.indexOf(cell[1]);

        if (row < 0 || col < 0) throw new Error("updateMatrix(cell): the element updated does not exist in the adjacency matrix.");

        let elementIndex = row * this._colLabel.length + col;
        this._adjMatData[elementIndex].colorWeight += colorWeight;
    }


    /** 
     * Increases the matix column color weight and darkens the color.
     * @param {Array} colLabel - The label of the column to increase weight is represented by a label in string.
     * @param {Integer} weight - Used to increase the color weight of the cell.
     */
    increaseColumnColor(colLabel, colorWeight) {
        let col = this._colLabel.indexOf(colLabel);

        if (col < 0) throw new Error("increaseColumnColor: the column updated does not exist in the adjacency matrix.");

        for (let row = 0; row < this._rowLabel.length; row++) {
            let elementIndex = row * this._colLabel.length + col;
            this._adjMatData[elementIndex].colorWeight += colorWeight;
        }
    }


    /** 
     * Reset the matix cell weight.
     * @function WeightedAdjMat.resetMatrixWeight.
     */
    resetMatrixWeight() {
        /* Reset each matrix cell weight to 0 */
        for (let i = 0; i < this._adjMatData.length; i++) {
            if (this._adjMatData[i].type === "cellData") {
                this._adjMatData[i].weight = 0;
            }
        }
    }

    /** 
     * Reset the matix cell color weight and updates color based on the weight.
     * @function WeightedAdjMat.resetMatrixColorWeight.
     */
    resetMatrixColorWeight() {
        /* Reset each matrix cell weight to 0 */
        for (let i = 0; i < this._adjMatData.length; i++) {
            if (this._adjMatData[i].type === "cellData") {
                this._adjMatData[i].colorWeight = 0;
            }
        }
    }

    /** 
     * Get the matix cell weight by cell pair, e.g. (i, j).
     * @param {Array} cell - the cell to increase weight is represented by a coordinate pair, ie. cell = (i, j).
     * @return {Integer} The cell weight.
     */
    getCellWeight(cell) {
        let i = cell[0];
        let j = cell[1];

        if (i > this._colLabel.length || j > this._rowLabel.length || i < 0 || j < 0) throw new Error("getCellWeight(cell): the element to get does not exist in the adjacency matrix.");

        let elementIndex = i + j * this._colLabel.length;
        return this._adjMatData[elementIndex].weight;
    }

    /** 
     * Get the matrix cell data as a 1D array.
     * @return {Integer} The matrix data as a 1D array.
     */
    getMatrix1D() {
        let matrix1D = [];
        for (let i = 0; i < this._adjMatData.length; i++) {
            if (this._adjMatData[i].type == "cellData") {
                matrix1D.push(this._adjMatData[i].weight);
            }
        }
        return matrix1D;
    }



    //    resetRowLabel(row) {
    //        this._rowLabel = row;
    //    }
    //
    //    resetColLabel(col) {
    //        this._colLabel = col;
    //    }
}