Rendering a chart over several days, with a dataset that has 24 hour data, but it's only useful during M-F, 7AM to 5PM. If I setup a time series with the code below, I get a chart that contains all 24 hours, 7 days a week. Makes sense, but not for my use case.
Is there a way to define what interval the time series displays? Or do I need to use a different chart type and attempt to fit my data into regular periods? I hope not the latter, while the data I receive is usually in 30 second intervals, there can easily be gaps.
It's pretty impossible to post an SSCE of a working UI with a chart dynamically asking for data from a server, but some highlights are below to get an idea of the chart types I'm using.
Some of the plot.add, CombinedDomainXY, index 0 code may seem strange. I have three subplots with the shared time values, I've pared it down to one here to keep it short. I assume there is a way to do what I need for one plot it would work for a chart with multiple subplots.
public ChartPanel extends JPanel
{
private final MyDataset _myDataset = new MyDataset();
private final XYPlot _myPlot = new XYPlot();
_chartPanel = new ChartPanel( createChart() );
private JFreeChart createChart()
{
CombinedDomainXYPlot plot = new CombinedDomainXYPlot(
timeAxis );
plot.setGap( 10.0 );
plot.setDomainPannable( true );
plot.setDataset( index, dataset );
NumberAxis axis = new NumberAxis();
axis.setAutoRangeIncludesZero( false );
plot.setRangeAxis( 0, axis );
plot.setRangeAxisLocation( 0, axisLocation );
plot.setRenderer( 0, new StandardXYItemRenderer() );
plot.mapDatasetToRangeAxis( 0, index );
// add the subplots...
plot.add( _myPlot, 1 );
}
}
public class MyDataset implements XYDataset
{
#Override
public double getYValue( int series, int item )
{
return getMyData(item);
}
#Override
public double getXValue( int series, int item )
{
return _bars.get( item ).DateTime.toInstant().toEpochMilli();
}
//other basic overloaded methods left out for brevity
}
You may be able to use a DateAxis with a custom Timeline. SegmentedTimeline, examined here, is a concrete implementation; although deprecated, it may serve as a guide. Based on this example, your notional newWorkdayTimeline() might look something like this:
public static SegmentedTimeline newWorkdayTimeline() {
SegmentedTimeline timeline = new SegmentedTimeline(
SegmentedTimeline.HOUR_SEGMENT_SIZE, 10, 14);
timeline.setStartTime(SegmentedTimeline.firstMondayAfter1900()
+ 7 * timeline.getSegmentSize());
timeline.setBaseTimeline(SegmentedTimeline.newMondayThroughFridayTimeline());
return timeline;
}
This example illustrates one way to mitigate any rendering artifacts you encounter.
Related
I'm trying to write a method to create a simple graph of a normal distribution in JFreeChart and save it as a file. Here's an example of an output image that's pretty much exactly what I want
Notice that there are exactly 9 tick marks on the x axis. The center one is the mean of the distribution, and the rest of the ticks indicate standard deviations. There is one tick for each standard deviation from the mean.
Here's an example of another chart showing a normal distribution with a mean of 7 and a standard deviation of 5 and no other code changes.
This is not what I want. Suddenly there are only 8 tick marks, and there is no tick in the center to mark the mean. It appears that JFreeChart wants to only use nice round numbers instead of the odd 7 as the center tick.
I've tried reading other StackOverflow questions on forcing axis labels, but it appears everyone else wants to do this with some form of dates. It would help if I could simply specify 9 exactly values to put on the axis instead of them being autogenerated, but I don't know how to do that.
There's also one other problem. If you look at the curve near the sides of the graph, it is clipping below the frame of the plot and running into the tick marks. I want to add padding between the curve and the tick marks. I tried using something like plot.getRangeAxis().setRange(-0.01, 0.09); but I ran into a bizarre problem where it appears that the height of the normal distribution is impacted by its width. Large means and standard deviations cause this to break miserably. (That makes zero sense from a statistics standpoint and I'm starting to question this normal distribution method.)
Anyway I basically need a way to force the chart to (a) add padding around the curve and (b) use exactly nine tick marks corresponding to the mean and four standard deviations out.
Here's my current code, which was mostly stolen online and trimmed to what appeared to be actually necessary:
static double mean = 7.0, sd = 5.0;
static Color line = new Color(0x6AA2A3);
static Color grey = new Color(0x555555);
public static void main(String[] args) throws IOException {
// Create the normal distribution
double minX = mean - (4 * sd), maxX = mean + (4 * sd);
Function2D normal = new NormalDistributionFunction2D(mean, sd);
XYDataset dataset = DatasetUtils.sampleFunction2D(normal, minX, maxX, 100, "Normal");
JFreeChart chart = ChartFactory.createXYLineChart(null, null, null, dataset, PlotOrientation.VERTICAL, false, false, false);
chart.setBorderVisible(true);
// Create and format the Plot
XYPlot plot = chart.getXYPlot();
plot.setBackgroundPaint(Color.WHITE);
plot.getRangeAxis().setVisible(false);
plot.setOutlineVisible(false);
// Format the X axis to look pretty
NumberAxis domain = (NumberAxis) plot.getDomainAxis();
domain.setRange(minX, maxX);
domain.setAxisLineVisible(false);
domain.setAutoRangeStickyZero(false);
domain.setTickUnit(new NumberTickUnit(sd));
domain.setTickLabelFont(new Font("Roboto", Font.PLAIN, 20));
domain.setTickLabelPaint(grey);
domain.setTickMarkStroke(new BasicStroke(2, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
domain.setTickMarkInsideLength(8);
domain.setTickMarkPaint(grey);
// Create a renderer to turn the chart into an image
XYLineAndShapeRenderer render = (XYLineAndShapeRenderer) plot.getRenderer(0);
render.setSeriesStroke(0, new BasicStroke(4));
render.setSeriesPaint(0, line);
// Output the final image
chart.setPadding(new RectangleInsets(5, 20, 20, 20));
BufferedImage image = chart.createBufferedImage(600,400);
File outFile = new File("graph.png");
outFile.createNewFile();
ImageIO.write(image, "png", outFile);
}
for request a),
plot.setAxisOffset(new RectangleInsets(5,5,5,5));
should do the trick.
For request b), the general recommendation is to override
refreshTicks(Graphics2D g2, AxisState state,Rectangle2D dataArea,RectangleEdge edge)
of the
ValueAxis
class and return a suitable List of ticks. Though doing so may look a bit intimidating, it is not if your logic is simple. You could try to simply add a NumberTick for the mean to the auto-generated tick list.
I'm trying to to make a chart that takes data from serial port and plot them in y axes and i want current time in x axes.. I think that i set my code correctly because i managed to run it as XY chart now in TimeSeries chart my only issue is that in method series.add(TIME, SERIALDATA); i dont know how to initialize TIME , i know that i want an object RegularTimePeriod but i dont know how to do that..
here is the code.. i know that only some lines are missing please help me to find them...
void initialize() {
frame = new JFrame();
frame.setBounds(100, 100, 817, 525);
final TimeSeries series = new TimeSeries("Charts");
final SerialDataReceived serialdataprint = new SerialDataReceived();
final TimeSeriesCollection data = new TimeSeriesCollection(series);
final JFreeChart chart = ChartFactory.createXYLineChart(
"Tmperature IN",
"Time",
"C",
data,
PlotOrientation.VERTICAL,
true,
true,
false
);
final ChartPanel chartPanel = new ChartPanel(chart);
chartPanel.setBounds(10, 11, 477, 224);
chartPanel.setPreferredSize(new java.awt.Dimension(500, 270));
chartPanel.setVisible(true);
frame.getContentPane().setLayout(null);
frame.getContentPane().add(chartPanel);
chartPanel.setLayout(null);
Thread outtempthread=new Thread() { //THREAD THAT RUNS ALL THE TIME
public void run() {
try {
while (true){
Thread.sleep(2000);
double intemp = serialdataprint.getintemp(); //THIS WHERE I TAKE MY SERIAL DATA
series.add(I WANT TO DISPLAY HERE LETS SAY 13:23:15, intemp); //HERE IS MY PROBLEM
}}
catch (InterruptedException ie) {}
}
};
outtempthread.start();
}
I've only ever used TimeSeries measured in days so I used the org.jfree.data.time.Day class.
Here's the jfreechart javadoc for all the different time classes : http://www.jfree.org/jfreechart/api/javadoc/org/jfree/data/time/package-summary.html
Try out a few and see what's right for you.
Since you appear to only need hour,minute second of a single day, you might be able to use the Second class.
Here is how you would make a TimeSeries that way:
int todaysDay =...
int todaysMonth =...
int todaysYear =...
TimeSeries series = new TimeSeries(name, Second.class);
//this should mark 'inTemp' as 13:23:15
series.add(new Second(15,23,13,todaysDay, todaysMonth, todaysYear),
inTemp);
ok!! finally i found the solution! i don't know if is the correct way but it works an now i have real time in my chart every time my serial port updates here is the fix code!
String timeStamp1 = new SimpleDateFormat("mm").format(Calendar.getInstance().getTime());
int minute = Integer.parseInt(timeStamp1);
double intemp = serialdataprint.getintemp();
series.addOrUpdate(new Minute(minute,hour), intemp);
A couple of pointers:
The ChartFactory.createXYLineChart() method will create a line chart where both the X and Y axes are numerical. Try the createTimeSeriesChart() to get a chart that shows dates on the X axis (or create a new DateAxis() instance and call plot.setDomainAxis() to change the X axis);
The TimeSeriesCollection class is a good dataset to use for time series data if you need the structure that it provides (it enforces a regular time period and prevents duplicates among other things). However, bear in mind that it is simply an implementation of the XYDataset interface where the x-values returned are "milliseconds since 1-Jan-1970" (the standard encoding of "dates" in Java). You can simplify your code by using an XYSeriesCollection (which also implements the XYDataset interface), and call System.currentTimeInMillis() to get the current x-value when new data comes in. The date axis on your chart will take care of presenting a date scale for this data.
i'm trying to make an overlaid plot,
And my problem is that i can't bring the second plot to fit the second one.
Here is my first plot :
And here the second one :
And when i try to fit both, here is what i get :
So basically, i would like to fit the whole second plot between 0 and 30, how can i do this without losing any data?
First I tried using plot.mapDatasetToRangeAxis()
Then i tried with :
domain.setRange(0.00, 30.0);
domain.setTickUnit(new NumberTickUnit(1));
But i couldn't bring neither the first, nor the second one to work as i wish.
Do you have any other ideas? (except buying this - which i can't afford right now as a student).
Any help will be greatly appreciated :)
Oh and by the way the x-axis is a speed (forgot to draw it on the plot).
So here a very ugly photomontage of the kind of result i wish to have (with fitting units on the x and y axis) :
Sorry for my Gimp skills, which are beyond bad.
Here is what i did :
private JFreeChart createOverlaidChart()
{
final NumberAxis domainAxis = new NumberAxis("Speed (m / s)");
final ValueAxis rangeAxis = new NumberAxis("Power (kw)");
// create plot ...
final IntervalXYDataset data0 = createDataset0();
final XYItemRenderer renderer0 = new XYBarRenderer(0.20);
// change "new XYBarRenderer(0.20)" to "StandardXYItemRenderer()" if you want to change type of graph
final XYPlot plot = new XYPlot(data0, domainAxis, rangeAxis, renderer0);
// add a second dataset and renderer...
final IntervalXYDataset data1 = createDataset1();
final XYLineAndShapeRenderer renderer1 = new XYLineAndShapeRenderer(false, true);
// arguments of new XYLineAndShapeRenderer are to activate or deactivate the display of points or line. Set first argument to true if you want to draw lines between the points for e.g.
plot.setDataset(1, data1);
plot.setRenderer(1, renderer1);
// add a third dataset and renderer...
final IntervalXYDataset data2 = createDataset2();
final XYLineAndShapeRenderer renderer2 = new XYLineAndShapeRenderer(true, true);
// arguments of new XYLineAndShapeRenderer are to activate or deactivate the display of points or line. Set first argument to true if you want to draw lines between the points for e.g.
plot.setDataset(2, data2);
plot.setRenderer(2, renderer2);
plot.setDatasetRenderingOrder(DatasetRenderingOrder.FORWARD);
// return a new chart containing the overlaid plot...
return new JFreeChart("Test", JFreeChart.DEFAULT_TITLE_FONT, plot, true);
}
The Range Axis is the Vertical/Y Axis, you need to add a second Domain Axis (Horizontal/X Axis) to your chart.
I did something similar -- although using same X axis vales but differing Y axis. For this, I use single Domain (you'd want single Range). I convert for you (there may be typos due to my edits):
priceXYPlot.setRangeAxis( new new NumberAxis( "Y" ) );
priceXYPlot.setDomainAxis( 0, new NumberAxis( "X1" ) );
priceXYPlot.setDomainAxis( 1, new NumberAxis("X2") );
and mapDatasetToRangeAxis so that you had two diff X axis along top & bottom, something like:
priceXYPlot.setDataset( 0, data0);
priceXYPlot.mapDatasetToDomainAxis( 0, 0 ); //1st dataset to 1st x-axis
priceXYPlot.setDataset( 1, data1 );
priceXYPlot.mapDatasetToDomainAxis( 1, 1 ); //2nd dataset to 2nd x-axis
I have created a Chart shows Questions (X) / Time (Y) answered during a test.
You can see the first question here for details.
But now I need to show the chart bullet values correctly, at the moment shows the milliseconds value but i need to show the custom hh:mm:ss value like I've done with the Y-Axis label and somehow customize the Y-Axis area to show the full values correctly.
Below is a screenshot of how the chart looks like now.
[EDIT]
With Dan's help I almost got what I want. It's just a little problem.
Check in the screenshot below where the chart values now appearing.
I updated to 1.1.0 from the AChartEngine repository.
addNotations is on the TimeSeries objects. I copy paste my code below where adding data to my TimeSeries instance.
myQuestionsTimeSeries.add(i, DateTimeHelper.getMillisFromTime(answer.getEstimatedAnswerTime()));
xyMultipleSeriesRenderer.addYTextLabel(DateTimeHelper.getMillisFromTime(answer.getEstimatedAnswerTime()),
String.valueOf(answer.getEstimatedAnswerTime()));
myQuestionsTimeSeries.addAnnotation(String.valueOf(answer.getEstimatedAnswerTime()), i,
DateTimeHelper.getMillisFromTime(answer.getEstimatedAnswerTime()));
The code results to this Chart:
[EDIT]
This is basically the whole class:
private void initQuestionsTimeChart() {
xyMultipleSeriesDataset = new XYMultipleSeriesDataset();
xyMultipleSeriesRenderer = new XYMultipleSeriesRenderer();
questionsTimeChart = ChartFactory.getLineChartView(getActivity(), xyMultipleSeriesDataset, xyMultipleSeriesRenderer);
rootView.addView(questionsTimeChart);
initSeriesData();
}
private void initSeriesData() {
createMyQuestionsSeries();
addSeriesAndRenderer(myQuestionsTimeSeries, myQuestionsRenderer);
xyMultipleSeriesRenderer.setYTitle("Questions Time");
xyMultipleSeriesRenderer.setXTitle("Questions Number");
xyMultipleSeriesRenderer.setMarginsColor(Color.argb(0, 255, 255, 255));
xyMultipleSeriesRenderer.setAxesColor(Color.BLACK);
xyMultipleSeriesRenderer.setLabelsColor(Color.BLACK);
xyMultipleSeriesRenderer.setXLabelsColor(Color.BLACK);
xyMultipleSeriesRenderer.setYLabelsColor(0, Color.BLACK);
xyMultipleSeriesRenderer.setAxisTitleTextSize(16);
xyMultipleSeriesRenderer.setLabelsTextSize(15);
xyMultipleSeriesRenderer.setYLabelsAlign(Paint.Align.RIGHT);
xyMultipleSeriesRenderer.setSelectableBuffer(20);
xyMultipleSeriesRenderer.setYLabels(0);
xyMultipleSeriesRenderer.setMargins(new int[]{ 80, 80, 80, 80 });
}
private void addSeriesAndRenderer(XYSeries series, XYSeriesRenderer renderer) {
xyMultipleSeriesDataset.addSeries(series);
xyMultipleSeriesRenderer.addSeriesRenderer(renderer);
}
private void createMyQuestionsSeries() {
myQuestionsTimeSeries = new TimeSeries("My Questions/Time");
myQuestionsRenderer = new XYSeriesRenderer();
myQuestionsRenderer.setColor(Color.BLUE);
myQuestionsRenderer.setLineWidth(3f);
myQuestionsRenderer.setPointStyle(PointStyle.CIRCLE);
myQuestionsRenderer.setFillPoints(true);
myQuestionsRenderer.setChartValuesSpacing(10f);
}
private void fillData() {
int i = 0;
for (Answer answer : getAnswers()) {
i++;
if (answer.getEstimatedAnswerTime() != null) {
myQuestionsTimeSeries.add(i, DateTimeHelper.getMillisFromTime(answer.getEstimatedAnswerTime()));
xyMultipleSeriesRenderer.addYTextLabel(DateTimeHelper.getMillisFromTime(answer.getEstimatedAnswerTime()),
String.valueOf(answer.getEstimatedAnswerTime()));
myQuestionsTimeSeries.addAnnotation(String.valueOf(answer.getEstimatedAnswerTime()), i,
DateTimeHelper.getMillisFromTime(answer.getEstimatedAnswerTime()));
}
}
}
Thank you in advance!
First of all, hide the chart values:
renderer.setDisplayChartValues(false);
Then, for each chart value, add an annotation:
renderer.addAnnotation("text", x, y);
For the Y axis labels to be visible, just align them to LEFT:
renderer.setYLabelsAlign(Align.LEFT);
Or you can increase the margins:
renderer.setMargins(margins);
Make sure you are using the latest ACE build available here.
I know there are many comparisons of java plotting libraries out there, but I'm not finding what I need. I just want a mind-numbingly simple toolkit that creates images of scatterplots from a set of coordinates. No GUI, no interaction, no fancy display, just a basic XY coordinate system with points.
It wouldn't be the end of the world to use something that offers a lot more functionality than I need, but I'd rather not. Do you know of anything like what I'm looking for?
Have you looked at JFreeChart? While it can do some very advanced things, it also does the simple as well. Shown below is a screenshot of its scatter plot capability.
(source: jfree.org)
I looked around at what existed, and realized that jcckit is technically pretty good, but just lacks a simple wrapper around it to make it easy to use.
So I forked it and made a really simple wrapper. Here's how to use:
import static easyjcckit.QuickPlot.*;
double[] xaxis = new double[]{0,1,2,3,4,5};
double[] yvalues = new double[]{0,1,4,9,16,25};
scatter( xaxis, yvalues ); // create a plot using xaxis and yvalues
double[] yvalues2 = new double[]{0,1,2,3,4,5};
addScatter( xaxis, yvalues2 ); // create a second plot on top of first
System.out.println("Press enter to exit");
System.in.read();
As well as scatter plots, you can freely add lines to the same axes if you wish using 'addPlot' and 'plot'.
Here is the code: https://bitbucket.org/hughperkins/easyjcckit
You an use a custom JPanel to draw your data(not tested, but you get the idea...)
private List<Point2D> data=(...);
JPanel pane=new JPanel()
{
protected paintComponent(Graphics2D g)
{
super.paintComponent(g);
int minx=(...),miny=(...),maxx=(...),maxy=(...);
for(Point2D p: data)
{
int x=((p.getX()-minx)/(maxx-minx))*this.getWidth();
int y=((p.getY()-miny)/(maxy-miny))*this.getHeight();
g.drawLine(x-5,y,x+5,y);
g.drawLine(x,y-5,x,y+5);
}
}
pane.setOpaque(true);
(...)
anotherComponent.add(pane);
(...)
}
Also you could check Simple Java Plot. Minimal example (no options):
Plot plot = Plot.plot(null).
// setting data
series(null, Plot.data().
xy(1, 2).
xy(3, 4), null);
// saving sample_minimal.png
plot.save("sample_minimal", "png");