Skip to content

Commit

Permalink
Merge pull request #205 from devinit/feature/fetch-data-from-api
Browse files Browse the repository at this point in the history
Feature/ Add api data fetching in other components
  • Loading branch information
ThatcherK authored Nov 22, 2023
2 parents ed1703f + 5c007f8 commit 689a124
Show file tree
Hide file tree
Showing 10 changed files with 141 additions and 118 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ module.exports = {
],
plugins: ['react', 'import', /* 'jsx-a11y', */ '@emotion'],
parserOptions: {
ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features
ecmaVersion: 2020, // Allows for the parsing of modern ECMAScript features
sourceType: 'module', // Allows for the use of imports
},
rules: {
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,9 @@ Here a full configuration for the maps.
name: 'Number of Primary Schools', // name of indicator as will appear in dropdown, tooltips and legend
description: 'Description Goes Here!', // a brief description of the indicator - appears above the legend.
url: '', // data source URL - CSV, JSON or API endpoint that returns JSON data
dataID: '', // data source ID - to fetch indicator data from API. The url property has precedence over dataID property
schoolLocationUrl: '', // school geolocation data source URL - CSV or JSON. Takes precedence over schoolLocationdataID if both are set.
schoolLocationdataID: '', // data source dataID to be used for fetching data via an API endpoint.
yearRange: [2016, 2021], // max and min year range - use to generate years for the year filter.
colours: ['#faa2c1', '#f06595', '#d6336c'], // a list of hex colours for each range of data
range: [5, 10, 15, 20], // groups values into sepecified ranges e.g < 5, 5 - 10, 10 - 15, 15 - 20, > 20 - the specified colours map to these ranges and highlight different values on the map.
Expand Down
2 changes: 1 addition & 1 deletion assets/core.js

Large diffs are not rendered by default.

3 changes: 1 addition & 2 deletions src/components/BaseMap/BaseMap.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const BaseMap = (props) => {

const renderLayers = () =>
Children.map(props.children, (child) =>
isValidElement(child) && child.type === BaseMapLayer ? cloneElement(child, { map: baseMap }) : null
isValidElement(child) && child.type === BaseMapLayer ? cloneElement(child, { map: baseMap }) : null,
);

return (
Expand Down Expand Up @@ -111,7 +111,6 @@ BaseMap.propTypes = {
onLoad: PropTypes.func,
options: PropTypes.object,
children: PropTypes.node,
locationData: PropTypes.object,
};

// eslint-disable-next-line import/prefer-default-export
Expand Down
9 changes: 5 additions & 4 deletions src/core/KeyFacts.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import { createRoot } from 'react-dom/client';
import fetchData from '../utils/data';
import fetchData, { fetchDataFromAPI } from '../utils/data';
import KeyFacts from './components/KeyFacts';
import NoDataCentered from './components/NoDataCentered';

Expand All @@ -22,8 +22,9 @@ const renderViz = (className) => {
dichart.showLoading();
const { keyFacts, location } = window.DIState.getState;

if (keyFacts && keyFacts.url) {
fetchData(keyFacts.url)
if (keyFacts?.url || keyFacts?.dataID) {
const dataFetchFunction = keyFacts?.url ? fetchData : fetchDataFromAPI;
dataFetchFunction(keyFacts?.url || keyFacts?.dataID)
.then((data) => {
if (Array.isArray(data)) {
root.render(<KeyFacts data={data} options={keyFacts} location={location} />);
Expand All @@ -32,7 +33,7 @@ const renderViz = (className) => {
} else {
window.console.error(
'Invalid data shape. Expected an array or an object with a results property.',
data
data,
);
root.render(<NoDataCentered />);
}
Expand Down
18 changes: 5 additions & 13 deletions src/core/components/DistrictMap/DistrictMap.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,7 @@ import { getLocationStyles } from '../../../components/BaseMap/utils';
import { DistrictMapContext } from '../../context';
import DistrictMapSidebar from '../DistrictMapSidebar';
import useMap from '../hooks/DistrictMap';
import {
COLOURED_LAYER,
coreLayer,
getRawFilterOptions,
getTopicById,
onAddLayer,
getSchoolMarkers,
} from './utils/index';
import { COLOURED_LAYER, coreLayer, getRawFilterOptions, getTopicById, onAddLayer } from './utils/index';

const renderLayers = (loading, data, location, layerConfig, mapConfig) => {
const hiddenLayers = [layerConfig].map((layer) => (
Expand Down Expand Up @@ -57,7 +50,7 @@ const renderLayers = (loading, data, location, layerConfig, mapConfig) => {
'fill-outline-color': '#ffffff',
}}
onAdd={onAddHighlightLayer}
/>
/>,
);
}

Expand All @@ -75,7 +68,7 @@ const renderLayers = (loading, data, location, layerConfig, mapConfig) => {
'fill-outline-color': '#ffffff',
}}
onAdd={onAddHighlightLayer}
/>
/>,
);
};

Expand Down Expand Up @@ -120,7 +113,7 @@ const DistrictMap = (props) => {
setOptions,
} = useMap(
props.location,
props.configs.formatter ? { ...coreLayer, formatter: props.configs.formatter } : coreLayer
props.configs.formatter ? { ...coreLayer, formatter: props.configs.formatter } : coreLayer,
);
useEffect(() => {
// set map options using their caption values
Expand Down Expand Up @@ -182,7 +175,6 @@ const DistrictMap = (props) => {
{renderDashboardButton()}
<BaseMap
accessToken="pk.eyJ1IjoiZWR3aW5tcCIsImEiOiJjazFsdHVtcG0wOG9mM2RueWJscHhmcXZqIn0.cDR43UvfMaOY9cNJsEKsvg"
locationData={getSchoolMarkers(props.location.name)}
options={{
style: coreLayer.style,
center: coreLayer.center,
Expand All @@ -200,7 +192,7 @@ const DistrictMap = (props) => {
data,
props.location,
props.configs.formatter ? { ...coreLayer, formatter: props.configs.formatter } : coreLayer,
activeIndicator ? { range: activeIndicator.range, colours: activeIndicator.colours } : {}
activeIndicator ? { range: activeIndicator.range, colours: activeIndicator.colours } : {},
)}
</BaseMap>
</div>
Expand Down
186 changes: 102 additions & 84 deletions src/core/components/DistrictMap/utils/index.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { groupBy } from 'lodash';
import { flyToLocation, getProperLocationName } from '../../../../components/BaseMap/utils';
import { filterData } from '../../../utils';
import fetchData from '../../../../utils/data';
import fetchData, { fetchDataFromAPI } from '../../../../utils/data';

const activeBranch = 'dev'
export const COLOURED_LAYER = 'highlight';
export const coreLayer = {
type: 'shapefile',
Expand Down Expand Up @@ -93,97 +92,116 @@ export const processData = (data, indicator, year) => {
};

const processCoordinates = (data) => {
const coordinates = data.split(',')
const coordinates = data.split(',');

return coordinates.map((item) => parseFloat(item))
}

const MASINDI_EXCLUDE_LIST = ['Waiga Primary School','Gods Mercy Primary School', 'Bukeeka COU Primary School',]
const KAYUNGA_EXCLUDE_LIST = ['Bukeeka COU Primary School', 'Kungu CU Primary School', 'King Jesus Nursery And Primary School', 'Nile View Primary School',
'Imam Hassan Primary School Maligita', 'Bright Future Nursery And Primary School Kangulumira'
]
return coordinates.map((item) => parseFloat(item));
};

export const getSchoolMarkers = (district, schoolSpecs) => {
const dataUrl = `https://raw.githubusercontent.com/devinit/ug-district-dashboard-viz/${activeBranch}/public/assets/data/${district.toLowerCase()}/schools-locations.csv`
const MASINDI_EXCLUDE_LIST = ['Waiga Primary School', 'Gods Mercy Primary School', 'Bukeeka COU Primary School'];
const KAYUNGA_EXCLUDE_LIST = [
'Bukeeka COU Primary School',
'Kungu CU Primary School',
'King Jesus Nursery And Primary School',
'Nile View Primary School',
'Imam Hassan Primary School Maligita',
'Bright Future Nursery And Primary School Kangulumira',
];

export const getSchoolMarkers = (district, schoolSpecs, dataUrl, dataID) => {
const finalGeoJSON = {
type: 'FeatureCollection',
features: []
}
if (!schoolSpecs || !dataUrl) return finalGeoJSON
if (dataUrl) {
fetchData(dataUrl).then((data) => {
const filteredData = data.filter((row) => district === 'Masindi'? !MASINDI_EXCLUDE_LIST.includes(row.school_name) : !KAYUNGA_EXCLUDE_LIST.includes(row.school_name) )
if (schoolSpecs.ownership === 'all')
{
filteredData.filter((d)=> d.level === schoolSpecs.level ).forEach((item) => {
if (item.gps_coordinates) {
const itemCoordinates = processCoordinates(item.gps_coordinates)

if (itemCoordinates) {
finalGeoJSON.features.push({
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [itemCoordinates[1], itemCoordinates[0]]
},
properties: {
level: item.level,
ownership: item.ownership,
name: item.school_name,
parish: item.parish,
features: [],
};
const dataVariable = dataUrl || dataID;
const dataFetchFunction = dataUrl ? fetchData : fetchDataFromAPI;
if (!schoolSpecs || !dataVariable) return finalGeoJSON;
if (dataVariable) {
dataFetchFunction(dataVariable)
.then((data) => {
const filteredData = data.filter((row) =>
district === 'Masindi'
? !MASINDI_EXCLUDE_LIST.includes(row.school_name)
: !KAYUNGA_EXCLUDE_LIST.includes(row.school_name),
);
if (schoolSpecs.ownership === 'all') {
filteredData
.filter((d) => d.level === schoolSpecs.level)
.forEach((item) => {
if (item.gps_coordinates) {
const itemCoordinates = processCoordinates(item.gps_coordinates);

if (itemCoordinates) {
finalGeoJSON.features.push({
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [itemCoordinates[1], itemCoordinates[0]],
},
properties: {
level: item.level,
ownership: item.ownership,
name: item.school_name,
parish: item.parish,
},
});
}
})
}
}
})
} else {
filteredData.filter((d)=> d.level === schoolSpecs.level && d.ownership === schoolSpecs.ownership ).forEach((item) => {
if (item.gps_coordinates) {
const itemCoordinates = processCoordinates(item.gps_coordinates)

if (itemCoordinates) {
finalGeoJSON.features.push({
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [itemCoordinates[1], itemCoordinates[0]]
},
properties: {
level: item.level,
ownership: item.ownership,
name: item.school_name,
parish: item.parish,
}
});
} else {
filteredData
.filter((d) => d.level === schoolSpecs.level && d.ownership === schoolSpecs.ownership)
.forEach((item) => {
if (item.gps_coordinates) {
const itemCoordinates = processCoordinates(item.gps_coordinates);

if (itemCoordinates) {
finalGeoJSON.features.push({
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [itemCoordinates[1], itemCoordinates[0]],
},
properties: {
level: item.level,
ownership: item.ownership,
name: item.school_name,
parish: item.parish,
},
});
}
})
}
}
})
}

}).catch((error) => {
console.log(error)
})

return finalGeoJSON
}
});
}
})
.catch((error) => {
console.log(error);
});

return finalGeoJSON;
}

return finalGeoJSON
}
return finalGeoJSON;
};

export function schoolLevel(indicator) {
if (indicator === 'numberOfPrimarySchools')
{return {level:'Primary', ownership: 'all'}}
if(indicator === 'numberOfSecondarySchools')
{return {level:'Secondary', ownership: 'all'}}
if(indicator === 'numberOfGovernmentPrimarySchools')
{return {level:'Primary', ownership: 'Government'}}
if(indicator === 'numberOfPrivatePrimarySchools')
{return {level:'Primary', ownership: 'Private'}}
if(indicator === 'numberOfGovernmentSecondarySchools')
{return {level:'Secondary', ownership: 'Government'}}
if(indicator === 'numberOfPrivateSecondarySchools')
{return {level:'Secondary', ownership: 'Private'}}

return ''
if (indicator === 'numberOfPrimarySchools') {
return { level: 'Primary', ownership: 'all' };
}
if (indicator === 'numberOfSecondarySchools') {
return { level: 'Secondary', ownership: 'all' };
}
if (indicator === 'numberOfGovernmentPrimarySchools') {
return { level: 'Primary', ownership: 'Government' };
}
if (indicator === 'numberOfPrivatePrimarySchools') {
return { level: 'Primary', ownership: 'Private' };
}
if (indicator === 'numberOfGovernmentSecondarySchools') {
return { level: 'Secondary', ownership: 'Government' };
}
if (indicator === 'numberOfPrivateSecondarySchools') {
return { level: 'Secondary', ownership: 'Private' };
}

return '';
}
13 changes: 7 additions & 6 deletions src/core/components/Selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import ChartFilters from '../../components/ChartFilters';
import Select from '../../components/Select';
import fetchData from '../../utils/data';
import fetchData, { fetchDataFromAPI } from '../../utils/data';

const Selectors = (props) => {
const [selectors, setSelectors] = useState([]);
Expand All @@ -21,8 +21,9 @@ const Selectors = (props) => {
};
item.options = selector.defaultValue ? [selector.defaultValue] : [];
let data = selector.data || [];
if (selector.url) {
data = await fetchData(selector.url);
if (selector.url || selector.dataID) {
const dataFetchFunction = selector.url ? fetchData : fetchDataFromAPI;
data = await dataFetchFunction(selector.url || selector.dataID);
}
item.options = item.options.concat(
data.reduce((options, curr) => {
Expand All @@ -34,11 +35,11 @@ const Selectors = (props) => {
}

return options;
}, [])
}, []),
);

return item;
})
}),
)
.then(setSelectors)
.catch((error) => window.console.log(error));
Expand Down Expand Up @@ -77,7 +78,7 @@ Selectors.propTypes = {
defaultValue: PropTypes.shape({ value: PropTypes.string, label: PropTypes.string }),
labelProperty: PropTypes.string.isRequired,
valueProperty: PropTypes.string.isRequired,
})
}),
),
onChange: PropTypes.func,
className: PropTypes.string,
Expand Down
Loading

0 comments on commit 689a124

Please sign in to comment.