Skip to content

Commit

Permalink
GeoIP Map (#142)
Browse files Browse the repository at this point in the history
* First working version of world map chart

* Cleanup code, fix aspect ratio, add GeoIP Map header

* Remove limited-height on session list with already limited content

* Update package lock

* Integrate map into service page

* Adjust map colors

* Adjust colors further

Co-authored-by: R. Miles McCain <github@sendmiles.email>
  • Loading branch information
CasperVerswijvelt and milesmcc authored Jun 13, 2021
1 parent 83b2064 commit 9832de0
Show file tree
Hide file tree
Showing 8 changed files with 1,100 additions and 40 deletions.
1,018 changes: 1,015 additions & 3 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"@fortawesome/fontawesome-free": "^5.15.1",
"a17t": "^0.5.1",
"apexcharts": "^3.24.0",
"datamaps": "^0.5.9",
"flag-icon-css": "^3.5.0",
"inter-ui": "^3.15.0",
"litepicker": "^2.0.11"
Expand Down
5 changes: 5 additions & 0 deletions shynet/dashboard/static/dashboard/css/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@
max-height: 400px;
}

.force-limited-height {
max-height: 400px;
overflow: hidden;
}

.rf {
text-align: right !important;
}
Expand Down
6 changes: 4 additions & 2 deletions shynet/dashboard/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@
<script src="{% static 'apexcharts/dist/apexcharts.min.js'%}"></script>
<script src="{% static 'litepicker/dist/nocss/litepicker.js' %}"></script>
<script src="{% static 'litepicker/dist/plugins/ranges.js' %}"></script>
<link rel="stylesheet" href="{% static 'litepicker/dist/css/litepicker.css' %}">

<script src="{% static 'd3/d3.min.js' %}"></script>
<script src="{% static 'topojson/build/topojson.min.js' %}"></script>
<script src="{% static 'datamaps/dist/datamaps.world.min.js' %}"></script>
<script src="{% static 'dashboard/js/base.js' %}"></script>
<link rel="stylesheet" href="{% static 'dashboard/css/global.css' %}">
<link rel="stylesheet" href="{% static 'flag-icon-css/css/flag-icon.min.css' %}">
<link rel="stylesheet" href="{% static 'litepicker/dist/css/litepicker.css' %}">
{% block extra_head %}
{% endblock %}
</head>
Expand Down
59 changes: 59 additions & 0 deletions shynet/dashboard/templates/dashboard/includes/map_chart.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
{% load helpers %}

<div id="map-chart" class="relative"></div>
<script>
// Colors
const lightBlue = "#C4B5FD";
const highlightBlue = "#8B5CF6";
const white = "#ffffff";

// Data maps
const countryMapData = {};
const countryMapColors = {};
const countryMap = {
{% for country in countries %}"{{country.country|safe|datamap_id}}": {{country.count}},
{% endfor %}
};

// Max session count will be full opacity
const maxSessionCount = Math.max(...Object.values(countryMap));

// Color scale starts from opacity 0.1 - 1.0, 0 sessions gets opacity 0
const minPercentage = 0.1

// Loop over country map and transform data for Datamaps use
const keys = Object.keys(countryMap);
const length = keys.length;
for (let i = 0; i < length; i++) {
countryMapData[keys[i]] = {
sessionCount: countryMap[keys[i]],
color: `rgba(124, 58, 237, ${countryMap[keys[i]] === 0 ? 0 : minPercentage + (countryMap[keys[i]] / maxSessionCount * (1 - minPercentage))})`
};
countryMapColors[keys[i]] = countryMapData[keys[i]].color;
}

// Create datamap
const map = new Datamap({
element: document.getElementById('map-chart'),
projection: 'mercator',
responsive: true,
geographyConfig: {
borderColor: lightBlue,
highlightBorderColor: highlightBlue,
highlightBorderWidth: 1.5,
highlightFillColor: (geography) => geography.color || white,
highlightFillOpacity: 0.9,
popupTemplate: (geography, data) => '<div class="hoverinfo"><strong>' + geography.properties.name + '</strong>: ' + data.sessionCount + ' sessions</div>'

},
fills: {
defaultFill: white
},
data: countryMapData,
aspectRatio: 0.68
});
map.updateChoropleth(countryMapColors);

// Handle resize. TODO: debounce?
window.onresize = () => map.resize();
</script>
40 changes: 5 additions & 35 deletions shynet/dashboard/templates/dashboard/pages/service.html
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,10 @@
</tbody>
</table>
</div>
<div class="card ~neutral !low force-limited-height py-2 overflow-y-hidden">
<p class="text-sm font-semibold mx-2 p-2 border-b mb-2">Sessions by Geography</p>
{% include 'dashboard/includes/map_chart.html' with countries=stats.countries %}
</div>
<div class="card ~neutral !low limited-height py-2">
<table class="table">
<thead class="text-sm">
Expand Down Expand Up @@ -166,40 +170,6 @@
</tbody>
</table>
</div>
<div class="card ~neutral !low limited-height py-2">
<table class="table">
<thead class="text-sm">
<tr>
<th>Country</th>
<th class="rf">Sessions</th>
</tr>
</thead>
<tbody>
{% for country in stats.countries %}
<tr>
<td class="truncate w-full max-w-0 relative" title="{{country.country|country_name}}">
{% include 'dashboard/includes/bar.html' with count=country.count max=stats.countries.0.count total=stats.session_count %}
<div class="relative flex items-center">
<span class="flex-none {{country.country|flag_class}}"></span> {{country.country|country_name}}
</div>
</td>
<td>
<div class="flex justify-end items-center">
{{country.count|intcomma}}
<span class="text-xs rf" style="min-width: 48px">
({{country.count|percent:stats.session_count}})
</span>
</div>
</td>
</tr>
{% empty %}
<tr>
<td><span class="text-gray-600">No data yet...</span></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="card ~neutral !low limited-height py-2">
<table class="table">
<thead class="text-sm">
Expand Down Expand Up @@ -304,7 +274,7 @@
</table>
</div>
</div>
<div class="card ~neutral !low limited-height py-2">
<div class="card ~neutral !low py-2">
{% include 'dashboard/includes/session_list.html' %}
<hr class="sep h-8 md:h-12">
<a href="{% contextual_url 'dashboard:service_session_list' service.uuid %}" class="button ~neutral w-auto mb-2">View more
Expand Down
8 changes: 8 additions & 0 deletions shynet/dashboard/templatetags/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@ def country_name(isocode):
return "Unknown"


@register.filter
def datamap_id(isocode):
try:
return pycountry.countries.get(alpha_2=isocode).alpha_3
except:
return "UNKNOWN"


@register.simple_tag
def relative_stat_tone(
start,
Expand Down
3 changes: 3 additions & 0 deletions shynet/shynet/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,9 @@
"stimulus": [os.path.join("dist", "stimulus.umd.js")],
"inter-ui": [os.path.join("Inter (web)", "*")],
"@fortawesome": [os.path.join("fontawesome-free", "js", "all.min.js")],
"datamaps": [os.path.join("dist", "datamaps.world.min.js")],
"d3": ["d3.min.js"],
"topojson": [os.path.join("build", "topojson.min.js")],
"flag-icon-css": [
os.path.join("css", "flag-icon.min.css"),
os.path.join("flags", "*"),
Expand Down

0 comments on commit 9832de0

Please sign in to comment.