The code below plots a graph with unwanted vertical gray areas (stripes) corresponding with alternate domain ticks.
I have tried unsuccessfully to remove them from the graph to obtain a plot with white background.
I have been searching through the methods of XYPlot or NumberAxis (last try was setting to null xyplot.setDomainTickBandPaint(null); and xyplot.setRangeTickBandPaint(null);), but I have not experience enough with JFreeChart to know what method to use.
This is the code for the above graph:
public class MyPlotChart {
private static Color MetalColor = new Color(255, 152, 0);
static double[] yData = new double[] { 49.68, 49.18, 49.78, 49.65, 48.94, 50.02, 50.27};
static String[] labels = new String[] { "2021-10-28", "2021-10-29", "2021-11-01", "2021-11-02", "2021-11-03", "2021-11-04", "2021-11-05"};
public static void plot(String metal, int samples) throws IOException {
XYSeries series = new XYSeries(metal);
int i = 0;
for (i = 0; i < yData.length; i++) {
series.add(i, yData[i]);
}
XYDataset dataset = new XYSeriesCollection(series);
NumberAxis domain = new SymbolAxis(null, labels);
NumberAxis verticalAxis = new NumberAxis(null);
verticalAxis.setAutoRangeIncludesZero(false);
domain.setTickUnit(new NumberTickUnit(1.0));
domain.setMarkerBand(null);
double vericalTickUnit = (series.getMaxY() - series.getMinY()) / 5;
NumberFormat numberFormat = NumberFormat.getInstance(Locale.getDefault());
numberFormat.setRoundingMode(RoundingMode.HALF_DOWN);
numberFormat.setMinimumFractionDigits(2);
numberFormat.setMaximumFractionDigits(2);
NumberTickUnit nt = new NumberTickUnit(vericalTickUnit, numberFormat);
verticalAxis.setTickUnit(nt);
verticalAxis.setAutoRange(true);
verticalAxis.setRange(new Range(series.getMinY()-0.1, series.getMaxY()+0.1));
verticalAxis.setTickMarksVisible(true);
verticalAxis.setTickMarkInsideLength(3f);
XYSplineRenderer r = new XYSplineRenderer(10);
r.setSeriesPaint(0, MetalColor);
r.setDefaultShapesVisible(false);
r.setSeriesStroke(0, new BasicStroke(3.0f));
XYPlot xyplot = new XYPlot(dataset, domain, verticalAxis, r);
xyplot.getDomainAxis().setVerticalTickLabels(true);
xyplot.setDomainGridlinesVisible(false);
xyplot.setBackgroundImage(null);
xyplot.setBackgroundPaint(Color.WHITE);
Font font = xyplot.getDomainAxis().getTickLabelFont();
Font fontnew = new Font(font.getName(), Font.BOLD, 14);
xyplot.getDomainAxis().setTickLabelFont(fontnew);
xyplot.getRangeAxis().setTickLabelFont(fontnew);
JFreeChart chart = new JFreeChart(xyplot);
chart.removeLegend();//Remove legend
chart.setBackgroundPaint(Color.WHITE);
String fileName = "myChart"+metal+samples+"TEST.png";
ChartUtils.saveChartAsPNG(new File(fileName), chart, 600, 600);
}
public static void main(String[] args) throws IOException {
MyPlotChart.plot("metal", 7);
}
}
As suggested in the comment, I opted to use DateAxis which do not implement alternating background and also gives more accurate treatment for tick labels when the data is time related.
I have attached the code and the plot obtained:
public class MyPlotChart {
private static Color MetalColor = new Color(255, 152, 0);
static double[] yData = new double[] { 49.68, 49.18, 49.78, 49.65, 48.94, 50.02, 50.27 };
static String[] labels = new String[] { "2021-10-28", "2021-10-29", "2021-11-01", "2021-11-02", "2021-11-03",
"2021-11-04", "2021-11-05" };
public static void plot(String metal, int samples) throws IOException, ParseException {
SimpleDateFormat dateformatyyyy_MM_dd = new SimpleDateFormat("yyyy-MM-dd");
SimpleDateFormat dateformatdd_MM_yyyy = new SimpleDateFormat("dd-MM-yyyy");
XYSeries series = new XYSeries(metal);
for (int i = 0; i < yData.length; i++) {
Date date = dateformatyyyy_MM_dd.parse(labels[i]);
series.add(date.getTime(), yData[i]);
}
//Configure Vertical Axis
NumberAxis verticalAxis = new NumberAxis(null);
NumberFormat numberFormat = NumberFormat.getInstance(Locale.getDefault());
numberFormat.setRoundingMode(RoundingMode.HALF_DOWN);
numberFormat.setMinimumFractionDigits(2);
numberFormat.setMaximumFractionDigits(2);
double vericalTickUnit = (series.getMaxY() - series.getMinY()) / 7;
NumberTickUnit nt = new NumberTickUnit(vericalTickUnit, numberFormat);
verticalAxis.setTickUnit(nt);
double percentOverRange = 0.05;// 2%
double initalRange = series.getMaxY() - series.getMinY();
double increase = initalRange * percentOverRange;
verticalAxis.setRange(new Range(series.getMinY()-increase, series.getMaxY()+increase));
verticalAxis.setAutoRange(true);
verticalAxis.setAutoRangeIncludesZero(false);
verticalAxis.setTickMarksVisible(true);
verticalAxis.setTickMarkInsideLength(3f);
//Configure Domain Axis
DateAxis domainAxis = new DateAxis(null);
domainAxis.setTickUnit(new DateTickUnit(DateTickUnitType.DAY, 1, dateformatdd_MM_yyyy));
//Configure Renderer
XYSplineRenderer r = new XYSplineRenderer(10);
r.setSeriesPaint(0, MetalColor);
r.setDefaultShapesVisible(false);
r.setSeriesStroke(0, new BasicStroke(3.0f));
XYDataset dataset = new XYSeriesCollection(series);
XYPlot xyplot = new XYPlot(dataset, domainAxis, verticalAxis, r);
xyplot.getDomainAxis().setVerticalTickLabels(true);
xyplot.setDomainGridlinesVisible(false);
xyplot.setBackgroundImage(null);
xyplot.setBackgroundPaint(Color.WHITE);
Font font = xyplot.getDomainAxis().getTickLabelFont();
Font fontnew = new Font(font.getName(), Font.BOLD, 14);
xyplot.getDomainAxis().setTickLabelFont(fontnew);
xyplot.getRangeAxis().setTickLabelFont(fontnew);
JFreeChart chart = new JFreeChart(xyplot);
chart.removeLegend();// Remove legend
chart.setBackgroundPaint(Color.WHITE);
String fileName = "myChart" + metal + samples + "TEST.png";
ChartUtils.saveChartAsPNG(new File(fileName), chart, 600, 600);
}
public static void main(String[] args) throws IOException, ParseException {
MyPlotChart.plot("metal", 7);
}
}
Related
I want to disable the right click menu of JFreeChart.
I tried chartPanel.setPopupMenu(null), but it didn't work.
The following example creates a simple XYPlot with "panel.setPopupMenu( null )" disabling the popup Menu.
`
public class DisableChartPopupMenu extends ApplicationFrame {
public DisableChartPopupMenu(String title) {
super(title);
}
public static void main(final String[] args) {
(new DisableChartPopupMenu("example")).createChartNoPopupMenu();
}
public void createChartNoPopupMenu(){
final XYSeries series1 = new XYSeries("Series 1");
series1.add(10.0, 12353.3);
series1.add(20.0, 13734.4);
series1.add(30.0, 14525.3);
series1.add(40.0, 13984.3);
final XYSeriesCollection collection = new XYSeriesCollection();
collection.addSeries(series1);
final XYItemRenderer renderer1 = new StandardXYItemRenderer();
final NumberAxis rangeAxis1 = new NumberAxis("Range 1");
final XYPlot subplot1 = new XYPlot( collection, null, rangeAxis1, renderer1);
final CombinedDomainXYPlot plot = new CombinedDomainXYPlot(new NumberAxis("Domain"));
plot.add(subplot1, 1);
JFreeChart chart = new JFreeChart(" Demo", JFreeChart.DEFAULT_TITLE_FONT, plot, true);
final ChartPanel panel = new ChartPanel(chart, true, true, true, false, true);
panel.setPopupMenu( null );
setContentPane(panel); pack(); setVisible(true);
}
}
`
I'm using CombinedDomainXYPlot to plot the charts. I have another requirement where, I need to show the two charts horizontally.
Currently i'm having only one chart. what i need is, i need two charts in the first row.
like Chart1 Chart2
Code:
CombinedDomainXYPlot plot = new CombinedDomainXYPlot();
plot.add(getChart1(), 2);
plot.add(getChart2(), 2);
It is giving only one chart in the first row. and second chart2 in the another row.
Is there any way I can make these two charts into single row?
Edit: Actually I wanted it like your ThermometerDemo example. For that you have used JPanel, but here I'm using JFrame.
I wanted it like your ThermometerDemo example.
Based on this example, the code below adds two panels to a GridLayout(1, 0). Each panel includes it's own chart and control panel.
import java.awt.*;
import java.awt.event.ActionEvent;
import java.util.Random;
import javax.swing.*;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.plot.CombinedDomainXYPlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.StandardXYItemRenderer;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
/**
* #see https://stackoverflow.com/a/20243624/230513
* #see https://stackoverflow.com/q/11870416/230513
*/
public class CombinedPlot {
private static final int MAX = 3;
private static final Random rand = new Random();
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
init();
}
});
}
private static void init() {
JFrame frame = new JFrame("Combined Plot Example");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new GridLayout(1, 0));
frame.add(createPanel());
frame.add(createPanel());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
private static JPanel createPanel() {
JPanel p = new JPanel(new BorderLayout());
XYItemRenderer renderer = new StandardXYItemRenderer();
XYPlot plot1 = new XYPlot(
generateData(), null, new NumberAxis("Range 1"), renderer);
XYPlot plot2 = new XYPlot(
generateData(), null, new NumberAxis("Range 2"), renderer);
final CombinedDomainXYPlot plot
= new CombinedDomainXYPlot(new NumberAxis("Domain"));
plot.add(plot1);
plot.add(plot2);
plot.setOrientation(PlotOrientation.VERTICAL);
JFreeChart chart = new JFreeChart(
"Combined Plots", JFreeChart.DEFAULT_TITLE_FONT, plot, false);
ChartPanel chartPanel = new ChartPanel(chart) {
#Override
public Dimension getPreferredSize() {
return new Dimension(320, 320);
}
};
JPanel controlPanel = new JPanel();
controlPanel.add(new JButton(new UpdateAction(plot, 0)));
controlPanel.add(new JButton(new UpdateAction(plot, 1)));
p.add(chartPanel, BorderLayout.CENTER);
p.add(controlPanel, BorderLayout.SOUTH);
return p;
}
private static class UpdateAction extends AbstractAction {
private final XYPlot plot;
public UpdateAction(CombinedDomainXYPlot plot, int i) {
super("Update plot " + (i + 1));
this.plot = (XYPlot) plot.getSubplots().get(i);
}
#Override
public void actionPerformed(ActionEvent e) {
plot.setDataset(CombinedPlot.generateData());
}
}
private static XYSeriesCollection generateData() {
XYSeriesCollection data = new XYSeriesCollection();
for (int i = 0; i < MAX; i++) {
data.addSeries(generateSeries("Series " + (i + 1)));
}
return data;
}
private static XYSeries generateSeries(String key) {
XYSeries series = new XYSeries(key);
for (int i = 0; i < 16; i++) {
series.add(rand.nextGaussian(), rand.nextGaussian());
}
return series;
}
}
I have an array of 100,000 samples all of double type. I want to display or plot this array so that I get a moving chart/ plot (dynamic) instead of displaying it at once. Can anyone help me out. In plot ee[] and y[] is obtained after some processing.
private byte[] FileR(String filename) {
byte[] data = null;
AudioInputStream ais;
try {
File fileIn = new File(filename);
if (fileIn.exists()) {
ais = AudioSystem.getAudioInputStream(fileIn);
data = new byte[ais.available()];
ais.read(data);
}
} catch (UnsupportedAudioFileException | IOException e) {
System.out.println(e.getMessage());
throw new RuntimeException("Could not read " + filename);
}
return data;
}
private byte[] Capture(double t) throws LineUnavailableException {
AudioFormat format = new AudioFormat(48000, 16, 2, true, false);
DataLine.Info info = new DataLine.Info(TargetDataLine.class, format);
line = (TargetDataLine) AudioSystem.getLine(info);
line.open(format);
line.open();
int size = (int) (line.getBufferSize() * t);
byte[] b = new byte[size];
line.start();
line.read(b, 0, size);
return b;
}
private void plot(double[] ee, double[] y) {
XYSeries see = new XYSeries("Filtered");
for (int i = 0; i < ee.length; i++) {
see.add(i, ee[i]);
}
XYSeriesCollection cee = new XYSeriesCollection();
cee.addSeries(see);
XYItemRenderer ree = new StandardXYItemRenderer();
NumberAxis rangeAxisee = new NumberAxis("Filtered");
XYPlot subplot1 = new XYPlot(cee, null, rangeAxisee, ree);
subplot1.setRangeAxisLocation(AxisLocation.BOTTOM_OR_LEFT);
XYSeries sy = new XYSeries("Noisy");
for (int i = 0; i < y.length; i++) {
sy.add(i, y[i]);
}
XYSeriesCollection cy = new XYSeriesCollection();
cy.addSeries(sy);
XYItemRenderer ry = new StandardXYItemRenderer();
NumberAxis rangeAxisy = new NumberAxis("Noisy");
XYPlot subplot2 = new XYPlot(cy, null, rangeAxisy, ry);
subplot2.setRangeAxisLocation(AxisLocation.BOTTOM_OR_LEFT);
CombinedDomainXYPlot plot = new CombinedDomainXYPlot(new NumberAxis("Domain"));
plot.setGap(10.0);
plot.add(subplot1);
plot.add(subplot2);
plot.setOrientation(PlotOrientation.VERTICAL);
JFreeChart chart = new JFreeChart("Adaptive Filter", JFreeChart.DEFAULT_TITLE_FONT, plot, true);
panel = new ChartPanel(chart, true, true, true, false, true);
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(750, 500);
frame.add(panel, BorderLayout.CENTER);
frame.setVisible(true);
}
You need to have a thread where all this data is coming from. For example from your backend. Then, every time there is a new set of data for the chart you will need to update the chart via the Event Dispatch Thread. If your chart data is coming in regular intervals it is fairly easy (ie. pull), however if it is push (ie. the data is more random), and can get a little more tricky.
Remove all the GUI creation out of the plot method :
JFreeChart chart = new JFreeChart("Adaptive Filter", JFreeChart.DEFAULT_TITLE_FONT, plot, true);
panel = new ChartPanel(chart, true, true, true, false, true);
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(750, 500);
frame.add(panel, BorderLayout.CENTER);
frame.setVisible(true);
This only needs to be called once. The plot method will be called every time new data comes.
Here is a simple approach :
public void startCharting() {
final MySoundCard card = new MySoundCard();
final MyJFreeChart chart = new MyJFreeChart();
Runnable r = new Runnable() {
#Override
public void run() {
while(true) {
int[] i = card.FileR();
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
chart.plot();
}
});
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
Thread t = new Thread(r);
t.start();
}
A thread calls your datasource every second and then updates the chart. The updates are invoked in the Event Dispatch Thread.
how to customize the colors of JFreeChart graphic.
lets see my java code :
private StreamedContent chartImage ;
public void init(){
JFreeChart jfreechart = ChartFactory.createPieChart("title", createDataset(), true, true, false);
File chartFile = new File("dynamichart");
ChartUtilities.saveChartAsPNG(chartFile, jfreechart, 375, 300);
chartImage = new DefaultStreamedContent(new FileInputStream( chartFile), "image/png");
}
public PieDataset createDataset() {
DefaultPieDataset dataset = new DefaultPieDataset();
dataset.setValue("J-2", 10);
dataset.setValue("J-1", 15);
dataset.setValue("J", 50);
dataset.setValue("J+1", 20);
dataset.setValue("J+2", 15);
return dataset;
}
html page :
<p:graphicImage id="MyImage" value="#{beanCreateImage.chartImage}" />
You can change the color of single pieces like this:
JFreeChart chart = ChartFactory.createPieChart("title", createDataset(), true, true, false);
PiePlot plot = (PiePlot) chart.getPlot();
plot.setSectionPaint("J+1", Color.black);
plot.setSectionPaint("J-1", new Color(120, 0, 120));
// or do this, if you are using an older version of JFreeChart:
//plot.setSectionPaint(1, Color.black);
//plot.setSectionPaint(3, new Color(120, 0, 120));
So with your code, all the pies are colored automatically, after my code changes, the J-1 and J+1 have a fixed color, the rest gets automatically colored.
To set the colous for a chart you can implement the DrawingSupplier inferface in this case I've used DefaultDrawingSupplier:
public class ChartDrawingSupplier extends DefaultDrawingSupplier {
public Paint[] paintSequence;
public int paintIndex;
public int fillPaintIndex;
{
paintSequence = new Paint[] {
new Color(227, 26, 28),
new Color(000,102, 204),
new Color(102,051,153),
new Color(102,51,0),
new Color(156,136,48),
new Color(153,204,102),
new Color(153,51,51),
new Color(102,51,0),
new Color(204,153,51),
new Color(0,51,0),
};
}
#Override
public Paint getNextPaint() {
Paint result
= paintSequence[paintIndex % paintSequence.length];
paintIndex++;
return result;
}
#Override
public Paint getNextFillPaint() {
Paint result
= paintSequence[fillPaintIndex % paintSequence.length];
fillPaintIndex++;
return result;
}
}
Then include this code in your `init()' method
JFreeChart jfreechart = ChartFactory.createPieChart("title", createDataset(), true, true, false);
Plot plot = jfreechart.getPlot();
plot.setDrawingSupplier(new ChartDrawingSupplier());
...
You can customize the colors according to the labels while getting the data from the dataset:
// Add custom colors
PiePlot plot = (PiePlot) chart.getPlot();
for (int i = 0; i < dataset.getItemCount(); i++) {
if(dataset.getKey(i).equals("J+1")){
plot.setSectionPaint(i, Color.black);
}
}
You can also use a switch-case statement or the one you prefer.
I have code that hides/shows a series when the LegendItemEntity is clicked. The code works, except I use the index of the series as the index of the LegendItem in the LegendItemCollection. The problem is that sometimes there are more series than LegendItems and their respective indices are not the same. How do I get the LegendItem given that I have the LegendItemEntity?
private final ChartMouseListener chartMouseListener = new ChartMouseListener() {
public void chartMouseClicked(ChartMouseEvent cme) {
ChartEntity entity = cme.getEntity();
if (entity instanceof LegendItemEntity) {
LegendItemEntity itemEntity = (LegendItemEntity) entity;
int index = dataset.getSeriesIndex(itemEntity.getSeriesKey());
XYPlot plot = (XYPlot) chart.getPlot();
XYItemRenderer renderer = plot.getRenderer();
renderer.setSeriesVisible(index, !renderer.isSeriesVisible(index));
LegendItem legendItem = plot.getLegendItems().get(index);
if (renderer.isSeriesVisible(index)) {
legendItem.setLinePaint(renderer.getSeriesPaint(index));
} else {
legendItem.setLinePaint(Color.WHITE);
}
}
}
public void chartMouseMoved(ChartMouseEvent cme) {
}
};
You can use renderer.getLegendItem(). The result will be null for a non-visible series. Given a JFreeChart chart, a chartMouseClicked() implementation might look like this:
#Override
public void chartMouseClicked(ChartMouseEvent e) {
ChartEntity entity = e.getEntity();
if (entity instanceof LegendItemEntity) {
LegendItemEntity itemEntity = (LegendItemEntity) entity;
Comparable key = itemEntity.getSeriesKey();
XYDataset dataset = (XYDataset) itemEntity.getDataset();
int seriesIndex = dataset.indexOf(key);
XYPlot plot = (XYPlot) chart.getPlot();
int datasetIndex = plot.indexOf(dataset);
XYItemRenderer renderer = plot.getRenderer();
LegendItem legendItem = renderer.getLegendItem(datasetIndex, seriesIndex);
System.out.println(key + " " + datasetIndex + " " + seriesIndex);
System.out.println(legendItem);
}
}