예제 #1
0
  /**
   * Draws the background to the specified graphics device. If the dial frame specifies a window,
   * the clipping region will already have been set to this window before this method is called.
   *
   * @param g2 the graphics device (<code>null</code> not permitted).
   * @param plot the plot (ignored here).
   * @param frame the dial frame (ignored here).
   * @param view the view rectangle (<code>null</code> not permitted).
   */
  @Override
  public void draw(Graphics2D g2, DialPlot plot, Rectangle2D frame, Rectangle2D view) {

    // work out the anchor point
    Rectangle2D f = DialPlot.rectangleByRadius(frame, this.radius, this.radius);
    Arc2D arc = new Arc2D.Double(f, this.angle, 0.0, Arc2D.OPEN);
    Point2D pt = arc.getStartPoint();

    // the indicator bounds is calculated from the templateValue (which
    // determines the minimum size), the maxTemplateValue (which, if
    // specified, provides a maximum size) and the actual value
    FontMetrics fm = g2.getFontMetrics(this.font);
    double value = plot.getValue(this.datasetIndex);
    String valueStr = this.formatter.format(value);
    Rectangle2D valueBounds = TextUtilities.getTextBounds(valueStr, g2, fm);

    // calculate the bounds of the template value
    String s = this.formatter.format(this.templateValue);
    Rectangle2D tb = TextUtilities.getTextBounds(s, g2, fm);
    double minW = tb.getWidth();
    double minH = tb.getHeight();

    double maxW = Double.MAX_VALUE;
    double maxH = Double.MAX_VALUE;
    if (this.maxTemplateValue != null) {
      s = this.formatter.format(this.maxTemplateValue);
      tb = TextUtilities.getTextBounds(s, g2, fm);
      maxW = Math.max(tb.getWidth(), minW);
      maxH = Math.max(tb.getHeight(), minH);
    }
    double w = fixToRange(valueBounds.getWidth(), minW, maxW);
    double h = fixToRange(valueBounds.getHeight(), minH, maxH);

    // align this rectangle to the frameAnchor
    Rectangle2D bounds =
        RectangleAnchor.createRectangle(new Size2D(w, h), pt.getX(), pt.getY(), this.frameAnchor);

    // add the insets
    Rectangle2D fb = this.insets.createOutsetRectangle(bounds);

    // draw the background
    g2.setPaint(this.backgroundPaint);
    g2.fill(fb);

    // draw the border
    g2.setStroke(this.outlineStroke);
    g2.setPaint(this.outlinePaint);
    g2.draw(fb);

    // now find the text anchor point
    Shape savedClip = g2.getClip();
    g2.clip(fb);

    Point2D pt2 = RectangleAnchor.coordinates(bounds, this.valueAnchor);
    g2.setPaint(this.paint);
    g2.setFont(this.font);
    TextUtilities.drawAlignedString(
        valueStr, g2, (float) pt2.getX(), (float) pt2.getY(), this.textAnchor);
    g2.setClip(savedClip);
  }
  /**
   * Draws an item label.
   *
   * @param g2 the graphics device.
   * @param orientation the orientation.
   * @param dataset the dataset.
   * @param series the series index (zero-based).
   * @param item the item index (zero-based).
   * @param x the x coordinate (in Java2D space).
   * @param y the y coordinate (in Java2D space).
   * @param negative indicates a negative value (which affects the item label position).
   */
  private void drawAdditionalItemLabel(
      Graphics2D g2,
      PlotOrientation orientation,
      XYDataset dataset,
      int series,
      int item,
      double x,
      double y) {

    if (this.additionalItemLabelGenerator == null) {
      return;
    }

    Font labelFont = getItemLabelFont(series, item);
    Paint paint = getItemLabelPaint(series, item);
    g2.setFont(labelFont);
    g2.setPaint(paint);
    String label = this.additionalItemLabelGenerator.generateLabel(dataset, series, item);

    ItemLabelPosition position = getNegativeItemLabelPosition(series, item);
    Point2D anchorPoint =
        calculateLabelAnchorPoint(position.getItemLabelAnchor(), x, y, orientation);
    TextUtilities.drawRotatedString(
        label,
        g2,
        (float) anchorPoint.getX(),
        (float) anchorPoint.getY(),
        position.getTextAnchor(),
        position.getAngle(),
        position.getRotationAnchor());
  }
  /**
   * Draws an item label.
   *
   * @param g2 the graphics device.
   * @param orientation the orientation.
   * @param dataset the dataset.
   * @param row the row.
   * @param column the column.
   * @param x the x coordinate (in Java2D space).
   * @param y the y coordinate (in Java2D space).
   * @param negative indicates a negative value (which affects the item label position).
   */
  protected void drawItemLabel(
      Graphics2D g2,
      PlotOrientation orientation,
      CategoryDataset dataset,
      int row,
      int column,
      double x,
      double y,
      boolean negative) {

    CategoryItemLabelGenerator generator = getItemLabelGenerator(row, column);
    if (generator != null) {
      Font labelFont = getItemLabelFont(row, column);
      Paint paint = getItemLabelPaint(row, column);
      g2.setFont(labelFont);
      g2.setPaint(paint);
      String label = generator.generateLabel(dataset, row, column);
      ItemLabelPosition position = null;
      if (!negative) {
        position = getPositiveItemLabelPosition(row, column);
      } else {
        position = getNegativeItemLabelPosition(row, column);
      }
      Point2D anchorPoint =
          calculateLabelAnchorPoint(position.getItemLabelAnchor(), x, y, orientation);
      TextUtilities.drawRotatedString(
          label,
          g2,
          (float) anchorPoint.getX(),
          (float) anchorPoint.getY(),
          position.getTextAnchor(),
          position.getAngle(),
          position.getRotationAnchor());
    }
  }
