如何在 JFreeChart 中显示交叉线和红色以显示价格下降?

How to show cross lines and red color for price drop in JFreeChart?

我的应用简化如下:

import java.awt.*;
import java.awt.event.*;
import java.text.*;
import javax.swing.*;
import org.jfree.chart.*;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.entity.*;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYBarRenderer;
import org.jfree.data.time.*;
import org.jfree.data.xy.XYDataset;
import org.jfree.chart.labels.*;
import org.jfree.chart.panel.*;
import org.jfree.chart.plot.*;

public class PriceVolume_Chart extends JPanel implements ChartMouseListener    // A demo application for price-volume chart.   
{
  ChartPanel panel;
  TimeSeries Price_series=new TimeSeries("Price");
  TimeSeries Volume_Series=new TimeSeries("Volume");
  Crosshair xCrosshair,yCrosshair;
  
  public PriceVolume_Chart(String Symbol)
  {
    JFreeChart chart=createChart(Symbol);
    panel=new ChartPanel(chart,true,true,true,false,true);
    panel.setPreferredSize(new java.awt.Dimension(1000,500));
    panel.addChartMouseListener(this);
    CrosshairOverlay crosshairOverlay=new CrosshairOverlay();
    xCrosshair=new Crosshair(Double.NaN,Color.GRAY,new BasicStroke(0f));
    xCrosshair.setLabelVisible(true);
    yCrosshair=new Crosshair(Double.NaN,Color.GRAY,new BasicStroke(0f));
    yCrosshair.setLabelVisible(true);
    crosshairOverlay.addDomainCrosshair(xCrosshair);
    crosshairOverlay.addRangeCrosshair(yCrosshair);
    panel.addOverlay(crosshairOverlay);
    add(panel);
  }

  private JFreeChart createChart(String Symbol)
  {
    createPriceDataset(Symbol);
    XYDataset priceData=new TimeSeriesCollection(Price_series);
    JFreeChart chart=ChartFactory.createTimeSeriesChart(Symbol,
                                                        "Date",
                                                        getYLabel("Price ( $ )"),
                                                        priceData,
                                                        true,
                                                        true,
                                                        true
                                                       );
    XYPlot plot=chart.getXYPlot();
    plot.setBackgroundPaint(new Color(192,196,196));
    NumberAxis rangeAxis1=(NumberAxis)plot.getRangeAxis();
    rangeAxis1.setLowerMargin(0.40);                                           // Leave room for volume bars
//    plot.getRenderer().setDefaultToolTipGenerator(new StandardXYToolTipGenerator(StandardXYToolTipGenerator.DEFAULT_TOOL_TIP_FORMAT,DateFormat.getDateInstance(), NumberFormat.getCurrencyInstance()));
    plot.getRenderer().setDefaultToolTipGenerator(new StandardXYToolTipGenerator(StandardXYToolTipGenerator.DEFAULT_TOOL_TIP_FORMAT,new SimpleDateFormat("yyyy-MM-d"),NumberFormat.getCurrencyInstance()));
//    DecimalFormat format=new DecimalFormat("00.00");
//    rangeAxis1.setNumberFormatOverride(format);
//    rangeAxis1.setNumberFormatOverride(NumberFormat.getCurrencyInstance());  

    NumberAxis rangeAxis2=new NumberAxis("Volume");
    rangeAxis2.setUpperMargin(1.00);                                           // Leave room for price line   
    rangeAxis2.setNumberFormatOverride(NumberFormat.getNumberInstance());
    plot.setRangeAxis(1,rangeAxis2);
    plot.setDataset(1,new TimeSeriesCollection(Volume_Series));
    plot.setRangeAxis(1,rangeAxis2);
    plot.mapDatasetToRangeAxis(1,1);
    XYBarRenderer renderer2=new XYBarRenderer(0.20);
    renderer2.setShadowVisible(false);
    renderer2.setDefaultToolTipGenerator(new StandardXYToolTipGenerator(StandardXYToolTipGenerator.DEFAULT_TOOL_TIP_FORMAT,new SimpleDateFormat("yyyy-MM-d"),new DecimalFormat("0,000")));
    plot.setRenderer(1,renderer2);
    return chart;
  }

  private void createPriceDataset(String Symbol)
  {
    String Lines[]=new String[21],Items[],Date;
    int Year,Month,Day;
    long Volume;
    double Price;

    Lines[0]="Date,Open,High,Low,Close,Adj Close,Volume";
    Lines[1]="2019-09-23,129.589996,130.710007,128.240005,129.300003,126.555969,553700";
    Lines[2]="2019-09-24,129.309998,129.529999,125.500000,126.750000,124.060089,732900";
    Lines[3]="2019-09-25,126.570000,128.500000,126.190002,127.879997,125.166100,422000";
    Lines[4]="2019-09-26,127.849998,128.589996,127.169998,127.779999,125.068230,376100";
    Lines[5]="2019-09-27,128.669998,129.289993,126.389999,126.419998,123.737083,332900";
    Lines[6]="2019-09-30,126.589996,128.789993,125.849998,128.130005,125.410797,456700";
    Lines[7]="2019-10-01,129.039993,130.899994,125.480003,126.040001,123.365158,322700";
    Lines[8]="2019-10-02,125.059998,125.180000,121.620003,123.120003,120.507126,577100";
    Lines[9]="2019-10-03,122.650002,123.320000,119.089996,122.559998,119.959007,581300";
    Lines[10]="2019-10-04,122.970001,123.949997,121.320000,123.879997,121.250992,315700";
    Lines[11]="2019-10-07,123.139999,124.610001,122.669998,122.879997,120.272217,510300";
    Lines[12]="2019-10-08,121.720001,121.879997,118.089996,118.660004,116.141777,616600";
    Lines[13]="2019-10-09,119.410004,119.610001,116.680000,118.419998,115.906868,603300";
    Lines[14]="2019-10-10,119.089996,121.209999,117.080002,118.209999,115.701324,483300";
    Lines[15]="2019-10-11,120.330002,123.040001,119.720001,122.550003,119.949226,700500";
    Lines[16]="2019-10-14,122.550003,123.720001,120.940002,122.540001,119.939430,492900";
    Lines[17]="2019-10-15,122.849998,124.220001,121.230003,123.699997,121.074814,598200";
    Lines[18]="2019-10-16,123.889999,124.849998,122.800003,123.209999,120.595207,663600";
    Lines[19]="2019-10-17,123.449997,124.889999,122.790001,123.360001,120.742035,563200";
    Lines[20]="2019-10-18,123.050003,124.620003,122.459999,123.540001,120.918213,650300";
    
    for (int i=1;i<Lines.length;i++)
    {
      Items=Lines[i].split(",");
      Date=Items[0].replace("-0","-");
      Price=Double.parseDouble(Items[5]);
      Volume=Long.parseLong(Items[6]);
      Items=Date.split("-");
      Year=Integer.parseInt(Items[0]);
      Month=Integer.parseInt(Items[1]);
      Day=Integer.parseInt(Items[2]);
      Price_series.add(new Day(Day,Month,Year),Price);
      Volume_Series.add(new Day(Day,Month,Year),Volume);
    }
  }

  @Override
  public void chartMouseClicked(ChartMouseEvent event)
  {
    // ignore
  }

  public void chartMouseMoved(ChartMouseEvent cmevent)
  {
    ChartEntity chartentity=cmevent.getEntity();
    if (chartentity instanceof XYItemEntity)
    {
      XYItemEntity e=(XYItemEntity)chartentity;
      XYDataset d=e.getDataset();
      int s=e.getSeriesIndex();
      int i=e.getItem();
      double x=d.getXValue(s,i);
      double y=d.getYValue(s,i);
      this.xCrosshair.setValue(x);
      this.yCrosshair.setValue(y);
    }
  }
  String getYLabel(String Text)
  {
    String Result="";
    
    for (int i=0;i<Text.length();i++) Result+=Text.charAt(i)+(i<Text.length()-1?"\u2009":"");
//    Out(Result);
    return Result;
  }

  private static void out(String message) { System.out.print(message); }
  private static void Out(String message) { System.out.println(message); }

  // Create the GUI and show it. For thread safety, this method should be invoked from the event-dispatching thread.
  static void Create_And_Show_GUI()
  {
    final PriceVolume_Chart demo=new PriceVolume_Chart("ADS");

    JFrame frame=new JFrame("PriceVolume_Chart Frame");
    frame.add(demo);
    frame.addWindowListener( new WindowAdapter()
    {
      public void windowActivated(WindowEvent e) { }
      public void windowClosed(WindowEvent e) { }
      public void windowClosing(WindowEvent e) { System.exit(0); }
      public void windowDeactivated(WindowEvent e) { }
      public void windowDeiconified(WindowEvent e) { demo.repaint(); }
      public void windowGainedFocus(WindowEvent e) { demo.repaint(); }
      public void windowIconified(WindowEvent e) { }
      public void windowLostFocus(WindowEvent e) { }
      public void windowOpening(WindowEvent e) { demo.repaint(); }
      public void windowOpened(WindowEvent e) { }
      public void windowResized(WindowEvent e) { demo.repaint(); }
      public void windowStateChanged(WindowEvent e) { demo.repaint(); }
    });
    frame.pack();
    frame.setLocationRelativeTo(null);
    frame.setVisible(true);
  }

  public static void main(String[] args)
  {
    // Schedule a job for the event-dispatching thread : creating and showing this application's GUI.
    SwingUtilities.invokeLater(new Runnable() { public void run() { Create_And_Show_GUI(); } });
  }
}

============================================= ========================

在@trashgod 的帮助下,我能够将十字线添加到图表中。但是我仍然想知道如何实现以下目标:

 [1] The value shown for volume when cross-hair moves, is in-correct, how to show correct volume value ?
 [2] How to have solid non-transparent background for the dynamic values shown near the axis ? Now it's transparent, hard to read.
 [3] How to set the cross-hair at startup on a certain [ e.g. 20th ] date ?
 [4] How to show red color for volumes when price drops ?

这是我正在寻找的图片:

Show cross lines when my mouse points to different locations on the image?

您能够采用 CrosshairOverlayDemo1, suggested 中所示的方法。

The value shown for volume when cross-hair moves, is incorrect, how to show correct volume value?

您的 ChartMouseListener 实现访问价格数据集中的 xy 值。 x 值为日期; y 值是价格。如果没有格式化,十字准线会正确显示 epoch 的毫秒数。您可能想要获取相应的卷。

How to have solid non-transparent background for the dynamic values shown near the axis? Now it's transparent, hard to read.

您可以修改十字准线的外观,如图所示。对于背景,使用不透明颜色或指定 alpha 值 255 来构建纯色。

How to set the cross-hair at startup on a certain e.g. 20th date?

你可以使用Robot,如图here,但我不想用这种方式给用户惊喜。

How to show red color for volumes when price drops?

您可以覆盖 getItemPaint(),如此处所示 here 以根据体积更改渲染颜色。

一般来说,您会发现在可以集成到您的代码中的独立示例中更容易检查此类问题,就像您在十字准线覆盖演示中所做的那样。