Home > Enterprise >  Time Slide Widget ArcGIS API, Any better approach to do this?
Time Slide Widget ArcGIS API, Any better approach to do this?

Time:10-10

I tried this sample which demonstrates how to use TimeSlider widget with minimal configuration. The TimeSlider widget simplifies the process of visualizing temporal data in a JavaScript application.

It uses an instance of FeatureLayer to visualize incremental precipitation forecast for next 72 hours across the contiguous United States with 6-hour interval.

The map displays the Quantitative Precipitation Forecast (QPF) for the next 72 hours across the contiguous United States. Data are updated hourly from the National Digital Forecast Database produced by the National Weather Service. Visit the portal item description page for more information.

There are four different mode options when initializing the TimeSlider widget: instant, time-window, cumulative-from-start, and cumulative-from-end. The default mode for the time slider is time-window. The sample uses this default mode, meaning that slider will show precipitation data that falls within a given time range, which is 6 hours in this case.

We are setting the view property on the time slider widget. The TimeSlider widget will update the view's timeExtent whenever the time slider's timeExtent changes. Setting the view property will affect any time-aware layer in the view.

<script>
  require([
    "esri/Map",
    "esri/views/MapView",
    "esri/layers/FeatureLayer",
    "esri/widgets/TimeSlider",
    "esri/widgets/Expand",
    "esri/widgets/Legend"
  ], (Map, MapView, FeatureLayer, TimeSlider, Expand, Legend) => {
    const layer = new FeatureLayer({
      url: "https://services9.arcgis.com/RHVPKKiFTONKtxq3/arcgis/rest/services/NDFD_Precipitation_v1/FeatureServer/0"
    });

    const map = new Map({
      basemap: "hybrid",
      layers: [layer]
    });

    const view = new MapView({
      map: map,
      container: "viewDiv",
      zoom: 4,
      center: [-100, 30]
    });

    // time slider widget initialization
    const timeSlider = new TimeSlider({
      container: "timeSlider",
      view: view,
      timeVisible: true, // show the time stamps on the timeslider
      loop: true
    });

    // add the UI for a title
    view.ui.add("titleDiv", "top-right");

    view.whenLayerView(layer).then((lv) => {
      // around up the full time extent to full hour
      timeSlider.fullTimeExtent =
        layer.timeInfo.fullTimeExtent.expandTo("hours");
      timeSlider.stops = {
        interval: layer.timeInfo.interval
      };
    });

    const legend = new Legend({
      view: view
    });
    const legendExpand = new Expand({
      expandIconClass: "esri-icon-legend",
      expandTooltip: "Legend",
      view: view,
      content: legend,
      expanded: false
    });
    view.ui.add(legendExpand, "top-left");
  });
</script>

CodePudding user response:

For time offset on the slider bar, this is another approach to do the time slider.