예제 #4
0
  /**
   * Reserve some space on each axis side because we draw a centered label at each extremity.
   *
   * @param g2 the graphics device.
   * @param plot the plot.
   * @param plotArea the plot area.
   * @param edge the edge.
   * @param space the space already reserved.
   * @return The reserved space.
   */
  public AxisSpace reserveSpace(
      Graphics2D g2, Plot plot, Rectangle2D plotArea, RectangleEdge edge, AxisSpace space) {

    this.internalMarkerCycleBoundTick = null;
    AxisSpace ret = super.reserveSpace(g2, plot, plotArea, edge, space);
    if (this.internalMarkerCycleBoundTick == null) {
      return ret;
    }

    FontMetrics fm = g2.getFontMetrics(getTickLabelFont());
    Rectangle2D r =
        TextUtilities.getTextBounds(this.internalMarkerCycleBoundTick.getText(), g2, fm);

    if (RectangleEdge.isTopOrBottom(edge)) {
      if (isVerticalTickLabels()) {
        space.add(r.getHeight() / 2, RectangleEdge.RIGHT);
      } else {
        space.add(r.getWidth() / 2, RectangleEdge.RIGHT);
      }
    } else if (RectangleEdge.isLeftOrRight(edge)) {
      if (isVerticalTickLabels()) {
        space.add(r.getWidth() / 2, RectangleEdge.TOP);
      } else {
        space.add(r.getHeight() / 2, RectangleEdge.TOP);
      }
    }

    return ret;
  }
예제 #5
0
  /**
   * Returns the content size for the title. This will reflect the fact that a text title positioned
   * on the left or right of a chart will be rotated 90 degrees.
   *
   * @param g2 the graphics device.
   * @param widthRange the width range.
   * @param heightRange the height range.
   * @return The content size.
   */
  protected Size2D arrangeRR(Graphics2D g2, Range widthRange, Range heightRange) {
    RectangleEdge position = getPosition();
    if (position == RectangleEdge.TOP || position == RectangleEdge.BOTTOM) {
      float maxWidth = (float) widthRange.getUpperBound();
      g2.setFont(this.font);
      this.content =
          TextUtilities.createTextBlock(
              this.text,
              this.font,
              this.paint,
              maxWidth,
              this.maximumLinesToDisplay,
              new G2TextMeasurer(g2));
      this.content.setLineAlignment(this.textAlignment);
      Size2D contentSize = this.content.calculateDimensions(g2);
      if (this.expandToFitSpace) {
        return new Size2D(maxWidth, contentSize.getHeight());
      } else {
        return contentSize;
      }
    } else if (position == RectangleEdge.LEFT || position == RectangleEdge.RIGHT) {
      float maxWidth = (float) heightRange.getUpperBound();
      g2.setFont(this.font);
      this.content =
          TextUtilities.createTextBlock(
              this.text,
              this.font,
              this.paint,
              maxWidth,
              this.maximumLinesToDisplay,
              new G2TextMeasurer(g2));
      this.content.setLineAlignment(this.textAlignment);
      Size2D contentSize = this.content.calculateDimensions(g2);

      // transpose the dimensions, because the title is rotated
      if (this.expandToFitSpace) {
        return new Size2D(contentSize.getHeight(), maxWidth);
      } else {
        return new Size2D(contentSize.height, contentSize.width);
      }
    } else {
      throw new RuntimeException("Unrecognised exception.");
    }
  }
예제 #6
0
  public Point textExtent(String text) {

    String[] lines = text.split(Const.CR);
    int maxWidth = 0;
    for (String line : lines) {
      Rectangle2D bounds = TextUtilities.getTextBounds(line, gc, gc.getFontMetrics());
      if (bounds.getWidth() > maxWidth) maxWidth = (int) bounds.getWidth();
    }
    int height = gc.getFontMetrics().getHeight() * lines.length;

    return new Point((int) maxWidth, (int) height);
  }
예제 #7
0
  /**
   * A utility method that draws a string inside a rectangle.
   *
   * @param g2 the graphics device.
   * @param bounds the rectangle.
   * @param font the font.
   * @param text the text.
   */
  private void drawStringInRect(Graphics2D g2, Rectangle2D bounds, Font font, String text) {

    g2.setFont(font);
    FontMetrics fm = g2.getFontMetrics(font);
    Rectangle2D r = TextUtilities.getTextBounds(text, g2, fm);
    double x = bounds.getX();
    if (r.getWidth() < bounds.getWidth()) {
      x = x + (bounds.getWidth() - r.getWidth()) / 2;
    }
    LineMetrics metrics = font.getLineMetrics(text, g2.getFontRenderContext());
    g2.drawString(
        text, (float) x, (float) (bounds.getMaxY() - this.bottomInnerGap - metrics.getDescent()));
  }
예제 #8
0
파일: MeterPlot.java 프로젝트: nologic/nabs
 /**
  * Draws the value label just below the center of the dial.
  *
  * @param g2 the graphics device.
  * @param area the plot area.
  */
 protected void drawValueLabel(Graphics2D g2, Rectangle2D area) {
   g2.setFont(this.valueFont);
   g2.setPaint(this.valuePaint);
   String valueStr = "No value";
   if (this.dataset != null) {
     Number n = this.dataset.getValue();
     if (n != null) {
       valueStr = this.tickLabelFormat.format(n.doubleValue()) + " " + this.units;
     }
   }
   float x = (float) area.getCenterX();
   float y = (float) area.getCenterY() + DEFAULT_CIRCLE_SIZE;
   TextUtilities.drawAlignedString(valueStr, g2, x, y, TextAnchor.TOP_CENTER);
 }
예제 #9
0
파일: MeterPlot.java 프로젝트: nologic/nabs
  /**
   * Draws a tick on the dial.
   *
   * @param g2 the graphics device.
   * @param meterArea the meter area.
   * @param value the tick value.
   * @param label a flag that controls whether or not a value label is drawn.
   */
  protected void drawTick(Graphics2D g2, Rectangle2D meterArea, double value, boolean label) {

    double valueAngle = valueToAngle(value);

    double meterMiddleX = meterArea.getCenterX();
    double meterMiddleY = meterArea.getCenterY();

    g2.setPaint(this.tickPaint);
    g2.setStroke(new BasicStroke(2.0f));

    double valueP2X = 0;
    double valueP2Y = 0;

    double radius = (meterArea.getWidth() / 2) + DEFAULT_BORDER_SIZE;
    double radius1 = radius - 15;

    double valueP1X = meterMiddleX + (radius * Math.cos(Math.PI * (valueAngle / 180)));
    double valueP1Y = meterMiddleY - (radius * Math.sin(Math.PI * (valueAngle / 180)));

    valueP2X = meterMiddleX + (radius1 * Math.cos(Math.PI * (valueAngle / 180)));
    valueP2Y = meterMiddleY - (radius1 * Math.sin(Math.PI * (valueAngle / 180)));

    Line2D.Double line = new Line2D.Double(valueP1X, valueP1Y, valueP2X, valueP2Y);
    g2.draw(line);

    if (this.tickLabelsVisible && label) {

      String tickLabel = this.tickLabelFormat.format(value);
      g2.setFont(this.tickLabelFont);
      g2.setPaint(this.tickLabelPaint);

      FontMetrics fm = g2.getFontMetrics();
      Rectangle2D tickLabelBounds = TextUtilities.getTextBounds(tickLabel, g2, fm);

      double x = valueP2X;
      double y = valueP2Y;
      if (valueAngle == 90 || valueAngle == 270) {
        x = x - tickLabelBounds.getWidth() / 2;
      } else if (valueAngle < 90 || valueAngle > 270) {
        x = x - tickLabelBounds.getWidth();
      }
      if ((valueAngle > 135 && valueAngle < 225) || valueAngle > 315 || valueAngle < 45) {
        y = y - tickLabelBounds.getHeight() / 2;
      } else {
        y = y + tickLabelBounds.getHeight() / 2;
      }
      g2.drawString(tickLabel, (float) x, (float) y);
    }
  }
