diff --git a/src/components/Timeline.jsx b/src/components/Timeline.jsx
index b7d802c..0c54cd1 100644
--- a/src/components/Timeline.jsx
+++ b/src/components/Timeline.jsx
@@ -12,7 +12,6 @@ import TimelineLogic from '../js/timeline/timeline.js';
import TimelineLabels from './TimelineLabels.jsx';
import TimelineMarkers from './TimelineMarkers.jsx'
import TimelineEvents from './TimelineEvents.jsx';
-import TimelineCategories from './TimelineCategories.jsx';
class Timeline extends React.Component {
constructor(props) {
@@ -26,20 +25,22 @@ class Timeline extends React.Component {
width_controls: 100,
height_controls: 115,
margin_left: 120
- }
+ },
+ softTimeUpdate: 0
};
}
componentDidMount() {
- this.timeline = new TimelineLogic(this.svgRef.current, this.props.app, this.props.ui, this.props.methods);
- this.timeline.update(this.props.domain, this.props.app);
+ this.methods = Object.assign({}, this.props.methods, { onSoftUpdate: (toggle) => { this.setState({ softTimeUpdate: toggle }) }});
+ this.timeline = new TimelineLogic(this.svgRef.current, this.props.ui, this.methods);
+ this.timeline.update(this.props.domain.categories, this.props.app.timerange);
this.computeDims();
window.addEventListener('resize', () => { this.computeDims(); });
}
componentWillReceiveProps(nextProps) {
if (hash(nextProps) !== hash(this.props)) {
- this.timeline.update(nextProps.domain, nextProps.app);
+ this.timeline.update(nextProps.domain.categories, nextProps.app.timerange);
}
}
@@ -69,7 +70,23 @@ class Timeline extends React.Component {
return this.timeline.applyZoom(zoom);
}
return null;
- }
+ }
+
+ renderTimelineClip() {
+ const dims = this.state.dims;
+
+ return (
+
+
+
+
+ );
+ }
renderSVG() {
const dims = this.state.dims;
@@ -80,17 +97,25 @@ class Timeline extends React.Component {
width={dims.width}
height={dims.height}
>
-
-
-
- { this.onMoveTime(dir) }} />
- { this.onApplyZoom(zoom); }} />
-
- {/* { this.timeline.onDragStart(ev) }}
- onDrag={(ev) => { this.timeline.onDrag(ev); }}
- />*/}
- this.timeline.getEventX(e)} getEventY={(e) => this.timeline.getEventY(e)} />
+ {this.renderTimelineClip()}
+ { this.onMoveTime(dir) }}
+ />
+ { this.onApplyZoom(zoom); }}
+ />
+
+ this.timeline.getEventX(e)}
+ getEventY={(e) => this.timeline.getEventY(e)}
+ />
this.timeline.getEventX(e)}
diff --git a/src/components/TimelineCategories.jsx b/src/components/TimelineCategories.jsx
deleted file mode 100644
index 07a3da3..0000000
--- a/src/components/TimelineCategories.jsx
+++ /dev/null
@@ -1,49 +0,0 @@
-import React from 'react';
-
-class TimelineCategories extends React.Component {
-
- constructor() {
- super();
-
- this.state = {
- isDragging: false,
- dragPos0: 0,
- scaleXDomain
- }
- }
-
- getY(idx) {
- const h = 76;
- console.log((idx + 1) * h / this.props.categories.length)
- return (idx + 1) * h / this.props.categories.length;
- }
-
- renderCategory(cat, idx) {
- return (
-
- { this.props.onDragStart(ev) }}
- onDrag={(ev) => { this.props.onDrag(ev) }}
- onMouseDown={this.setState({ isDragging: true })}
- onMouseUp={this.setState({ isDragging: false })}
- stroke="red" x2="-720">
- {cat.category}
-
- );
- }
-
- render () {
- return (
-
-
- {this.props.categories.map((cat, idx) => { return this.renderCategory(cat, idx); })}
-
- );
- }
-}
-
-export default TimelineCategories;
\ No newline at end of file
diff --git a/src/components/TimelineEvents.jsx b/src/components/TimelineEvents.jsx
index 9b8462e..60c676e 100644
--- a/src/components/TimelineEvents.jsx
+++ b/src/components/TimelineEvents.jsx
@@ -17,7 +17,7 @@ class TimelineEvents extends React.Component {
cy={0}
style={{
'transform': `translate(${this.props.getEventX(event)}px, ${this.props.getEventY(event)}px)`,
- 'transition': 'transform 0.5s ease'
+ 'transition': 'transform 0.3s ease'
}}
r={5}
fill={this.props.getCategoryColor(event.category)}
diff --git a/src/components/TimelineMarkers.jsx b/src/components/TimelineMarkers.jsx
index 893caea..a97b3cf 100644
--- a/src/components/TimelineMarkers.jsx
+++ b/src/components/TimelineMarkers.jsx
@@ -10,7 +10,7 @@ class TimelineMarkers extends React.Component {
cy={0}
style={{
'transform': `translate(${this.props.getEventX(event)}px, ${this.props.getEventY(event)}px)`,
- 'transition': 'transform 0.5s ease',
+ 'transition': 'transform 0.3s ease',
'opacity': 0.9
}}
r="10"
diff --git a/src/js/timeline/timeline.js b/src/js/timeline/timeline.js
index 23bd0b8..4c41b51 100644
--- a/src/js/timeline/timeline.js
+++ b/src/js/timeline/timeline.js
@@ -8,34 +8,31 @@ import { parseDate } from '../utilities';
import hash from 'object-hash';
import esLocale from '../data/es-MX.json';
-export default function(svg, newApp, ui, methods) {
+export default function(svg, ui, methods) {
d3.timeFormatDefaultLocale(esLocale);
- const domain = {
- categories: []
- }
- const app = {
- timerange: newApp.timerange,
- }
+ let categories = [];
+ let timerange = [null, null];
// Dimension of the client
const WIDTH_CONTROLS = 100;
let WIDTH = getCurrentWidth() - WIDTH_CONTROLS;
+ const HEIGHT = 80;
// NB: is it possible to do this with SCSS?
// A: Maybe, although we are using it programmatically here for now
- const margin = { left: 120 };
+ const margin = { left: 120, top: 20 };
// Drag behavior
let dragPos0;
- let transitionDuration = 500;
+ let transitionDuration = 300;
/**
* Create scales
*/
const scale = {};
scale.x = d3.scaleTime()
- .domain(app.timerange)
+ .domain(timerange)
.range([margin.left, WIDTH]);
scale.y = d3.scaleOrdinal()
@@ -72,13 +69,13 @@ export default function(svg, newApp, ui, methods) {
d3.axisBottom(scale.x)
.ticks(10)
.tickPadding(5)
- .tickSize(80)
+ .tickSize(HEIGHT)
.tickFormat(d3.timeFormat('%d %b'));
axis.x1 =
d3.axisBottom(scale.x)
.ticks(10)
- .tickPadding(20)
+ .tickPadding(margin.top)
.tickSize(0)
.tickFormat(d3.timeFormat('%H:%M'));
@@ -114,7 +111,7 @@ export default function(svg, newApp, ui, methods) {
}
addResizeListener();
- /*
+ /**
* Get y height of eventPoint, considering the ordinal Y scale
* @param {object} eventPoint: regular eventPoint data
*/
@@ -123,7 +120,7 @@ export default function(svg, newApp, ui, methods) {
return scale.y(yGroup);
}
- /*
+ /**
* Get x position of eventPoint, considering the time scale
* @param {object} eventPoint: regular eventPoint data
*/
@@ -131,14 +128,13 @@ export default function(svg, newApp, ui, methods) {
return scale.x(parseDate(eventPoint.timestamp));
}
+ /**
+ * Returns the time scale (x) extent in minutes
+ */
function getTimeScaleExtent() {
return (scale.x.domain()[1].getTime() - scale.x.domain()[0].getTime()) / 60000;
}
- function getScaleX() {
- return scale.x;
- }
-
/**
* Apply zoom level to timeline
* @param {object} zoom: zoom level from zoomLevels
@@ -147,10 +143,11 @@ export default function(svg, newApp, ui, methods) {
const extent = getTimeScaleExtent();
const newCentralTime = d3.timeMinute.offset(scale.x.domain()[0], extent / 2);
- const domain0 = d3.timeMinute.offset(newCentralTime, -zoom.duration / 2);
- const domainF = d3.timeMinute.offset(newCentralTime, zoom.duration / 2);
+ scale.x.domain([
+ d3.timeMinute.offset(newCentralTime, -zoom.duration / 2),
+ d3.timeMinute.offset(newCentralTime, zoom.duration / 2)
+ ]);
- scale.x.domain([domain0, domainF]);
methods.onUpdateTimerange(scale.x.domain());
}
@@ -192,23 +189,27 @@ export default function(svg, newApp, ui, methods) {
toggleTransition(false);
}
+ /*
+ * Drag and update
+ */
function onDrag() {
const drag0 = scale.x.invert(dragPos0).getTime();
const dragNow = scale.x.invert(d3.event.x).getTime();
const timeShift = (drag0 - dragNow) / 1000;
- const newDomain0 = d3.timeSecond.offset(app.timerange[0], timeShift);
- const newDomainF = d3.timeSecond.offset(app.timerange[1], timeShift);
+ const newDomain0 = d3.timeSecond.offset(timerange[0], timeShift);
+ const newDomainF = d3.timeSecond.offset(timerange[1], timeShift);
scale.x.domain([newDomain0, newDomainF]);
render();
- //app.timerange = scale.x.domain();
- //methods.onUpdateTimerange(scale.x.domain());
+ // Updates components without updating timerange
+ methods.onSoftUpdate(1);
}
function onDragEnd() {
toggleTransition(true);
- app.timerange = scale.x.domain();
+ timerange = scale.x.domain();
+ methods.onSoftUpdate(0);
methods.onUpdateTimerange(scale.x.domain());
}
@@ -217,6 +218,26 @@ export default function(svg, newApp, ui, methods) {
.on('drag', onDrag)
.on('end', onDragEnd);
+ /**
+ * Updates data displayed by this timeline, but only render if necessary
+ * @param {Object} domain: Redux state domain subtree
+ * @param {Object} app: Redux state app subtree
+ */
+ function updateAxis() {
+ let groupYs = Array.apply(null, Array(categories.length));
+ groupYs = groupYs.map((g, i) => {
+ return (i + 1) * HEIGHT / groupYs.length;
+ });
+ scale.y = d3.scaleOrdinal()
+ .domain(categories)
+ .range(groupYs);
+
+ axis.y =
+ d3.axisLeft(scale.y)
+ .tickValues(categories.map(c => c.category));
+ }
+
+
/**
* Render axis on timeline and viewbox boundaries
*/
@@ -237,9 +258,9 @@ export default function(svg, newApp, ui, methods) {
dom.axis.dragGrabber = dom.svg.insert('rect', ':first-child')
.attr('class', 'drag-grabber')
.attr('x', margin.left)
- .attr('y', 20)
+ .attr('y', margin.top)
.attr('width', WIDTH - margin.left)
- .attr('height', 80)
+ .attr('height', HEIGHT)
.call(drag);
}
@@ -253,41 +274,18 @@ export default function(svg, newApp, ui, methods) {
.call(axis.y);
}
- /**
- * Updates data displayed by this timeline, but only render if necessary
- * @param {Object} domain: Redux state domain subtree
- * @param {Object} app: Redux state app subtree
- */
- function updateAxis() {
- let groupYs = Array.apply(null, Array(domain.categories.length));
- groupYs = groupYs.map((g, i) => {
- const h = 76;
- return (i + 1) * h / groupYs.length;
- });
- scale.y = d3.scaleOrdinal()
- .domain(domain.categories)
- .range(groupYs);
-
- axis.y =
- d3.axisLeft(scale.y)
- .tickValues(domain.categories.map(c => c.category));
- }
-
/**
* Updates displayable data on the timeline: events, selected and
* potentially adjusts time range
- * @param {Object} newDomain: object of arrays of events and categories
- * @param {Object} newApp: object of time range and selected events
+ * @param {Object} newCategories: object of arrays of categories
+ * @param {Object} newTimerange: object of time range
*/
- function update(newDomain, newApp) {
- const isNewDomain = (hash(domain) !== hash(newDomain));
- const isNewAppProps = (hash(app) !== hash(newApp));
+ function update(newCategories, newTimerange) {
+ if (hash(categories) !== hash(newCategories)) categories = newCategories;
+ if (hash(timerange) !== hash(newTimerange)) timerange = newTimerange;
- if (isNewDomain) domain.categories = newDomain.categories;
- if (isNewAppProps) app.timerange = newApp.timerange;
-
- if (isNewDomain || isNewAppProps) render();
+ render();
}
function render() {
@@ -296,14 +294,10 @@ export default function(svg, newApp, ui, methods) {
}
return {
- getScaleX,
getEventX,
getEventY,
applyZoom,
moveTime,
- update,
- onDragStart,
- onDrag,
- onDragEnd
+ update
};
}
diff --git a/src/scss/timeline.scss b/src/scss/timeline.scss
index c7b1802..54d63a3 100644
--- a/src/scss/timeline.scss
+++ b/src/scss/timeline.scss
@@ -192,7 +192,6 @@
}
.event {
- fill: $event_default;
cursor: pointer;
opacity: .7;