Deck.gl как показать всплывающее окно onClick

Настраивать:

Приложение Basic React с использованием react-map-gl для отображения карты с Deck.gl ScatterplotLayer поверх для визуализации данных

Цель:

1) Отображение точек на карте в виде кругов заданного радиуса и цвета.
2) Когда пользователь нажимает на круг, всплывающая подсказка / всплывающее окно должны отображаться с дополнительными данными о нем (включенными в предоставленные данные) до тех пор, пока пользователь не щелкнет (по сути, так же, как этот график, но для щелчка вместо наведения, http://uber.github.io/deck.gl/#/documentation/layer-catalog/scatterplot-layer. К вашему сведению, я посмотрел на код для этого, и логика наведения была удалена, я полагаю, для простоты).

Проблема:

Я выполнил пункт 1, но не могу заставить работать пункт 2. Самое большое, что я смог доказать, это данные для входа в консоль.

Отметить:

Я не замужем за реакцией-всплывающей подсказкой - я не возражаю полностью удалить ее, если есть лучший способ сделать это. Мне нужно оставить только mapbox и deck.gl.

Данные: https://gist.github.com/NikkiChristofi/bf79ca37028b293b50cfbf79ca37028b293b50cd

deckgl-overlay.js

import React, {Component} from 'react';
import ReactTooltip from 'react-tooltip';
import DeckGL, {ScatterplotLayer} from 'deck.gl';

export default class DeckGLOverlay extends Component {

  static get defaultViewport() {
    return {
      longitude: 0,
      latitude: 0,
      zoom: 2,
      maxZoom: 16,
      pitch: 0,
      bearing: 0
    };
  }

# in this method I want to update the variable tooltipText with
# whatever object data has been clicked. 
# The console log successfully logs the right data (i.e. the third 
# element in the array), but the tooltip doesn't even show 

  onClickHandler = (info) => {
    let dataToShow = info ? info.object[2] : "not found";
    this.tooltipText = dataToShow;
    console.log(dataToShow);
  }

  render() {
    const {viewport, lowPerformerColor, highPerformerColor, data, radius, smallRadius, largeRadius} = this.props;

    if (!data) {
      return null;
    }

    const layer = new ScatterplotLayer({
      id: 'scatter-plot',
      data,
      radiusScale: radius,
      radiusMinPixels: 0.25,
      getPosition: d => [d[1], d[0], 0],
      getColor: d => d[2] > 50 ? lowPerformerColor : highPerformerColor,
      getRadius: d => d[2] < 25 || d[2] > 75 ? smallRadius : largeRadius,
      updateTriggers: {
        getColor: [lowPerformerColor, highPerformerColor]
      },
      pickable: true, 
      onClick: info => this.onClickHandler(info),
      opacity: 0.3   
    });

    return (
      <DeckGL {...viewport} layers={ [layer] } data-tip={this.tooltipText}>
        <ReactTooltip />
      </DeckGL>
    );
  }
}

app.js

import React, {Component} from 'react';
import {render} from 'react-dom';
import MapGL from 'react-map-gl';
import DeckGLOverlay from './deckgl-overlay.js';
import {json as requestJson} from 'd3-request';

const MAPBOX_TOKEN = process.env.MAPBOX_TOKEN; // eslint-disable-line
const lowPerformerColor = [204, 0, 0];
const highPerformerColor = [0, 255, 0];
const smallRadius = 500;
const largeRadius = 1000;

const DATA_URL = 'https://gist.github.com/NikkiChristofi/bf79ca37028b29b50cffb215360db999';
export default class App extends Component {

  constructor(props) {
    super(props);
    this.state = {
      viewport: {
        ...DeckGLOverlay.defaultViewport,
        width: 500,
        height: 500
      },
      data: null
    };

    requestJson(DATA_URL, (error, response) => {
      if (!error) {
        console.log(response);
        this.setState({data: response});
      }
      else{
          console.log(error);
      }
    });
  }

  componentDidMount() {
    window.addEventListener('resize', this._resize.bind(this));
    this._resize();
  }

  _resize() {
    this._onViewportChange({
      width: window.innerWidth,
      height: window.innerHeight
    });
  }

  _onViewportChange(viewport) {
    this.setState({
      viewport: {...this.state.viewport, ...viewport}
    });
  }

  render() {
    const {viewport, data} = this.state;

    return (
      <MapGL
        {...viewport}
        onViewportChange={this._onViewportChange.bind(this)}
        mapboxApiAccessToken={MAPBOX_TOKEN}
        mapStyle='mapbox://styles/mapbox/dark-v9'>
        <DeckGLOverlay viewport={viewport}
          data={data}
          lowPerformerColor={lowPerformerColor}
          highPerformerColor={highPerformerColor}
          smallRadius={smallRadius}
          largeRadius={largeRadius}
          radius={300}
          />
      </MapGL>
    );
  }
}

person Naadof    schedule 19.09.2017    source источник


Ответы (2)


Выяснили, как это сделать.

Решение

Я перенёс событие onClick на слой MapGL и использовал элемент Popup для отображения данных.

поэтому в app.js:
1) импортируйте элемент Popup из react-map-gl