예제 #10
0
 /**
  * Returns the maximum of the relevant dimension (height or width) of the subcategory labels.
  *
  * @param g2 the graphics device.
  * @param edge the edge.
  * @return The maximum dimension.
  */
 private double getMaxDim(Graphics2D g2, RectangleEdge edge) {
   double result = 0.0;
   g2.setFont(this.subLabelFont);
   FontMetrics fm = g2.getFontMetrics();
   Iterator iterator = this.subCategories.iterator();
   while (iterator.hasNext()) {
     Comparable subcategory = (Comparable) iterator.next();
     String label = subcategory.toString();
     Rectangle2D bounds = TextUtilities.getTextBounds(label, g2, fm);
     double dim = 0.0;
     if (RectangleEdge.isLeftOrRight(edge)) {
       dim = bounds.getWidth();
     } else { // must be top or bottom
       dim = bounds.getHeight();
     }
     result = Math.max(result, dim);
   }
   return result;
 }
예제 #11
0
  /**
   * Returns a rectangle that encloses the axis label. This is typically used for layout purposes
   * (it gives the maximum dimensions of the label).
   *
   * @param g2 the graphics device.
   * @param edge the edge of the plot area along which the axis is measuring.
   * @return The enclosing rectangle.
   */
  protected Rectangle2D getLabelEnclosure(Graphics2D g2, RectangleEdge edge) {

    Rectangle2D result = new Rectangle2D.Double();
    String axisLabel = getLabel();
    if (axisLabel != null && !axisLabel.equals("")) {
      FontMetrics fm = g2.getFontMetrics(getLabelFont());
      Rectangle2D bounds = TextUtilities.getTextBounds(axisLabel, g2, fm);
      RectangleInsets insets = getLabelInsets();
      bounds = insets.createOutsetRectangle(bounds);
      double angle = getLabelAngle();
      if (edge == RectangleEdge.LEFT || edge == RectangleEdge.RIGHT) {
        angle = angle - Math.PI / 2.0;
      }
      double x = bounds.getCenterX();
      double y = bounds.getCenterY();
      AffineTransform transformer = AffineTransform.getRotateInstance(angle, x, y);
      Shape labelBounds = transformer.createTransformedShape(bounds);
      result = labelBounds.getBounds2D();
    }

    return result;
  }
  /**
   * Draws the annotation.
   *
   * @param g2 the graphics device.
   * @param plot the plot.
   * @param dataArea the data area.
   * @param domainAxis the domain axis.
   * @param rangeAxis the range axis.
   * @param rendererIndex the renderer index.
   * @param info the plot rendering info.
   */
  public void draw(
      Graphics2D g2,
      XYPlot plot,
      Rectangle2D dataArea,
      ValueAxis domainAxis,
      ValueAxis rangeAxis,
      int rendererIndex,
      PlotRenderingInfo info) {

    PlotOrientation orientation = plot.getOrientation();
    RectangleEdge domainEdge =
        Plot.resolveDomainAxisLocation(plot.getDomainAxisLocation(), orientation);
    RectangleEdge rangeEdge =
        Plot.resolveRangeAxisLocation(plot.getRangeAxisLocation(), orientation);
    double j2DX = domainAxis.valueToJava2D(getX(), dataArea, domainEdge);
    double j2DY = rangeAxis.valueToJava2D(getY(), dataArea, rangeEdge);
    if (orientation == PlotOrientation.HORIZONTAL) {
      double temp = j2DX;
      j2DX = j2DY;
      j2DY = temp;
    }
    double startX = j2DX + Math.cos(this.angle) * this.baseRadius;
    double startY = j2DY + Math.sin(this.angle) * this.baseRadius;

    double endX = j2DX + Math.cos(this.angle) * this.tipRadius;
    double endY = j2DY + Math.sin(this.angle) * this.tipRadius;

    double arrowBaseX = endX + Math.cos(this.angle) * this.arrowLength;
    double arrowBaseY = endY + Math.sin(this.angle) * this.arrowLength;

    double arrowLeftX = arrowBaseX + Math.cos(this.angle + Math.PI / 2.0) * this.arrowWidth;
    double arrowLeftY = arrowBaseY + Math.sin(this.angle + Math.PI / 2.0) * this.arrowWidth;

    double arrowRightX = arrowBaseX - Math.cos(this.angle + Math.PI / 2.0) * this.arrowWidth;
    double arrowRightY = arrowBaseY - Math.sin(this.angle + Math.PI / 2.0) * this.arrowWidth;

    GeneralPath arrow = new GeneralPath();
    arrow.moveTo((float) endX, (float) endY);
    arrow.lineTo((float) arrowLeftX, (float) arrowLeftY);
    arrow.lineTo((float) arrowRightX, (float) arrowRightY);
    arrow.closePath();

    g2.setStroke(this.arrowStroke);
    g2.setPaint(this.arrowPaint);
    Line2D line = new Line2D.Double(startX, startY, arrowBaseX, arrowBaseY);
    g2.draw(line);
    g2.fill(arrow);

    // draw the label
    double labelX = j2DX + Math.cos(this.angle) * (this.baseRadius + this.labelOffset);
    double labelY = j2DY + Math.sin(this.angle) * (this.baseRadius + this.labelOffset);
    g2.setFont(getFont());
    Shape hotspot =
        TextUtilities.calculateRotatedStringBounds(
            getText(),
            g2,
            (float) labelX,
            (float) labelY,
            getTextAnchor(),
            getRotationAngle(),
            getRotationAnchor());
    if (getBackgroundPaint() != null) {
      g2.setPaint(getBackgroundPaint());
      g2.fill(hotspot);
    }
    g2.setPaint(getPaint());
    TextUtilities.drawRotatedString(
        getText(),
        g2,
        (float) labelX,
        (float) labelY,
        getTextAnchor(),
        getRotationAngle(),
        getRotationAnchor());
    if (isOutlineVisible()) {
      g2.setStroke(getOutlineStroke());
      g2.setPaint(getOutlinePaint());
      g2.draw(hotspot);
    }

    String toolTip = getToolTipText();
    String url = getURL();
    if (toolTip != null || url != null) {
      addEntity(info, hotspot, rendererIndex, toolTip, url);
    }
  }
