text: /******************************************************************************
  * Copyright (c) 2018(-2021) STMicroelectronics.
  * All rights reserved.
  *
  * This file is part of the TouchGFX 4.17.0 distribution.
  *
  * This software is licensed under terms that can be found in the LICENSE file in
  * the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  *******************************************************************************/
  
  #include <touchgfx/hal/Types.hpp>
  #include <touchgfx/Drawable.hpp>
  #include <touchgfx/hal/HAL.hpp>
  #include <touchgfx/widgets/canvas/CWRUtil.hpp>
  #include <touchgfx/widgets/canvas/Canvas.hpp>
  #include <touchgfx/widgets/canvas/CanvasWidget.hpp>
  #include <touchgfx/widgets/graph/AbstractDataGraph.hpp>
  #include <touchgfx/widgets/graph/GraphElements.hpp>
  
  namespace touchgfx
  {
  AbstractGraphElement::AbstractGraphElement()
      : CanvasWidget(),
        dataScale(1)
  {
  }
  
  void AbstractGraphElement::setScale(int scale)
  {
      dataScale = scale;
  }
  
  int AbstractGraphElement::getScale() const
  {
      return dataScale;
  }
  
  AbstractDataGraph* AbstractGraphElement::getGraph() const
  {
      return (AbstractDataGraph*)(getParent()->getParent());
  }
  
  CWRUtil::Q5 AbstractGraphElement::valueToScreenXQ5(const AbstractDataGraph* graph, int x) const
  {
      return graph->valueToScreenXQ5(x);
  }
  
  CWRUtil::Q5 AbstractGraphElement::valueToScreenYQ5(const AbstractDataGraph* graph, int y) const
  {
      return graph->valueToScreenYQ5(y);
  }
  
  CWRUtil::Q5 AbstractGraphElement::indexToScreenXQ5(const AbstractDataGraph* graph, int16_t index) const
  {
      return graph->indexToScreenXQ5(index);
  }
  
  CWRUtil::Q5 AbstractGraphElement::indexToScreenYQ5(const AbstractDataGraph* graph, int16_t index) const
  {
      return graph->indexToScreenYQ5(index);
  }
  
  bool AbstractGraphElement::xScreenRangeToIndexRange(int16_t xLow, int16_t xHigh, int16_t& elementLow, int16_t& elementHigh) const
  {
      return getGraph()->xScreenRangeToIndexRange(xLow, xHigh, elementLow, elementHigh);
  }
  
  Rect AbstractGraphElement::rectFromQ5Coordinates(CWRUtil::Q5 screenXminQ5, CWRUtil::Q5 screenYminQ5, CWRUtil::Q5 screenXmaxQ5, CWRUtil::Q5 screenYmaxQ5) const
  {
      if (screenXminQ5 > screenXmaxQ5)
      {
          const CWRUtil::Q5 tmp = screenXminQ5;
          screenXminQ5 = screenXmaxQ5;
          screenXmaxQ5 = tmp;
      }
      if (screenYminQ5 > screenYmaxQ5)
      {
          const CWRUtil::Q5 tmp = screenYminQ5;
          screenYminQ5 = screenYmaxQ5;
          screenYmaxQ5 = tmp;
      }
      const int16_t xMin = screenXminQ5.to<int>();
      const int16_t yMin = screenYminQ5.to<int>();
      const int16_t xMax = screenXmaxQ5.ceil();
      const int16_t yMax = screenYmaxQ5.ceil();
      return Rect(xMin, yMin, xMax - xMin, yMax - yMin);
  }
  
  Rect AbstractGraphElement::rectAround(CWRUtil::Q5 xQ5, CWRUtil::Q5 yQ5, CWRUtil::Q5 diameterQ5) const
  {
      const CWRUtil::Q5 d2Q5 = CWRUtil::Q5((int32_t)diameterQ5 + 1) / 2;
      return rectFromQ5Coordinates(xQ5 - d2Q5, yQ5 - d2Q5, xQ5 + d2Q5, yQ5 + d2Q5);
  }
  
  CWRUtil::Q5 AbstractGraphElement::roundQ5(CWRUtil::Q5 q5) const
  {
      return CWRUtil::toQ5(q5.round());
  }
  
  int AbstractGraphElement::convertToGraphScale(const AbstractDataGraph* graph, int value, int scale) const
  {
      return graph->convertToGraphScale(value, scale);
  }
  
  int AbstractGraphElement::getGraphXAxisScaleScaled(const AbstractDataGraph* graph) const
  {
      return graph->getXAxisScaleScaled();
  }
  
  int AbstractGraphElement::getGraphXAxisOffsetScaled(const AbstractDataGraph* graph) const
  {
      return graph->getXAxisOffsetScaled();
  }
  
  int AbstractGraphElement::getGraphRangeYMinScaled(const AbstractDataGraph* graph) const
  {
      return graph->getGraphRangeYMinScaled();
  }
  
  int AbstractGraphElement::getGraphRangeYMaxScaled(const AbstractDataGraph* graph) const
  {
      return graph->getGraphRangeYMaxScaled();
  }
  
  bool AbstractGraphElement::isCenterInvisible(const AbstractDataGraph* graph, int16_t index) const
  {
      const int16_t screenXCenter = indexToScreenXQ5(graph, index).round();
      const int16_t screenYCenter = indexToScreenYQ5(graph, index).round();
      return screenXCenter < graph->getGraphAreaPaddingLeft() ||
             screenXCenter >= graph->getGraphAreaPaddingLeft() + graph->getGraphAreaWidth() ||
             screenYCenter < graph->getGraphAreaPaddingTop() ||
             screenYCenter >= graph->getGraphAreaPaddingTop() + graph->getGraphAreaHeight();
  }
  
  AbstractGraphElementNoCWR::AbstractGraphElementNoCWR()
      : color(0)
  {
  }
  
  void AbstractGraphElementNoCWR::setColor(colortype newColor)
  {
      color = newColor;
  }
  
  colortype AbstractGraphElementNoCWR::getColor() const
  {
      return color;
  }
  
  bool AbstractGraphElementNoCWR::drawCanvasWidget(const Rect& /*invalidatedArea*/) const
  {
      return true;
  }
  
  void AbstractGraphElementNoCWR::normalizeRect(Rect& rect) const
  {
      if (rect.width < 0)
      {
          rect.x += rect.width;
          rect.width = -rect.width;
      }
      if (rect.height < 0)
      {
          rect.y += rect.height;
          rect.height = -rect.height;
      }
  }
  
  GraphElementGridBase::GraphElementGridBase()
      : gridInterval(10),
        lineWidth(1),
        majorGrid(0)
  {
  }
  
  int GraphElementGridBase::getCorrectlyScaledMajorInterval(const AbstractDataGraph* graph) const
  {
      return majorGrid == 0 ? 0 : convertToGraphScale(graph, majorGrid->getIntervalScaled(), majorGrid->getScale());
  }
  
  void GraphElementGridBase::setIntervalScaled(int interval)
  {
      gridInterval = abs(interval);
  }
  
  void GraphElementGridBase::setInterval(int interval)
  {
      setIntervalScaled(abs(interval) * dataScale);
  }
  
  void GraphElementGridBase::setInterval(float interval)
  {
      setIntervalScaled(AbstractDataGraph::float2scaled(abs(interval), dataScale));
  }
  
  int GraphElementGridBase::getIntervalScaled() const
  {
      return gridInterval;
  }
  
  int GraphElementGridBase::getIntervalAsInt() const
  {
      return AbstractDataGraph::scaled2int(getIntervalScaled(), dataScale);
  }
  
  float GraphElementGridBase::getIntervalAsFloat() const
  {
      return AbstractDataGraph::scaled2float(getIntervalScaled(), dataScale);
  }
  
  void GraphElementGridBase::setMajorGrid(const GraphElementGridBase& major)
  {
      majorGrid = &major;
  }
  
  void GraphElementGridBase::setLineWidth(uint8_t width)
  {
      lineWidth = width;
  }
  
  uint8_t GraphElementGridBase::getLineWidth() const
  {
      return lineWidth;
  }
  
  void GraphElementGridBase::invalidateGraphPointAt(int16_t index)
  {
  }
  
  void GraphElementGridX::draw(const Rect& invalidatedArea) const
  {
      const AbstractDataGraph* graph = getGraph();
      const uint8_t alpha = LCD::div255(getAlpha() * graph->getAlpha());
      if (lineWidth == 0 || alpha == 0)
      {
          return;
      }
  
      Rect invalidRect = Rect(0, graph->getGraphAreaPaddingTop(), graph->getGraphAreaWidthIncludingPadding(), graph->getGraphAreaHeight()) & invalidatedArea;
  
      const int minorInterval = getIntervalAsInt();
      const int majorInterval = (majorGrid == 0) ? 0 : majorGrid->getIntervalAsInt();
  
      const CWRUtil::Q5 lw2Q5minor = CWRUtil::toQ5(lineWidth) / 2;
      const int16_t start = graph->getGraphAreaPaddingTop();
      const int16_t length = graph->getGraphAreaHeight();
      if (minorInterval == 0 && majorInterval == 0)
      {
          const int16_t pos = (valueToScreenXQ5(graph, 0) - lw2Q5minor).round();
          const int16_t graphPos = pos - graph->getGraphAreaPaddingLeft();
          if (graphPos + lineWidth >= 0 && graphPos < graph->getGraphAreaWidth())
          {
              drawLine(invalidRect, pos, start, lineWidth, length, color, alpha);
          }
      }
      else if (minorInterval > 0)
      {
          int rangeMin = graph->getGraphRangeXMin();
          int rangeMax = graph->getGraphRangeXMax();
          if (abs(rangeMax - rangeMin) / minorInterval > 500)
          {
              return; // Too many grid lines
          }
  
          if (valueToScreenXQ5(graph, rangeMin) > valueToScreenXQ5(graph, rangeMax))
          {
              const int tmp = rangeMin;
              rangeMin = rangeMax;
              rangeMax = tmp;
          }
          // Now rangeMax has a higher screen coordinate than rangeMin
  
          const int margin = rangeMin < rangeMax ? +1 : -1;
          const int minorLo = (rangeMin / minorInterval) - margin;
          const int minorHi = (rangeMax / minorInterval) + margin;
          const int minorStep = minorLo < minorHi ? +1 : -1;
          if (majorInterval == 0)
          {
              // No major lines, simply draw all lines
              for (int minorIndex = minorLo; minorIndex != minorHi + minorStep; minorIndex += minorStep)
              {
                  const int16_t pos = (valueToScreenXQ5(graph, minorInterval * minorIndex) - lw2Q5minor).round();
                  const int16_t graphPos = pos - graph->getGraphAreaPaddingLeft();
                  if (graphPos + lineWidth >= 0 && graphPos < graph->getGraphAreaWidth())
                  {
                      drawLine(invalidRect, pos, start, lineWidth, length, color, alpha);
                  }
              }
          }
          else
          {
              const int majorLo = (rangeMin / majorInterval) - margin;
              const int majorHi = (rangeMax / majorInterval) + margin;
              const int majorStep = majorLo < majorHi ? +1 : -1;
              int majorIndex = majorLo;
              int16_t majorCoord = valueToScreenXQ5(graph, majorInterval * majorIndex).round();
              int minorIndex = minorLo;
              int16_t minorCoord = valueToScreenXQ5(graph, minorInterval * minorIndex).round();
              for (;;)
              {
                  // Draw minor lines up to the major line
                  while (minorCoord < majorCoord)
                  {
                      const int16_t pos = (valueToScreenXQ5(graph, minorInterval * minorIndex) - lw2Q5minor).round();
                      const int16_t graphPos = pos - graph->getGraphAreaPaddingLeft();
                      if (graphPos + lineWidth >= 0 && graphPos < graph->getGraphAreaWidth())
                      {
                          drawLine(invalidRect, pos, start, lineWidth, length, color, alpha);
                      }
                      minorIndex += minorStep;
                      minorCoord = valueToScreenXQ5(graph, minorInterval * minorIndex).round();
                  }
                  // Advance minor past the major line we are about to draw
                  while (minorCoord <= majorCoord)
                  {
                      minorIndex += minorStep;
                      minorCoord = valueToScreenXQ5(graph, minorInterval * minorIndex).round();
                  }
                  if (majorCoord <= minorCoord)
                  {
                      majorIndex += majorStep;
                      if (majorIndex == majorHi + majorStep)
                      {
                          break;
                      }
                      majorCoord = valueToScreenXQ5(graph, majorInterval * majorIndex).round();
                  }
              }
          }
      }
  }
  
  void GraphElementGridX::drawLine(const Rect& invalidatedArea, int16_t xMin, int16_t yMin, int16_t width, int16_t length, colortype color, uint8_t alpha) const
  {
      Rect rect(xMin, yMin, width, length);
      normalizeRect(rect);
      rect = rect & invalidatedArea;
      if (!rect.isEmpty())
      {
          translateRectToAbsolute(rect);
          HAL::lcd().fillRect(rect, color, alpha);
      }
  }
  
  void GraphElementGridY::draw(const Rect& invalidatedArea) const
  {
      const AbstractDataGraph* graph = getGraph();
      const uint8_t alpha = LCD::div255(getAlpha() * graph->getAlpha());
      if (lineWidth == 0 || alpha == 0)
      {
          return;
      }
  
      Rect invalidRect = Rect(graph->getGraphAreaPaddingLeft(), 0, graph->getGraphAreaWidth(), graph->getGraphAreaHeightIncludingPadding()) & invalidatedArea;
  
      const int minorInterval = convertToGraphScale(graph, gridInterval, dataScale);
      int majorInterval = getCorrectlyScaledMajorInterval(graph);
  
      const CWRUtil::Q5 lw2Q5minor = CWRUtil::toQ5(lineWidth) / 2;
      const int16_t start = graph->getGraphAreaPaddingLeft();
      const int16_t length = graph->getGraphAreaWidth();
      if (majorInterval == 0 && minorInterval == 0)
      {
          const int16_t pos = (valueToScreenYQ5(graph, 0) - lw2Q5minor).round();
          const int16_t graphPos = pos - graph->getGraphAreaPaddingTop();
          if (graphPos + lineWidth >= 0 && graphPos < graph->getGraphAreaHeight())
          {
              drawLine(invalidRect, start, pos, lineWidth, length, color, alpha);
          }
      }
      else if (minorInterval > 0)
      {
          int rangeMin = getGraphRangeYMinScaled(graph);
          int rangeMax = getGraphRangeYMaxScaled(graph);
          if (abs(rangeMax - rangeMin) / minorInterval > 500)
          {
              return; // Too many grid lines
          }
  
          if (valueToScreenYQ5(graph, rangeMin) > valueToScreenYQ5(graph, rangeMax))
          {
              const int tmp = rangeMin;
              rangeMin = rangeMax;
              rangeMax = tmp;
          }
          // Now rangeMax has a higher screen coordinate than rangeMin
  
          const int margin = rangeMin < rangeMax ? +1 : -1;
          const int minorLo = (rangeMin / minorInterval) - margin;
          const int minorHi = (rangeMax / minorInterval) + margin;
          const int minorStep = minorLo < minorHi ? +1 : -1;
          if (majorInterval == 0)
          {
              for (int minorIndex = minorLo; minorIndex != minorHi + minorStep; minorIndex += minorStep)
              {
                  const int16_t pos = (valueToScreenYQ5(graph, minorInterval * minorIndex) - lw2Q5minor).round();
                  const int16_t graphPos = pos - graph->getGraphAreaPaddingTop();
                  if (graphPos + lineWidth >= 0 && graphPos < graph->getGraphAreaHeight())
                  {
                      drawLine(invalidRect, start, pos, lineWidth, length, color, alpha);
                  }
              }
          }
          else
          {
              const int majorLo = (rangeMin / majorInterval) - margin;
              const int majorHi = (rangeMax / majorInterval) + margin;
              const int majorStep = majorLo < majorHi ? +1 : -1;
              int majorIndex = majorLo;
              int16_t majorCoord = valueToScreenYQ5(graph, majorInterval * majorIndex).round();
              int minorIndex = minorLo;
              int16_t minorCoord = valueToScreenYQ5(graph, minorInterval * minorIndex).round();
              for (;;)
              {
                  // Draw minor lines up to the major line
                  while (minorCoord < majorCoord)
                  {
                      const int16_t pos = (valueToScreenYQ5(graph, minorInterval * minorIndex) - lw2Q5minor).round();
                      const int16_t graphPos = pos - graph->getGraphAreaPaddingTop();
                      if (graphPos + lineWidth >= 0 && graphPos < graph->getGraphAreaHeight())
                      {
                          drawLine(invalidRect, start, pos, lineWidth, length, color, alpha);
                      }
                      minorIndex += minorStep;
                      minorCoord = valueToScreenYQ5(graph, minorInterval * minorIndex).round();
                  }
                  // Advance minor past the major line we are about to draw
                  while (minorCoord <= majorCoord)
                  {
                      minorIndex += minorStep;
                      minorCoord = valueToScreenYQ5(graph, minorInterval * minorIndex).round();
                  }
                  if (majorCoord <= minorCoord)
                  {
                      majorIndex += majorStep;
                      if (majorIndex == majorHi + majorStep)
                      {
                          break;
                      }
                      majorCoord = valueToScreenYQ5(graph, majorInterval * majorIndex).round();
                  }
              }
          }
      }
  }
  
  void GraphElementGridY::drawLine(const Rect& invalidatedArea, int16_t xMin, int16_t yMin, int16_t width, int16_t length, colortype color, uint8_t alpha) const
  {
      Rect rect(xMin, yMin, length, width);
      normalizeRect(rect);
      rect = rect & invalidatedArea;
      if (!rect.isEmpty())
      {
          translateRectToAbsolute(rect);
          HAL::lcd().fillRect(rect, color, alpha);
      }
  }
  
  GraphElementArea::GraphElementArea()
      : yBaseline(0)
  {
  }
  
  void GraphElementArea::setBaselineScaled(int baseline)
  {
      yBaseline = baseline;
      invalidate();
  }
  
  void GraphElementArea::setBaseline(int baseline)
  {
      setBaselineScaled(baseline * dataScale);
  }
  
  void GraphElementArea::setBaseline(float baseline)
  {
      setBaselineScaled(AbstractDataGraph::float2scaled(baseline, dataScale));
  }
  
  int GraphElementArea::getBaselineScaled() const
  {
      return yBaseline;
  }
  
  int GraphElementArea::getBaselineAsInt() const
  {
      return AbstractDataGraph::scaled2int(getBaselineScaled(), dataScale);
  }
  
  float GraphElementArea::getBaselineAsFloat() const
  {
      return AbstractDataGraph::scaled2float(getBaselineScaled(), dataScale);
  }
  
  bool GraphElementArea::drawCanvasWidget(const Rect& invalidatedArea) const
  {
      const AbstractDataGraph* graph = getGraph();
      if (graph->getUsedCapacity() <= 1)
      {
          return true; // Nothing to draw, everything is fine!
      }
  
      int16_t indexLow;
      int16_t indexHigh;
      if (!xScreenRangeToIndexRange(invalidatedArea.x, invalidatedArea.right(), indexLow, indexHigh))
      {
          return true; // Nothing to draw, everything is fine!
      }
  
      const int16_t gapIndex = graph->getGapBeforeIndex();
      const int baseline = convertToGraphScale(graph, yBaseline, dataScale);
      const CWRUtil::Q5 screenYbaseQ5 = roundQ5(valueToScreenYQ5(graph, baseline));
      CWRUtil::Q5 screenXQ5;
      if (indexLow + 1 == gapIndex)
      {
          if (indexLow > 0)
          {
              indexLow--; // Draw the last line segment before the gap
          }
          else
          {
              indexLow++; // Do not draw a 1 segment line (a "dot")
          }
      }
  
      Rect invalidRect = Rect(graph->getGraphAreaPaddingLeft(), graph->getGraphAreaPaddingTop(), graph->getGraphAreaWidth(), graph->getGraphAreaHeight()) & invalidatedArea;
      Canvas canvas(this, invalidRect);
      canvas.moveTo(roundQ5(indexToScreenXQ5(graph, indexLow)), screenYbaseQ5);
      for (int16_t index = indexLow; index <= indexHigh; index++)
      {
          if (index == gapIndex)
          {
              canvas.lineTo(screenXQ5, screenYbaseQ5);
              screenXQ5 = roundQ5(indexToScreenXQ5(graph, index));
              canvas.lineTo(screenXQ5, screenYbaseQ5);
          }
          else
          {
              screenXQ5 = roundQ5(indexToScreenXQ5(graph, index));
          }
          canvas.lineTo(screenXQ5, roundQ5(indexToScreenYQ5(graph, index)));
      }
      canvas.lineTo(screenXQ5, screenYbaseQ5);
      return canvas.render(graph->getAlpha());
  }
  
  void GraphElementArea::invalidateGraphPointAt(int16_t index)
  {
      const AbstractDataGraph* graph = getGraph();
      const int baseline = convertToGraphScale(graph, yBaseline, dataScale);
      const CWRUtil::Q5 screenYbaseQ5 = roundQ5(valueToScreenYQ5(graph, baseline));
      CWRUtil::Q5 screenXminQ5 = indexToScreenXQ5(graph, index);
      CWRUtil::Q5 screenXmaxQ5 = screenXminQ5;
      CWRUtil::Q5 screenYminQ5 = indexToScreenYQ5(graph, index);
      CWRUtil::Q5 screenYmaxQ5 = screenYminQ5;
  
      if (index > 0)
      {
          screenXminQ5 = indexToScreenXQ5(graph, index - 1);
          const CWRUtil::Q5 screenYnewQ5 = indexToScreenYQ5(graph, index - 1);
          screenYminQ5 = MIN(screenYminQ5, screenYnewQ5);
          screenYmaxQ5 = MAX(screenYmaxQ5, screenYnewQ5);
      }
      if (index < getGraph()->getUsedCapacity() - 1)
      {
          screenXmaxQ5 = indexToScreenXQ5(graph, index + 1);
          const CWRUtil::Q5 screenYnewQ5 = indexToScreenYQ5(graph, index + 1);
          screenYminQ5 = MIN(screenYminQ5, screenYnewQ5);
          screenYmaxQ5 = MAX(screenYmaxQ5, screenYnewQ5);
      }
      screenYminQ5 = MIN(screenYminQ5, screenYbaseQ5);
      screenYmaxQ5 = MAX(screenYmaxQ5, screenYbaseQ5);
      Rect dirty(rectFromQ5Coordinates(screenXminQ5, screenYminQ5, screenXmaxQ5, screenYmaxQ5));
      dirty = dirty & Rect(0, graph->getGraphAreaPaddingTop(), graph->getGraphAreaWidthIncludingPadding(), graph->getGraphAreaHeight());
      invalidateRect(dirty);
  }
  
  GraphElementLine::GraphElementLine()
      : lineWidth(2)
  {
  }
  
  void GraphElementLine::setLineWidth(uint8_t width)
  {
      lineWidth = width;
  }
  
  uint8_t GraphElementLine::getLineWidth() const
  {
      return lineWidth;
  }
  
  bool GraphElementLine::drawCanvasWidget(const Rect& invalidatedArea) const
  {
      const AbstractDataGraph* graph = getGraph();
      if (graph->getUsedCapacity() <= 1)
      {
          return true; // Nothing to draw, everything is fine!
      }
  
      const CWRUtil::Q5 lineWidthQ5 = CWRUtil::toQ5(lineWidth);
      const uint16_t lineWidthHalf = CWRUtil::Q5(((int)lineWidthQ5 + 1) / 2).ceil();
      int16_t indexMin;
      int16_t indexMax;
      if (!xScreenRangeToIndexRange(invalidatedArea.x - lineWidthHalf, invalidatedArea.right() + lineWidthHalf, indexMin, indexMax))
      {
          return true; // Nothing to draw, everything is fine!
      }
  
      Rect invalidRect = Rect(0, graph->getGraphAreaPaddingTop(), graph->getGraphAreaWidthIncludingPadding(), graph->getGraphAreaHeight()) & invalidatedArea;
      Canvas canvas(this, invalidRect);
      const int16_t gapIndex = graph->getGapBeforeIndex();
      if (gapIndex <= 0 || gapIndex <= indexMin || gapIndex > indexMax)
      {
          drawIndexRange(canvas, graph, indexMin, indexMax);
      }
      else
      {
          drawIndexRange(canvas, graph, indexMin, gapIndex - 1);
          drawIndexRange(canvas, graph, gapIndex, indexMax);
      }
      return canvas.render(graph->getAlpha());
  }
  
  void GraphElementLine::invalidateGraphPointAt(int16_t index)
  {
      const AbstractDataGraph* graph = getGraph();
      CWRUtil::Q5 lineWidthQ5 = CWRUtil::toQ5(lineWidth);
      Rect dirty(rectAround(indexToScreenXQ5(graph, index), indexToScreenYQ5(graph, index), lineWidthQ5));
      if (index > 0)
      {
          Rect other(rectAround(indexToScreenXQ5(graph, index - 1), indexToScreenYQ5(graph, index - 1), lineWidthQ5));
          dirty.expandToFit(other);
      }
      if (index < graph->getUsedCapacity() - 1)
      {
          Rect other(rectAround(indexToScreenXQ5(graph, index + 1), indexToScreenYQ5(graph, index + 1), lineWidthQ5));
          dirty.expandToFit(other);
      }
      dirty = dirty & Rect(0, graph->getGraphAreaPaddingTop(), graph->getGraphAreaWidthIncludingPadding(), graph->getGraphAreaHeight());
      invalidateRect(dirty);
  }
  
  void GraphElementLine::drawIndexRange(Canvas& canvas, const AbstractDataGraph* graph, int16_t indexMin, int16_t indexMax) const
  {
      if (indexMin == indexMax)
      {
          return;
      }
  
      const CWRUtil::Q5 lineWidthQ5 = CWRUtil::toQ5(lineWidth);
  
      CWRUtil::Q5 screenXstartQ5 = roundQ5(indexToScreenXQ5(graph, indexMin));
      CWRUtil::Q5 screenYstartQ5 = roundQ5(indexToScreenYQ5(graph, indexMin));
      canvas.moveTo(screenXstartQ5, screenYstartQ5);
      int16_t index = indexMin;
      int16_t advance = 1;
      do
      {
          if (index == indexMax)
          {
              advance = -1;
          }
          index += advance;
          const CWRUtil::Q5 screenXendQ5 = roundQ5(indexToScreenXQ5(graph, index));
          const CWRUtil::Q5 screenYendQ5 = roundQ5(indexToScreenYQ5(graph, index));
          CWRUtil::Q5 dxQ5 = screenXendQ5 - screenXstartQ5;
          CWRUtil::Q5 dyQ5 = screenYendQ5 - screenYstartQ5;
          const CWRUtil::Q5 dQ5 = CWRUtil::length(dxQ5, dyQ5);
          if (dQ5)
          {
              dyQ5 = CWRUtil::muldivQ5(lineWidthQ5, dyQ5, dQ5) / 2;
              dxQ5 = CWRUtil::muldivQ5(lineWidthQ5, dxQ5, dQ5) / 2;
              canvas.lineTo(screenXstartQ5 - dyQ5, screenYstartQ5 + dxQ5);
              canvas.lineTo(screenXendQ5 - dyQ5, screenYendQ5 + dxQ5);
              screenXstartQ5 = screenXendQ5;
              screenYstartQ5 = screenYendQ5;
          }
      } while (index > indexMin);
  }
  
  void GraphElementVerticalGapLine::setGapLineWidth(uint16_t width)
  {
      lineWidth = width;
  }
  
  uint16_t GraphElementVerticalGapLine::getGapLineWidth() const
  {
      return lineWidth;
  }
  
  void GraphElementVerticalGapLine::draw(const Rect& invalidatedArea) const
  {
      const AbstractDataGraph* graph = getGraph();
      const int16_t gapIndex = graph->getGapBeforeIndex();
      const uint8_t alpha = LCD::div255(getAlpha() * graph->getAlpha());
      if (gapIndex == 0 || alpha == 0)
      {
          return;
      }
  
      const int16_t screenXmin = indexToScreenXQ5(graph, gapIndex - 1).round();
      int16_t screenXmax = screenXmin + lineWidth;
      if (lineWidth == 0)
      {
          screenXmax = indexToScreenXQ5(graph, gapIndex).round();
      }
      Rect rect(screenXmin, graph->getGraphAreaPaddingTop(), screenXmax - screenXmin, graph->getGraphAreaHeight());
      normalizeRect(rect);
      rect = rect & invalidatedArea;
      if (!rect.isEmpty())
      {
          translateRectToAbsolute(rect);
          HAL::lcd().fillRect(rect, color, alpha);
      }
  }
  
  void GraphElementVerticalGapLine::invalidateGraphPointAt(int16_t index)
  {
      const AbstractDataGraph* graph = getGraph();
      invalidateIndex(graph, graph->getGapBeforeIndex());
      invalidateIndex(graph, index);
  }
  
  void GraphElementVerticalGapLine::invalidateIndex(const AbstractDataGraph* graph, int16_t index)
  {
      if (index > 0)
      {
          const int16_t screenXmin = indexToScreenXQ5(graph, index - 1).round();
          int16_t screenXmax = screenXmin + lineWidth;
          if (lineWidth == 0)
          {
              screenXmax = indexToScreenXQ5(graph, index).round();
          }
          Rect dirty(screenXmin, graph->getGraphAreaPaddingTop(), screenXmax - screenXmin, graph->getGraphAreaHeight());
          normalizeRect(dirty);
          invalidateRect(dirty);
      }
  }
  
  GraphElementHistogram::GraphElementHistogram()
      : yBaseline(0),
        barWidth(2),
        barOffset(0)
  {
  }
  
  void GraphElementHistogram::setBaselineScaled(int baseline)
  {
      yBaseline = baseline;
      invalidate();
  }
  
  void GraphElementHistogram::setBaseline(int baseline)
  {
      setBaselineScaled(baseline * dataScale);
  }
  
  void GraphElementHistogram::setBaseline(float baseline)
  {
      setBaselineScaled(AbstractDataGraph::float2scaled(baseline, dataScale));
  }
  
  int GraphElementHistogram::getBaselineScaled() const
  {
      return yBaseline;
  }
  
  int GraphElementHistogram::getBaselineAsInt() const
  {
      return AbstractDataGraph::scaled2int(getBaselineScaled(), dataScale);
  }
  
  float GraphElementHistogram::getBaselineAsFloat() const
  {
      return AbstractDataGraph::scaled2float(getBaselineScaled(), dataScale);
  }
  
  void GraphElementHistogram::setBarWidth(uint16_t width)
  {
      barWidth = width;
  }
  
  uint16_t GraphElementHistogram::getBarWidth() const
  {
      return barWidth;
  }
  
  void GraphElementHistogram::setBarOffset(int16_t offset)
  {
      barOffset = offset;
  }
  
  int16_t GraphElementHistogram::getBarOffset() const
  {
      return barOffset;
  }
  
  void GraphElementHistogram::draw(const Rect& invalidatedArea) const
  {
      const AbstractDataGraph* graph = getGraph();
      const uint8_t alpha = LCD::div255(getAlpha() * graph->getAlpha());
      if (graph->getUsedCapacity() == 0 || barWidth == 0 || alpha == 0)
      {
          return; // Nothing to draw, everything is fine!
      }
  
      const int baseline = convertToGraphScale(graph, yBaseline, dataScale);
      const CWRUtil::Q5 barOffsetQ5 = CWRUtil::toQ5(barOffset);
      const CWRUtil::Q5 barWidthQ5 = CWRUtil::toQ5(barWidth);
      const CWRUtil::Q5 barWidthHalfQ5 = CWRUtil::Q5(((int)barWidthQ5 + 1) / 2);
      const int16_t barWidthHalf = barWidthHalfQ5.ceil();
      const int16_t screenYzero = valueToScreenYQ5(graph, baseline).round();
      int16_t indexLow;
      int16_t indexHigh;
      if (!xScreenRangeToIndexRange(invalidatedArea.x + barOffset - barWidthHalf, invalidatedArea.right() + barOffset + barWidthHalf, indexLow, indexHigh))
      {
          return; // Nothing to draw, everything is fine!
      }
  
      Rect invalidRect = Rect(0, graph->getGraphAreaPaddingTop(), graph->getGraphAreaWidthIncludingPadding(), graph->getGraphAreaHeight()) & invalidatedArea;
      for (int16_t index = indexLow; index <= indexHigh; index++)
      {
          const int16_t screenX = (indexToScreenXQ5(graph, index) + barOffsetQ5 - barWidthHalfQ5).round();
          const int16_t screenY = indexToScreenYQ5(graph, index).round();
          Rect rect(screenX, screenY, barWidth, screenYzero - screenY);
          normalizeRect(rect);
          rect = rect & invalidRect;
          if (!rect.isEmpty())
          {
              translateRectToAbsolute(rect);
              HAL::lcd().fillRect(rect, color, alpha);
          }
      }
  }
  
  void GraphElementHistogram::invalidateGraphPointAt(int16_t index)
  {
      const AbstractDataGraph* graph = getGraph();
      const CWRUtil::Q5 screenXQ5 = indexToScreenXQ5(graph, index);
      const CWRUtil::Q5 barWidthHalfQ5 = CWRUtil::Q5((int)CWRUtil::toQ5(barWidth) + 1) / 2;
      const CWRUtil::Q5 barOffsetQ5 = CWRUtil::toQ5(barOffset);
      const int baseline = convertToGraphScale(graph, yBaseline, dataScale);
      Rect dirty(rectFromQ5Coordinates(screenXQ5 + barOffsetQ5 - barWidthHalfQ5, indexToScreenYQ5(graph, index), screenXQ5 + barOffsetQ5 + barWidthHalfQ5, valueToScreenYQ5(graph, baseline)));
      dirty = dirty & Rect(0, graph->getGraphAreaPaddingTop(), graph->getGraphAreaWidthIncludingPadding(), graph->getGraphAreaHeight());
      invalidateRect(dirty);
  }
  
  GraphElementBoxes::GraphElementBoxes()
      : boxWidth(2)
  {
  }
  
  void GraphElementBoxes::setBoxWidth(uint16_t width)
  {
      boxWidth = width;
  }
  
  uint16_t GraphElementBoxes::getBoxWidth() const
  {
      return boxWidth;
  }
  
  void GraphElementBoxes::draw(const Rect& invalidatedArea) const
  {
      const AbstractDataGraph* graph = getGraph();
      const uint8_t alpha = LCD::div255(getAlpha() * graph->getAlpha());
      if (graph->getUsedCapacity() == 0 || boxWidth == 0 || alpha == 0)
      {
          return; // Nothing to draw, everything is fine!
      }
  
      const CWRUtil::Q5 boxWidthQ5 = CWRUtil::toQ5(boxWidth);
      const CWRUtil::Q5 boxWidthHalfQ5 = boxWidthQ5 / 2;
      int16_t indexLow;
      int16_t indexHigh;
      const uint16_t boxWidthHalf = CWRUtil::Q5(((int)boxWidthQ5 + 1) / 2).ceil();
      if (!xScreenRangeToIndexRange(invalidatedArea.x - boxWidthHalf, invalidatedArea.right() + boxWidthHalf, indexLow, indexHigh))
      {
          return; // Nothing to draw, everything is fine!
      }
  
      for (int16_t index = indexLow; index <= indexHigh; index++)
      {
          if (isCenterInvisible(graph, index))
          {
              continue;
          }
          const CWRUtil::Q5 screenXQ5 = indexToScreenXQ5(graph, index);
          const CWRUtil::Q5 screenYQ5 = indexToScreenYQ5(graph, index);
          Rect rect((screenXQ5 - boxWidthHalfQ5).round(), (screenYQ5 - boxWidthHalfQ5).round(), boxWidth, boxWidth);
          rect = rect & invalidatedArea;
          if (!rect.isEmpty())
          {
              translateRectToAbsolute(rect);
              HAL::lcd().fillRect(rect, color, alpha);
          }
      }
  }
  
  void GraphElementBoxes::invalidateGraphPointAt(int16_t index)
  {
      const AbstractDataGraph* graph = getGraph();
      if (isCenterInvisible(graph, index))
      {
          return;
      }
      const CWRUtil::Q5 boxWidthQ5 = CWRUtil::toQ5(boxWidth);
      const CWRUtil::Q5 boxWidthHalfQ5 = boxWidthQ5 / 2;
      const CWRUtil::Q5 screenXQ5 = indexToScreenXQ5(graph, index);
      const CWRUtil::Q5 screenYQ5 = indexToScreenYQ5(graph, index);
      Rect dirty((screenXQ5 - boxWidthHalfQ5).round(), (screenYQ5 - boxWidthHalfQ5).round(), boxWidth, boxWidth);
      invalidateRect(dirty);
  }
  
  GraphElementDots::GraphElementDots()
      : dotWidth(2)
  {
  }
  
  void GraphElementDots::setDotWidth(uint8_t width)
  {
      dotWidth = width;
  }
  
  uint8_t GraphElementDots::getDotWidth() const
  {
      return dotWidth;
  }
  
  bool GraphElementDots::drawCanvasWidget(const Rect& invalidatedArea) const
  {
      const AbstractDataGraph* graph = getGraph();
      if (graph->getUsedCapacity() == 0)
      {
          return true; // Nothing to draw, everything is fine!
      }
  
      const CWRUtil::Q5 dotWidthQ5 = CWRUtil::toQ5(dotWidth);
      const CWRUtil::Q5 dotWidth3Q5 = CWRUtil::muldivQ5(dotWidthQ5, CWRUtil::toQ5(3), CWRUtil::toQ5(10));
      const CWRUtil::Q5 dotWidth4Q5 = CWRUtil::muldivQ5(dotWidthQ5, CWRUtil::toQ5(4), CWRUtil::toQ5(10));
      const CWRUtil::Q5 dotWidth5Q5 = CWRUtil::muldivQ5(dotWidthQ5, CWRUtil::toQ5(5), CWRUtil::toQ5(26));
      const CWRUtil::Q5 dotWidth12Q5 = CWRUtil::muldivQ5(dotWidthQ5, CWRUtil::toQ5(12), CWRUtil::toQ5(26));
      const CWRUtil::Q5 dotWidth2Q5 = CWRUtil::muldivQ5(dotWidthQ5, CWRUtil::toQ5(1), CWRUtil::toQ5(2));
      const uint16_t dotWidthHalf = CWRUtil::Q5(((int)dotWidthQ5 + 1) / 2).ceil(); // Round up
      int16_t indexLow;
      int16_t indexHigh;
      if (!xScreenRangeToIndexRange(invalidatedArea.x - dotWidthHalf, invalidatedArea.right() + dotWidthHalf, indexLow, indexHigh))
      {
          return true;
      }
  
      const bool bigDots = (dotWidth > 6);
      Canvas canvas(this, invalidatedArea);
      for (int16_t index = indexLow; index <= indexHigh; index++)
      {
          if (isCenterInvisible(graph, index))
          {
              continue;
          }
          const CWRUtil::Q5 screenXcenterQ5 = roundQ5(indexToScreenXQ5(graph, index));
          const CWRUtil::Q5 screenYcenterQ5 = roundQ5(indexToScreenYQ5(graph, index));
          Rect dirty(rectAround(screenXcenterQ5, screenYcenterQ5, dotWidthQ5) & invalidatedArea);
          if (!dirty.isEmpty())
          {
              if (bigDots)
              {
                  canvas.moveTo(screenXcenterQ5 - dotWidth2Q5, screenYcenterQ5);
                  canvas.lineTo(screenXcenterQ5 - dotWidth12Q5, screenYcenterQ5 - dotWidth5Q5);
                  canvas.lineTo(screenXcenterQ5 - dotWidth4Q5, screenYcenterQ5 - dotWidth3Q5);
                  canvas.lineTo(screenXcenterQ5 - dotWidth3Q5, screenYcenterQ5 - dotWidth4Q5);
                  canvas.lineTo(screenXcenterQ5 - dotWidth5Q5, screenYcenterQ5 - dotWidth12Q5);
                  canvas.lineTo(screenXcenterQ5, screenYcenterQ5 - dotWidth2Q5);
                  canvas.lineTo(screenXcenterQ5 + dotWidth5Q5, screenYcenterQ5 - dotWidth12Q5);
                  canvas.lineTo(screenXcenterQ5 + dotWidth3Q5, screenYcenterQ5 - dotWidth4Q5);
                  canvas.lineTo(screenXcenterQ5 + dotWidth4Q5, screenYcenterQ5 - dotWidth3Q5);
                  canvas.lineTo(screenXcenterQ5 + dotWidth12Q5, screenYcenterQ5 - dotWidth5Q5);
                  canvas.lineTo(screenXcenterQ5 + dotWidth2Q5, screenYcenterQ5);
                  canvas.lineTo(screenXcenterQ5 + dotWidth12Q5, screenYcenterQ5 + dotWidth5Q5);
                  canvas.lineTo(screenXcenterQ5 + dotWidth4Q5, screenYcenterQ5 + dotWidth3Q5);
                  canvas.lineTo(screenXcenterQ5 + dotWidth3Q5, screenYcenterQ5 + dotWidth4Q5);
                  canvas.lineTo(screenXcenterQ5 + dotWidth5Q5, screenYcenterQ5 + dotWidth12Q5);
                  canvas.lineTo(screenXcenterQ5, screenYcenterQ5 + dotWidth2Q5);
                  canvas.lineTo(screenXcenterQ5 - dotWidth5Q5, screenYcenterQ5 + dotWidth12Q5);
                  canvas.lineTo(screenXcenterQ5 - dotWidth3Q5, screenYcenterQ5 + dotWidth4Q5);
                  canvas.lineTo(screenXcenterQ5 - dotWidth4Q5, screenYcenterQ5 + dotWidth3Q5);
                  canvas.lineTo(screenXcenterQ5 - dotWidth12Q5, screenYcenterQ5 + dotWidth5Q5);
              }
              else
              {
                  canvas.moveTo(screenXcenterQ5 - dotWidth2Q5, screenYcenterQ5);
                  canvas.lineTo(screenXcenterQ5 - dotWidth4Q5, screenYcenterQ5 - dotWidth3Q5);
                  canvas.lineTo(screenXcenterQ5 - dotWidth3Q5, screenYcenterQ5 - dotWidth4Q5);
                  canvas.lineTo(screenXcenterQ5, screenYcenterQ5 - dotWidth2Q5);
                  canvas.lineTo(screenXcenterQ5 + dotWidth3Q5, screenYcenterQ5 - dotWidth4Q5);
                  canvas.lineTo(screenXcenterQ5 + dotWidth4Q5, screenYcenterQ5 - dotWidth3Q5);
                  canvas.lineTo(screenXcenterQ5 + dotWidth2Q5, screenYcenterQ5);
                  canvas.lineTo(screenXcenterQ5 + dotWidth4Q5, screenYcenterQ5 + dotWidth3Q5);
                  canvas.lineTo(screenXcenterQ5 + dotWidth3Q5, screenYcenterQ5 + dotWidth4Q5);
                  canvas.lineTo(screenXcenterQ5, screenYcenterQ5 + dotWidth2Q5);
                  canvas.lineTo(screenXcenterQ5 - dotWidth3Q5, screenYcenterQ5 + dotWidth4Q5);
                  canvas.lineTo(screenXcenterQ5 - dotWidth4Q5, screenYcenterQ5 + dotWidth3Q5);
              }
          }
      }
      return canvas.render(graph->getAlpha());
  }
  
  void GraphElementDots::invalidateGraphPointAt(int16_t index)
  {
      const AbstractDataGraph* graph = getGraph();
      if (isCenterInvisible(graph, index))
      {
          return;
      }
      Rect dirty(rectAround(indexToScreenXQ5(graph, index), indexToScreenYQ5(graph, index), CWRUtil::toQ5(dotWidth)));
      dirty = dirty & Rect(0, graph->getGraphAreaPaddingTop(), graph->getGraphAreaWidthIncludingPadding(), graph->getGraphAreaHeight());
      invalidateRect(dirty);
  }
  
  GraphElementDiamonds::GraphElementDiamonds()
      : diamondWidth(2)
  {
  }
  
  void GraphElementDiamonds::setDiamondWidth(uint8_t width)
  {
      diamondWidth = width;
  }
  
  uint8_t GraphElementDiamonds::getDiamondWidth() const
  {
      return diamondWidth;
  }
  
  bool GraphElementDiamonds::drawCanvasWidget(const Rect& invalidatedArea) const
  {
      const AbstractDataGraph* graph = getGraph();
      if (graph->getUsedCapacity() == 0)
      {
          return true; // Nothing to draw, everything is fine!
      }
  
      const CWRUtil::Q5 diamondWidthQ5 = CWRUtil::toQ5(diamondWidth);
      const CWRUtil::Q5 dotWidthHalfQ5 = diamondWidthQ5 / 2;
      const uint16_t dotWidthHalf = CWRUtil::Q5(((int)diamondWidthQ5 + 1) / 2).ceil(); // Round up
      int16_t indexLow;
      int16_t indexHigh;
      if (!xScreenRangeToIndexRange(invalidatedArea.x - dotWidthHalf, invalidatedArea.right() + dotWidthHalf, indexLow, indexHigh))
      {
          return true;
      }
  
      Canvas canvas(this, invalidatedArea);
      for (int16_t index = indexLow; index <= indexHigh; index++)
      {
          if (isCenterInvisible(graph, index))
          {
              continue;
          }
          const CWRUtil::Q5 screenXcenterQ5 = roundQ5(indexToScreenXQ5(graph, index));
          const CWRUtil::Q5 screenYcenterQ5 = roundQ5(indexToScreenYQ5(graph, index));
          const Rect dirty(rectAround(screenXcenterQ5, screenYcenterQ5, diamondWidthQ5) & invalidatedArea);
          if (!dirty.isEmpty())
          {
              canvas.moveTo(screenXcenterQ5 - dotWidthHalfQ5, screenYcenterQ5);
              canvas.lineTo(screenXcenterQ5, screenYcenterQ5 - dotWidthHalfQ5);
              canvas.lineTo(screenXcenterQ5 + dotWidthHalfQ5, screenYcenterQ5);
              canvas.lineTo(screenXcenterQ5, screenYcenterQ5 + dotWidthHalfQ5);
          }
      }
      return canvas.render(graph->getAlpha());
  }
  
  void GraphElementDiamonds::invalidateGraphPointAt(int16_t index)
  {
      const AbstractDataGraph* graph = getGraph();
      if (isCenterInvisible(graph, index))
      {
          return;
      }
      Rect dirty(rectAround(indexToScreenXQ5(graph, index), indexToScreenYQ5(graph, index), CWRUtil::toQ5(diamondWidth)));
      invalidateRect(dirty);
  }
  
  } // namespace touchgfx
  