CSV Data Comparison and Visualization

Explanation of generateComparisonTable Function

The generateComparisonTable function takes two CSV data inputs (a main CSV and a modified CSV), compares them, and generates an HTML table to visualize the differences. This function helps in identifying added, deleted, and modified rows between two versions of a dataset.

1. Function Parameters

  • mainCsvData: The original dataset in CSV format.
  • modifiedCsvData: The modified version of the dataset in CSV format.

2. Convert CSV to JSON

Both CSV inputs are converted into arrays of objects using the csvToJson function (not included in the snippet, but assumed to exist). Each row in the CSV is transformed into a JSON object, where column headers become keys.

3. Create a Map for Faster Lookup

A Map object is created from the modified CSV data using Internal ID as the key. This helps in quick lookups when comparing rows.

const modifiedCsvMap = new Map(modifiedCsvArray.map(row => [row[internalIDCol], row]));

4. Compare Main CSV with Modified CSV

Each row from the main CSV is checked against the modified CSV:

  • If an Internal ID exists in mainCsvArray but not in modifiedCsvArray, the row is marked as a delete-action (indicating that the row was removed in the modified dataset).
  • If an Internal ID exists in modifiedCsvArray but not in mainCsvArray, the row is marked as an add-action (indicating that the row was newly added).
  • If an Internal ID is present in both datasets, each field is checked for differences:
  • If a value has changed, it is recorded, and the row is marked as changed.
  • A keyStatus object is maintained to store which fields were modified.
if (row1[key] !== row2[key]) {
    isChanged = true;
    modifiedRow[key] = row2[key];  // Store the new value
    keyStatus[key] = "changed";   // Mark the field as changed
}

5. Handling Remaining Rows in Modified CSV

After processing all rows from mainCsvArray, any remaining rows in modifiedCsvMap are treated as new entries (add-action).

modifiedCsvMap.forEach(row2 => {
    comparisonResult.push({
        internalID: row2[internalIDCol] || null,
        status: "add-action",
        data: row2
    });
});

6. Sorting the Comparison Results

The comparison results are sorted based on the Step Number column to ensure a logical order in the output.

comparisonResult.sort((a, b) => {
    const stepA = parseInt(a.data["Step Number"], 10) || 0;
    const stepB = parseInt(b.data["Step Number"], 10) || 0;
    return stepA - stepB;
});

7. Generating the HTML Table

The function dynamically constructs an HTML table:

  • Headers: Extracted from the first row.
  • Rows: Each row is generated based on comparisonResult, with CSS classes applied to visually indicate changes.
  • Cell Highlighting:
  • New rows (add-action): Highlighted with a specific class.
  • Deleted rows (delete-action): Highlighted with a different class.
  • Modified fields (changed): Individual cells are marked to show which values changed.
if (keyStatus === "changed") {
    cellClass = "changed";
} else if (keyStatus === "") {
    cellClass = "";
}

8. Returning the Final HTML Table

The function returns the completed HTML table string, ready for rendering on a webpage.

return tableHTML;

Conclusion

This function is useful for visually comparing CSV files in a structured format. The key features include:

  • Efficient comparison using Maps for quick lookups.
  • Color-coded highlighting to indicate added, removed, and changed data.
  • Sorting by step number to maintain logical order.

