void paint()

in packages/devtools_app/lib/src/charts/chart.dart [152:423]


  void paint(Canvas canvas, Size size) {
    // TODO(terry): Used to monitor total painting time. For large
    //              datasets e.g., offline files of 10,000s of data
    //              points time can be kind of slows. Consider only
    //              sampling 1 point per horizontal pixel.
    final startTime = DateTime.now();

    final axis = Paint()
      ..strokeWidth = axisWidth
      ..color = Colors.grey;

    if (size != chartController.size) {
      chartController.size = size;
      chartController.computeChartArea();
    }

    drawTranslate(
      canvas,
      chartController.xCanvasChart,
      chartController.yCanvasChart,
      (canavas) {
        drawAxes(
          canvas,
          size,
          axis,
          displayX: chartController.displayXAxis,
          displayTopLine: chartController.displayTopLine,
        );
      },
    );

    final traces = chartController.traces;
    final tracesDataIndex = List<int>.generate(
      traces.length,
      (int index) {
        final length = traces[index].data.length;
        return length > 0 ? length - 1 : -1;
      },
    );

    /// Key is trace index and value is x,y point.
    final previousTracesData = <int, PointAndBase>{};

    /// Key is trace index and value is x,y point.
    final currentTracesData = <int, PointAndBase>{};

    // Visible Y max.
    var visibleYMax = 0.0;

    // TODO(terry): Need to compute x-axis left-most position for last timestamp.
    //              May need to do the other direction so it looks better.
    final endVisibleIndex =
        chartController.timestampsLength - chartController.visibleTicks;

    final xTranslation = chartController.xCoordLeftMostVisibleTimestamp;
    final yTranslation = chartController.zeroYPosition;

    int xTickIndex = chartController.timestampsLength;
    while (--xTickIndex >= 0) {
      final currentTimestamp = chartController.timestamps[xTickIndex];

      if (xTickIndex < endVisibleIndex) {
        // Once outside of visible range of data skip the rest of the collected data.
        break;
      }

      final tracesLength = traces.length;

      // Short-circuit if no traceDataIndexes left (all are -1) then we're done.
      if (!tracesDataIndex.any((element) => element >= 0)) continue;

      // Remember old cliprect.
      canvas.save();

      // Clip to the just the area being plotted.  This is important so symbols
      // larger than the tick area doesn't spill out on the left-side.
      clipChart(canvas);

      // Y base (stackedY) used for stacked traces each Y base for stack traces
      // starts at zero. Then Y base for the current data point is the previous Y
      // base (previous Y's datapoint). Then current data point's Y is added to Y base.
      double stackedY = 0.0;

      for (var index = 0; index < tracesLength; index++) {
        final traceDataIndex = tracesDataIndex[index];
        if (traceDataIndex >= 0) {
          final trace = traces[index];
          final traceData = trace.data[traceDataIndex];

          final yValue = (trace.stacked) ? stackedY + traceData.y : traceData.y;

          final xTimestamp = traceData.timestamp;
          final xCanvasCoord =
              chartController.timestampToXCanvasCoord(xTimestamp);
          if (currentTimestamp == xTimestamp) {
            if (xCanvasCoord != null) {
              // Get ready to render on canvas. Remember old canvas state
              // and setup translations for x,y coordinates into the rendering
              // area of the chart.
              drawTranslate(
                canvas,
                xTranslation,
                yTranslation,
                (canvas) {
                  final xCoord = xCanvasCoord;
                  final yCoord =
                      chartController.yPositionToYCanvasCoord(yValue);
                  final hasMultipleExtensionEvents =
                      traceData is DataAggregate ? traceData.count > 1 : false;

                  // Is the visible Y-axis max larger.
                  if (yValue > visibleYMax) {
                    visibleYMax = yValue;
                  }

                  currentTracesData[index] = PointAndBase(
                    xCoord,
                    yCoord,
                    yBase: chartController.yPositionToYCanvasCoord(stackedY),
                  );

                  if (trace.chartType == ChartType.symbol) {
                    assert(!trace.stacked);
                    drawSymbol(
                      canvas,
                      trace.characteristics,
                      xCoord,
                      yCoord,
                      hasMultipleExtensionEvents,
                      trace.symbolPath,
                    );
                  } else if (trace.chartType == ChartType.line) {
                    if (trace.characteristics.symbol ==
                        ChartSymbol.dashedLine) {
                      // TODO(terry): Collect all points and draw a dashed line using
                      // path_drawing package.
                      drawDashed(
                        canvas,
                        trace.characteristics,
                        xCoord,
                        yCoord,
                        chartController.tickWidth - 4,
                      );
                    } else if (previousTracesData[index] != null) {
                      // Stacked lines.
                      // Drawline from previous plotted point to new point.
                      drawConnectedLine(
                        canvas,
                        trace.characteristics,
                        xCoord,
                        yCoord,
                        previousTracesData[index].x,
                        previousTracesData[index].y,
                      );
                      drawSymbol(
                        canvas,
                        trace.characteristics,
                        xCoord,
                        yCoord,
                        hasMultipleExtensionEvents,
                        trace.symbolPath,
                      );
                      // TODO(terry): Honor z-order and also maybe path just on the traces e.g.,
                      //              fill from top of trace 0 to top of trace 1 don't origin
                      //              from zero.
                      // Fill area between traces.
                      drawFillArea(
                        canvas,
                        trace.characteristics,
                        previousTracesData[index].x,
                        previousTracesData[index].y,
                        previousTracesData[index].base,
                        currentTracesData[index].x,
                        currentTracesData[index].y,
                        currentTracesData[index].base,
                      );
                    } else {
                      // Draw point
                      drawSymbol(
                        canvas,
                        trace.characteristics,
                        xCoord,
                        yCoord,
                        hasMultipleExtensionEvents,
                        trace.symbolPath,
                      );
                    }
                  }
                  tracesDataIndex[index]--;
                },
              );
            }

            final tapLocation = chartController.tapLocation?.value;
            if (tapLocation?.index == xTickIndex ||
                tapLocation?.timestamp == currentTimestamp) {
              drawTranslate(
                canvas,
                xTranslation,
                yTranslation,
                (canavas) {
                  drawSelection(
                    canvas,
                    xCanvasCoord,
                    chartController.canvasChartHeight,
                  );
                },
              );
            }
          }

          if (trace.stacked) {
            stackedY += traceData.y;
          }
        }

        previousTracesData.addAll(currentTracesData);
        currentTracesData.clear();
      }

      // Undo the clipRect at beginning of for loop.
      canvas.restore();
    }

    chartController.computeChartArea();
    chartController.buildLabelTimestamps();

    if (chartController.displayXAxis || chartController.displayXLabels) {
      // Y translation is below X-axis line.
      drawTranslate(
        canvas,
        xTranslation,
        chartController.zeroYPosition + 1,
        (canvas) {
          // Draw the X-axis labels.
          for (var timestamp in chartController.labelTimestamps) {
            final xCoord = chartController.timestampToXCanvasCoord(timestamp);
            drawXTick(canvas, timestamp, xCoord, axis, displayTime: true);
          }
        },
      );

      // X translation is left-most edge of chart widget.
      drawTranslate(
        canvas,
        chartController.xCanvasChart,
        yTranslation,
        (canvas) {
          // Rescale Y-axis to max visible Y range.
          chartController.resetYMaxValue(visibleYMax);

          // Draw Y-axis ticks and labels.
          // TODO(terry): Optimization add a listener for Y-axis range changing
          //              only need to redraw Y-axis if the range changed.
          if (chartController.displayYLabels) {
            drawYTicks(canvas, chartController, axis);
          }
        },
      );
    }

    drawTitle(canvas, size, chartController.title);

    final elapsedTime = DateTime.now().difference(startTime).inMilliseconds;
    if (debugTrackPaintTime && elapsedTime > 500) {
      logger.log('${chartController.name} ${chartController.timestampsLength} '
          'CustomPainter paint elapsed time $elapsedTime');
    }

    // Once painted we're not dirty anymore.
    chartController.dirty = false;
  }