Hexagon Geospatial
MENU

Shared Samples

M.App Portfolio provides a modern, cloud-based platform for creating and delivering diverse geospatial web applications.
Through the M.App Studio, our partners can design, build, and deploy their own Hexagon Smart M.Apps.
Showing results for 
Search instead for 
Do you mean 

Half-customized, half-configured BI application.

by Technical Evangelist ‎07-26-2016 05:39 AM - edited ‎07-26-2016 07:07 AM (903 Views)

An attachment contains files required to finish following exercise.

 

Link to repository containing code snippets for following exercise: BitBucket repository

 

1. Configuration of app.

 

1. Selection of map.

001.PNG

 

In first configuration window the "Polska" map is selected. Then click "Next" button.

 

2. Selection of datasets: Geometry and Attributes.

002.PNG

 

In the next section, geometry and attributes are selected. In case of this application, "labour_geom" geometry and "labour_real" attributes are needed

 

3. Defining dimensions and measures of data. 

003.PNG

 

 

In this step "Advanced" edit mode is selected. Primary key is "id_woj". Then one custom attribute is added with name "POPULATION_TOTAL_EMPLOYED" which have value "POPULATION_M_EMPLOYED+POPULATION_F_EMPLOYED". After adding attributes, colors are applied. For choropleth map yellow palette is choosen, with minimal value "2500" and maximum value "5000" with name "ColorsTotal". Next color scheme is "ColorsFemale" with red palette and same values as choropleth. Third and last color scheme is "ColorsMale" which is blue palette with same values as previous palette's.

 

 

Next step is choice of dimensions (x, independent variables). First Dimension is Attribute Based "id_woj". Next one is Time "YEAR" with Time format "%Y" and Dimension name "year_d". Next dimension is Attribute Based "NUTS1_NAME" and last one is also Attribute Based "NUTS1"

 

Now measures are added (y, dependent variables):
1. Type: Count, Measure Name: Count.
2. Type: Custom Expression: YEAR,

     Measure Name: year_m.
3. Type: Custom Expression: $sum(WAGES_TOTAL*POPULATION_TOTAL)/$sum(POPULATION_TOTAL),

     Measure Name: Avarage_wage.
4. Type: Custom Expression: $sum(WAGES_M*POPULATION_M_EMPLOYED)/$sum(POPULATION_M_EMPLOYED),

     Measure Name: Avarage_wage_men.
5. Type: Custom Expression: $sum(WAGES_F*POPULATION_F_EMPLOYED)/$sum(POPULATION_F_EMPLOYED)

     Measure Name: Avarage_wage_women.
6. Type: Sum: Sum of values of the: POPULATION_TOTAL_EMPLOYED attribute..

     Measure Name: Total employed population.
7. Type: Custom Expression: $sum(WAGES_F_REAL*POPULATION_F_EMPLOYED)/$sum(POPULATION_F_EMPLOYED)

     Measure Name: Avarage_wage_women_real.
8. Type: Custom Expression: $sum(WAGES_M_REAL*POPULATION_M_EMPLOYED)/$sum(POPULATION_M_EMPLOYED)

     Measure Name: Avarage_wage_men_real.
9. Type: Custom Expression: $sum(WAGES_TOTAL_REAL*POPULATION_TOTAL_EMPLOYED)/$sum(POPULATION_TOTAL_EMPLOYED)

     Measure Name: Avarage_wage_real.
10. Type: Custom Expression: $sum(WAGES_M*POPULATION_M_EMPLOYED)/$sum(POPULATION_M_EMPLOYED) - $sum(WAGES_F*POPULATION_F_EMPLOYED)/$sum(POPULATION_F_EMPLOYED)

     Measure Name: Avarage_wage_diff.
11. Type: Sum, Sum of values of the "POPULATION_M_EMPLOYED" attribute...

     Measure Name: POPULATION_M_EMPLOYED.
12. Type: Sum, Sum of values of the "POPULATION_F_EMPLOYED" attribute...

     Measure Name: POPULATION_F_EMPLOYED.
13. Type: Custom Expression: ($sum(WAGES_M*POPULATION_M_EMPLOYED)/$sum(POPULATION_M_EMPLOYED) - $sum(WAGES_F*POPULATION_F_EMPLOYED)/$sum(POPULATION_F_EMPLOYED))/($sum(WAGES_M*POPULATION_M_EMPLOYED)/$sum(POPULATION_M_EMPLOYED))

     Measure Name: Wage_gap_percentage.

 

 