예제 #13
0
  /**
   * Draws an item label. This method is provided as an alternative to {@link
   * #drawItemLabel(Graphics2D, PlotOrientation, XYDataset, int, int, double, double, boolean)} so
   * that the bar can be used to calculate the label anchor point.
   *
   * @param g2 the graphics device.
   * @param dataset the dataset.
   * @param series the series index.
   * @param item the item index.
   * @param plot the plot.
   * @param generator the label generator ({@code null} permitted, in which case the method does
   *     nothing, just returns).
   * @param bar the bar.
   * @param negative a flag indicating a negative value.
   */
  protected void drawItemLabel(
      Graphics2D g2,
      XYDataset dataset,
      int series,
      int item,
      XYPlot plot,
      XYItemLabelGenerator generator,
      Rectangle2D bar,
      boolean negative) {

    if (generator == null) {
      return; // nothing to do
    }
    String label = generator.generateLabel(dataset, series, item);
    if (label == null) {
      return; // nothing to do
    }

    Font labelFont = getItemLabelFont(series, item);
    g2.setFont(labelFont);
    Paint paint = getItemLabelPaint(series, item);
    g2.setPaint(paint);

    // find out where to place the label...
    ItemLabelPosition position;
    if (!negative) {
      position = getPositiveItemLabelPosition(series, item);
    } else {
      position = getNegativeItemLabelPosition(series, item);
    }

    // work out the label anchor point...
    Point2D anchorPoint =
        calculateLabelAnchorPoint(position.getItemLabelAnchor(), bar, plot.getOrientation());

    if (isInternalAnchor(position.getItemLabelAnchor())) {
      Shape bounds =
          TextUtilities.calculateRotatedStringBounds(
              label,
              g2,
              (float) anchorPoint.getX(),
              (float) anchorPoint.getY(),
              position.getTextAnchor(),
              position.getAngle(),
              position.getRotationAnchor());

      if (bounds != null) {
        if (!bar.contains(bounds.getBounds2D())) {
          if (!negative) {
            position = getPositiveItemLabelPositionFallback();
          } else {
            position = getNegativeItemLabelPositionFallback();
          }
          if (position != null) {
            anchorPoint =
                calculateLabelAnchorPoint(
                    position.getItemLabelAnchor(), bar, plot.getOrientation());
          }
        }
      }
    }

    if (position != null) {
      TextUtilities.drawRotatedString(
          label,
          g2,
          (float) anchorPoint.getX(),
          (float) anchorPoint.getY(),
          position.getTextAnchor(),
          position.getAngle(),
          position.getRotationAnchor());
    }
  }