This script is a great tool for data auditing, change tracking, and version comparison in web applications.

    function generateComparisonTable(mainCsvData, modifiedCsvData) {

        try {

            const internalIDCol = “Internal ID”; // Assuming “Internal ID” is the header

            // Convert both CSV contents into arrays of objects

            const mainCsvArray = csvToJson(mainCsvData);

            const modifiedCsvArray = csvToJson(modifiedCsvData);

            console.log(‘mainCsvArray’,mainCsvArray);

            console.log(‘modifiedCsvArray’,modifiedCsvArray);

            // Create a Map for the modified CSV for faster lookup by Internal ID

            const modifiedCsvMap = new Map(modifiedCsvArray.map(row => [row[internalIDCol], row]));

            console.log(‘modifiedCsvMap’, modifiedCsvMap);

            // Initialize comparisonResult to store the final results

            let comparisonResult = [];

            // Compare the main CSV rows with the modified CSV

            mainCsvArray.forEach(row1 => {

                const internalID = row1[internalIDCol];

                // console.log(‘Row 1:’, row1[internalIDCol]);

                // If the Internal ID exists in the modified CSV, mark the row as green

                let rowClass = “”;

                let status = ;

                let row2 = modifiedCsvMap.get(internalID);

                let modifiedRow = { …row1 };

                let keyStatus = {};

                // console.log(‘row2:’, row2);

                if (!row2 && internalID !== “”) {

                    // If Internal ID is missing from the modified CSV, mark the row as red (delete-action)

                    comparisonResult.push({

                        internalID,

                        status: “delete-action”,

                        data: row1

                    });

                } else if (row2 && internalID === “”) {

                    // If Internal ID is empty in the modified file, mark as add-action

                    comparisonResult.push({

                        internalID: row2[internalIDCol] || null,

                        status: “add-action”,

                        data: row2

                    });

                } else if (row2) {

                    let isChanged = false;

                    Object.keys(row1).forEach(key => {

                        if (row1[key] !== row2[key]) {

                            isChanged = true;

                            modifiedRow[key] = row2[key]; // Store new value

                            keyStatus[key] = “changed”;  // Mark this key as changed

                        } else {

                            keyStatus[key] = “”;  // No change for this key

                        }

                    });

                    if (isChanged) {

                        status = “changed”;

                    }

                    comparisonResult.push({

                        internalID,

                        status,

                        keyStatus,  // Stores individual key status

                        data: modifiedRow

                    });

                    modifiedCsvMap.delete(internalID); // Remove matched row from modifiedCsvMap

                }

            });

            // Process remaining rows in modifiedCsvMap (add-action for new records)

            modifiedCsvMap.forEach(row2 => {

                comparisonResult.push({

                    internalID: row2[internalIDCol] || null,

                    status: “add-action”,

                    data: row2

                });

            });

            // console.log(‘Comparison Result Before:’, comparisonResult);

            // Sort the array by Step Number

            comparisonResult.sort((a, b) => {

                const stepA = parseInt(a.data[“Step Number”], 10) || 0;

                const stepB = parseInt(b.data[“Step Number”], 10) || 0;

                return stepAstepB;

            });

            comparedJson = comparisonResult;

            console.log(‘Comparison Result After:’, comparisonResult);

            let tableHTML = “<table><thead><tr>”;

            const headers = Object.keys(comparisonResult[0].data);

            tableHTML += `<thead><tr>`;

            headers.forEach(header => {

                tableHTML += `<th>${header}</th>`;

            });

            tableHTML += `</tr></thead>`;

            // Generate table rows

            tableHTML += `<tbody>`;

            comparisonResult.forEach(item => {

                // Add row class based on status

                let rowClass = “”;

                if (item.status === “add-action”) {

                    rowClass = “add-action td”;

                } else if (item.status === “delete-action”) {

                    rowClass = “delete-action td”;

                }

                tableHTML += `<tr class=”${rowClass}“>`;

                // Add cells for each header

                headers.forEach(header => {

                    const value = item.data[header] || “”; // Use empty string if the key doesn’t exist

                    // console.log(‘HEADER:’,header);

                    // console.log(‘KEY Status:’,item.keyStatus);

                    let cellClass = “”;

                    let keyStatus=;

                    if(!item.keyStatus ){

                    }else{

                        if(header===“Internal ID” || header===“Timestamp of Entry” || header===“Timestamp of Exit”){

                           

                        }else{

                            keyStatus = item.keyStatus[header] || “”; // Get key-specific status (default to empty string if not found)

                        }

                       

                    }

                    // Apply the class based on key-specific status

                    if (keyStatus === “changed”) {

                        cellClass = “changed”;  // Mark as changed if keyStatus is “changed”

                    } else if (keyStatus === “”) {

                        cellClass = “”;  // No change for this key

                    }

                    // Add the value and cell class to the table cell

                    tableHTML += `<td class=”${cellClass}“>${value}</td>`;

                });

                tableHTML += `</tr>`;

            });

            tableHTML += `</tbody></table>`;

            return tableHTML;

           

        } catch (e) {

            console.error(‘Error in generateComparisonTable’, {message:e.message,stack:e.stack});

        }

}

Leave a comment

Your email address will not be published. Required fields are marked *