4. Defining visual side of application like: map name, display of legend, title of application and configuration of charts.

004.PNG

 

In define visualization window firstly Map Attributes are choosen:


Map name: labour_geom
Select dimension for map: id_woj
Select measure for map: Avarage_wage_real
Select colors for map: ColorsTotal


Legend: ON.

 

Title Bar: ON
Text: The avarage gross salary in {{year_m}} was {{Avarage_wage}} in nominal ZŁ (={{Avarage_wage_real}} in current ZŁ)

 

Charts configuration

 

1. First chart.


Placement: Bottom,
Chart Display Name: AVARAGE GROSS SALARY IN NOMINAL ZŁ OVER TIME
Dimensions: year_d
Measures: Avarage_wage, ColorsTotal
Legend: Disabled

 

2. Second chart

 

Widget: Bar chart
Placement: Left:
Chart Display Name: EMPLOYED POPULATION BY SEX AND NUTS1 REGION
Dimension: NUTS1
Measures: POPULATION_M_EMPLOYED - blue color, POPULATION_F_EMPLOYED - pink color
Legend: disabled

 

3. Third chart

 

Widget: Bar chart,
Placement: Lefr
Chart Display Name: AVERAGE GROSS SALARY BY NUTS1 REGION IN CURRENT ZŁ
Dimension: NUTS1
Measure: Average_wage_real - ColorsStaticYellow
Legend: disabled

 

2. Custom code.

 

CSS code.

 

.bi-pink, .bi-blue, .bi-diff {
    font-weight: bold;
    font-size: larger;
    margin-left: 4px;
}

.bi-pink {
    color: #FF8C7A;
}

.bi-blue {
    color: #2f69f6;
}

.bi-diff {
    color: black;
}

.em {
    color: black;
    font-weight: bold;
}

.male, .female, .total {
    display: inline-block;
    margin: 10px;
    cursor: pointer;
}

.male >i, .female>i, .total>i {
    width: 16px;
    height: 16px;
    display: inline-block;
    margin-right: 5px;
}

.male > i{
    background-color: #2f69f6;
}

.female > i{
    background-color: #FF8C7A;
}

.total > i{
    background-color: #ffbf00;
}

.paygap-legend {
    margin-top: 20px;
}

.paygap-legend {
    padding: 10px;
    text-align: left;
    line-height: 2;
}

.widget-caption {
    font-weight: 600 !important;
    color: #555555 !important;
}

.mft-legend {
    margin-top: 25px;
}

.psz-legend-box {
    height: 90px !important;
}

 

JavaScript code.

 

/*
* Unfortunately we need to update these IDs after reconfiguring the charts... Already fixed in dev ;)
* Identifiers can be found by executing the following snippet in the console 
* 
* $GP.ready("v1.0", function($GP) {
*     $GP.bi.stage.findStage("zbyszek", function(stage) {
*         console.log(stage.widgets());
*     })
* });
*/
var timeChartId = "af5de2d9f8d4f443fbf925041482cb6df",
    choroplthId = "a844f8bc3da014f90bc616a55c1247869",
    averageSalaryChartId = "a587850059e0248489dbd39e0505d9d11",
    maleRealWagesId = "a68d1ace15cab4a089336843dbebfcae4",
    maleNominalWagesId = "a0ec106e9975f4151b96cd4d8a92140b4",
    maleColorsId = "a6f5bbdcecb084030a5685f35670f5db3",
    femaleRealWagesId = "a84f6781f923244838bfa8630a425a65a",
    femaleNominalWagesId = "a96c531ac7631469a92e2ec48c62f2a27",
    femaleColorsId = "a9e736463b0e8469d87659d018673b362",
    realWagesId = "a049f29f4f529498aab334384dada0d31",
    nominalWagesId = "a96282bc9718a48b09474505702cdbde9",
    totalColorsId = "a72b657b5c1904987b70d7c9451a1a411",
    realWagesDiffId ="a6fbd0fd8dff74480b7e1fd524b3b8e0f",
    provincesCountId = "ab82e4b98d3544489a7acdf8c933ac546",
    wagesGapPercentageId = "a863e7ab968b948b9883c7d3cd7c1e02e";