예제 #14
0
  /**
   * Draws the category labels and returns the updated axis state.
   *
   * @param g2 the graphics device (<code>null</code> not permitted).
   * @param plotArea the plot area (<code>null</code> not permitted).
   * @param dataArea the area inside the axes (<code>null</code> not permitted).
   * @param edge the axis location (<code>null</code> not permitted).
   * @param state the axis state (<code>null</code> not permitted).
   * @param plotState collects information about the plot (<code>null</code> permitted).
   * @return The updated axis state (never <code>null</code>).
   */
  protected AxisState drawSubCategoryLabels(
      Graphics2D g2,
      Rectangle2D plotArea,
      Rectangle2D dataArea,
      RectangleEdge edge,
      AxisState state,
      PlotRenderingInfo plotState) {

    if (state == null) {
      throw new IllegalArgumentException("Null 'state' argument.");
    }

    g2.setFont(this.subLabelFont);
    g2.setPaint(this.subLabelPaint);
    CategoryPlot plot = (CategoryPlot) getPlot();
    CategoryDataset dataset = plot.getDataset();
    int categoryCount = dataset.getColumnCount();

    double maxdim = getMaxDim(g2, edge);
    for (int categoryIndex = 0; categoryIndex < categoryCount; categoryIndex++) {

      double x0 = 0.0;
      double x1 = 0.0;
      double y0 = 0.0;
      double y1 = 0.0;
      if (edge == RectangleEdge.TOP) {
        x0 = getCategoryStart(categoryIndex, categoryCount, dataArea, edge);
        x1 = getCategoryEnd(categoryIndex, categoryCount, dataArea, edge);
        y1 = state.getCursor();
        y0 = y1 - maxdim;
      } else if (edge == RectangleEdge.BOTTOM) {
        x0 = getCategoryStart(categoryIndex, categoryCount, dataArea, edge);
        x1 = getCategoryEnd(categoryIndex, categoryCount, dataArea, edge);
        y0 = state.getCursor();
        y1 = y0 + maxdim;
      } else if (edge == RectangleEdge.LEFT) {
        y0 = getCategoryStart(categoryIndex, categoryCount, dataArea, edge);
        y1 = getCategoryEnd(categoryIndex, categoryCount, dataArea, edge);
        x1 = state.getCursor();
        x0 = x1 - maxdim;
      } else if (edge == RectangleEdge.RIGHT) {
        y0 = getCategoryStart(categoryIndex, categoryCount, dataArea, edge);
        y1 = getCategoryEnd(categoryIndex, categoryCount, dataArea, edge);
        x0 = state.getCursor();
        x1 = x0 + maxdim;
      }
      Rectangle2D area = new Rectangle2D.Double(x0, y0, (x1 - x0), (y1 - y0));
      int subCategoryCount = this.subCategories.size();
      float width = (float) ((x1 - x0) / subCategoryCount);
      float height = (float) ((y1 - y0) / subCategoryCount);
      float xx = 0.0f;
      float yy = 0.0f;
      for (int i = 0; i < subCategoryCount; i++) {
        if (RectangleEdge.isTopOrBottom(edge)) {
          xx = (float) (x0 + (i + 0.5) * width);
          yy = (float) area.getCenterY();
        } else {
          xx = (float) area.getCenterX();
          yy = (float) (y0 + (i + 0.5) * height);
        }
        String label = this.subCategories.get(i).toString();
        TextUtilities.drawRotatedString(
            label, g2, xx, yy, TextAnchor.CENTER, 0.0, TextAnchor.CENTER);
      }
    }

    if (edge.equals(RectangleEdge.TOP)) {
      double h = maxdim;
      state.cursorUp(h);
    } else if (edge.equals(RectangleEdge.BOTTOM)) {
      double h = maxdim;
      state.cursorDown(h);
    } else if (edge == RectangleEdge.LEFT) {
      double w = maxdim;
      state.cursorLeft(w);
    } else if (edge == RectangleEdge.RIGHT) {
      double w = maxdim;
      state.cursorRight(w);
    }
    return state;
  }
  /**
   * Draws a marker for the range axis.
   *
   * @param g2 the graphics device (not <code>null</code>).
   * @param plot the plot (not <code>null</code>).
   * @param axis the range axis (not <code>null</code>).
   * @param marker the marker to be drawn (not <code>null</code>).
   * @param dataArea the area inside the axes (not <code>null</code>).
   */
  public void drawRangeMarker(
      Graphics2D g2, CategoryPlot plot, ValueAxis axis, Marker marker, Rectangle2D dataArea) {

    if (marker instanceof ValueMarker) {
      ValueMarker vm = (ValueMarker) marker;
      double value = vm.getValue();
      Range range = axis.getRange();

      if (!range.contains(value)) {
        return;
      }

      PlotOrientation orientation = plot.getOrientation();
      double v = axis.valueToJava2D(value, dataArea, plot.getRangeAxisEdge());
      Line2D line = null;
      if (orientation == PlotOrientation.HORIZONTAL) {
        line = new Line2D.Double(v, dataArea.getMinY(), v, dataArea.getMaxY());
      } else if (orientation == PlotOrientation.VERTICAL) {
        line = new Line2D.Double(dataArea.getMinX(), v, dataArea.getMaxX(), v);
      }

      g2.setPaint(marker.getPaint());
      g2.setStroke(marker.getStroke());
      g2.draw(line);

      String label = marker.getLabel();
      RectangleAnchor anchor = marker.getLabelAnchor();
      if (label != null) {
        Font labelFont = marker.getLabelFont();
        g2.setFont(labelFont);
        g2.setPaint(marker.getLabelPaint());
        Point2D coordinates =
            calculateRangeMarkerTextAnchorPoint(
                g2,
                orientation,
                dataArea,
                line.getBounds2D(),
                marker.getLabelOffset(),
                LengthAdjustmentType.EXPAND,
                anchor);
        TextUtilities.drawAlignedString(
            label,
            g2,
            (float) coordinates.getX(),
            (float) coordinates.getY(),
            marker.getLabelTextAnchor());
      }
    } else if (marker instanceof IntervalMarker) {

      IntervalMarker im = (IntervalMarker) marker;
      double start = im.getStartValue();
      double end = im.getEndValue();
      Range range = axis.getRange();
      if (!(range.intersects(start, end))) {
        return;
      }

      // don't draw beyond the axis range...
      start = range.constrain(start);
      end = range.constrain(end);

      double v0 = axis.valueToJava2D(start, dataArea, plot.getRangeAxisEdge());
      double v1 = axis.valueToJava2D(end, dataArea, plot.getRangeAxisEdge());

      PlotOrientation orientation = plot.getOrientation();
      Rectangle2D rect = null;
      if (orientation == PlotOrientation.HORIZONTAL) {
        rect = new Rectangle2D.Double(v0, dataArea.getMinY(), v1 - v0, dataArea.getHeight());
      } else if (orientation == PlotOrientation.VERTICAL) {
        rect =
            new Rectangle2D.Double(
                dataArea.getMinX(), Math.min(v0, v1),
                dataArea.getWidth(), Math.abs(v1 - v0));
      }
      Paint p = marker.getPaint();
      if (p instanceof GradientPaint) {
        GradientPaint gp = (GradientPaint) p;
        GradientPaintTransformer t = im.getGradientPaintTransformer();
        if (t != null) {
          gp = t.transform(gp, rect);
        }
        g2.setPaint(gp);
      } else {
        g2.setPaint(p);
      }
      g2.fill(rect);

      String label = marker.getLabel();
      RectangleAnchor anchor = marker.getLabelAnchor();
      if (label != null) {
        Font labelFont = marker.getLabelFont();
        g2.setFont(labelFont);
        g2.setPaint(marker.getLabelPaint());
        Point2D coordinates =
            calculateRangeMarkerTextAnchorPoint(
                g2,
                orientation,
                dataArea,
                rect,
                marker.getLabelOffset(),
                marker.getLabelOffsetType(),
                anchor);
        TextUtilities.drawAlignedString(
            label,
            g2,
            (float) coordinates.getX(),
            (float) coordinates.getY(),
            marker.getLabelTextAnchor());
      }
    }
  }
