First, this is the final front-end interaction looks like:
From the technical point of view, The interactive data visualization involved over 10,000 data strings and a series of states that frequently updating and synchronizing in different components.
So I decided using Redux
for managing the state and monitoring the events.
Redux
is a pattern and library for state managing. It serves as a centralized store for state that needs to be used across entire applications. For more information please check out Redux Intro.
In general, the interaction design consists of two parts: 3D globe in WebGL and UI elements.
- GeoSyria.Container - read only the coordinates data and deaths number of individual battle.
- GeoSyria.UI - read and update the state of application.
Here is the data flow of the frontend application:
Basic components of Redux
ActionTypes
- differentiate the interactive types that can be synchronized betweenreducer
andActionCreators
.
export const ACTION_TYPES = {
DATA_LOAD: 'data_load',
DATA_FILTER: 'data_filter',
DATA_SUM: 'data_sum',
DATA_DETAIL: 'data_detail'
}
Reducers
- Copy, Update states (aka. data) when actions are triggered.
the reducer
always accepts two parameters initialState
and action
:
export const dataReducer = (initialState, action) => {
console.log("dataReducer: " + action.payload);
switch (action.type) {
case ACTION_TYPES.DATA_LOAD:
return {
...initialState,
[action.payload.dataType]: action.payload.conflictList
};
case ACTION_TYPES.DATA_FILTER:
return {
...initialState,
[action.payload.dataType]: action.payload.filter,
[`${action.payload.dataType}_param`]: action.payload.param
...
storeData
- store the data and wrap it into thereact
components that interact with the data.
...
export const storeData = createStore(dataReducer, applyMiddleware(asyncActions));
...
<Redux.Provider store={storeData}>
<GeoSyria.UI />
<GeoSyria.Container />
</Redux.Provider>
Redux.connect
- For those components, who are involved in the interaction with data, it's important to connect thestate
anddispatch
(aka. actions) usingRedux.connect
.
const mapState = storeData => ({...storeData})
const mapDispatch = {
...Actions
}
export default connect(mapState, mapDispatch)(class extends Component {
render() {
return
<React.Fragment>
{this.props.coordinate && <GeoSyriaUI {...this.props} />}
{this.props.coordinate && <GeoSyriaContainer coordinate={this.props.coordinate} filter={this.props.filter}
details={this.props.details}/>}
</React.Fragment>
}
// first data query when application loading
componentDidMount() {
this.props.getData(DATA_TYPES.COORDINATION)
this.props.getTotal(DATA_TYPES.SUM)
}
})
Trigger actions
After the preparation in the redux components, the actions
and states
are automatically stored in the react components' props
, call the action
will update the state globally.
In the button component:
export const TabPanel = ({coordinate, getDetail, cleanDetail, details, checked}) => {
const classes = useStyles();
const handleClick = id => {
details._id === id ? cleanDetail(DATA_TYPES.DETAILS) : getDetail(DATA_TYPES.DETAILS, {id});
}
...
The getDetail
and cleanDetail
are functions for queries that store in the ActionCreators
:
export const getDetail = (dataType, param) => ({
type: ACTION_TYPES.DATA_DETAIL,
payload: dataQuery.getBasicData(dataType, param).then(response => ({
dataType,
detail: response.data
}))
})
export const cleanDetail = (dataType) => ({
type: ACTION_TYPES.DATA_DETAIL,
payload: {
dataType,
detail: null
}
})