Skip to main content

Data Visualization Part II

· 3 min read
Bowei Xiao

First, this is the final front-end interaction looks like: interaction sample

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.

info

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.

  1. GeoSyria.Container - read only the coordinates data and deaths number of individual battle.
  2. GeoSyria.UI - read and update the state of application.

Here is the data flow of the frontend application:

redux data flow diagram

Basic components of Redux

  • ActionTypes - differentiate the interactive types that can be synchronized between reducer and ActionCreators.
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 the react 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 the state and dispatch (aka. actions) using Redux.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
}
})