// Here is configuration for numbers showing salary in widget called "Pay Gap in Poland"
var config = {
    "male": {
        "real": maleRealWagesId,
        "nominal": maleNominalWagesId,
        "colors": maleColorsId,
        "color": "#2f69f6",
        "title": '<span style="color:#2f69f6;">male gross salary</span>'
    },
    "female": {
        "real": femaleRealWagesId,
        "nominal": femaleNominalWagesId,
        "colors": femaleColorsId,
        "color": "#FF8C7A",
        "title": '<span style="color:#FF8C7A;">female gross salary</span>'
    },
    "total": {
        "real": realWagesId,
        "nominal": nominalWagesId,
        "colors": totalColorsId,
        "color": "#ffbf00",
        "title": 'gross salary'
    }
};

// Created <div> which containts <span> with title of one of widgets in sidebar which shows text message about Pay Gap in Poland
var root = document.createElement("div");
root.setAttribute("class", "widget dc-chart");
root.innerHTML = '<span class="widget-caption">Pay Gap in Poland</span>';

var block = document.createElement("div");
block.setAttribute("style", "margin-top: 30px;");
block.setAttribute("class", "paygap-legend");

root.appendChild(block);

document.querySelector("#widgets-left").appendChild(root);

// Creating html elements to put in widgets
var customMatchElement0 = document.createElement("span");
block.appendChild(customMatchElement0);

var customMatchElement1 = document.createElement("span");
block.appendChild(customMatchElement1);

var customMatchElement2 = document.createElement("span");
block.appendChild(customMatchElement2);

var customMatchElement3 = document.createElement("span");
block.appendChild(customMatchElement3);

var customMatchElement4 = document.createElement("span");
block.appendChild(customMatchElement4);

// Created <div> with <span> containing legend
var legendDiv = document.createElement("div");
legendDiv.setAttribute("class", "widget dc-chart psz-legend-box");
legendDiv.innerHTML = '<span class="widget-caption">Legend</span><div class="mft-legend"><div class="male"><i>&nbsp;</i>Male</div><div class="female"><i>&nbsp;</i>Female</div><div class="total"><i>&nbsp;</i>Total</div></div>';
document.querySelector("#widgets-left").appendChild(legendDiv);

$GP.bi.stage.requireLibraries(function(gvc, dc, d3, cf, _) {
    // fix the year_m value by pulling it from the time chart
    $GP.bi.stage.findWidget({
        descriptor: timeChartId
    }, function(ret1) {
        if (!ret1 || !ret1.chart) return;
        $GP.bi.stage.findWidget({
            descriptor: "amatch0"
        }, function(ret2) {
            if (!ret2 || !ret2.chart) return;

            function updateYear() {
                var year = ret1.chart.dimension().top(1)[0].YEAR;
                ret2.chart
                    .formatNumber(d3.format(".0f"))
                    .data(year)
                    .redraw();
            }

            updateYear();
            ret1.chart.on("filtered", updateYear);
        });
    });
    // fix elasticY in the average salary chart
    $GP.bi.stage.findWidget({
        descriptor: averageSalaryChartId
    }, function(ret1) {
        if (!ret1 || !ret1.chart) return;
        ret1.chart.elasticY(true).redraw();
    });
    
    // Function configuring onClick events
    function makeOnclickHandler (cfg) {
        return function () {
            // Avarage salary bar chart configuration
            $GP.bi.stage.findWidget({
                descriptor: averageSalaryChartId
            }, function(ret1) {
                if (!ret1 || !ret1.chart) return;
                ret1.chart.redrawChart({
                    values: cfg.real,
                    colors: cfg.color
                });
                ret1.chart.yAxisLabel(" ").redraw();
                ret1.chart.elasticY(true).redraw();
                ret1.chart.render();
                // Configuration of top bar which shows text information about choosed region
                $GP.bi.stage.findWidget({
                    descriptor: "amatch0"
                }, function(ret2) {
                    if (!ret2 || !ret2.chart) return;
                    ret2.chart.redrawChart({
                        "number": {
                            "html": {
                                "one": "The average "+cfg.title+" in %number",
                                "some": "The average "+cfg.title+" in %number",
                                "none": "The average "+cfg.title+" in %number",
                                "negative": "The average "+cfg.title+" in %number"
                            },
                            "format": "0.2f"
                        }
                    });
                    ret2.chart
                        .formatNumber(d3.format(".0f"))
                        .data(ret1.chart.dimension().top(1)[0].YEAR)
                        .redraw();
                });
            });
            //Choropleth customization
            $GP.bi.stage.findWidget({
                descriptor: choroplthId
            }, function(ret1) {
                if (!ret1 || !ret1.chart) return;
                ret1.chart.redrawChart({
                    values: cfg.real,
                    colors: cfg.colors
                });
            });

            $GP.bi.stage.findWidget({
                descriptor: "amatch1"
            }, function(ret1) {
                if (!ret1 || !ret1.chart) return;
                ret1.chart.redrawChart({
                    values: cfg.nominal,
                    colors: cfg.colors
                });
            });
            $GP.bi.stage.findWidget({
                descriptor: "amatch2"
            }, function(ret1) {
                if (!ret1 || !ret1.chart) return;
                ret1.chart.redrawChart({
                    values: cfg.real,
                    colors: cfg.colors
                });
            });
        }
    }
    
    document.querySelector(".male").onclick = makeOnclickHandler(config.male);
    document.querySelector(".female").onclick = makeOnclickHandler(config.female);
    document.querySelector(".total").onclick = makeOnclickHandler(config.total);
});

