Skip to content
Snippets Groups Projects
Commit 6baddf40 authored by Tim Molter's avatar Tim Molter
Browse files

Issue #112 Bar charts with values above bars

parent 10f31ee5
No related branches found
No related tags found
No related merge requests found
Showing
with 221 additions and 109 deletions
......@@ -33,7 +33,7 @@ import org.knowm.xchart.style.Styler.LegendPosition;
* <li>All positive values
* <li>Single series
* <li>Place legend at Inside-NW position
* <li>Bars span 100% allowed space
* <li>Bar Chart Annotations
*/
public class BarChart01 implements ExampleChart<CategoryChart> {
......@@ -52,7 +52,7 @@ public class BarChart01 implements ExampleChart<CategoryChart> {
// Customize Chart
chart.getStyler().setLegendPosition(LegendPosition.InsideNW);
chart.getStyler().setPlotContentSize(1.0);
chart.getStyler().setHasAnnotations(true);
// Series
chart.addSeries("test 1", Arrays.asList(new Integer[] { 0, 1, 2, 3, 4 }), Arrays.asList(new Integer[] { 4, 5, 9, 6, 5 }));
......
......@@ -33,6 +33,7 @@ import org.knowm.xchart.demo.charts.ExampleChart;
* <li>Multiple series
* <li>Missing point in series
* <li>Manually setting y-axis min and max values
* <li>Bar Chart Annotations
*/
public class BarChart04 implements ExampleChart<CategoryChart> {
......@@ -52,6 +53,7 @@ public class BarChart04 implements ExampleChart<CategoryChart> {
// Customize Chart
chart.getStyler().setYAxisMin(5);
chart.getStyler().setYAxisMax(70);
chart.getStyler().setHasAnnotations(true);
// Series
chart.addSeries("female", Arrays.asList(new Integer[] { 10, 20, 30, 40, 50 }), Arrays.asList(new Integer[] { 50, 10, 20, 40, 35 }));
......
......@@ -18,9 +18,13 @@ package org.knowm.xchart.internal.chartpart;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.awt.geom.AffineTransform;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import java.awt.geom.Rectangle2D;
import java.text.DecimalFormat;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
......@@ -77,9 +81,6 @@ public class PlotContent_Category_Bar<ST extends Styler, S extends Series> exten
// System.out.println("gridStep: " + gridStep);
// Y-Axis
double yTickSpace = stylerCategory.getPlotContentSize() * bounds.getHeight();
double yTopMargin = Utils.getTickStartOffset(bounds.getHeight(), yTickSpace);
double yMin = chart.getAxisPair().getYAxis().getMin();
double yMax = chart.getAxisPair().getYAxis().getMax();
......@@ -98,6 +99,10 @@ public class PlotContent_Category_Bar<ST extends Styler, S extends Series> exten
// System.out.println(yMax);
// System.out.println("chartForm: " + chartForm);
double yTickSpace = stylerCategory.getPlotContentSize() * bounds.getHeight();
double yTopMargin = Utils.getTickStartOffset(bounds.getHeight(), yTickSpace);
// plot series
int seriesCounter = 0;
for (CategorySeries series : seriesMap.values()) {
......@@ -198,8 +203,41 @@ public class PlotContent_Category_Bar<ST extends Styler, S extends Series> exten
// g.setStroke(series.getLineStyle());
// g.setColor(series.getLineColor());
// g.draw(path);
g.setColor(series.getFillColor());
g.fill(path);
if (stylerCategory.hasAnnotations() && next != null) {
DecimalFormat twoPlaces = new DecimalFormat("#.#");
if (stylerCategory.getYAxisDecimalPattern() != null) {
twoPlaces = new DecimalFormat(stylerCategory.getYAxisDecimalPattern());
}
String numberAsString = twoPlaces.format(next);
TextLayout textLayout = new TextLayout(numberAsString, stylerCategory.getAnnotationsFont(), new FontRenderContext(null, true, false));
Rectangle2D annotationRectangle = textLayout.getBounds();
double annotationX = xOffset + barWidth / 2 - annotationRectangle.getWidth() / 2;
double annotationY;
if (next.doubleValue() >= 0.0) {
annotationY = yOffset - 4;
}
else {
annotationY = zeroOffset + 4 + annotationRectangle.getHeight();
}
Shape shape = textLayout.getOutline(null);
g.setColor(stylerCategory.getChartFontColor());
g.setFont(stylerCategory.getAnnotationsFont());
AffineTransform orig = g.getTransform();
AffineTransform at = new AffineTransform();
at.translate(annotationX, annotationY);
g.transform(at);
g.fill(shape);
g.setTransform(orig);
}
}
else if (CategorySeriesRenderStyle.Stick.equals(series.getChartCategorySeriesRenderStyle())) {
......
......@@ -127,103 +127,105 @@ public class PlotContent_Pie<ST extends Styler, S extends Series> extends PlotCo
g.draw(new Arc2D.Double(pieBounds.getX(), pieBounds.getY(), pieBounds.getWidth(), pieBounds.getHeight(), startAngle, arcAngle, Arc2D.PIE));
// curValue += y.doubleValue();
// draw annotation
String annotation = "";
if (stylerPie.getAnnotationType() == AnnotationType.Label) {
annotation = series.getName();
}
else if (stylerPie.getAnnotationType() == AnnotationType.LabelAndPercentage) {
double percentage = y.doubleValue() / total * 100;
annotation = series.getName() + " (" + df.format(percentage) + "%)";
}
else if (stylerPie.getAnnotationType() == AnnotationType.Percentage) {
double percentage = y.doubleValue() / total * 100;
annotation = df.format(percentage) + "%";
}
TextLayout textLayout = new TextLayout(annotation, stylerPie.getAnnotationFont(), new FontRenderContext(null, true, false));
Rectangle2D percentageRectangle = textLayout.getBounds();
double xCenter = pieBounds.getX() + pieBounds.getWidth() / 2 - percentageRectangle.getWidth() / 2;
double yCenter = pieBounds.getY() + pieBounds.getHeight() / 2 + percentageRectangle.getHeight() / 2;
double angle = (arcAngle + startAngle) - arcAngle / 2;
double xOffset = xCenter + Math.cos(Math.toRadians(angle)) * (pieBounds.getWidth() / 2 * stylerPie.getAnnotationDistance());
double yOffset = yCenter - Math.sin(Math.toRadians(angle)) * (pieBounds.getHeight() / 2 * stylerPie.getAnnotationDistance());
if (stylerPie.hasAnnotations()) {
// get annotation width
Shape shape = textLayout.getOutline(null);
Rectangle2D annotationBounds = shape.getBounds2D();
double annotationWidth = annotationBounds.getWidth();
// System.out.println("annotationWidth= " + annotationWidth);
double annotationHeight = annotationBounds.getHeight();
// System.out.println("annotationHeight= " + annotationHeight);
// get slice area
double xOffset1 = xCenter + Math.cos(Math.toRadians(startAngle)) * (pieBounds.getWidth() / 2 * stylerPie.getAnnotationDistance());
double yOffset1 = yCenter - Math.sin(Math.toRadians(startAngle)) * (pieBounds.getHeight() / 2 * stylerPie.getAnnotationDistance());
double xOffset2 = xCenter + Math.cos(Math.toRadians((arcAngle + startAngle))) * (pieBounds.getWidth() / 2 * stylerPie.getAnnotationDistance());
double yOffset2 = yCenter - Math.sin(Math.toRadians((arcAngle + startAngle))) * (pieBounds.getHeight() / 2 * stylerPie.getAnnotationDistance());
// System.out.println("xOffset1= " + xOffset1);
// System.out.println("yOffset1= " + yOffset1);
// System.out.println("xOffset2= " + xOffset2);
// System.out.println("yOffset2= " + yOffset2);
double xDiff = Math.abs(xOffset1 - xOffset2);
double yDiff = Math.abs(yOffset1 - yOffset2);
// System.out.println("xDiff= " + xDiff);
// System.out.println("yDiff= " + yDiff);
// double max = Math.max(xDiff, yDiff);
// System.out.println(" ================== ");
boolean annotationWillFit = false;
if (xDiff >= yDiff) { // assume more vertically orientated slice
if (annotationWidth < xDiff) {
annotationWillFit = true;
// draw annotation
String annotation = "";
if (stylerPie.getAnnotationType() == AnnotationType.Label) {
annotation = series.getName();
}
}
else if (xDiff <= yDiff) { // assume more horizontally orientated slice
if (annotationHeight < yDiff) {
annotationWillFit = true;
else if (stylerPie.getAnnotationType() == AnnotationType.LabelAndPercentage) {
double percentage = y.doubleValue() / total * 100;
annotation = series.getName() + " (" + df.format(percentage) + "%)";
}
else if (stylerPie.getAnnotationType() == AnnotationType.Percentage) {
double percentage = y.doubleValue() / total * 100;
annotation = df.format(percentage) + "%";
}
}
// draw annotation
if (stylerPie.isDrawAllAnnotations() || annotationWillFit) {
TextLayout textLayout = new TextLayout(annotation, stylerPie.getAnnotationsFont(), new FontRenderContext(null, true, false));
Rectangle2D annotationRectangle = textLayout.getBounds();
double xCenter = pieBounds.getX() + pieBounds.getWidth() / 2 - annotationRectangle.getWidth() / 2;
double yCenter = pieBounds.getY() + pieBounds.getHeight() / 2 + annotationRectangle.getHeight() / 2;
double angle = (arcAngle + startAngle) - arcAngle / 2;
double xOffset = xCenter + Math.cos(Math.toRadians(angle)) * (pieBounds.getWidth() / 2 * stylerPie.getAnnotationDistance());
double yOffset = yCenter - Math.sin(Math.toRadians(angle)) * (pieBounds.getHeight() / 2 * stylerPie.getAnnotationDistance());
// get annotation width
Shape shape = textLayout.getOutline(null);
Rectangle2D annotationBounds = shape.getBounds2D();
double annotationWidth = annotationBounds.getWidth();
// System.out.println("annotationWidth= " + annotationWidth);
double annotationHeight = annotationBounds.getHeight();
// System.out.println("annotationHeight= " + annotationHeight);
// get slice area
double xOffset1 = xCenter + Math.cos(Math.toRadians(startAngle)) * (pieBounds.getWidth() / 2 * stylerPie.getAnnotationDistance());
double yOffset1 = yCenter - Math.sin(Math.toRadians(startAngle)) * (pieBounds.getHeight() / 2 * stylerPie.getAnnotationDistance());
double xOffset2 = xCenter + Math.cos(Math.toRadians((arcAngle + startAngle))) * (pieBounds.getWidth() / 2 * stylerPie.getAnnotationDistance());
double yOffset2 = yCenter - Math.sin(Math.toRadians((arcAngle + startAngle))) * (pieBounds.getHeight() / 2 * stylerPie.getAnnotationDistance());
// System.out.println("xOffset1= " + xOffset1);
// System.out.println("yOffset1= " + yOffset1);
// System.out.println("xOffset2= " + xOffset2);
// System.out.println("yOffset2= " + yOffset2);
double xDiff = Math.abs(xOffset1 - xOffset2);
double yDiff = Math.abs(yOffset1 - yOffset2);
// System.out.println("xDiff= " + xDiff);
// System.out.println("yDiff= " + yDiff);
// double max = Math.max(xDiff, yDiff);
// System.out.println(" ================== ");
boolean annotationWillFit = false;
if (xDiff >= yDiff) { // assume more vertically orientated slice
if (annotationWidth < xDiff) {
annotationWillFit = true;
}
}
else if (xDiff <= yDiff) { // assume more horizontally orientated slice
if (annotationHeight < yDiff) {
annotationWillFit = true;
}
}
g.setColor(stylerPie.getChartFontColor());
g.setFont(stylerPie.getChartTitleFont());
AffineTransform orig = g.getTransform();
AffineTransform at = new AffineTransform();
// draw annotation
if (stylerPie.isDrawAllAnnotations() || annotationWillFit) {
// inside
if (stylerPie.getAnnotationDistance() <= 1.0) {
at.translate(xOffset, yOffset);
}
g.setColor(stylerPie.getChartFontColor());
g.setFont(stylerPie.getAnnotationsFont());
AffineTransform orig = g.getTransform();
AffineTransform at = new AffineTransform();
// outside
else {
// inside
if (stylerPie.getAnnotationDistance() <= 1.0) {
at.translate(xOffset, yOffset);
}
// Tick Mark
xCenter = pieBounds.getX() + pieBounds.getWidth() / 2;
yCenter = pieBounds.getY() + pieBounds.getHeight() / 2;
// double endPoint = Math.min((2.0 - (stylerPie.getAnnotationDistance() - 1)), 1.95);
double endPoint = (3.0 - stylerPie.getAnnotationDistance());
double xOffsetStart = xCenter + Math.cos(Math.toRadians(angle)) * (pieBounds.getWidth() / 2.01);
double xOffsetEnd = xCenter + Math.cos(Math.toRadians(angle)) * (pieBounds.getWidth() / endPoint);
double yOffsetStart = yCenter - Math.sin(Math.toRadians(angle)) * (pieBounds.getHeight() / 2.01);
double yOffsetEnd = yCenter - Math.sin(Math.toRadians(angle)) * (pieBounds.getHeight() / endPoint);
// outside
else {
g.setStroke(new BasicStroke(2.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER));
Shape line = new Line2D.Double(xOffsetStart, yOffsetStart, xOffsetEnd, yOffsetEnd);
g.draw(line);
// Tick Mark
xCenter = pieBounds.getX() + pieBounds.getWidth() / 2;
yCenter = pieBounds.getY() + pieBounds.getHeight() / 2;
// double endPoint = Math.min((2.0 - (stylerPie.getAnnotationDistance() - 1)), 1.95);
double endPoint = (3.0 - stylerPie.getAnnotationDistance());
double xOffsetStart = xCenter + Math.cos(Math.toRadians(angle)) * (pieBounds.getWidth() / 2.01);
double xOffsetEnd = xCenter + Math.cos(Math.toRadians(angle)) * (pieBounds.getWidth() / endPoint);
double yOffsetStart = yCenter - Math.sin(Math.toRadians(angle)) * (pieBounds.getHeight() / 2.01);
double yOffsetEnd = yCenter - Math.sin(Math.toRadians(angle)) * (pieBounds.getHeight() / endPoint);
// annotation
at.translate(xOffset - Math.sin(Math.toRadians(angle - 90)) * annotationWidth / 2 + 3, yOffset);
g.setStroke(new BasicStroke(2.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER));
Shape line = new Line2D.Double(xOffsetStart, yOffsetStart, xOffsetEnd, yOffsetEnd);
g.draw(line);
}
// annotation
at.translate(xOffset - Math.sin(Math.toRadians(angle - 90)) * annotationWidth / 2 + 3, yOffset);
g.transform(at);
g.fill(shape);
g.setTransform(orig);
}
g.transform(at);
g.fill(shape);
g.setTransform(orig);
}
}
// else {
// System.out.println("Won't fit.");
......
......@@ -401,4 +401,19 @@ public class GGPlot2Theme implements Theme {
return false;
}
// Annotations ///////////////////////////////
@Override
public Font getAnnotationFont() {
return new Font(Font.SANS_SERIF, Font.PLAIN, 12);
}
@Override
public boolean hasAnnotations() {
return false;
}
}
......@@ -405,4 +405,19 @@ public class MatlabTheme implements Theme {
return false;
}
// Annotations ///////////////////////////////
@Override
public Font getAnnotationFont() {
return new Font(Font.SANS_SERIF, Font.PLAIN, 12);
}
@Override
public boolean hasAnnotations() {
return false;
}
}
......@@ -16,8 +16,6 @@
*/
package org.knowm.xchart.style;
import java.awt.Font;
import org.knowm.xchart.PieSeries.PieSeriesRenderStyle;
/**
......@@ -34,7 +32,6 @@ public class PieStyler extends Styler {
private boolean isCircular;
private double startAngleInDegrees;
private Font annotationFont;
private double annotationDistance;
private AnnotationType annotationType;
private boolean drawAllAnnotations;
......@@ -53,7 +50,6 @@ public class PieStyler extends Styler {
this.chartPieSeriesRenderStyle = PieSeriesRenderStyle.Pie; // set default to pie, donut may be a future one
this.isCircular = theme.isCircular();
this.annotationFont = theme.getPieFont();
this.annotationDistance = theme.getAnnotationDistance();
this.annotationType = theme.getAnnotationType();
this.drawAllAnnotations = theme.isDrawAllAnnotations();
......@@ -107,22 +103,6 @@ public class PieStyler extends Styler {
return this;
}
public Font getAnnotationFont() {
return annotationFont;
}
/**
* Sets the font used on the Pie Chart's annotations
*
* @param pieFont
*/
public PieStyler setAnnotationFont(Font pieFont) {
this.annotationFont = pieFont;
return this;
}
public double getAnnotationDistance() {
return annotationDistance;
......
......@@ -93,6 +93,10 @@ public abstract class Styler {
private boolean isPlotBorderVisible;
private double plotContentSize = .92;
// Annotations ///////////////////////////////
private Font annotationsFont;
private boolean showAnnotations;
protected void setAllStyles() {
// Chart Style ///////////////////////////////
......@@ -125,6 +129,9 @@ public abstract class Styler {
plotBorderColor = theme.getPlotBorderColor();
isPlotBorderVisible = theme.isPlotBorderVisible();
plotContentSize = theme.getPlotContentSize();
// Annotations ///////////////////////////////
annotationsFont = theme.getAnnotationFont();
}
// Chart Style ///////////////////////////////
......@@ -497,4 +504,36 @@ public abstract class Styler {
return this;
}
// Annotations ///////////////////////////////
public boolean hasAnnotations() {
return showAnnotations;
}
/**
* Sets if annotations should be added to charts. Each chart type has a different annotation type
*
* @param showAnnotations
*/
public void setHasAnnotations(boolean showAnnotations) {
this.showAnnotations = showAnnotations;
}
public Font getAnnotationsFont() {
return annotationsFont;
}
/**
* Sets the Font used for chart annotations
*
* @param annotationsFont
*/
public void setAnnotationsFont(Font annotationsFont) {
this.annotationsFont = annotationsFont;
}
}
......@@ -159,4 +159,10 @@ public interface Theme extends SeriesMarkers, SeriesLines, SeriesColors {
public boolean isErrorBarsColorSeriesColor();
// Annotations ///////////////////////////////
public Font getAnnotationFont();
public boolean hasAnnotations();
}
......@@ -401,4 +401,19 @@ public class XChartTheme implements Theme {
return false;
}
// Annotations ///////////////////////////////
@Override
public Font getAnnotationFont() {
return new Font(Font.SANS_SERIF, Font.PLAIN, 12);
}
@Override
public boolean hasAnnotations() {
return false;
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment