【问题标题】:How to display volume in the domain crosshair and set the crosshair programmatically?如何在域十字准线中显示音量并以编程方式设置十字准线?
【发布时间】:2020-10-01 02:30:47
【问题描述】:

这个问题与我之前的问题 [How to show cross lines and red color for price drop in JFreeChart?] 有关。感谢@trashgod,我从那个问题中解决了很多问题,我会问这个问题还剩下什么。我的示例代码如下所示:

import java.awt.*;
import java.awt.event.*;
import java.text.*;
import java.util.*;
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;
  static Vector<String> Volume_Color_Vector=new Vector();

  public PriceVolume_Chart(String Symbol)
  {
    JFreeChart chart=createChart(Symbol);
    panel=new ChartPanel(chart,true,true,true,false,true);
    panel.setPreferredSize(new Dimension(1000,500));
    panel.addChartMouseListener(this);
    CrosshairOverlay crosshairOverlay=new CrosshairOverlay();
    float[] dash={2f,0f,2f};
    BasicStroke bs=new BasicStroke(1,BasicStroke.CAP_BUTT,BasicStroke.JOIN_ROUND,1.0f,dash,2f);

    xCrosshair=new Crosshair(Double.NaN,Color.GRAY,bs);
    xCrosshair.setLabelBackgroundPaint(new Color(0f,0f,0f,1f));
    xCrosshair.setLabelFont(xCrosshair.getLabelFont().deriveFont(14f));
    xCrosshair.setLabelPaint(new Color(1f,1f,1f,1f));
    xCrosshair.setLabelVisible(true);
    yCrosshair=new Crosshair(Double.NaN,Color.GRAY,bs);
    yCrosshair.setLabelBackgroundPaint(new Color(0f,0f,0f,1f));
    yCrosshair.setLabelFont(xCrosshair.getLabelFont().deriveFont(14f));
    yCrosshair.setLabelPaint(new Color(1f,1f,1f,1f));
    yCrosshair.setLabelVisible(true);
    crosshairOverlay.addDomainCrosshair(xCrosshair);
    crosshairOverlay.addRangeCrosshair(yCrosshair);
    panel.addOverlay(crosshairOverlay);
    add(panel);
    xCrosshair.setValue(1.5700752E12);
    xCrosshair.setVisible(true);
    yCrosshair.setValue(119.959007);
    yCrosshair.setVisible(true);
  }

  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);
    plot.setRenderer(1,new MyRender());

    return chart;
  }

  private void createPriceDataset(String Symbol)
  {
    String Lines[]=new String[21], Items[], Date;
    int Year, Month, Day;
    long Volume,Last_Volume=0;
    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);
      Volume_Color_Vector.add(Volume>=Last_Volume?"+":"-");
      Last_Volume=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);
//      Out("x = "+x+"  y = "+y);
      xCrosshair.setValue(x);
      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(); } });
  }
}

class MyRender extends XYBarRenderer
{
  @Override
  public Paint getItemPaint(int row,int col)
  {
    System.out.println(row+" "+col+" "+super.getItemPaint(row,col));
    return PriceVolume_Chart.Volume_Color_Vector.elementAt(col).equals("+")?super.getItemPaint(row,col):new Color(0.56f,0.2f,0.5f,1f);
  }
}

我的问题是:

[1] How to correctly show Volume data in cross-hair [ it's now showing the incorrect value, yet I don't know how to fix it ] ?
[2] How to point the mouse on the N-th [ e.g. 12th ] day when the app starts ? 

关于第二个问题的更多细节,当我移动鼠标时,我可以在屏幕上输出鼠标的 x,y 值,我可以执行以下操作在启动时将鼠标设置在所需的位置:

...
panel.addOverlay(crosshairOverlay);
add(panel);
xCrosshair.setValue(1.5700752E12);
yCrosshair.setValue(119.959007);

但是由于我不知道第N天的[x,y]位置,所以我不知道如何设置那天的鼠标。

我想这样做的原因是:在我的应用程序中,我想在另一个面板中查看某一天的一些数据,用户可以点击某一天,我希望 JFreeChart 立即指向那一天在图表上。

所以我的第二个问题是:“我怎样才能得到图表上第 N 天的 [x,y] 位置?

【问题讨论】:

  • 请专注于一个问题。您的示例变得相当笨拙;它也可能有助于集中注意力。您不能只设置域十字准线来显示该日期的音量吗?当用户控制鼠标时,您还考虑了哪些其他指标?标记?注释?
  • 我不知道如何正确显示音量,你能告诉我需要改变什么来修复它吗?

标签: java jfreechart


【解决方案1】:

如何在十字准线中正确显示体积数据;它现在显示的值不正确。

正如here 建议的那样,“x 值是日期……没有格式,十字准线正确显示了从纪元开始的毫秒数。”要更改十字准线的显示,请添加自定义 CrosshairLabelGenerator。下面的实现在您的卷系列中搜索匹配的时间戳并将相应的值格式化为数字。

xCrosshair.setLabelGenerator(new CrosshairLabelGenerator() {
    @Override
    public String generateLabel(Crosshair crosshair) {
        long ms = (long) crosshair.getValue();
        TimeSeriesDataItem item = null;
        for (int i = 0; i < Volume_Series.getItemCount(); i++) {
            item = Volume_Series.getDataItem(i);
            if (ms == item.getPeriod().getFirstMillisecond()) {
                break;
            }
        }
        long volume = item.getValue().longValue();
        return NumberFormat.getInstance().format(volume);
    }
});

考虑调整域轴的下边距以实现对称。

DateAxis domainAxis = (DateAxis) plot.getDomainAxis();
domainAxis.setLowerMargin(0.05);

还可以考虑将MyRenderer 的对齐因子设置为在价格点下方居中。

this.setBarAlignmentFactor(0.5);

当用户控制鼠标时,我会避免以编程方式移动十字准线。相反,请查看合适的AnnotationMarker。话虽如此,请将chartMouseMoved() 的实现与here 所示的实现进行比较。您对遇到的每个新XYItemEntity 的更新,因此十字准线“捕捉”到点或条。相比之下,后者不断更新,通过java2DToValue() 扩展,如here 所述。

【讨论】:

  • 一如既往,你有很好的答案^_^!但是有一个问题一直困扰着我,可能是我解释的不够好,所以在新的问题中换个角度问。谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-05-15
  • 2020-08-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-02-03
相关资源
最近更新 更多