Fullscreen map
The resource proposes a full screen map visualization. Ideal for datasets which key visuazation is the map. This resource contains filters and analytics drawers to propose filtering features and charts or KPI visualizations on top of the map. The example here uses air quality data.
Dataset in use: openaq
(See it on public domain)
Fields in use:
location | measurements_sourcename | measurements_parameter (numeric) | measurements_lastupdated (datetime) | measurements_value (numeric) | measurements_sourcename |
---|---|---|---|---|---|
Wien Belgradplatz | EEA Austria | NO2 | 2016-12-12T09:00:00+00:00 | 19.0 | EEA Austria |
Graz Don Bosco | EEA Austria | SO2 | 2016-12-12T09:00:00+00:00 | 5.0 | EEA Austria |
Targinie | Australia - Queensland | PM10 | 2021-12-13T10:00:00+00:00 | 16.2 | Australia - Queensland |
Rocklea | Australia - Queensland | PM10 | 2021-12-13T10:00:00+00:00 | 10.9 | Australia - Queensland |
<ods-dataset-context context="ctx"
ctx-dataset="openaq"
ctx-domain="public"
ctx-parameters="{'q.forcefilter':'measurements_value < 1000'}"
ng-init="activemaplayer = 'measurements';
search = {'query' : ''};">
{{
nb_active_filters =
(ctx.parameters["refine.location"].length>=0?1:0) +
(ctx.parameters["refine.measurements_sourcename"].length>=0?1:0) +
(ctx.parameters["refine.measurements_parameter"].length>=0?1:0) ; ""
}}
<div class="left"
ng-init="leftmodal = false">
<div href="#"
class="filter-btn"
ng-class="{'filter-btn--active': leftmodal}"
ng-click="leftmodal = !leftmodal">
Filters
<span class="filtered" ng-if="nb_active_filters > 0">
({{ nb_active_filters }})
</span>
</div>
<div class="cl-modal" ng-class="{'cl-modal-active':leftmodal}">
<div class="cl-modal__header">
<div href="#"
class="closebtn"
ng-click="leftmodal = false">
<span><</span> Close
</div>
</div>
<div class="cl-modal__content">
<div class="ods-filters-placeholder" ods-sticky-scrollable="">
<div class="">
<h1 class="ods-filters__count">
<span class="ods-filters__count-number ng-binding">{{ctx.nhits | number}}</span>
<span class="ods-filters__count-units">Air measurements</span>
</h1>
<ods-filter-summary context="ctx"
exclude="refine.measurements_parameter,q.forcefilter"></ods-filter-summary>
<h1 class="ods-filters__filters">
Filters
</h1>
<ods-facets context="ctx">
<h3>
Polluant
</h3>
<ods-facet name="measurements_parameter"
ng-click="activemaplayer = 'measurements'"></ods-facet>
<h3>
Location
</h3>
<ods-facet name="location"></ods-facet>
</ods-facets>
</div>
</div>
</div>
</div>
</div>
<div class="right"
ng-init="rightmodal">
<div href="#"
class="filter-btn"
ng-class="{'filter-btn--active': rightmodal}"
ng-click="rightmodal = !rightmodal">
Analytics
<!--span class="filtered" ng-if="nb_active_filters > 0">
({{ nb_active_filters }})
</span-->
</div>
<div class="cl-modal" ng-class="{'cl-modal-active':rightmodal}">
<div class="cl-modal__header">
<div href="#"
class="closebtn"
ng-click="rightmodal = false">
Close <span>></span>
</div>
</div>
<div class="cl-modal__content">
<h3>
Average {{ activemaplayer }} evolution over months
</h3>
<ods-chart timescale="year" display-legend="false" align-month="true">
<ods-chart-query context="ctx" field-x="measurements_lastupdated" maxpoints="5" timescale="month">
<ods-chart-serie expression-y="measurements_value" chart-type="spline" function-y="AVG"
label-y="Average value evolution over time" color="#CF4626"
display-values="true" display-stack-values="false" logarithmic="false"
scientific-display="true">
</ods-chart-serie>
</ods-chart-query>
</ods-chart>
<h3>
Most measured pollutant
</h3>
<ods-chart display-legend="false" align-month="true">
<ods-chart-query context="ctx" field-x="measurements_parameter" maxpoints="5" sort="serie1-1"
category-colors="{'NO2':'#F4F2ED','O3':'#6a83ab','PM10':'#CF4626','SO2':'#45423E','PM2.5':'#CF4626','CO':'#6B83AB','BC':'#45423E'}">
<ods-chart-serie expression-y="measurements_value" chart-type="pie" function-y="COUNT"
label-y="Most measured pollutant"
color="range-Paired" display-values="false" display-stack-values="false"
logarithmic="false" scientific-display="true">
</ods-chart-serie>
</ods-chart-query>
</ods-chart>
<h3>
Most active source
</h3>
<ods-chart display-legend="false" align-month="true">
<ods-chart-query context="ctx" field-x="measurements_sourcename" maxpoints="5" sort="serie1-1">
<ods-chart-serie expression-y="measurements_value" chart-type="column" function-y="COUNT"
label-y="Most active source" color="#CF4626" display-values="true"
display-stack-values="false" logarithmic="false" scientific-display="true">
</ods-chart-serie>
</ods-chart-query>
</ods-chart>
<h3>
Most active location
</h3>
<ods-chart display-legend="false" align-month="true">
<ods-chart-query context="ctx" field-x="location" maxpoints="5" sort="serie1-1">
<ods-chart-serie expression-y="measurements_value" chart-type="pie" function-y="COUNT"
label-y="Most active source" display-values="false"
display-stack-values="false" logarithmic="false" scientific-display="true"
color="#cf4526">
</ods-chart-serie>
</ods-chart-query>
</ods-chart>
</div>
<!--div class="cl-modal__footer">
Recherchez, cliquez pour sélectionner et filtrer, puis revenez pour voir votre rapport mis à jour
</div-->
</div>
</div>
<div class="search-module">
<div class="input-holder">
<div class="zoom-before-input"><i class="fa fa-search" aria-hidden="true"></i></div>
<input type="text"
name="xzxzzxzxzxzxzxzxzxxzxzzxx"
autocomplete="off"
placeholder="Search..."
ng-model="search['query']"
ng-model-options="{ updateOn: 'keyup',
debounce: { 'default': 500, 'blur': 0 }}"
ng-change="ctx.parameters['q'] = search['query'];"/>
<div class="clear-button"
ng-if="search['query']"
ng-click="search.query = undefined;
ctx.parameters['q'] = null">
<i class="fa fa-ban" aria-hidden="true"></i>
</div>
</div>
<div class="input-holder dark"
ods-tooltip="<span style='font-size:1.4em; font-weight: 200;'>Measurements</strong>"
ng-click="activemaplayer = 'measurements';
ctx.parameters['refine.measurements_parameter'] = undefined;">
<i class="fa fa-map-marker" aria-hidden="true"></i>
</div>
<div class="input-holder red"
ods-tooltip="<span style='font-size:1.4em; font-weight: 200;'>PM10 Heatmap</strong>"
ng-click="activemaplayer = 'PM10';
ctx.parameters['refine.measurements_parameter'] = 'PM10';">
<ods-picto url="'https://public.opendatasoft.com/static/pictos/img/set-v3/pictos/fire.svg'"
color="'#f4f2ec'"></ods-picto>
</div>
<div class="input-holder blue"
ods-tooltip="<span style='font-size:1.4em; font-weight: 200;'>Ozone Heatmap</strong>"
ng-click="activemaplayer = 'O3';
ctx.parameters['refine.measurements_parameter'] = 'O3';">
<ods-picto url="'https://public.opendatasoft.com/static/pictos/img/set-v3/pictos/fire.svg'"
color="'#f4f2ec'"></ods-picto>
</div>
</div>
<div class="kpi-module"
ng-if="ctx.parameters['refine.measurements_parameter']"
ods-aggregation="min,max,avg"
ods-aggregation-min-context="ctx"
ods-aggregation-min-function="MIN"
ods-aggregation-min-expression="measurements_value"
ods-aggregation-max-context="ctx"
ods-aggregation-max-function="MAX"
ods-aggregation-max-expression="measurements_value"
ods-aggregation-avg-context="ctx"
ods-aggregation-avg-function="AVG"
ods-aggregation-avg-expression="measurements_value">
<div class="kpi" ng-if="activemaplayer != 'measurements'"
ng-class="{
'blue':activemaplayer=='O3',
'red':activemaplayer=='PM10'
}">
<div class="kpi-title">Min {{ activemaplayer }}</div>
<div class="kpi-title--unit">Minimum value measurement</div>
<div class="kpi-value">{{ min | number : 1 }}</div>
</div>
<div class="kpi" ng-if="activemaplayer != 'measurements'"
ng-class="{
'blue':activemaplayer=='O3',
'red':activemaplayer=='PM10'
}">
<div class="kpi-title">Avg {{ activemaplayer }}</div>
<div class="kpi-title--unit">Average value measurement</div>
<div class="kpi-value">{{ avg | number : 1 }}</div>
</div>
<div class="kpi" ng-if="activemaplayer != 'measurements'"
ng-class="{
'blue':activemaplayer=='O3',
'red':activemaplayer=='PM10'
}">
<div class="kpi-title">Max {{ activemaplayer }}</div>
<div class="kpi-title--unit">Maximum value measurement</div>
<div class="kpi-value">{{ max | number : 1 }}</div>
</div>
</div>
<ods-map no-refit="true" scroll-wheel-zoom="true" display-control="false"
display-control-single-layer="false"
toolbar-fullscreen="false"
basemap="mapbox.light">
<ods-map-layer context="ctx"
show-if="activemaplayer == 'measurements'"
color="#45423E" picto="ods-connected_temp" show-marker="false" display="auto" shape-opacity="0.5"
point-opacity="1" border-color="#FFFFFF" border-opacity="1" border-size="1"
border-pattern="solid"
caption="false" caption-picto-color="#E5E5E5" title="Air Quality measurements" size="5"
size-min="2" size-max="6" size-function="linear">
<ul style="display: block; list-style-type: none; padding:0; margin:0;">
<li><strong style="font-size:17px;">{{ record.fields.location }}</strong></li>
<li>{{ record.fields.measurements_lastupdated | date:'medium'}}</li>
<li><strong><i
class="fa fa-leaf"></i> {{ record.fields.measurements_parameter }}: {{ record.fields.measurements_value }} {{ record.fields.measurements_unit }}</strong>
</li>
<li>(source: {{ record.fields.measurements_sourcename }})</li>
</ul>
</ods-map-layer>
<ods-map-layer context="ctx"
show-if="activemaplayer == 'PM10'"
color-gradient="{'1':'#FFFFFF','0.3':'#FF0000','0.6':'#FFFF00'}" picto="ods-connected_temp"
show-marker="false" display="heatmap" radius="1" function="AVG" expression="measurements_value"
shape-opacity="0.5" point-opacity="1" border-color="#FFFFFF" border-opacity="1" border-size="1"
border-pattern="solid"
caption="false" caption-picto-color="#E5E5E5" title="PM10 Heatmap"
size-function="log"></ods-map-layer>
<ods-map-layer context="ctx"
show-if="activemaplayer == 'O3'"
color-gradient="{'1':'#00FAFA','0.2':'#000000','0.4':'#183567','0.6':'#2E649E','0.8':'#17ADCB'}"
picto="ods-connected_temp" show-marker="false" display="heatmap" radius="1" function="AVG"
expression="measurements_value" shape-opacity="0.5" point-opacity="1" border-color="#FFFFFF"
border-opacity="1" border-size="1" border-pattern="solid"
caption="false" caption-picto-color="#E5E5E5" title="O3 Heatmap"
size-function="log"></ods-map-layer>
</ods-map>
</ods-dataset-context>
body, h1, h2, h3 {
color: #45423E;
}
main, body {
margin: 0;
padding: 0;
}
.main--page {
margin-top: 0px;
}
.ods-front-header, .ods-front-footer, .ods-content:after, footer {
display: none;
}
ods-dataset-context {
position: relative;
}
.odswidget.odswidget-map {
width: 100%;
height: 100vh;
}
.leaflet-popup-content {
width: 350px !important;
}
.odswidget-record-image {
text-align: center;
}
.ods-filters__filters, .ods-filters__count {
color: #f4f2ec;
background-color: #6B83AB;
}
ods-facets h3 {
color: #45423c;
}
rect.highcharts-background {
fill: transparent !important;
}
.highcharts-yaxis {
display: none;
}
/* Button */
.filter-btn {
position: absolute;
top: 225px;
left: -15px;
transform: rotate(-90deg);
display: flex;
align-items: center;
justify-content: center;
font-size: 1.4em;
width: 200px;
height: 40px;
padding: 11px;
border: #F4F2ED;
border-radius: 0px 0px 4px 4px;
transition: ease-in 0.3s;
box-shadow: 4px 4px 12px -1px #94928c;
text-decoration: none;
z-index: 3;
color: #f4f2ec;
background-color: #6B83AB;
}
.filter-btn--active {
left: 315px;
}
.filter-btn:hover {
box-shadow: 0 10px 26px 5px #a7a49d;
}
span.filtered {
color: red;
margin: 0px 4px;
font-weight: 600;
}
/* MODAL */
.cl-modal {
display: flex;
flex-direction: column;
height: -webkit-calc(100vh - 195px);
height: -moz-calc(100vh - 195px);
height: calc(100vh - 195px);
margin-top: 105px;
left: 45px;
width: 20px;
position: absolute;
background-color: #f4f2ec;
border-radius: 4px;
border-left: 8px solid #6B83AB;
z-index: 3;
overflow: hidden; /* Disable horizontal scroll */
transition: ease-in 0.3s;
}
.cl-modal-active {
width: 350px;
}
.cl-modal__header,
.cl-modal__footer {
height: 65px;
background-color: #F4F2ED;
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: opacity 0s;
}
.cl-modal-active .cl-modal__header, .cl-modal-active .cl-modal__footer {
opacity: 1;
transition-delay: 0.3s;
}
.cl-modal__header {
border-bottom: 1px solid #bbbbbb;
}
.cl-modal__footer {
border-top: 1px solid #bbbbbb;
}
.cl-modal__content {
height: 100%;
overflow-y: auto;
padding: 25px;
opacity: 0;
transition: opacity 0s;
}
.cl-modal-active .cl-modal__content {
opacity: 1;
transition-delay: 0.3s;
}
/* The button used to close the modal */
.closebtn {
font-size: 2.5em;
font-weight: 200;
}
.closebtn span {
font-weight: 300;
}
.closebtn:hover,
.closebtn:focus {
color: #000;
text-decoration: none;
cursor: pointer;
}
/* specific right modal */
.right {
position: relative;
}
.right .filter-btn {
right: 0px;
left: initial;
transform: rotate(90deg);
background-color: #CF4626;
top: -webkit-calc(100vh - 180px);
top: -moz-calc(100vh - 180px);
top: calc(100vh - 180px);
}
.right .cl-modal {
right: 60px;
left: initial;
border-left: none;
border-right: 7px solid #CF4626;
height: -webkit-calc(100vh - 130px);
height: -moz-calc(100vh - 130px);
height: calc(100vh - 130px);
margin-top: 100px;
}
.right .filter-btn--active {
left: initial;
right: 430px;
}
.right .cl-modal-active {
width: 450px;
}
/* specific because charts are loaded inside */
.right .cl-modal__content {
width: 450px;
}
/* SEARCH MODULE */
.search-module {
position: fixed;
left: 0;
right: 0;
margin-left: auto;
margin-right: auto;
z-index: 2;
top: 20px;
width: 400px;
font-size: 1.1em;
font-weight: 300;
display: flex;
}
.zoom-before-input {
color: #ababab;
font-size: 1.3em;
}
.input-holder, .odswidget-timerange__from, .odswidget-timerange__to {
display: flex;
align-items: center;
justify-content: center;
min-width: 55px; /* used for ODS picto */
font-size: 1.4em; /* used for fontawesome picto */
max-height: 55px;
border: 1px solid #e4e4e4;
padding: 11px;
margin: 3px;
border-radius: 4px;
box-shadow: 1px 1px 5px gainsboro;
background-color: #f4f2ec;
z-index: 2;
}
.input-holder:hover {
box-shadow: 1px 1px 10px 3px #74716c;
}
.input-holder input, .odswidget-timerange__from input, .odswidget-timerange__to input {
border: none;
width: 100%;
margin-left: 8px;
background-color: #f4f2ec;
}
.input-holder input:focus, .odswidget-timerange__from input:focus, .odswidget-timerange__to input:focus {
outline: 0;
}
.input-holder .odswidget-picto {
height: 30px;
width: 30px;
}
.clear-button {
font-size: 1.3em;
}
.blue {
border: 1px solid #6a83ab;
background-color: #6a83ab;
color: #f4f2ec;
}
.red {
border: 1px solid #cf4526;
background-color: #cf4526;
color: #f4f2ec;
}
.dark {
border: 1px solid #45423E;
background-color: #45423E;
color: #f4f2ec;
}
/* KPI MODULE */
.kpi-module {
position: fixed;
left: 0;
right: 0;
margin-left: auto;
margin-right: auto;
z-index: 2;
bottom: 20px;
width: 600px;
display: flex;
}
.kpi {
width: 100%;
margin: 5px;
padding: 0px 7px;
background-color: #45423Eb5;
border: 2px solid #45423Ed4;
border-radius: 4px;
color: #f4f2ec;
}
.kpi.blue {
border: 2px solid #6a83abd4;
background-color: #6a83abb5;
}
.kpi.red {
border: 2px solid #cf4526d4;
background-color: #cf4526b5;
}
.kpi-title {
font-weight: 600;
font-size: 1.3em;
}
.kpi-title--unit {
font-weight: 100;
font-size: 0.8em;
}
.kpi-value {
font-weight: 400;
font-size: 2em;
text-align: center;
}