Skip to content

Commit

Permalink
Merge pull request #173 from flacoman91/per-capita
Browse files Browse the repository at this point in the history
Per capita
  • Loading branch information
flacoman91 authored Feb 10, 2020
2 parents e951381 + 77015b8 commit 48c28ea
Show file tree
Hide file tree
Showing 21 changed files with 844 additions and 147 deletions.
9 changes: 6 additions & 3 deletions src/DateIntervals.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,17 @@ export class DateIntervals extends React.Component {
}

_btnClassName( dateInterval ) {
const btnClass = 'date-selector ';
return dateInterval === this.props.dateInterval ?
btnClass + 'selected' : btnClass;
const classes = [ 'date-selector', 'interval-' + dateInterval ]
if ( dateInterval === this.props.dateInterval ) {
classes.push( 'selected' )
}
return classes.join( ' ' )
}

render() {
return (
<section className="date-intervals">
<p>Date range (Click to modify range)</p>
{ dateIntervals.map( dateInterval =>
<button onClick={ () => this._setDateInterval( dateInterval ) }
className={ this._btnClassName( dateInterval ) }
Expand Down
7 changes: 4 additions & 3 deletions src/DateIntervals.less
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
@import (less) "base.less";

.date-intervals {
width: 100%;
float: left;
p {
display: block;
}
.date-selector {
background-color: @pacific-40;
&.selected {
background-color: @pacific-80;
}
}
}
}
6 changes: 5 additions & 1 deletion src/MapPanel.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { connect } from 'react-redux'
import DateIntervals from './DateIntervals'
import Loading from './Dialogs/Loading'
import MapToolbar from './MapToolbar'
import PerCapita from './PerCapita'
import React from 'react'
import RowChart from './RowChart'
import TileChartMap from './TileChartMap'
Expand All @@ -13,7 +14,10 @@ export class MapPanel extends React.Component {
return (
<section className="map-panel">
<ActionBar/>
<DateIntervals/>
<div className="layout-row refine">
<DateIntervals/>
<PerCapita/>
</div>
<TileChartMap/>
{ this.props.selectedState &&
<MapToolbar/>
Expand Down
8 changes: 7 additions & 1 deletion src/MapPanel.less
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,10 @@
h2 {
padding: @gutter-normal;
}
}

.refine {
section {
margin: 10px;
}
}
}
33 changes: 33 additions & 0 deletions src/PerCapita.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import './PerCapita.less'
import { GEO_NORM_NONE, GEO_NORM_PER1000 } from './constants'
import { connect } from 'react-redux'
import { dataNormalizationChanged } from './actions/map';
import React from 'react'


export class PerCapita extends React.Component {
render() {
return (
<section className="per-capita">
<p>Map Shading</p>
<select value={this.props.dataNormalization}
onChange={this.props.onDataNormalization}>
<option value={GEO_NORM_NONE}>Complaints</option>
<option value={GEO_NORM_PER1000}>Per capita</option>
</select>
</section>
)
}
}

export const mapStateToProps = state => ( {
dataNormalization: state.map.dataNormalization
} );

export const mapDispatchToProps = dispatch => ( {
onDataNormalization: ev => {
dispatch( dataNormalizationChanged( ev.target.value ) )
}
} );

export default connect( mapStateToProps, mapDispatchToProps )( PerCapita )
13 changes: 13 additions & 0 deletions src/PerCapita.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
@import (less) "base.less";

.per-capita {
p {
display: block;
}
.date-selector {
background-color: @pacific-40;
&.selected {
background-color: @pacific-80;
}
}
}
31 changes: 30 additions & 1 deletion src/TileChartMap.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import './TileChartMap.less'
import { GEO_NORM_NONE, STATE_DATA } from './constants'
import { addStateFilter } from './actions/map'
import { connect } from 'react-redux'
import { hashObject } from './utils'
Expand Down Expand Up @@ -63,7 +64,7 @@ export class TileChartMap extends React.Component {
// eslint-disable-next-line no-unused-vars
const chart = new TileMap( {
el: document.getElementById( 'tile-chart-map' ),
data: this.props.data,
data: updateData( this.props ),
colors,
localize: true,
events: {
Expand All @@ -74,6 +75,32 @@ export class TileChartMap extends React.Component {
}
}

/**
* helper function to get display value of tile based on selected dropdown.
* @param {object} props contains data and normalization
* @returns {object} data provided to tile map
*/
function updateData( props ) {
const { data, dataNormalization } = props
const showDefault = dataNormalization === GEO_NORM_NONE
const res = data[0].map( o => ( {
...o,
displayValue: showDefault ? o.value : o.perCapita
} ) )

return [ res ]
}

/**
* helper function to calculate percapita value
* @param {object} stateObj a state containing abbr and value
* @returns {string} the per capita value
*/
function getPerCapita( stateObj ) {
const pop = STATE_DATA.find( o => o.abbr === stateObj.name ).population
return ( stateObj.value / pop * 1000 ).toFixed( 2 )
}

export const getStateClass = ( statesFilter, name ) => {
// no filters so no classes.
if ( statesFilter.length === 0 ) {
Expand All @@ -87,6 +114,7 @@ export const processStates = state => {
const statesFilter = state.query.state || []
const states = state.map.state
const stateData = states.map( o => {
o.perCapita = getPerCapita( o )
o.className = getStateClass( statesFilter, o.name )
return o
} )
Expand All @@ -95,6 +123,7 @@ export const processStates = state => {

export const mapStateToProps = state => ( {
data: processStates( state ),
dataNormalization: state.map.dataNormalization,
stateFilters: state.query.state,
selectedState: state.map.selectedState
} )
Expand Down
5 changes: 4 additions & 1 deletion src/UrlBarSynch.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,10 @@ export class UrlBarSynch extends React.Component {
}

export const mapStateToProps = state => ( {
params: { ...state.query }
params: {
...state.query,
dataNormalization: state.map.dataNormalization
}
} )

export const mapDispatchToProps = dispatch => ( {
Expand Down
2 changes: 1 addition & 1 deletion src/__tests__/DateIntervals.spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ describe( 'component: DateIntervals', () => {
} )

it( 'toggleDateInterval is called the button is clicked', () => {
const prev = target.find( '.date-intervals button:first-child' )
const prev = target.find( '.date-intervals .interval-3m' )
prev.simulate( 'click' )
expect( cb ).toHaveBeenCalledWith('3m')
} )
Expand Down
60 changes: 60 additions & 0 deletions src/__tests__/PerCapita.spec.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import configureMockStore from 'redux-mock-store'
import {
mapDispatchToProps, mapStateToProps, PerCapita
} from '../PerCapita'
import { Provider } from 'react-redux'
import React from 'react'
import renderer from 'react-test-renderer'
import { shallow } from 'enzyme'
import thunk from 'redux-thunk'

function setupSnapshot() {
const middlewares = [ thunk ]
const mockStore = configureMockStore( middlewares )
const store = mockStore( {
map: {}
} )

return renderer.create(
<Provider store={ store }>
<PerCapita />
</Provider>
)
}

describe( 'component: PerCapita', () => {
describe( 'initial state', () => {
it( 'renders without crashing', () => {
const target = setupSnapshot()
let tree = target.toJSON()
expect( tree ).toMatchSnapshot()
} )
} )

describe('mapDispatchToProps', () => {
it('hooks into onDataNormalization', () => {
const dispatch = jest.fn();
const ev = {
target: {
value: 123
}
}
mapDispatchToProps(dispatch).onDataNormalization( ev );
expect(dispatch.mock.calls.length).toEqual(1);
})
})

describe( 'mapStateToProps', () => {
it( 'maps state and props', () => {
const state = {
map: {
dataNormalization: 'foo'
}
}
let actual = mapStateToProps( state )
expect( actual ).toEqual( { dataNormalization: 'foo' } )
} )
} )


} )
42 changes: 25 additions & 17 deletions src/__tests__/TileChartMap.spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,9 @@ describe( 'component: TileChartMap', () => {

it( 'redraw when the data is the same but map element is missing', () => {
// append children to mock test
target = shallow( <TileChartMap data={ [ [ 23, 4, 3 ] ] }/> )
target = shallow( <TileChartMap data={ [ [ { name: 'TX', value: 100 } ] ] }/> )
redrawSpy = jest.spyOn( target.instance(), '_redrawMap' )
target.setProps( { data: [ [ 23, 4, 3 ] ] } )
target.setProps( { data: [ [ { name: 'TX', value: 100 } ] ] } )
expect( TileMap ).toHaveBeenCalledTimes( 1 )
expect( redrawSpy ).toHaveBeenCalledTimes( 1 )
} )
Expand All @@ -108,9 +108,9 @@ describe( 'component: TileChartMap', () => {
} )

it( 'trigger a new update when data changes', () => {
target = shallow( <TileChartMap data={ [ [ 23, 4, 3 ] ] }/> )
target = shallow( <TileChartMap data={ [ [ { name: 'TX', value: 100}, { name: 'LA', value: 10 } ] ] }/> )
redrawSpy = jest.spyOn( target.instance(), '_redrawMap' )
target.setProps( { data: [ [ 2, 5 ] ] } )
target.setProps( { data: [ [ { name: 'TX', value: 100 }, { name: 'LA', value: 100 } ] ] } )
expect( redrawSpy ).toHaveBeenCalledTimes( 1 )
expect( TileMap ).toHaveBeenCalledTimes( 1 )
} )
Expand All @@ -131,45 +131,53 @@ describe( 'component: TileChartMap', () => {
it( 'maps state and props', () => {
const state = {
map: {
dataNormalization: false,
state: [
// name comes from agg api
{ name: 'aa', issue: 'something', product: 'a prod' },
{ name: 'bb', issue: 'something', product: 'b prod' },
{ name: 'cc', issue: 'something', product: 'c prod' }
{ name: 'TX', issue: 'something', product: 'a prod', value: 100000 },
{ name: 'LA', issue: 'something', product: 'b prod', value: 2 },
{ name: 'CA', issue: 'something', product: 'c prod', value: 3 }
],
// fyi Selected State comes from map
selectedState: { abbr: 'bb', name: 'bb state' }
selectedState: { abbr: 'TX', name: 'Texas' }
},
query: {
state: [ 'aa' ]
state: [ 'TX' ]
}
}
let actual = mapStateToProps( state )
expect( actual ).toEqual( {
data: [
[
{
name: 'aa',
name: 'TX',
className: 'selected',
issue: 'something',
product: 'a prod'
perCapita: '3.65',
product: 'a prod',
value: 100000
},
{
name: 'bb',
name: 'LA',
className: 'deselected',
issue: 'something',
product: 'b prod'
perCapita: '0.00',
product: 'b prod',
value: 2
},
{
name: 'cc',
name: 'CA',
className: 'deselected',
issue: 'something',
product: 'c prod'
perCapita: '0.00',
product: 'c prod',
value: 3
}
]
],
stateFilters: [ 'aa' ],
selectedState: { abbr: 'bb', name: 'bb state' }
dataNormalization: false,
stateFilters: [ 'TX' ],
selectedState: { abbr: 'TX', name: 'Texas' }
} )
} )
} )
Expand Down
Loading

0 comments on commit 48c28ea

Please sign in to comment.