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

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more