Skip to content

Commit

Permalink
Animate zooming to make it appear smoother (especially on Firefox).
Browse files Browse the repository at this point in the history
  • Loading branch information
fbarl committed Jul 31, 2017
1 parent ba24f83 commit e0beb1e
Showing 1 changed file with 43 additions and 36 deletions.
79 changes: 43 additions & 36 deletions client/app/scripts/components/time-travel-timeline.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,21 @@ const ZOOM_SENSITIVITY = 2;
const FADE_OUT_FACTOR = 1.4;


function getTimeScale({ focusedTimestamp, durationPerPixel }) {
const roundedTimestamp = moment(focusedTimestamp).utc().startOf('second');
const startDate = moment(roundedTimestamp).subtract(durationPerPixel);
const endDate = moment(roundedTimestamp).add(durationPerPixel);
return scaleUtc()
.domain([startDate, endDate])
.range([-1, 1]);
}

function findOptimalDurationFit(durations, { durationPerPixel }) {
const minimalDuration = scaleDuration(durationPerPixel, 1.1 * MIN_TICK_SPACING_PX);
return find(durations, d => d >= minimalDuration);
}


class TimeTravelTimeline extends React.Component {
constructor(props, context) {
super(props, context);
Expand Down Expand Up @@ -173,24 +188,8 @@ class TimeTravelTimeline extends React.Component {
this.jumpRelativePixels(-this.state.boundingRect.width / 4);
}

findOptimalDuration(durations) {
const { durationPerPixel } = this.state;
const minimalDuration = scaleDuration(durationPerPixel, 1.1 * MIN_TICK_SPACING_PX);
return find(durations, d => d >= minimalDuration);
}

getTimeScale(focusedTimestamp) {
const roundedTimestamp = moment(focusedTimestamp).utc().startOf('second');
const startDate = moment(roundedTimestamp).subtract(this.state.durationPerPixel);
const endDate = moment(roundedTimestamp).add(this.state.durationPerPixel);
return scaleUtc()
.domain([startDate, endDate])
.range([-1, 1]);
}

getVerticalShiftForPeriod(period) {
getVerticalShiftForPeriod(period, { durationPerPixel }) {
const { childPeriod, parentPeriod } = TICK_SETTINGS_PER_PERIOD[period];
const currentDuration = this.state.durationPerPixel;

let shift = 1;
if (parentPeriod) {
Expand All @@ -200,28 +199,28 @@ class TimeTravelTimeline extends React.Component {
const fadedOutDuration = scaleDuration(fadedInDuration, FADE_OUT_FACTOR);

const durationLog = d => Math.log(d.asMilliseconds());
const transitionFactor = durationLog(fadedOutDuration) - durationLog(currentDuration);
const transitionFactor = durationLog(fadedOutDuration) - durationLog(durationPerPixel);
const transitionLength = durationLog(fadedOutDuration) - durationLog(fadedInDuration);

shift = clamp(transitionFactor / transitionLength, 0, 1);
}

if (childPeriod) {
shift += this.getVerticalShiftForPeriod(childPeriod, currentDuration);
shift += this.getVerticalShiftForPeriod(childPeriod, { durationPerPixel });
}

return shift;
}

getTicksForPeriod(period, focusedTimestamp) {
getTicksForPeriod(period, timelineTransform) {
// First find the optimal duration between the ticks - if no satisfactory
// duration could be found, don't render any ticks for the given period.
const { parentPeriod, intervals } = TICK_SETTINGS_PER_PERIOD[period];
const duration = this.findOptimalDuration(intervals);
const duration = findOptimalDurationFit(intervals, timelineTransform);
if (!duration) return [];

// Get the boundary values for the displayed part of the timeline.
const timeScale = this.getTimeScale(focusedTimestamp);
const timeScale = getTimeScale(timelineTransform);
const startPosition = -this.state.boundingRect.width / 2;
const endPosition = this.state.boundingRect.width / 2;
const startDate = moment(timeScale.invert(startPosition));
Expand Down Expand Up @@ -288,11 +287,11 @@ class TimeTravelTimeline extends React.Component {
);
}

renderPeriodTicks(period, focusedTimestamp) {
renderPeriodTicks(period, timelineTransform) {
const periodFormat = TICK_SETTINGS_PER_PERIOD[period].format;
const ticks = this.getTicksForPeriod(period, focusedTimestamp);
const ticks = this.getTicksForPeriod(period, timelineTransform);

const verticalShift = this.getVerticalShiftForPeriod(period);
const verticalShift = this.getVerticalShiftForPeriod(period, timelineTransform);
const transform = `translate(0, ${62 - (verticalShift * 15)})`;

// Ticks quickly fade in from the bottom and then slowly
Expand All @@ -306,8 +305,8 @@ class TimeTravelTimeline extends React.Component {
);
}

renderDisabledShadow(focusedTimestamp) {
const timeScale = this.getTimeScale(focusedTimestamp);
renderDisabledShadow(timelineTransform) {
const timeScale = getTimeScale(timelineTransform);
const nowShift = timeScale(this.state.timestampNow);
const { width, height } = this.state.boundingRect;

Expand All @@ -320,7 +319,7 @@ class TimeTravelTimeline extends React.Component {
);
}

renderAxis(focusedTimestamp) {
renderAxis(timelineTransform) {
const { width, height } = this.state.boundingRect;

return (
Expand All @@ -330,23 +329,31 @@ class TimeTravelTimeline extends React.Component {
transform={`translate(${-width / 2}, 0)`}
width={width} height={height} fillOpacity={0}
/>
{this.renderDisabledShadow(focusedTimestamp)}
{this.renderDisabledShadow(timelineTransform)}
<g className="ticks">
{this.renderPeriodTicks('year', focusedTimestamp)}
{this.renderPeriodTicks('month', focusedTimestamp)}
{this.renderPeriodTicks('day', focusedTimestamp)}
{this.renderPeriodTicks('minute', focusedTimestamp)}
{this.renderPeriodTicks('year', timelineTransform)}
{this.renderPeriodTicks('month', timelineTransform)}
{this.renderPeriodTicks('day', timelineTransform)}
{this.renderPeriodTicks('minute', timelineTransform)}
</g>
</g>
);
}

renderAnimatedContent() {
const timestamp = this.state.focusedTimestamp.valueOf();
const focusedTimestampValue = this.state.focusedTimestamp.valueOf();
const durationPerPixelValue = this.state.durationPerPixel.asMilliseconds();

return (
<Motion style={{ timestamp: spring(timestamp, NODES_SPRING_FAST_ANIMATION_CONFIG) }}>
{interpolated => this.renderAxis(moment(interpolated.timestamp))}
<Motion
style={{
focusedTimestampValue: spring(focusedTimestampValue, NODES_SPRING_FAST_ANIMATION_CONFIG),
durationPerPixelValue: spring(durationPerPixelValue, NODES_SPRING_FAST_ANIMATION_CONFIG),
}}>
{interpolated => this.renderAxis({
focusedTimestamp: moment(interpolated.focusedTimestampValue),
durationPerPixel: moment.duration(interpolated.durationPerPixelValue),
})}
</Motion>
);
}
Expand Down

0 comments on commit e0beb1e

Please sign in to comment.