Infoscreen API

This section explains data structre of UDP messaging between the frontend application and the infoscreen.

Receiving messages from the frontend

a message to switch modes should follow the following structure:

{
    "mode" : string # "buildings_interaction", "individual_data_view" or "total_data_view"
}

mode messages received from the frontend<frontend_udp_message> are processed at main.js line 15 - 20:

if (json.hasOwnProperty('mode')) {
  if (json.mode != currentUserMode) {
    switchUserMode(json.mode);
    currentUserMode = json.mode;
  }
}

switchUserMode function at render.js line 98 - 125 toggles the visibility of html elements which is relevant to the respective modes.

const switchUserMode = function (mode) {
    // TODO: wrap in resetAnswer function
    grayoutAnswerYes()
    grayoutAnswerNo()
    if (mode == 'simulation') {
        displaySimulationMode()
    }
    else if (mode == 'input_scenarios') {
        displayInputEnvironmentMode()
    }
    else if (mode == 'buildings_interaction') {
        displayBuildingsInteractionMode()
        // displayDataViewIndividualMode()
    }
    else if (mode == 'questionnaire') {
        const question = questions[getRandomInt(5)] //TODO: get question number from UDP

        displayQuestionnaireMode(question)
    }
    else if (mode == 'individual_data_view') {
        displayDataViewIndividualMode()
        removeHouseholdCards()
        createHouseholdCards()
    }
    else if (mode == 'total_data_view') {
        displayDataViewTotalMode()
    }
}

if a message contains ‘current_iteration_round’ property, at main.js line 21 - 31 it updates the global variable currentIterationRound which is stored at appData.js and refreshes the number of the columns in the table at the buildings interation view.

if (json.hasOwnProperty("current_iteration_round")) {
  if (currentIterationRound != json.current_iteration_round) {
    currentIterationRound = json.current_iteration_round;
    console.log("current_iteration_round = " + currentIterationRound);
    tableAddColumn(json.current_iteration_round);
    $('#currentRound').find('span').text(currentIterationRound + 1);
    if (currentIterationRound == 0) {
      location.reload(); // reload page
    }
  }
}

if a message contains ‘buildings_groups’ property, at main.js line 46 - 58 renderHouseInfo funtion is triggered and it injects the data to the view for the buildings interaction view and the individual data view :

if (json.hasOwnProperty('buildings_groups')) {
    if ('group_0' in json.buildings_groups) renderHouseInfo(json.buildings_groups.group_0, "dataViewIndividualQuarter0");
    if ('group_1' in json.buildings_groups) renderHouseInfo(json.buildings_groups.group_1, "dataViewIndividualQuarter1");
    if ('group_2' in json.buildings_groups) renderHouseInfo(json.buildings_groups.group_2, "dataViewIndividualQuarter2");
    if ('group_3' in json.buildings_groups) renderHouseInfo(json.buildings_groups.group_3, "dataViewIndividualQuarter3");

    ...

    updateSelectedConnectionsNumber(json);
    updateIndividualData(json);
}

if a message contains ‘sliders’ property, at main.js line x 78 - 80, it calls processSliderHandle function in render.js and changes the display of boarder around slider handle:

if (json.hasOwnProperty('sliders')) {
  processSliderHandle(json.sliders);
}
function processSliderHandle(slider_data) {
    console.log(slider_data)
    if (slider_data.id == "slider0") {
        if (slider_data.handle == "scenario_energy_prices") {
        $("#current_energy_prices_scenario").css("border", "5px goldenrod solid"); // create thick golden border
        }
        else {
        $("#current_energy_prices_scenario").css("border", "1px goldenrod dotted"); // remove border
        }
        if (slider_data.handle == "num_connections") {
        $("#scenario_num_connections").css("border", "5px chartreuse solid"); // create thick blue border
        }
        else{
        $("#scenario_num_connections").css("border", "1px chartreuse dotted"); // create thick blue border
        }
    }
}

Main.js line x 85 - 96 checks if a message contains ‘data_view_neighborhood_data’, ‘neighborhood_images’ or ‘individual_data_view’ property and calls respective functions at render.js to change the view in the individual data view:

if (json.hasOwnProperty("data_view_neighborhood_data")) {
  injectDataToDataView(json.data_view_neighborhood_data)
}
if (json.hasOwnProperty("neighborhood_images")) {
  renewResultsImages(json.neighborhood_images)
}

if (currentUserMode == 'individual_data_view') {
  if (json.hasOwnProperty("active_user_focus_data")) {
    focusActiveUserData(json.active_user_focus_data);
  }
}
const injectDataToDataView = function (data) {
    console.log(data)
    data.forEach((data_per_iteration) => { // for each val in arr, exec func
        renewResultsImgSrcPath(data_per_iteration.iteration_round, data_per_iteration)
        console.log(GAMASimulationImgSrcPaths)
        renewDataViewGAMAImgsPerSection(data_per_iteration.iteration_round)
    });
    removeEmissionComparisonChart()
    createEmissionComparisonChart(data[0].emissions_data_paths)
}

