Javascript Deck.GL如何正确调整设置状态,以便Scatterplotlayer和Tripslayer可以同步渲染
(1) 在deck.gl中,叠加两层后实现视觉效果。现在实现的效果是,由于添加了动画,TripsLayer可以自动渲染,但ScatterplotLayer只能通过拖动下面的时间线进度条来实现动画效果,如下所示。也就是说,在叠加之后,它们现在看起来是两个不相关的层。在ScatterplotLayer(添加了DataFilterExtension)中,圆的半径仍由屏幕底部的进度条控制。拖动以更改大小,而不是类似TripsLayer的自动渲染 我希望它们能够实现基于公共属性时间戳的同步渲染。换言之,当杀雷击机的轨迹移动时,散射层的圆半径也会随之改变。我以前也在ScatterplotLayer中添加了动画,但没有效果,可能是Setstate未正确调整。非常感谢你的帮助~~ 现在实现的效果截图和完整代码如下:Javascript Deck.GL如何正确调整设置状态,以便Scatterplotlayer和Tripslayer可以同步渲染,javascript,reactjs,deck.gl,Javascript,Reactjs,Deck.gl,(1) 在deck.gl中,叠加两层后实现视觉效果。现在实现的效果是,由于添加了动画,TripsLayer可以自动渲染,但ScatterplotLayer只能通过拖动下面的时间线进度条来实现动画效果,如下所示。也就是说,在叠加之后,它们现在看起来是两个不相关的层。在ScatterplotLayer(添加了DataFilterExtension)中,圆的半径仍由屏幕底部的进度条控制。拖动以更改大小,而不是类似TripsLayer的自动渲染 我希望它们能够实现基于公共属性时间戳的同步渲染。换言之,当
import React, {Component, Fragment} from 'react';
import {render} from 'react-dom';
import {StaticMap} from 'react-map-gl';
import {AmbientLight, PointLight, LightingEffect} from '@deck.gl/core';
import DeckGL from '@deck.gl/react';
import {PolygonLayer} from '@deck.gl/layers';
import {TripsLayer} from '@deck.gl/geo-layers';
import {ScatterplotLayer} from '@deck.gl/layers';
import {DataFilterExtension} from '@deck.gl/extensions';
import {MapView} from '@deck.gl/core';
import RangeInput from './range-input';
// Set your mapbox token here
const MAPBOX_TOKEN = process.env.MapboxAccessToken; // eslint-disable-line
// Source data CSV
const DATA_URL1 = {
TRIPS:
'./package1.json' // eslint-disable-line
};
const DATA_URL =
'./data2.csv'; // eslint-disable-line
const MAP_VIEW = new MapView({
// 1 is the distance between the camera and the ground
farZMultiplier: 100
});
const ambientLight = new AmbientLight({
color: [122, 122, 122],
intensity: 1.0
});
const pointLight = new PointLight({
color: [255, 255, 255],
intensity: 2.0,
position: [127.05, 37.5, 8000]
});
const lightingEffect = new LightingEffect({ambientLight, pointLight});
const material = {
ambient: 0.1,
diffuse: 0.6,
shininess: 32,
specularColor: [60, 64, 70]
};
const DEFAULT_THEME = {
buildingColor: [74, 80, 87],
trailColor0: [253, 128, 93],
trailColor1: [23, 184, 190],
material,
effects: [lightingEffect]
};
const INITIAL_VIEW_STATE = {
longitude: 126.9779692,
latitude: 37.566535,
zoom: 6,
pitch: 0,
bearing: 0
};
const landCover = [[[-74.0, 40.7], [-74.02, 40.7], [-74.02, 40.72], [-74.0, 40.72]]];
const MS_PER_DAY = 8.64e7; // milliseconds in a day
const dataFilter = new DataFilterExtension({filterSize: 1});
export default class App extends Component {
constructor(props) {
super(props);
this.state1 = {
time: 0
};
const timeRange = this._getTimeRange(props.data);
this.state = {
timeRange,
filterValue: timeRange,
hoveredObject: null,
};
this._onHover = this._onHover.bind(this);
this._renderTooltip = this._renderTooltip.bind(this);
}
componentWillReceiveProps(nextProps) {
if (nextProps.data !== this.props.data) {
const timeRange = this._getTimeRange(nextProps.data);
this.setState({timeRange, filterValue: timeRange});
}
}
componentDidMount() {
this._animate();
}
componentWillUnmount() {
if (this._animationFrame) {
window.cancelAnimationFrame(this._animationFrame);
}
}
_animate() {
const {
loopLength = 1000, // unit corresponds to the timestamp in source data
animationSpeed = 20 // unit time per second
} = this.props;
const timestamp = Date.now() / 1000;
const loopTime = loopLength / animationSpeed;
this.setState({
time: ((timestamp % loopTime) / loopTime) * loopLength
});
this._animationFrame = window.requestAnimationFrame(this._animate.bind(this));
}
_getTimeRange(data) {
if (!data) {
return null;
}
return data.reduce(
(range, d) => {
const t = d.timestamp / MS_PER_DAY;
range[0] = Math.min(range[0], t);
range[1] = Math.max(range[1], t);
return range;
},
[Infinity, -Infinity]
);
}
_onHover({x, y, object}) {
this.setState({x, y, hoveredObject: object});
}
_renderLayers() {
const {
buildings = DATA_URL1.BUILDINGS,
trips = DATA_URL1.TRIPS,
trailLength = 30,
theme = DEFAULT_THEME
} = this.props;
const {data} = this.props;
const {filterValue} = this.state;
return [
data &&
new ScatterplotLayer({
id: 'earthquakes',
data,
opacity: 0.8,
radiusScale: 1,
radiusMinPixels: 1,
wrapLongitude: true,
getPosition: d => [d.longitude, d.latitude, -d.depth * 1000],
getRadius: d => d.VisitingTime * 200,
getFillColor: d => {
const r = Math.sqrt(Math.max(d.depth, 0));
return [255 - r * 15, r * 5, r * 10];
},
getFilterValue: d => d.timestamp / MS_PER_DAY, // in days
filterRange: [filterValue[0], filterValue[1]],
filterSoftRange: [
filterValue[0] * 0.9 + filterValue[1] * 0.1,
filterValue[0] * 0.1 + filterValue[1] * 0.9
],
extensions: [dataFilter],
pickable: true,
onHover: this._onHover
}),
new PolygonLayer({
id: 'ground',
data: landCover,
getPolygon: f => f,
stroked: false,
getFillColor: [0, 0, 0, 0]
}),
new TripsLayer({
id: 'trips',
data: trips,
getPath: d => d.path,
getTimestamps: d => d.timestamps,
getColor: d => (d.vendor === 0 ? theme.trailColor0 : theme.trailColor1),
opacity: 0.3,
widthMinPixels: 2,
rounded: true,
trailLength,
currentTime: this.state.time,
shadowEnabled: false
}),
new PolygonLayer({
id: 'buildings',
data: buildings,
extruded: true,
wireframe: false,
opacity: 0.5,
getPolygon: f => f.polygon,
getElevation: f => f.height,
getFillColor: theme.buildingColor,
material: theme.material
})
];
}
_renderTooltip() {
const {x, y, hoveredObject} = this.state;
return (
hoveredObject && (
<div className="tooltip" style={{top: y, left: x}}>
<div>
<b>Time: </b>
<span>{new Date(hoveredObject.timestamp).toUTCString()}</span>
</div>
<div>
<b>VisitingTime: </b>
<span>{hoveredObject.VisitingTime}</span>
</div>
<div>
<b>Depth: </b>
<span>{hoveredObject.depth} km</span>
</div>
</div>
)
);
}
_formatLabel(t) {
const date = new Date(t * MS_PER_DAY);
return `${date.getUTCFullYear()}/${date.getUTCMonth() + 1}`;
}
render() {
const {
viewState,
mapStyle = 'mapbox://styles/mapbox/light-v9',
theme = DEFAULT_THEME
} = this.props;
const {timeRange, filterValue} = this.state;
return (
<Fragment>
<DeckGL
views={MAP_VIEW}
layers={this._renderLayers()}
effects={theme.effects}
initialViewState={INITIAL_VIEW_STATE}
viewState={viewState}
controller={true}
>
<StaticMap
reuseMaps
mapStyle={mapStyle}
preventStyleDiffing={true}
mapboxApiAccessToken={MAPBOX_TOKEN}
/>
{this._renderTooltip}
</DeckGL>
{timeRange && (
<RangeInput
min={timeRange[0]}
max={timeRange[1]}
value={filterValue}
formatLabel={this._formatLabel}
onChange={({value}) => this.setState({filterValue: value})}
/>
)}
</Fragment>
);
}
}
export function renderToDOM(container) {
require('d3-request').csv(DATA_URL, (error, response) => {
if (!error) {
const data = response.map(row => ({
timestamp: new Date(`${row.DateTime} UTC`).getTime(),
latitude: Number(row.Latitude),
longitude: Number(row.Longitude),
depth: Number(row.Depth),
VisitingTime: Number(row.VisitingTime)
}));
render(<App data={data} />, container);
}
});
}
import React,{Component,Fragment}来自'React';
从'react dom'导入{render};
从“react map gl”导入{StaticMap};
从'@deck.gl/core'导入{AmbientLight,PointLight,LightingEffect};
从“@deck.gl/react”导入DeckGL;
从'@deck.gl/layers'导入{PolygonLayer};
从'@deck.gl/geo layers'导入{TripsLayer};
从'@deck.gl/layers'导入{ScatterplotLayer};
从'@deck.gl/extensions'导入{DataFilterExtension};
从'@deck.gl/core'导入{MapView};
从“/范围输入”导入范围输入;
//在此处设置地图盒令牌
const MAPBOX_TOKEN=process.env.MapboxAccessToken;//eslint禁用线
//源数据CSV
常量数据\u URL1={
旅行:
'./package1.json'//eslint禁用行
};
常量数据URL=
“./data2.csv”;//eslint禁用线
常量地图视图=新地图视图({
//1是相机与地面之间的距离
法兹倍增管:100
});
常量环境光=新环境光({
颜色:[122122122],
强度:1.0
});
常量点光源=新点光源({
颜色:[255,255,255],
强度:2.0,
职位:[127.05,37.5,8000]
});
const lightingEffect=新的lightingEffect({ambientLight,pointLight});
常数材料={
环境温度:0.1,
漫反射:0.6,
光泽度:32,
镜面颜色:[60,64,70]
};
常量默认主题={
buildingColor:[74,80,87],
trailColor0:[253,128,93],
trailclor1:[23184190],
材料,
效果:[照明效果]
};
常量初始视图状态={
经度:126.9779692,
纬度:37.566535,
缩放:6,
音高:0,
方位:0
};
常量土地覆盖率=[[-74.0,40.7]、-74.02,40.7]、-74.02,40.72]、-74.0,40.72];
每日施工成本=8.64e7;//一天几毫秒
const dataFilter=new DataFilterExtension({filterSize:1});
导出默认类应用程序扩展组件{
建造师(道具){
超级(道具);
this.state1={
时间:0
};
const timeRange=this.\u getTimeRange(props.data);
此.state={
时间范围,
filterValue:时间范围,
hoveredObject:null,
};
this.\u onHover=this.\u onHover.bind(this);
this.\u renderTooltip=this.\u renderTooltip.bind(this);
}
组件将接收道具(下一步){
if(nextrops.data!==this.props.data){
const timeRange=this.\u getTimeRange(nextProps.data);
this.setState({timeRange,filterValue:timeRange});
}
}
componentDidMount(){
这个;
}
组件将卸载(){
如果(此._animationFrame){
window.cancelAnimationFrame(此.\u animationFrame);
}
}
_制作动画(){
常数{
loopLength=1000,//单位对应于源数据中的时间戳
动画速度=20//单位时间/秒
}=这是道具;
const timestamp=Date.now()/1000;
const loopTime=循环长度/动画速度;
这是我的国家({
时间:((时间戳%loopTime)/loopTime)*loopLength
});
this._animationFrame=window.requestAnimationFrame(this._animate.bind(this));
}
_getTimeRange(数据){
如果(!数据){
返回null;
}
返回数据.reduce(
(范围d)=>{
常数t=d.时间戳/MS_/天;
范围[0]=数学最小值(范围[0],t);
范围[1]=数学最大值(范围[1],t);
返回范围;
},
[无限,-无限]
);
}
_onHover({x,y,object}){
this.setState({x,y,hoveredObject:object});
}
_renderLayers(){
常数{
建筑物=数据1.建筑物,
行程=数据_URL1.trips,
牵引长度=30,
主题=默认的主题
}=这是道具;
const{data}=this.props;
const{filterValue}=this.state;
返回[
资料&&
新散射层({
id:‘地震’,
数据,
不透明度:0.8,
半径刻度:1,
半径最小像素:1,
包装:真的,
getPosition:d=>[d.经度,d.纬度,-d.深度*1000],
getRadius:d=>d.访问时间*200,
getFillColor:d=>{
常数r=Math.sqrt(Math.max(d.depth,0));
返回[255-r*15,r*5,r*10];
},
getFilterValue:d=>d.timestamp/MS\u/天,//以天为单位
filterRange:[filterValue[0],filterValue[1]],
过滤器软件范围:[
过滤值[0]*0.9+过滤值[1]*0.1,
filterValue[0]*0.1+filterValue[1]*0.9
],
扩展:[数据过滤器],
pickable:是的,
这个
}),
新型多晶层({
id:'地面',
数据:土地覆盖,
getPolygon:f=>f,
抚摸:错,
getFillColor:[0,0,0,0]
}),
新三枪杀手({
id:‘旅行’,
数据:trips,
getPath:d=>d.path,
getTimestamps:d=>d.timestamps,
getColor:d=>(d.vendor==0?theme.trailclor0:theme.trailclor1),
不透明度:0.3,
像素宽度:2,
四舍五入:对,
追踪长度,
currentTime:this.state.time,
阴影启用:
import React, {Component, Fragment} from 'react';
import {render} from 'react-dom';
import {StaticMap} from 'react-map-gl';
import {AmbientLight, PointLight, LightingEffect} from '@deck.gl/core';
import DeckGL from '@deck.gl/react';
import {PolygonLayer} from '@deck.gl/layers';
import {TripsLayer} from '@deck.gl/geo-layers';
import {ScatterplotLayer} from '@deck.gl/layers';
import {DataFilterExtension} from '@deck.gl/extensions';
import {MapView} from '@deck.gl/core';
// Set your mapbox token here
const MAPBOX_TOKEN = process.env.MapboxAccessToken; // eslint-disable-line
// Source data CSV
const DATA_URL1 = {
TRIPS:
'./package1.json' // eslint-disable-line
};
const DATA_URL =
'./data2.csv'; // eslint-disable-line
const MAP_VIEW = new MapView({
// 1 is the distance between the camera and the ground
farZMultiplier: 100
});
const ambientLight = new AmbientLight({
color: [122, 122, 122],
intensity: 1.0
});
const pointLight = new PointLight({
color: [255, 255, 255],
intensity: 2.0,
position: [127.05, 37.5, 8000]
});
const lightingEffect = new LightingEffect({ambientLight, pointLight});
const material = {
ambient: 0.1,
diffuse: 0.6,
shininess: 32,
specularColor: [60, 64, 70]
};
const DEFAULT_THEME = {
buildingColor: [74, 80, 87],
trailColor0: [253, 128, 93],
trailColor1: [23, 184, 190],
material,
effects: [lightingEffect]
};
const INITIAL_VIEW_STATE = {
longitude: 126.9779692,
latitude: 37.566535,
zoom: 6,
pitch: 0,
bearing: 0
};
const landCover = [[[-74.0, 40.7], [-74.02, 40.7], [-74.02, 40.72], [-74.0, 40.72]]];
const MS_PER_DAY = 8.64e7; // milliseconds in a day
const dataFilter = new DataFilterExtension({filterSize: 1});
export default class App extends Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
const timeRange = this._getTimeRange(props.data);
this.state = {
timeRange,
filterValue: timeRange,
};
}
componentWillReceiveProps(nextProps) {
if (nextProps.data !== this.props.data) {
const timeRange = this._getTimeRange(nextProps.data);
this.setState({timeRange, filterValue: timeRange});
}
}
componentDidMount() {
this._animate();
this.timerID = setInterval(
() => this.tick(),
1000
);
}
componentWillUnmount() {
if (this._animationFrame) {
window.cancelAnimationFrame(this._animationFrame);
clearInterval(this.timerID);
}
}
tick() {
this.setState({
date: new Date()
});
}
_animate() {
const {
loopLength = 1000, // unit corresponds to the timestamp in source data
animationSpeed = 20 // unit time per second
} = this.props;
const timestamp = Date.now() / 1000;
const loopTime = loopLength / animationSpeed;
this.setState({
time: ((timestamp % loopTime) / loopTime) * loopLength
});
this._animationFrame = window.requestAnimationFrame(this._animate.bind(this));
}
_getTimeRange(data) {
if (!data) {
return null;
}
return data.reduce(
(range, d) => {
const t = d.timestamp / MS_PER_DAY;
range[0] = Math.min(range[0], t);
range[1] = Math.max(range[1], t);
return range;
},
[Infinity, -Infinity]
);
}
_renderLayers() {
const {
buildings = DATA_URL1.BUILDINGS,
trips = DATA_URL1.TRIPS,
trailLength = 30,
theme = DEFAULT_THEME
} = this.props;
const {data} = this.props;
const {filterValue} = this.state;
return [
data &&
new ScatterplotLayer({
id: 'ScatterplotLayer',
data,
opacity: 0.8,
radiusScale: 1,
radiusMinPixels: 1,
wrapLongitude: true,
rounded: true,
getTimestamps: d => d.timestamps,
getPosition: d => [d.longitude, d.latitude],
getRadius: d => d.VisitingTime * 200,
getFillColor: d => {
const r = Math.sqrt(Math.max(d.depth, 0));
return [255 - r * 15, r * 5, r * 10];
},
getFilterValue: d => d.timestamp / MS_PER_DAY, // in days
currentTime: this.state.time,
filterRange: [filterValue[0], filterValue[1]],
filterSoftRange: [
filterValue[0] * 0.9 + filterValue[1] * 0.1,
filterValue[0] * 0.1 + filterValue[1] * 0.9
],
extensions: [dataFilter]
}),
new PolygonLayer({
id: 'ground',
data: landCover,
getPolygon: f => f,
stroked: false,
getFillColor: [0, 0, 0, 0]
}),
new TripsLayer({
id: 'trips',
data: trips,
getPath: d => d.path,
getTimestamps: d => d.timestamps,
getColor: d => (d.vendor === 0 ? theme.trailColor0 : theme.trailColor1),
opacity: 0.3,
widthMinPixels: 2,
rounded: true,
trailLength,
currentTime: this.state.time,
shadowEnabled: false
}),
new PolygonLayer({
id: 'buildings',
data: buildings,
extruded: true,
wireframe: false,
opacity: 0.5,
getPolygon: f => f.polygon,
getElevation: f => f.height,
getFillColor: theme.buildingColor,
material: theme.material
})
];
}
render() {
const {
viewState,
mapStyle = 'mapbox://styles/mapbox/light-v9',
theme = DEFAULT_THEME
} = this.props;
return (
<Fragment>
<DeckGL
views={MAP_VIEW}
layers={this._renderLayers()}
effects={theme.effects}
initialViewState={INITIAL_VIEW_STATE}
viewState={viewState}
controller={true}
>
<StaticMap
reuseMaps
mapStyle={mapStyle}
preventStyleDiffing={true}
mapboxApiAccessToken={MAPBOX_TOKEN}
/>
</DeckGL>
</Fragment>
);
}
}
export function renderToDOM(container) {
require('d3-request').csv(DATA_URL, (error, response) => {
if (!error) {
const data = response.map(row => ({
timestamp: new Date(`${row.DateTime} UTC`).getTime(),
latitude: Number(row.Latitude),
longitude: Number(row.Longitude),
depth: Number(row.Depth),
VisitingTime: Number(row.VisitingTime)
}));
render(<App data={data} />, container);
}
});
}