예제 #16
0
  /**
   * Draws the scale on the dial plot.
   *
   * @param g2 the graphics target (<code>null</code> not permitted).
   * @param plot the dial plot (<code>null</code> not permitted).
   * @param frame the reference frame that is used to construct the geometry of the plot (<code>null
   *     </code> not permitted).
   * @param view the visible part of the plot (<code>null</code> not permitted).
   */
  public void draw(Graphics2D g2, DialPlot plot, Rectangle2D frame, Rectangle2D view) {

    Rectangle2D arcRect =
        DialPlot.rectangleByRadius(frame, this.getTickRadius(), this.getTickRadius());
    Rectangle2D arcRectMajor =
        DialPlot.rectangleByRadius(
            frame,
            this.getTickRadius() - this.getMajorTickLength(),
            this.getTickRadius() - this.getMajorTickLength());
    Rectangle2D arcRectMinor = arcRect;
    if (this.getMinorTickCount() > 0 && this.getMinorTickLength() > 0.0) {
      arcRectMinor =
          DialPlot.rectangleByRadius(
              frame,
              this.getTickRadius() - this.getMinorTickLength(),
              this.getTickRadius() - this.getMinorTickLength());
    }
    Rectangle2D arcRectForLabels =
        DialPlot.rectangleByRadius(
            frame,
            this.getTickRadius() - this.getTickLabelOffset(),
            this.getTickRadius() - this.getTickLabelOffset());

    boolean firstLabel = true;

    Arc2D arc = new Arc2D.Double();
    Line2D workingLine = new Line2D.Double();
    Stroke arcStroke = new BasicStroke(0.75f);

    for (double v = this.getLowerBound();
        v <= this.getUpperBound();
        v += this.getMajorTickIncrement()) {
      arc.setArc(arcRect, this.getStartAngle(), valueToAngle(v) - this.getStartAngle(), Arc2D.OPEN);
      g2.setPaint(this.getMajorTickPaint());
      g2.setStroke(arcStroke);
      g2.draw(arc);

      Point2D pt0 = arc.getEndPoint();
      arc.setArc(
          arcRectMajor, this.getStartAngle(), valueToAngle(v) - this.getStartAngle(), Arc2D.OPEN);
      Point2D pt1 = arc.getEndPoint();
      g2.setPaint(this.getMajorTickPaint());
      g2.setStroke(this.getMajorTickStroke());
      workingLine.setLine(pt0, pt1);
      g2.draw(workingLine);
      arc.setArc(
          arcRectForLabels,
          this.getStartAngle(),
          valueToAngle(v) - this.getStartAngle(),
          Arc2D.OPEN);
      Point2D pt2 = arc.getEndPoint();

      if (this.getTickLabelsVisible()) {
        if (!firstLabel || this.getFirstTickLabelVisible()) {
          g2.setFont(this.getTickLabelFont());
          TextUtilities.drawAlignedString(
              this.getTickLabelFormatter().format(v),
              g2,
              (float) pt2.getX(),
              (float) pt2.getY(),
              TextAnchor.CENTER);
        }
      }
      firstLabel = false;

      // now do the minor tick marks
      if (this.getMinorTickCount() > 0 && this.getMinorTickLength() > 0.0) {
        double minorTickIncrement = this.getMajorTickIncrement() / (this.getMinorTickCount() + 1);
        for (int i = 0; i < this.getMinorTickCount(); i++) {
          double vv = v + ((i + 1) * minorTickIncrement);
          if (vv >= this.getUpperBound()) {
            break;
          }
          double angle = valueToAngle(vv);

          arc.setArc(arcRect, this.getStartAngle(), angle - this.getStartAngle(), Arc2D.OPEN);
          pt0 = arc.getEndPoint();
          arc.setArc(arcRectMinor, this.getStartAngle(), angle - this.getStartAngle(), Arc2D.OPEN);
          Point2D pt3 = arc.getEndPoint();
          g2.setStroke(this.getMinorTickStroke());
          g2.setPaint(this.getMinorTickPaint());
          workingLine.setLine(pt0, pt3);
          g2.draw(workingLine);
        }
      }
    }
  }
  /**
   * Draws a range marker.
   *
   * @param g2 the graphics device.
   * @param plot the plot.
   * @param axis the value axis.
   * @param marker the marker.
   * @param dataArea the area for plotting data (not including 3D effect).
   */
  public void drawRangeMarker(
      Graphics2D g2, CategoryPlot plot, ValueAxis axis, Marker marker, Rectangle2D dataArea) {

    Rectangle2D adjusted =
        new Rectangle2D.Double(
            dataArea.getX(),
            dataArea.getY() + getYOffset(),
            dataArea.getWidth() - getXOffset(),
            dataArea.getHeight() - getYOffset());
    if (marker instanceof ValueMarker) {
      ValueMarker vm = (ValueMarker) marker;
      double value = vm.getValue();
      Range range = axis.getRange();
      if (!range.contains(value)) {
        return;
      }

      GeneralPath path = null;
      PlotOrientation orientation = plot.getOrientation();
      if (orientation == PlotOrientation.HORIZONTAL) {
        float x = (float) axis.valueToJava2D(value, adjusted, plot.getRangeAxisEdge());
        float y = (float) adjusted.getMaxY();
        path = new GeneralPath();
        path.moveTo(x, y);
        path.lineTo((float) (x + getXOffset()), y - (float) getYOffset());
        path.lineTo((float) (x + getXOffset()), (float) (adjusted.getMinY() - getYOffset()));
        path.lineTo(x, (float) adjusted.getMinY());
        path.closePath();
      } else if (orientation == PlotOrientation.VERTICAL) {
        float y = (float) axis.valueToJava2D(value, adjusted, plot.getRangeAxisEdge());
        float x = (float) dataArea.getX();
        path = new GeneralPath();
        path.moveTo(x, y);
        path.lineTo(x + (float) this.xOffset, y - (float) this.yOffset);
        path.lineTo((float) (adjusted.getMaxX() + this.xOffset), y - (float) this.yOffset);
        path.lineTo((float) (adjusted.getMaxX()), y);
        path.closePath();
      }
      g2.setPaint(marker.getPaint());
      g2.fill(path);
      g2.setPaint(marker.getOutlinePaint());
      g2.draw(path);

      String label = marker.getLabel();
      RectangleAnchor anchor = marker.getLabelAnchor();
      if (label != null) {
        Font labelFont = marker.getLabelFont();
        g2.setFont(labelFont);
        g2.setPaint(marker.getLabelPaint());
        Point2D coordinates =
            calculateRangeMarkerTextAnchorPoint(
                g2,
                orientation,
                dataArea,
                path.getBounds2D(),
                marker.getLabelOffset(),
                LengthAdjustmentType.EXPAND,
                anchor);
        TextUtilities.drawAlignedString(
            label,
            g2,
            (float) coordinates.getX(),
            (float) coordinates.getY(),
            marker.getLabelTextAnchor());
      }

    } else {
      super.drawRangeMarker(g2, plot, axis, marker, adjusted);
      // TODO: draw the interval marker with a 3D effect
    }
  }
