From e1c455f6c2504275e8d2b3f87d4d2df5f951ae22 Mon Sep 17 00:00:00 2001
From: Martin Crawford <martin@martincrawford.ca>
Date: Sat, 19 Jul 2014 14:41:55 +0200
Subject: [PATCH] Added text-alignment functionality for axis labels. Added
 functionality for combining both line & area plots on one chart. Added demo
 of functionality added.

---
 .../com/xeiam/xchart/demo/XChartDemo.java     |  10 +-
 .../demo/charts/line/LineAreaChart07.java     | 257 ++++++++++++++++++
 .../main/java/com/xeiam/xchart/Series.java    |  26 ++
 .../java/com/xeiam/xchart/StyleManager.java   |  25 ++
 .../xchart/internal/chartpart/AxisPair.java   |  22 +-
 .../internal/chartpart/AxisTickLabels.java    |  71 +++--
 .../chartpart/PlotContentLineChart.java       |   4 +-
 7 files changed, 378 insertions(+), 37 deletions(-)
 create mode 100644 xchart-demo/src/main/java/com/xeiam/xchart/demo/charts/line/LineAreaChart07.java

diff --git a/xchart-demo/src/main/java/com/xeiam/xchart/demo/XChartDemo.java b/xchart-demo/src/main/java/com/xeiam/xchart/demo/XChartDemo.java
index 7232b4b8..3a45c734 100644
--- a/xchart-demo/src/main/java/com/xeiam/xchart/demo/XChartDemo.java
+++ b/xchart-demo/src/main/java/com/xeiam/xchart/demo/XChartDemo.java
@@ -47,12 +47,7 @@ import com.xeiam.xchart.demo.charts.date.DateChart04;
 import com.xeiam.xchart.demo.charts.date.DateChart05;
 import com.xeiam.xchart.demo.charts.date.DateChart06;
 import com.xeiam.xchart.demo.charts.date.DateChart07;
-import com.xeiam.xchart.demo.charts.line.LineChart01;
-import com.xeiam.xchart.demo.charts.line.LineChart02;
-import com.xeiam.xchart.demo.charts.line.LineChart03;
-import com.xeiam.xchart.demo.charts.line.LineChart04;
-import com.xeiam.xchart.demo.charts.line.LineChart05;
-import com.xeiam.xchart.demo.charts.line.LineChart06;
+import com.xeiam.xchart.demo.charts.line.*;
 import com.xeiam.xchart.demo.charts.realtime.RealtimeChart01;
 import com.xeiam.xchart.demo.charts.realtime.RealtimeChart02;
 import com.xeiam.xchart.demo.charts.scatter.ScatterChart01;
