private void redraw()

in adt-ui/src/main/java/com/android/tools/adtui/chart/linechart/LineChart.java [190:393]


  private void redraw(@NotNull Dimension dim) {
    long duration = System.nanoTime();

    // Store the last stacked series to use them to increment the Y values
    // of the current stacked series.
    List<SeriesData<Long>> lastStackedSeries = null;

    Deque<Path2D> orderedPaths = new ArrayDeque<>(myLinesConfig.size());
    Deque<RangedContinuousSeries> orderedSeries = new ArrayDeque<>(myLinesConfig.size());

    for (RangedContinuousSeries ranged : myModel.getSeries()) {
      if (ranged.getXRange().isEmpty() || ranged.getXRange().isPoint()
          || ranged.getYRange().isEmpty() || ranged.getYRange().isPoint()) {
        continue;
      }
      final LineConfig config = getLineConfig(ranged);

      List<SeriesData<Long>> seriesList = ranged.getSeries();
      if (config.isStacked()) {
        if (lastStackedSeries == null) {
          // Create a new list of SeriesData to prevent modifying the backing data series, which could be cached.
          lastStackedSeries = ContainerUtil.map(seriesList, data -> new SeriesData<>(data.x, data.value));
        }
        else {
          // If the current series is stacked, increment its value by the value of the last stacked
          // series. As the series are constantly populated, the current series might have more
          // points than the last stacked series (meaning that the last one was populated in a
          // prior iteration). In this case, ignore the new points (i.e. we take only the intersection
          // across all series).
          for (int i = 0; i < seriesList.size() && i < lastStackedSeries.size(); ++i) {
            // An assumption is made here that the x values across series are aligned.
            lastStackedSeries.get(i).value += seriesList.get(i).value;
          }
          seriesList = lastStackedSeries;
        }
      }

      Path2D path = new Path2D.Float();
      double xMin = ranged.getXRange().getMin();
      double xLength = ranged.getXRange().getLength();
      double yMin = ranged.getYRange().getMin();
      double yLength = ranged.getYRange().getLength();

      // X coordinate of the first point
      double firstXd = 0f;
      // Actual value of first point
      double firstX = 0;
      seriesList = myReducer.reduceData(seriesList, config);
      double xBucketInterval = config.getDataBucketInterval() / xLength;
      double xBucketBarWidth = xBucketInterval * BUCKET_BAR_PERCENTAGE;
      // If we are a stepped chart or bar chart, we don't need to worry about start and end points' Y value.
      boolean optimizeYZooming = !config.isStepped() && xBucketInterval == 0;
      for (int i = 0; i < seriesList.size(); i++) {
        SeriesData<Long> data = seriesList.get(i);
        SeriesData<Long> dataNext = seriesList.get(i + 1 == seriesList.size() ? i : i + 1);
        SeriesData<Long> dataPrev = seriesList.get(i - 1 < 0 ? i : i - 1);
        // TODO: refactor to allow different types (e.g. double)
        double xd = (data.x - xMin) / xLength;
        // Swing's (0, 0) coordinate is in top-left. As we use bottom-left (0, 0), we need to adjust the y coordinate.
        double yd = 1 - (data.value - yMin) / yLength;

        // This change significantly speeds up drawing when zoomed into the chart. Without this change a line could extend
        // a few thousand pixels off the screen in both directions. The fill/draw function would then spend a lot of time
        // computing a line fill for pixels never to be rendered.
        // Truncate points that are off screen. Ones that cross the border get pushed to the border, and the
        // height gets scaled accordingly.
        // For example, two out of bounds points would get snapped into place.
        //                |                    |
        //                |                    * <-- *
        //          * --> *                    |
        //                |                    |
        // X Axis: -------|--------------------|------
        //                0                    1

        double originalXd = xd;
        if (xd < 0) {
          double xdNext = (dataNext.x - xMin) / xLength;
          // If our next point is also offscreen then ignore this point and continue.
          if (xdNext < 0) {
            if (data == dataNext) {
              // The last point is still off screen, we should add a point at (0, y) to avoid drawing nothing.
              //     |   |
              // *-->*----
              //     |   |
              //     0   1
              path.moveTo(0f, yd);
            }
            continue;
          }

          //Get the Y offset of our next point.
          double ydNext = 1 - (dataNext.value - yMin) / yLength;

          // If we are a dash line we get the closest normalized point to are graph otherwise we just set our point to 0.
          double newPosition = 0;
          if (config.isDash()) {
            newPosition = xd % 1.0f;
          }
          if (optimizeYZooming) {
            // If we are not stepped we need to adjust the starting Y position to be a linear interpolation of our new X point.
            double ratio = (newPosition - xd) / (xdNext - xd);
            yd = (1 - ratio) * yd + (ratio * ydNext);
          }
          // Set our new X position and carry on.
          xd = newPosition;
        }
        else if (xd > 1) {
          double xdPrev = (dataPrev.x - xMin) / xLength;
          if (xdPrev > 1) {
            break;
          }
          if (optimizeYZooming) {
            double ratio = (1 - xdPrev) / (xd - xdPrev);
            double ydPrev = 1 - (dataPrev.value - yMin) / yLength;
            yd = (1 - ratio) * ydPrev + (ratio * yd);
          }
          xd = 1;
        }

        if (path.getCurrentPoint() == null) {
          firstXd = xd;
          firstX = data.x;
          // If for bucket data, because the previous ending x value is next data point's starting
          // x value, i.e. (xd + interval, 1), move the path start point to (xd, 1).
          // Otherwise, move the path start point to (xd, yd).
          path.moveTo(xd, xBucketInterval != 0 ? 1 : yd);
        }
        else if (xBucketInterval == 0) {
          // If the chart is stepped, a horizontal line should be drawn from the current
          // point (e.g. (x0, y0)) to the destination's X value (e.g. (x1, y0)) before
          // drawing a line to the destination point itself (e.g. (x1, y1)).
          if (config.isStepped()) {
            float y = (float)path.getCurrentPoint().getY();
            path.lineTo(xd, y);
          }
          path.lineTo(xd, yd);
        }

        if (xBucketInterval != 0) {
          // Move line to (xd, 1) first because data points may not be equal time buckets, for example, data points are (1000, 1),
          // (1200, 2), (2000, 3).
          double barX = Math.min(1, originalXd + xBucketBarWidth);
          if (barX - xd > EPSILON) {
            path.lineTo(xd, 1);
            path.lineTo(xd, yd);
            path.lineTo(barX, yd);
            path.lineTo(barX, 1);
          }
        }
      }

      if (path.getCurrentPoint() != null) {
        // Extends the last point on the path to the end
        path.lineTo(Math.max(path.getCurrentPoint().getX(), myFillEndSupplier.getAsDouble()), path.getCurrentPoint().getY());
      }

      if (config.isFilled() && path.getCurrentPoint() != null) {
        // If the chart is filled, draw a line from the last point to X
        // axis and another one from this new point to the first destination point.
        path.lineTo(path.getCurrentPoint().getX(), 1f);
        path.lineTo(firstXd, 1f);
      }

      if (config.isFilled()) {
        // Draw the filled lines first, otherwise other lines won't be visible.
        // Also, to draw stacked and filled lines correctly, they need to be drawn in reverse order to their adding order.
        orderedPaths.addFirst(path);
        orderedSeries.addFirst(ranged);
      }
      else {
        orderedPaths.addLast(path);
        orderedSeries.addLast(ranged);
      }

      if (config.isDash() && config.isAdjustDash()) {
        DashInfo dashInfo;
        if (!myDashInfoCache.containsKey(config)) {
          dashInfo = new DashInfo();
          myDashInfoCache.put(config, dashInfo);
          // No previous dataInfo so don't bother trying to adjust dash phase.
        }
        else {
          dashInfo = myDashInfoCache.get(config);
          computeAdjustedDashPhase(dashInfo, config, path, dim, firstX, xMin, xLength, yLength);
        }
        dashInfo.myPreviousFirstX = firstX;
        dashInfo.myPreviousXMin = xMin;
        dashInfo.myPreviousXLength = xLength;
        dashInfo.myPreviousYLength = yLength;
        dashInfo.myPreviousDashPath = path;
      }
      else {
        myDashInfoCache.remove(config);
      }
    }

    myLinePaths.clear();
    myLinePaths.addAll(orderedPaths);

    myLinePathSeries.clear();
    myLinePathSeries.addAll(orderedSeries);

    addDebugInfo("postAnimate time: %d ms", TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - duration));
  }