import MapGL, { Popup } from 'react-map-gl';

2) Установите состояние координат и "информацию" (чтобы показать во всплывающем окне)

constructor(props) {
    super(props);
    this.state = {
      viewport: {
        ...DeckGLOverlay.defaultViewport,
        width: 500,
        height: 500
      },
      data: null,
      coordinates: [-0.13235092163085938,51.518250335096376],
      info: "Hello"
    };

3) Создайте метод обратного вызова, который устанавливает состояние с новыми данными (информация будет просто элементом из данных, хотя может быть все, что вы хотите отобразить во всплывающем окне)

myCallback = (info) => {
    console.log(info);
    if(info){
      this.setState({coordinates: info.lngLat, info: info.object[2]});
    }
  }

4) Визуализируйте всплывающее окно и укажите метод обратного вызова в слое DeckGL.

 return (
      <MapGL
        {...viewport}
        {...this.props}
        onViewportChange={this._onViewportChange.bind(this)}
        mapboxApiAccessToken={MAPBOX_TOKEN}
        mapStyle='mapbox://styles/mapbox/dark-v9'>
        <Popup
          longitude={this.state.coordinates[0]}
          latitude={this.state.coordinates[1]}>
          <div style={style}>
            <p>{this.state.info}</p>
          </div>
        </Popup>
        <DeckGLOverlay viewport={viewport}
          data={data}
          lowPerformerColor={lowPerformerColor}
          highPerformerColor={highPerformerColor}
          smallRadius={smallRadius}
          largeRadius={largeRadius}
          radius={300}
          callbackFromParent={this.myCallback}
          />
      </MapGL>
    );

и в deckgl-overlay.js:
1) Подайте информацию о данных в родительский метод (app.js)

onClick: info => this.props.callbackFromParent(info),

(очевидно, удалите элемент React-tooltip и обработчик событий onClick в deckoverlay.js для очистки)

person Naadof    schedule 20.09.2017

Для тех, кто читает это, кто хочет использовать пользовательское всплывающее окно или один из сторонней библиотеки, такой как antd, который не поддерживает точное положение в качестве опоры, я решил эту проблему, просто создав <div style={{ position: 'absolute', left: x, top: y}} />, который будет действовать как дочерний узел для всплывающего окна, чтобы ссылка. X и Y изначально установлены на 0:

    const [selectedPoint, setSelectedPoint] = useState({});
    const [x, setX] = useState(0);
    const [y, setY] = useState(0);

а затем устанавливаются onClick в GeoJsonLayer:

    const onClick = ({ x, y, object }) => {
        setSelectedPoint(object);
        setX(x);
        setY(y);
    };

    const layer = new GeoJsonLayer({
        id: "geojson-layer",
        data,
        pickable: true,
        stroked: false,
        filled: true,
        extruded: true,
        lineWidthScale: 20,
        lineWidthMinPixels: 2,
        getFillColor: [0, 0, 0, 255],
        getRadius: 50,
        getLineWidth: 1,
        getElevation: 30,
        onClick
    });

Обратной стороной этого подхода является то, что всплывающее окно не будет оставаться с точкой, если карта масштабируется / панорамируется, потому что X и Y являются координатами области просмотра относительно широты и долготы.

person ancr    schedule 18.12.2019