JFreeChart中如何获取图表上某个点的[x,y]值?
In JFreeChart how to get the [x,y] values of a certain point on the chart?
这个问题与我之前的问题[]有关,在@trashgod的帮助下,我得到了大部分问题的答案,但还有一个问题没有得到解答,在这里我会问换个角度问这个问题。
在我的 Swing 应用程序中,有一个包含一些数据的日期列表,当我单击某个日期时,我希望我的应用程序显示一个图表并在图像显示时指向该日期,所以我不必须四处移动鼠标才能查找和显示该日期的信息。所以应用程序屏幕截图如下所示。
因此,正如您从顶部屏幕截图中看到的那样,如果我在日期列表中单击 2020-07-29,图表将加载并且十字准线将自动聚焦在 2020-07-29 上,并且相关信息将自动显示。在底部截图中,如果我点击日期 2020-08-11,图表图像将加载,鼠标将设置在 2020-08-11,因此作为用户,我不必在图像上搜索并找到该日期的数据。
我的简化版应用如下:
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.*;
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.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);
}
});
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.5959952E12);
xCrosshair.setVisible(true);
yCrosshair.setValue(45.230579);
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,new SimpleDateFormat("yyyy-MM-d"),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);
MyRender Renderer=new MyRender();
Renderer.setShadowVisible(false);
plot.setRenderer(1,Renderer);
DateAxis domainAxis=(DateAxis) plot.getDomainAxis(); // Consider adjusting the lower margin of the domain axis for symmetry.
domainAxis.setLowerMargin(0.05);
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]="2020-07-17,44.110001,44.369999,41.919998,42.509998,42.323395,849700";
Lines[2]="2020-07-20,41.630001,41.680000,39.669998,40.119999,39.943886,1319300";
Lines[3]="2020-07-21,40.880001,42.860001,40.860001,42.270000,42.084450,2070300";
Lines[4]="2020-07-22,41.919998,42.700001,41.090000,42.570000,42.383133,1317600";
Lines[5]="2020-07-23,43.919998,46.389999,43.279999,44.759998,44.563519,1917700";
Lines[6]="2020-07-24,46.500000,46.500000,43.950001,44.410000,44.215057,1384600";
Lines[7]="2020-07-27,44.000000,44.240002,42.610001,43.860001,43.667469,799800";
Lines[8]="2020-07-28,43.389999,44.590000,42.930000,43.020000,42.831158,699700";
Lines[9]="2020-07-29,42.759998,45.590000,42.740002,45.430000,45.230579,826200";
Lines[10]="2020-07-30,44.160000,44.639999,42.959999,44.500000,44.304661,798100";
Lines[11]="2020-07-31,44.330002,44.419998,42.580002,44.360001,44.165276,1037800";
Lines[12]="2020-08-03,44.560001,45.599998,43.419998,44.939999,44.742729,797000";
Lines[13]="2020-08-04,44.900002,45.500000,43.450001,43.540001,43.348877,971100";
Lines[14]="2020-08-05,44.860001,45.389999,43.650002,45.330002,45.131020,902000";
Lines[15]="2020-08-06,45.049999,46.279999,44.330002,45.299999,45.101147,645200";
Lines[16]="2020-08-07,44.849998,46.189999,44.189999,46.150002,45.947418,604900";
Lines[17]="2020-08-10,46.669998,48.410000,46.549999,47.290001,47.082417,960200";
Lines[18]="2020-08-11,49.110001,50.849998,48.799999,48.910000,48.695301,1187700";
Lines[19]="2020-08-12,49.759998,50.009998,47.060001,47.840000,47.630001,752800";
Lines[20]="2020-08-13,46.950001,48.369999,46.459999,47.110001,47.110001,535700";
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)
{
this.setBarAlignmentFactor(0.5);
// 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);
}
}
我知道如何在图表上的某个点设置十字准线[ 注释掉:xCrosshair.setValue(1.5959952E12); ],但我不知道某个图表上某个日期的 [x,y] 值,因此我的问题是:在像我这里的 JFreeChart 上,如何获取一个的 [x,y] 值确定日期 [ 例如2020-07-29 ],或者如何获取第 N 个日期的 [x,y] 值 [ 例如第 9 个日期 = 2020-07-29 ] ?
NumberAxis
方法 java2DToValue()
狭隘地关注“如何获取特定日期的 [x,y] 值”这一问题
和 valueToJava2D()
说明了该方法。 CrosshairOverlayDemo1
is a concrete example, and the general principle is examined .
幸运的是,您可以仅使用其数据突出显示图表元素。特别是,Annotation
、Marker
和 Crosshair
的实现都使用数据坐标。作为具体示例,让 PriceVolume_Chart
实现 ActionListener
并向图表的构造函数添加 javax.swing.Timer
:
public PriceVolume_Chart(String Symbol) {
…
Timer t = new Timer(500, this);
t.start();
}
然后,给定合适的 index
和听众,您会看到十字准线以 2 Hz 的频率连续移动到每个日期。
private int index;
@Override
public void actionPerformed(ActionEvent e) {
TimeSeriesDataItem item = Volume_Series.getDataItem(index);
xCrosshair.setValue(item.getPeriod().getFirstMillisecond());
item = Price_series.getDataItem(index);
yCrosshair.setValue(item.getValue().doubleValue());
index++;
if (index == Volume_Series.getItemCount()) {
index = 0;
}
}
您可以使用类似的侦听器在用户选择特定数据条目时移动您选择的指示器。确切的细节取决于您对观察者模式的实现,讨论 here. As you'll want to let both chart and table views share access to a common model, you may consider extending AbstractXYDataset
, shown here and implementing a Swing TableModel
; alternatively, consider extending AbstractTableModel
, shown here 和实现 XYDataset
接口。
好吧,@trashgod 鼓舞人心。我终于尝试了他的建议并找到了我想要的答案;这是基本的解决方案:
TimeSeriesDataItem itemX = Volume_Series.getDataItem(Index);
xCrosshair.setValue(itemX.getPeriod().getFirstMillisecond());
TimeSeriesDataItem itemY = Price_series.getDataItem(Index);
yCrosshair.setValue(itemY.getValue().doubleValue());
完整示例:
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.*;
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();
private int index;
public PriceVolume_Chart(String Symbol,int Index)
{
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.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);
}
});
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);
TimeSeriesDataItem itemX=Volume_Series.getDataItem(Index);
xCrosshair.setValue(itemX.getPeriod().getFirstMillisecond());
TimeSeriesDataItem itemY=Price_series.getDataItem(Index);
yCrosshair.setValue(itemY.getValue().doubleValue());
}
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,new SimpleDateFormat("yyyy-MM-d"),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);
MyRender Renderer=new MyRender();
Renderer.setShadowVisible(false);
plot.setRenderer(1,Renderer);
DateAxis domainAxis=(DateAxis) plot.getDomainAxis(); // Consider adjusting the lower margin of the domain axis for symmetry.
domainAxis.setLowerMargin(0.05);
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]="2020-07-17,44.110001,44.369999,41.919998,42.509998,42.323395,849700";
Lines[2]="2020-07-20,41.630001,41.680000,39.669998,40.119999,39.943886,1319300";
Lines[3]="2020-07-21,40.880001,42.860001,40.860001,42.270000,42.084450,2070300";
Lines[4]="2020-07-22,41.919998,42.700001,41.090000,42.570000,42.383133,1317600";
Lines[5]="2020-07-23,43.919998,46.389999,43.279999,44.759998,44.563519,1917700";
Lines[6]="2020-07-24,46.500000,46.500000,43.950001,44.410000,44.215057,1384600";
Lines[7]="2020-07-27,44.000000,44.240002,42.610001,43.860001,43.667469,799800";
Lines[8]="2020-07-28,43.389999,44.590000,42.930000,43.020000,42.831158,699700";
Lines[9]="2020-07-29,42.759998,45.590000,42.740002,45.430000,45.230579,826200";
Lines[10]="2020-07-30,44.160000,44.639999,42.959999,44.500000,44.304661,798100";
Lines[11]="2020-07-31,44.330002,44.419998,42.580002,44.360001,44.165276,1037800";
Lines[12]="2020-08-03,44.560001,45.599998,43.419998,44.939999,44.742729,797000";
Lines[13]="2020-08-04,44.900002,45.500000,43.450001,43.540001,43.348877,971100";
Lines[14]="2020-08-05,44.860001,45.389999,43.650002,45.330002,45.131020,902000";
Lines[15]="2020-08-06,45.049999,46.279999,44.330002,45.299999,45.101147,645200";
Lines[16]="2020-08-07,44.849998,46.189999,44.189999,46.150002,45.947418,604900";
Lines[17]="2020-08-10,46.669998,48.410000,46.549999,47.290001,47.082417,960200";
Lines[18]="2020-08-11,49.110001,50.849998,48.799999,48.910000,48.695301,1187700";
Lines[19]="2020-08-12,49.759998,50.009998,47.060001,47.840000,47.630001,752800";
Lines[20]="2020-08-13,46.950001,48.369999,46.459999,47.110001,47.110001,535700";
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",19);
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)
{
this.setBarAlignmentFactor(0.5);
// 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);
}
}
这个问题与我之前的问题[
在我的 Swing 应用程序中,有一个包含一些数据的日期列表,当我单击某个日期时,我希望我的应用程序显示一个图表并在图像显示时指向该日期,所以我不必须四处移动鼠标才能查找和显示该日期的信息。所以应用程序屏幕截图如下所示。
因此,正如您从顶部屏幕截图中看到的那样,如果我在日期列表中单击 2020-07-29,图表将加载并且十字准线将自动聚焦在 2020-07-29 上,并且相关信息将自动显示。在底部截图中,如果我点击日期 2020-08-11,图表图像将加载,鼠标将设置在 2020-08-11,因此作为用户,我不必在图像上搜索并找到该日期的数据。
我的简化版应用如下:
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.*;
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.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);
}
});
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.5959952E12);
xCrosshair.setVisible(true);
yCrosshair.setValue(45.230579);
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,new SimpleDateFormat("yyyy-MM-d"),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);
MyRender Renderer=new MyRender();
Renderer.setShadowVisible(false);
plot.setRenderer(1,Renderer);
DateAxis domainAxis=(DateAxis) plot.getDomainAxis(); // Consider adjusting the lower margin of the domain axis for symmetry.
domainAxis.setLowerMargin(0.05);
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]="2020-07-17,44.110001,44.369999,41.919998,42.509998,42.323395,849700";
Lines[2]="2020-07-20,41.630001,41.680000,39.669998,40.119999,39.943886,1319300";
Lines[3]="2020-07-21,40.880001,42.860001,40.860001,42.270000,42.084450,2070300";
Lines[4]="2020-07-22,41.919998,42.700001,41.090000,42.570000,42.383133,1317600";
Lines[5]="2020-07-23,43.919998,46.389999,43.279999,44.759998,44.563519,1917700";
Lines[6]="2020-07-24,46.500000,46.500000,43.950001,44.410000,44.215057,1384600";
Lines[7]="2020-07-27,44.000000,44.240002,42.610001,43.860001,43.667469,799800";
Lines[8]="2020-07-28,43.389999,44.590000,42.930000,43.020000,42.831158,699700";
Lines[9]="2020-07-29,42.759998,45.590000,42.740002,45.430000,45.230579,826200";
Lines[10]="2020-07-30,44.160000,44.639999,42.959999,44.500000,44.304661,798100";
Lines[11]="2020-07-31,44.330002,44.419998,42.580002,44.360001,44.165276,1037800";
Lines[12]="2020-08-03,44.560001,45.599998,43.419998,44.939999,44.742729,797000";
Lines[13]="2020-08-04,44.900002,45.500000,43.450001,43.540001,43.348877,971100";
Lines[14]="2020-08-05,44.860001,45.389999,43.650002,45.330002,45.131020,902000";
Lines[15]="2020-08-06,45.049999,46.279999,44.330002,45.299999,45.101147,645200";
Lines[16]="2020-08-07,44.849998,46.189999,44.189999,46.150002,45.947418,604900";
Lines[17]="2020-08-10,46.669998,48.410000,46.549999,47.290001,47.082417,960200";
Lines[18]="2020-08-11,49.110001,50.849998,48.799999,48.910000,48.695301,1187700";
Lines[19]="2020-08-12,49.759998,50.009998,47.060001,47.840000,47.630001,752800";
Lines[20]="2020-08-13,46.950001,48.369999,46.459999,47.110001,47.110001,535700";
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)
{
this.setBarAlignmentFactor(0.5);
// 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);
}
}
我知道如何在图表上的某个点设置十字准线[ 注释掉:xCrosshair.setValue(1.5959952E12); ],但我不知道某个图表上某个日期的 [x,y] 值,因此我的问题是:在像我这里的 JFreeChart 上,如何获取一个的 [x,y] 值确定日期 [ 例如2020-07-29 ],或者如何获取第 N 个日期的 [x,y] 值 [ 例如第 9 个日期 = 2020-07-29 ] ?
NumberAxis
方法 java2DToValue()
狭隘地关注“如何获取特定日期的 [x,y] 值”这一问题
和 valueToJava2D()
说明了该方法。 CrosshairOverlayDemo1
is a concrete example, and the general principle is examined
幸运的是,您可以仅使用其数据突出显示图表元素。特别是,Annotation
、Marker
和 Crosshair
的实现都使用数据坐标。作为具体示例,让 PriceVolume_Chart
实现 ActionListener
并向图表的构造函数添加 javax.swing.Timer
:
public PriceVolume_Chart(String Symbol) {
…
Timer t = new Timer(500, this);
t.start();
}
然后,给定合适的 index
和听众,您会看到十字准线以 2 Hz 的频率连续移动到每个日期。
private int index;
@Override
public void actionPerformed(ActionEvent e) {
TimeSeriesDataItem item = Volume_Series.getDataItem(index);
xCrosshair.setValue(item.getPeriod().getFirstMillisecond());
item = Price_series.getDataItem(index);
yCrosshair.setValue(item.getValue().doubleValue());
index++;
if (index == Volume_Series.getItemCount()) {
index = 0;
}
}
您可以使用类似的侦听器在用户选择特定数据条目时移动您选择的指示器。确切的细节取决于您对观察者模式的实现,讨论 here. As you'll want to let both chart and table views share access to a common model, you may consider extending AbstractXYDataset
, shown here and implementing a Swing TableModel
; alternatively, consider extending AbstractTableModel
, shown here 和实现 XYDataset
接口。
好吧,@trashgod 鼓舞人心。我终于尝试了他的建议并找到了我想要的答案;这是基本的解决方案:
TimeSeriesDataItem itemX = Volume_Series.getDataItem(Index);
xCrosshair.setValue(itemX.getPeriod().getFirstMillisecond());
TimeSeriesDataItem itemY = Price_series.getDataItem(Index);
yCrosshair.setValue(itemY.getValue().doubleValue());
完整示例:
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.*;
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();
private int index;
public PriceVolume_Chart(String Symbol,int Index)
{
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.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);
}
});
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);
TimeSeriesDataItem itemX=Volume_Series.getDataItem(Index);
xCrosshair.setValue(itemX.getPeriod().getFirstMillisecond());
TimeSeriesDataItem itemY=Price_series.getDataItem(Index);
yCrosshair.setValue(itemY.getValue().doubleValue());
}
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,new SimpleDateFormat("yyyy-MM-d"),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);
MyRender Renderer=new MyRender();
Renderer.setShadowVisible(false);
plot.setRenderer(1,Renderer);
DateAxis domainAxis=(DateAxis) plot.getDomainAxis(); // Consider adjusting the lower margin of the domain axis for symmetry.
domainAxis.setLowerMargin(0.05);
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]="2020-07-17,44.110001,44.369999,41.919998,42.509998,42.323395,849700";
Lines[2]="2020-07-20,41.630001,41.680000,39.669998,40.119999,39.943886,1319300";
Lines[3]="2020-07-21,40.880001,42.860001,40.860001,42.270000,42.084450,2070300";
Lines[4]="2020-07-22,41.919998,42.700001,41.090000,42.570000,42.383133,1317600";
Lines[5]="2020-07-23,43.919998,46.389999,43.279999,44.759998,44.563519,1917700";
Lines[6]="2020-07-24,46.500000,46.500000,43.950001,44.410000,44.215057,1384600";
Lines[7]="2020-07-27,44.000000,44.240002,42.610001,43.860001,43.667469,799800";
Lines[8]="2020-07-28,43.389999,44.590000,42.930000,43.020000,42.831158,699700";
Lines[9]="2020-07-29,42.759998,45.590000,42.740002,45.430000,45.230579,826200";
Lines[10]="2020-07-30,44.160000,44.639999,42.959999,44.500000,44.304661,798100";
Lines[11]="2020-07-31,44.330002,44.419998,42.580002,44.360001,44.165276,1037800";
Lines[12]="2020-08-03,44.560001,45.599998,43.419998,44.939999,44.742729,797000";
Lines[13]="2020-08-04,44.900002,45.500000,43.450001,43.540001,43.348877,971100";
Lines[14]="2020-08-05,44.860001,45.389999,43.650002,45.330002,45.131020,902000";
Lines[15]="2020-08-06,45.049999,46.279999,44.330002,45.299999,45.101147,645200";
Lines[16]="2020-08-07,44.849998,46.189999,44.189999,46.150002,45.947418,604900";
Lines[17]="2020-08-10,46.669998,48.410000,46.549999,47.290001,47.082417,960200";
Lines[18]="2020-08-11,49.110001,50.849998,48.799999,48.910000,48.695301,1187700";
Lines[19]="2020-08-12,49.759998,50.009998,47.060001,47.840000,47.630001,752800";
Lines[20]="2020-08-13,46.950001,48.369999,46.459999,47.110001,47.110001,535700";
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",19);
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)
{
this.setBarAlignmentFactor(0.5);
// 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);
}
}