kepler.gl
Version:
kepler.gl is a webgl based application to visualize large scale location data in the browser
191 lines (167 loc) • 6.3 kB
JavaScript
// Copyright (c) 2020 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import React, {Component} from 'react';
import styled from 'styled-components';
import moment from 'moment';
import {requestAnimationFrame, cancelAnimationFrame} from 'global/window';
import Slider from 'components/common/slider/slider';
import {BottomWidgetInner} from 'components/common/styled-components';
import SpeedControlFactory from './speed-control';
import AnimationPlaybacksFactory from './playback-controls';
import FloatingTimeDisplayFactory from './floating-time-display';
import {BASE_SPEED} from 'constants/default-settings';
const SliderWrapper = styled.div`
display: flex;
position: relative;
flex-grow: 1;
margin-right: 24px;
margin-left: 24px;
`;
const AnimationWidgetInner = styled.div`
position: relative;
display: flex;
align-items: center;
height: 32px;
.animation-control__speed-control {
margin-right: -10px;
.animation-control__speed-slider {
right: calc(0% - 10px);
}
}
`;
const StyledDomain = styled.div`
color: ${props => props.theme.titleTextColor};
font-weight: 400;
font-size: 10px;
`;
const defaultTimeFormat = 'MM/DD/YY hh:mm:ss';
const BUTTON_HEIGHT = '18px';
AnimationControlFactory.deps = [
SpeedControlFactory,
AnimationPlaybacksFactory,
FloatingTimeDisplayFactory
];
function AnimationControlFactory(SpeedControl, AnimationPlaybacks, FloatingTimeDisplay) {
class AnimationControl extends Component {
constructor(props) {
super(props);
this.state = {
isAnimating: false,
width: 288,
showSpeedControl: false
};
this._animation = null;
}
componentDidUpdate() {
if (!this._animation && this.state.isAnimating) {
this._animation = requestAnimationFrame(this._nextFrame);
}
}
onSlider1Change = val => {
const {domain} = this.props.animationConfig;
if (val >= domain[0] && val <= domain[1]) {
this.props.updateAnimationTime(val);
}
};
_updateAnimationTime = () => {
const {domain} = this.props.animationConfig;
this.props.updateAnimationTime(domain[0]);
this._startAnimation();
};
_startAnimation = () => {
this._pauseAnimation();
this.setState({isAnimating: true});
};
_nextFrame = () => {
this._animation = null;
const {currentTime, domain, speed = 1} = this.props.animationConfig;
const adjustedSpeed = ((domain[1] - domain[0]) / BASE_SPEED) * speed;
const nextTime = currentTime + speed > domain[1] ? domain[0] : currentTime + adjustedSpeed;
this.props.updateAnimationTime(nextTime);
};
_pauseAnimation = () => {
if (this._animation) {
cancelAnimationFrame(this._animation);
this._animation = null;
}
this.setState({isAnimating: false});
};
toggleSpeedControl = () => {
this.setState({showSpeedControl: !this.state.showSpeedControl});
};
onChange = () => {
this.toggleSpeedControl();
};
render() {
const {currentTime, domain, speed} = this.props.animationConfig;
const {showSpeedControl} = this.state;
return (
<BottomWidgetInner className="bottom-widget--inner">
<AnimationWidgetInner className="animation-widget--inner">
<div style={{marginLeft: '-10px'}}>
<AnimationPlaybacks
className="animation-control-playpause"
startAnimation={this._startAnimation}
isAnimating={this.state.isAnimating}
pauseAnimation={this._pauseAnimation}
resetAnimation={this._resetAnimation}
buttonHeight={BUTTON_HEIGHT}
buttonStyle="link"
/>
</div>
<StyledDomain className="animation-control__time-domain">
<span>{moment.utc(domain[0]).format(defaultTimeFormat)}</span>
</StyledDomain>
<SliderWrapper className="animation-control__slider">
<Slider
showValues={false}
isRanged={false}
minValue={domain ? domain[0] : 0}
maxValue={domain ? domain[1] : 1}
value1={currentTime}
onSlider1Change={this.onSlider1Change}
enableBarDrag={true}
/>
</SliderWrapper>
<StyledDomain className="animation-control__time-domain">
<span>{moment.utc(domain[1]).format(defaultTimeFormat)}</span>
</StyledDomain>
<div className="animation-control__speed-control">
<SpeedControl
onClick={this.toggleSpeedControl}
showSpeedControl={showSpeedControl}
updateAnimationSpeed={this.props.updateAnimationSpeed}
speed={speed}
buttonHeight={BUTTON_HEIGHT}
/>
</div>
</AnimationWidgetInner>
<FloatingTimeDisplay currentTime={currentTime} />
</BottomWidgetInner>
);
}
}
AnimationControl.defaultProps = {
sliderHandleWidth: 12,
onChange: () => {}
};
return AnimationControl;
}
export default AnimationControlFactory;