Skip to content

Commit

Permalink
Merge branch 'food-group-map' into sand-soil
Browse files Browse the repository at this point in the history
  • Loading branch information
mwbernard committed Jan 18, 2024
2 parents 80c3c22 + a634ff5 commit f4ee2ae
Show file tree
Hide file tree
Showing 29 changed files with 11,337 additions and 10,370 deletions.
20,472 changes: 10,236 additions & 10,236 deletions vacs-map-app/public/data/crop-yields-mean-models.csv

Large diffs are not rendered by default.

106 changes: 102 additions & 4 deletions vacs-map-app/scripts/format-yields-data.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,17 @@ const source_filename = 'public/data-raw/crop-yields-mean-models.csv';
const yields_target_filename = 'public/data/crop-yields-mean-models.csv';
const grid_target_filename = 'public/data/grid.csv';

const file = fs.readFileSync(source_filename, 'utf-8')
const crop_info_filename = 'public/data/crop-info-general.csv';

const yields = d3.csvParse(file, (d, i) => {
const yields_file = fs.readFileSync(source_filename, 'utf-8')
const crop_info_file = fs.readFileSync(crop_info_filename, 'utf-8')

const cropInfo = d3.csvParse(crop_info_file, d3.autoType);

// ---------------------------------------
// SPLIT SOURCE YIELDS FILE INTO TWO, ONE THAT STORES THE DATA AND ASSOCIATES
// EACH GRID CELL WITH AND ID, AND ONE THAT STORES THE GRID IDS AND COORDS
const yields = d3.csvParse(yields_file, (d, i) => {
let obj = Object.fromEntries(
Object.entries(d).slice(1).map(([k, v]) => {
return [k, v && v !== '' ? +v : null]
Expand All @@ -17,7 +25,7 @@ const yields = d3.csvParse(file, (d, i) => {
return obj;
})

const grid = d3.csvParse(file, (d, i) => {
const grid = d3.csvParse(yields_file, (d, i) => {
const regExp = /\(([^)]+)\)/g;
const coords = [...d.geometry.matchAll(regExp)].flat()[1]
return {
Expand All @@ -26,7 +34,97 @@ const grid = d3.csvParse(file, (d, i) => {
Y: coords.split(' ')[1]
}
})
// ---------------------------------------
// PRECALCULATE YIELD RATIOS FOR EACH GRID CELL AND CONSOLIDATE THE INFORMATION STORED
const yieldKeys = Object.keys(yields[0]).filter((k) => k.startsWith('yield'))

// add yield ratio columns for all crops and scenarios
const yieldsWithRatios = yields.map((d, i) => {
const rowWithYields = Object.fromEntries(Object.entries(d).filter(([k, v]) => v !== null))

yieldKeys.forEach((k) => {
const [_, crop, timeframe, scenario] = k.split('_')
if (timeframe === 'historical') return

// NB: abbreviating these could get more performance improvements
const historicalKey = ['yield', crop, 'historical'].join('_')

if (!yieldKeys.includes(historicalKey)) return

const yieldRatioKey = ['yieldratio', crop, timeframe, scenario].join('_')

let yieldRatioValue = null
if (
rowWithYields[k] !== null &&
rowWithYields[historicalKey] !== null &&
rowWithYields[historicalKey]
) {
yieldRatioValue =
(rowWithYields[k] - rowWithYields[historicalKey]) / rowWithYields[historicalKey]
}

if (yieldRatioValue === null) return
rowWithYields[yieldRatioKey] = yieldRatioValue
})

return rowWithYields
})

// ---------------------------------------
// PRECACLULATE THE INFO FOR CROP GROUP MAPS
const cropGroups = Array.from(new Set(cropInfo.map((d) => d.crop_group)))
const futureScenarios = ['future_ssp126', 'future_ssp370']

const groupYieldRatioKeysHash = Object.fromEntries(
d3.cross(futureScenarios, cropGroups).map(([scenario, cropGroup]) => {
return [`${cropGroup}_${scenario}`,
cropInfo
.filter((c) => c.crop_group === cropGroup)
.map((c) => {
return ['yieldratio', c.id, scenario].join('_')
})
];
})
);

const yieldsWithCropGroups = yieldsWithRatios.map((row, i) => {
d3.cross(futureScenarios, cropGroups)
.forEach(([scenario, cropGroup]) => {
const groupKey = [cropGroup, scenario].join('_')
const groupYieldRatioKeys = groupYieldRatioKeysHash[groupKey];
const rowHasYieldRatios = Object.keys(row)
.some((k) => groupYieldRatioKeys.includes(k))
if (!rowHasYieldRatios) {
return
}
const obj = {
maxCrop: 'none',
minCrop: 'none',
maxVal: null,
minVal: null
}

// TODO might be faster just to pull out the relevant entries and sort
// them?
groupYieldRatioKeys.forEach((k) => {
if (row[k] === null) return;
if (row[k] > obj.maxVal) {
obj.maxVal = row[k]
obj.maxCrop = k.split('_')[1]
}
if (row[k] < obj.minVal) {
obj.minVal = row[k]
obj.minCrop = k.split('_')[1]
}
})

Object.entries(obj).forEach(([k, v]) => {
row[groupKey+"_"+k] = v;
})
})
return { ...row };
})

fs.writeFileSync(yields_target_filename, d3.csvFormat(yields));
fs.writeFileSync(yields_target_filename, d3.csvFormat(yieldsWithCropGroups));
fs.writeFileSync(grid_target_filename, d3.csvFormat(grid));

4 changes: 2 additions & 2 deletions vacs-map-app/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,16 @@ const documentHeight = () => {
}
onMounted(() => {
cropInformationStore.load()
gridStore.load()
cropYieldsStore.load()
cropInformationStore.load()
window.addEventListener('resize', documentHeight)
documentHeight
})
onUnmounted(() => {
window.removeEventListener('resize')
window.removeEventListener('resize', documentHeight)
})
</script>

Expand Down
8 changes: 5 additions & 3 deletions vacs-map-app/src/MapExplorer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
<div class="map-overlay desktop">
<div class="overlay-left">
<ExploreSidebar class="interactive" ref="overlayLeftRef" />
<MapLegend class="interactive" :class="{ hidden: showSandAndSoil }" />
<MapLegendCropGroups v-if="showCropGroupMap" class="interactive" :class="{ hidden: showSandAndSoil }"/>
<MapLegend v-else class="interactive" :class="{ hidden: showSandAndSoil }"/>
</div>

<div class="overlay-right">
Expand Down Expand Up @@ -53,7 +54,8 @@ import MobileExploreMapControls from '@/components/MobileExploreMapControls.vue'
import MapTooltip from '@/components/MapTooltip.vue'
import MapLegend from '@/components/MapLegend.vue'
import OverviewTop from '@/components/OverviewTop.vue'
import DataDisclaimer from './components/DataDisclaimer.vue'
import DataDisclaimer from '@/components/DataDisclaimer.vue'
import MapLegendCropGroups from '@/components/MapLegendCropGroups.vue'
const basePadding = 50
const leftWidth = ref(null)
Expand All @@ -69,7 +71,7 @@ useResizeObserver(overlayLeftRef, ([entry]) => {
})
const mapExploreStore = useMapExploreStore()
const { selectedMap, mapPadding, showSandAndSoil } = storeToRefs(mapExploreStore)
const { selectedMap, mapPadding, showCropGroupMap, showSandAndSoil } = storeToRefs(mapExploreStore)
if (!selectedMap.value) {
selectedMap.value = 'explore-map'
Expand Down
1 change: 1 addition & 0 deletions vacs-map-app/src/assets/base.css
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
--ui-blue-light-bg: #5473a5;
--font-family-header: PPeiko-heavy;
--font-family-h2: PPeiko-medium;
--font-family-h3: PPeiko-thin;
--font-family-body: 'Work Sans';
--shadow: 0px 0px 20px 0px rgba(255, 254, 254, 0.1);

Expand Down
3 changes: 3 additions & 0 deletions vacs-map-app/src/assets/img/arrow-right-pointy.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions vacs-map-app/src/assets/img/negative-yield.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions vacs-map-app/src/assets/img/positive-yield.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions vacs-map-app/src/assets/img/reference-crop.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
120 changes: 107 additions & 13 deletions vacs-map-app/src/components/CardWrapper.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,23 @@
<template>
<div class="card-wrapper" @click="handleClick" :class="{ active: isActive, bold: boldTitle }">
<div
class="card-wrapper"
@click="handleClick"
:class="{ dynamic: isDynamic, active: isActive, bold: boldTitle }"
>
<slot></slot>

<div class="info" :class="{ bold: boldTitle }">
<div class="title" :class="{ bold: boldTitle }">{{ title }}</div>
<div class="info" :class="{ bold: boldTitle, hasDescription: description }">
<div class="title-row" :class="{ bold: boldTitle }">
<span class="title">
{{ title }}
<img v-if="referenceCrop" src="../assets/img/reference-crop.svg" alt="" />
</span>
<span class="subtitle"> {{ subtitle }} </span>
<img v-if="isDynamic && !isActive" src="../assets/img/arrow-right-pointy.svg" alt="" />
</div>
<div v-if="referenceCrop" class="reference-crop-message">
<span> {{ cropGroup }} reference crop</span>
</div>
<div v-if="indicator" class="indicator">
<span class="indicator-category"> {{ indicator.key }} </span>
<span
Expand Down Expand Up @@ -36,6 +50,11 @@ const props = defineProps({
default: ''
},
subtitle: {
type: String,
default: ''
},
boldTitle: {
type: Boolean,
default: false
Expand All @@ -46,11 +65,26 @@ const props = defineProps({
default: ''
},
referenceCrop: {
type: Boolean,
default: false
},
cropGroup: {
type: String,
default: ''
},
handleClick: {
type: Function,
default: () => {}
},
isDynamic: {
type: Boolean,
default: false
},
isActive: {
type: Boolean,
default: false
Expand All @@ -66,7 +100,18 @@ const props = defineProps({
default: () => {}
}
})
const { title, description, handleClick, indicator } = toRefs(props)
const {
title,
description,
handleClick,
indicator,
isActive,
isDynamic,
showMoreInfo,
boldTitle,
referenceCrop,
cropGroup
} = toRefs(props)
const colorStore = useColorStore()
const { stopLight: stopLightScheme, colorblindFriendly } = storeToRefs(colorStore)
Expand Down Expand Up @@ -108,6 +153,14 @@ const useDarkIndicatorText = computed(() => {
border-radius: 0.75rem;
}
.card-wrapper.dynamic {
height: var(--title-height);
}
.card-wrapper.dynamic.active {
height: unset;
}
.info {
transition: all 0.5s ease;
position: absolute;
Expand All @@ -131,23 +184,55 @@ const useDarkIndicatorText = computed(() => {
height: unset;
}
.title {
.card-wrapper.dynamic .info {
transition: none;
padding: 0.375rem 0.75rem;
background: var(--dark-gray);
color: var(--white);
}
.card-wrapper.dynamic.active .info {
padding: 0.75rem;
padding-top: 0;
}
.title-row {
font-family: var(--font-family-h2);
height: var(--title-height);
font-size: 1.125rem;
text-transform: capitalize;
display: flex;
align-items: center;
gap: 0.5rem;
align-items: baseline;
overflow-x: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.title.bold {
.title-row img {
margin-left: auto;
}
.title-row.bold {
font-family: var(--font-family-header);
font-size: 1.375rem;
}
.title {
height: 100%;
display: flex;
align-items: center;
gap: 0.5rem;
}
.subtitle {
font-size: 0.875rem;
font-family: var(--font-family-h3);
}
.reference-crop-message {
color: var(--dark-gray);
}
.indicator {
display: flex;
gap: 0.375rem;
Expand Down Expand Up @@ -179,19 +264,28 @@ const useDarkIndicatorText = computed(() => {
background: var(--ui-blue);
}
.info:hover {
transform: translateY(calc((100% - var(--title-height)) * -1));
background: var(--white-80);
.dynamic.active {
border-color: var(--ui-blue);
color: var(--black);
}
.active .info:hover {
background: var(--ui-blue-80);
.dynamic.active .info {
background: var(--ui-blue);
color: var(--black);
}
.info.hasDescription:hover {
transform: translateY(calc((100% - var(--title-height)) * -1));
}
.card-wrapper:hover {
box-shadow: 0 0 0 2px var(--ui-blue);
}
.card-wrapper.dynamic.active:hover {
box-shadow: 0 0 0 1px var(--gray);
}
.more-info {
text-decoration: underline;
cursor: pointer;
Expand Down
Loading

0 comments on commit f4ee2ae

Please sign in to comment.