Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/dashboard/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
- The `GEODATA.gpkg` should have a layer for each country in the project, with ALL admin levels in the same table. See `UKR` for example.
- Two additional tables should be added before starting the service: `migration.csv` and `pop.csv`. These should either be generated randomly through the script `./scripts/make_dummy_pop_migration.py`, which will save the output to the `db-data` folder. This table should be added BEFORE launching the database container to prevent memory issues.
- Once the tables are in place, the API can be run as a standalone service, or together with the UI container.
- If you want to display some geographical unit that have no available data, add 'Missing' in front of both `pcode` and `name_en`.

## Running API as a standalone service for debugging and development
- **NOTE** The environment variable for `ENV` should be set to `local` for this to work.
Expand Down
18 changes: 9 additions & 9 deletions src/dashboard/api/app/data_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,38 +15,38 @@ def decorated_function(*args, **kwargs):
m.Countries.country == country
).first()[0].name
except TypeError:
return "Invalid country", 400
return f"Data not available for country: {country}", 400
if admin_level := kwargs.get("admin_level", None):
if admin_level not in [1, 2, 3]:
return "Invalid admin level", 400
return f"Data not available for admin level: {admin_level}", 400
if admin_id := kwargs.get("admin_id", None):
try:
db.session.query(m.AdminUnits.pcode).filter(
m.AdminUnits.pcode == admin_id
).first()[0]
except TypeError:
return "Invalid admin_id", 400
return f"Data not available for admin id: {admin_id}", 400
age_ranges = get_age_ranges(country)
age_mins = [age_range["age_min"] for age_range in age_ranges]
age_maxs = [age_range["age_max"] for age_range in age_ranges]
if age_min_male := kwargs.get("age_min_male", None):
if not isinstance(age_min_male, int) or age_min_male not in age_mins:
return f"Invalid age_min -> Accepted values: {age_mins}", 400
return f"Data not available for this age_min -> Accepted values: {age_mins}", 400
if age_max_male := kwargs.get("age_max_male", None):
if not isinstance(age_max_male, int) or age_max_male not in age_maxs:
return f"Invalid age_max_male -> Accepted values: {age_maxs}", 400
return f"Data not available for this age_max_male -> Accepted values: {age_maxs}", 400
if age_min_female := kwargs.get("age_min_female", None):
if not isinstance(age_min_female, int) or age_min_female not in age_mins:
return f"Invalid age_min -> Accepted values: {age_mins}", 400
return f"Data not available for this age_min -> Accepted values: {age_mins}", 400
if age_max_female := kwargs.get("age_max_female", None):
if not isinstance(age_max_female, int) or age_max_female not in age_maxs:
return f"Invalid age_max_female -> Accepted values: {age_maxs}", 400
return f"Data not available for this age_max_female -> Accepted values: {age_maxs}", 400
if age_min := kwargs.get("age_min", None):
if not isinstance(age_min, int) or age_min not in age_mins:
return f"Invalid age_min -> Accepted values: {age_mins}", 400
return f"Data not available for this age_min -> Accepted values: {age_mins}", 400
if age_max := kwargs.get("age_max", None):
if not isinstance(age_max, int) or age_max not in age_maxs:
return f"Invalid age_max -> Accepted values: {age_maxs}", 400
return f"Data not available for this age_max -> Accepted values: {age_maxs}", 400
return f(*args, **kwargs)

return decorated_function
Expand Down
8 changes: 6 additions & 2 deletions src/dashboard/www/public_html/js/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ am5.ready(function () {

});


