I have been trying for the last week to find a way to make JFreeChart display something similar to the image below. Basically you are looking at three series (upper, middle, lower) with a fill inbetween. And underneath there is a (light green) fill color, or an area chart as some would perhaps call it - no meaning, just for looks.
The only thing really missing from what I have come up with is the last part: the fill underneath / area chart:
I even tried to subclass XYDifferenceRenderer and combine it with the renderer for Areachart, but I could not control the height of the areachart, basically filling up the plot to the top. So that was a no-go. Having created as simple rendererer to create rounded bar charts earlier, I thought that I might be able to change the code for XYDifferenceRenderer. But the code for XYDifferenceRenderer is quite a handful of geometry and inner workings of JFree chart, and the task was a bit overwhelming. So any tips on how to achieve this effect in any "normal" way (that does not involve hacking JFreeChart's inner workings)?
Found an old post describing how to use two renderers in the same plot, which was just the thing in this case.
To get a fill underneath you need to
create two new series
one is the lower bound of the difference plot
the other is the values at the bottom of the plot - often just zero. Easily got by calling plot.getRangeAxis().getLowerBound()
add them to a new dataset and add this to the plot
I was unaware that a plot could have several datasets. Turns out one can just use an index to access them.
create a new renderer for the "fill" dataset
create a new renderer
set the right fill paint
set the rendererer for the new dataset to be the new renderer
The code is something akin to the following, where the fill Paint obviously is up to you:
static void addFill(Plot plot) {
XYSeries lowerLimitSeries = ((XYSeriesCollection) (plot.getDataset())).getSeries(1);
XYSeriesCollection fillSet = new XYSeriesCollection();
double lowerBound = plot.getRangeAxis().getLowerBound();
fillSet.addSeries(lowerLimitSeries);
fillSet.addSeries(createLowerFillSeries(lowerLimitSeries, lowerBound));
plot.setDataset(1, fillSet);
Paint fillPaint = Color.GREEN;
XYDifferenceRenderer fillRenderer = new XYDifferenceRenderer(fillPaint, fillPaint, false);
fillRenderer.setSeriesStroke(0, new BasicStroke(0)); //do not show
fillRenderer.setSeriesStroke(1, new BasicStroke(0)); //do not show
plot.setRenderer(1, fillRenderer);
...
}
static XYSeries createLowerFillSeries(XYSeries lowerLimitSeries, double lowerLimit) {
int size = lowerLimitSeries.getItems().size();
XYSeries res = new XYSeries("lowerFillSeries");
for (int i = 0; i < size; i++) res.add(new XYDataItem(lowerLimitSeries.getX(i), lowerLimit));
return res;
}
Related
I am obviously not understanding the documentation for the getSeriesPaint method. I have the TimeSeries object and I want to get the color being used to render it. However, it seems like I am in a catch-22. I need to know the series index (getIndex), but to find that I need to know the series time period. However, to find the series time period, I need to know the index. I'm looking to do something like this:
Color color=(Color) r1.getSeriesPaint(arg0);
where r1 is the XYLineAndShapeRenderer. What do I use for arg0 given the TimeSeries object?
Because XYLineAndShapeRenderer is an XYItemRenderer, it invokes the AbstractRenderer method getItemPaint(), which "Returns the paint used to color data items as they are drawn." Note that "The default implementation passes control to the lookupSeriesPaint() method." Starting from this example, the following fragment obtains the dataset and renderer from the chart. It then enumerates the series paints—shades of red and blue seen in the image:
JFreeChart chart = chartPanel.getChart();
XYPlot plot = (XYPlot) chart.getPlot();
TimeSeriesCollection tsc = (TimeSeriesCollection) plot.getDataset();
XYLineAndShapeRenderer renderer = (XYLineAndShapeRenderer) plot.getRenderer();
for (int i = 0; i < tsc.getSeriesCount(); i++) {
System.out.println(renderer.lookupSeriesPaint(i));
}
Console:
java.awt.Color[r=255,g=85,b=85]
java.awt.Color[r=85,g=85,b=255]
Alternatively, consider a custom DrawingSupplier, mentioned here.
I'm using an XYBoxAnnotation to demarcate a rectangular area on a JFreeChart. I would like one side of the box to be "open", i.e go out to infinity. I tried setting the value to Double.POSITIVE_INFINITY but this did not seem to work. I also tried setting it to Double.MAX_VALUE, with no luck either. In these cases, the annotation doesn't even show up on the plot at all. And there are no exceptions thrown.
Below is a very simple version of my code in which I generate the XYBoxAnnotation and add it to the plot.
XYBoxAnnotation _axisMarker = new XYBoxAnnotation(xLow, yLow, Double.POSITIVE_INFINITY, yHigh, new BasicStroke(0.5F), Color.WHITE, Color.WHITE);
_plot.getRenderer().addAnnotation(_axisMarker, Layer.BACKGROUND);
EDIT:
I figured out that the reason the annotation wasn't showing up was because the x value for the annotation was much much larger than the axis scale. For some reason, this causes the annotation to not be visible until you zoom out enough.
Thanks to #trashgod's answer below, I came up with a solution. His answer didn't quite work for me since my plot allows zooming and you could see the edge of the box when you zoomed out.
First, I added a PlotChangeListener to listen for when the plot is zoomed:
// define PlotChangeListener to update the annotation when the plot is zoomed
private PlotChangeListener _zoomListener = new PlotChangeListener() {
#Override
public void plotChanged(PlotChangeEvent plotChangeEvent) {
if (_basisIsotope != null) {
updateAxisMarkers();
}
}
};
Then I created a function to re-draw the annotation based on the new plot bounds:
// function to re-draw the annotation
private void updateAxisMarkers() {
_plot.removeChangeListener(_zoomListener); // remove to prevent triggering infinite loop
// define xLow, yLow and yHigh...
double xHigh = _plot.getDomainAxis().getUpperBound() * 1.1;
XYBoxAnnotation _axisMarker = new = new XYBoxAnnotation(xLow, yLow, xHigh, yHigh, new BasicStroke(0.5F), Color.WHITE, Color.WHITE);
_plot.getRenderer().addAnnotation(annotation);
_plot.addChangeListener(_zoomListener); // add back
}
Double.MAX_VALUE is too large to scale to the relevant axis, but Double.MAX_VALUE / 2 works as well as any value larger than the upper bound of the axis. A better choice might be a value that exceeds the maximum value of the domain by some margin. The fragment below shades a plot of some Gaussian data with an XYBoxAnnotation that has domain bounds extending from 42 to the maximum domain value + 10%; the range bounds are ±1σ.
XYSeriesCollection dataset = createDataset();
JFreeChart chart = createChart(dataset);
Color color = new Color(0, 0, 255, 63);
double max = dataset.getSeries(0).getMaxX() * 1.1;
XYBoxAnnotation annotation = new XYBoxAnnotation(
42, -1, max, 1, new BasicStroke(1f), color, color);
chart.getXYPlot().getRenderer().addAnnotation(annotation);
Is it possible to display custom text centered between 2 points on the graph?
I've got MPAndroidChart setup to display a step function type graph (representing hours spent doing a specific task) with horizontal and vertical lines only. What I would like to be able to do is show a label over the horizontal sections indicating the size of the section (aka the time spent calculated by taking the difference between the x values). Is there a way to do this? I've been look into modifying the library but I can't seem to figure out where would be the correct place to do so.
My best guess would be some changes in BarLineChartBase onDraw() method or maybe in the LineChartRenderer drawLinear() method.
Here is what I am able to produce:
Here is an example of what I am trying to produce:
Figured it out! Just add a new method drawTime() to the LineChart class at the end of onDraw() right after drawDescription(). Since each horizontal line is described by 2 Entry points I simply loop through 2 entries at a time for my single data set and calculate the difference:
protected void drawTime(Canvas c)
{
Paint timePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
timePaint.setTextSize(Utils.convertDpToPixel(16));
timePaint.setColor(Color.BLUE);
timePaint.setTextAlign(Paint.Align.CENTER);
MPPointD position;
LineData data = this.getLineData();
ILineDataSet dataSet = data.getDataSetByIndex(0);
for (int i = 1; i < dataSet.getEntryCount(); i+=2)
{
Entry e1 = dataSet.getEntryForIndex(i-1);
Entry e2 = dataSet.getEntryForIndex(i);
float time = e2.getX() - e1.getX();
position = getPixelForValues(e1.getX() + time/2, e1.getY() - 0.05f, YAxis.AxisDependency.LEFT);
c.drawText(String.valueOf(time), (float)position.x, (float)position.y, timePaint);
}
}
The resulting graph looks like this
I am using JFreeChart with Java to evaluate experimental results using the boxplot chart. I want to change the color and shape of the outliers and the farout entries.
This is how my plots currently look like when I use the normal BoxAndWhiskerRenderer:
I set up the renderer like this:
BoxAndWhiskerRenderer renderer = new BoxAndWhiskerRenderer();
renderer.setFillBox(true);
renderer.setSeriesPaint(0, Color.DARK_GRAY);
renderer.setSeriesPaint(1, Color.LIGHT_GRAY);
renderer.setSeriesOutlinePaint(0, Color.BLACK);
renderer.setSeriesOutlinePaint(1, Color.BLACK);
renderer.setUseOutlinePaintForWhiskers(true);
Font legendFont = new Font("SansSerif", Font.PLAIN, 15);
renderer.setLegendTextFont(0, legendFont);
renderer.setLegendTextFont(1, legendFont);
renderer.setMeanVisible(false);
Here, I cannot change the color and shape of the outliers. I would want them in black, not in the color of their series. And I would want them to look like small crosses rather than these big empty circles.
Also no farout values are shown at all and it seems like one of the outliers is cut off.
Then I found the ExtendedBoxAndWhiskerRenderer which allows to edit the color and shape of both outliers and farouts. This is what that looks like:
I set up the renderer like before, but I added two lines to set the color for the outliers and the farout entries:
renderer.setOutlierPaint(Color.BLACK); renderer.setFaroutPaint(Color.LIGHT_GRAY);
I also experimented with the shape of the outliers by reducing the cirle raduis in the extended renderer's implementation to 1.0 instead of 2.0:
private Shape createEllipse(Point2D point, double oRadius) {
Ellipse2D dot = new Ellipse2D.Double(point.getX(), point.getY(), oRadius*1.0, oRadius*1.0);
return dot;
}
However, I don't like these plots too much either. The Whiskers/Outlines of my plots aren't black anymore even though I set them to black. The mean is visible again even though I set it to invisible. And the huge number of outliers looks kind of ridiculous and makes me wonder why there are no farouts at the plots with the normal renderer at all.
If anyone could help me with these smaller appearance problems, that would be very nice. Otherwise, I will just take the current plots with the weird looking outliers and missing farouts...
While ExtendedBoxAndWhiskerRenderer is exemplary, it is somewhat dated, and much of its functionality has been incorporated into the mainline version. Your experiment suggests that the old renderer and new dataset are incompatible.
Because the outlier rendering methods are private, an alternative approach is to override the relevant draw*Item() method and let it invoke your own variations. You'll need to recapitulate the existing code, using the public accessors as required. In outline, the following variations demonstrate using Color.black, illustrated below.
plot.setRenderer(new BoxAndWhiskerRenderer() {
#Override
public void drawVerticalItem(Graphics2D g2, …) {
// existing code that calls the methods below
}
private void drawEllipse(Point2D point, double oRadius, Graphics2D g2) {
Paint temp = g2.getPaint();
g2.setColor(Color.black);
Ellipse2D dot = new Ellipse2D.Double(point.getX() + oRadius / 2,
point.getY(), oRadius, oRadius);
g2.draw(dot);
g2.setPaint(temp);
}
private void drawHighFarOut(double aRadius, Graphics2D g2, double xx,
double m) {
Paint temp = g2.getPaint();
g2.setColor(Color.black);
double side = aRadius * 2;
g2.draw(new Line2D.Double(xx - side, m + side, xx + side, m + side));
g2.draw(new Line2D.Double(xx - side, m + side, xx, m));
g2.draw(new Line2D.Double(xx + side, m + side, xx, m));
g2.setPaint(temp);
}
}
I am using JasperReports to create a line chart for my webapps.
I have successfully passed the dataset to the compiled report (created in iReport) and can see the data correctly.
However, I want to do some customization on the margin.
The value shown on the line chart is trimming for the highest value as there is no margin.
The X-Axis label is coming after few empty space from Y-Axis 0 value. I want to remove that margin and start the X-Axis from very close to the meeting point of X & Y.
Please see the picture:
I am using customized class which is defined in my webspps. I am able to change the font size and rotation of the label but don't know how to adjust margin.
public class LineChartCustomizer implements JRChartCustomizer {
#Override
public void customize(JFreeChart jFreeChart, JRChart jrChart) {
CategoryPlot plot = jFreeChart.getCategoryPlot();
DecimalFormat dfKey = new DecimalFormat("###,###");
StandardCategoryItemLabelGenerator labelGenerator = new StandardCategoryItemLabelGenerator("{2}", dfKey);
LineAndShapeRenderer renderer = new LineAndShapeRenderer();
renderer.setBaseItemLabelsVisible(true);
renderer.setBaseItemLabelGenerator(labelGenerator);
renderer.setBaseItemLabelFont(new java.awt.Font("SansSerif", java.awt.Font.PLAIN, 4));
renderer.setSeriesShape(0, ShapeUtilities.createDiamond(1F));
plot.setRenderer(renderer);
}
}
I think* you're looking for ValueAxis#setUpperMargin(double) and CategoryAxis#setLowerMargin(double). You can get the CategoryAxis and ValueAxis from plot.getDomainAxis() and plot.getRangeAxis(). Note that the margins are a percentage of the axis length and not pixel values.
* I'm not familiar with JasperReports, but it seems a little strange that you have a CategoryPlot in hand as opposed to an XYPlot. I would have expected the chart in your picture to have used an xy time series. I have only ever tested this with an XYPlot, so I'm not entirely sure how it will behave with a CategoryPlot.