@@ -216,6 +211,9 @@ public class XChartDemo extends JPanel implements TreeSelectionListener {
     defaultMutableTreeNode = new DefaultMutableTreeNode(new ChartInfo("LineChart06 - Logarithmic Y-Axis with Error Bars", new LineChart06().getChart()));
     category.add(defaultMutableTreeNode);
 
+    defaultMutableTreeNode = new DefaultMutableTreeNode(new ChartInfo("LineAreaChart07 - Line & Area Chart", new LineAreaChart07().getChart()));
+    category.add(defaultMutableTreeNode);
+
     // Scatter category
     category = new DefaultMutableTreeNode("Scatter Charts");
     top.add(category);
diff --git a/xchart-demo/src/main/java/com/xeiam/xchart/demo/charts/line/LineAreaChart07.java b/xchart-demo/src/main/java/com/xeiam/xchart/demo/charts/line/LineAreaChart07.java
new file mode 100644
index 00000000..39776b99
--- /dev/null
+++ b/xchart-demo/src/main/java/com/xeiam/xchart/demo/charts/line/LineAreaChart07.java
@@ -0,0 +1,257 @@
+/**
+ * Copyright 2011 - 2014 Xeiam LLC.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.xeiam.xchart.demo.charts.line;
+
+import com.xeiam.xchart.*;
+import com.xeiam.xchart.StyleManager.LegendPosition;
+import com.xeiam.xchart.demo.charts.ExampleChart;
+
+import java.awt.*;
+
+/**
+ * Combination Line & Area Chart
+ * <p/>
+ * Demonstrates the following:
+ * <ul>
+ * <li>Combination of Line and Area series
+ * <li>Axis Label Alignment
+ * <li>Ensuring a chart axis on a tick
+ */
+public class LineAreaChart07 implements ExampleChart {
+
+  public static void main(String[] args) {
+
+    ExampleChart exampleChart = new LineAreaChart07();
+    Chart chart = exampleChart.getChart();
+    new SwingWrapper(chart).displayChart();
+  }
+
+  @Override
+  public Chart getChart() {
+
+    // Create Chart
+    Chart chart = new Chart(800, 600);
+
+    // Customize Chart
+    chart.setChartTitle("LineAreaChart07");
+    chart.setXAxisTitle("Age");
+    chart.setYAxisTitle("Amount");
+    chart.getStyleManager().setLegendPosition(LegendPosition.InsideNW);
+
+    double[] xAges = new double[]{
+        60, 61, 62, 63, 64, 65, 66, 67, 68, 69,
+        70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
+        80, 81, 82, 83, 84, 85, 86, 87, 88, 89,
+        90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100};
+
+    double[] yLiability = new double[]{
+        672234,
+        691729,
+        711789,
+        732431,
+        753671,
+        775528,
+        798018,
+        821160,
+        844974,
+        869478,
+        907735,
+        887139,
+        865486,
+        843023,
+        819621,
+        795398,
+        770426,
+        744749,
+        719011,
+        693176,
+        667342,
+        641609,
+        616078,
+        590846,
+        565385,
+        540002,
+        514620,
+        489380,
+        465149,
+        441817,
+        419513,
+        398465,
+        377991,
+        358784,
+        340920,
+        323724,
+        308114,
+        293097,
+        279356,
+        267008,
+        254873
+    };
+
+    double[] yPercentile75th = new double[]{
+        800000,
+        878736,
+        945583,
+        1004209,
+        1083964,
+        1156332,
+        1248041,
+        1340801,
+        1440138,
+        1550005,
+        1647728,
+        1705046,
+        1705032,
+        1710672,
+        1700847,
+        1683418,
+        1686522,
+        1674901,
+        1680456,
+        1679164,
+        1668514,
+        1672860,
+        1673988,
+        1646597,
+        1641842,
+        1653758,
+        1636317,
+        1620725,
+        1589985,
+        1586451,
+        1559507,
+        1544234,
+        1529700,
+        1507496,
+        1474907,
+        1422169,
+        1415079,
+        1346929,
+        1311689,
+        1256114,
+        1221034
+    };
+
+    double[] yPercentile50th = new double[]{
+        800000,
+        835286,
+        873456,
+        927048,
+        969305,
+        1030749,
+        1101102,
+        1171396,
+        1246486,
+        1329076,
+        1424666,
+        1424173,
+        1421853,
+        1397093,
+        1381882,
+        1364562,
+        1360050,
+        1336885,
+        1340431,
+        1312217,
+        1288274,
+        1271615,
+        1262682,
+        1237287,
+        1211335,
+        1191953,
+        1159689,
+        1117412,
+        1078875,
+        1021020,
+        974933,
+        910189,
+        869154,
+        798476,
+        744934,
+        674501,
+        609237,
+        524516,
+        442234,
+        343960,
+        257025
+    };
+
+    double[] yPercentile25th = new double[]{
+        800000,
+        791439,
+        809744,
+        837020,
+        871166,
+        914836,
+        958257,
+        1002955,
+        1054094,
+        1118934,
+        1194071,
+        1185041,
+        1175401,
+        1156578,
+        1132121,
+        1094879,
+        1066202,
+        1054411,
+        1028619,
+        987730,
+        944977,
+        914929,
+        880687,
+        809330,
+        783318,
+        739751,
+        696201,
+        638242,
+        565197,
+        496959,
+        421280,
+        358113,
+        276518,
+        195571,
+        109514,
+        13876,
+        29,
+        0,
+        0,
+        0,
+        0};
+
+    Series seriesLiability = chart.addSeries("Liability", xAges, yLiability);
+    seriesLiability.setMarker(SeriesMarker.NONE);
+    seriesLiability.setSeriesType(Series.SeriesType.Area);
+
+    Series seriesPercentile75th = chart.addSeries("75th Percentile", xAges, yPercentile75th);
+    seriesPercentile75th.setMarker(SeriesMarker.NONE);
+
+    Series seriesPercentile50th = chart.addSeries("50th Percentile", xAges, yPercentile50th);
+    seriesPercentile50th.setMarker(SeriesMarker.NONE);
+
+    Series seriesPercentile25th = chart.addSeries("25th Percentile", xAges, yPercentile25th);
+    seriesPercentile25th.setMarker(SeriesMarker.NONE);
+
+    chart.getStyleManager().setYAxisLabelAlignment(StyleManager.TextAlignment.Right);
+    chart.getStyleManager().setDecimalPattern("$ #,###.##"); // TODO need a different patter for y and x axis
+
+    chart.getStyleManager().setPlotPadding(0);
+    chart.getStyleManager().setAxisTickSpaceRatio(1);
+    chart.getStyleManager().setYAxisMax(1620725 * 1.15); // We want to ensure there is a % of padding on the top of the chart
+    return chart;
+  }
+
+}
diff --git a/xchart/src/main/java/com/xeiam/xchart/Series.java b/xchart/src/main/java/com/xeiam/xchart/Series.java
index 254bcbad..2599fef0 100644
--- a/xchart/src/main/java/com/xeiam/xchart/Series.java
+++ b/xchart/src/main/java/com/xeiam/xchart/Series.java
@@ -32,6 +32,10 @@ import com.xeiam.xchart.internal.style.SeriesColorMarkerLineStyle;
  */
 public class Series {
 
+  public enum SeriesType {
+    Line, Area
+  }
+
   private String name = "";
 
   private Collection<?> xData;
@@ -40,6 +44,8 @@ public class Series {
   private Collection<? extends Number> yData;
   private AxisType yAxisType;
 
+  private SeriesType seriesType;
+
   private Collection<? extends Number> errorBars;
 
   /** the minimum value of axis range */
@@ -60,6 +66,9 @@ public class Series {
   /** Line Color */
   private Color strokeColor;
 
+  /** Fill Colour */
+  private Color fillColor;
+
   /** Marker Style */
   private Marker marker;
 
@@ -91,6 +100,7 @@ public class Series {
     this.errorBars = errorBars;
 
     strokeColor = seriesColorMarkerLineStyle.getColor();
+    fillColor = seriesColorMarkerLineStyle.getColor();
     markerColor = seriesColorMarkerLineStyle.getColor();
     marker = seriesColorMarkerLineStyle.getMarker();
     stroke = seriesColorMarkerLineStyle.getStroke();
@@ -241,6 +251,14 @@ public class Series {
     return this;
   }
 
+  public SeriesType getSeriesType() {
+    return seriesType;
+  }
+
+  public void setSeriesType(SeriesType seriesType) {
+    this.seriesType = seriesType;
+  }
+
   public Collection<?> getXData() {
 
     return xData;
@@ -296,6 +314,14 @@ public class Series {
     return markerColor;
   }
 
+  public Color getFillColor() {
+    return fillColor;
+  }
+
+  public void setFillColor(Color fillColor) {
+    this.fillColor = fillColor;
+  }
+
   public String getName() {
 
     return name;
diff --git a/xchart/src/main/java/com/xeiam/xchart/StyleManager.java b/xchart/src/main/java/com/xeiam/xchart/StyleManager.java
index 0856bb70..e3743a98 100644
--- a/xchart/src/main/java/com/xeiam/xchart/StyleManager.java
+++ b/xchart/src/main/java/com/xeiam/xchart/StyleManager.java
@@ -66,6 +66,12 @@ public class StyleManager {
     }
   }
 
+  public enum TextAlignment {
+    Left,
+    Centre,
+    Right;
+  }
+
   /** the default Theme */
   private Theme theme = new XChartTheme();
 
@@ -117,6 +123,8 @@ public class StyleManager {
   private Double yAxisMin;
   private Double yAxisMax;
   private double axisTickSpaceRatio;
+  private TextAlignment xAxisLabelAlignment = TextAlignment.Centre;
+  private TextAlignment yAxisLabelAlignment = TextAlignment.Left;
 
   // Chart Plot Area ///////////////////////////////
   private boolean isPlotGridLinesVisible;
@@ -872,6 +880,23 @@ public class StyleManager {
     return axisTickSpaceRatio;
   }
 
+  public TextAlignment getXAxisLabelAlignment() {
+    return xAxisLabelAlignment;
+  }
+
+  public void setXAxisLabelAlignment(TextAlignment xAxisLabelAlignment) {
+    this.xAxisLabelAlignment = xAxisLabelAlignment;
+  }
+
+  public TextAlignment getYAxisLabelAlignment() {
+    return yAxisLabelAlignment;
+  }
+
+  public void setYAxisLabelAlignment(TextAlignment yAxisLabelAlignment) {
+    this.yAxisLabelAlignment = yAxisLabelAlignment;
+  }
+
+
   // Chart Plot Area ///////////////////////////////
 
   /**
diff --git a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/AxisPair.java b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/AxisPair.java
index 03eb1fae..5f5d3e44 100644
--- a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/AxisPair.java
+++ b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/AxisPair.java
@@ -15,21 +15,15 @@
  */
 package com.xeiam.xchart.internal.chartpart;
 
-import java.awt.Graphics2D;
-import java.awt.Rectangle;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Date;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-
 import com.xeiam.xchart.Series;
 import com.xeiam.xchart.StyleManager.ChartType;
 import com.xeiam.xchart.internal.chartpart.Axis.AxisType;
 import com.xeiam.xchart.internal.style.SeriesColorMarkerLineStyleCycler;
 
+import java.awt.*;
+import java.util.*;
+import java.util.List;
+
 /**
  * @author timmolter
  */
@@ -115,6 +109,14 @@ public class AxisPair implements ChartPart {
       series = new Series(seriesName, generatedXData, xAxis.getAxisType(), yData, yAxis.getAxisType(), errorBars, seriesColorMarkerLineStyleCycler.getNextSeriesColorMarkerLineStyle());
     }
 
+    switch (chartPainter.getStyleManager().getChartType()) {
+      case Area:
+        series.setSeriesType(Series.SeriesType.Area);
+        break;
+      case Line:
+        series.setSeriesType(Series.SeriesType.Line);
+    }
+
     // Sanity check
     if (xData != null && xData.size() != yData.size()) {
       throw new IllegalArgumentException("X and Y-Axis sizes are not the same!!!");
diff --git a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/AxisTickLabels.java b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/AxisTickLabels.java
index a9d30804..849de832 100644
--- a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/AxisTickLabels.java
+++ b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/AxisTickLabels.java
@@ -15,12 +15,13 @@
  */
 package com.xeiam.xchart.internal.chartpart;
 
-import java.awt.Graphics2D;
-import java.awt.Shape;
+import java.awt.*;
 import java.awt.font.FontRenderContext;
 import java.awt.font.TextLayout;
 import java.awt.geom.AffineTransform;
 import java.awt.geom.Rectangle2D;
+import java.util.HashMap;
+import java.util.Map;
 
 /**
  * Axis tick labels
@@ -58,38 +59,58 @@ public class AxisTickLabels implements ChartPart {
 
     if (axisTick.getAxis().getDirection() == Axis.Direction.Y && getChartPainter().getStyleManager().isYAxisTicksVisible()) { // Y-Axis
 
-      double xOffset = axisTick.getAxis().getAxisTitle().getBounds().getX() + axisTick.getAxis().getAxisTitle().getBounds().getWidth();
+      double xWidth = axisTick.getAxis().getAxisTitle().getBounds().getWidth();
+      double xOffset = axisTick.getAxis().getAxisTitle().getBounds().getX() + xWidth;
       double yOffset = axisTick.getAxis().getPaintZone().getY();
       double height = axisTick.getAxis().getPaintZone().getHeight();
       double maxTickLabelWidth = 0;
+      Map<Double, TextLayout> axisLabelTextLayouts = new HashMap<Double, TextLayout>();
 
       for (int i = 0; i < axisTick.getTickLabels().size(); i++) {
-
         String tickLabel = axisTick.getTickLabels().get(i);
-        // System.out.println(tickLabel);
         double tickLocation = axisTick.getTickLocations().get(i);
         double flippedTickLocation = yOffset + height - tickLocation;
 
         if (tickLabel != null && flippedTickLocation > yOffset && flippedTickLocation < yOffset + height) { // some are null for logarithmic axes
-
           FontRenderContext frc = g.getFontRenderContext();
-          TextLayout layout = new TextLayout(tickLabel, getChartPainter().getStyleManager().getAxisTickLabelsFont(), frc);
-          Rectangle2D tickLabelBounds = layout.getBounds();
+          TextLayout axisLabelTextLayout = new TextLayout(tickLabel, getChartPainter().getStyleManager().getAxisTickLabelsFont(), frc);
+          Rectangle2D tickLabelBounds = axisLabelTextLayout.getBounds();
+          double boundWidth = tickLabelBounds.getWidth();
+          if (boundWidth > maxTickLabelWidth) {
+            maxTickLabelWidth = boundWidth;
+          }
+          axisLabelTextLayouts.put(tickLocation, axisLabelTextLayout);
+        }
+      }
 
-          Shape shape = layout.getOutline(null);
+      for (Double tickLocation : axisLabelTextLayouts.keySet()) {
 
-          AffineTransform orig = g.getTransform();
-          AffineTransform at = new AffineTransform();
-          at.translate(xOffset, flippedTickLocation + tickLabelBounds.getHeight() / 2.0);
-          g.transform(at);
-          g.fill(shape);
-          g.setTransform(orig);
+        TextLayout axisLabelTextLayout = axisLabelTextLayouts.get(tickLocation);
+        Rectangle2D tickLabelBounds = axisLabelTextLayout.getBounds();
+        Shape shape = axisLabelTextLayout.getOutline(null);
 
-          if (tickLabelBounds.getWidth() > maxTickLabelWidth) {
-            maxTickLabelWidth = tickLabelBounds.getWidth();
-          }
+        double flippedTickLocation = yOffset + height - tickLocation;
 
+        AffineTransform orig = g.getTransform();
+        AffineTransform at = new AffineTransform();
+        double boundWidth = tickLabelBounds.getWidth();
+        double xPos;
+        switch (getChartPainter().getStyleManager().getYAxisLabelAlignment()) {
+          case Right:
+            xPos = xOffset + maxTickLabelWidth - boundWidth;
+            break;
+          case Centre:
+            xPos = xOffset + (maxTickLabelWidth - boundWidth) / 2;
+            break;
+          case Left:
+          default:
+            xPos = xOffset;
         }
+        at.translate(xPos, flippedTickLocation + tickLabelBounds.getHeight() / 2.0);
+        g.transform(at);
+        g.fill(shape);
+        g.setTransform(orig);
+
       }
 
       // bounds
@@ -124,7 +145,19 @@ public class AxisTickLabels implements ChartPart {
 
           AffineTransform orig = g.getTransform();
           AffineTransform at = new AffineTransform();
-          at.translate(shiftedTickLocation - tickLabelBounds.getWidth() / 2.0, yOffset);
+          double xPos;
+          switch (getChartPainter().getStyleManager().getXAxisLabelAlignment()) {
+            case Left:
+              xPos = shiftedTickLocation;
+              break;
+            case Right:
+              xPos = shiftedTickLocation - tickLabelBounds.getWidth();
+              break;
+            case Centre:
+            default:
+              xPos = shiftedTickLocation - tickLabelBounds.getWidth() / 2.0;
+          }
+          at.translate(xPos, yOffset);
           g.transform(at);
           g.fill(shape);
           g.setTransform(orig);
diff --git a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/PlotContentLineChart.java b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/PlotContentLineChart.java
index 27f39f03..076dbe42 100644
--- a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/PlotContentLineChart.java
+++ b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/PlotContentLineChart.java
@@ -181,11 +181,11 @@ public class PlotContentLineChart extends PlotContent {
         }
 
         // paint area
-        if (getChartPainter().getStyleManager().getChartType() == ChartType.Area) {
+        if (getChartPainter().getStyleManager().getChartType() == ChartType.Area || Series.SeriesType.Area.equals(series.getSeriesType())) {
 
           if (previousX != Integer.MIN_VALUE && previousY != Integer.MIN_VALUE) {
 
-            g.setColor(series.getStrokeColor());
+            g.setColor(series.getFillColor());
             double yBottomOfArea = bounds.getY() + bounds.getHeight() - yTopMargin;
 
             if (path == null) {
-- 
GitLab