diff --git a/src/actions/index.js b/src/actions/index.js
index 9df2fe0..d701978 100644
--- a/src/actions/index.js
+++ b/src/actions/index.js
@@ -145,12 +145,12 @@ export function updateTagFilters(tag) {
}
}
-export const UPDATE_TIMERANGE = 'UPDATE_TIMERANGE'
-export function updateTimeRange(range) {
+export const UPDATE_TIMERANGE = 'UPDATE_TIMERANGE';
+export function updateTimeRange(timerange) {
return {
type: UPDATE_TIMERANGE,
- range
- }
+ timerange
+ };
}
export const RESET_ALLFILTERS = 'RESET_ALLFILTERS'
diff --git a/src/components/Card.jsx b/src/components/Card.jsx
index 01dab45..bb2864e 100644
--- a/src/components/Card.jsx
+++ b/src/components/Card.jsx
@@ -58,30 +58,6 @@ class Card extends React.Component {
);
}
- // NB: is this function for a future feature?
- renderIncidents() {
- const incident_type_lang = copy[this.props.language].cardstack.incident_type;
- const incidentTags = []; //this.props.event.tags.filter(tag => tag.type === 'incident_type');
-
- return (
-
{incident_type_lang}
- {
- incidentTags.map((tag, idx) => {
- return (
- {tag.name}{
- (idx < incidentTags.length - 1)
- ? ','
- : ''
- }
- );
- })
- }
- );
- }
-
renderSummary() {
const summary = copy[this.props.language].cardstack.description;
const desc = this.props.event.description;
@@ -162,15 +138,6 @@ class Card extends React.Component {
}
}
- renderHeader() {
- return (
- {this.renderWarning()}
- {this.renderCategory()}
- {this.renderTimestamp()}
- {this.renderSummary()}
-
);
- }
-
renderCardLink(event, direction) {
if (event !== null) {
const timelabel = this.getTimeLabel();
@@ -183,6 +150,7 @@ class Card extends React.Component {
renderNarrative() {
const links = this.props.getNarrativeLinks(this.props.event);
+
if (links !== null) {
return (
Connected events
@@ -192,6 +160,22 @@ class Card extends React.Component {
}
}
+ renderSpinner() {
+ return (
);
+ }
+
+ renderHeader() {
+ return (
+ {this.renderWarning()}
+ {this.renderCategory()}
+ {this.renderTimestamp()}
+ {this.renderSummary()}
+
);
+ }
+
renderContent() {
if (this.state.isFolded) {
return (
);
@@ -200,31 +184,15 @@ class Card extends React.Component {
{this.renderSpinner()}
);
} else {
- if (!this.props.event.hasOwnProperty('receiver') && !this.props.event.hasOwnProperty('transmitter')) {
- return (
- {this.renderLocation()}
- {this.renderTags()}
- {this.renderSource()}
- {this.renderNarrative()}
-
);
- } else {
- return (
- {this.renderTags()}
- {this.renderSource()}
- {this.renderNarrative()}
-
);
- }
+ return (
+ {this.renderLocation()}
+ {this.renderTags()}
+ {this.renderSource()}
+ {this.renderNarrative()}
+
);
}
}
-
- renderSpinner() {
- return ();
- }
-
renderArrow() {
let classes = (this.state.isFolded)
? 'arrow-down folded'
diff --git a/src/components/Dashboard.jsx b/src/components/Dashboard.jsx
index 6018db0..7e31d31 100644
--- a/src/components/Dashboard.jsx
+++ b/src/components/Dashboard.jsx
@@ -38,11 +38,11 @@ class Dashboard extends React.Component {
handleSelect(selected) {
if (selected) {
- // attacks are not susceptible to tag filters, so make sure this happens only when they are found
- // in the domain
let eventsToSelect = selected.map(eventId => this.props.domain.events[eventId]);
+ const parser = this.props.ui.tools.parser;
+
eventsToSelect = eventsToSelect.sort((a, b) => {
- return this.props.ui.tools.parser(a.timestamp) - this.props.ui.tools.parser(b.timestamp);
+ return parser(a.timestamp) - parser(b.timestamp);
});
if (eventsToSelect.every(event => (event))) {
@@ -58,7 +58,7 @@ class Dashboard extends React.Component {
});
eventsSelected = eventsSelected.sort((a, b) => {
- return this.props.ui.tools.parser(a.timestamp) - this.props.ui.tools.parser(b.timestamp);
+ return parser(a.timestamp) - parser(b.timestamp);
});
this.props.actions.updateSelected(eventsSelected);
@@ -105,8 +105,8 @@ class Dashboard extends React.Component {
}
getCategoryLabel(category) {
- const label = this.props.domain.categories.find(t => t.category === category).category_label;
- return label;
+ const categories = this.props.domain.categories;
+ return categories.find(t => t.category === category).category_label;
}
getNarrativeLinks(event) {
@@ -176,7 +176,7 @@ class Dashboard extends React.Component {
narratives={this.props.domain.narratives}
categoryGroups={this.props.domain.categoryGroups}
- range={this.props.app.filters.range}
+ timerange={this.props.app.filters.timerange}
selected={this.props.app.selected}
language={this.props.app.language}
@@ -202,7 +202,7 @@ class Dashboard extends React.Component {
toggle={() => this.handleToggle('TOGGLE_NOTIFICATIONS')}
/>
@@ -218,21 +218,23 @@ function mapStateToProps(state) {
return Object.assign({}, state, {
domain: Object.assign({}, state.domain, {
- events: selectors.getFilteredEvents(state),
- locations: selectors.getFilteredLocations(state),
- categories: selectors.getFilteredCategories(state),
- categoryGroups: selectors.getCategoryGroups(state),
- sites: selectors.getSites(state),
- tags: selectors.getAllTags(state),
- narratives: selectors.getFilteredNarratives(state),
+ // These items are affected by app selectionFilters
+ events: selectors.selectEvents(state),
+ locations: selectors.selectLocations(state),
+ categories: selectors.selectCategories(state),
+ categoryGroups: selectors.selectCategoryGroups(state),
+ narratives: selectors.selectNarratives(state),
- notifications: state.domain.notifications,
+ // These items are not affected by selectionFilters
+ sites: selectors.getSites(state),
+ tags: selectors.getTagTree(state),
+ notifications: selectors.getNotifications(state)
}),
app: Object.assign({}, state.app, {
error: state.app.error,
filters: Object.assign({}, state.app.filters, {
- range: selectors.getRangeFilter(state),
- tags: selectors.getTagFilters(state)
+ timerange: selectors.getTimeRange(state),
+ tags: selectors.selectTagList(state)
})
}),
ui: state.ui
diff --git a/src/components/LoadingOverlay.jsx b/src/components/LoadingOverlay.jsx
index ab7145e..ad455f3 100644
--- a/src/components/LoadingOverlay.jsx
+++ b/src/components/LoadingOverlay.jsx
@@ -1,9 +1,9 @@
import React from 'react';
import copy from '../js/data/copy.json';
-const LoadingOverlay = ({ ui, language }) => {
+const LoadingOverlay = ({ isFetchingDomain, language }) => {
let classes = 'loading-overlay';
- classes += (!ui.flags.isFetchingDomain) ? ' hidden' : '';
+ classes += (!isFetchingDomain) ? ' hidden' : '';
return (
diff --git a/src/components/Timeline.jsx b/src/components/Timeline.jsx
index a7e3157..e388ad0 100644
--- a/src/components/Timeline.jsx
+++ b/src/components/Timeline.jsx
@@ -16,7 +16,7 @@ class Timeline extends React.Component {
categoryGroups: this.props.categoryGroups
}
const app = {
- range: this.props.range,
+ timerange: this.props.timerange,
selected: this.props.selected,
language: this.props.language,
select: this.props.select,
@@ -43,7 +43,7 @@ class Timeline extends React.Component {
}
const app = {
- range: nextProps.range,
+ timerange: nextProps.timerange,
selected: nextProps.selected,
language: nextProps.language,
select: nextProps.select,
@@ -75,8 +75,8 @@ class Timeline extends React.Component {
const labels_title_lang = copy[this.props.language].timeline.labels_title;
const info_lang = copy[this.props.language].timeline.info;
let classes = `timeline-wrapper ${(this.state.isFolded) ? ' folded' : ''}`;
- const date0 = this.props.tools.formatterWithYear(this.props.range[0]);
- const date1 = this.props.tools.formatterWithYear(this.props.range[1]);
+ const date0 = this.props.tools.formatterWithYear(this.props.timerange[0]);
+ const date1 = this.props.tools.formatterWithYear(this.props.timerange[1]);
return (
diff --git a/src/components/View2D.jsx b/src/components/View2D.jsx
deleted file mode 100644
index c617f74..0000000
--- a/src/components/View2D.jsx
+++ /dev/null
@@ -1,63 +0,0 @@
-import '../scss/main.scss';
-import React from 'react';
-import Map from '../js/map/map.js';
-import { areEqual } from '../js/data/utilities.js';
-
-class View2D extends React.Component {
- constructor(props) {
- super(props);
- }
-
- componentDidMount() {
- const domain = {
- locations: this.props.locations,
- narratives: this.props.narratives,
- sites: this.props.sites,
- categoryGroups: this.props.categoryGroups
- }
- const app = {
- views: this.props.views,
- selected: this.props.selected,
- highlighted: this.props.highlighted,
- getCategoryGroup: this.props.getCategoryGroup,
- getCategoryGroupColor: this.props.getCategoryGroupColor,
- mapAnchor: this.props.mapAnchor
- }
- const ui = {
- style: this.props.uiStyle,
- dom: this.props.dom
- }
-
- this.map = new Map(app, ui, this.props.select);
- this.map.update(domain, app);
- }
-
- componentWillReceiveProps(nextProps) {
- const domain = {
- locations: nextProps.locations,
- narratives: nextProps.narratives,
- sites: nextProps.sites,
- categoryGroups: nextProps.categoryGroups
- }
- const app = {
- views: nextProps.views,
- selected: nextProps.selected,
- highlighted: nextProps.highlighted,
- getCategoryGroup: nextProps.getCategoryGroup,
- getCategoryGroupColor: nextProps.getCategoryGroupColor,
- mapAnchor: this.props.mapAnchor
- }
-
- this.map.update(domain, app);
- }
-
- render() {
- return (
-
- );
- }
-}
-
-export default View2D;
diff --git a/src/components/Viewport.jsx b/src/components/Viewport.jsx
index e3620bb..2bac985 100644
--- a/src/components/Viewport.jsx
+++ b/src/components/Viewport.jsx
@@ -1,36 +1,62 @@
import '../scss/main.scss';
import React from 'react';
-import View2D from './View2D.jsx';
+import Map from '../js/map/map.js';
+import { areEqual } from '../js/data/utilities.js';
class Viewport extends React.Component {
constructor(props) {
super(props);
}
- render() {
- if( this.props.isView2d ) {
- return (
-
this.props.getCategoryGroupColor(category)}
- getCategoryGroup={category => this.props.getCategoryGroup(category)}
- />
- );
+ componentDidMount() {
+ const domain = {
+ locations: this.props.locations,
+ narratives: this.props.narratives,
+ sites: this.props.sites,
+ categoryGroups: this.props.categoryGroups
}
+ const app = {
+ views: this.props.views,
+ selected: this.props.selected,
+ highlighted: this.props.highlighted,
+ getCategoryGroup: this.props.getCategoryGroup,
+ getCategoryGroupColor: this.props.getCategoryGroupColor,
+ mapAnchor: this.props.mapAnchor
+ }
+ const ui = {
+ style: this.props.uiStyle,
+ dom: this.props.dom
+ }
+
+ this.map = new Map(app, ui, this.props.select);
+ this.map.update(domain, app);
+ }
+
+ componentWillReceiveProps(nextProps) {
+ const domain = {
+ locations: nextProps.locations,
+ narratives: nextProps.narratives,
+ sites: nextProps.sites,
+ categoryGroups: nextProps.categoryGroups
+ }
+ const app = {
+ views: nextProps.views,
+ selected: nextProps.selected,
+ highlighted: nextProps.highlighted,
+ getCategoryGroup: nextProps.getCategoryGroup,
+ getCategoryGroupColor: nextProps.getCategoryGroupColor,
+ mapAnchor: this.props.mapAnchor
+ }
+
+ this.map.update(domain, app);
+ }
+
+ render() {
+ return (
+
+ );
}
}
diff --git a/src/js/timeline/timeline.js b/src/js/timeline/timeline.js
index 662b0ab..f29b20d 100644
--- a/src/js/timeline/timeline.js
+++ b/src/js/timeline/timeline.js
@@ -54,7 +54,7 @@ export default function(app, ui) {
let events = [];
let categoryGroups = [];
let selected = [];
- let range = app.range;
+ let timerange = app.timerange;
const timeFilter = app.filter;
const select = app.select;
@@ -90,7 +90,7 @@ export default function(app, ui) {
const scale = {};
scale.x = d3.scaleTime()
- .domain(range)
+ .domain(timerange)
.range([mg.l, WIDTH]);
const groupStep = (106 - 30) / categoryGroups.length;
@@ -222,8 +222,8 @@ export default function(app, ui) {
const dragNow = scale.x.invert(d3.event.x).getTime();
const timeShift = (drag0 - dragNow) / 1000;
- const newDomain0 = d3.timeSecond.offset(range[0], timeShift);
- const newDomainF = d3.timeSecond.offset(range[1], timeShift);
+ const newDomain0 = d3.timeSecond.offset(timerange[0], timeShift);
+ const newDomainF = d3.timeSecond.offset(timerange[1], timeShift);
scale.x.domain([newDomain0, newDomainF])
render();
@@ -417,7 +417,7 @@ export default function(app, ui) {
* It automatically sets brush timeline to a domain set by the params
*/
function updateTimeRange() {
- scale.x.domain(range);
+ scale.x.domain(timerange);
axis.x0.scale(scale.x);
axis.x1.scale(scale.x);
}
@@ -430,12 +430,12 @@ export default function(app, ui) {
dom.axis.label0
.attr('x', 5)
.attr('y', 15)
- .text(formatterWithYear(range[0]));
+ .text(formatterWithYear(timerange[0]));
dom.axis.label1
.attr('x', WIDTH - 5)
.attr('y', 15)
- .text(formatterWithYear(range[1]))
+ .text(formatterWithYear(timerange[1]))
.style('text-anchor', 'end');
}
@@ -618,7 +618,7 @@ export default function(app, ui) {
renderAxis();
events = domain.events;
- range = app.range;
+ timerange = app.timerange;
selected = app.selected.slice(0);
updateTimeRange();
}
diff --git a/src/reducers/app.js b/src/reducers/app.js
index d9f579b..d9bd94a 100644
--- a/src/reducers/app.js
+++ b/src/reducers/app.js
@@ -48,7 +48,7 @@ function updateTagFilters(appState, action) {
function updateTimeRange(appState, action) { // XXX
return Object.assign({}, appState, {
filters: Object.assign({}, appState.filters, {
- range: action.range
+ timerange: action.timerange
}),
});
}
@@ -58,7 +58,7 @@ function resetAllFilters(appState) { // XXX
filters: Object.assign({}, appState.filters, {
tags: [],
categories: [],
- range: [
+ timerange: [
d3.timeParse("%Y-%m-%dT%H:%M:%S")("2014-09-25T12:00:00"),
d3.timeParse("%Y-%m-%dT%H:%M:%S")("2014-09-28T12:00:00")
],
diff --git a/src/selectors/index.js b/src/selectors/index.js
index 2114159..9be31ca 100644
--- a/src/selectors/index.js
+++ b/src/selectors/index.js
@@ -10,61 +10,67 @@ export const getSites = (state) => {
if (process.env.features.USE_SITES) return state.domain.sites
return []
}
-export const getAllTags = state => state.domain.tags
+export const getNotifications = state => state.domain.notifications;
+export const getTagTree = state => state.domain.tags;
+export const getTagsFilter = state => state.app.filters.tags;
+export const getTimeRange = state => state.app.filters.timerange;
-export const getCategoriesFilter = state => state.app.filters.categories
-export const getTagsFilter = state => state.app.filters.tags
-export const getRangeFilter = state => state.app.filters.range
+/**
+* Some handy helpers
+*/
+const parseTimestamp = ts => d3.timeParse("%Y-%m-%dT%H:%M:%S")(ts);
-// NB: should we stick with the default semantics and name these as selectors?
-// e.g. 'selectEvents', 'selectCoevents'.
-// Filter events
-function isTaggedIn (event, tagFilters) {
+/**
+ * Given an event and all tags,
+ * returns true/false if event has any tag that is active
+ */
+function isTaggedIn(event, tagFilters) {
if (event.tags) {
- const tagsArray = event.tags.split(',')
- const isTagged = tagsArray.some((tag) => {
- return tagFilters.find((tagFilter) => {
- return (tagFilter.key === tag && tagFilter.active)
- })
- })
- return isTagged
+ const tagsInEvent = event.tags.split(",");
+ const isTagged = tagsInEvent.some((tag) => {
+ return tagFilters.find(tF => (tF.key === tag && tF.active));
+ });
+ return isTagged;
} else {
return false
}
}
+/**
+ * Given an event and a time range,
+ * returns true/false if the event falls within timeRange
+ */
+function isTimeRangedIn(event, timeRange) {
+ return (
+ timeRange[0] < parseTimestamp(event.timestamp)
+ && parseTimestamp(event.timestamp) < timeRange[1]
+ );
+}
+
+
+function isNoTags(tagFilters) {
+ return (
+ tagFilters.length === 0
+ || !process.env.features.USE_TAGS
+ || tagFilters.every(t => !t.active)
+ );
+}
+
/**
* Of all available events, selects those that fall within the time range,
* and if TAGS are being used, select them if their tags are enabled
*/
-export const getFilteredEvents = createSelector(
- [getEvents, getTagsFilter, getRangeFilter],
- (events, tagFilters, rangeFilter) => {
- return events.reduce((acc, value) => {
- const noTags = (tagFilters.length === 0 || !process.env.features.USE_TAGS || tagFilters.every(t => !t.active))
+export const selectEvents = createSelector(
+ [getEvents, getTagsFilter, getTimeRange],
+ (events, tagFilters, timeRange) => {
- const isTagged = (noTags) || isTaggedIn(value, tagFilters)
+ return events.reduce((acc, event) => {
+ const isTagged = isTaggedIn(event, tagFilters) || isNoTags(tagFilters);
+ const isTimeRanged = isTimeRangedIn(event, timeRange);
- // TODO: put this datetime format as a constant
- const isRange = (rangeFilter[0] < d3.timeParse('%Y-%m-%dT%H:%M:%S')(value.timestamp)) &&
- (d3.timeParse('%Y-%m-%dT%H:%M:%S')(value.timestamp) < rangeFilter[1])
-
-<<<<<<< HEAD
- if (isRange && isTagged) {
- const event = Object.assign({}, value)
- acc[event.id] = event
- }
- return acc
- }, [])
- }
-)
-=======
- const isRange = (rangeFilter[0] < d3.timeParse("%Y-%m-%dT%H:%M:%S")(value.timestamp)) &&
- (d3.timeParse("%Y-%m-%dT%H:%M:%S")(value.timestamp) < rangeFilter[1]);
-
- if (isRange && isTagged) {
- const event = Object.assign({}, value);
- acc[event.id] = event;
+ if (isTimeRanged && isTagged) {
+ const eventClone = Object.assign({}, event);
+ acc[event.id] = eventClone;
}
return acc;
@@ -76,21 +82,20 @@ export const getFilteredEvents = createSelector(
* Of all available events, selects those that fall within the time range,
* and if TAGS are being used, select them if their tags are enabled
*/
-const parseTimestamp = ts => d3.timeParse("%Y-%m-%dT%H:%M:%S")(ts);
-export const getFilteredNarratives = createSelector(
- [getEvents, getTagsFilter, getRangeFilter],
- (events, tagFilters, rangeFilter) => {
+export const selectNarratives = createSelector(
+ [getEvents, getTagsFilter, getTimeRange],
+ (events, tagFilters, timeRange) => {
const narratives = {};
events.forEach((evt) => {
- const noTags = (tagFilters.length === 0 || !process.env.features.USE_TAGS || tagFilters.every(t => !t.active));
+ const isTagged = isTaggedIn(evt, tagFilters) || isNoTags(tagFilters);
+ const isTimeRanged = isTimeRangedIn(evt, timeRange);
+ const isInNarrative = evt.narrative;
- const isTagged = (noTags) || isTaggedIn(evt, tagFilters);
- const isRange = (rangeFilter[0] < parseTimestamp(evt.timestamp)) &&
- (parseTimestamp(evt.timestamp) < rangeFilter[1]);
-
- if (isRange && isTagged && evt.narrative) {
- if (!narratives[evt.narrative]) narratives[evt.narrative] = { key: evt.narrative, steps: [], byId: {} };
+ if (isTimeRanged && isTagged && isInNarrative) {
+ if (!narratives[evt.narrative]) {
+ narratives[evt.narrative] = { key: evt.narrative, steps: [], byId: {} };
+ }
narratives[evt.narrative].steps.push(evt);
narratives[evt.narrative].byId[evt.id] = { next: null, prev: null };
}
@@ -112,16 +117,18 @@ export const getFilteredNarratives = createSelector(
* Of all the filtered events, group them by location and return a list of
* locations with at least one event in it, based on the time range and tags
*/
-export const getFilteredLocations = createSelector(
- [getFilteredEvents],
+export const selectLocations = createSelector(
+ [selectEvents],
(events) => {
- const filteredLocations = {}
+
+ const selectedLocations = {};
events.forEach(event => {
- const location = event.location
- if (filteredLocations[location]) {
- filteredLocations[location].events.push(event)
+ const location = event.location;
+
+ if (selectedLocations[location]) {
+ selectedLocations[location].events.push(event);
} else {
- filteredLocations[location] = {
+ selectedLocations[location] = {
label: location,
events: [event],
latitude: event.latitude,
@@ -130,51 +137,60 @@ export const getFilteredLocations = createSelector(
}
})
- // Make locations an array are remove if any are undefined
- return Object.values(filteredLocations).filter(item => item)
- }
-)
+ // Make locations an array are remove if any are undefined
+ return Object.values(selectedLocations).filter(item => item);
+});
-// Filter categories
-export const getFilteredCategories = createSelector(
+
+/*
+* Select categories, return them as a list
+*/
+export const selectCategories = createSelector(
[getCategories],
- (categories) => Object.values(categories))
+ (categories) => {
+ return Object.values(categories);
+ }
+);
/**
* Return categories by group
*/
-export const getCategoryGroups = createSelector(
- [getFilteredCategories],
+export const selectCategoryGroups = createSelector(
+ [selectCategories],
(categories) => {
- const groups = {}
- categories.forEach((t) => { if (t.group && !groups[t.group]) { groups[t.group] = t.group_label } })
- return Object.keys(groups).concat(['other'])
+ const groups = {};
+ categories.forEach((t) => {
+ if (t.group && !groups[t.group]) {
+ groups[t.group] = t.group_label;
+ }
+ });
+ return Object.keys(groups).concat(['other']);
}
-)
+);
/**
- * Given a tree of tags, return those tags as a list, where each node has been
- * aware of its depth, and given an 'active' flag
+ * Given a tree of tags, return those tags as a list
+ * Each node has been aware of its depth, and given an 'active' flag
*/
-export const getTagFilters = createSelector(
- [getAllTags],
+export const selectTagList = createSelector(
+ [getTagTree],
(tags) => {
- const allTagFilters = []
- let depth = 0
- function traverseNode (node, depth) {
- node.active = (!node.hasOwnProperty('active')) ? false : node.active
- node.depth = depth
- if (node.active) allTagFilters.push(node)
- depth = depth + 1
+ const tagList = [];
+ let depth = 0;
+ function traverseNode(node, depth) {
+ node.active = (!node.hasOwnProperty('active')) ? false : node.active;
+ node.depth = depth;
+
+ if (node.active) tagList.push(node)
if (Object.keys(node.children).length > 0) {
Object.values(node.children).forEach((childNode) => {
- traverseNode(childNode, depth)
- })
+ traverseNode(childNode, depth + 1);
+ });
}
}
- if (tags && tags.key && tags.children) traverseNode(tags, depth)
- return allTagFilters
+ if (tags.key && tags.children) traverseNode(tags, depth)
+ return tagList;
}
)
diff --git a/src/store/initial.js b/src/store/initial.js
index fd2e900..ba925fe 100644
--- a/src/store/initial.js
+++ b/src/store/initial.js
@@ -7,7 +7,7 @@ const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl')
const initial = {
domain: {
events: [],
- narratives: [],
+ narratives: [],
locations: [],
categories: [],
@@ -23,7 +23,7 @@ const initial = {
selected: [],
notifications: [],
filters: {
- range: [
+ timerange: [
d3.timeParse("%Y-%m-%dT%H:%M:%S")("2014-08-22T12:00:00"),
d3.timeParse("%Y-%m-%dT%H:%M:%S")("2014-08-27T12:00:00")
],