Skip to content

Commit

Permalink
Merge pull request #364 from MAPC/january-2021
Browse files Browse the repository at this point in the history
January 2021
  • Loading branch information
mzagaja authored Dec 28, 2020
2 parents 22204c9 + ff564d2 commit 53d2776
Show file tree
Hide file tree
Showing 22 changed files with 452 additions and 39 deletions.
7 changes: 6 additions & 1 deletion app/javascript/pages/assets/data/calendar-data.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
[{
[ {
"year": 2021,
"title": "Introducing the MAPC Zoning Atlas",
"month": "January",
"url": "/gallery/2021/january"
}, {
"year": 2020,
"title": "Employment by Industry",
"month": "January",
Expand Down
2 changes: 1 addition & 1 deletion app/javascript/pages/assets/images/calendar-home.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions app/javascript/pages/components/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ const App = (props) => (
<Route exact path="/gallery" component={Gallery} />
<Route exact path="/login" component={Login} />
<Route path="/calendar/:year/:month" component={CalendarEntry} />
<Route path="/gallery/:year/:month" component={CalendarEntry} />
<Route path="/browser/datasets/:id" component={DataViewer} />
<Route
path="/browser/:menuOneSelectedItem?/:menuTwoSelectedItem?"
Expand Down
4 changes: 2 additions & 2 deletions app/javascript/pages/components/Home.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@ class Home extends React.Component {
lens.
</p>
<p>
<strong>December’s visualization</strong>
<strong>January’s visualization</strong>
{' '}
revisits the digital divide in an exploration of internet download speeds across Massachusetts. As more and more aspects of our lives go online, the need for reliable internet access is more crucial than ever.
highlights the recently-released MAPC Zoning Atlas and explores residential density throughout the region. A resource nine years in the making, the Zoning Atlas provides standardized methods for comparing land use across the 101 municipalities of Greater Boston.
</p>
<CallToAction
link="/gallery"
Expand Down
159 changes: 159 additions & 0 deletions app/javascript/pages/components/gallery/2021/January.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/* eslint-disable max-len */
/* eslint-disable object-curly-newline */
import React, { useState } from 'react';
import { Helmet } from 'react-helmet';
import { MapContainer, TileLayer, ZoomControl, Popup } from 'react-leaflet';
import { FeatureLayer } from 'react-esri-leaflet';
import MapLegend from '../../visualizations/MapLegend';

const dataNa = '#d8d8d8';
const legend = [
{ color: '#f6c61e', value: '1–4', min: 1, max: 5 },
{ color: '#f4a617', value: '5–9', min: 5, max: 10 },
{ color: '#F8970D', value: '10–14', min: 10, max: 15 },
{ color: '#e96b14', value: '15–19', min: 15, max: 20 },
{ color: '#fc3a1c', value: '20–24', min: 20, max: 25 },
{ color: '#eb003f', value: '25–49', min: 25, max: 50 },
{ color: '#dd0058', value: '50–74', min: 50, max: 75 },
{ color: '#f40080', value: '75–99', min: 75, max: 100 },
{ color: '#b700a6', value: '100–199', min: 100, max: 200 },
{ color: '#6800b6', value: '200–299', min: 200, max: 300 },
{ color: '#0000E3', value: '300–399', min: 300, max: 400 },
{ color: dataNa, value: 'Data n/a' },
];

const January = () => {
const [selectedZone, setZone] = useState('');
const [latLng, setLatLng] = useState('');
return (
<>
<Helmet>
<link
rel="stylesheet"
href="https://unpkg.com/[email protected]/dist/leaflet.css"
integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A=="
crossOrigin=""
/>
<script
src="https://unpkg.com/[email protected]/dist/leaflet.js"
integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA=="
crossOrigin=""
/>
<link
rel="stylesheet"
href="https://unpkg.com/esri-leaflet-geocoder/dist/esri-leaflet-geocoder.css"
/>
</Helmet>
<h1 className="calendar-viz__title">Introducing the MAPC Zoning Atlas</h1>
<div className="calendar-viz__wrapper">
<MapContainer
center={[42.37, -70.944]}
zoom={9}
maxZoom={14}
minZoom={9}
zoomControl={false}
>
<TileLayer
url="https://api.mapbox.com/styles/v1/mapbox/light-v10/tiles/{z}/{x}/{y}/?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4M29iazA2Z2gycXA4N2pmbDZmangifQ.-g_vE53SD2WrJ6tFX7QHmA#1.07/0/0"
attribution='© <a href="https://www.mapbox.com/about/maps/">Mapbox</a> © <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> <strong><a href="https://www.mapbox.com/map-feedback/" target="_blank">Improve this map</a></strong>'
tileSize={512}
zoomOffset={-1}
/>
<FeatureLayer
url="https://geo.mapc.org/server/rest/services/gisdata/Zoning_Atlas_v01/MapServer/2"
pane="tilePane"
simplifyFactor={0.25}
style={(feature) => {
const color = legend.find((option) => feature.properties.dupac_eff >= option.min && feature.properties.dupac_eff < option.max)
? legend.find((option) => feature.properties.dupac_eff >= option.min && feature.properties.dupac_eff < option.max).color
: dataNa;
return {
color,
weight: 0.5,
fillOpacity: 0.8,
opacity: 1,
};
}}
eventHandlers={{
click: (e) => {
setLatLng(e.latlng);
setZone(e.layer.feature.properties);
},
}}
/>
<TileLayer pane="overlayPane" url="https://api.mapbox.com/styles/v1/ihill/cki9ablq87wb01apa878hhbj8/tiles/{z}/{x}/{y}?access_token=pk.eyJ1IjoiaWhpbGwiLCJhIjoiY2plZzUwMTRzMW45NjJxb2R2Z2thOWF1YiJ9.szIAeMS4c9YTgNsJeG36gg" />
<MapLegend
columns={2}
title="Effective Maximum Dwelling Units per Acre"
legend={legend}
dataLink=""
>
<span className="map-legend__title">Explore & Download Data</span>
<ul className="map-legend__resource-list">
<li className="map-legend__resource map-legend__entry">
<a className="map-legend__link" href="https://mapc365.sharepoint.com/:x:/s/DataServicesSP/Efonrnmw_kdMhmG3Dw2BkTcBIpe2sC_2ADWTWfUjOs4JhQ?e=K65BCE">Tabular data (.csv)</a>
</li>
<li className="map-legend__resource map-legend__entry">
<a className="map-legend__link" href="https://mapc365.sharepoint.com/sites/DataServicesSP/Shared%20Documents/Forms/AllItems.aspx?originalPath=aHR0cHM6Ly9tYXBjMzY1LnNoYXJlcG9pbnQuY29tLzpmOi9zL0RhdGFTZXJ2aWNlc1NQL0VyS2tYU0xIX2lCT2xEaEpyVFhsZHJZQklJWjRaWGU0Qmt3N095VmFwVnBYM1E%5FcnRpbWU9ekZlV3AzeW4yRWc&viewid=8aabc982%2D537d%2D48a6%2D89f7%2D8d8f0e9b716c&id=%2Fsites%2FDataServicesSP%2FShared%20Documents%2FZoning%20Database%2FShapefiles">Spatial data (.shp)</a>
</li>
<li className="map-legend__resource map-legend__entry">
<a className="map-legend__link" href="https://zoningatlas.mapc.org">MAPC Zoning Atlas</a>
</li>
</ul>
</MapLegend>
{selectedZone ? (
<Popup position={latLng}>
<p className="tooltip__title">
{selectedZone.zo_name}
{' '}
(
{selectedZone.muni}
)
</p>
<ul className="tooltip__list">
<li className="tooltip__text">
{selectedZone.dupac_eff ? `${selectedZone.dupac_eff} effective maximum dwelling units per acre` : 'Effective maximum dwelling units per acre data not available'}
</li>
</ul>
</Popup>
) : ''}
<ZoomControl position="bottomright" />
</MapContainer>
<a href="http://mapbox.com/about/maps" className="mapboxgl__watermark" target="_blank" rel="noreferrer">Mapbox</a>
</div>
<p>MAPC&apos;s Zoning Atlas, nine years in the making, is the first zoning map for Greater Boston since 1999, and the first to include region-wide information about commercial density, overlay districts, multifamily housing, and more.</p>
<p>
Among much else, the map offers a standardized way to compare residential density across zones that allow that use by right: Dwelling units per acre are shown in this month’s map.
<sup>1</sup>
</p>
<p>Why does the Atlas matter? Zoning is the DNA of a community&apos;s land use. It doesn&apos;t determine everything, but it&apos;s an underlying, coded blueprint for change, or sometimes the lack thereof. It affects a community&apos;s housing options, climate resilience, economy, public health, equity, transportation, emissions, and more.</p>
<p>In this sense, zoning not only shapes a municipality&apos;s own future, but also that of its neighbors, and its neighbors&apos; neighbors. Our larger challenges demand an understanding of land use regulations on a regional basis.</p>
<p>Yet Greater Boston’s zoning codes are complex, varied, hard to access, and bewildering, both as a whole and often on a municipality-by-municipality basis. The codes we were able to collect are a localized jumble of approaches, regulated metrics, exceptions and use-specific regulations, methods of calculation, and even definitions. One base zoning district had 58 footnotes detailing possible edge cases.</p>
<p>To make sense of it all, the Atlas condenses the zoning codes of the region’s 101 municipalities into just under 80 fields. We identified a set of five “core fields” covering use allowances and dimensional requirements: minimum lot size, dwelling units allowed per acre, maximum building height, maximum floor area ratio (FAR), and whether multifamily housing was a permitted use (by right or with a special permit).</p>
<p>
Explore the Atlas and read more its creation and significance
{' '}
<a href="https://zoningatlas.mapc.org/" className="calendar-viz__link">here</a>
. Even more important, help make the Atlas better. It is a dynamic online resource that will improve over time as municipal staff and other contributors refine the data and provide updates.
</p>
<p>
The Zoning Atlas is part of
{' '}
<a href="https://metrocommon.mapc.org/" className="calendar-viz__link">MetroCommon 2050</a>
, Greater Boston&apos;s next long-term plan, underway now.
</p>
<p>
<em>
<sup>1</sup>
{' '}
This metric is based directly on zoning code for approximately 20% of eligible zones and estimated for an additional 80%. A more thorough examination of our estimation methods and calculations can be found in the project’s
{' '}
<a href="https://metropolitan-area-planning-counc.gitbook.io/zoning-atlas-appendix/" className="calendar-viz__link">technical appendix</a>
.
</em>
</p>
</>
);
};

export default January;
3 changes: 3 additions & 0 deletions app/javascript/pages/components/gallery/2021/images.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import January from '../../../assets/images/gallery/2021/january.png';

export { January };
3 changes: 3 additions & 0 deletions app/javascript/pages/components/gallery/2021/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import January from './January';

export { January };
9 changes: 7 additions & 2 deletions app/javascript/pages/components/gallery/CalendarEntry.jsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import React from 'react';
import { Switch, Route } from 'react-router-dom';
import { Switch, Route, Redirect } from 'react-router-dom';
import * as calendar2020 from './2020/index';
import * as calendar2021 from './2021/index';

const CalendarEntry = () => (
<section className="route Calendar">
<div className="container">
<a href="/gallery" className="back-link">&lt;&lt; Back to 2020 Gallery</a>
<a href="/gallery" className="back-link">&lt;&lt; Back to Gallery</a>
<Switch>
<Route path="/calendar/2020/january" component={calendar2020.January} />
<Route path="/calendar/2020/february" component={calendar2020.February} />
Expand All @@ -19,6 +20,10 @@ const CalendarEntry = () => (
<Route path="/calendar/2020/october" component={calendar2020.October} />
<Route path="/calendar/2020/november" component={calendar2020.November} />
<Route path="/calendar/2020/december" component={calendar2020.December} />
<Route path="/gallery/2021/january" component={calendar2021.January} />
<Route path="/calendar/2021/january">
<Redirect to="/gallery/2021/january" />
</Route>
</Switch>
</div>
</section>
Expand Down
4 changes: 1 addition & 3 deletions app/javascript/pages/components/gallery/Gallery.jsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import React from 'react';
import YearNav from './YearNav';
import CalendarGrid from './CalendarGrid';
import GalleryFooter from './GalleryFooter';

class Gallery extends React.Component {
constructor(props) {
super(props);
this.state = { selectedYear: 2020 };
this.state = { selectedYear: 2021 };
this.changeYear = this.changeYear.bind(this);
this.mobileChangeYear = this.mobileChangeYear.bind(this);
}
Expand Down Expand Up @@ -41,7 +40,6 @@ class Gallery extends React.Component {
<CalendarGrid
selectedYear={this.state.selectedYear}
/>
<GalleryFooter />
</>
);
}
Expand Down
21 changes: 11 additions & 10 deletions app/javascript/pages/components/gallery/YearNav.jsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
import React from 'react';
import YearNavItem from './YearNavItem'
import YearNavItem from './YearNavItem';

class YearNav extends React.Component {
constructor(props) {
super(props);
this.state = {selectedYear: this.props.selectedYear};
this.state = { selectedYear: this.props.selectedYear };
}

render() {
const previousYears = ["19","18","17","16","15"]
const previousYearButtons = previousYears.map((year, i) => {
return <YearNavItem
const previousYears = ['20', '19', '18', '17', '16', '15'];
const previousYearButtons = previousYears.map((year, i) => (
<YearNavItem
year={year}
selected={false}
changeYear={this.props.changeYear}
key={i+1}
key={i + 1}
/>
})
));
return (
<nav aria-labelledby="year-navigation__list" className="year-navigation">
<ul className="container year-navigation__list year-navigation__desktop">
<YearNavItem
year="20"
year="21"
selected
changeYear={this.props.changeYear}
key={0}
Expand All @@ -30,14 +30,15 @@ class YearNav extends React.Component {
</ul>

<select className="year-navigation__mobile" onChange={this.props.mobileChangeYear}>
<option value={this.state.selectedYear}>2020</option>
<option value={this.state.selectedYear}>2021</option>
<option value="2020" >2020</option>
<option value="2019">2019</option>
<option value="2018">2018</option>
<option value="2017">2017</option>
<option value="2016">2016</option>
<option value="2015">2015</option>
</select>
</nav>
</nav>
);
}
}
Expand Down
2 changes: 2 additions & 0 deletions app/javascript/pages/components/gallery/images.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import * as images2021 from './2021/images';
import * as images2020 from './2020/images';
import * as images2019 from './2019/images';
import * as images2018 from './2018/images';
Expand All @@ -6,6 +7,7 @@ import * as images2016 from './2016/images';
import * as images2015 from './2015/images';

export default {
2021: images2021,
2020: images2020,
2019: images2019,
2018: images2018,
Expand Down
47 changes: 47 additions & 0 deletions app/javascript/pages/components/visualizations/MapLegend.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';

function setLegend(legend) {
return legend.map((entry) => (
<li key={entry.value}>
<svg width="10" height="10">
<circle cx="5" cy="5" r="5" fill={entry.color} stroke="black" />
</svg>
<span className="map-legend__entry">{entry.value}</span>
</li>
));
}

const MapLegend = ({
legend, title, columns, children,
}) => {
const [isExpanded, setExpansion] = useState(true);
return (
<div position="topright" className="map-legend">
{ isExpanded ? (
<>
<span className="map-legend__title">{title}</span>
<ul className={columns === 1 ? 'map-legend__list map-legend__list--one-col' : 'map-legend__list map-legend__list--two-col'}>
{setLegend(legend)}
</ul>
{children}
</>
) : <span className="map-legend__title">Expand Legend</span>}
<button type="button" className="map-legend__toggle" onClick={() => setExpansion(!isExpanded)}>
{isExpanded ? '-' : '+'}
</button>
</div>
);
};

MapLegend.propTypes = {
legend: PropTypes.arrayOf(PropTypes.object).isRequired,
title: PropTypes.string.isRequired,
columns: PropTypes.number,
};

MapLegend.defaultProps = {
columns: 1,
};

export default MapLegend;
3 changes: 3 additions & 0 deletions app/javascript/styles/components/visualizations/D3Map.scss
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@
width: 12.5rem;
z-index: 20;
&__title {
color: $color_bg-dark;
font-family: 'Montserrat';
font-size: .75rem;
font-weight: 800;
Expand All @@ -115,6 +116,7 @@
}

&__text {
color: $color_bg-dark;
font-family: 'Montserrat';
font-size: .75rem;
font-weight: 300;
Expand All @@ -123,6 +125,7 @@
}

&__list {
color: $color_bg-dark;
list-style: circle;
margin: unset;
padding-left: 1rem;
Expand Down
Loading

0 comments on commit 53d2776

Please sign in to comment.