예제 #18
0
  /**
   * Draws the tick labels for one "band" of time periods.
   *
   * @param band the band index (zero-based).
   * @param g2 the graphics device.
   * @param state the axis state.
   * @param dataArea the data area.
   * @param edge the edge where the axis is located.
   * @return The updated axis state.
   */
  protected AxisState drawTickLabels(
      int band, Graphics2D g2, AxisState state, Rectangle2D dataArea, RectangleEdge edge) {

    // work out the initial gap
    double delta1 = 0.0;
    FontMetrics fm = g2.getFontMetrics(this.labelInfo[band].getLabelFont());
    if (edge == RectangleEdge.BOTTOM) {
      delta1 = this.labelInfo[band].getPadding().calculateTopOutset(fm.getHeight());
    } else if (edge == RectangleEdge.TOP) {
      delta1 = this.labelInfo[band].getPadding().calculateBottomOutset(fm.getHeight());
    }
    state.moveCursor(delta1, edge);
    long axisMin = this.first.getFirstMillisecond();
    long axisMax = this.last.getLastMillisecond();
    g2.setFont(this.labelInfo[band].getLabelFont());
    g2.setPaint(this.labelInfo[band].getLabelPaint());

    // work out the number of periods to skip for labelling
    RegularTimePeriod p1 =
        this.labelInfo[band].createInstance(new Date(axisMin), this.timeZone, this.locale);
    RegularTimePeriod p2 =
        this.labelInfo[band].createInstance(new Date(axisMax), this.timeZone, this.locale);
    String label1 =
        this.labelInfo[band].getDateFormat().format(new Date(p1.getMiddleMillisecond()));
    String label2 =
        this.labelInfo[band].getDateFormat().format(new Date(p2.getMiddleMillisecond()));
    Rectangle2D b1 = TextUtilities.getTextBounds(label1, g2, g2.getFontMetrics());
    Rectangle2D b2 = TextUtilities.getTextBounds(label2, g2, g2.getFontMetrics());
    double w = Math.max(b1.getWidth(), b2.getWidth());
    long ww = Math.round(java2DToValue(dataArea.getX() + w + 5.0, dataArea, edge));
    if (isInverted()) {
      ww = axisMax - ww;
    } else {
      ww = ww - axisMin;
    }
    long length = p1.getLastMillisecond() - p1.getFirstMillisecond();
    int periods = (int) (ww / length) + 1;

    RegularTimePeriod p =
        this.labelInfo[band].createInstance(new Date(axisMin), this.timeZone, this.locale);
    Rectangle2D b = null;
    long lastXX = 0L;
    float y = (float) (state.getCursor());
    TextAnchor anchor = TextAnchor.TOP_CENTER;
    float yDelta = (float) b1.getHeight();
    if (edge == RectangleEdge.TOP) {
      anchor = TextAnchor.BOTTOM_CENTER;
      yDelta = -yDelta;
    }
    while (p.getFirstMillisecond() <= axisMax) {
      float x = (float) valueToJava2D(p.getMiddleMillisecond(), dataArea, edge);
      DateFormat df = this.labelInfo[band].getDateFormat();
      String label = df.format(new Date(p.getMiddleMillisecond()));
      long first = p.getFirstMillisecond();
      long last = p.getLastMillisecond();
      if (last > axisMax) {
        // this is the last period, but it is only partially visible
        // so check that the label will fit before displaying it...
        Rectangle2D bb = TextUtilities.getTextBounds(label, g2, g2.getFontMetrics());
        if ((x + bb.getWidth() / 2) > dataArea.getMaxX()) {
          float xstart = (float) valueToJava2D(Math.max(first, axisMin), dataArea, edge);
          if (bb.getWidth() < (dataArea.getMaxX() - xstart)) {
            x = ((float) dataArea.getMaxX() + xstart) / 2.0f;
          } else {
            label = null;
          }
        }
      }
      if (first < axisMin) {
        // this is the first period, but it is only partially visible
        // so check that the label will fit before displaying it...
        Rectangle2D bb = TextUtilities.getTextBounds(label, g2, g2.getFontMetrics());
        if ((x - bb.getWidth() / 2) < dataArea.getX()) {
          float xlast = (float) valueToJava2D(Math.min(last, axisMax), dataArea, edge);
          if (bb.getWidth() < (xlast - dataArea.getX())) {
            x = (xlast + (float) dataArea.getX()) / 2.0f;
          } else {
            label = null;
          }
        }
      }
      if (label != null) {
        g2.setPaint(this.labelInfo[band].getLabelPaint());
        b = TextUtilities.drawAlignedString(label, g2, x, y, anchor);
      }
      if (lastXX > 0L) {
        if (this.labelInfo[band].getDrawDividers()) {
          long nextXX = p.getFirstMillisecond();
          long mid = (lastXX + nextXX) / 2;
          float mid2d = (float) valueToJava2D(mid, dataArea, edge);
          g2.setStroke(this.labelInfo[band].getDividerStroke());
          g2.setPaint(this.labelInfo[band].getDividerPaint());
          g2.draw(new Line2D.Float(mid2d, y, mid2d, y + yDelta));
        }
      }
      lastXX = last;
      for (int i = 0; i < periods; i++) {
        p = p.next();
      }
      p.peg(this.calendar);
    }
    double used = 0.0;
    if (b != null) {
      used = b.getHeight();
      // work out the trailing gap
      if (edge == RectangleEdge.BOTTOM) {
        used += this.labelInfo[band].getPadding().calculateBottomOutset(fm.getHeight());
      } else if (edge == RectangleEdge.TOP) {
        used += this.labelInfo[band].getPadding().calculateTopOutset(fm.getHeight());
      }
    }
    state.moveCursor(used, edge);
    return state;
  }