const injectDataToDataView = function (data) {
    console.log(data)
    data.forEach((data_per_iteration) => { // for each val in arr, exec func
        renewResultsImgSrcPath(data_per_iteration.iteration_round, data_per_iteration)
        console.log(GAMASimulationImgSrcPaths)
        renewDataViewGAMAImgsPerSection(data_per_iteration.iteration_round)
    });
    removeEmissionComparisonChart()
    createEmissionComparisonChart(data[0].emissions_data_paths)
}

function focusActiveUserData(userNumber){
    // hide all quarterSections:
    for (var i = 0; i<4; i++){
        $("#dataViewIndividualQuarter" + i).css("display", "None");
    }

    // show only active quarterSection:
    let individualQuarter = $("#dataViewIndividualQuarter" + userNumber)
    individualQuarter.css("display", "grid");
}

Main.js line 102 - 104 checks if a message contains ‘final_step’ and renews the global variables ‘simulationFinalStep’ at appData.js Main.js line 105 - 107 checks if a message contains ‘step’ property and calls updateSimulationProgress function at render.js and renews progress bar:

if (json.hasOwnProperty("final_step")) {
  simulationFinalStep = json.final_step;
}
if (json.hasOwnProperty("step")) {
  updateSimulationProgress(json.step)
const updateSimulationProgress = function (step) {
    const percentage = Math.ceil(step * 100 / simulationFinalStep)
    $("#simulationProgress").find('span').text(percentage)
}

Mode-Specific Updates

Buildings Interaction View

the function renderHouseInfo displays information of building groups that are conitaned in the incoming data.

const renderHouseInfo = function (groupData, quarterID) {
    const individualQuarter = $("#" + quarterID);
    buildings = groupData.buildings;

    if (groupData[0] == '') {
        individualQuarter.css("visibility", "hidden");
    }
    else {
        // show hidden elements:
        if (individualQuarter.css("visibility") == "hidden") {
        individualQuarter.css("visibility", "visible");
        }

        // get only first element of building list:
        const targetBuilding = buildings[buildings.length - 1];

        // update address:
        individualQuarter.find('.address').text(targetBuilding.address);

        // update building type
        target = "#" + quarterID + " > .nameAndTable > .houseInfo > .buildingType > span";
        if (targetBuilding.type == "MFH")
        $(target).text("Mehrfamilienhaus");
        if (targetBuilding.type == "EFH")
        $(target).text("Einfamilienhaus");

        // update consumption data:
        target = "#" + quarterID + " > .nameAndTable > .houseInfo > .heatConsumption > img";
        let heatConsumptionHandle = "default";
        if (targetBuilding.spec_heat_consumption > 250)
        heatConsumptionHandle = "h";
        if (targetBuilding.spec_heat_consumption < 250)
        heatConsumptionHandle = "g";
        if (targetBuilding.spec_heat_consumption < 200)
        heatConsumptionHandle = "f";
        if (targetBuilding.spec_heat_consumption < 160)
        heatConsumptionHandle = "e";
        if (targetBuilding.spec_heat_consumption < 130)
        heatConsumptionHandle = "d";
        if (targetBuilding.spec_heat_consumption < 100)
        heatConsumptionHandle = "c";
        if (targetBuilding.spec_heat_consumption < 75)
        heatConsumptionHandle = "b";
        if (targetBuilding.spec_heat_consumption < 50)
        heatConsumptionHandle = "a";
        if (targetBuilding.spec_heat_consumption < 30)
        heatConsumptionHandle = "aplus";
        $(target).attr("src", "img/qscope_energy_graph_triangle_" + heatConsumptionHandle + "_.png");

        // highlight selected decision:
        if (groupData.slider_handles.length > 0) {
        groupData.slider_handles.forEach(element => {
            individualQuarter.find("." + element).removeClass('highlightedRow')
            individualQuarter.find("." + element).addClass('highlightedRow')
        });
        }
        else {
        individualQuarter.find(".highlightedRow").removeClass('highlightedRow')
        }

        // update image:
        individualQuarter.find(".emissions_graphs img").attr("src", targetBuilding["emissions_graphs"]);
        individualQuarter.find(".energy_prices_graphs img").attr("src", targetBuilding["energy_prices_graphs"]);

    }
}

Individual Data View

Individual data view shows detailed information of selected building group. In addition to what is displayed in the buildings interaction view, the emissions graph and the energy prices graph

const updateIndividualData = function (data) {
    ...
    individualQuarter.find(".emissions_graphs img").attr("src", targetBuilding["emissions_graphs"]);
    individualQuarter.find(".energy_prices_graphs img").attr("src", targetBuilding["energy_prices_graphs"]);
    ...
}

Total Data View

Total data view displayes four different graphs of GAMA simulation: energy_price, emissions_neighborhood_accu, emissions_groups, and energy_prices_groups.

const renewResultsImages = function(data){
    document.getElementById("energy_prices").src = data.energy_prices + "?update=" + new Date().getTime();
    document.getElementById("emissions_neighborhood_accu").src = data.emissions_neighborhood_accu + "?update=" + new Date().getTime();
    document.getElementById("emissions_groups").src = data.emissions_groups + "?update=" + new Date().getTime();
    document.getElementById("energy_prices_groups").src = data.energy_prices_groups + "?update=" + new Date().getTime();
}