我正在尝试在堆积的条形图上绘制一条曲线箭头。我已经能够绘制曲线和箭头。但是我无法将箭头连接到曲线的末端。我正在使用绘制曲线的仿射变换。下面的链接描述了我能够绘制的曲线和箭头http://i58.tinypic.com/2m422hy.png.Can任何人都可以指导我如何将箭头连接到曲线的末端。
这是代码
包Stack;
/ * *要更改此模板,请选择“工具”|模板 *并在编辑器中打开模板。 * /
/ ** * * @author OSPL-B4 / / *要更改此模板,请选择“工具”|模板 *并在编辑器中打开模板。 * /
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Polygon;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.font.TextLayout;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.awt.geom.QuadCurve2D;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import org.jfree.chart.annotations.CategoryAnnotation;
import org.jfree.chart.axis.CategoryAnchor;
import org.jfree.chart.axis.CategoryAxis;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.event.AnnotationChangeListener;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.Plot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.data.category.CategoryDataset;
import org.jfree.io.SerialUtilities;
import org.jfree.ui.RectangleEdge;
import org.jfree.util.ObjectUtilities;
import org.jfree.util.PaintUtilities;
// import java.awt.Font;
/**
* A line annotation that can be placed on a
* {@link org.jfree.chart.plot.CategoryPlot}.
*/
public class CategoryLineAnnotation_demo1 implements CategoryAnnotation,
Cloneable, Serializable {
/** The category for the start of the line. */
private Comparable category1;
/** The value for the start of the line. */
private double value1;
/** The category for the end of the line. */
private Comparable category2;
/** The value for the end of the line. */
private double value2;
private final int ARR_SIZE = 4;
/** The line color. */
private transient Paint paint = Color.black;
/** The line stroke. */
private transient Stroke stroke = new BasicStroke(1.0f);
/**
* Creates a new annotation that draws a line between (category1, value1)
* and (category2, value2).
*
* @param category1 the category (<code>null</code> not permitted).
* @param value1 the value.
* @param category2 the category (<code>null</code> not permitted).
* @param value2 the value.
*/
public CategoryLineAnnotation_demo1(Comparable category1, double value1,
Comparable category2, double value2,
Paint paint, Stroke stroke) {
if (category1 == null) {
throw new IllegalArgumentException("Null 'category1' argument.");
}
if (category2 == null) {
throw new IllegalArgumentException("Null 'category2' argument.");
}
if (paint == null) {
throw new IllegalArgumentException("Null 'paint' argument.");
}
if (stroke == null) {
throw new IllegalArgumentException("Null 'stroke' argument.");
}
this.category1 = category1;
System.out.println("First Category value is "+category1);
this.value1 = value1;
this.category2 = category2;
System.out.println("Second Category value is "+category2);
this.value2 = value2;
this.paint = paint;
this.stroke = stroke;
}
/**
* Returns the category for the start of the line.
*
* @return The category for the start of the line (never <code>null</code>).
*/
public Comparable getCategory1() {
return this.category1;
}
/**
* Sets the category for the start of the line.
*
* @param category the category (<code>null</code> not permitted).
*/
public void setCategory1(Comparable category) {
if (category == null) {
throw new IllegalArgumentException("Null 'category' argument.");
}
this.category1 = category;
}
/**
* Returns the y-value for the start of the line.
*
* @return The y-value for the start of the line.
*/
public double getValue1() {
return this.value1;
}
/**
* Sets the y-value for the start of the line.
*
* @param value the value.
*/
public void setValue1(double value) {
this.value1 = value;
}
/**
* Returns the category for the end of the line.
*
* @return The category for the end of the line (never <code>null</code>).
*/
public Comparable getCategory2() {
return this.category2;
}
/**
* Sets the category for the end of the line.
*
* @param category the category (<code>null</code> not permitted).
*/
public void setCategory2(Comparable category) {
if (category == null) {
throw new IllegalArgumentException("Null 'category' argument.");
}
this.category2 = category;
}
/**
* Returns the y-value for the end of the line.
*
* @return The y-value for the end of the line.
*/
public double getValue2() {
return this.value2;
}
/**
* Sets the y-value for the end of the line.
*
* @param value the value.
*/
public void setValue2(double value) {
this.value2 = value;
}
/**
* Returns the paint used to draw the connecting line.
*
* @return The paint (never <code>null</code>).
*/
public Paint getPaint() {
return this.paint;
}
/**
* Sets the paint used to draw the connecting line.
*
* @param paint the paint (<code>null</code> not permitted).
*/
public void setPaint(Paint paint) {
if (paint == null) {
throw new IllegalArgumentException("Null 'paint' argument.");
}
this.paint = paint;
}
/**
* Returns the stroke used to draw the connecting line.
*
* @return The stroke (never <code>null</code>).
*/
public Stroke getStroke() {
// System.out.println("In Stacked bar Stroke is "+getStroke());
return this.stroke;
}
/**
* Sets the stroke used to draw the connecting line.
*
* @param stroke the stroke (<code>null</code> not permitted).
*/
public void setStroke(Stroke stroke) {
if (stroke == null) {
throw new IllegalArgumentException("Null 'stroke' argument.");
}
this.stroke = stroke;
}
/**
* 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.
*/
public void draw(Graphics2D g2, CategoryPlot plot, Rectangle2D dataArea,
CategoryAxis domainAxis, ValueAxis rangeAxis) {
CategoryDataset dataset = plot.getDataset();
int catIndex1 = dataset.getColumnIndex(this.category1);
int catIndex2 = dataset.getColumnIndex(this.category2);
int catCount = dataset.getColumnCount();
double lineX1 = 0.0f;
double lineY1 = 0.0f;
double lineX2 = 0.0f;
double lineY2 = 0.0f;
PlotOrientation orientation = plot.getOrientation();
RectangleEdge domainEdge = Plot.resolveDomainAxisLocation(
plot.getDomainAxisLocation(), orientation);
RectangleEdge rangeEdge = Plot.resolveRangeAxisLocation(
plot.getRangeAxisLocation(), orientation);
if (orientation == PlotOrientation.HORIZONTAL) {
lineY1 = domainAxis.getCategoryJava2DCoordinate(
CategoryAnchor.MIDDLE, catIndex1, catCount, dataArea,
domainEdge);
lineX1 = rangeAxis.valueToJava2D(this.value1, dataArea, rangeEdge);
lineY2 = domainAxis.getCategoryJava2DCoordinate(
CategoryAnchor.MIDDLE, catIndex2, catCount, dataArea,
domainEdge);
lineX2 = rangeAxis.valueToJava2D(this.value2, dataArea, rangeEdge);
}
else if (orientation == PlotOrientation.VERTICAL) {
lineX1 = domainAxis.getCategoryJava2DCoordinate(
CategoryAnchor.MIDDLE, catIndex1, catCount, dataArea,
domainEdge);
lineY1 = rangeAxis.valueToJava2D(this.value1, dataArea, rangeEdge);
lineX2 = domainAxis.getCategoryJava2DCoordinate(
CategoryAnchor.MIDDLE, catIndex2, catCount, dataArea,
domainEdge);
lineY2 = rangeAxis.valueToJava2D(this.value2, dataArea, rangeEdge);
}
g2.setPaint(this.paint);
g2.setStroke(this.stroke);
drawArrow(g2,(int) lineX1, (int) lineY1, (int) lineX2, (int) lineY2);
}
void drawArrow(Graphics g1, int x1, int y1, int x2, int y2) {
Graphics2D g = (Graphics2D) g1.create();
double dx = x2 - x1, dy = y2 - y1;
System.out.println("Value of DX "+dx);
System.out.println("Value of DY "+dy);
double angle = Math.atan2(dy, dx);
System.out.println("Getting angle "+angle);
int len = (int) Math.sqrt(dx*dx + dy*dy);
AffineTransform at = AffineTransform.getTranslateInstance(x1, y1);
at.concatenate(AffineTransform.getRotateInstance(angle));
g.transform(at);
System.out.println("Affine transform X co-ordinate value is "+at.getScaleX());
System.out.println("Affine transform Y co-ordinate value is "+at.getScaleY());
float center1=(x1+x2)/2-40;
float center2= (y1+y2)/2-40;
QuadCurve2D q=new QuadCurve2D.Float(0,0,center1,center2,x2,y2);
g.draw(q);
g.setColor(Color.RED);
System.out.println("Length of arrow is "+len);
System.out.println("Get Start point 2D "+q.getP1());
System.out.println("Get End point 2D "+q.getP2());
g.fillPolygon(new int[] {len, len-ARR_SIZE, len-ARR_SIZE-10, len-60},
new int[] {0, -ARR_SIZE, ARR_SIZE-20, 5}, 4);
}
public void paintComponent(Graphics g) {
for (int x = 15; x < 200; x += 16)
drawArrow(g, x, x, x, 150);
drawArrow(g, 30, 300, 300, 190);
}
/**
* Tests this object for equality with another.
*
* @param obj the object (<code>null</code> permitted).
*
* @return <code>true</code> or <code>false</code>.
*/
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof CategoryLineAnnotation_demo1)) {
return false;
}
CategoryLineAnnotation_demo1 that = (CategoryLineAnnotation_demo1) obj;
if (!this.category1.equals(that.getCategory1())) {
return false;
}
if (this.value1 != that.getValue1()) {
return false;
}
if (!this.category2.equals(that.getCategory2())) {
return false;
}
if (this.value2 != that.getValue2()) {
return false;
}
if (!PaintUtilities.equal(this.paint, that.paint)) {
return false;
}
if (!ObjectUtilities.equal(this.stroke, that.stroke)) {
return false;
}
return true;
}
/**
* Returns a hash code for this instance.
*
* @return A hash code.
*/
public int hashCode() {
// TODO: this needs work
return this.category1.hashCode() + this.category2.hashCode();
}
/**
* Returns a clone of the annotation.
*
* @return A clone.
*
* @throws CloneNotSupportedException this class will not throw this
* exception, but subclasses (if any) might.
*/
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
/**
* Provides serialization support.
*
* @param stream the output stream.
*
* @throws IOException if there is an I/O error.
*/
private void writeObject(ObjectOutputStream stream) throws IOException {
stream.defaultWriteObject();
SerialUtilities.writePaint(this.paint, stream);
SerialUtilities.writeStroke(this.stroke, stream);
}
/**
* Provides serialization support.
*
* @param stream the input stream.
*
* @throws IOException if there is an I/O error.
* @throws ClassNotFoundException if there is a classpath problem.
*/
private void readObject(ObjectInputStream stream)
throws IOException, ClassNotFoundException {
stream.defaultReadObject();
this.paint = SerialUtilities.readPaint(stream);
this.stroke = SerialUtilities.readStroke(stream);
}
@Override
public void addChangeListener(AnnotationChangeListener al) {
}
@Override
public void removeChangeListener(AnnotationChangeListener al) {
}
}
答案 0 :(得分:1)
您可以根据最后一个线段创建箭头(可能已经使用AffineTransform进行了转换)
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Shape;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class ArrowPainter
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
@Override
public void run()
{
createAndShowGUI();
}
});
}
private static void createAndShowGUI()
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel panel = new ArrowPaintPanel();
f.getContentPane().add(panel);
f.setSize(500,500);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
class ArrowPaintPanel extends JPanel implements MouseMotionListener
{
private Point2D startPoint = null;
private Point2D endPoint = null;
ArrowPaintPanel()
{
addMouseMotionListener(this);
}
@Override
protected void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D)gr;
if (startPoint == null)
{
startPoint = new Point(getWidth()/2, getHeight()/2);
}
if (endPoint == null)
{
return;
}
Line2D line = new Line2D.Double(startPoint, endPoint);
Shape arrowHead = createArrowHead(line, 30, 20);
g.draw(line);
g.fill(arrowHead);
}
@Override
public void mouseDragged(MouseEvent e)
{
endPoint = e.getPoint();
repaint();
}
@Override
public void mouseMoved(MouseEvent e)
{
endPoint = e.getPoint();
repaint();
}
private static Shape createArrowHead(Line2D line, double length, double width)
{
Point2D p0 = line.getP1();
Point2D p1 = line.getP2();
double x0 = p0.getX();
double y0 = p0.getY();
double x1 = p1.getX();
double y1 = p1.getY();
double dx = x1 - x0;
double dy = y1 - y0;
double invLength = 1.0 / Math.sqrt(dx*dx+dy*dy);
double dirX = dx * invLength;
double dirY = dy * invLength;
double ax = x1 - length * dirX;
double ay = y1 - length * dirY;
double offsetX = width * -dirY * 0.5;
double offsetY = width * dirX * 0.5;
double c0x = ax + offsetX;
double c0y = ay + offsetY;
double c1x = ax - offsetX;
double c1y = ay - offsetY;
Path2D arrowHead = new Path2D.Double();
arrowHead.moveTo(x1, y1);
arrowHead.lineTo(c0x, c0y);
arrowHead.lineTo(c1x, c1y);
arrowHead.closePath();
return arrowHead;
}
}
编辑:上述编辑和评论的更新:这是很多代码,但仍然没有什么可以轻松测试。替换行时会发生什么
drawArrow(g2,(int) lineX1, (int) lineY1, (int) lineX2, (int) lineY2);
与
g.fill(createArrowHead(new Line2D.Double(lineX1, lineY1, lineX2, lineY2), 30, 20));