Skip to content

Commit

Permalink
Develop functionality to show occurrence data per month (#3650)
Browse files Browse the repository at this point in the history
* Add chart data by month for site dashboard

* Implement switch dashboard frequency in frontend
  • Loading branch information
dimasciput authored Jan 1, 2024
1 parent 5a1aecd commit a8f28a1
Show file tree
Hide file tree
Showing 7 changed files with 450 additions and 39 deletions.
8 changes: 8 additions & 0 deletions bims/api_views/download_request.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from preferences import preferences
import ast

from rest_framework import status

from bims.download.csv_download import send_new_csv_notification
from bims.models.taxonomy import Taxonomy

Expand Down Expand Up @@ -30,6 +33,11 @@ def post(self, request):
input, creates the download request, and sends a notification email if
successful.
"""
if not request.user.is_authenticated:
return Response(
{'error': 'User needs to be logged in first'},
status=status.HTTP_401_UNAUTHORIZED)

resource_name = request.POST.get('resource_name')
resource_type = request.POST.get('resource_type')
purpose = request.POST.get('purpose')
Expand Down
37 changes: 35 additions & 2 deletions bims/api_views/location_site.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@
from bims.tasks.email_csv import send_csv_via_email
from bims.tasks.collection_record import download_gbif_ids
from bims.enums.ecosystem_type import ECOSYSTEM_WETLAND
from bims.api_views.location_site_dashboard import (
PER_YEAR_FREQUENCY,
PER_MONTH_FREQUENCY
)


class LocationSiteList(APIView):
Expand Down Expand Up @@ -173,15 +177,23 @@ def generate(self, filters, search_process):
collection_results = search.process_search()
site_id = filters['siteId']

# Indicates whether to show occurrence data per year (y) or per month (m)
data_frequency = filters.get('d', PER_YEAR_FREQUENCY)

self.iucn_category = dict(
(x, y) for x, y in IUCNStatus.CATEGORY_CHOICES)

self.origin_name_list = dict(
(x, y) for x, y in Taxonomy.CATEGORY_CHOICES
)

taxa_occurrence = self.site_taxa_occurrences_per_year(
collection_results)
if data_frequency == PER_MONTH_FREQUENCY:
taxa_occurrence = self.site_taxa_occurrences_per_date(
collection_results
)
else:
taxa_occurrence = self.site_taxa_occurrences_per_year(
collection_results)

category_summary = collection_results.exclude(
taxonomy__origin=''
Expand Down Expand Up @@ -403,6 +415,27 @@ def site_taxa_occurrences_per_year(self, collection_results):
result['occurrences_line_chart']['title'] = 'Occurrences'
return result

def site_taxa_occurrences_per_date(self, collection_results):
"""
Get occurrence data for charts based on the date sampled
:param collection_results: collection record queryset
:return: dict of taxa occurrence data for line graph
"""
taxa_occurrence_data = collection_results.values('collection_date').annotate(
count=Count('collection_date')
).order_by('collection_date')
result = dict()
result['occurrences_line_chart'] = {
'values': [],
'keys': [],
'title': 'Occurrences per Date Sampled'
}
for data in taxa_occurrence_data:
formatted_date = data['collection_date'].strftime('%Y-%m-%d')
result['occurrences_line_chart']['keys'].append(formatted_date)
result['occurrences_line_chart']['values'].append(data['count'])
return result

def get_biodiversity_data(self, collection_results):
biodiversity_data = {}
biodiversity_data['species'] = {}
Expand Down
162 changes: 128 additions & 34 deletions bims/api_views/location_site_dashboard.py
Original file line number Diff line number Diff line change
@@ -1,45 +1,46 @@
from bims.models.taxonomy import Taxonomy
from rest_framework.views import APIView, Response
from django.db.models import Case, F, Count, Value, When
from django.db.models.functions import ExtractYear
from django.db.models import Case, F, Count, Value, When, CharField
from django.db.models.functions import ExtractYear, ExtractMonth, Concat, LPad, Cast
from bims.api_views.search import CollectionSearch

PER_YEAR_FREQUENCY = 'y'
PER_MONTH_FREQUENCY = 'm'


class ChartDataApiView(APIView):
data_frequency = PER_YEAR_FREQUENCY

def format_data(self, collection_data, categories):
chart_data = {
'dataset_labels': [],
'dataset_labels': categories,
'labels': [],
'data': {}
'data': {category: [] for category in categories}
}
year_labels = []
category_with_data = dict()
for category in categories:
category_with_data[category] = []

if self.data_frequency == PER_MONTH_FREQUENCY:
year_labels = sorted({data['year'] for data in collection_data},
key=lambda x: (x.split('-')[1], x.split('-')[0]))
else:
year_labels = sorted({data['year'] for data in collection_data})

data_counts = {
year: {category: 0 for category in categories}
for year in year_labels}
for data in collection_data:
new_data = False
if data['year'] not in year_labels:
new_data = True
year_labels.append(data['year'])

if new_data and len(year_labels) > 1:
# Add 0 to previous
for category in categories:
if len(category_with_data[category]) < len(
year_labels) - 1:
category_with_data[category].insert(
len(year_labels) - 2, 0)

category_with_data[data['name']].append(data['count'])

chart_data['dataset_labels'] = categories
data_counts[data['year']][data['name']] += data['count']
for year in year_labels:
for category in categories:
chart_data['data'][category].append(
data_counts[year][category])

chart_data['labels'] = year_labels
chart_data['data'] = category_with_data

return chart_data

def chart_data_per_month(self, collection_results):
raise NotImplementedError

def chart_data(self, collection_results):
raise NotImplementedError

Expand All @@ -48,9 +49,16 @@ def categories(self, collection_results):

def get(self, request):
filters = request.GET.dict()

self.data_frequency = filters.get('d', PER_YEAR_FREQUENCY)

search = CollectionSearch(filters)
collection_results = search.process_search()
chart_data = self.chart_data(collection_results)
if self.data_frequency == PER_YEAR_FREQUENCY:
chart_data = self.chart_data(collection_results)
else:
chart_data = self.chart_data_per_month(collection_results)

categories = self.categories(collection_results)
result = self.format_data(
chart_data,
Expand All @@ -63,25 +71,47 @@ class OccurrencesChartData(ChartDataApiView):
"""
Get occurrence data categorized by origin for chart
"""

def categories(self, collection_results):
return list(collection_results.annotate(
name=Case(
When(taxonomy__origin='', then=Value('Unknown')),
When(taxonomy__origin__icontains='unknown',
then=Value('Unknown')),
default=F('taxonomy__origin'))
When(taxonomy__origin='', then=Value('Unknown')),
When(taxonomy__origin__icontains='unknown',
then=Value('Unknown')),
default=F('taxonomy__origin'))
).values_list(
'name', flat=True
).distinct('name'))

def chart_data_per_month(self, collection_results):
return collection_results.annotate(
y=ExtractYear('collection_date'),
m=LPad(Cast(ExtractMonth('collection_date'), CharField()), 2, Value('0')),
year=Concat(
'm', Value('-'), 'y',
output_field=CharField()
),
name=Case(
When(taxonomy__origin='', then=Value('Unknown')),
When(taxonomy__origin__icontains='unknown',
then=Value('Unknown')),
default=F('taxonomy__origin'))
).values(
'year', 'name'
).annotate(
count=Count('year'),
).values(
'year', 'name', 'count'
).order_by('collection_date')

def chart_data(self, collection_results):
return collection_results.annotate(
year=ExtractYear('collection_date'),
name=Case(
When(taxonomy__origin='', then=Value('Unknown')),
When(taxonomy__origin__icontains='unknown',
then=Value('Unknown')),
default=F('taxonomy__origin'))
When(taxonomy__origin='', then=Value('Unknown')),
When(taxonomy__origin__icontains='unknown',
then=Value('Unknown')),
default=F('taxonomy__origin'))
).values(
'year', 'name'
).annotate(
Expand All @@ -105,6 +135,25 @@ def categories(self, collection_results):
'name', flat=True
).distinct('name'))

def chart_data_per_month(self, collection_results):
return collection_results.annotate(
y=ExtractYear('collection_date'),
m=LPad(Cast(ExtractMonth('collection_date'), CharField()), 2, Value('0')),
year=Concat(
'm', Value('-'), 'y',
output_field=CharField()
),
name=Case(When(taxonomy__iucn_status__category__isnull=False,
then=F('taxonomy__iucn_status__category')),
default=Value('NE')),
).values(
'year', 'name'
).annotate(
count=Count('year'),
).values(
'year', 'name', 'count'
).order_by('collection_date')

def chart_data(self, collection_results):
return collection_results.annotate(
year=ExtractYear('collection_date'),
Expand All @@ -124,6 +173,7 @@ class LocationSitesTaxaChartData(ChartDataApiView):
"""
"""
taxa = None

def categories(self, collection_results):
if not self.taxa:
self.taxa = Taxonomy.objects.filter(
Expand All @@ -138,6 +188,30 @@ def categories(self, collection_results):
'name', flat=True
).distinct('name'))

def chart_data_per_month(self, collection_results):
if not self.taxa:
self.taxa = Taxonomy.objects.filter(
biologicalcollectionrecord__id__in=
collection_results.values_list('id', flat=True)
).distinct()[:25]
return collection_results.filter(
taxonomy__in=self.taxa
).annotate(
y=ExtractYear('collection_date'),
m=LPad(Cast(ExtractMonth('collection_date'), CharField()), 2, Value('0')),
year=Concat(
'm', Value('-'), 'y',
output_field=CharField()
),
name=F('taxonomy__scientific_name'),
).values(
'year', 'name'
).annotate(
count=Count('year'),
).values(
'year', 'name', 'count'
).order_by('collection_date')

def chart_data(self, collection_results):
if not self.taxa:
self.taxa = Taxonomy.objects.filter(
Expand All @@ -162,6 +236,7 @@ class LocationSitesEndemismChartData(ChartDataApiView):
"""
Return data for endemism bar chart.
"""

def categories(self, collection_results):
return list(collection_results.annotate(
name=Case(When(taxonomy__endemism__isnull=False,
Expand All @@ -171,6 +246,25 @@ def categories(self, collection_results):
'name', flat=True
).distinct('name'))

def chart_data_per_month(self, collection_results):
return collection_results.annotate(
y=ExtractYear('collection_date'),
m=LPad(Cast(ExtractMonth('collection_date'), CharField()), 2, Value('0')),
year=Concat(
'm', Value('-'), 'y',
output_field=CharField()
),
name=Case(When(taxonomy__endemism__isnull=False,
then=F('taxonomy__endemism__name')),
default=Value('Unknown'))
).values(
'year', 'name'
).annotate(
count=Count('year'),
).values(
'year', 'name', 'count'
).order_by('collection_date')

def chart_data(self, collection_results):
return collection_results.annotate(
year=ExtractYear('collection_date'),
Expand Down
10 changes: 8 additions & 2 deletions bims/static/js/non_requirejs/download_request_popup.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,14 @@ function showDownloadPopup(resource_type, resource_name, callback, auto_approved
callback(data['download_request_id']);
$downloadPopup.modal('hide');
$submitDownloadPopup.prop('disabled', false);
}, error: function () {
alert('Error submitting download request');
}, error: function (jqXHR, textStatus, errorThrown) {
let errorMessage = "Error submitting download request.";
if (jqXHR.responseJSON && jqXHR.responseJSON.error) {
errorMessage += " " + jqXHR.responseJSON.error;
} else if (textStatus) {
errorMessage += " " + textStatus;
}
alert(errorMessage);
$downloadPopup.modal('hide');
$submitDownloadPopup.prop('disabled', false);
}
Expand Down
Loading

0 comments on commit a8f28a1

Please sign in to comment.