let palette_population = ["#f7fbff", "#e9f2f9", "#deebf7", "#c6dbef", "#9ecae1", "#6baed6", "#4292c6", "#2171b5", "#08519c", "#08306b"];
// last color: data not avalaible
let palette_population = ["#f7fbff", "#e9f2f9", "#deebf7", "#c6dbef", "#9ecae1", "#6baed6", "#4292c6", "#2171b5", "#08519c", "#08306b", "#fefec0ff"];
var country_ISO3 = "";
var accessToken = "null";
var admin_level = 1;
Expand Down Expand Up @@ -252,6 +252,10 @@ var layerCountry = L.geoJson(null, {
admin_pcode = e.target.feature.properties.pcode;
admin_name_en = e.target.feature.properties.name_en;

if (admin_name_en.includes('Missing')) {
return; // Do nothing if 'Missing' is found
}

main_get_pop_migration(API_URL, country_ISO3, admin_level, admin_pcode, dates_available, age_ranges_available, accessToken);

document.getElementById('controlPanel_BottomleftID').style.height = '400px';
Expand Down
112 changes: 60 additions & 52 deletions src/dashboard/www/public_html/js/population_map.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,62 +31,60 @@ for (var key in data) {
cont++;
}

let breaks_unique = uniqueArray(breaks);

// include actual min and max boundaries to form intervals
breaks.unshift(vls[0]); // min
breaks.push(vls[vls.length - 1]); // max

let palette_final = [];
let diference = palette_colors.length-breaks_unique.length;
for (var i = 0; i < breaks_unique.length; i++) {
palette_final.push(
palette_colors[i+diference]
);
}

let palette = {"breaks":breaks_unique, "colors":palette_final};
// unique & sorted numeric breaks
const breaks_unique = Array.from(new Set(breaks)).sort((a, b) => a - b);

// the number of intervals is breaks_unique.length - 1
const intervals = Math.max(1, breaks_unique.length - 1);

// palette_colors: last element is NA color, the rest are candidates
const baseColors = palette_colors.slice(0, palette_colors.length - 1);

return palette;
let palette_final;
const start = baseColors.length - intervals;
palette_final = baseColors.slice(start);


return {
breaks: breaks_unique,
colors: palette_final,
naColor: palette_colors[palette_colors.length - 1]
};

}


export function getColor(v, palette) {

if (v === undefined || v === null) {
return "#EBEBE4";
}

let xcase = false;
let color;
let breaks = palette["breaks"];
let colors = palette["colors"];
let lp = breaks.length;

if (lp === 1) {
return colors[0];
return palette.naColor;
}

if (breaks[lp - 2] === breaks[lp - 1]) {
lp--;
xcase = true;
}

for (let i = 0; i < lp - 1; i++) {

if (v >= breaks[i] && v <= breaks[i + 1]) {
color = colors[i];
const val = +v;
const breaks = palette.breaks;
const colors = palette.colors;


// iterate intervals: [breaks[i], breaks[i+1]) except include last as >=
for (let i = 0; i < breaks.length - 1; i++) {
const low = breaks[i];
const high = breaks[i + 1];
if (i < breaks.length - 2) {
// all but final interval: inclusive low, exclusive high
if (val >= low && val < high) return colors[i];
} else {
// last interval: inclusive both sides (to include max)
if (val >= low && val <= high) return colors[i];
}
}

if (xcase && v >= breaks[lp - 1]) {
color = colors[lp - 1];
}

if (v < breaks[0]) {
color = colors[0];
}

return color;

// fallback: if smaller than first break
if (val < breaks[0]) return colors[0];
// fallback otherwise
return colors[colors.length - 1] || palette.naColor;
}

export function RestyleLayerPopMapOpacity(_layer) {
Expand Down Expand Up @@ -114,18 +112,18 @@ export function RestyleLayerPopMap(_layer, palette, _admin_pcode) {
_layer.eachLayer(function(featureInstanceLayer) {
var propertyValue = featureInstanceLayer.feature.properties[propertyName];

var mFillColor = getColor(propertyValue, palette);

if (propertyValue == undefined || propertyValue == null) {

featureInstanceLayer.setStyle({
fillColor: "#fefec0ff",
fillColor: mFillColor,
fillOpacity: Opacity,
color: "black",
weight: 2
});
}else{

var mFillColor = getColor(propertyValue, palette);

if (_admin_pcode === featureInstanceLayer.feature.properties["pcode"]){
featureInstanceLayer.setStyle({
Expand Down Expand Up @@ -176,7 +174,7 @@ export function updatePopulationMap(_map, _layer, geoJson, data, palette_colors,
let palette = getPalettePopMap(data, palette_colors);
RestyleLayerPopMap(_layer, palette, _admin_pcode);

loadLagentPopMap(title, palette["colors"], palette["breaks"], "subtitles");
loadLagentPopMap(title, palette);

const resizeObserver = new ResizeObserver(() => {
_map.invalidateSize();
Expand All @@ -188,25 +186,35 @@ export function updatePopulationMap(_map, _layer, geoJson, data, palette_colors,
}


export function loadLagentPopMap(title, colors, breaks, subtitles) {
export function loadLagentPopMap(title, palette) {
const colors = palette.colors.slice().reverse();
const breaks = palette.breaks.slice().reverse();

var html = '<div style="width:80px"><p>' + title + '</p></div>';

var subtitlesArray = Array(colors.length).fill('');
subtitlesArray[0] = subtitles[0];
subtitlesArray[colors.length-1] = subtitles[1];
// var subtitlesArray = Array(colors.length+1).fill('');
// subtitlesArray[0] = subtitles[0];
// subtitlesArray[colors.length-1] = subtitles[1];

// html += '<div style="width:100px">' + subtitles[0] + '</div>';
// reverse legend order
colors = colors.slice().reverse();
breaks = breaks.slice().reverse();

html += '<ul style="list-style-type: none;margin-top: 2px;margin-bottom: 2px;padding-inline-start: 10px;">';
for (var i = 0, len = colors.length; i < len; i++) {
var rgb = _ImageFromRGB.hexToRGB(colors[i]);
var mCanvas = _ImageFromRGB.createImageFromRGBdata(rgb.r, rgb.g, rgb.b, 20, 20);

html += '<li><img width="16px" height="16px" src="' + mCanvas.toDataURL() + '"><span>&#32;&#32;&#32;&#32; &nbsp;&nbsp;' + _utils.nFormatter_Space(breaks[i], 1) + '</span></li>';
}
{
var rgbNA = _ImageFromRGB.hexToRGB(palette.naColor);
var mCanvasNA = _ImageFromRGB.createImageFromRGBdata(rgbNA.r, rgbNA.g, rgbNA.b, 20, 20);
html += '<li style="display:flex; align-items:center; margin-top:6px;">' +
`<img width="16" height="16" src="${mCanvasNA.toDataURL()}" style="margin-right:8px;">` +
`<span style="font-size:13px;">Data unavailable</span>` +
'</li>';
}

html += '</ul>';

document.getElementById('legend_PopMap_info').innerHTML = html;
Expand Down
2 changes: 1 addition & 1 deletion src/dashboard/www/public_html/js/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,7 @@ export function update_Age_Range_labele(vFirst, vLast , age_ranges_available, ag
{
let age_min = age_ranges_available[0][vFirst];
let age_max = age_ranges_available[1][vLast];
if (vLast === age_ranges_available[1].length-1){
if (vLast === age_ranges_available[1].length-1 && age_ranges_available[1].length-1===999){
age_max = age_ranges_available[0][age_ranges_available[0].length - 1] + "+";
}
document.getElementById('label_Age_range').innerHTML = "Ages: "+ age_min +" - "+ age_max;
Expand Down