/** * Created an entity for the axis. * * @param cursor the initial cursor value. * @param state the axis state after completion of the drawing with a possibly updated cursor * position. * @param dataArea the data area. * @param edge the edge. * @param plotState the PlotRenderingInfo from which a reference to the entity collection can be * obtained. * @since 1.0.13 */ protected void createAndAddEntity( double cursor, AxisState state, Rectangle2D dataArea, RectangleEdge edge, PlotRenderingInfo plotState) { if (plotState == null || plotState.getOwner() == null) { return; // no need to create entity if we can´t save it anyways... } Rectangle2D hotspot = null; if (edge.equals(RectangleEdge.TOP)) { hotspot = new Rectangle2D.Double( dataArea.getX(), state.getCursor(), dataArea.getWidth(), cursor - state.getCursor()); } else if (edge.equals(RectangleEdge.BOTTOM)) { hotspot = new Rectangle2D.Double( dataArea.getX(), cursor, dataArea.getWidth(), state.getCursor() - cursor); } else if (edge.equals(RectangleEdge.LEFT)) { hotspot = new Rectangle2D.Double( state.getCursor(), dataArea.getY(), cursor - state.getCursor(), dataArea.getHeight()); } else if (edge.equals(RectangleEdge.RIGHT)) { hotspot = new Rectangle2D.Double( cursor, dataArea.getY(), state.getCursor() - cursor, dataArea.getHeight()); } EntityCollection e = plotState.getOwner().getEntityCollection(); if (e != null) { e.add(new AxisEntity(hotspot, this)); } }
/** * 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 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 the plot on a Java 2D graphics device (such as the screen or a printer). * * @param g2 the graphics device. * @param cursor the cursor. * @param plotArea the area within which the chart should be drawn. * @param dataArea the area within which the plot should be drawn (a subset of the drawArea). * @param reservedArea the reserved area. * @param edge the color bar location. * @return The new cursor location. */ public double draw( Graphics2D g2, double cursor, Rectangle2D plotArea, Rectangle2D dataArea, Rectangle2D reservedArea, RectangleEdge edge) { Rectangle2D colorBarArea = null; double thickness = calculateBarThickness(dataArea, edge); if (this.colorBarThickness > 0) { thickness = this.colorBarThickness; // allow fixed thickness } double length = 0.0; if (RectangleEdge.isLeftOrRight(edge)) { length = dataArea.getHeight(); } else { length = dataArea.getWidth(); } if (this.colorBarLength > 0) { length = this.colorBarLength; } if (edge == RectangleEdge.BOTTOM) { colorBarArea = new Rectangle2D.Double( dataArea.getX(), plotArea.getMaxY() + this.outerGap, length, thickness); } else if (edge == RectangleEdge.TOP) { colorBarArea = new Rectangle2D.Double( dataArea.getX(), reservedArea.getMinY() + this.outerGap, length, thickness); } else if (edge == RectangleEdge.LEFT) { colorBarArea = new Rectangle2D.Double( plotArea.getX() - thickness - this.outerGap, dataArea.getMinY(), thickness, length); } else if (edge == RectangleEdge.RIGHT) { colorBarArea = new Rectangle2D.Double( plotArea.getMaxX() + this.outerGap, dataArea.getMinY(), thickness, length); } // update, but dont draw tick marks (needed for stepped colors) this.axis.refreshTicks(g2, new AxisState(), colorBarArea, edge); drawColorBar(g2, colorBarArea, edge); AxisState state = null; if (edge == RectangleEdge.TOP) { cursor = colorBarArea.getMinY(); state = this.axis.draw(g2, cursor, reservedArea, colorBarArea, RectangleEdge.TOP, null); } else if (edge == RectangleEdge.BOTTOM) { cursor = colorBarArea.getMaxY(); state = this.axis.draw(g2, cursor, reservedArea, colorBarArea, RectangleEdge.BOTTOM, null); } else if (edge == RectangleEdge.LEFT) { cursor = colorBarArea.getMinX(); state = this.axis.draw(g2, cursor, reservedArea, colorBarArea, RectangleEdge.LEFT, null); } else if (edge == RectangleEdge.RIGHT) { cursor = colorBarArea.getMaxX(); state = this.axis.draw(g2, cursor, reservedArea, colorBarArea, RectangleEdge.RIGHT, null); } return state.getCursor(); }
/** * 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; }
/** * Draws the major and minor tick marks for an axis that lies at the top or bottom of the plot. * * @param g2 the graphics device. * @param state the axis state. * @param dataArea the data area. * @param edge the edge. */ protected void drawTickMarksHorizontal( Graphics2D g2, AxisState state, Rectangle2D dataArea, RectangleEdge edge) { List<ValueTick> ticks = new ArrayList<ValueTick>(); double x0; double y0 = state.getCursor(); double insideLength = getTickMarkInsideLength(); double outsideLength = getTickMarkOutsideLength(); RegularTimePeriod t = createInstance( this.majorTickTimePeriodClass, this.first.getStart(), getTimeZone(), this.locale); long t0 = t.getFirstMillisecond(); Line2D inside = null; Line2D outside = null; long firstOnAxis = getFirst().getFirstMillisecond(); long lastOnAxis = getLast().getLastMillisecond() + 1; while (t0 <= lastOnAxis) { ticks.add(new NumberTick((double) t0, "", TextAnchor.CENTER, TextAnchor.CENTER, 0.0)); x0 = valueToJava2D(t0, dataArea, edge); if (edge == RectangleEdge.TOP) { inside = new Line2D.Double(x0, y0, x0, y0 + insideLength); outside = new Line2D.Double(x0, y0, x0, y0 - outsideLength); } else if (edge == RectangleEdge.BOTTOM) { inside = new Line2D.Double(x0, y0, x0, y0 - insideLength); outside = new Line2D.Double(x0, y0, x0, y0 + outsideLength); } if (t0 >= firstOnAxis) { g2.setPaint(getTickMarkPaint()); g2.setStroke(getTickMarkStroke()); g2.draw(inside); g2.draw(outside); } // draw minor tick marks if (this.minorTickMarksVisible) { RegularTimePeriod tminor = createInstance(this.minorTickTimePeriodClass, new Date(t0), getTimeZone(), this.locale); long tt0 = tminor.getFirstMillisecond(); while (tt0 < t.getLastMillisecond() && tt0 < lastOnAxis) { double xx0 = valueToJava2D(tt0, dataArea, edge); if (edge == RectangleEdge.TOP) { inside = new Line2D.Double(xx0, y0, xx0, y0 + this.minorTickMarkInsideLength); outside = new Line2D.Double(xx0, y0, xx0, y0 - this.minorTickMarkOutsideLength); } else if (edge == RectangleEdge.BOTTOM) { inside = new Line2D.Double(xx0, y0, xx0, y0 - this.minorTickMarkInsideLength); outside = new Line2D.Double(xx0, y0, xx0, y0 + this.minorTickMarkOutsideLength); } if (tt0 >= firstOnAxis) { g2.setPaint(this.minorTickMarkPaint); g2.setStroke(this.minorTickMarkStroke); g2.draw(inside); g2.draw(outside); } tminor = tminor.next(); tminor.peg(this.calendar); tt0 = tminor.getFirstMillisecond(); } } t = t.next(); t.peg(this.calendar); t0 = t.getFirstMillisecond(); } if (edge == RectangleEdge.TOP) { state.cursorUp(Math.max(outsideLength, this.minorTickMarkOutsideLength)); } else if (edge == RectangleEdge.BOTTOM) { state.cursorDown(Math.max(outsideLength, this.minorTickMarkOutsideLength)); } state.setTicks(ticks); }
/** * 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 drawCategoryLabels( Graphics2D g2, Rectangle2D plotArea, Rectangle2D dataArea, RectangleEdge edge, AxisState state, PlotRenderingInfo plotState) { ParamChecks.nullNotPermitted(state, "state"); if (!isTickLabelsVisible()) { return state; } List<CategoryTick> ticks = refreshTicks(g2, state, plotArea, edge); // state.setTicks(ticks); //FIXME MMC had to remove this as the types don't match int categoryIndex = 0; for (CategoryTick tick : ticks) { g2.setFont(getTickLabelFont(tick.getCategory())); g2.setPaint(getTickLabelPaint(tick.getCategory())); CategoryLabelPosition position = this.categoryLabelPositions.getLabelPosition(edge); double x0 = 0.0; double x1 = 0.0; double y0 = 0.0; double y1 = 0.0; if (edge == RectangleEdge.TOP) { x0 = getCategoryStart(categoryIndex, ticks.size(), dataArea, edge); x1 = getCategoryEnd(categoryIndex, ticks.size(), dataArea, edge); y1 = state.getCursor() - this.categoryLabelPositionOffset; y0 = y1 - state.getMax(); } else if (edge == RectangleEdge.BOTTOM) { x0 = getCategoryStart(categoryIndex, ticks.size(), dataArea, edge); x1 = getCategoryEnd(categoryIndex, ticks.size(), dataArea, edge); y0 = state.getCursor() + this.categoryLabelPositionOffset; y1 = y0 + state.getMax(); } else if (edge == RectangleEdge.LEFT) { y0 = getCategoryStart(categoryIndex, ticks.size(), dataArea, edge); y1 = getCategoryEnd(categoryIndex, ticks.size(), dataArea, edge); x1 = state.getCursor() - this.categoryLabelPositionOffset; x0 = x1 - state.getMax(); } else if (edge == RectangleEdge.RIGHT) { y0 = getCategoryStart(categoryIndex, ticks.size(), dataArea, edge); y1 = getCategoryEnd(categoryIndex, ticks.size(), dataArea, edge); x0 = state.getCursor() + this.categoryLabelPositionOffset; x1 = x0 - state.getMax(); } Rectangle2D area = new Rectangle2D.Double(x0, y0, (x1 - x0), (y1 - y0)); Point2D anchorPoint = RectangleAnchor.coordinates(area, position.getCategoryAnchor()); TextBlock block = tick.getLabel(); block.draw( g2, (float) anchorPoint.getX(), (float) anchorPoint.getY(), position.getLabelAnchor(), (float) anchorPoint.getX(), (float) anchorPoint.getY(), position.getAngle()); Shape bounds = block.calculateBounds( g2, (float) anchorPoint.getX(), (float) anchorPoint.getY(), position.getLabelAnchor(), (float) anchorPoint.getX(), (float) anchorPoint.getY(), position.getAngle()); if (plotState != null && plotState.getOwner() != null) { EntityCollection entities = plotState.getOwner().getEntityCollection(); if (entities != null) { String tooltip = getCategoryLabelToolTip(tick.getCategory()); String url = getCategoryLabelURL(tick.getCategory()); entities.add(new CategoryLabelEntity(tick.getCategory(), bounds, tooltip, url)); } } categoryIndex++; } if (edge.equals(RectangleEdge.TOP)) { double h = state.getMax() + this.categoryLabelPositionOffset; state.cursorUp(h); } else if (edge.equals(RectangleEdge.BOTTOM)) { double h = state.getMax() + this.categoryLabelPositionOffset; state.cursorDown(h); } else if (edge == RectangleEdge.LEFT) { double w = state.getMax() + this.categoryLabelPositionOffset; state.cursorLeft(w); } else if (edge == RectangleEdge.RIGHT) { double w = state.getMax() + this.categoryLabelPositionOffset; state.cursorRight(w); } return state; }