Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/ Add api data fetching in other components #205

Merged
merged 12 commits into from
Nov 22, 2023
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