I am monitoring CPU, RAM and disk IO for a computational pipeline that consists of multiple steps to process the input data. I would like to visualize the hardware consumption for the entire pipeline but also per step of the pipeline.
In order to render hardware consumptions, I'd like to draw a combined plot with jFreeChart with four vertically aligned subplots that all have the same x-axis - time. The top plot is supposed to be a gantt plot to indicate the step of the pipeline. In the second plot I'd like to render the CPU consumption over time. In the third plot the RAM consumption, the bottom plot is for disk IO. The aim is to visually slide horizontally over the plot and see, which step of the pipeline uses how much CPU, RAM and disk.
I'm all set up in eclipse, jFreeChart library installed and all my data is imported and ready to render. All I need is some guidance for how to set up the combined chart up, cause my code blows up, when I try to combine the Gantt plot (which is a category plot) and the CPU plot (which is XY plot):
public JFreeChart createChart() {
final JFreeChart ganttChart = ChartFactory.createGanttChart(
"Gantt of Tasks", // chart title
"Task", // domain axis label
"Time", // range axis label
createGanttDataset(), // data
true, // include legend
true, // tooltips
false // urls
);
JFreeChart cpuChart = ChartFactory.createTimeSeriesChart(
"CPU load", // title
"Time", // x-axis label
"CPU Load", // y-axis label
createCpuDataset(), // data
true, // create legend?
true, // generate tooltips?
false // generate URLs?
);
// parent plot...
final CombinedRangeCategoryPlot plot = new CombinedRangeCategoryPlot();
plot.setGap(10.0);
// add the subplots...
plot.add(ganttChart.getCategoryPlot(), 1);
plot.add(cpuChart.getCategoryPlot(), 1);
plot.setOrientation(PlotOrientation.VERTICAL);
// return a new chart containing the overlaid plot...
return new JFreeChart("Combined GANTT/CPU plot",
JFreeChart.DEFAULT_TITLE_FONT, plot, true);
}
private IntervalCategoryDataset createGanttDataset() {
final TaskSeries taskSeries = new TaskSeries("Tasks");
for (Map.Entry<DoubleLong, String> entry : algoStartEndDates.entrySet()) {
final String taskTitle = entry.getValue();
final long startDate = entry.getKey().n1;
final long endDate = entry.getKey().n2;
taskSeries.add(new Task(taskTitle, new SimpleTimePeriod(startDate, endDate)));
}
final TaskSeriesCollection collection = new TaskSeriesCollection();
collection.add(taskSeries);
return collection;
}
private XYDataset createCpuDataset() {
final TimeSeries timeSeries = new TimeSeries("CPU load");
for (CollectlData data : collectlData) {
final RegularTimePeriod t = new Millisecond(new Date(data.getUtcTime()));
timeSeries.add(t, data.getCpuData().getTotal());
}
final TimeSeriesCollection dataset = new TimeSeriesCollection();
dataset.addSeries(timeSeries);
dataset.setDomainIsPointsInTime(true);
return dataset;
}
I would expect this to create a combined plot, but I'm receiving the error message:
Caused by: java.lang.ClassCastException: class org.jfree.chart.plot.XYPlot cannot be cast to class org.jfree.chart.plot.CategoryPlot (org.jfree.chart.plot.XYPlot and org.jfree.chart.plot.CategoryPlot are in unnamed module of loader 'app')
at org.jfree.chart.JFreeChart.getCategoryPlot(JFreeChart.java:824) ~[jfreechart-1.0.15.jar:1.0.15]
Related
I drew a 3D stacked bar chart using apache poi and I have trouble manipulating the font size of the left axis title and the legend of the chart. I have looked at the ooxml documentation found here but couldn't identify which object is responsible for this. I have added a piece of my code and the resulting chart, I appreciate any help directing me on the right path.
XSSFSheet sheet = wb.getSheetAt(0);
// Bar chart coordinates
XSSFDrawing drawing = sheet.createDrawingPatriarch();
XSSFClientAnchor anchor = drawing.createAnchor(0, 0, 0, 0, 0, 37, 16, 47);
XSSFChart chart = drawing.createChart(anchor);
// configure axis properties
XDDFCategoryAxis bottomAxis = chart.createCategoryAxis(AxisPosition.BOTTOM);
XDDFValueAxis leftAxis = chart.createValueAxis(AxisPosition.LEFT);
leftAxis.setTitle("Call Duration");
// font size for left axis labels (ticks)
leftAxis.getOrAddTextProperties().setFontSize(8d);
leftAxis.setCrosses(AxisCrosses.AUTO_ZERO);
leftAxis.setCrossBetween(AxisCrossBetween.BETWEEN);
XDDFChartData data = chart.createData(ChartTypes.BAR3D, bottomAxis, leftAxis);
// create legend
XDDFChartLegend legend = chart.getOrAddLegend();
legend.setPosition(LegendPosition.BOTTOM);
/*DATA ADDITION HERE*/
output bar chart
I have found which xml object addresses the font style of the the bar graph axis and legend. I did it basically by comparing the underlying xl/charts/chart1.xml generated by my java code and another bar graph that I had done with hand using ms excel. I had to do all this because, say for the value axis, apache poi's XDDFValueAxis object doesn't implement the axis title manipulation methods and org.openxmlformats.schemas.drawingml.x2006 API is needed. As I mentioned in my question the doc can be found here. The problem with the documentation is it's not apparent each objects functionality (at least for me), therefore, comparing and analyzing the xml objects gives insight to what actually some of the objects are responsible for. Here's my code piece and it has apache poi 4.1.2 and poi-ooxml-schemas-4.1.2 dependencies. Hope it'll be of help for someone with a similar problem.
XSSFSheet sheet = wb.getSheetAt(0);
// Bar chart coordinates
XSSFDrawing drawing = sheet.createDrawingPatriarch();
XSSFClientAnchor anchor = drawing.createAnchor(0, 0, 0, 0, 0, 38, 16, 47);
XSSFChart chart = drawing.createChart(anchor);
// configure axis properties
XDDFCategoryAxis bottomAxis = chart.createCategoryAxis(AxisPosition.BOTTOM);
bottomAxis.getOrAddTextProperties().setFontSize(6d);
XDDFValueAxis leftAxis = chart.createValueAxis(AxisPosition.LEFT);
leftAxis.setTitle("Call Duration");
leftAxis.setCrosses(AxisCrosses.AUTO_ZERO);
leftAxis.setCrossBetween(AxisCrossBetween.BETWEEN);
// font size for left axis labels (ticks)
leftAxis.getOrAddTextProperties().setFontSize(6d);
// create legend
XDDFChartLegend legend = chart.getOrAddLegend();
legend.setPosition(LegendPosition.BOTTOM);
// reflect the underlying the xml objects in order to access fields that are not implemented in apache poi
org.openxmlformats.schemas.drawingml.x2006.chart.CTValAx ctValAx = null;
org.openxmlformats.schemas.drawingml.x2006.chart.CTLegend ctLegend = null;
java.lang.reflect.Field ctValRef;
java.lang.reflect.Field chartLegendRef;
try {
ctValRef = XDDFValueAxis.class.getDeclaredField("ctValAx");
ctValRef.setAccessible(true);
ctValAx = (org.openxmlformats.schemas.drawingml.x2006.chart.CTValAx) ctValRef.get(leftAxis);
chartLegendRef = XDDFChartLegend.class.getDeclaredField("legend");
chartLegendRef.setAccessible(true);
ctLegend = (org.openxmlformats.schemas.drawingml.x2006.chart.CTLegend) chartLegendRef.get(legend);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
// set title properties for left axis
CTTitle ctTitle = ctValAx.getTitle();
ctTitle.getTx().getRich().getPArray(0).getRArray(0).getRPr().setSz(600);
// adjust the font size of the legend
ctLegend.addNewTxPr();
ctLegend.getTxPr().addNewBodyPr();
ctLegend.getTxPr().addNewLstStyle(); // font size in hundreds format (6*100)
ctLegend.getTxPr().addNewP().addNewPPr().addNewDefRPr().setSz(600);
/*DATA ADDITION HERE*/
At least for the legend's font size setting using org.apache.poi.xddf.usermodel.text.XDDFTextBody is possible. That has the advantage that this is a high level apache poi class which gets developed further. So if you have XDDFChartLegend legend, then construct a XDDFTextBody from this and use this for font settings.
Example:
...
XDDFChartLegend legend = chart.getOrAddLegend();
legend.setPosition(LegendPosition.BOTTOM);
XDDFTextBody legendTextBody = new XDDFTextBody(legend);
legendTextBody.getXmlObject().addNewBodyPr();
legendTextBody.addNewParagraph().addDefaultRunProperties().setFontSize(8d);
legend.setTextBody(legendTextBody);
...
For axis font settings using the low level org.openxmlformats.schemas.drawingml.x2006.chart.CTValAx or org.openxmlformats.schemas.drawingml.x2006.chart.CTCatAx is necessary. But there also is org.apache.poi.xddf.usermodel.chart.XDDFTitle which is the high level wrapper for CTValAx-title or CTCatAx-title. So we should using that instead of directly using the CT* classes.
Example:
Do having XDDFTitle getOrSetAxisTitle methods:
private static XDDFTitle getOrSetAxisTitle(XDDFValueAxis axis) {
try {
java.lang.reflect.Field _ctValAx = XDDFValueAxis.class.getDeclaredField("ctValAx");
_ctValAx.setAccessible(true);
org.openxmlformats.schemas.drawingml.x2006.chart.CTValAx ctValAx =
(org.openxmlformats.schemas.drawingml.x2006.chart.CTValAx)_ctValAx.get(axis);
if (!ctValAx.isSetTitle()) {
ctValAx.addNewTitle();
}
XDDFTitle title = new XDDFTitle(null, ctValAx.getTitle());
return title;
} catch (Exception ex) {
ex.printStackTrace();
return null;
}
}
private static XDDFTitle getOrSetAxisTitle(XDDFCategoryAxis axis) {
try {
java.lang.reflect.Field _ctCatAx = XDDFCategoryAxis.class.getDeclaredField("ctCatAx");
_ctCatAx.setAccessible(true);
org.openxmlformats.schemas.drawingml.x2006.chart.CTCatAx ctCatAx =
(org.openxmlformats.schemas.drawingml.x2006.chart.CTCatAx)_ctCatAx.get(axis);
if (!ctCatAx.isSetTitle()) {
ctCatAx.addNewTitle();
}
XDDFTitle title = new XDDFTitle(null, ctCatAx.getTitle());
return title;
} catch (Exception ex) {
ex.printStackTrace();
return null;
}
}
Then do using those as so:
...
XDDFCategoryAxis bottomAxis = chart.createCategoryAxis(AxisPosition.BOTTOM);
//bottomAxis.setTitle("...");
XDDFTitle title = getOrSetAxisTitle(bottomAxis);
title.setOverlay(false);
title.setText("...");
title.getBody().getParagraph(0).addDefaultRunProperties().setFontSize(8d);
bottomAxis.getOrAddTextProperties().setFontSize(8d);
XDDFValueAxis leftAxis = chart.createValueAxis(AxisPosition.LEFT);
//leftAxis.setTitle("...");
title = getOrSetAxisTitle(leftAxis);
title.setOverlay(false);
title.setText("...");
title.getBody().getParagraph(0).addDefaultRunProperties().setFontSize(8d);
leftAxis.setCrosses(AxisCrosses.AUTO_ZERO);
leftAxis.setCrossBetween(AxisCrossBetween.BETWEEN);
leftAxis.getOrAddTextProperties().setFontSize(8d);
...
I'm currently using JFreeChart to create a line chart. This line chart gets updated every second with a new value (currently a random value). This way you can see how your data has changed over a certain time period. However after I've added over ten values they don't fit on the line anymore turning into dots. I would like to only have 5 values shown at a time which are spread across the entire timings. This is how it looks now:
Notice the dots at the bottom of the chart. I would like it to be changed to this:
Note that I want to keep all points which are created between these points in time. So data from 11:31:00, 11:31:01, 11:31:02 etc. should be still there.
This is what I currently have:
LocalDateTime date = LocalDateTime.now();
category = new DefaultCategoryDataset();
category.addValue(new Random().nextInt(10), "Data", date.getHour() + ":" + date.getMinute() + ":" + date.getSecond());
chart = ChartFactory.createLineChart("Values", "Time", "Data", category, PlotOrientation.VERTICAL, false, true, false);
((NumberAxis) ((CategoryPlot) chart.getPlot()).getRangeAxis()).setStandardTickUnits(NumberAxis.createIntegerTickUnits());
I got it by using a TimeSeriesChart. This is what I ended up with:
TimeSeriesCollection collection = new TimeSeriesCollection();
TimeSeries serie = new TimeSeries("Data");
collection.addSeries(serie);
JFreeChart chart = ChartFactory.createTimeSeriesChart("Data", "Time", "Data", collection, false, true, false);
I am learning java and I am trying to build an app. I'm stuck with this one last part of the application and was hoping some of you may be able to help me. The application stores values in a database and, upon the users request, it will retrieve the data and plot this data on a line graph. The application is producing a line graph, but my issue is that it is producing one line graph for each piece of data that it retrieves from the database. So if the query returns 15 results, the application produces 15 graphs with one plot each. I want all of the data retrieved and plotted onto one graph. Below is my code. Can someone point me in the right direction?
try
{
Connection con = new DataConnection().connect();
ResultSet rs;
PreparedStatement retrieve = con.prepareStatement("SELECT row FROM table");
rs = retrieve.executeQuery();
while (rs.next())
{
String string = rs.getString(1);
double double = Double.parseDouble(string);
DefaultCategoryDataset dataset = new DefaultCategoryDataset();
dataset.addValue(double, "Chart", "Data");
JFreeChart chart = ChartFactory.createLineChart("Graph", "Data", "Data", dataset, PlotOrientation.VERTICAL, true, false, false);
CategoryPlot p = chart.getCategoryPlot();
p.setRangeGridlinePaint(Color.black);
ChartFrame frame = new ChartFrame("Line Chart", chart);
frame.setVisible(true);
frame.setSize(450, 350);
}
}
So if the query returns 15 results, the application produces 15 graphs
with one plot each. I want all of the data retrieved and plotted onto
one graph.
Your code creates a new JFreeChart instance for each row in your database query result set and that's why you get too many frames with charts opened. You have to create just a single JFreeChart instance and add the data to its model as a series.
See this related Q&A: Multiple graphs in multiple figures using jFreeChart. There are also plenty of examples under jfreechart tag.
Some background info: I'm creating a system to register lost, found and returned luggage. The amount of those 3 variable need to be in a LineChart to get a nice overview from how much luggage is lost etc. over an amount of time.
Now the problem:
When I create a LineChart and add it to a ChartPanel the LineChart represent a dataset. I've heard that when you edit/update your dataset, the chart is also automatically updated. Well I have an update button next to the chart which has to update the chart when clicked. Independent of the chart in an already existing JPanel/ChartPanel I also create a new frame which represent a ChartFrame.
When I click the update button a new frame pops-up and with the most recent data from the updated dataset. When I click it again, obviously another frame is created, but the already existing frame is also update while the chart in the JPanel isn't though they use the same dataset which is static and comes from a LineChart model.
Well here some code:
LineChartModel
//At the top of the document the dataset is initialize static
public static DefaultCategoryDataset dataset = null;
/*****************Other code omitted***********************/
/**
*
* Updates an already existing dataset
*
* #param dataset
* #return dataset
*/
public CategoryDataset updateDataset(DefaultCategoryDataset dataset) {
this.dataset = dataset;
// row keys...
final String rowLost = "Lost";
final String rowFound = "Found";
final String rowReturned = "Returned";
//Don't pay attention to this. It's setting the value for the dataset from different arrays which I know of the are filled correctly
int i = 0;
while (i < 12) {
dataset.setValue(lost[i], rowLost, type[i]);
System.out.println("There were " + lost[i]
+ " lost luggages in month: " + type[i]);
i++;
}
for (int j = 0; j < 12; j++) {
dataset.setValue(found[j], rowFound, type[j]);
System.out.println("There were " + found[j]
+ " found luggages in month: " + type[j]);
}
for (int j = 0; j < 12; j++) {
dataset.setValue(returned[j], rowReturned, type[j]);
System.out.println("There were " + returned[j]
+ " returned luggages in month: " + type[j]);
}
return dataset;
}
The LineChart Class with his constructor
LineChartModel model = new LineChartModel();
public ChartPanel chartPanel;
/**
* Creates a new demo.
*
* #param title the frame title.
*/
public LineChart(final String title, LineChartModel m) {
m = model;
m.selectRange();
m.createDataset();
final JFreeChart chart = createChart(m.dataset);
chartPanel = new ChartPanel(chart);
chartPanel.setPreferredSize(new Dimension(500, 270));
}
ChartController
First the params of the constructor
public ChartController(final ManCharts v, final LineChartModel m) {
view = v;
model = m;
And here the actionlistener of the button.
//Select a range by sumbitting the variables
v.date.btnSubmit.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
//selectRange select all the data between 2 dates but isn't important for this problem
m.selectRange(/*v.date.dateChooserFrom, v.date.dateChooserTo*/);
//updateDataset updates the dataset from the LineChartModel which is static
m.updateDataset(m.dataset);
//The data in the chart should already be updated but here I'm trying to replace the current chart by a new one
v.chart.chartPanel = new ChartPanel(v.chart.createChart(m.dataset));
//This is the new chart which does automatically update when the button is pressed
JFreeChart chart = ChartFactory.createLineChart("Something chart", "Date", "Value", m.dataset);
ChartFrame frame = new ChartFrame("New line chart", chart);
frame.setVisible(true);
}
});
I really hope someone can help and that this problem is not too complicated without having the full code.
If you need more code or something. Just say so.
Thanks in advance!
EDIT!
I think it has to do something with how I create the chart. The chart in my JPanel which is created at the start of my application is created with an edited method (this is part of my LineChart class and stands below my constructor):
/**
* Creates a sample chart.
*
* #param dataset a dataset.
*
* #return The chart.
*/
public JFreeChart createChart(final CategoryDataset dataset) {
// create the chart...
final JFreeChart chart = ChartFactory.createLineChart(
"Luggage", // chart title
"Time", // domain axis label
"Value", // range axis label
dataset, // data
PlotOrientation.VERTICAL, // orientation
true, // include legend
true, // tooltips
false // urls
);
chart.setBackgroundPaint(Color.decode("#d6d9df"));
final CategoryPlot plot = (CategoryPlot) chart.getPlot();
plot.setBackgroundPaint(Color.lightGray);
plot.setRangeGridlinePaint(Color.white);
// customise the range axis...
final NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis();
rangeAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
rangeAxis.setAutoRangeIncludesZero(true);
// customise the renderer...
final LineAndShapeRenderer renderer = (LineAndShapeRenderer) plot.getRenderer();
//renderer.setDrawShapes(true);
renderer.setSeriesStroke(
0, new BasicStroke(
2.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND,
1.0f, new float[] {10.0f, 6.0f}, 0.0f
)
);
renderer.setSeriesStroke(
1, new BasicStroke(
2.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND,
1.0f, new float[] {6.0f, 6.0f}, 0.0f
)
);
renderer.setSeriesStroke(
2, new BasicStroke(
2.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND,
1.0f, new float[] {2.0f, 6.0f}, 0.0f
)
);
// OPTIONAL CUSTOMISATION COMPLETED.
return chart;
}
SOLVED
It had to do with another dataset that was created when clicking the submit button. So I've fixed the recreation and it's working now. Not really a chart problem, but just a problem in my model. Thanks for the help anyway!
ChartPanel implements an event listener that prompts it to repaint() itself when needed; JPanel does not. See the implementation of ChartChangeListener in chartChanged(), for example. When debugging, look for one instance of ChartPanel that shadows another or an errant use of JPanel.
Addednum Does the frame also need to be an ApplicationFrame or could it work in a JFrame?
Either is acceptable, as shown in this JFrame example or this ApplicationFrame example; both update dynamically. Note that Swing GUI objects should be constructed and manipulated only on the event dispatch thread.
I need to draw hysteresis loops and then calculate the area closed within the loop. I am using jFreeChart.
consider the following data:
hyst[0]=0;
hyst[1]=0;
hyst[2]=0.0098;
hyst[3]=0.0196;
hyst[4]=0.0489;
hyst[5]=0.0879;
hyst[6]=0.0684;
hyst[7]=0.0489;
hyst[8]=0.0196;
hyst[9]=0.0098;
hyst[10]=0;
hyst[11]=0;
hyst[12]=0;
hyst[13]=0;
hyst[14]=0;
hyst[15]=-0.0195;
hyst[16]=-0.0488;
hyst[17]=-0.0391;
hyst[18]=-0.0195;
hyst[19]=0;
hyst[20]=0;
When I try :
public void plotHysteresis()
{
int j=0;
int i=0;
XYSeries series1 = new XYSeries("Before Treatment");
// DefaultCategoryDataset series1 = new DefaultCategoryDataset();
for(i=0;i<6;i++)
{
series1.add(j,hyst[i]);
logTextArea.append(Integer.toString(j) +" : " +Double.toString(hyst[i])+"\n");
j=j+5;
}
j=j-5;
for(;i<11;i++)
{
j=j-5;
series1.add(j,hyst[i]);
logTextArea.append(Integer.toString(j) +" : " +Double.toString(hyst[i])+"\n");
}
for(;i<16;i++)
{
j=j-5;
series1.add(j,hyst[i]);
logTextArea.append(Integer.toString(j) +" : " +Double.toString(hyst[i])+"\n");
}
for(;i<21;i++)
{
j=j+5;
series1.add(j,hyst[i]);
logTextArea.append(Integer.toString(j) +" : " +Double.toString(hyst[i])+"\n");
}
XYSeriesCollection dataset = new XYSeriesCollection();
dataset.addSeries(series1);
JFreeChart chart = ChartFactory.createXYAreaChart(
"Hysteresis Plot", // chart title
"Pounds (lb)", // x axis label
"Distance (inches)", // y axis label
dataset, // data
PlotOrientation.VERTICAL,
true, // include legend
true, // tooltips
false // urls
);
chart.setBackgroundPaint(Color.white);
ChartPanel frame = new ChartPanel(chart);
frame.setVisible(true);
frame.setSize(plotPanel.getWidth(),plotPanel.getHeight());
plotPanel.add(frame);
plotPanel.repaint();
}
It gives me below result:
If I use :
JFreeChart chart = ChartFactory.createXYLineChart(
"Hysteresis Plot", // chart title
"Pounds (lb)", // x axis label
"Distance (inches)", // y axis label
dataset, // data
PlotOrientation.VERTICAL,
true, // include legend
true, // tooltips
false // urls
);
I gives:
I need a hysteresis plot that looks like:
I guess the difference is the way points are being connected. Please guide how to obtained the desired hysteresis loop with jFreeChart and then how to calculate area enclosed.
Thanks
How can I change the line color as well the symbols representing the data points. I want all of them to be uniform.
It appears you've settled on JFreeChart for your view. Synthesizing a few other comments,
You can make the colors and shapes of your several series homogeneous by providing a DrawingSupplier, as suggested here and shown here.
You can combine the series into a GeneralPath and estimate the area as outlined here.