Skip to content

Commit

Permalink
fix trend dl, refactor trend cntnr (#1075)
Browse files Browse the repository at this point in the history
  • Loading branch information
brendansudol authored Jun 27, 2017
1 parent 0e4d88e commit 5db4ee5
Show file tree
Hide file tree
Showing 6 changed files with 88 additions and 118 deletions.
1 change: 0 additions & 1 deletion content/crimes/violent-crime.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ caveats:
text: |
In 2013, the FBI started collecting rape data under a revised definition and removed “forcible” from the offense name. All reported rape incidents—whether collected under the revised definition or the legacy definition—are presented here. Since the rape definition changed, some state and local law enforcement agencies have continued to report incidents with the legacy definition, because they haven’t been able to change their records management systems to accommodate the change.
links:
- url: https://ucr.fbi.gov/ucr-publications
text: "FBI: Uniform Crime Reporting Publications"
Expand Down
2 changes: 1 addition & 1 deletion src/components/AboutTheData.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ class AboutTheData extends React.Component {
<div className="flex-none mb2 col-12 lg-block lg-col-4 lg-ml3 lg-mb0">
<div className="p2 sm-px4 sm-py3 bg-blue white">
<h4 className="mt0 mb1 fs-18">Further reading</h4>
<ul className="m0 p0 fs-14 sm-fs-16 left-bars">
<ul className="m0 p0 fs-14 left-bars">
{links.map((l, i) =>
<li key={i} className="mb1">
<a className="white" href={l.url}>{l.text}</a>
Expand Down
89 changes: 33 additions & 56 deletions src/components/TrendChart.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,21 @@ class TrendChart extends React.Component {
}
}

createSeries = ({ crimes, data, places }) => {
createSeries = (crimes, data, places) => {
const [dates, rates] = [[], []]
const series = places
.map(p =>
crimes.map(c => {
.map(place =>
crimes.map(crime => {
const gaps = []
const segments = [[]]
const values = data.filter(d => d[p][c] && d[p][c].count).map(d => ({
date: d.date,
year: d.year,
population: d[p].population,
...d[p][c],
}))
const values = data
.filter(d => d[place][crime] && d[place][crime].count)
.map(d => ({
date: d.date,
year: d.year,
population: d[place].population,
...d[place][crime],
}))

values.forEach(d => {
if (d.count && d.count !== 0) {
Expand All @@ -60,24 +62,14 @@ class TrendChart extends React.Component {
rates.push(d.rate)
})

return {
crime: c,
gaps,
place: p,
segments,
values,
}
return { crime, gaps, place, segments, values }
}),
)
.reduce((a, n) => a.concat(n), [])

return { dates, rates, series }
}

forgetValue = () => {
this.setState({ hover: null })
}

updateYear = year => {
this.setState({ yearSelected: year })
}
Expand All @@ -92,67 +84,59 @@ class TrendChart extends React.Component {
}

render() {
const { crime, colors, data, showMarkers, since, size, until } = this.props
const { crime, colors, data, places, since, size, until } = this.props
const { hover, svgParentWidth, yearSelected } = this.state

const { margin } = size
const color = scaleOrdinal(colors)
const svgWidth = svgParentWidth || size.width
const svgHeight = svgWidth / 2.25
const width = svgWidth - margin.left - margin.right
const height = svgHeight - margin.top - margin.bottom
const xPadding = svgWidth < 500 ? 15 : 30
const xPad = svgWidth < 500 ? 15 : 30
const parse = timeParse('%Y')
const places = Object.keys(data[0]).filter(
k => k !== 'year' && k !== 'date',
)

const isRape = crime === 'rape'
const crimes = [isRape ? 'rape-legacy' : crime]
if (isRape) crimes.push('rape-revised')

const dataByYear = data.map(d => ({ ...d, date: parse(d.year) }))
const { dates, rates, series } = this.createSeries({
crimes,
data: dataByYear,
places,
})

const x = scaleTime()
.domain(extent(dates))
.range([xPadding, width - xPadding])
const newSeries = this.createSeries(crimes, dataByYear, places)
const { dates, rates, series } = newSeries

const x = scaleTime().domain(extent(dates)).range([xPad, width - xPad])
const y = scaleLinear().domain([0, max(rates)]).range([height, 0]).nice()

let active = series.map(d => ({
crime: d.crime,
place: d.place,
let active = series.map(({ crime: c, place, values }) => ({
crime: c,
place,
...(yearSelected
? d.values.find(v => v.year === yearSelected)
: d.values[d.values.length - 1]),
? values.find(v => v.year === yearSelected)
: values[values.length - 1]),
}))

if (!yearSelected && hover) {
const bisectDate = bisector(d => d.date).left
const x0 = x.invert(hover.x * width)
active = series
.map(({ crime: c, place: p, values }) => {
.map(({ crime: c, place, values }) => {
if (x0.getFullYear() < 2013 && c === 'rape-revised') return null
const i = bisectDate(values, x0, 1)
const [d0, d1] = [values[i - 1], values[i]]

return {
crime: c,
place: p,
...(d0 && d1 && x0 - d0.date > d1.date - x0 ? d1 : d0),
}
const pt = d0 && d1 && x0 - d0.date > d1.date - x0 ? d1 : d0
return { crime: c, place, ...pt }
})
.filter(s => s)
}

const dataHover = places
.map(p =>
.map(place =>
active.filter(
a => a.place === p && a.crime !== 'rape-revised' && a.rate && a.count,
a =>
a.place === place &&
a.crime !== 'rape-revised' &&
a.rate &&
a.count,
),
)
.reduce((a, n) => a.concat(n), [])
Expand Down Expand Up @@ -182,13 +166,7 @@ class TrendChart extends React.Component {
tickCt={svgWidth < 500 ? 4 : 8}
/>
<YAxis scale={y} width={width} />
<TrendChartLineSeries
color={color}
series={series}
showMarkers={showMarkers}
x={x}
y={y}
/>
<TrendChartLineSeries color={color} series={series} x={x} y={y} />
{until > 2013 &&
crime === 'rape' &&
<g
Expand Down Expand Up @@ -255,7 +233,6 @@ TrendChart.defaultProps = {
margin: { top: 16, right: 0, bottom: 24, left: 32 },
},
colors: ['#ff5e50', '#95aabc', '#52687d'],
showMarkers: false,
}

export default TrendChart
106 changes: 50 additions & 56 deletions src/containers/TrendContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,72 +14,66 @@ import { generateCrimeReadme } from '../util/content'
import { getPlaceInfo } from '../util/place'
import mungeSummaryData from '../util/summary'

const TrendContainer = ({
crime,
place,
placeType,
since,
summaries,
until,
}) => {
let chart
const { error, loading } = summaries
const getContent = ({ crime, place, since, summaries, until }) => {
const { loading, error } = summaries

if (loading) return <Loading />
if (error) return <ErrorCard error={error} />

const data = mungeSummaryData({
crime,
summaries: summaries.data,
place,
since,
until,
})

if (!data || data.length === 0) return <NoData />

const places = Object.keys(summaries.data)
const fname = `${place}-${crime}-${since}-${until}`
const title =
`Reported ${pluralize(crime)} in ` +
`${startCase(place)}, ${since}-${until}`

const readme = generateCrimeReadme({ crime, title })
const dlData = data.map(d => {
const placeData = places.map(p => ({ [p]: { ...d[p][crime] } }))
return { year: d.year, ...Object.assign(...placeData) }
})

const download = [
{
content: generateCrimeReadme({
crime,
title: `Reported ${pluralize(crime)} in ${startCase(
place,
)}, ${since}-${until}`,
}),
filename: 'README.md',
},
{ content: readme, filename: 'README.md' },
{ data: dlData, filename: `${fname}.csv` },
]

if (loading) chart = <Loading />
else if (error) chart = <ErrorCard error={error} />
else {
const data = mungeSummaryData({
crime,
summaries: summaries.data,
place,
since,
until,
})
if (!data || data.length === 0) chart = <NoData />
else {
download.push({
data,
filename: `${place}-${crime}-${since}-${until}.csv`,
})
chart = (
<div>
<TrendChart
crime={crime}
data={data}
place={place}
since={since}
until={until}
/>
<DownloadDataBtn
data={download}
filename={`${place}-${crime}-${since}-${until}`}
/>
</div>
)
}
}
return (
<div>
<TrendChart
crime={crime}
data={data}
places={places}
since={since}
until={until}
/>
<DownloadDataBtn data={download} filename={fname} />
</div>
)
}

const TrendContainer = props => {
const { crime, place, placeType, since, summaries, until } = props
const isReady = !summaries.loading

return (
<div>
<div className="mb2 p2 sm-p4 bg-white border-top border-blue border-w8">
<h2 className="mt0 mb3 sm-mb5 fs-24 sm-fs-28 sans-serif">
{startCase(crime)} rate in {startCase(place)},{' '}
{since}{until}
{startCase(crime)} rate in {startCase(place)}, {since}-{until}
</h2>
{chart}
{getContent(props)}
</div>
{!loading &&
{isReady &&
<TrendSourceText
crime={crime}
place={place}
Expand Down
6 changes: 3 additions & 3 deletions src/util/content.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@ const content = {
export const generateCrimeReadme = ({ crime, title = 'README' }) => {
const crimeJson = content.crimes[crime]
const caveats = crimeJson.caveats
.map(c => `### ${c.heading}\n${c.text}\n`)
.join('\n')
.map(c => `### ${c.heading}\n\n${c.text}`)
.join('\n\n')
const links = crimeJson.links.map(l => `* [${l.text}](${l.url})`).join('\n')

return `# ${title}\n## Caveats\n${caveats}\n## Links\n${links}`
return `# ${title}\n\n## Caveats\n\n${caveats}\n\n## Links\n\n${links}`
}

export default content
2 changes: 1 addition & 1 deletion test/components/TrendChart.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ describe('TrendChart', () => {
since: 2012,
until: 2014,
crime: 'violent-crime',
place: 'united-states',
places: ['united-states'],
}
let chart

Expand Down

0 comments on commit 5db4ee5

Please sign in to comment.