//Configuration of widgets
$GP.bi.stage.requireLibraries(function(gvc, dc, d3, cf, _) {
    $GP.bi.stage.addWidget({
        descriptor: {
            chartM: {
                "chart": "number",
                "id": "customMatch0",
                "name": "customMatch0",
                "target": "customMatch0",
                "values": provincesCountId,
                "number": {
                    "format": ".0f",
                    "html": {
                        "negative": "",
                        "none": "",
                        "one": "In selected %number province",
                        "some": "In selected %number provinces"
                    }
                }
            },
            domElement: customMatchElement0
        }
    });
    $GP.bi.stage.addWidget({
        descriptor: {
            chartM: {
                "chart": "number",
                "id": "customMatch1",
                "name": "customMatch1",
                "target": "customMatch1",
                "values": maleNominalWagesId,
                "number": {
                    "format": "0.2f",
                    "html": {
                        "negative": " the average male gross salary was <span class=\"bi-blue\"> %number ZŁ </span>",
                        "none": " the average male gross salary was <span class=\"bi-blue\"> %number ZŁ </span>",
                        "one": " the average male gross salary was <span class=\"bi-blue\"> %number ZŁ </span>",
                        "some": " the average male gross salary was <span class=\"bi-blue\"> %number ZŁ </span>"
                    }
                }
            },
            domElement: customMatchElement1
        }
    });
    $GP.bi.stage.addWidget({
        descriptor: {
            chartM: {
                "chart": "number",
                "id": "customMatch2",
                "name": "customMatch2",
                "target": "customMatch2",
                "values": femaleNominalWagesId,
                "number": {
                    "format": "0.2f",
                    "html": {
                        "negative": "<br>while the average female gross salary was <span class=\"bi-pink\"> %number ZŁ.</span>",
                        "none": "<br>while the average female gross salary was <span class=\"bi-pink\"> %number ZŁ.</span>",
                        "one": "<br>while the average female gross salary was <span class=\"bi-pink\"> %number ZŁ.</span>",
                        "some": "<br>while the average female gross salary was <span class=\"bi-pink\"> %number ZŁ.</span>"
                    }
                }
            },
            domElement: customMatchElement2
        }
    });
    $GP.bi.stage.addWidget({
        descriptor: {
            chartM: {
                "chart": "number",
                "id": "customMatch3",
                "name": "customMatch3",
                "target": "customMatch3",
                "values": realWagesDiffId,
                "number": {
                    "format": "0.2f",
                    "html": {
                        "negative": "<br>The average female salary was <span class=\"bi-diff\"> %number ZŁ </span> lower than the average male salary",
                        "none": "<br>The average female salary was <span class=\"bi-diff\"> %number ZŁ </span> lower than the average male salary",
                        "one": "<br>The average female salary was <span class=\"bi-diff\"> %number ZŁ </span> lower than the average male salary",
                        "some": "<br>The average female salary was <span class=\"bi-diff\"> %number ZŁ </span> lower than the average male salary"
                    }
                }
            },
            domElement: customMatchElement3
        }
    });
    $GP.bi.stage.addWidget({
        descriptor: {
            chartM: {
                "chart": "number",
                "id": "customMatch4",
                "name": "customMatch4",
                "target": "customMatch4",
                "values": wagesGapPercentageId,
                "number": {
                    "format": "0.2%",
                    "html": {
                        "negative": " (by <span class=\"bi-diff\"> %number </span>).",
                        "none": " (by <span class=\"bi-diff\"> %number </span>).",
                        "one": " (by <span class=\"bi-diff\"> %number </span>).",
                        "some": " (by <span class=\"bi-diff\"> %number </span>).",
                    }
                }
            },
            domElement: customMatchElement4
        }
    });
});