From e89a2fbb2ab1fded237f7805d9dbbaedbbd7f815 Mon Sep 17 00:00:00 2001 From: Raven Laing <58245926+DJ-Raven@users.noreply.github.com> Date: Sun, 4 Aug 2024 17:37:50 +0700 Subject: [PATCH 1/2] Add padding to crosshair label --- .../java/org/jfree/chart/plot/Crosshair.java | 35 ++++++++++++ .../jfree/chart/swing/CrosshairOverlay.java | 54 +++++++++++-------- 2 files changed, 68 insertions(+), 21 deletions(-) diff --git a/src/main/java/org/jfree/chart/plot/Crosshair.java b/src/main/java/org/jfree/chart/plot/Crosshair.java index 9e8e66cdee..909483bd8a 100644 --- a/src/main/java/org/jfree/chart/plot/Crosshair.java +++ b/src/main/java/org/jfree/chart/plot/Crosshair.java @@ -47,6 +47,7 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; +import org.jfree.chart.api.RectangleInsets; import org.jfree.chart.internal.HashUtils; import org.jfree.chart.labels.CrosshairLabelGenerator; import org.jfree.chart.labels.StandardCrosshairLabelGenerator; @@ -108,6 +109,11 @@ public class Crosshair implements Cloneable, PublicCloneable, Serializable { */ private double labelYOffset; + /** + * The label padding. + */ + private RectangleInsets labelPadding; + /** * The label font. */ @@ -170,6 +176,7 @@ public Crosshair(double value, Paint paint, Stroke stroke) { this.labelAnchor = RectangleAnchor.BOTTOM_LEFT; this.labelXOffset = 5.0; this.labelYOffset = 5.0; + this.labelPadding = RectangleInsets.ZERO_INSETS; this.labelFont = new Font("Tahoma", Font.PLAIN, 12); this.labelPaint = Color.BLACK; this.labelBackgroundPaint = new Color(0, 0, 255, 63); @@ -409,6 +416,30 @@ public void setLabelYOffset(double offset) { this.pcs.firePropertyChange("labelYOffset", old, offset); } + /** + * Returns the label padding. + * + * @return The label padding (never {@code null}). + * @see #setLabelPadding + */ + public RectangleInsets getLabelPadding() { + return labelPadding; + } + + /** + * Sets the label padding and sends a property change event (with the name + * 'labelPadding') to all registered listeners. + * + * @param padding the padding ({@code null} not permitted). + * @see #getLabelPadding() + */ + public void setLabelPadding(RectangleInsets padding) { + Args.nullNotPermitted(padding, "padding"); + RectangleInsets old = this.labelPadding; + this.labelPadding = padding; + this.pcs.firePropertyChange("labelPadding", old, padding); + } + /** * Returns the label font. * @@ -609,6 +640,9 @@ public boolean equals(Object obj) { if (this.labelYOffset != that.labelYOffset) { return false; } + if (!this.labelPadding.equals(labelPadding)) { + return false; + } if (!this.labelFont.equals(that.labelFont)) { return false; } @@ -649,6 +683,7 @@ public int hashCode() { hash = HashUtils.hashCode(hash, this.labelGenerator); hash = HashUtils.hashCode(hash, this.labelXOffset); hash = HashUtils.hashCode(hash, this.labelYOffset); + hash = HashUtils.hashCode(hash, this.labelPadding); hash = HashUtils.hashCode(hash, this.labelFont); hash = HashUtils.hashCode(hash, this.labelPaint); hash = HashUtils.hashCode(hash, this.labelBackgroundPaint); diff --git a/src/main/java/org/jfree/chart/swing/CrosshairOverlay.java b/src/main/java/org/jfree/chart/swing/CrosshairOverlay.java index 2ab1e505ce..cc05eb382e 100644 --- a/src/main/java/org/jfree/chart/swing/CrosshairOverlay.java +++ b/src/main/java/org/jfree/chart/swing/CrosshairOverlay.java @@ -51,6 +51,7 @@ import java.util.ArrayList; import java.util.List; import org.jfree.chart.JFreeChart; +import org.jfree.chart.api.RectangleInsets; import org.jfree.chart.axis.ValueAxis; import org.jfree.chart.plot.Crosshair; import org.jfree.chart.plot.PlotOrientation; @@ -279,20 +280,23 @@ protected void drawHorizontalCrosshair(Graphics2D g2, Rectangle2D dataArea, Font savedFont = g2.getFont(); g2.setFont(crosshair.getLabelFont()); RectangleAnchor anchor = crosshair.getLabelAnchor(); - Point2D pt = calculateLabelPoint(line, anchor, crosshair.getLabelXOffset(), crosshair.getLabelYOffset()); + RectangleInsets padding = crosshair.getLabelPadding(); + Point2D pt = calculateLabelPoint(line, anchor, crosshair.getLabelXOffset(), crosshair.getLabelYOffset(), padding); float xx = (float) pt.getX(); float yy = (float) pt.getY(); TextAnchor alignPt = textAlignPtForLabelAnchorH(anchor); Shape hotspot = TextUtils.calculateRotatedStringBounds( label, g2, xx, yy, alignPt, 0.0, TextAnchor.CENTER); + hotspot = padding.createOutsetRectangle(hotspot.getBounds2D()); if (!dataArea.contains(hotspot.getBounds2D())) { anchor = flipAnchorV(anchor); - pt = calculateLabelPoint(line, anchor, crosshair.getLabelXOffset(), crosshair.getLabelYOffset()); + pt = calculateLabelPoint(line, anchor, crosshair.getLabelXOffset(), crosshair.getLabelYOffset(), padding); xx = (float) pt.getX(); yy = (float) pt.getY(); alignPt = textAlignPtForLabelAnchorH(anchor); hotspot = TextUtils.calculateRotatedStringBounds( label, g2, xx, yy, alignPt, 0.0, TextAnchor.CENTER); + hotspot = padding.createOutsetRectangle(hotspot.getBounds2D()); } g2.setPaint(crosshair.getLabelBackgroundPaint()); @@ -338,20 +342,23 @@ protected void drawVerticalCrosshair(Graphics2D g2, Rectangle2D dataArea, Font savedFont = g2.getFont(); g2.setFont(crosshair.getLabelFont()); RectangleAnchor anchor = crosshair.getLabelAnchor(); - Point2D pt = calculateLabelPoint(line, anchor, crosshair.getLabelXOffset(), crosshair.getLabelYOffset()); + RectangleInsets padding = crosshair.getLabelPadding(); + Point2D pt = calculateLabelPoint(line, anchor, crosshair.getLabelXOffset(), crosshair.getLabelYOffset(), padding); float xx = (float) pt.getX(); float yy = (float) pt.getY(); TextAnchor alignPt = textAlignPtForLabelAnchorV(anchor); Shape hotspot = TextUtils.calculateRotatedStringBounds( label, g2, xx, yy, alignPt, 0.0, TextAnchor.CENTER); + hotspot = padding.createOutsetRectangle(hotspot.getBounds2D()); if (!dataArea.contains(hotspot.getBounds2D())) { anchor = flipAnchorH(anchor); - pt = calculateLabelPoint(line, anchor, crosshair.getLabelXOffset(), crosshair.getLabelYOffset()); + pt = calculateLabelPoint(line, anchor, crosshair.getLabelXOffset(), crosshair.getLabelYOffset(), padding); xx = (float) pt.getX(); yy = (float) pt.getY(); alignPt = textAlignPtForLabelAnchorV(anchor); hotspot = TextUtils.calculateRotatedStringBounds( label, g2, xx, yy, alignPt, 0.0, TextAnchor.CENTER); + hotspot = padding.createOutsetRectangle(hotspot.getBounds2D()); } g2.setPaint(crosshair.getLabelBackgroundPaint()); g2.fill(hotspot); @@ -377,11 +384,12 @@ protected void drawVerticalCrosshair(Graphics2D g2, Rectangle2D dataArea, * @param anchor the anchor point. * @param deltaX the x-offset. * @param deltaY the y-offset. + * @param padding the label padding * * @return The anchor point. */ private Point2D calculateLabelPoint(Line2D line, RectangleAnchor anchor, - double deltaX, double deltaY) { + double deltaX, double deltaY, RectangleInsets padding) { double x, y; boolean left = (anchor == RectangleAnchor.BOTTOM_LEFT || anchor == RectangleAnchor.LEFT @@ -402,32 +410,36 @@ private Point2D calculateLabelPoint(Line2D line, RectangleAnchor anchor, x = line.getX1(); y = (line.getY1() + line.getY2()) / 2.0; if (left) { - x = x - deltaX; - } - if (right) { - x = x + deltaX; + x = x - deltaX - padding.getRight(); + } else if (right) { + x = x + deltaX + padding.getLeft(); + } else { + x = x + (padding.getLeft() - padding.getRight()) / 2.0; } if (top) { - y = Math.min(line.getY1(), line.getY2()) + deltaY; - } - if (bottom) { - y = Math.max(line.getY1(), line.getY2()) - deltaY; + y = Math.min(line.getY1(), line.getY2()) + deltaY + padding.getTop(); + } else if (bottom) { + y = Math.max(line.getY1(), line.getY2()) - deltaY - padding.getBottom(); + } else { + y = y + (padding.getTop() - padding.getBottom()) / 2.0; } } else { // horizontal x = (line.getX1() + line.getX2()) / 2.0; y = line.getY1(); if (left) { - x = Math.min(line.getX1(), line.getX2()) + deltaX; - } - if (right) { - x = Math.max(line.getX1(), line.getX2()) - deltaX; + x = Math.min(line.getX1(), line.getX2()) + deltaX + padding.getLeft(); + } else if (right) { + x = Math.max(line.getX1(), line.getX2()) - deltaX - padding.getRight(); + } else { + x = x + (padding.getLeft() - padding.getRight()) / 2.0; } if (top) { - y = y - deltaY; - } - if (bottom) { - y = y + deltaY; + y = y - deltaY - padding.getBottom(); + } else if (bottom) { + y = y + deltaY + padding.getTop(); + } else { + y = y + (padding.getTop() - padding.getBottom()) / 2.0; } } return new Point2D.Double(x, y); From 46c3c2281078a2640068bf84d82fab84ad8d9819 Mon Sep 17 00:00:00 2001 From: Raven Laing <58245926+DJ-Raven@users.noreply.github.com> Date: Sun, 4 Aug 2024 17:41:20 +0700 Subject: [PATCH 2/2] Adjusted crosshair label position to be within chart dataArea --- .../jfree/chart/swing/CrosshairOverlay.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/main/java/org/jfree/chart/swing/CrosshairOverlay.java b/src/main/java/org/jfree/chart/swing/CrosshairOverlay.java index cc05eb382e..a24bd535a2 100644 --- a/src/main/java/org/jfree/chart/swing/CrosshairOverlay.java +++ b/src/main/java/org/jfree/chart/swing/CrosshairOverlay.java @@ -293,6 +293,16 @@ protected void drawHorizontalCrosshair(Graphics2D g2, Rectangle2D dataArea, pt = calculateLabelPoint(line, anchor, crosshair.getLabelXOffset(), crosshair.getLabelYOffset(), padding); xx = (float) pt.getX(); yy = (float) pt.getY(); + if (anchor == RectangleAnchor.CENTER || alignPt.isHalfAscent()) { + double labelHeight = hotspot.getBounds2D().getHeight(); + double minY = dataArea.getY() + (labelHeight + padding.getTop() - padding.getBottom()) / 2.0; + double maxY = dataArea.getY() + dataArea.getHeight() - (labelHeight + padding.getBottom() - padding.getTop()) / 2.0; + if (yy < minY) { + yy = (float) (minY); + } else if (yy > maxY) { + yy = (float) (maxY); + } + } alignPt = textAlignPtForLabelAnchorH(anchor); hotspot = TextUtils.calculateRotatedStringBounds( label, g2, xx, yy, alignPt, 0.0, TextAnchor.CENTER); @@ -355,6 +365,16 @@ protected void drawVerticalCrosshair(Graphics2D g2, Rectangle2D dataArea, pt = calculateLabelPoint(line, anchor, crosshair.getLabelXOffset(), crosshair.getLabelYOffset(), padding); xx = (float) pt.getX(); yy = (float) pt.getY(); + if (alignPt.isHorizontalCenter()) { + double labelWidth = hotspot.getBounds2D().getWidth(); + double minX = dataArea.getX() + (labelWidth + padding.getLeft() - padding.getRight()) / 2.0; + double maxX = dataArea.getX() + dataArea.getWidth() - (labelWidth + padding.getRight() - padding.getLeft()) / 2.0; + if (xx < minX) { + xx = (float) (minX); + } else if (xx > maxX) { + xx = (float) (maxX); + } + } alignPt = textAlignPtForLabelAnchorV(anchor); hotspot = TextUtils.calculateRotatedStringBounds( label, g2, xx, yy, alignPt, 0.0, TextAnchor.CENTER);