let timeSlider, firesChart;
    const map = new Map({
      basemap: {
        portalItem: {
          id: "4f2e99ba65e34bb8af49733d9778fb8e"
        }
      }
    });

    const view = new MapView({
      container: "viewDiv",
      map: map,
      zoom: 5,
      center: [-118, 37.49] // longitude, latitude
    });

    // create five new instances of feature layers
    // based on the following definitions
    const url =
      "https://services.arcgis.com/V6ZHFr6zdgNZuVG0/arcgis/rest/services/California_fires_since_2014/FeatureServer/";
    const definitions = [
      { id: 0, year: 2014, offset: 4 },
      { id: 1, year: 2015, offset: 3 },
      { id: 2, year: 2016, offset: 2 },
      { id: 3, year: 2017, offset: 1 },
      { id: 4, year: 2018, offset: 0 }
    ];

    const layers = definitions.map((definition) => {
      return createLayer(definition);
    });
    // add the california fire layers
    map.addMany(layers);

    // get layerviews of each fire layers once the layers are loaded
    const layerViewsEachAlways = () => {
      return promiseUtils.eachAlways(
        layers.map((layer) => {
          return view.whenLayerView(layer);
        })
      );
    };

    view.when(() => {
      timeSlider = new TimeSlider({
        container: "timeSliderDiv",
        view: view,
        fullTimeExtent: {
          start: new Date(2018, 0, 1),
          end: new Date(2018, 11, 1)
        },
        playRate: 2000,
        stops: {
          interval: {
            value: 1,
            unit: "months"
          }
        },
        // create actions that will allow users to
        // jump to months that had most burns
        actions: [
          {
            id: "july",
            icon: "clock",
            title: "Month with most burns"
          },
          {
            id: "august",
            icon: "clock",
            title: "Month with second most burns"
          },
          {
            id: "september",
            icon: "clock",
            title: "Month with third most burns"
          }
        ],
        // set custom labels for the timeslider's min, max dates
        // then hide the extent label
        labelFormatFunction: (value, type, element, layout) => {
          const normal = new Intl.DateTimeFormat("en-us");
          switch (type) {
            case "min":
              element.setAttribute("style", "color: #ff642e;");
              element.innerText = "Jan";
              break;
            case "max":
              element.setAttribute("style", "color: #ff642e;");
              element.innerText = "Dec";
              break;
            case "extent":
              element.parentNode.setAttribute("style", "width:0px");
              break;
          }
        }
      });
      // the "trigger-action" event occurs when an item in the action button is selected
      //  define custom actions to occur for each action.id
      timeSlider.on("trigger-action", function (event) {
        var id = event.action.id;

        if (id === "july") {
          timeSlider.timeExtent = {
            start: new Date(2018, 6, 1),
            end: new Date(2018, 7, 1)
          };
        } else if (id === "august") {
          timeSlider.timeExtent = {
            start: new Date(2018, 7, 1),
            end: new Date(2018, 8, 1)
          };
        } else if (id === "september") {
          timeSlider.timeExtent = {
            start: new Date(2018, 8, 1),
            end: new Date(2018, 9, 1)
          };
        }
      });

      // update the fire stats between 2014 - 2018 once timeExtent of
      // the timeSlider changes
      timeSlider.watch("timeExtent", () => {
        updateFiresStats();
      });
      createFiresChart();
    });

    view.whenLayerView(layers[0]).then((layerView) => {
      watchUtils.whenFalseOnce(layerView, "updating", () => {
        updateFiresStats();
      });
    });

    // fire stats for each year between 2014 - 2018
    const sumAcres = {
      onStatisticField: "GIS_ACRES",
      outStatisticFieldName: "acres_sum",
      statisticType: "sum"
    };
    const fireCounts = {
      onStatisticField: "OBJECTID",
      outStatisticFieldName: "fire_counts",
      statisticType: "count"
    };
    const year = {
      onStatisticField: "ALARM_DATE",
      outStatisticFieldName: "year",
      statisticType: "max"
    };
    // stats query
    const statQuery = {
      outStatistics: [sumAcres, fireCounts, year]
    };

    // query five layerviews representing fires between 2014-2018
    // this will be called from the UudateFiresStats function
    const queryFeaturesForStats = (layerViewsResults) => {
      return promiseUtils.eachAlways(
        layerViewsResults.map((result) => {
          // If a Promise was rejected, you can check for the rejected error
          if (result.error) {
            return promiseUtils.resolve(result.error);
          }
          // The results of the Promise are returned in the value property
          else {
            const layerView = result.value;

            // set the timeExtent for the stats query object. But
            // we need to offset the timeExtent for each layer by
            // number of years specified in the layer.timeOffset
            let start = new Date(timeSlider.timeExtent.start);
            let end = new Date(timeSlider.timeExtent.end);
            start.setFullYear(
              start.getFullYear() - layerView.layer.timeOffset.value
            );
            end.setFullYear(
              end.getFullYear() - layerView.layer.timeOffset.value
            );

            // now we have the right timeExtent for each layer
            // set the timeExtent for the stats query
            statQuery.timeExtent = {
              start: start,
              end: end
            };
            // query the layerviews for the stats
            return layerView.queryFeatures(statQuery).then(
              (response) => {
                return response.features[0].attributes;
              },
              (e) => {
                return promiseUtils.resolve(e);
              }
            );
          }
        })
      );
    };

    // This function is called when the timeSlider's timeExtent changes
    // It queries each layer view for fire stats and updates the chart
    function updateFiresStats() {
      layerViewsEachAlways().then((layerViewsResults) => {
        // query each and every fire layerviews for stats and process the results
        queryFeaturesForStats(layerViewsResults).then(
          (fireStatsQueryResults) => {
            monthDiv.innerHTML = "";
            let month;
            let acresBurntList = [];
            let chartLabels = [];
            // fire stats query results are back. Loop through them and process.
            fireStatsQueryResults.map((result) => {
              if (result.error) {
                return promiseUtils.resolve(result.error);
              }
              // The results of the Promise are returned in the value property
              else {
                // if the stats query returned a year for the given layerview
                // then process and update the chart
                if (result.value.year !== null) {
                  // extract the year and month from the date
                  let date = new Date(result.value.year);
                  let year = date.getFullYear();

                  // array of burnt acres sum returned in the query results
                  // for each layerview representing fires between 2014-2018
                  acresBurntList.push(result.value.acres_sum.toFixed(2));

                  //chart labels will show the year and count of fires for that year
                  const label = year   ", "   result.value.fire_counts;
                  chartLabels.push(label);
                }
              }
            });
            // query results have been processed. Update the fires chart to display
            // sum of acres burnt for the given month and year
            firesChart.data.datasets[0].data = acresBurntList;
            firesChart.data.labels = chartLabels;
            firesChart.update();
            startMonth = timeSlider.timeExtent.start.toLocaleString(
              "default",
              { month: "long" }
            );
            endMonth = timeSlider.timeExtent.end.toLocaleString("default", {
              month: "long"
            });
            monthDiv.innerHTML =
              "<b> Month: <span>"  
              startMonth  
              " - "  
              endMonth  
              "</span></b>";
          }
        );
      });
    }

    // Creates five instances of feature layers each representing
    // fires for the given year (between 2014 - 2018)
    function createLayer(definition) {
      const year = definition.year;

      return new FeatureLayer({
        url: url   definition.id,
        timeOffset: {
          value: definition.offset,
          unit: "years"
        },
        outFields: ["*"],
        popupTemplate: {
          title: "{ALARM_DATE}",
          content: "{YEAR_}, {ALARM_DATE}, {GIS_ACRES}"
        }
      });
    }

    // create the legend
    const layerInfos = layers.map((layer, i) => {
      return {
        title: "",
        layer: layer
      };
    });

    const legend = new Legend({
      view: view,
      layerInfos: layerInfos
    });
    view.ui.add(legend, "top-left");

    // Fires chart section
    const monthDiv = document.getElementById("monthDiv");
    const infoDiv = document.getElementById("infoDiv");
    const chartCanvas = document.getElementById("fireChart");
    const infoDivExpand = new Expand({
      collapsedIconClass: "esri-icon-collapse",
      expandIconClass: "esri-icon-expand",
      expandTooltip: "Expand earthquakes info",
      view: view,
      content: infoDiv,
      expanded: true
    });
    view.ui.add(infoDivExpand, "top-right");

    // create a bar chart to display sum of acres
    // burnt for the given month and year.
    function createFiresChart() {
      Chart.defaults.global.defaultFontColor = "#d1d1d1";
      firesChart = new Chart(chartCanvas.getContext("2d"), {
        type: "bar",
        data: {
          labels: [2014, 2015, 2016, 2017, 2018],
          datasets: [
            {
              label: "Acres burnt, year, fire counts",
              backgroundColor: "#ff642e",
              data: [0, 0, 0, 0, 0]
            }
          ]
        },
        options: {
          responsive: false,
          legend: {
            position: "bottom"
          },
          title: {
            display: true,
            text: "Acres burnt by month",
            fontFamily:
              "'Avenir Next W00','Helvetica Neue', Helvetica, Arial, sans-serif"
          },
          scales: {
            yAxes: [
              {
                ticks: {
                  beginAtZero: true,
                  callback: (value) => {
                    if (value % 1 === 0) {
                      return value;
                    }
                  }
                },
                gridLines: {
                  zeroLineColor: "#d1d1d1",
                  color: "#696969"
                }
              }
            ],
            xAxes: [
              {
                gridLines: {
                  zeroLineColor: "#d1d1d1",
                  color: "#696969"
                }
              }
            ]
          },
          tooltips: {
            callbacks: {
              title: (tooltipItem, data) => {
                return "Fire";
              },
              label: (tooltipItem, data) => {
                const yearCount = tooltipItem.xLabel.split(",");
                let customTooltip = [];
                customTooltip.push("Year: "   yearCount[0]);
                customTooltip.push("Count:"   yearCount[1]);
                customTooltip.push("Acres:"   tooltipItem.yLabel);
                return customTooltip;
              }
            },
            displayColors: false
          }
        }
      });

CodePudding user response:

For a more formatted and visualized approach, use the below code:

 labelFormatFunction: (value, type, element, layout) => {
const normal = new Intl.DateTimeFormat('en-us');
switch (type) {
  case "min":
  case "max":
    element.setAttribute("style", "font-family: 'Orbitron', sans-serif; font-size: 16px; color: magenta;");
    element.innerText = normal.format(value);
    break;
  case "extent":
    const start = timeSlider.fullTimeExtent.start;
    const days = (value[0].getTime() - start.getTime()) / 1000 / 3600 / 24;
    element.setAttribute(`style`, `font-family: 'Orbitron', sans-serif; font-size: 22px; color: red;`);
    element.innerText = `Day ${days}`;
    break;
}

CodePudding user response:

As compared to previous versions, this Javascript API is the most simplified version. But, as in my case, i was using older version, but than to change on newer version requires a lot of effort to rewrite the whole code again.

CodePudding user response:

This is my code, hope this will help you out

 function setupHoverTooltip(layerview) {
        var promise;
        var highlight;

        var tooltip = createTooltip();

        view.on("pointer-move", function (event) {
            if (promise) {
                promise.cancel();
            }

            promise = view.hitTest(event.x, event.y)
              .then(function (hit) {
                  promise = null;

                  // remove current highlighted feature
                  if (highlight) {
                      highlight.remove();
                      highlight = null;
                  }

                  var results = hit.results.filter(function (result) {
                      return result.graphic.layer === bldtmlinelayer;
                  });


                  if (results.length) {
                      var graphic = results[0].graphic;
                      var screenPoint = hit.screenPoint;

                      highlight = layerview.highlight(graphic);
                      tooltip.show(screenPoint, "Built in "   graphic.getAttribute(attrib.BUILDINGS.YEARBUILT));
                  } else {
                      tooltip.hide();
                  }
              });
        });
    }


    function startAnimation() {
        stopAnimation();
        animation = animate(parseFloat(slider.value));
        playButton.classList.add("toggled");
    }


    function stopAnimation() {
        if (!animation) {
            return;
        }

        animation.remove();
        animation = null;
        playButton.classList.remove("toggled");
    }


    function animate(startValue) { var animating = true; var value = startValue;



            setYear(value);

            // Update at 30fps
            setTimeout(function () {
                requestAnimationFrame(frame);
            }, 1000 / 3);
        };

        frame();

        return {
            remove: function () {
                animating = false;
            }
        };
    }

    function createTooltip() {
        var tooltip = document.createElement("div");
        var style = tooltip.style;

        tooltip.setAttribute("role", "tooltip");
        tooltip.classList.add("tooltip");

        var textElement = document.createElement("div");
        textElement.classList.add("esri-widget");
        tooltip.appendChild(textElement);

        view.container.appendChild(tooltip);

        var x = 0;
        var y = 0;
        var targetX = 0;
        var targetY = 0;
        var visible = false;

        // move the tooltip progressively
        function move() {
            x  = (targetX - x) * 0.1;
            y  = (targetY - y) * 0.1;

            if (Math.abs(targetX - x) < 1 && Math.abs(targetY - y) < 1) {
                x = targetX;
                y = targetY;
            } else {
                requestAnimationFrame(move);
            }

            style.transform = "translate3d("   Math.round(x)   "px,"   Math.round(y)   "px, 0)";
        }
        return {
            show: function (point, text) { if (!visible) { x = point.x;  y = point.y;} targetX = point.x; targetY = point.y; style.opacity = 1; visible = true; textElement.innerHTML = text; move();},
            hide: function () { style.opacity = 0; visible = false; }
        };
    }

scene.add(bldtmlinelayer);
  • Related