예제 #19
0
  /**
   * Draws the axis label.
   *
   * @param label the label text.
   * @param g2 the graphics device.
   * @param plotArea the plot area.
   * @param dataArea the area inside the axes.
   * @param edge the location of the axis.
   * @param state the axis state (<code>null</code> not permitted).
   * @return Information about the axis.
   */
  protected AxisState drawLabel(
      String label,
      Graphics2D g2,
      Rectangle2D plotArea,
      Rectangle2D dataArea,
      RectangleEdge edge,
      AxisState state) {

    // it is unlikely that 'state' will be null, but check anyway...
    ParamChecks.nullNotPermitted(state, "state");

    if ((label == null) || (label.equals(""))) {
      return state;
    }

    Font font = getLabelFont();
    RectangleInsets insets = getLabelInsets();
    g2.setFont(font);
    g2.setPaint(getLabelPaint());
    FontMetrics fm = g2.getFontMetrics();
    Rectangle2D labelBounds = TextUtilities.getTextBounds(label, g2, fm);

    if (edge == RectangleEdge.TOP) {
      AffineTransform t =
          AffineTransform.getRotateInstance(
              getLabelAngle(), labelBounds.getCenterX(), labelBounds.getCenterY());
      Shape rotatedLabelBounds = t.createTransformedShape(labelBounds);
      labelBounds = rotatedLabelBounds.getBounds2D();
      double labelx = dataArea.getCenterX();
      double labely = state.getCursor() - insets.getBottom() - labelBounds.getHeight() / 2.0;
      TextUtilities.drawRotatedString(
          label,
          g2,
          (float) labelx,
          (float) labely,
          TextAnchor.CENTER,
          getLabelAngle(),
          TextAnchor.CENTER);
      state.cursorUp(insets.getTop() + labelBounds.getHeight() + insets.getBottom());
    } else if (edge == RectangleEdge.BOTTOM) {
      AffineTransform t =
          AffineTransform.getRotateInstance(
              getLabelAngle(), labelBounds.getCenterX(), labelBounds.getCenterY());
      Shape rotatedLabelBounds = t.createTransformedShape(labelBounds);
      labelBounds = rotatedLabelBounds.getBounds2D();
      double labelx = dataArea.getCenterX();
      double labely = state.getCursor() + insets.getTop() + labelBounds.getHeight() / 2.0;
      TextUtilities.drawRotatedString(
          label,
          g2,
          (float) labelx,
          (float) labely,
          TextAnchor.CENTER,
          getLabelAngle(),
          TextAnchor.CENTER);
      state.cursorDown(insets.getTop() + labelBounds.getHeight() + insets.getBottom());
    } else if (edge == RectangleEdge.LEFT) {
      AffineTransform t =
          AffineTransform.getRotateInstance(
              getLabelAngle() - Math.PI / 2.0, labelBounds.getCenterX(), labelBounds.getCenterY());
      Shape rotatedLabelBounds = t.createTransformedShape(labelBounds);
      labelBounds = rotatedLabelBounds.getBounds2D();
      double labelx = state.getCursor() - insets.getRight() - labelBounds.getWidth() / 2.0;
      double labely = dataArea.getCenterY();
      TextUtilities.drawRotatedString(
          label,
          g2,
          (float) labelx,
          (float) labely,
          TextAnchor.CENTER,
          getLabelAngle() - Math.PI / 2.0,
          TextAnchor.CENTER);
      state.cursorLeft(insets.getLeft() + labelBounds.getWidth() + insets.getRight());
    } else if (edge == RectangleEdge.RIGHT) {

      AffineTransform t =
          AffineTransform.getRotateInstance(
              getLabelAngle() + Math.PI / 2.0, labelBounds.getCenterX(), labelBounds.getCenterY());
      Shape rotatedLabelBounds = t.createTransformedShape(labelBounds);
      labelBounds = rotatedLabelBounds.getBounds2D();
      double labelx = state.getCursor() + insets.getLeft() + labelBounds.getWidth() / 2.0;
      double labely = dataArea.getY() + dataArea.getHeight() / 2.0;
      TextUtilities.drawRotatedString(
          label,
          g2,
          (float) labelx,
          (float) labely,
          TextAnchor.CENTER,
          getLabelAngle() + Math.PI / 2.0,
          TextAnchor.CENTER);
      state.cursorRight(insets.getLeft() + labelBounds.getWidth() + insets.getRight());
    }

    return state;
  }
  /**
   * Draws a marker for the domain axis.
   *
   * @param g2 the graphics device (not <code>null</code>).
   * @param plot the plot (not <code>null</code>).
   * @param axis the range axis (not <code>null</code>).
   * @param marker the marker to be drawn (not <code>null</code>).
   * @param dataArea the area inside the axes (not <code>null</code>).
   */
  public void drawDomainMarker(
      Graphics2D g2,
      CategoryPlot plot,
      CategoryAxis axis,
      CategoryMarker marker,
      Rectangle2D dataArea) {

    Comparable category = marker.getKey();
    CategoryDataset dataset = plot.getDataset(plot.getIndexOf(this));
    int columnIndex = dataset.getColumnIndex(category);
    if (columnIndex < 0) {
      return;
    }
    PlotOrientation orientation = plot.getOrientation();
    Rectangle2D bounds = null;
    if (marker.getDrawAsLine()) {
      double v =
          axis.getCategoryMiddle(
              columnIndex, dataset.getColumnCount(),
              dataArea, plot.getDomainAxisEdge());
      Line2D line = null;
      if (orientation == PlotOrientation.HORIZONTAL) {
        line = new Line2D.Double(dataArea.getMinX(), v, dataArea.getMaxX(), v);
      } else if (orientation == PlotOrientation.VERTICAL) {
        line = new Line2D.Double(v, dataArea.getMinY(), v, dataArea.getMaxY());
      }

      g2.setPaint(marker.getPaint());
      g2.setStroke(marker.getStroke());
      g2.draw(line);
      bounds = line.getBounds2D();
    } else {
      double v0 =
          axis.getCategoryStart(
              columnIndex, dataset.getColumnCount(),
              dataArea, plot.getDomainAxisEdge());
      double v1 =
          axis.getCategoryEnd(
              columnIndex, dataset.getColumnCount(),
              dataArea, plot.getDomainAxisEdge());
      Rectangle2D area = null;
      if (orientation == PlotOrientation.HORIZONTAL) {
        area = new Rectangle2D.Double(dataArea.getMinX(), v0, dataArea.getWidth(), (v1 - v0));
      } else if (orientation == PlotOrientation.VERTICAL) {
        area = new Rectangle2D.Double(v0, dataArea.getMinY(), (v1 - v0), dataArea.getHeight());
      }
      g2.setPaint(marker.getPaint());
      g2.fill(area);
      bounds = area;
    }

    String label = marker.getLabel();
    RectangleAnchor anchor = marker.getLabelAnchor();
    if (label != null) {
      Font labelFont = marker.getLabelFont();
      g2.setFont(labelFont);
      g2.setPaint(marker.getLabelPaint());
      Point2D coordinates =
          calculateDomainMarkerTextAnchorPoint(
              g2,
              orientation,
              dataArea,
              bounds,
              marker.getLabelOffset(),
              marker.getLabelOffsetType(),
              anchor);
      TextUtilities.drawAlignedString(
          label,
          g2,
          (float) coordinates.getX(),
          (float) coordinates.getY(),
          marker.getLabelTextAnchor());
    }
  }