Public
Edited
Jun 9, 2023
1 fork
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
defaultColorHex = "#808080"
Insert cell
plantColorPalette = {
let plantAccessColors = colorbrewer.Set3[4]
let plantAccess = ["One Plant", "Two Plants", "Three Plants", "4+ Plants"]
let hexPalette = Object.fromEntries(plantAccess.map((access,i) => ([access, plantAccessColors[i]])))
let rgbPalette = Object.entries(hexPalette).map(([key, hex]) => {
return [key, Object.values(tinycolor(hex).toRgb())]
})

for (let key in rgbPalette) {
let rgb = rgbPalette[key][1]
rgb[3] = 255
rgbPalette[key][1] = rgb
}
return Object.fromEntries(rgbPalette)
}
Insert cell
// set up colors - need a trick for 21 colors
corpColorPalette = {
//TODO: fix color palette number selection
//Include logic for if the number of companies is above 12
let corpColors = colorbrewer.Set3[Math.min(filteredCompanies.length, 12)]
let hexPalette = Object.fromEntries(filteredCompanies.map((company, i) => ([company, corpColors[i]])))

// set undefined colors to default
for (let key in hexPalette) {
if (hexPalette[key] === undefined) {
hexPalette[key] = defaultColorHex
}
}

let rgbPalette = Object.entries(hexPalette).map(([key, hex]) => {
return [key, Object.values(tinycolor(hex).toRgb())]
})

for (let key in rgbPalette) {
let rgb = rgbPalette[key][1]
rgb[3] = 255
rgbPalette[key][1] = rgb
}
return Object.fromEntries(rgbPalette)
}
Insert cell
markerPalette = {
return {
farm: [220, 220, 220, 255],
plant: [255, 255, 255, 255],
default: [140, 140, 140, 255]
}
}
Insert cell
colorPalette = {
return Object.assign(plantColorPalette, corpColorPalette, markerPalette)
}
Insert cell
Insert cell
corpDeckGL = {

const accessMapLayer = new deck.GeoJsonLayer({
data: filteredAreas,
pickable: true,
//TODO: maybe add tooltip back
// onHover: onHoverPlantAccess,
getFillColor: function(dataRow) {
switch(dataRow.properties.plant_access){
case 1:
return colorPalette["One Plant"]
case 2:
return colorPalette["Two Plants"]
case 3:
return colorPalette["Three Plants"]
case 4:
return colorPalette["4+ Plants"]
}
}
})
const corpMapLayer = new deck.GeoJsonLayer({
data: stateCorpMonopsony,

// Tooltip
pickable: true,
// onHover: onHoverMonop,

// Layer color
getFillColor: function(dataRow) {
return colorPalette[dataRow.properties.parent_corporation]
}
})

const plantLayer = new deck.IconLayer({
id: 'icon-layer',
data: poultryPlants,
iconAtlas: 'https://raw.githubusercontent.com/visgl/deck.gl-data/master/website/icon-atlas.png',
iconMapping: {
marker: {x: 0, y: 0, width: 128, height: 128, mask: true}
},
getIcon: d => 'marker',
getPosition: d => d.geometry.coordinates,
getSize: d => 35,
getColor: d => colorPalette.plant,

pickable: true,
onHover: onHoverPlant
})

const farmLayer = new deck.IconLayer({
id: 'icon-layer',
data: filteredFarms,
pickable: true,
iconAtlas: 'https://raw.githubusercontent.com/visgl/deck.gl-data/master/website/icon-atlas.png',
iconMapping: {
marker: {x: 0, y: 0, width: 128, height: 128, mask: true}
},
//TODO: Make farms less chaotic
getIcon: d => 'marker',
getPosition: d => d.geometry.coordinates,
getSize: d => 10,
getColor: d => colorPalette.farm
})
return new deck.DeckGL({
// The HTML container to render into
container: container,

// Mapbox settings
// set `map: false` to turn off Mapbox base map
map: mapboxgl,
// This token is for demo-purpose only and rotated regularly. Get your token at https://www.mapbox.com
mapboxApiAccessToken: 'pk.eyJ1IjoidWJlcmRhdGEiLCJhIjoiY2pudzRtaWloMDAzcTN2bzN1aXdxZHB5bSJ9.2bkj3IiRC8wj3jLThvDGdA',
mapStyle: 'mapbox://styles/mapbox/dark-v9',

// Viewport settings
// longitude: -90.9712,
// latitude: 40.7831,
// zoom: 3.5,
// zoom to match selection
longitude: currentLatLonZoom.longitude,
latitude: currentLatLonZoom.latitude,
zoom: currentLatLonZoom.zoom,
pitch: 0,
bearing: -0,

//TODO: Want different picking radius for geoJSON and for marker
pickingRadius: 200,

//TODO: Make layers displayed dynamic
//Select either plant access or corporate monopsony on the map
layers: [
accessMapLayer,
//corpMapLayer,
plantLayer,
farmLayer
],
})
}
Insert cell
Insert cell
tooltip = container.querySelector("#tooltip")
Insert cell
html `<link href='https://api.tiles.mapbox.com/mapbox-gl-js/v0.44.1/mapbox-gl.css' rel='stylesheet' />
<style>
#tooltip:empty {
display: none;
}
#tooltip {
font-family: Helvetica, Arial, sans-serif;
font-size: 11px;
position: absolute;
padding: 4px;
margin: 8px;
background: rgba(0, 0, 0, 0.8);
color: #fff;
max-width: 300px;
font-size: 10px;
z-index: 9;
pointer-events: none;
}
#legend {
position:absolute;
right:8px;
bottom:8px;
background:white;
padding:4px;
border:1px solid red;
z-index:500;

}
#legend .legend-entry {
display:flex;
}
#legend .swatch{
width:1rem;
height:1rem;
margin-right:0.5rem;
}
</style>
(Stylesheets)`
Insert cell
onHoverPlant = {
return function(info) {
const {x, y, object} = info;
console.log(x,y,object)
if (object) {
tooltip.style.top = `${y}px`;
tooltip.style.left = `${x}px`;
tooltip.innerHTML = `
<div><b>${object.properties['Establishment Name']}</b></div>
<div><b>${object.properties['Full Address']}</b></div>
<div><b>Parent Corporation: ${object.properties['Parent Corporation']}</b></div>
`;
} else {
tooltip.innerHTML = '';
}
};
}
Insert cell
onHoverMonop = {
return function(info) {
const {x, y, object} = info;
// console.log(x,y,object)
if (object) {
tooltip.style.top = `${y}px`;
tooltip.style.left = `${x}px`;
tooltip.innerHTML = `
<div><b>Monopsony Area: ${object.properties.parent_corporation}</b></div>
`;
} else {
tooltip.innerHTML = '';
}
};
}
Insert cell
onHoverPlantAccess = {
return function(info) {
const {x, y, object} = info;
// console.log(x,y,object)
if (object) {
tooltip.style.top = `${y}px`;
tooltip.style.left = `${x}px`;
tooltip.innerHTML = `
<div><b>Plant Access: ${object.properties.plant_access}</b></div>
`;
} else {
tooltip.innerHTML = '';
}
};
}
Insert cell
Insert cell
currentGeojson = ({
type: "FeatureCollection",
features: filteredAreas
})
Insert cell
bbox = turf.bbox(currentGeojson)
Insert cell
width = container.getBoundingClientRect().width
Insert cell
height = container.getBoundingClientRect().height
Insert cell
fittedViewport = new deck.WebMercatorViewport({width, height})
Insert cell
currentLatLonZoom = fittedViewport.fitBounds([[bbox[0],bbox[1]],[bbox[2],bbox[3]]], {width, height})
Insert cell
Insert cell
companySalesFiltered = {
// build dictionary for each company in the area
let companySales = {}
for (let i=0; i < filteredCompanies.length; i++) {
companySales[filteredCompanies[i]] = 0
}

for (let i=0; i < filteredPlants.length; i++) {
let salesVolume = filteredPlants[i].properties["Sales Volume (Location)"]
if (!Number.isNaN(salesVolume)) {
companySales[filteredPlants[i].properties["Parent Corporation"]] += salesVolume
}
}

// filter NaN values and return dictionary
let filtered = Object.entries(companySales).reduce((filtered, [key, value]) => {
if (!Number.isNaN(value)) {
filtered[key] = value
}
return filtered
}, {})

// sort on value
let sorted = Object.entries(filtered).sort((a, b) => b[1] - a[1])

return Object.fromEntries(sorted)
}
Insert cell
companySalesTable = {
return Object.entries(companySalesFiltered).map(([company, sales]) => ({company, sales}))
}
Insert cell
farmCountFiltered = {
// build dictionary for each company in the area
let farmCounts = {
1: 0,
2: 0,
3: 0,
4: 0
}

let totalFarms = filteredFarms.length
for (let i=0; i < totalFarms; i++) {
farmCounts[filteredFarms[i].properties.plant_access] += 1
}

let percentCaptured = {}
Object.keys(farmCounts).forEach(key => {
percentCaptured[key] = farmCounts[key]/totalFarms
})

let farmsTable = Object.entries(percentCaptured).map(([plantAccess, percentFarms]) => ({plantAccess, percentFarms}))

return farmsTable
}
Insert cell
calculatedData = {
let areas = {
1: 0,
2: 0,
3: 0,
4: 0
}

for (let i=0; i < filteredAreas.length; i++) {
areas[filteredAreas[i].properties.plant_access] += filteredAreas[i].properties.area
}

let totalArea = Object.values(areas).reduce((acc, val) => acc + val, 0)

let percentArea = {}
Object.keys(areas).forEach(key => {
percentArea[key] = areas[key]/totalArea
})

let areasTable = Object.entries(percentArea).map(([plantAccess, percentArea]) => ({plantAccess, percentArea}))

return areasTable
}
Insert cell
Insert cell
stateMonopsony = FileAttachment("all_states@3.geojson").json()
Insert cell
filteredAreas = stateMonopsony.features.filter(
row => {
if (selectedStates.includes(row.properties.state)) {
return true
} else {
return false
}
})
Insert cell
allState = stateMonopsony.features.map(feature => feature.properties.state).filter((value, index, array) => array.indexOf(value) === index)
Insert cell
filteredCompanies = filteredPlants.map(plant => plant.properties["Parent Corporation"]).filter((value, index, array) => array.indexOf(value) === index)
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
geo = require("geotoolbox@latest")
Insert cell
colorbrewer = require('colorbrewer')
Insert cell
tinycolor = require("tinycolor2")
Insert cell
turf = require('@turf/turf')
Insert cell
Insert cell
poultry_plants_with_sales@1.csv
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell
plantsGeoSales = geo.coords2geo(poultry_plants_with_sales, { lat: "latitude", lng: "longitude" })
Insert cell
poultryPlants = plantsGeoSales.features.filter(
row => {
if (row.properties["Animals Processed"] === "Chicken" && row.properties.Size === "Large") {
return true
}
else {
return false
}
}
)
Insert cell
filteredPlants = poultryPlants.filter(
row => {
if (selectedStates.includes(row.properties.State)) {
return true
} else {
return false
}
})
Insert cell
farms = FileAttachment("counterglow_geojson@1.geojson").json()
Insert cell
filteredFarms = farms.features.filter(
row => {
if (selectedStates.includes(row.properties.state)) {
return true
} else {
return false
}
})
Insert cell
stateCorpMonopsony = FileAttachment("all_states_with_parent_corp@1.geojson").json()
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more