Skip to content

Commit

Permalink
Add quintiles and diverging color scales (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
ebrelsford authored Oct 30, 2023
1 parent 930dea8 commit 7f54c53
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 6 deletions.
102 changes: 96 additions & 6 deletions vacs-map-app/src/components/GridOverlay.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<template></template>

<script setup>
import * as d3 from 'd3';
import { computed, onMounted, toRefs, watch } from 'vue';
import { storeToRefs } from 'pinia';
import { point, featureCollection } from '@turf/helpers';
Expand Down Expand Up @@ -45,6 +46,14 @@ const selectedColumn = computed(() => {
].join('_');
});
const selectedColumnExtent = computed(() => {
return cropYieldsStore.getExtent(selectedColumn.value);
});
const selectedColumnQuintiles = computed(() => {
return cropYieldsStore.getQuintiles(selectedColumn.value);
});
onMounted(() => {
gridStore.load();
cropYieldsStore.load();
Expand Down Expand Up @@ -116,19 +125,33 @@ const addLayerToMap = (geoJson) => {
addLayer();
};
const getCircleColor = () => {
const getCircleColorQuintiles = (quintiles) => {
if (!quintiles) return 'gray';
const getColor = quantile => {
// This is the easiest thing we can do for sequential color schemes--use
// d3 to interpolate a given color scheme.
// There are built-in d3 interpolators we can use:
const interpolator = d3.interpolateGreens;
// Or it's pretty easy to define your own:
// (you can include as many colors here as you like)
// const interpolator = d3.interpolateHsl("purple", "orange");
return interpolator(quantile);
};
// This is linear interpolating between quantiles but we could easily switch
// to strict buckets if we prefer.
return [
'case',
['!=', ['get', selectedColumn.value], null],
[
'interpolate',
['linear'],
['get', selectedColumn.value],
// TODO these are hard coded right now, will likely want the real extent
// of the data and/or quintiles
0, 'red',
500, 'yellow',
1000, 'green',
...quintiles.map(({ value, quantile }) => ([value, getColor(quantile)])).flat()
],
'transparent',
];
Expand Down Expand Up @@ -161,6 +184,73 @@ const getCircleRadius = () => {
];
};
const getCircleColorExtent = (extent) => {
if (extent[0] === undefined) return 'gray';
const [min, max] = extent;
const middle = min + (max - min) / 2;
// We could switch the color selection here to a similar scheme as in
// getCircleColorQuintiles if we wanted
return [
'case',
['!=', ['get', selectedColumn.value], null],
[
'interpolate',
['linear'],
['get', selectedColumn.value],
min, 'red',
middle, 'yellow',
max, 'green',
],
'white',
];
};
const getCircleColorDiverging = (extent, center) => {
if (extent[0] === undefined) return 'gray';
const [min, max] = extent;
const getColor = value => {
// See getCircleColorQuintiles for details about how this works
// const interpolator = d3.interpolateBrBG;
const interpolator = d3.interpolatePiYG;
// const interpolator = d3.interpolateHsl("purple", "orange");
return interpolator(value);
};
return [
'case',
['!=', ['get', selectedColumn.value], null],
[
'interpolate',
['linear'],
['get', selectedColumn.value],
min, getColor(0),
center, getColor(0.5),
max, getColor(1),
],
'white',
];
}
const getCircleColor = () => {
if (selectedMetric.value === 'yieldratio') {
// < 1, decrease
// 1 = no change
// > 1, increase
return getCircleColorDiverging(selectedColumnExtent.value, 1);
}
return getCircleColorQuintiles(selectedColumnQuintiles.value);
// Defaulting to using quintiles but here's how to invoke another method:
// return getCircleColorExtent(selectedColumnExtent.value);
};
const updateLayer = () => {
if (!map.value.getLayer(LAYER_ID)) return;
map.value.setPaintProperty(LAYER_ID, 'circle-color', getCircleColor());
Expand Down
34 changes: 34 additions & 0 deletions vacs-map-app/src/stores/cropYields.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,43 @@ export const useCropYieldsStore = defineStore('cropYields', () => {
data.value = Object.freeze(transformedData);
};

const getColumnValues = columnName => {
if (!(data.value && data.value.length)) return null;
return data.value.map(d => d[columnName]).filter(d => !!d).sort(d3.ascending);
};

/*
* Get an extent for the given column name. Uses the 98th percentile for the
* maximum to guard against outliers.
*/
const getExtent = columnName => {
const values = getColumnValues(columnName);
return [
values[0],
d3.quantileSorted(values, 0.98),
];
};

const getQuintiles = columnName => {
const values = getColumnValues(columnName);
return [
...d3.range(0, 1, 0.2).map(d => ({
value: d3.quantileSorted(values, d),
quantile: d,
})),
{
value: d3.quantileSorted(values, 0.98),
quantile: 1,
},
]
};

return {
data,
loading,
load,

getExtent,
getQuintiles,
};
});

0 comments on commit 7f54c53

Please sign in to comment.