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