diff --git a/XChart/.github/ISSUE_TEMPLATE/bug_report.md b/XChart/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 0000000000000000000000000000000000000000..548f06c5762e815e74efafcc3be66de62d41aecd
--- /dev/null
+++ b/XChart/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,49 @@
+---
+name: Bug report
+about: Create a report to help us improve
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+**Describe the bug**
+A clear and concise description of what the bug is.
+
+**To Reproduce**
+Please make a code snippet demonstrating the bug using the following sample chart code:
+```
+public class TestForIssue834 {
+
+  public static void main(String[] args) throws ParseException {
+
+    XYChart chart = getXYChart();
+    new SwingWrapper(chart).displayChart();
+  }
+
+  public static XYChart getXYChart() {
+    XYChart chart =
+        new XYChartBuilder()
+            .width(720)
+            .height(480)
+            .title("Buggy Example")
+            .xAxisTitle("Count")
+            .yAxisTitle("Value")
+            .build();
+
+
+    double[] xValues = new double[] {1, 2, 3};
+    double[] yValues = new double[] {1, 2, 3};
+    chart.addSeries("main", xValues, yValues);
+
+    return chart;
+  }
+}
+```
+
+**Screenshots**
+Add screenshots to help explain your problem.
+
+
+**Expected behavior**
+A clear and concise description of what you expected to happen.
diff --git a/XChart/.github/dependabot.yml b/XChart/.github/dependabot.yml
new file mode 100644
index 0000000000000000000000000000000000000000..2d475ce374d96fef7a762773254217feae64518b
--- /dev/null
+++ b/XChart/.github/dependabot.yml
@@ -0,0 +1,15 @@
+version: 2
+updates:
+- package-ecosystem: maven
+  directory: "/"
+  schedule:
+    interval: daily
+    time: "12:00"
+  open-pull-requests-limit: 10
+  ignore:
+  - dependency-name: org.openjfx:javafx-controls
+    versions:
+    - 15.0.1
+  - dependency-name: org.openjfx:javafx-swing
+    versions:
+    - 15.0.1
diff --git a/XChart/.github/workflows/maven_on_pull_request.yml b/XChart/.github/workflows/maven_on_pull_request.yml
new file mode 100644
index 0000000000000000000000000000000000000000..1e3fe7d75be1985d7b85079367cf186ef18d677a
--- /dev/null
+++ b/XChart/.github/workflows/maven_on_pull_request.yml
@@ -0,0 +1,23 @@
+# This workflow will build a Java project with Maven
+# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
+
+name: Java CI with Maven on Pull Request
+
+on:
+  pull_request:
+    branches: [ develop ]
+
+jobs:
+  build:
+
+    runs-on: ubuntu-latest
+
+    steps:
+    - uses: actions/checkout@v2
+    - name: Set up JDK 11
+      uses: actions/setup-java@v2
+      with:
+        java-version: '11'
+        distribution: 'adopt'
+    - name: Build with Maven
+      run: mvn clean verify --no-transfer-progress --batch-mode --settings etc/settings.xml -Dmaven.javadoc.skip=true -Dfmt.skip=true;
diff --git a/XChart/.github/workflows/maven_on_push.yml b/XChart/.github/workflows/maven_on_push.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c6910579eba48290567bbb1aab2a7cdbb49d96ad
--- /dev/null
+++ b/XChart/.github/workflows/maven_on_push.yml
@@ -0,0 +1,29 @@
+# This workflow will build a Java project with Maven
+# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
+
+name: Java CI with Maven on Push
+
+on:
+  push:
+    branches: [ develop ]
+
+jobs:
+  build:
+
+    runs-on: ubuntu-latest
+
+    steps:
+    - uses: actions/checkout@v2
+    - name: Set up JDK 11
+      uses: actions/setup-java@v2
+      with:
+        java-version: '11'
+        distribution: 'adopt'
+    - name: Build with Maven
+      run: mvn clean install --no-transfer-progress --batch-mode --settings etc/settings.xml -Dfmt.skip=true
+    - name: Deploy with Maven
+      run: mvn clean deploy --no-transfer-progress --batch-mode --settings etc/settings.xml -Dfmt.skip=true;
+      if: github.repository_owner == 'knowm'
+      env:
+        CI_DEPLOY_USERNAME: ${{ secrets.CI_DEPLOY_USERNAME }}
+        CI_DEPLOY_PASSWORD: ${{ secrets.CI_DEPLOY_PASSWORD }}
diff --git a/XChart/.gitignore b/XChart/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..96366192c6de00b89245c003683e9bcce959bb30
--- /dev/null
+++ b/XChart/.gitignore
@@ -0,0 +1,33 @@
+# IntelliJ
+.idea/
+*.iws
+*.iml
+*.ipr
+*.ips
+.idea/
+
+
+# Eclipse
+.settings/
+.metadata/
+.classpath
+.project
+
+# Generated
+target/
+javadoc/
+bin/
+log/
+
+# Misc.
+.DS_Store
+*.bak
+
+# Sample images
+*.jpg
+*.bmp
+*.svg
+*.pdf
+*.eps
+*.png
+*.gif
diff --git a/XChart/CONTRIBUTORS b/XChart/CONTRIBUTORS
new file mode 100644
index 0000000000000000000000000000000000000000..41e5e2452b8d6c4ac8f412e7fe40411b0eb515c8
--- /dev/null
+++ b/XChart/CONTRIBUTORS
@@ -0,0 +1,83 @@
+XChart is developed by Knowm Inc. members and the open-source community.
+
+We thank all of our contributors: https://github.com/timmolter/xchart/graphs/contributors 
+
+For the detailed history of contributions of a given file, try
+
+    git blame file
+
+To see line-by-line credits and to see the change log even across renames and rewrites, try
+
+    git log --follow file
+
+Copyright is held by the original contributor according to the versioning history; see NOTICE.
+
+The following list of authors was automatically generated from the XChart project's git repo with the command:
+
+    git log --format='%aN' | sort -u
+
+Adam Walsh
+Alex Kofke
+Alex Nugent
+AlexanderRadaev
+Anthony Quiros
+Arthur Peters
+Bryan Cardillo
+Bryn Jeffries
+Carlos Lopez-Camey
+Cedric Martineau
+Chiamh
+Christoffer SOOP
+Colm Costelloe
+Damian Szczypior
+Ekkart Kleinod
+Emil Sauer Lynge
+Flole998
+Greg Oledzki
+Grzegorz Olędzki
+Hakan
+Hakan Işıktekin
+Hongjia Cao
+Hua Shao
+Hwaipy Li
+Jean Niklas L'orange
+Jonathan Leitschuh
+Juha-Matti Tilli
+Marc Jakobi
+Marshall Pierce
+Martin Crawford
+Matan Rubin
+Michael Stummvoll
+Mike Jensen
+Mr14huashao
+Mykola Makhin
+Nathan Klick
+Nicolas Roduit
+Niklas Polke
+Ramon Chiara
+Ross Jourdain
+Roughy
+Sebastian Jaenicke
+Sylvain Furt
+Thomas Diesler
+Thomas Neidhart
+Tim Molter
+TomTroppmann
+Tomas Svensson
+Tornai András, PMK-ACS-SWA
+alessiosavi
+alexmaycon
+dependabot-preview[bot]
+dependabot[bot]
+gitkonst
+hakantkn
+heavySea
+kalan
+kibertoad
+rebaomi
+ruX[Ruslan Zaharov]
+timmolter
+yuan
+zhzzang
+
+
diff --git a/XChart/LICENSE b/XChart/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..c9683962aef689c9ca2f407959941ad72959fb96
--- /dev/null
+++ b/XChart/LICENSE
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!) The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   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.
diff --git a/XChart/NOTICE b/XChart/NOTICE
new file mode 100644
index 0000000000000000000000000000000000000000..de896e49386d25283dcf46478c5c50f388cab0f9
--- /dev/null
+++ b/XChart/NOTICE
@@ -0,0 +1,14 @@
+Copyright 2015-2023 Knowm Inc. (http://knowm.org) and contributors.
+Copyright 2011-2015 Xeiam LLC (http://xeiam.com) and contributors.
+
+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.
diff --git a/XChart/README.md b/XChart/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..b356a1cfc71fb5c2b3757ed205e43f2939f1742e
--- /dev/null
+++ b/XChart/README.md
@@ -0,0 +1,663 @@
+## [![XChart](https://raw.githubusercontent.com/knowm/XChart/develop/etc/XChart_64_64.png)](http://knowm.org/open-source/xchart) XChart
+
+XChart is a light-weight Java library for plotting data.
+
+## Description
+
+XChart is a light-weight and convenient library for plotting data designed to go from data to chart in the least amount of time possible and to take the guess-work out of
+customizing the chart style.
+
+## Simplest Example
+
+Create a `XYChart` instance via `QuickChart`, add a series of data to it, and either display it or save it as a bitmap.
+
+```java
+
+double[] xData = new double[]{0.0, 1.0, 2.0};
+double[] yData = new double[]{2.0, 1.0, 0.0};
+
+// Create Chart
+XYChart chart = QuickChart.getChart("Sample Chart", "X", "Y", "y(x)", xData, yData);
+
+// Show it
+new SwingWrapper(chart).displayChart();
+
+// Save it
+BitmapEncoder.saveBitmap(chart, "./Sample_Chart",BitmapFormat.PNG);
+
+// or save it in high-res
+BitmapEncoder.saveBitmapWithDPI(chart, "./Sample_Chart_300_DPI",BitmapFormat.PNG, 300);
+```
+
+![](https://raw.githubusercontent.com/knowm/XChart/develop/etc/XChart_Simplest.png)
+
+## Intermediate Example
+
+Create a `XYChart` via a `XYChartBuilder`, style chart, add a series to it, style series, and display chart.
+
+```java
+
+// Create Chart
+XYChart chart = new XYChartBuilder().width(600).height(500).title("Gaussian Blobs").xAxisTitle("X").yAxisTitle("Y").build();
+
+// Customize Chart
+chart.getStyler().setDefaultSeriesRenderStyle(XYSeriesRenderStyle.Scatter);
+chart.getStyler().setChartTitleVisible(false);
+chart.getStyler().setLegendPosition(LegendPosition.InsideSW);
+chart.getStyler().setMarkerSize(16);
+
+// Series
+chart.addSeries("Gaussian Blob 1",getGaussian(1000, 1,10),getGaussian(1000,1,10));
+XYSeries series = chart.addSeries("Gaussian Blob 2", getGaussian(1000, 1, 10), getGaussian(1000, 0, 5));
+series.setMarker(SeriesMarkers.DIAMOND);
+
+new SwingWrapper(chart).displayChart();
+```
+
+![](https://raw.githubusercontent.com/knowm/XChart/develop/etc/XChart_Intermediate.png)
+
+## Advanced Example
+
+Create a `XYChart` via a `XYChartBuilder`, style chart, add a series to it, add chart to `XChartPanel`, embed in Java Swing App, and display GUI.
+
+```java
+
+// Create Chart
+final XYChart chart = new XYChartBuilder().width(600).height(400).title("Area Chart").xAxisTitle("X").yAxisTitle("Y").build();
+
+// Customize Chart
+chart.getStyler().setLegendPosition(LegendPosition.InsideNE);
+chart.getStyler().setDefaultSeriesRenderStyle(XYSeriesRenderStyle.Area);
+
+// Series
+chart.addSeries("a",new double[] { 0, 3, 5, 7, 9},new double[]{-3,5,9,6,5});
+chart.addSeries("b",new double[] { 0, 2, 4, 6, 9},new double[]{-1,6,4,0,4});
+chart.addSeries("c",new double[] { 0, 1, 3, 8, 9},new double[]{-2,-1,1,0,1});
+
+// Schedule a job for the event-dispatching thread:
+// creating and showing this application's GUI.
+        javax.swing.SwingUtilities.
+
+invokeLater(new Runnable() {
+
+    @Override
+    public void run () {
+
+        // Create and set up the window.
+        JFrame frame = new JFrame("Advanced Example");
+        frame.setLayout(new BorderLayout());
+        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+
+        // chart
+        JPanel chartPanel = new XChartPanel<XYChart>(chart);
+        frame.add(chartPanel, BorderLayout.CENTER);
+
+        // label
+        JLabel label = new JLabel("Blah blah blah.", SwingConstants.CENTER);
+        frame.add(label, BorderLayout.SOUTH);
+
+        // Display the window.
+        frame.pack();
+        frame.setVisible(true);
+    }
+});
+
+```
+
+![](https://raw.githubusercontent.com/knowm/XChart/develop/etc/XChart_Advanced.png)
+
+To make it real-time, simply call `updateXYSeries` on the `XYChart` instance to update the series data, followed by `revalidate()` and `repaint()` on the `XChartPanel` instance to
+repaint.
+
+## Features
+
+* [x] No *required* additional dependencies
+* [x] Multiple Y-Axis charts
+* [x] Line charts
+* [x] Step charts
+* [x] Scatter charts
+* [x] Area charts
+* [x] Step Area charts
+* [x] Bar charts
+* [x] Histogram charts
+* [x] Pie charts
+* [x] Donut charts
+* [x] Bubble charts
+* [x] Stick charts
+* [x] Dial charts
+* [x] Radar charts
+* [x] OHLC charts
+* [x] Box charts
+* [x] Heat maps
+* [x] Error bars
+* [x] Logarithmic axes
+* [x] Number, Date, Bubble and Category X-Axis
+* [x] Multiple series
+* [x] Tool tips
+* [x] Extensive customization
+* [x] Themes - XChart, GGPlot2, Matlab
+* [x] Right-click, Save-As...
+* [x] User-defined axes range
+* [x] Definable legend placement
+* [x] CSV import and export
+* [x] High resolution chart export
+* [x] Export as PNG, JPG, BMP, GIF with custom DPI setting
+* [x] Export SVG, EPS using optional `de.erichseifert.vectorgraphics2d` library
+* [x] Export PDF using optional `pdfbox-graphics2d` library
+* [x] Real-time charts
+* [x] Java 8 and up
+
+## Chart Types
+
+Currently, there are 5 major chart types. Each type has its corresponding `ChartBuilder`, `Styler` and `Series`.
+
+| Chart Type    | Builder              | Styler         | Series         | Allowed Data Types   | Default Series Render Style |
+|---------------|----------------------|----------------|----------------|----------------------|-----------------------------|
+| XYChart       | XYChartBuilder       | XYStyler       | XYSeries       | Number, Date         | Line                        |
+| CategoryChart | CategoryChartBuilder | CategoryStyler | CategorySeries | Number, Date, String | Bar                         |
+| PieChart      | PieChartBuilder      | PieStyler      | PieSeries      | String               | Pie                         |
+| BubbleChart   | BubbleChartBuilder   | BubbleStyler   | BubbleSeries   | Number, Date         | Round                       |
+| DialChart     | DialChartBuilder     | DialStyler     | DialSeries     | double               | Round                       |
+| RadarChart    | RadarChartBuilder    | RadarStyler    | RadarSeries    | double[]             | Round                       |
+| OHLCChart     | OHLCChartBuilder     | OHLCStyler     | OHLCSeries     | OHLC with Date       | Candle                      |
+| BoxChart      | BoxChartBuilder      | BoxStyler      | BoxSeries      | Number, Date, String | Box                         |
+| HeatMapChart  | HeatMapChartBuilder  | HeatMapStyler  | HeatMapSeries  | Number, Date, String | --                          |
+
+The different Stylers contain chart styling methods specific to the corresponding chart type as well as common styling methods common across all chart types.
+
+### XYChart
+
+![](https://raw.githubusercontent.com/knowm/XChart/develop/etc/XChart_XYChart.png)
+
+`XYChart` charts take Date or Number data types for the X-Axis and Number data types for the Y-Axis. For both axes, the tick marks are auto generated to span the range and domain
+of the data in evenly-spaced intervals.
+
+Series render styles include: `Line`, `Scatter`, `Area`, `Step` and `StepArea`.
+
+### CategoryChart
+
+![](https://raw.githubusercontent.com/knowm/XChart/develop/etc/XChart_CategoryChart.png)
+
+`CategoryChart` charts take Date, Number or String data types for the X-Axis and Number data types for the Y-Axis. For the X-Axis, each category is given its own tick mark.
+
+Series render styles include: `Bar`, `Line`, `Scatter`, `Area` and `Stick`.
+
+### PieChart
+
+![](https://raw.githubusercontent.com/knowm/XChart/develop/etc/XChart_PieChart.png)
+
+`PieChart` charts take String data types for the pie slice name and Number data types for the pie slice value.
+
+Series render styles include: `Pie` and `Donut`.
+
+### BubbleChart
+
+![](https://raw.githubusercontent.com/knowm/XChart/develop/etc/XChart_Bubble_Chart.png)
+
+`BubbleChart` charts take Date or Number data types for the X-Axis and Number data types for the Y-Axis and bubble sizes.
+
+Series render styles include: `Round` and in the near future `Square`.
+
+### DialChart
+
+![](https://raw.githubusercontent.com/knowm/XChart/develop/etc/XChart_Dial_Chart.png)
+
+`DialChart` charts take a `double` to set the position of the dial pointer and a `String` to set the label. Extensive customization is possible.
+
+### RadarChart
+
+![](https://raw.githubusercontent.com/knowm/XChart/develop/etc/XChart_Radar_Chart.png)
+
+`RadarChart` charts take a `double[]` of values between `0.0.` and `1.0` to set the position of the series' data point along each radii. Radii
+labels, if displayed, are set by passing a `String[]`.
+
+Radar chart render styles are: `Polygon` or `Circle`.
+
+### OHLCChart
+
+![](https://raw.githubusercontent.com/knowm/XChart/develop/etc/XChart_OHLC.png)
+
+`OHLCChart` charts take Date data types for the X-Axis and 4 Number data types for the Y-Axis. For both axes, the tick marks are auto generated to span the range and domain of the
+data in evenly-spaced intervals.
+
+Series render styles include: `Candle`, `HiLo`.
+
+### BoxChart
+
+![](https://raw.githubusercontent.com/knowm/XChart/develop/etc/XChart_BoxChart.png)
+
+`BoxChart` charts take String data (seriesNames) types for the X-Axis and Number data types for the Y-Axis. Each box chart is calculated from the corresponding series yData.
+Create a BoxChart via a BoxChartBuilder, style chart, add a series to it.
+
+```java
+// Create Chart
+BoxChart chart =
+        new BoxChartBuilder().title("box plot demo").build();
+
+// Choose a calculation method
+chart.getStyler().setBoxplotCalCulationMethod(BoxplotCalCulationMethod.N_LESS_1_PLUS_1);
+chart.getStyler().setToolTipsEnabled(true);
+
+// Series
+chart.addSeries("boxOne",Arrays.asList(1,2,3,4));
+new SwingWrapper<BoxChart>(chart).displayChart();
+```
+
+Four calculation methods for boxplots:
+
+- "N_PLUS_1": determine the position of the quartile, where Qi is = i (n + 1) / 4, where i = 1, 2, and 3. n represents the number of items contained in the sequence.
+  Calculate the corresponding quartile based on location.
+- "N_LESS_1": Determine the position of the quartile, where Qi is = i (n-1) / 4, where i = 1, 2, and 3. n represents the number of items contained in the sequence.
+  Calculate the corresponding quartile based on location.
+- "NP": Determine the position of the quartile, where Qi is np = (i * n) / 4, where i = 1, 2, and 3. n represents the number of items contained in the sequence.
+  If np is not an integer, Qi = X [np + 1];
+  If np is an integer, Qi = (X [np] + X [np + 1]) / 2.
+- "N_LESS_1_PLUS_1": Determine the position of the quartile, where Qi is = i (n-1) / 4 + 1, where i = 1, 2, 3. n represents the number of items contained in the sequence.
+  Calculate the corresponding quartile based on location.
+
+Interquartile range, IQR = Q3-Q1.
+
+Upper whisker = Q3 + 1.5 * IQR = Q3 + 1.5 * (Q3 - Q1), if Upper whisker is greater than the maximum value of yData, Upper whisker = maximum value of yData.
+
+Lower whisker = Q1 - 1.5 * IQR = Q1 - 1.5 * (Q3 -Q1), if the lower whisker is less than the minimum value of yData, the lower whisker = the minimum value of yData.
+
+E.g:
+
+An example of a set of sequence numbers: 12, 15, 17, 19, 20, 23, 25, 28, 30, 33, 34, 35, 36, 37
+
+- "N_PLUS_1":
+  Q1's position = (14 + 1) /4=3.75,
+  Q1 = 0.25 × third term + 0.75 × fourth term = 0.25 × 17 + 0.75 × 19 = 18.5;
+- "N_LESS_1":
+  Q1's location = (14-1) /4=3.25,
+  Q1 = 0.75 × third term + 0.25 × fourth term = 0.75 × 17 + 0.25 × 19 = 17.5;
+- "NP":
+  Q1's position = 14 * 0.25 = 3.5,
+  Q1 = 19;
+- "N_LESS_1_PLUS_1":
+  Q1's location = (14-1) / 4 + 1 = 4.25
+  Q1 = 0.75 × the fourth term + 0.25 × the fifth term = 0.75 × 19 + 0.25 × 20 = 19.25.
+
+### HeatMapChart
+
+![](https://raw.githubusercontent.com/knowm/XChart/develop/etc/XChart_HeatMap.png)
+
+`HeatMapChart` take Date, Number or String data types for the X-Axis, Y-Axis.
+
+## Real-time Java Charts using XChart
+
+![](https://raw.githubusercontent.com/knowm/XChart/develop/etc/XChart_SimpleRealtime.gif)
+
+Creating real-time charts is as simple as calling `updateXYSeries` for one or more series objects through the `XYChart` instance and triggering a redraw of the `JPanel` containing
+the chart. This works for all chart types including `XYChart`, `CategoryChart`, `BubbleChart` and `PieChart`, for which example source code can be
+found [here](https://github.com/knowm/XChart/tree/develop/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/realtime). Examples demonstrate using the `SwingWrapper`
+with `repaintChart()` method as well as `XChartPanel` with `revalidate()` and `repaint()`.
+
+The following sample code used to generate the above real-time chart can be
+found [here](https://github.com/knowm/XChart/blob/develop/xchart-demo/src/main/java/org/knowm/xchart/standalone/readme/SimpleRealTime.java).
+
+```java
+public class SimpleRealTime {
+
+    public static void main(String[] args) throws Exception {
+
+        double phase = 0;
+        double[][] initdata = getSineData(phase);
+
+        // Create Chart
+        final XYChart chart = QuickChart.getChart("Simple XChart Real-time Demo", "Radians", "Sine", "sine", initdata[0], initdata[1]);
+
+        // Show it
+        final SwingWrapper<XYChart> sw = new SwingWrapper<XYChart>(chart);
+        sw.displayChart();
+
+        while (true) {
+
+            phase += 2 * Math.PI * 2 / 20.0;
+
+            Thread.sleep(100);
+
+            final double[][] data = getSineData(phase);
+
+            javax.swing.SwingUtilities.invokeLater(new Runnable() {
+
+                @Override
+                public void run() {
+
+                    chart.updateXYSeries("sine", data[0], data[1], null);
+                    sw.repaintChart();
+                }
+            });
+        }
+
+    }
+
+    private static double[][] getSineData(double phase) {
+
+        double[] xData = new double[100];
+        double[] yData = new double[100];
+        for (int i = 0; i < xData.length; i++) {
+            double radians = phase + (2 * Math.PI / xData.length * i);
+            xData[i] = radians;
+            yData[i] = Math.sin(radians);
+        }
+        return new double[][]{xData, yData};
+    }
+}
+```
+
+## Chart Customization
+
+All the styling options can be found in one of two possible places: 1) the Chart's `Styler` or 2) the series' `set` methods. With this chart customization design, all customization
+options can be quickly "discovered" using an IDE's built in "Content Assist". With centralized styling like this, there is no need to hunt around the entire charting API to find
+that one customization you're looking for - it's all right in one spot!
+
+![](https://raw.githubusercontent.com/knowm/XChart/develop/etc/XChart_Chart_Customization.png)
+
+![](https://raw.githubusercontent.com/knowm/XChart/develop/etc/XChart_Series_Customization.png)
+
+### Customizing Axis Tick Labels
+
+XChart automatically creates axis tick labels for chart types with axes.
+
+Default axis tick placement can be altered with `chart.getStyler().setXAxisTickMarkSpacingHint(spacingHint);`.
+
+Default axis label labels can be altered with one of:
+
+```java
+chart.getStyler().setDatePattern(datePattern)
+chart.getStyler().setXAxisDecimalPattern(pattern);
+chart.getStyler().setYAxisDecimalPattern(pattern);
+```
+
+You can also create custom axis tick labels with a callback function. In the following example taken
+from [DateChart09](https://github.com/knowm/XChart/blob/develop/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/date/DateChart09.java), the X-Axis tick labels are generated
+via a custom lambda function which takes the numerical (double) tick label values and converts them to a `String`.
+
+```java
+// set custom X-Axis tick labels
+LocalDateTime startTime = LocalDateTime.of(2001, Month.JANUARY, 1, 0, 0, 0);
+DateTimeFormatter xTickFormatter = DateTimeFormatter.ofPattern("LLL");
+chart.getStyler().setxAxisTickLabelsFormattingFunction(x ->startTime.plusDays(x.longValue()).format(xTickFormatter));
+```
+
+In the following example taken from [DateChart06](https://github.com/knowm/XChart/blob/develop/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/date/DateChart06.java), the
+Y-Axis tick labels are converted
+to the englich word reprentation of the numbers.
+
+```java
+chart.getStyler().setyAxisTickLabelsFormattingFunction(x ->NumberWordConverter.convert(x.intValue()));
+```
+
+### Multiple Axes
+
+XChart has multiple y axes feature. Y offset is calculated according to the Y-Axis the series configured. Max `y` value in this axis is calculated
+according to the series on this axis only.
+To set the y group:
+
+```java
+series.setYAxisGroup(axisGroup);   
+```
+
+To manually change max/min of axis group:
+
+```java
+((AxesChartStyler)chart.getStyler()).setYAxisMax(axisGroup, 200.0);
+```
+
+Axis can be drawn on the left (default) or on the right of the chart:
+
+```java
+chart.getStyler().setYAxisGroupPosition(axisGroup, Styler.YAxisPosition.Right);
+```
+
+To set the Y axes titles:
+
+```java
+chart.setYAxisGroupTitle(0,"A");
+chart.setYAxisGroupTitle(1,"B");
+```
+
+### Zooming In
+
+For the `XYChart` chart type, zooming in is possible on an `XChartPanel` via select-dragging over a range on the X-Axis. Reverting out of the zoom can be accomplished by
+double-clicking on the chart or by clicking on the "reset" button, which can be posotioned as desired.
+
+![](https://raw.githubusercontent.com/knowm/XChart/develop/etc/XChart_Zoom.png)
+
+The following example zoom style options show which are available:
+
+```java
+chart.getStyler().setZoomEnabled(true);
+chart.getStyler().setZoomResetButtomPosition(Styler.CardinalPosition.InsideS);
+chart.getStyler().setZoomResetByDoubleClick(false);
+chart.getStyler().setZoomResetByButton(true);
+chart.getStyler().setZoomSelectionColor(new Color(0,0,192,128));
+```
+
+A working example can be found at [DateChart01](https://github.com/knowm/XChart/blob/develop/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/date/DateChart01.java).
+
+### Chart Annotations
+
+For all chart types, one or more chart annotations can be super-imposed on top of the chart. The following types of annotatins are available:
+
+- AnnotationLine
+- AnnotationImage
+- AnnotationText
+- AnnotationTextPanel
+
+The following is a chart with four `AnnotationLine`s, one `AnnotationImage` and one `AnnotationText`:
+
+![](https://raw.githubusercontent.com/knowm/XChart/develop/etc/XChart_AnnotationLineImageText.png)
+
+Positioning is relative to the bottom-left corner of the chart and to the center of the `AnnotationImage` or `AnnotationText`.
+
+The following example `AnnotationLine` and `AnnotationText` styling parameters show which are available:
+
+```java
+chart.getStyler().setAnnotationLineColor(Color.GREEN);
+chart.getStyler().setAnnotationLineStroke(new BasicStroke(3.0f));
+chart.getStyler().setAnnotationTextFont(new Font(Font.MONOSPACED, Font.ITALIC, 8));
+chart.getStyler().setAnnotationTextFontColor(Color.BLUE);
+```
+
+A working example can be found at [LineChart10](https://github.com/knowm/XChart/blob/develop/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/line/LineChart10.java).
+
+The following is a chart with three `AnnotationTextPanel`s:
+
+![](https://raw.githubusercontent.com/knowm/XChart/develop/etc/XChart_AnnotationTextPanel.png)
+
+Positioning is relative to the bottom-left corner of the chart and to the bottom-left corner of the `AnnotationTextPanel`.
+
+The following example `AnnotationTextPanel` styling parameters show which are available:
+
+```java
+chart.getStyler().setAnnotationTextPanelPadding(20);
+chart.getStyler().setAnnotationTextPanelFont(new Font("Verdana", Font.BOLD, 12));
+chart.getStyler().setAnnotationTextPanelBackgroundColor(Color.RED);
+chart.getStyler().setAnnotationTextPanelBorderColor(Color.BLUE);
+chart.getStyler().setAnnotationTextPanelFontColor(Color.GREEN);
+```
+
+A working example can be found at [ScatterChart04](https://github.com/knowm/XChart/blob/develop/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/scatter/ScatterChart04.java).
+
+### Tool Tips
+
+For all chart types, tool tips can be activated on an `XChartPanel` via
+
+```java
+chart.getStyler().setToolTipsEnabled(true);
+```
+
+![](https://raw.githubusercontent.com/knowm/XChart/develop/etc/XChart_Tooltips.png)
+
+The following example tooltip options show which are available:
+
+```java
+chart.getStyler().setToolTipsEnabled(true);
+chart.getStyler().setToolTipsAlwaysVisible(true);
+chart.getStyler().setToolTipFont( new Font("Verdana", Font.BOLD, 12));
+chart.getStyler().setToolTipHighlightColor(Color.CYAN);
+chart.getStyler().setToolTipBorderColor(Color.BLACK);
+chart.getStyler().setToolTipBackgroundColor(Color.LIGHT_GRAY);
+chart.getStyler().setToolTipType(Styler.ToolTipType.xAndYLabels);
+```
+
+A working example can be found at [LineChart05](https://github.com/knowm/XChart/blob/develop/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/line/LineChart05.java).
+
+### Cursor
+
+For the `XYChart` chart type, it is possible to add an interactive cursor on an `XChartPanel` via
+
+```java
+chart.getStyler().setCursorEnabled(true);
+```
+
+![](https://raw.githubusercontent.com/knowm/XChart/develop/etc/XChart_Cursor.png)
+
+The following example cursor options show which are available:
+
+```java
+chart.getStyler().setCursorEnabled(true);
+chart.getStyler().setCursorColor(Color.GREEN);
+chart.getStyler().setCursorLineWidth(30f);
+chart.getStyler().setCursorFont(new Font("Verdana", Font.BOLD, 12));
+chart.getStyler().setCursorFontColor(Color.ORANGE);
+chart.getStyler().setCursorBackgroundColor(Color.BLUE);
+chart.getStyler().setCustomCursorXDataFormattingFunction(x ->"hello xvalue: "+x);
+chart.getStyler().setCustomCursorYDataFormattingFunction(y ->"hello yvalue divided by 2: "+y /2);
+```
+
+A working example can be found at [LineChart09](https://github.com/knowm/XChart/blob/develop/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/line/LineChart09.java).
+
+## Chart Themes
+
+XChart ships with three different themes: Default `XChart`, `GGPlot2` and `Matlab`. Using a different theme is as simple as setting the Chart's theme with the `theme` method of
+the `ChartBuilder`.
+
+    XYChart chart = new XYChartBuilder().width(800).height(600).theme(ChartTheme.Matlab).build();
+
+![](https://raw.githubusercontent.com/knowm/XChart/develop/etc/XChart_Themes.png)
+
+## What's Next?
+
+Now go ahead and [study some more examples](http://knowm.org/open-source/xchart/xchart-example-code/), [download the thing](http://knowm.org/open-source/xchart/xchart-change-log)
+and [provide feedback](https://github.com/knowm/XChart/issues).
+
+## Getting Started
+
+### Non-Maven
+
+Download Jar: http://knowm.org/open-source/xchart/xchart-change-log
+
+### Maven
+
+The XChart release artifacts are hosted on Maven Central.
+
+Add the XChart library as a dependency to your pom.xml file:
+
+```xml
+
+<dependency>
+    <groupId>org.knowm.xchart</groupId>
+    <artifactId>xchart</artifactId>
+    <version>3.8.8</version>
+</dependency>
+```
+
+For snapshots, add the following to your pom.xml file:
+
+```xml
+
+<repository>
+    <id>sonatype-oss-snapshot</id>
+    <snapshots/>
+    <url>https://oss.sonatype.org/content/repositories/snapshots</url>
+</repository>
+
+<dependency>
+<groupId>org.knowm.xchart</groupId>
+<artifactId>xchart</artifactId>
+<version>3.8.9-SNAPSHOT</version>
+</dependency>
+```
+
+Snapshots can be manually downloaded from
+Sonatype: [https://oss.sonatype.org/content/groups/public/org/knowm/xchart/xchart/](https://oss.sonatype.org/content/groups/public/org/knowm/xchart/xchart/)
+
+### SBT
+
+To use XChart with the Scala Build Tool (SBT) add the following to your build.sbt
+
+```scala
+libraryDependencies += "org.knowm.xchart" % "xchart" % "3.8.8" exclude("de.erichseifert.vectorgraphics2d", "VectorGraphics2D") withSources()
+```
+
+## Building with Maven
+
+ Instruction                  | Command                                       
+------------------------------|----------------------------------------------- 
+ run unit tests               | `mvn clean test`                              
+ package jar                  | `mvn clean package`                           
+ install in local Maven repo  | `mvn clean install`                           
+ create project javadocs      | `mvn javadoc:aggregate`                       
+ generate dependency tree     | `mvn dependency:tree`                         
+ check for dependency updates | `mvn versions:display-dependency-updates`     
+ check for plugin updates     | `mvn versions:display-plugin-updates`         
+ code format                  | `mvn com.spotify.fmt:fmt-maven-plugin:format` 
+
+Formats your code using [google-java-format](https://github.com/google/google-java-format) which
+follows [Google's code styleguide](https://google.github.io/styleguide/javaguide.html).
+
+If you want your IDE to stick to the same format, check out the available configuration plugins:
+
+#### Eclipse
+
+Download [`google-java-format-eclipse-plugin_*.jar`](https://github.com/google/google-java-format/releases) and place in `/Applications/Eclipse Java.app/Contents/Eclipse/dropins`.
+Restart Eclipse. Select the plugin in `Preferences > Java > Code Style > Formatter > Formatter Implementation`.
+
+#### IntelliJ
+
+In the plugins section in IntelliJ search for `google-java-format` and install the plugin. Restart IntelliJ.
+
+## Running Demo - option 1 - using released version
+
+![](https://raw.githubusercontent.com/knowm/XChart/develop/etc/XChart_Demo.png)
+
+- Linux: execute command `java -cp xchart-demo-3.8.8.jar:xchart-3.8.8.jar org.knowm.xchart.demo.XChartDemo`.
+
+- Windows: In the cmd command window, execute the command `java -cp xchart-demo-3.8.8.jar;xchart-3.8.8.jar org.knowm.xchart.demo.XChartDemo`; In
+  the PowerShell command window, execute the command `java -cp "xchart-demo-3.8.8.jar;xchart-3.8.8.jar" org.knowm.xchart.demo.XChartDemo`.
+
+E.g:
+
+```sh
+cd /path/to/xchart-demo/jar/
+java -cp xchart-demo-3.8.8.jar:xchart-3.8.8.jar org.knowm.xchart.demo.XChartDemo
+```
+
+## Running Demo - option 2 - building yourself
+
+```sh
+mvn install
+mvn exec:java -Djava.awt.headless=false -pl xchart-demo -Dexec.mainClass=org.knowm.xchart.demo.XChartDemo
+```
+
+## Running Demo - option 3 - with tweakable style properties
+
+```sh
+mvn install
+mvn exec:java -Djava.awt.headless=false -pl xchart-demo -Dexec.mainClass=org.knowm.xchart.demo.XChartStyleDemo
+```
+
+![](https://raw.githubusercontent.com/knowm/XChart/develop/etc/XChartStyleDemo.png)
+
+## Bugs
+
+Please report any bugs or submit feature requests to [XChart's Github issue tracker](https://github.com/knowm/XChart/issues).
+
+## Continuous Integration
+
+* [![Java CI with Maven on Push](https://github.com/knowm/XChart/actions/workflows/maven_on_push.yml/badge.svg)](https://github.com/knowm/XChart/actions/workflows/maven_on_push.yml)
+* [Build History](https://github.com/knowm/XChart/actions)  
+
diff --git a/XChart/etc/settings.xml b/XChart/etc/settings.xml
new file mode 100644
index 0000000000000000000000000000000000000000..6e86c155f011c509a89b0d2536d11183eeaa666c
--- /dev/null
+++ b/XChart/etc/settings.xml
@@ -0,0 +1,21 @@
+<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
+                      http://maven.apache.org/xsd/settings-1.0.0.xsd">
+  <localRepository/>
+  <interactiveMode/>
+  <usePluginRegistry/>
+  <offline/>
+  <pluginGroups/>
+  <servers>
+    <server>
+      <id>sonatype-nexus-snapshots</id>
+      <username>${env.CI_DEPLOY_USERNAME}</username>
+      <password>${env.CI_DEPLOY_PASSWORD}</password>
+    </server>
+  </servers>
+  <mirrors/>
+  <proxies/>
+  <profiles/>
+  <activeProfiles/>
+</settings>
\ No newline at end of file
diff --git a/XChart/pom.xml b/XChart/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..5a768a49c2595bb96c301d01efd91d87ea6fbef9
--- /dev/null
+++ b/XChart/pom.xml
@@ -0,0 +1,269 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+    <prerequisites>
+        <maven>3.9.0</maven>
+    </prerequisites>
+
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>org.knowm.xchart</groupId>
+    <artifactId>xchart-parent</artifactId>
+    <version>3.8.9-SNAPSHOT</version>
+    <packaging>pom</packaging>
+    <name>XChart Parent</name>
+    <description>XChart is a light-weight Java library for plotting data.</description>
+    <url>http://knowm.org/open-source/xchart</url>
+    <inceptionYear>2011</inceptionYear>
+
+    <organization>
+        <name>Knowm Inc.</name>
+        <url>http://knowm.org/open-source/</url>
+    </organization>
+
+    <developers>
+        <developer>
+            <name>Tim Molter</name>
+        </developer>
+    </developers>
+
+    <licenses>
+        <license>
+            <name>The Apache Software License, Version 2.0</name>
+            <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
+            <distribution>repo</distribution>
+            <comments>A business-friendly OSS license</comments>
+        </license>
+    </licenses>
+
+    <issueManagement>
+        <system>GitHub</system>
+        <url>https://github.com/knowm/XChart/issues</url>
+    </issueManagement>
+
+    <scm>
+        <connection>scm:git:git@github.com:knowm/XChart.git</connection>
+        <developerConnection>scm:git:git@github.com:knowm/XChart.git</developerConnection>
+        <url>git@github.com:knowm/XChart.git</url>
+        <tag>HEAD</tag>
+    </scm>
+
+    <ciManagement>
+        <url>https://github.com/knowm/XChart/actions</url>
+    </ciManagement>
+
+    <modules>
+        <module>xchart</module>
+        <module>xchart-demo</module>
+    </modules>
+
+    <distributionManagement>
+        <snapshotRepository>
+            <id>sonatype-nexus-snapshots</id>
+            <name>Sonatype Nexus Snapshots</name>
+            <url>https://oss.sonatype.org/content/repositories/snapshots</url>
+        </snapshotRepository>
+        <repository>
+            <id>sonatype-nexus-staging</id>
+            <name>Nexus Release Repository</name>
+            <url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
+        </repository>
+        <downloadUrl>https://oss.sonatype.org/content/groups/public/org/knowm/xchart</downloadUrl>
+    </distributionManagement>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>de.erichseifert.vectorgraphics2d</groupId>
+                <artifactId>VectorGraphics2D</artifactId>
+                <version>0.13</version>
+                <optional>true</optional>
+            </dependency>
+            <dependency>
+                <groupId>de.rototor.pdfbox</groupId>
+                <artifactId>graphics2d</artifactId>
+                <version>3.0.2</version>
+                <optional>true</optional>
+            </dependency>
+            <dependency>
+                <groupId>com.madgag</groupId>
+                <artifactId>animated-gif-lib</artifactId>
+                <version>1.4</version>
+                <optional>true</optional>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-engine</artifactId>
+            <version>${project.junit.jupiter.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-params</artifactId>
+            <version>${project.junit.jupiter.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.assertj</groupId>
+            <artifactId>assertj-core</artifactId>
+            <version>3.26.0</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <profiles>
+        <profile>
+            <id>release-sign-artifacts</id>
+            <activation>
+                <property>
+                    <name>performRelease</name>
+                    <value>true</value>
+                </property>
+            </activation>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-gpg-plugin</artifactId>
+                        <version>3.2.4</version>
+                        <executions>
+                            <execution>
+                                <id>sign-artifacts</id>
+                                <phase>verify</phase>
+                                <goals>
+                                    <goal>sign</goal>
+                                </goals>
+                                <configuration>
+                                    <!-- This is necessary for gpg to not try to use the pinentry programs -->
+                                    <gpgArguments>
+                                        <arg>--pinentry-mode</arg>
+                                        <arg>loopback</arg>
+                                    </gpgArguments>
+                                </configuration>
+                            </execution>
+                        </executions>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+
+    </profiles>
+
+    <build>
+        <plugins>
+            <!-- Add Automatic Module Name to MANIFEST for compatibility with modular applications -->
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jar-plugin</artifactId>
+                <version>3.4.1</version>
+                <configuration>
+                    <archive>
+                        <manifestEntries>
+                            <Automatic-Module-Name>org.knowm.xchart</Automatic-Module-Name>
+                        </manifestEntries>
+                    </archive>
+                </configuration>
+            </plugin>
+            <!-- Need at least 2.22.0 to support JUnit 5 -->
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <version>3.2.5</version>
+            </plugin>
+
+            <!-- Ensure compilation is done under Java 8 in all environments -->
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.13.0</version>
+                <configuration>
+                    <source>1.8</source>
+                    <target>1.8</target>
+                    <showDeprecation>true</showDeprecation>
+                    <showWarnings>true</showWarnings>
+                </configuration>
+            </plugin>
+            <!-- Generates a source code JAR during package -->
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-source-plugin</artifactId>
+                <version>3.3.1</version>
+                <executions>
+                    <execution>
+                        <id>attach-sources</id>
+                        <goals>
+                            <goal>jar</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <!-- Generates JavaDocs during package -->
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-javadoc-plugin</artifactId>
+                <version>3.7.0</version>
+                <configuration>
+                    <doclint>none</doclint>
+                    <excludePackageNames>org.knowm.xchart.internal.*</excludePackageNames>
+                    <quiet>true</quiet>
+                    <skip>false</skip>
+                    <doclint>none</doclint>
+                    <source>1.8</source>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>attach-javadocs</id>
+                        <goals>
+                            <goal>jar</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <!-- for deploying to Maven Central -->
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-release-plugin</artifactId>
+                <version>3.0.1</version>
+                <configuration>
+                    <autoVersionSubmodules>true</autoVersionSubmodules>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>com.spotify.fmt</groupId>
+                <artifactId>fmt-maven-plugin</artifactId>
+                <version>2.23</version>
+                <configuration>
+                    <filesNamePattern>.*\.java</filesNamePattern>
+                    <skip>false</skip>
+                </configuration>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>format</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.sonatype.plugins</groupId>
+                <artifactId>nexus-staging-maven-plugin</artifactId>
+                <version>1.7.0</version>
+                <extensions>true</extensions>
+                <configuration>
+                    <serverId>ossrh</serverId>
+                    <nexusUrl>https://oss.sonatype.org/</nexusUrl>
+                    <autoReleaseAfterClose>true</autoReleaseAfterClose>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+        <project.junit.jupiter.version>5.10.2</project.junit.jupiter.version>
+    </properties>
+
+</project>
diff --git a/XChart/xchart-demo/CSV/CSVChartColumns/series1.csv b/XChart/xchart-demo/CSV/CSVChartColumns/series1.csv
new file mode 100644
index 0000000000000000000000000000000000000000..d348d396fd0c6dc3a259b2692ded3e29767e7e95
--- /dev/null
+++ b/XChart/xchart-demo/CSV/CSVChartColumns/series1.csv
@@ -0,0 +1,4 @@
+1,12,1.4
+2,34,1.12
+3,56,1.21
+4,47,1.5
diff --git a/XChart/xchart-demo/CSV/CSVChartColumns/series2.csv b/XChart/xchart-demo/CSV/CSVChartColumns/series2.csv
new file mode 100644
index 0000000000000000000000000000000000000000..7ae35e9dbd05d923e428a82aad1cb41b70326255
--- /dev/null
+++ b/XChart/xchart-demo/CSV/CSVChartColumns/series2.csv
@@ -0,0 +1,4 @@
+1,56
+2,34
+3,12
+4,26
\ No newline at end of file
diff --git a/XChart/xchart-demo/CSV/CSVChartColumnsExport/series1.csv b/XChart/xchart-demo/CSV/CSVChartColumnsExport/series1.csv
new file mode 100644
index 0000000000000000000000000000000000000000..ed1ef3983f55831715938184c0163f75ba6c801d
--- /dev/null
+++ b/XChart/xchart-demo/CSV/CSVChartColumnsExport/series1.csv
@@ -0,0 +1,4 @@
+1.0,12.0,1.4
+2.0,34.0,1.12
+3.0,56.0,1.21
+4.0,47.0,1.5
diff --git a/XChart/xchart-demo/CSV/CSVChartColumnsExport/series2.csv b/XChart/xchart-demo/CSV/CSVChartColumnsExport/series2.csv
new file mode 100644
index 0000000000000000000000000000000000000000..55d5b5fce165b72d9b5b2c30e19aab43c31762fc
--- /dev/null
+++ b/XChart/xchart-demo/CSV/CSVChartColumnsExport/series2.csv
@@ -0,0 +1,4 @@
+1.0,56.0
+2.0,34.0
+3.0,12.0
+4.0,26.0
diff --git a/XChart/xchart-demo/CSV/CSVChartRows/series1.csv b/XChart/xchart-demo/CSV/CSVChartRows/series1.csv
new file mode 100644
index 0000000000000000000000000000000000000000..c77030a194f340d8cd74525c95e685e8e240083a
--- /dev/null
+++ b/XChart/xchart-demo/CSV/CSVChartRows/series1.csv
@@ -0,0 +1,3 @@
+1,2,3
+12,34,56
+4,12,21
\ No newline at end of file
diff --git a/XChart/xchart-demo/CSV/CSVChartRows/series2.csv b/XChart/xchart-demo/CSV/CSVChartRows/series2.csv
new file mode 100644
index 0000000000000000000000000000000000000000..3df77443ee9815a8ce3f2e0684658b51b00d388f
--- /dev/null
+++ b/XChart/xchart-demo/CSV/CSVChartRows/series2.csv
@@ -0,0 +1,2 @@
+1,2,3
+56,34,12
diff --git a/XChart/xchart-demo/CSV/CSVChartRowsExport/series1.csv b/XChart/xchart-demo/CSV/CSVChartRowsExport/series1.csv
new file mode 100644
index 0000000000000000000000000000000000000000..92e3bfa8c0f382c63543cadc71deb0bb5ccb6bf4
--- /dev/null
+++ b/XChart/xchart-demo/CSV/CSVChartRowsExport/series1.csv
@@ -0,0 +1,3 @@
+1.0,2.0,3.0
+12.0,34.0,56.0
+4.0,12.0,21.0
diff --git a/XChart/xchart-demo/CSV/CSVChartRowsExport/series2.csv b/XChart/xchart-demo/CSV/CSVChartRowsExport/series2.csv
new file mode 100644
index 0000000000000000000000000000000000000000..201172b8eea37098aec65be38120d5735597c6a1
--- /dev/null
+++ b/XChart/xchart-demo/CSV/CSVChartRowsExport/series2.csv
@@ -0,0 +1,2 @@
+1.0,2.0,3.0
+56.0,34.0,12.0
diff --git a/XChart/xchart-demo/pom.xml b/XChart/xchart-demo/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..1b3a5d58783f5573c5603228c57656a366997d35
--- /dev/null
+++ b/XChart/xchart-demo/pom.xml
@@ -0,0 +1,49 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+    <prerequisites>
+        <maven>3.9.0</maven>
+    </prerequisites>
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.knowm.xchart</groupId>
+        <artifactId>xchart-parent</artifactId>
+        <version>3.8.9-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>xchart-demo</artifactId>
+
+    <name>XChart Demo</name>
+    <description>A Swing App demonstration of various charts using XChart</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.knowm.xchart</groupId>
+            <artifactId>xchart</artifactId>
+            <version>3.8.9-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.openjfx</groupId>
+            <artifactId>javafx-swing</artifactId>
+            <version>11.0.2</version>
+        </dependency>
+        <dependency>
+            <groupId>org.openjfx</groupId>
+            <artifactId>javafx-controls</artifactId>
+            <version>11.0.2</version>
+        </dependency>
+    </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.openjfx</groupId>
+                <artifactId>javafx-maven-plugin</artifactId>
+                <version>0.0.8</version>
+                <configuration>
+                    <mainClass>org.knowm.xchart.standalone.JavaFXDemo</mainClass>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/ChartInfo.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/ChartInfo.java
new file mode 100644
index 0000000000000000000000000000000000000000..245c2dea8efacf8d8f7d9d2cd9c5828328577be8
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/ChartInfo.java
@@ -0,0 +1,37 @@
+package org.knowm.xchart.demo;
+
+import org.knowm.xchart.demo.charts.ExampleChart;
+
+public final class ChartInfo {
+
+  private final String exampleChartName;
+  private final ExampleChart exampleChart;
+
+  /**
+   * Constructor
+   *
+   * @param exampleChartName
+   * @param exampleChart
+   */
+  public ChartInfo(String exampleChartName, ExampleChart exampleChart) {
+
+    this.exampleChartName = exampleChartName;
+    this.exampleChart = exampleChart;
+  }
+
+  public String getExampleChartName() {
+
+    return exampleChartName;
+  }
+
+  public ExampleChart getExampleChart() {
+
+    return exampleChart;
+  }
+
+  @Override
+  public String toString() {
+
+    return this.exampleChartName;
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/ChartStylePanel.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/ChartStylePanel.java
new file mode 100644
index 0000000000000000000000000000000000000000..899c3044cac7523040ffbcfc88f3af62fe277f89
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/ChartStylePanel.java
@@ -0,0 +1,751 @@
+package org.knowm.xchart.demo;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.GridLayout;
+import java.awt.Stroke;
+import java.beans.BeanInfo;
+import java.beans.IntrospectionException;
+import java.beans.Introspector;
+import java.beans.MethodDescriptor;
+import java.beans.PropertyDescriptor;
+import java.beans.PropertyEditor;
+import java.lang.reflect.Array;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.TimeZone;
+import java.util.TreeSet;
+import javax.swing.AbstractCellEditor;
+import javax.swing.DefaultCellEditor;
+import javax.swing.JComboBox;
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.JTextField;
+import javax.swing.border.LineBorder;
+import javax.swing.table.DefaultTableModel;
+import javax.swing.table.TableCellEditor;
+import javax.swing.table.TableColumnModel;
+import org.knowm.xchart.XChartPanel;
+import org.knowm.xchart.internal.chartpart.Chart;
+import org.knowm.xchart.internal.series.Series;
+import org.knowm.xchart.style.lines.SeriesLines;
+import org.knowm.xchart.style.markers.BaseSeriesMarkers;
+import org.knowm.xchart.style.markers.Marker;
+import org.knowm.xchart.style.theme.GGPlot2Theme;
+import org.knowm.xchart.style.theme.MatlabTheme;
+import org.knowm.xchart.style.theme.Theme;
+import org.knowm.xchart.style.theme.XChartTheme;
+
+public class ChartStylePanel extends JPanel {
+
+  public static final class LabelValue {
+    String label;
+    Object value;
+
+    public LabelValue(String label, Object value) {
+      this.label = label;
+      this.value = value;
+    }
+
+    @Override
+    public String toString() {
+
+      return label;
+    }
+  }
+
+  public static final class EditableProperty {
+    String name;
+    Method readMethod;
+    Method writeMethod;
+    Object obj;
+    PropertyEditor editor;
+    TableCellEditor cellEditor;
+    ChartStylePanel csp;
+    Object additionalParameter;
+
+    static HashMap<Class, TableCellEditor> editorMap;
+    static Class[] assignableClasses = {
+      Theme.class, BasicStroke.class, Marker.class, TimeZone.class
+    };
+
+    static {
+      editorMap = new HashMap<Class, TableCellEditor>();
+      {
+        JComboBox comboBox = new JComboBox(new Boolean[] {Boolean.TRUE, Boolean.FALSE});
+        TableCellEditor cellEditor = new DefaultCellEditor(comboBox);
+        editorMap.put(Boolean.class, cellEditor);
+        editorMap.put(Boolean.TYPE, cellEditor);
+      }
+
+      {
+        Class[][] clsArr = {
+          {int.class, Integer.class},
+          {byte.class, Byte.class},
+          {short.class, Short.class},
+          {long.class, Long.class},
+          {float.class, Float.class},
+          {double.class, Double.class, Number.class},
+          {String.class, String.class}
+        };
+
+        for (Class[] classes : clsArr) {
+          GenericEditorWithClass editor = new GenericEditorWithClass(classes[1]);
+          for (Class class1 : classes) {
+            editorMap.put(class1, editor);
+          }
+        }
+      }
+
+      {
+        JComboBox comboBox =
+            new JComboBox(new Theme[] {new XChartTheme(), new GGPlot2Theme(), new MatlabTheme()});
+        editorMap.put(Theme.class, new DefaultCellEditor(comboBox));
+      }
+
+      {
+        LabelValue[] values = {
+          new LabelValue("NONE", SeriesLines.NONE),
+          new LabelValue("SOLID", SeriesLines.SOLID),
+          new LabelValue("DOT_DOT", SeriesLines.DOT_DOT),
+          new LabelValue("DASH_DASH", SeriesLines.DASH_DASH),
+          new LabelValue("DASH_DOT", SeriesLines.DASH_DOT)
+        };
+        JComboBox comboBox = new JComboBox(values);
+        editorMap.put(BasicStroke.class, new DefaultCellEditor(comboBox));
+      }
+      {
+        LabelValue[] values = { //
+          // styler.plotGridLinesStroke java.awt.Stroke
+          new LabelValue(
+              "Base Grid Line",
+              new BasicStroke(
+                  1.0f,
+                  BasicStroke.CAP_BUTT,
+                  BasicStroke.JOIN_BEVEL,
+                  10.0f,
+                  new float[] {3.0f, 5.0f},
+                  0.0f)), //
+          new LabelValue("GGPlot2 Grid Line", new BasicStroke(1.0f)), //
+          new LabelValue(
+              "Matlab Grid Line",
+              new BasicStroke(
+                  .5f,
+                  BasicStroke.CAP_BUTT,
+                  BasicStroke.JOIN_ROUND,
+                  10.0f,
+                  new float[] {1f, 3.0f},
+                  0.0f)), //
+
+          // styler.axisTickMarksStroke java.awt.Stroke
+          new LabelValue("Base Tick Marks", new BasicStroke(1.0f)), //
+          new LabelValue("GGPlot2 Tick Marks", new BasicStroke(1.5f)), //
+          new LabelValue("Matlab Tick Marks", new BasicStroke(.5f)), //
+        };
+        JComboBox comboBox = new JComboBox(values);
+        editorMap.put(Stroke.class, new DefaultCellEditor(comboBox));
+      }
+
+      {
+        Marker[] seriesMarkers = new BaseSeriesMarkers().getSeriesMarkers();
+        JComboBox comboBox = new JComboBox(seriesMarkers);
+        editorMap.put(Marker.class, new DefaultCellEditor(comboBox));
+      }
+
+      {
+        Locale[] values =
+            new Locale[] {
+              Locale.ENGLISH,
+              Locale.US,
+              Locale.UK,
+              Locale.FRANCE,
+              Locale.FRANCE,
+              Locale.ITALIAN,
+              Locale.GERMAN,
+              new Locale("tr", "tr")
+            };
+        JComboBox comboBox = new JComboBox(values);
+        editorMap.put(Locale.class, new DefaultCellEditor(comboBox));
+      }
+
+      {
+        String[] availableIDs = TimeZone.getAvailableIDs();
+        TimeZone[] values = new TimeZone[availableIDs.length];
+        for (int i = 0; i < values.length; i++) {
+          values[i] = TimeZone.getTimeZone(availableIDs[i]);
+        }
+        JComboBox comboBox = new JComboBox(values);
+        editorMap.put(TimeZone.class, new DefaultCellEditor(comboBox));
+      }
+    }
+
+    public EditableProperty(
+        ChartStylePanel csp, String name, Object obj, Method readMethod, Method writeMethod) {
+      this(csp, name, obj, readMethod, writeMethod, null);
+    }
+
+    public EditableProperty(
+        ChartStylePanel csp,
+        String name,
+        Object obj,
+        Method readMethod,
+        Method writeMethod,
+        Object additionalParameter) {
+
+      this.csp = csp;
+      this.readMethod = readMethod;
+      this.writeMethod = writeMethod;
+      this.name = name;
+      this.obj = obj;
+      this.additionalParameter = additionalParameter;
+
+      initEditor();
+    }
+
+    private void initEditor() {
+
+      try {
+        Object val = getValue();
+        Class cls = val == null ? getValueClass() : val.getClass();
+        cellEditor = editorMap.get(cls);
+        if (cellEditor != null) {
+          return;
+        }
+
+        if (cls.isEnum()) {
+          JComboBox comboBox = new JComboBox(cls.getEnumConstants());
+          cellEditor = new DefaultCellEditor(comboBox);
+          return;
+        }
+
+        for (Class class1 : assignableClasses) {
+          if (class1.isAssignableFrom(cls)) {
+            cellEditor = editorMap.get(class1);
+            return;
+          }
+        }
+
+        editor = java.beans.PropertyEditorManager.findEditor(cls);
+        if (editor != null && editor.supportsCustomEditor()) {
+          cellEditor = new PropertyEditorAdapter(editor, this);
+          return;
+        }
+
+        System.out.println("Warning no editor found for property '" + name + "' with class " + cls);
+
+      } catch (Exception e) {
+        e.printStackTrace();
+      }
+    }
+
+    @Override
+    public String toString() {
+
+      return name;
+    }
+
+    public void setValue(Object aValue) {
+
+      if (aValue != null && aValue instanceof LabelValue) {
+        aValue = ((LabelValue) aValue).value;
+      }
+      try {
+        if (additionalParameter == null) {
+          writeMethod.invoke(obj, aValue);
+        } else {
+          if (writeMethod == null) {
+            Array.set(obj, (Integer) additionalParameter, aValue);
+          } else {
+            writeMethod.invoke(obj, additionalParameter, aValue);
+          }
+        }
+        csp.repaintChart();
+      } catch (Exception e) {
+        Class<?>[] parameterTypes = writeMethod.getParameterTypes();
+        if (aValue.getClass() != parameterTypes[0]) {
+          System.out.println(
+              name
+                  + " "
+                  + writeMethod.getName()
+                  + " requires "
+                  + parameterTypes[0]
+                  + " but got "
+                  + aValue
+                  + " ("
+                  + (aValue == null ? null : aValue.getClass())
+                  + ")");
+        }
+        e.printStackTrace();
+      }
+    }
+
+    public Object getValue() {
+
+      try {
+        if (additionalParameter == null) {
+          return readMethod.invoke(obj);
+        } else {
+          if (readMethod == null) {
+            return Array.get(obj, (Integer) additionalParameter);
+          }
+          return readMethod.invoke(obj, additionalParameter);
+        }
+      } catch (Exception e) {
+        System.out.println("Error reading " + name);
+        e.printStackTrace();
+      }
+      return null;
+    }
+
+    public TableCellEditor getTableCellEditor() {
+
+      return cellEditor;
+    }
+
+    public Class getValueClass() {
+
+      if (readMethod == null) {
+        // obj is array
+        return obj.getClass().getComponentType();
+      }
+      return readMethod.getReturnType();
+    }
+  }
+
+  public static class PropertyEditorAdapter extends AbstractCellEditor implements TableCellEditor {
+
+    PropertyEditor editor;
+    EditableProperty se;
+
+    public PropertyEditorAdapter(PropertyEditor editor, EditableProperty se) {
+
+      this.editor = editor;
+      this.se = se;
+    }
+
+    @Override
+    public Object getCellEditorValue() {
+
+      return editor.getValue();
+    }
+
+    @Override
+    public Component getTableCellEditorComponent(
+        JTable table, Object value, boolean isSelected, int row, int column) {
+
+      editor.setValue(value);
+      return editor.getCustomEditor();
+    }
+  }
+
+  static class GenericEditorWithClass extends DefaultCellEditor {
+
+    Class[] argTypes = new Class[] {String.class};
+    java.lang.reflect.Constructor constructor;
+    Object value;
+
+    public GenericEditorWithClass(Class cls) {
+      super(new JTextField());
+      getComponent().setName("Table.editor");
+      try {
+        constructor = cls.getConstructor(argTypes);
+      } catch (Exception e) {
+        e.printStackTrace();
+      }
+    }
+
+    public boolean stopCellEditing() {
+
+      String s = (String) super.getCellEditorValue();
+      // Here we are dealing with the case where a user
+      // has deleted the string value in a cell, possibly
+      // after a failed validation. Return null, so that
+      // they have the option to replace the value with
+      // null or use escape to restore the original.
+      // For Strings, return "" for backward compatibility.
+      try {
+        if ("".equals(s)) {
+          if (constructor.getDeclaringClass() == String.class) {
+            value = s;
+          }
+          return super.stopCellEditing();
+        }
+
+        value = constructor.newInstance(new Object[] {s});
+      } catch (Exception e) {
+        ((JComponent) getComponent()).setBorder(new LineBorder(Color.red));
+        return false;
+      }
+      return super.stopCellEditing();
+    }
+
+    public Component getTableCellEditorComponent(
+        JTable table, Object value, boolean isSelected, int row, int column) {
+
+      this.value = null;
+      ((JComponent) getComponent()).setBorder(new LineBorder(Color.black));
+      return super.getTableCellEditorComponent(table, value, isSelected, row, column);
+    }
+
+    public Object getCellEditorValue() {
+
+      return value;
+    }
+  }
+
+  public static class EditorTableModel extends DefaultTableModel {
+    ArrayList<EditableProperty> properties;
+    Chart chart;
+    int rowCount;
+    ChartStylePanel csp;
+
+    public EditorTableModel(ChartStylePanel csp, Chart chart) {
+      this.csp = csp;
+      addColumn("Name");
+      addColumn("Type");
+      addColumn("Value");
+      changeChart(chart);
+    }
+
+    public void changeChart(Chart chart) {
+
+      this.chart = chart;
+      properties = getProperties(csp, chart);
+      rowCount = properties.size();
+      fireTableStructureChanged();
+    }
+
+    @Override
+    public Class<?> getColumnClass(int columnIndex) {
+
+      switch (columnIndex) {
+        case 0:
+          return String.class;
+        case 1:
+          return Class.class;
+
+        default:
+          return Object.class;
+      }
+    }
+
+    public EditableProperty getValueAt(int row) {
+
+      return properties.get(row);
+    }
+
+    @Override
+    public Object getValueAt(int row, int column) {
+
+      EditableProperty prop = properties.get(row);
+      switch (column) {
+        case 0:
+          return prop.name;
+        case 1:
+          return prop.getValueClass().getName();
+        case 2:
+          return prop.getValue();
+        default:
+          return null;
+      }
+    }
+
+    @Override
+    public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
+
+      EditableProperty prop = properties.get(rowIndex);
+      prop.setValue(aValue);
+    }
+
+    @Override
+    public boolean isCellEditable(int rowIndex, int columnIndex) {
+
+      return columnIndex == 2;
+    }
+
+    @Override
+    public int getRowCount() {
+
+      return rowCount;
+    }
+  }
+
+  public static class EditorTable extends JTable {
+    EditorTableModel tableModel;
+
+    public EditorTable(ChartStylePanel csp, Chart chart) {
+      tableModel = new EditorTableModel(csp, chart);
+
+      setModel(tableModel);
+      setRowHeight(getRowHeight() * 2);
+      setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
+
+      TableColumnModel colmodel = getColumnModel();
+
+      colmodel.getColumn(0).setPreferredWidth(200);
+      colmodel.getColumn(1).setPreferredWidth(150);
+      colmodel.getColumn(2).setPreferredWidth(400);
+
+      setAutoCreateRowSorter(true);
+    }
+
+    public void changeChart(Chart chart) {
+
+      tableModel.changeChart(chart);
+    }
+
+    @Override
+    public TableCellEditor getCellEditor(int row, int column) {
+
+      int modelRowIndex = convertRowIndexToModel(row);
+      EditableProperty se = tableModel.getValueAt(modelRowIndex);
+      TableCellEditor editor = se.getTableCellEditor();
+      if (editor != null) {
+        return editor;
+      }
+      Class valueClass = se.getValueClass();
+      TableCellEditor defaultEditor = getDefaultEditor(valueClass);
+
+      // System.out.println(valueClass + "=>" + defaultEditor);
+      return defaultEditor;
+    }
+  }
+
+  private EditorTable table;
+  private XChartPanel chartPanel;
+
+  public ChartStylePanel(XChartPanel chartPanel) {
+    this.chartPanel = chartPanel;
+    table = new EditorTable(this, chartPanel.getChart());
+    JScrollPane scrollpane = new JScrollPane(table);
+    GridLayout layout = new GridLayout(1, 1);
+    setLayout(layout);
+    add(scrollpane);
+    setPreferredSize(new Dimension(800, 600));
+  }
+
+  public void changeChart(XChartPanel chartPanel) {
+
+    this.chartPanel = chartPanel;
+    table.changeChart(chartPanel.getChart());
+  }
+
+  protected void repaintChart() {
+
+    chartPanel.repaint();
+  }
+
+  static HashSet<String> skipSet =
+      new HashSet<String>(
+          Arrays.asList(
+              "class",
+              // chart
+              "styler",
+              "toolTips",
+              "height",
+              "width",
+              "seriesMap",
+              "YAxisGroupTitle",
+              // series
+              "XMax",
+              "XMin",
+              "YMax",
+              "YMin",
+              "extraValues",
+              "XData",
+              "YData",
+              "xAxisDataType",
+              "yAxisDataType",
+              "legendRenderType",
+              "name",
+              "YAxisAlignment",
+              "YAxisGroupPosition"));
+
+  public static ArrayList<EditableProperty> getProperties(ChartStylePanel csp, Chart chart) {
+
+    if (chart == null) {
+      return new ArrayList<EditableProperty>();
+    }
+    ArrayList<EditableProperty> list = getObjectProperties(csp, chart, "chart.", skipSet);
+    ArrayList<EditableProperty> list2 =
+        getObjectProperties(csp, chart.getStyler(), "styler.", skipSet);
+    list.addAll(list2);
+
+    Map<String, Series> seriesMap = chart.getSeriesMap();
+    int ind = 0;
+    TreeSet<Integer> seriesIndSet = new TreeSet<Integer>();
+    for (Entry<String, Series> e : seriesMap.entrySet()) {
+      Series series = e.getValue();
+      list2 = getObjectProperties(csp, series, "series[" + e.getKey() + "].", skipSet);
+      list.addAll(list2);
+      seriesIndSet.add(series.getYAxisGroup());
+      seriesIndSet.add(ind);
+      ind++;
+    }
+
+    try {
+      MethodDescriptor[] methodDescriptors =
+          Introspector.getBeanInfo(chart.getClass()).getMethodDescriptors();
+      HashMap<String, Method> chartMethodMap = new HashMap<String, Method>();
+      for (MethodDescriptor methodDescriptor : methodDescriptors) {
+        chartMethodMap.put(
+            methodDescriptor.getName().toLowerCase(Locale.ENGLISH), methodDescriptor.getMethod());
+      }
+
+      methodDescriptors =
+          Introspector.getBeanInfo(chart.getStyler().getClass()).getMethodDescriptors();
+      HashMap<String, Method> stylerMethodMap = new HashMap<String, Method>();
+      for (MethodDescriptor methodDescriptor : methodDescriptors) {
+        stylerMethodMap.put(
+            methodDescriptor.getName().toLowerCase(Locale.ENGLISH), methodDescriptor.getMethod());
+      }
+
+      // Skipping property (no read method): styler.YAxisAlignment null
+      // chart.getStyler().getYAxisGroupPosistion(yAxisGroup)
+      // chart.getStyler().setYAxisGroupPosition(yAxisGroup)
+
+      for (Integer i : seriesIndSet) {
+        EditableProperty styleEditor =
+            new EditableProperty(
+                csp,
+                "chart.YAxisGroupTitle[" + i + "]",
+                chart,
+                chartMethodMap.get("getyaxisgrouptitle"),
+                chartMethodMap.get("setyaxisgrouptitle"),
+                i);
+        list.add(styleEditor);
+      }
+    } catch (IntrospectionException e1) {
+      e1.printStackTrace();
+    }
+
+    return list;
+  }
+
+  public static Method getMethod(
+      PropertyDescriptor pd, boolean read, HashMap<String, Method> methodMap) {
+
+    Method method = read ? pd.getReadMethod() : pd.getWriteMethod();
+
+    if (method != null) {
+      return method;
+    }
+
+    String lowerCaseName = pd.getName().toLowerCase(Locale.ENGLISH);
+    String[] prefixes = read ? new String[] {"get", "is"} : new String[] {"set"};
+
+    for (String pre : prefixes) {
+      String name = pre + lowerCaseName;
+      method = methodMap.get(name);
+      if (method != null) {
+        if (method.getParameterCount() != (read ? 0 : 1)) {
+          method = null;
+          continue;
+        }
+        return method;
+      }
+    }
+    return method;
+  }
+
+  public static ArrayList<EditableProperty> getObjectProperties(
+      ChartStylePanel csp, Object obj, String prefix, Set<String> skipSet) {
+
+    ArrayList<EditableProperty> list = new ArrayList<EditableProperty>();
+    try {
+      BeanInfo info = Introspector.getBeanInfo(obj.getClass());
+      PropertyDescriptor[] propertyDescriptors = info.getPropertyDescriptors();
+
+      Arrays.sort(
+          propertyDescriptors,
+          new Comparator<PropertyDescriptor>() {
+            public int compare(PropertyDescriptor a, PropertyDescriptor b) {
+
+              return a.getName().compareToIgnoreCase(b.getName());
+            }
+          });
+
+      MethodDescriptor[] methodDescriptors = info.getMethodDescriptors();
+      HashMap<String, Method> methodMap = new HashMap<String, Method>();
+      for (MethodDescriptor methodDescriptor : methodDescriptors) {
+        methodMap.put(
+            methodDescriptor.getName().toLowerCase(Locale.ENGLISH), methodDescriptor.getMethod());
+      }
+      for (PropertyDescriptor pd : propertyDescriptors) {
+        try {
+          if (skipSet.contains(pd.getName())) {
+            continue;
+          }
+
+          Method readMethod = getMethod(pd, true, methodMap);
+
+          if (readMethod == null) {
+
+            // System.out.println("Skipping property (no read method): " +
+            // pd.getName() + " " + pd.getPropertyType());
+            System.out.println(
+                "Skipping property (no read method): "
+                    + prefix
+                    + pd.getName()
+                    + " "
+                    + pd.getPropertyType());
+            continue;
+          }
+          Method writeMethod = getMethod(pd, false, methodMap);
+          if (writeMethod == null) {
+
+            System.out.println(
+                "Skipping property (no write method): "
+                    + prefix
+                    + pd.getName()
+                    + " "
+                    + pd.getPropertyType());
+            // System.out.println("Skipping property (no write method): " +
+            // pd.getName() + " " + pd.getPropertyType());
+            continue;
+          }
+
+          if (pd.getReadMethod().getReturnType().isArray()) {
+            Object arr = readMethod.invoke(obj);
+            // arr may be null
+            if (arr == null) {
+              continue;
+            }
+            int size = Array.getLength(arr);
+            Method rm = null;
+            Method wm = null;
+            for (int i = 0; i < size; i++) {
+
+              EditableProperty styleEditor =
+                  new EditableProperty(csp, prefix + pd.getName() + "[" + i + "]", arr, rm, wm, i);
+              list.add(styleEditor);
+            }
+            continue;
+          }
+
+          EditableProperty styleEditor =
+              new EditableProperty(csp, prefix + pd.getName(), obj, readMethod, writeMethod);
+          list.add(styleEditor);
+        } catch (Exception e) {
+          e.printStackTrace();
+        }
+      }
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+
+    return list;
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/DemoChartsUtil.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/DemoChartsUtil.java
new file mode 100644
index 0000000000000000000000000000000000000000..8182f73b65d934d98560563196d677690a493ab5
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/DemoChartsUtil.java
@@ -0,0 +1,130 @@
+package org.knowm.xchart.demo;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.internal.chartpart.Chart;
+import org.knowm.xchart.internal.series.Series;
+import org.knowm.xchart.style.Styler;
+
+public class DemoChartsUtil {
+
+  private static final String DEMO_CHARTS_PACKAGE = "org.knowm.xchart.demo.charts";
+
+  public static List<ExampleChart<Chart<Styler, Series>>> getAllDemoCharts() {
+
+    List<ExampleChart<Chart<Styler, Series>>> demoCharts = null;
+    String packagePath = DEMO_CHARTS_PACKAGE.replace(".", "/");
+    ClassLoader loader = Thread.currentThread().getContextClassLoader();
+    URL url = loader.getResource(packagePath);
+
+    if (url != null) {
+      try {
+        demoCharts = getAllDemoCharts(url);
+      } catch (Exception e) {
+        e.printStackTrace();
+      }
+    }
+
+    return demoCharts;
+  }
+
+  @SuppressWarnings("unchecked")
+  private static List<ExampleChart<Chart<Styler, Series>>> getAllDemoCharts(URL url)
+      throws Exception {
+
+    List<ExampleChart<Chart<Styler, Series>>> demoCharts = new ArrayList<>();
+
+    List<Class<?>> classes = getAllAssignedClasses(url);
+    // sort
+    Collections.sort(
+        classes,
+        new Comparator<Class<?>>() {
+
+          @Override
+          public int compare(Class<?> c1, Class<?> c2) {
+            return c1.getName().compareTo(c2.getName());
+          }
+        });
+
+    for (Class<?> c : classes) {
+      demoCharts.add(((ExampleChart<Chart<Styler, Series>>) c.newInstance()));
+    }
+    return demoCharts;
+  }
+
+  private static List<Class<?>> getAllAssignedClasses(URL url)
+      throws ClassNotFoundException, IOException {
+
+    List<Class<?>> classes = null;
+
+    String type = url.getProtocol();
+    if ("file".equals(type)) {
+      classes = getClassesByFile(new File(url.getFile()), DEMO_CHARTS_PACKAGE);
+    } else if ("jar".equals(type)) {
+      classes = getClassesByJar(url.getPath());
+    }
+    List<Class<?>> allAssignedClasses = new ArrayList<>();
+    if (classes != null) {
+      for (Class<?> c : classes) {
+        if (ExampleChart.class.isAssignableFrom(c) && !ExampleChart.class.equals(c)) {
+          allAssignedClasses.add(c);
+        }
+      }
+    }
+    return allAssignedClasses;
+  }
+
+  private static List<Class<?>> getClassesByFile(File dir, String pk)
+      throws ClassNotFoundException {
+
+    List<Class<?>> classes = new ArrayList<>();
+    if (!dir.exists()) {
+      return classes;
+    }
+
+    String fileName = "";
+    for (File f : dir.listFiles()) {
+      fileName = f.getName();
+      if (f.isDirectory()) {
+        classes.addAll(getClassesByFile(f, pk + "." + fileName));
+      } else if (fileName.endsWith(".class")) {
+        classes.add(
+            Class.forName(pk + "." + fileName.substring(0, fileName.length() - ".class".length())));
+      }
+    }
+
+    return classes;
+  }
+
+  @SuppressWarnings("resource")
+  private static List<Class<?>> getClassesByJar(String jarPath)
+      throws IOException, ClassNotFoundException {
+
+    List<Class<?>> classes = new ArrayList<>();
+    String[] jarInfo = jarPath.split("!");
+    String jarFilePath = jarInfo[0].substring(jarInfo[0].indexOf("/"));
+    String packagePath = jarInfo[1].substring(1);
+    Enumeration<JarEntry> entrys = new JarFile(jarFilePath).entries();
+    JarEntry jarEntry = null;
+    String entryName = "";
+    String className = "";
+    while (entrys.hasMoreElements()) {
+      jarEntry = entrys.nextElement();
+      entryName = jarEntry.getName();
+      if (entryName.endsWith(".class") && entryName.startsWith(packagePath)) {
+        className = entryName.replace("/", ".").substring(0, entryName.lastIndexOf("."));
+        classes.add(Class.forName(className));
+      }
+    }
+    return classes;
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/XChartDemo.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/XChartDemo.java
new file mode 100644
index 0000000000000000000000000000000000000000..09ae98751803789d56d955b53c2f47c5fe6996e3
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/XChartDemo.java
@@ -0,0 +1,179 @@
+package org.knowm.xchart.demo;
+
+import java.awt.Dimension;
+import java.awt.GridLayout;
+import java.util.List;
+import java.util.Timer;
+import java.util.TimerTask;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JSplitPane;
+import javax.swing.JTree;
+import javax.swing.WindowConstants;
+import javax.swing.event.TreeSelectionEvent;
+import javax.swing.event.TreeSelectionListener;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.TreeSelectionModel;
+import org.knowm.xchart.XChartPanel;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.demo.charts.RealtimeExampleChart;
+import org.knowm.xchart.demo.charts.area.AreaChart01;
+import org.knowm.xchart.internal.chartpart.Chart;
+import org.knowm.xchart.internal.series.Series;
+import org.knowm.xchart.style.Styler;
+
+/** Class containing all XChart example charts */
+public class XChartDemo extends JPanel implements TreeSelectionListener {
+
+  /** The main split frame */
+  private final JSplitPane splitPane;
+
+  /** The tree */
+  private final JTree tree;
+
+  /** The panel for chart */
+  protected XChartPanel chartPanel;
+
+  Timer timer = new Timer();
+
+  /** Constructor */
+  public XChartDemo() {
+
+    super(new GridLayout(1, 0));
+
+    // Create the nodes.
+    DefaultMutableTreeNode top = new DefaultMutableTreeNode("XChart Example Charts");
+    createNodes(top);
+
+    // Create a tree that allows one selection at a time.
+    tree = new JTree(top);
+    tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
+
+    // Listen for when the selection changes.
+    tree.addTreeSelectionListener(this);
+
+    // Create the scroll pane and add the tree to it.
+    JScrollPane treeView = new JScrollPane(tree);
+
+    // Create Chart Panel
+    chartPanel = new XChartPanel(new AreaChart01().getChart());
+
+    // Add the scroll panes to a split pane.
+    splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
+    splitPane.setTopComponent(treeView);
+    splitPane.setBottomComponent(chartPanel);
+
+    Dimension minimumSize = new Dimension(130, 160);
+    treeView.setMinimumSize(minimumSize);
+    splitPane.setPreferredSize(new Dimension(700, 700));
+
+    // Add the split pane to this panel.
+    add(splitPane);
+  }
+
+  @Override
+  public void valueChanged(TreeSelectionEvent e) {
+
+    DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
+
+    if (node == null) {
+      return;
+    }
+
+    Object nodeInfo = node.getUserObject();
+    // tree leaf
+    if (node.isLeaf()) {
+      ChartInfo chartInfo = (ChartInfo) nodeInfo;
+      // displayURL(chartInfo.bookURL);
+      chartPanel = new XChartPanel(chartInfo.getExampleChart().getChart());
+      splitPane.setBottomComponent(chartPanel);
+
+      // start running a simulated data feed for the sample real-time plot
+      timer.cancel(); // just in case
+      if (chartInfo.getExampleChart() instanceof RealtimeExampleChart) {
+        final RealtimeExampleChart realtimeChart =
+            (RealtimeExampleChart) chartInfo.getExampleChart();
+        TimerTask chartUpdaterTask =
+            new TimerTask() {
+
+              @Override
+              public void run() {
+
+                realtimeChart.updateData();
+                chartPanel.revalidate();
+                chartPanel.repaint();
+              }
+            };
+        timer = new Timer();
+        timer.scheduleAtFixedRate(chartUpdaterTask, 0, 500);
+      }
+    }
+  }
+
+  /**
+   * Create the tree
+   *
+   * @param top
+   */
+  private void createNodes(DefaultMutableTreeNode top) {
+
+    // categories
+    DefaultMutableTreeNode category = null;
+    // leaves
+    DefaultMutableTreeNode defaultMutableTreeNode;
+
+    List<ExampleChart<Chart<Styler, Series>>> exampleList = DemoChartsUtil.getAllDemoCharts();
+    String categoryName = "";
+    for (ExampleChart exampleChart : exampleList) {
+      String name = exampleChart.getClass().getSimpleName();
+      name = name.substring(0, name.indexOf("Chart"));
+      if (!categoryName.equals(name)) {
+        String label = name.equals("") ? "Chart Themes" : (name + " Charts");
+        if (label.equals("Realtime Charts")) {
+          label = "Real-time Charts";
+        }
+        category = new DefaultMutableTreeNode(label);
+        top.add(category);
+        categoryName = name;
+      }
+      defaultMutableTreeNode =
+          new DefaultMutableTreeNode(
+              new ChartInfo(exampleChart.getExampleChartName(), exampleChart));
+      category.add(defaultMutableTreeNode);
+    }
+  }
+
+  /**
+   * Create the GUI and show it. For thread safety, this method should be invoked from the event
+   * dispatch thread.
+   */
+  private static void createAndShowGUI() {
+
+    // Create and set up the window.
+    JFrame frame = new JFrame("XChart Demo");
+    frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
+
+    // Add content to the window.
+    frame.add(new XChartDemo());
+
+    // Display the window.
+    frame.pack();
+    frame.setVisible(true);
+  }
+
+  public static void main(String[] args) {
+
+    // Schedule a job for the event dispatch thread:
+    // creating and showing this application's GUI.
+    javax.swing.SwingUtilities.invokeLater(
+        new Runnable() {
+
+          @Override
+          public void run() {
+
+            createAndShowGUI();
+          }
+        });
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/XChartStyleDemo.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/XChartStyleDemo.java
new file mode 100644
index 0000000000000000000000000000000000000000..71b48e33974f45c4d3456cff4845a85f4bf86f80
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/XChartStyleDemo.java
@@ -0,0 +1,65 @@
+package org.knowm.xchart.demo;
+
+import javax.swing.JFrame;
+import javax.swing.JSplitPane;
+import javax.swing.WindowConstants;
+import javax.swing.event.TreeSelectionEvent;
+import org.knowm.xchart.XChartPanel;
+
+public class XChartStyleDemo extends XChartDemo {
+
+  private JSplitPane styleSplitPane;
+  private ChartStylePanel stylePanel;
+
+  public XChartStyleDemo() {
+    styleSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
+    styleSplitPane.setLeftComponent(this);
+
+    stylePanel = new ChartStylePanel(chartPanel);
+    styleSplitPane.setRightComponent(stylePanel);
+  }
+
+  @Override
+  public void valueChanged(TreeSelectionEvent e) {
+    XChartPanel oldChartPanel = chartPanel;
+    super.valueChanged(e);
+    if (chartPanel != oldChartPanel) {
+      stylePanel.changeChart(chartPanel);
+    }
+  }
+
+  /**
+   * Create the GUI and show it. For thread safety, this method should be invoked from the event
+   * dispatch thread.
+   */
+  private static void createAndShowGUI() {
+
+    // Create and set up the window.
+    JFrame frame = new JFrame("XChart Style Demo");
+    frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
+
+    XChartStyleDemo demo = new XChartStyleDemo();
+
+    // Add content to the window.
+    frame.add(demo.styleSplitPane);
+
+    // Display the window.
+    frame.pack();
+    frame.setVisible(true);
+  }
+
+  public static void main(String[] args) {
+
+    // Schedule a job for the event dispatch thread:
+    // creating and showing this application's GUI.
+    javax.swing.SwingUtilities.invokeLater(
+        new Runnable() {
+
+          @Override
+          public void run() {
+
+            createAndShowGUI();
+          }
+        });
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/ExampleChart.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/ExampleChart.java
new file mode 100644
index 0000000000000000000000000000000000000000..49615739525d7de401c70dd35724c9fbc97f22df
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/ExampleChart.java
@@ -0,0 +1,10 @@
+package org.knowm.xchart.demo.charts;
+
+import org.knowm.xchart.internal.chartpart.Chart;
+
+public interface ExampleChart<C extends Chart<?, ?>> {
+
+  C getChart();
+
+  String getExampleChartName();
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/RealtimeExampleChart.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/RealtimeExampleChart.java
new file mode 100644
index 0000000000000000000000000000000000000000..eae0d79ff4b519ed5e4c18a0dbcc14c826388904
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/RealtimeExampleChart.java
@@ -0,0 +1,6 @@
+package org.knowm.xchart.demo.charts;
+
+public interface RealtimeExampleChart {
+
+  public void updateData();
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/area/AreaChart01.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/area/AreaChart01.java
new file mode 100644
index 0000000000000000000000000000000000000000..2453aa50e6de3df5a7c50e28ab4cb5c7021c659f
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/area/AreaChart01.java
@@ -0,0 +1,60 @@
+package org.knowm.xchart.demo.charts.area;
+
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYChartBuilder;
+import org.knowm.xchart.XYSeries.XYSeriesRenderStyle;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.style.Styler.LegendPosition;
+
+/**
+ * Area Chart with 3 series
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>Area Chart
+ *   <li>Place legend at Inside-NE position
+ *   <li>ChartBuilder
+ */
+public class AreaChart01 implements ExampleChart<XYChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<XYChart> exampleChart = new AreaChart01();
+    XYChart chart = exampleChart.getChart();
+    new SwingWrapper<>(chart).displayChart();
+  }
+
+  @Override
+  public XYChart getChart() {
+
+    // Create Chart
+    XYChart chart =
+        new XYChartBuilder()
+            .width(800)
+            .height(600)
+            .title(getClass().getSimpleName())
+            .xAxisTitle("X")
+            .yAxisTitle("Y")
+            .build();
+
+    // Customize Chart
+    chart.getStyler().setLegendPosition(LegendPosition.InsideNE);
+    chart.getStyler().setAxisTitlesVisible(false);
+    chart.getStyler().setDefaultSeriesRenderStyle(XYSeriesRenderStyle.Area);
+
+    // Series
+    chart.addSeries("a", new double[] {0, 3, 5, 7, 9}, new double[] {-3, 5, 9, 6, 5});
+    chart.addSeries("b", new double[] {0, 2, 4, 6, 9}, new double[] {-1, 6, 4, 0, 4});
+    chart.addSeries("c", new double[] {0, 1, 3, 8, 9}, new double[] {-2, -1, 1, 0, 1});
+
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - 3-Series";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/area/AreaChart02.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/area/AreaChart02.java
new file mode 100644
index 0000000000000000000000000000000000000000..9b374f0efdecd0863eab8090614d628a7b1e980d
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/area/AreaChart02.java
@@ -0,0 +1,79 @@
+package org.knowm.xchart.demo.charts.area;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYChartBuilder;
+import org.knowm.xchart.XYSeries.XYSeriesRenderStyle;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.style.Styler.LegendPosition;
+
+/**
+ * Null Y-Axis Data Points
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>Area Chart
+ *   <li>null Y-Axis values
+ *   <li>ChartBuilder
+ */
+public class AreaChart02 implements ExampleChart<XYChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<XYChart> exampleChart = new AreaChart02();
+    XYChart chart = exampleChart.getChart();
+    new SwingWrapper<XYChart>(chart).displayChart();
+  }
+
+  @Override
+  public XYChart getChart() {
+
+    // Create Chart
+    XYChart chart =
+        new XYChartBuilder()
+            .width(800)
+            .height(600)
+            .title(getClass().getSimpleName())
+            .xAxisTitle("X")
+            .yAxisTitle("Y")
+            .build();
+
+    // Customize Chart
+    chart.getStyler().setDefaultSeriesRenderStyle(XYSeriesRenderStyle.Area);
+    chart.getStyler().setLegendPosition(LegendPosition.InsideNW);
+
+    // Series
+    List<Integer> xData = new ArrayList<Integer>();
+    List<Integer> yData = new ArrayList<Integer>();
+    for (int i = 0; i < 5; i++) {
+      xData.add(i);
+      yData.add(i * i);
+    }
+    xData.add(5);
+    yData.add(null);
+
+    for (int i = 6; i < 10; i++) {
+      xData.add(i);
+      yData.add(i * i);
+    }
+    xData.add(10);
+    yData.add(null);
+    xData.add(11);
+    yData.add(100);
+    xData.add(12);
+    yData.add(90);
+
+    chart.addSeries("a", xData, yData);
+
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - Null Y-Axis Data Points";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/area/AreaChart03.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/area/AreaChart03.java
new file mode 100644
index 0000000000000000000000000000000000000000..3d3420b2451f9544ad4acdb9af78b3bdfae0f9cf
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/area/AreaChart03.java
@@ -0,0 +1,112 @@
+package org.knowm.xchart.demo.charts.area;
+
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYChartBuilder;
+import org.knowm.xchart.XYSeries;
+import org.knowm.xchart.XYSeries.XYSeriesRenderStyle;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.style.AxesChartStyler;
+import org.knowm.xchart.style.Styler.LegendPosition;
+import org.knowm.xchart.style.markers.SeriesMarkers;
+
+/**
+ * Combination of Line and 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
+ *   <li>Turning off series markers
+ */
+public class AreaChart03 implements ExampleChart<XYChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<XYChart> exampleChart = new AreaChart03();
+    XYChart chart = exampleChart.getChart();
+    new SwingWrapper<XYChart>(chart).displayChart();
+  }
+
+  @Override
+  public XYChart getChart() {
+
+    // Create Chart
+    XYChart chart =
+        new XYChartBuilder()
+            .width(800)
+            .height(600)
+            .title(getClass().getSimpleName())
+            .xAxisTitle("Age")
+            .yAxisTitle("Amount")
+            .build();
+
+    // Customize Chart
+    chart.getStyler().setLegendPosition(LegendPosition.InsideNW);
+    chart.getStyler().setDefaultSeriesRenderStyle(XYSeriesRenderStyle.Line);
+    chart.getStyler().setYAxisLabelAlignment(AxesChartStyler.TextAlignment.Right);
+    chart.getStyler().setYAxisDecimalPattern("$ #,###.##");
+    chart.getStyler().setPlotMargin(0);
+    chart.getStyler().setPlotContentSize(.95);
+
+    // Series
+    // @formatter:off
+    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
+        };
+    // @formatter:on
+
+    XYSeries series = chart.addSeries("Liability", xAges, yLiability);
+    series.setXYSeriesRenderStyle(XYSeries.XYSeriesRenderStyle.Area);
+    series.setMarker(SeriesMarkers.NONE);
+
+    chart.addSeries("75th Percentile", xAges, yPercentile75th);
+    chart.addSeries("50th Percentile", xAges, yPercentile50th);
+    chart.addSeries("25th Percentile", xAges, yPercentile25th);
+
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - Combination Area & Line Chart";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/area/AreaChart04.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/area/AreaChart04.java
new file mode 100644
index 0000000000000000000000000000000000000000..a34a2d0be9ebff38c18505214061578851922f1d
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/area/AreaChart04.java
@@ -0,0 +1,60 @@
+package org.knowm.xchart.demo.charts.area;
+
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYChartBuilder;
+import org.knowm.xchart.XYSeries.XYSeriesRenderStyle;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.style.Styler.LegendPosition;
+
+/**
+ * Area Chart with 3 series
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>Step Area Chart
+ *   <li>Place legend at Inside-NE position
+ *   <li>ChartBuilder
+ */
+public class AreaChart04 implements ExampleChart<XYChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<XYChart> exampleChart = new AreaChart04();
+    XYChart chart = exampleChart.getChart();
+    new SwingWrapper<XYChart>(chart).displayChart();
+  }
+
+  @Override
+  public XYChart getChart() {
+
+    // Create Chart
+    XYChart chart =
+        new XYChartBuilder()
+            .width(800)
+            .height(600)
+            .title(getClass().getSimpleName())
+            .xAxisTitle("X")
+            .yAxisTitle("Y")
+            .build();
+
+    // Customize Chart
+    chart.getStyler().setLegendPosition(LegendPosition.InsideNE);
+    chart.getStyler().setAxisTitlesVisible(false);
+    chart.getStyler().setDefaultSeriesRenderStyle(XYSeriesRenderStyle.StepArea);
+
+    // Series
+    chart.addSeries("a", new double[] {0, 3, 5, 7, 9}, new double[] {-3, 5, 9, 6, 5});
+    chart.addSeries("b", new double[] {0, 2, 4, 6, 9}, new double[] {-1, 6, 4, 0, 4});
+    chart.addSeries("c", new double[] {0, 1, 3, 8, 9}, new double[] {-2, -1, 1, 0, 1});
+
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - Step Area Rendering";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/area/AreaChart05.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/area/AreaChart05.java
new file mode 100644
index 0000000000000000000000000000000000000000..44f714c3987ef90f6ad66c1f65b07d2a0ebf5160
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/area/AreaChart05.java
@@ -0,0 +1,67 @@
+package org.knowm.xchart.demo.charts.area;
+
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYChartBuilder;
+import org.knowm.xchart.XYSeries.XYSeriesRenderStyle;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.style.Styler.LegendPosition;
+
+/**
+ * Area Chart with 1 series
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>Polygon Area Chart
+ *   <li>Place legend at Inside-NE position
+ *   <li>ChartBuilder
+ */
+public class AreaChart05 implements ExampleChart<XYChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<XYChart> exampleChart = new AreaChart05();
+    XYChart chart = exampleChart.getChart();
+    new SwingWrapper<XYChart>(chart).displayChart();
+  }
+
+  @Override
+  public XYChart getChart() {
+
+    // Create Chart
+    XYChart chart =
+        new XYChartBuilder()
+            .width(800)
+            .height(600)
+            .title(getClass().getSimpleName())
+            .xAxisTitle("X")
+            .yAxisTitle("Y")
+            .build();
+
+    // Customize Chart
+    chart.getStyler().setLegendPosition(LegendPosition.InsideNE);
+    chart.getStyler().setAxisTitlesVisible(false);
+    chart.getStyler().setDefaultSeriesRenderStyle(XYSeriesRenderStyle.PolygonArea);
+
+    // Series
+    chart.addSeries(
+        "a",
+        new double[] {
+          0, 3, 5, 7, 9, // x coordinates ascending
+          9, 7, 5, 3, 0
+        }, // x coordinates descending
+        new double[] {
+          -1, 6, 9, 6, 5, // upper y
+          4, 0, 4, 5, -3
+        }); // lower y
+
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - Polygon Area Rendering";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/bar/BarChart01.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/bar/BarChart01.java
new file mode 100644
index 0000000000000000000000000000000000000000..29d016156936a91513b548be63d713ef3b42631f
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/bar/BarChart01.java
@@ -0,0 +1,60 @@
+package org.knowm.xchart.demo.charts.bar;
+
+import java.util.Arrays;
+import org.knowm.xchart.CategoryChart;
+import org.knowm.xchart.CategoryChartBuilder;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.style.Styler.LegendPosition;
+
+/**
+ * Basic Bar Chart
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>Integer categories as List
+ *   <li>All positive values
+ *   <li>Single series
+ *   <li>Place legend at Inside-NW position
+ *   <li>Bar Chart Annotations
+ */
+public class BarChart01 implements ExampleChart<CategoryChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<CategoryChart> exampleChart = new BarChart01();
+    CategoryChart chart = exampleChart.getChart();
+    new SwingWrapper<>(chart).displayChart();
+  }
+
+  @Override
+  public CategoryChart getChart() {
+
+    // Create Chart
+    CategoryChart chart =
+        new CategoryChartBuilder()
+            .width(800)
+            .height(600)
+            .title(getClass().getSimpleName())
+            .xAxisTitle("Score")
+            .yAxisTitle("Number")
+            .build();
+
+    // Customize Chart
+    chart.getStyler().setLegendPosition(LegendPosition.InsideNW);
+    chart.getStyler().setLabelsVisible(false);
+    chart.getStyler().setPlotGridLinesVisible(false);
+
+    // Series
+    chart.addSeries("test 1", Arrays.asList(0, 1, 2, 3, 4), Arrays.asList(4, 5, 9, 6, 5));
+
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - Basic Bar Chart";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/bar/BarChart02.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/bar/BarChart02.java
new file mode 100644
index 0000000000000000000000000000000000000000..cb8de00dcef235d855cbc0ff6ce25731d5c679f2
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/bar/BarChart02.java
@@ -0,0 +1,84 @@
+package org.knowm.xchart.demo.charts.bar;
+
+import java.awt.Color;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Random;
+import org.knowm.xchart.CategoryChart;
+import org.knowm.xchart.CategoryChartBuilder;
+import org.knowm.xchart.CategorySeries;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.style.Styler.ChartTheme;
+
+/**
+ * Date Categories
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>Date categories as List
+ *   <li>All negative values
+ *   <li>Single series
+ *   <li>No horizontal plot gridlines
+ *   <li>Change series color
+ *   <li>MATLAB Theme
+ */
+public class BarChart02 implements ExampleChart<CategoryChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<CategoryChart> exampleChart = new BarChart02();
+    CategoryChart chart = exampleChart.getChart();
+    new SwingWrapper<>(chart).displayChart();
+  }
+
+  @Override
+  public CategoryChart getChart() {
+
+    // Create Chart
+    CategoryChart chart =
+        new CategoryChartBuilder()
+            .theme(ChartTheme.Matlab)
+            .width(800)
+            .height(600)
+            .title(getClass().getSimpleName())
+            .xAxisTitle("Year")
+            .yAxisTitle("Units Sold")
+            .build();
+
+    // Customize Chart
+    chart.getStyler().setPlotGridLinesVisible(false);
+    chart.getStyler().setDatePattern("yyyy");
+
+    // Series
+    List<Date> xData = new ArrayList<Date>();
+    List<Number> yData = new ArrayList<Number>();
+
+    Random random = new Random();
+    DateFormat sdf = new SimpleDateFormat("yyyy");
+    Date date = null;
+    for (int i = 1; i <= 8; i++) {
+      try {
+        date = sdf.parse("" + (2000 + i));
+      } catch (ParseException e) {
+        e.printStackTrace();
+      }
+      xData.add(date);
+      yData.add(-1 * 0.00000001 * ((random.nextInt(i) + 1)));
+    }
+    CategorySeries series = chart.addSeries("Model 77", xData, yData);
+    series.setFillColor(new Color(230, 150, 150));
+
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+    return getClass().getSimpleName() + " - Date Categories";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/bar/BarChart03.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/bar/BarChart03.java
new file mode 100644
index 0000000000000000000000000000000000000000..9f73d672af2a2bb01318813b9692e364309d7484
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/bar/BarChart03.java
@@ -0,0 +1,58 @@
+package org.knowm.xchart.demo.charts.bar;
+
+import org.knowm.xchart.CategoryChart;
+import org.knowm.xchart.CategoryChartBuilder;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.demo.charts.ExampleChart;
+
+/**
+ * Stacked Bar Chart
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>int categories as array
+ *   <li>Positive and negative values
+ *   <li>data labels and stack sum labels
+ */
+public class BarChart03 implements ExampleChart<CategoryChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<CategoryChart> exampleChart = new BarChart03();
+    CategoryChart chart = exampleChart.getChart();
+    new SwingWrapper<>(chart).displayChart();
+  }
+
+  @Override
+  public CategoryChart getChart() {
+
+    // Create Chart
+    CategoryChart chart =
+        new CategoryChartBuilder()
+            .width(800)
+            .height(600)
+            .title(getClass().getSimpleName())
+            .xAxisTitle("Age")
+            .yAxisTitle("Score")
+            .build();
+
+    // Customize Chart
+    chart.getStyler().setPlotGridVerticalLinesVisible(false);
+    chart.getStyler().setStacked(true);
+    chart.getStyler().setLabelsVisible(true);
+    chart.getStyler().setShowStackSum(true);
+
+    // Series
+    chart.addSeries("males", new int[] {10, 20, 30, 40}, new int[] {40, -30, -20, -60});
+    chart.addSeries("females", new int[] {10, 20, 30, 40}, new int[] {45, -35, -25, 65});
+
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - Stacked Bar Chart with Data and Stack Sum Labels";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/bar/BarChart04.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/bar/BarChart04.java
new file mode 100644
index 0000000000000000000000000000000000000000..fae8b296c7d8f3051bfdcbb951ad02cead787705
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/bar/BarChart04.java
@@ -0,0 +1,66 @@
+package org.knowm.xchart.demo.charts.bar;
+
+import java.util.Arrays;
+import org.knowm.xchart.CategoryChart;
+import org.knowm.xchart.CategoryChartBuilder;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.style.Styler;
+
+/**
+ * Missing Point in Series
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>Number categories
+ *   <li>Positive values
+ *   <li>Multiple series
+ *   <li>Missing point in series
+ *   <li>Manually setting y-axis min and max values
+ *   <li>Bar Chart Annotations
+ *   <li>Horizontal Legend OutsideS
+ */
+public class BarChart04 implements ExampleChart<CategoryChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<CategoryChart> exampleChart = new BarChart04();
+    CategoryChart chart = exampleChart.getChart();
+    new SwingWrapper<>(chart).displayChart();
+  }
+
+  @Override
+  public CategoryChart getChart() {
+
+    // Create Chart
+    CategoryChart chart =
+        new CategoryChartBuilder()
+            .width(800)
+            .height(600)
+            .title(getClass().getSimpleName())
+            .xAxisTitle("Age")
+            .yAxisTitle("XFactor")
+            .build();
+
+    // Customize Chart
+    chart.getStyler().setYAxisMin(5.0);
+    chart.getStyler().setYAxisMax(70.0);
+    chart.getStyler().setLabelsVisible(true);
+    chart.getStyler().setPlotGridVerticalLinesVisible(false);
+    chart.getStyler().setLegendPosition(Styler.LegendPosition.OutsideS);
+    chart.getStyler().setLegendLayout(Styler.LegendLayout.Horizontal);
+
+    // Series
+    chart.addSeries("female", Arrays.asList(10, 20, 30, 40, 50), Arrays.asList(50, 10, 20, 40, 35));
+    chart.addSeries("male", Arrays.asList(10, 20, 30, 40, 50), Arrays.asList(40, 30, 20, null, 60));
+
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - Missing Point in Series";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/bar/BarChart05.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/bar/BarChart05.java
new file mode 100644
index 0000000000000000000000000000000000000000..0e0f7985e49dd3e91936c020f0b4998b4173b538
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/bar/BarChart05.java
@@ -0,0 +1,77 @@
+package org.knowm.xchart.demo.charts.bar;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import org.knowm.xchart.CategoryChart;
+import org.knowm.xchart.CategoryChartBuilder;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.style.Styler.ChartTheme;
+
+/**
+ * GGPlot2 Theme Bar chart
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>String categories
+ *   <li>Positive and negative values
+ *   <li>Multiple series
+ */
+public class BarChart05 implements ExampleChart<CategoryChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<CategoryChart> exampleChart = new BarChart05();
+    CategoryChart chart = exampleChart.getChart();
+    new SwingWrapper<>(chart).displayChart();
+  }
+
+  @Override
+  public CategoryChart getChart() {
+
+    // Create Chart
+    CategoryChart chart =
+        new CategoryChartBuilder()
+            .width(800)
+            .height(600)
+            .title(getClass().getSimpleName())
+            .xAxisTitle("Color")
+            .yAxisTitle("Temperature")
+            .theme(ChartTheme.GGPlot2)
+            .build();
+
+    // Customize Chart
+    chart.getStyler().setPlotGridVerticalLinesVisible(false);
+
+    // Series
+    chart.addSeries(
+        "fish",
+        new ArrayList<>(Arrays.asList(new String[] {"Blue", "Red", "Green", "Yellow", "Orange"})),
+        new ArrayList<Number>(Arrays.asList(new Number[] {-40, 30, 20, 60, 60})));
+    chart.addSeries(
+        "worms",
+        new ArrayList<>(Arrays.asList(new String[] {"Blue", "Red", "Green", "Yellow", "Orange"})),
+        new ArrayList<Number>(Arrays.asList(new Number[] {50, 10, -20, 40, 60})));
+    chart.addSeries(
+        "birds",
+        new ArrayList<>(Arrays.asList(new String[] {"Blue", "Red", "Green", "Yellow", "Orange"})),
+        new ArrayList<Number>(Arrays.asList(new Number[] {13, 22, -23, -34, 37})));
+    chart.addSeries(
+        "ants",
+        new ArrayList<>(Arrays.asList(new String[] {"Blue", "Red", "Green", "Yellow", "Orange"})),
+        new ArrayList<Number>(Arrays.asList(new Number[] {50, 57, -14, -20, 31})));
+    chart.addSeries(
+        "slugs",
+        new ArrayList<>(Arrays.asList(new String[] {"Blue", "Red", "Green", "Yellow", "Orange"})),
+        new ArrayList<Number>(Arrays.asList(new Number[] {-2, 29, 49, -16, -43})));
+
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - GGPlot2 Theme";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/bar/BarChart06.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/bar/BarChart06.java
new file mode 100644
index 0000000000000000000000000000000000000000..41feb20b8e2aa0db0099ce5357b1e6303abc6bf5
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/bar/BarChart06.java
@@ -0,0 +1,74 @@
+package org.knowm.xchart.demo.charts.bar;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.knowm.xchart.CategoryChart;
+import org.knowm.xchart.CategoryChartBuilder;
+import org.knowm.xchart.Histogram;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.style.Styler.LegendPosition;
+
+/**
+ * Histogram Overlapped
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>Histogram
+ *   <li>Bar Chart styles - overlapped, bar width
+ */
+public class BarChart06 implements ExampleChart<CategoryChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<CategoryChart> exampleChart = new BarChart06();
+    CategoryChart chart = exampleChart.getChart();
+    new SwingWrapper<>(chart).displayChart();
+  }
+
+  @Override
+  public CategoryChart getChart() {
+
+    // Create Chart
+    CategoryChart chart =
+        new CategoryChartBuilder()
+            .width(800)
+            .height(600)
+            .title(getClass().getSimpleName())
+            .xAxisTitle("Mean")
+            .yAxisTitle("Count")
+            .build();
+
+    // Customize Chart
+    chart.getStyler().setLegendPosition(LegendPosition.InsideNW);
+    chart.getStyler().setAvailableSpaceFill(.96);
+    chart.getStyler().setOverlapped(true);
+    chart.getStyler().setPlotGridVerticalLinesVisible(false);
+
+    // Series
+    Histogram histogram1 = new Histogram(getGaussianData(10000), 20, -20, 20);
+    Histogram histogram2 = new Histogram(getGaussianData(5000), 20, -20, 20);
+    chart.addSeries("histogram 1", histogram1.getxAxisData(), histogram1.getyAxisData());
+    chart.addSeries("histogram 2", histogram2.getxAxisData(), histogram2.getyAxisData());
+
+    return chart;
+  }
+
+  private List<Double> getGaussianData(int count) {
+
+    List<Double> data = new ArrayList<Double>(count);
+    Random r = new Random();
+    for (int i = 0; i < count; i++) {
+      data.add(r.nextGaussian() * 10);
+    }
+    return data;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - Histogram Overlapped";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/bar/BarChart07.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/bar/BarChart07.java
new file mode 100644
index 0000000000000000000000000000000000000000..c2eae36b3d23412f4351afa843defe5df89ca864
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/bar/BarChart07.java
@@ -0,0 +1,78 @@
+package org.knowm.xchart.demo.charts.bar;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.knowm.xchart.CategoryChart;
+import org.knowm.xchart.CategoryChartBuilder;
+import org.knowm.xchart.Histogram;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.style.Styler;
+import org.knowm.xchart.style.Styler.LegendPosition;
+
+/**
+ * Histogram Not Overlapped
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>Histogram
+ *   <li>Bar Chart styles - not overlapped, bar width
+ *   <li>Integer data values
+ *   <li>Tool Tips
+ */
+public class BarChart07 implements ExampleChart<CategoryChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<CategoryChart> exampleChart = new BarChart07();
+    CategoryChart chart = exampleChart.getChart();
+    new SwingWrapper<>(chart).displayChart();
+  }
+
+  @Override
+  public CategoryChart getChart() {
+
+    // Create Chart
+    CategoryChart chart =
+        new CategoryChartBuilder()
+            .width(800)
+            .height(600)
+            .title(getClass().getSimpleName())
+            .xAxisTitle("Mean")
+            .yAxisTitle("Count")
+            .build();
+
+    // Customize Chart
+    chart.getStyler().setLegendPosition(LegendPosition.InsideNW);
+    chart.getStyler().setAvailableSpaceFill(.96);
+    chart.getStyler().setPlotGridVerticalLinesVisible(false);
+    chart.getStyler().setToolTipsEnabled(true);
+    chart.getStyler().setToolTipType(Styler.ToolTipType.yLabels);
+
+    // Series
+    Histogram histogram1 = new Histogram(getGaussianData(1000), 10, -30, 30);
+    chart.addSeries("histogram 1", histogram1.getxAxisData(), histogram1.getyAxisData());
+    Histogram histogram2 = new Histogram(getGaussianData(1000), 10, -30, 30);
+    chart.addSeries("histogram 2", histogram2.getxAxisData(), histogram2.getyAxisData());
+
+    return chart;
+  }
+
+  private List<Integer> getGaussianData(int count) {
+
+    List<Integer> data = new ArrayList<Integer>(count);
+    Random r = new Random();
+    for (int i = 0; i < count; i++) {
+      data.add((int) (r.nextGaussian() * 10));
+    }
+    return data;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - Histogram Not Overlapped with Tool Tips";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/bar/BarChart08.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/bar/BarChart08.java
new file mode 100644
index 0000000000000000000000000000000000000000..21d11c300ab0d76cd569c9034c0cf10d35489a04
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/bar/BarChart08.java
@@ -0,0 +1,87 @@
+package org.knowm.xchart.demo.charts.bar;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.knowm.xchart.CategoryChart;
+import org.knowm.xchart.CategoryChartBuilder;
+import org.knowm.xchart.Histogram;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.style.Styler.ChartTheme;
+import org.knowm.xchart.style.Styler.LegendPosition;
+
+/**
+ * Histogram with Error Bars
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>Histogram
+ *   <li>Bar Chart with error bars
+ */
+public class BarChart08 implements ExampleChart<CategoryChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<CategoryChart> exampleChart = new BarChart08();
+    CategoryChart chart = exampleChart.getChart();
+    new SwingWrapper<CategoryChart>(chart).displayChart();
+  }
+
+  @Override
+  public CategoryChart getChart() {
+
+    // Create Chart
+    CategoryChart chart =
+        new CategoryChartBuilder()
+            .width(800)
+            .height(600)
+            .title(getClass().getSimpleName())
+            .xAxisTitle("Mean")
+            .theme(ChartTheme.Matlab)
+            .yAxisTitle("Count")
+            .build();
+
+    // Customize Chart
+    chart.getStyler().setLegendPosition(LegendPosition.InsideNW);
+    chart.getStyler().setAvailableSpaceFill(.96);
+
+    // Series
+    Histogram histogram1 = new Histogram(getGaussianData(10000), 10, -10, 10);
+    chart.addSeries(
+        "histogram",
+        histogram1.getxAxisData(),
+        histogram1.getyAxisData(),
+        getFakeErrorData(histogram1.getxAxisData().size()));
+
+    return chart;
+  }
+
+  private List<Double> getGaussianData(int count) {
+
+    List<Double> data = new ArrayList<Double>(count);
+    Random r = new Random();
+    for (int i = 0; i < count; i++) {
+      data.add(r.nextGaussian() * 5);
+      // data.add(r.nextDouble() * 60 - 30);
+    }
+    return data;
+  }
+
+  private List<Double> getFakeErrorData(int count) {
+
+    List<Double> data = new ArrayList<Double>(count);
+    Random r = new Random();
+    for (int i = 0; i < count; i++) {
+      data.add(r.nextDouble() * 200);
+    }
+    return data;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - Histogram with Error Bars";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/bar/BarChart09.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/bar/BarChart09.java
new file mode 100644
index 0000000000000000000000000000000000000000..17855eb1c8a480a7d3a9a14718ad196de0232c21
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/bar/BarChart09.java
@@ -0,0 +1,78 @@
+package org.knowm.xchart.demo.charts.bar;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import org.knowm.xchart.CategoryChart;
+import org.knowm.xchart.CategoryChartBuilder;
+import org.knowm.xchart.CategorySeries;
+import org.knowm.xchart.CategorySeries.CategorySeriesRenderStyle;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.style.Styler.ChartTheme;
+import org.knowm.xchart.style.Styler.LegendPosition;
+
+/**
+ * Category chart with Bar, Line and Scatter Series
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>Mixed series types - Bar, Line and Scatter
+ *   <li>Bar Chart styles - overlapped, bar width
+ */
+public class BarChart09 implements ExampleChart<CategoryChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<CategoryChart> exampleChart = new BarChart09();
+    CategoryChart chart = exampleChart.getChart();
+    new SwingWrapper<>(chart).displayChart();
+  }
+
+  @Override
+  public CategoryChart getChart() {
+
+    // Create Chart
+    CategoryChart chart =
+        new CategoryChartBuilder()
+            .width(800)
+            .height(600)
+            .title(getClass().getSimpleName())
+            .xAxisTitle("Letter")
+            .yAxisTitle("Value")
+            .theme(ChartTheme.GGPlot2)
+            .build();
+
+    // Customize Chart
+    chart.getStyler().setLegendPosition(LegendPosition.InsideNW);
+    chart.getStyler().setAvailableSpaceFill(.55);
+    chart.getStyler().setOverlapped(true);
+
+    // Series
+    chart.addSeries(
+        "China",
+        new ArrayList<>(Arrays.asList(new String[] {"A", "B", "C", "D", "E"})),
+        new ArrayList<>(Arrays.asList(new Number[] {11, 23, 20, 36, 5})));
+    CategorySeries series2 =
+        chart.addSeries(
+            "Korea",
+            new ArrayList<>(Arrays.asList(new String[] {"A", "B", "C", "D", "E"})),
+            new ArrayList<>(Arrays.asList(new Number[] {13, 25, 22, 38, 7})),
+            new ArrayList<>(Arrays.asList(new Number[] {1, 3, 2, 1, 2})));
+    series2.setChartCategorySeriesRenderStyle(CategorySeriesRenderStyle.Line);
+    CategorySeries series3 =
+        chart.addSeries(
+            "World Ave.",
+            new ArrayList<>(Arrays.asList(new String[] {"A", "B", "C", "D", "E"})),
+            new ArrayList<>(Arrays.asList(new Number[] {20, 22, 18, 36, 32})));
+    series3.setChartCategorySeriesRenderStyle(CategorySeriesRenderStyle.Scatter);
+
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - Category chart with Bar, Line and Scatter Series";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/bar/BarChart10.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/bar/BarChart10.java
new file mode 100644
index 0000000000000000000000000000000000000000..aa7612e83404b06f36b59063960dd9f7d09222e3
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/bar/BarChart10.java
@@ -0,0 +1,107 @@
+package org.knowm.xchart.demo.charts.bar;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.knowm.xchart.CategoryChart;
+import org.knowm.xchart.CategoryChartBuilder;
+import org.knowm.xchart.CategorySeries;
+import org.knowm.xchart.CategorySeries.CategorySeriesRenderStyle;
+import org.knowm.xchart.Histogram;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.style.Styler.LegendPosition;
+
+/**
+ * Stepped Chart Histogram
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>Histogram
+ *   <li>Bar Chart styles - overlapped
+ *   <li>Custom Line Style
+ *   <li>Render style - Stepped Bars
+ */
+public class BarChart10 implements ExampleChart<CategoryChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<CategoryChart> exampleChart = new BarChart10();
+    CategoryChart chart = exampleChart.getChart();
+    new SwingWrapper<CategoryChart>(chart).displayChart();
+  }
+
+  @Override
+  public CategoryChart getChart() {
+
+    // Create Chart
+    CategoryChart chart =
+        new CategoryChartBuilder()
+            .width(800)
+            .height(600)
+            .title(getClass().getSimpleName())
+            .xAxisTitle("Mean")
+            .yAxisTitle("Count")
+            .build();
+
+    // Customize Chart
+    chart.getStyler().setLegendPosition(LegendPosition.InsideNW);
+    chart.getStyler().setAvailableSpaceFill(.96);
+    chart.getStyler().setPlotGridVerticalLinesVisible(false);
+
+    // While supported, SteppedBars in anything but overlapped mode are fairly useless.
+    chart.getStyler().setOverlapped(true);
+
+    // Series
+    Histogram histogram1 = new Histogram(getGaussianData(10000), 20, -20, 20);
+    Histogram histogram2 = new Histogram(getGaussianData(5000), 20, -20, 20);
+    CategorySeries series1 =
+        chart.addSeries("histogram 2", histogram2.getxAxisData(), histogram2.getyAxisData());
+    CategorySeries series2 =
+        chart.addSeries("histogram 1", histogram1.getxAxisData(), histogram1.getyAxisData());
+
+    // Set both series to SteppedBar
+    series2.setChartCategorySeriesRenderStyle(CategorySeriesRenderStyle.SteppedBar);
+    series1.setChartCategorySeriesRenderStyle(CategorySeriesRenderStyle.SteppedBar);
+
+    // Remove the outline from the first series
+    series1.setLineColor(new Color(0, 0, 0, 0));
+
+    // Make the fill of the second series transparent, leaving us with only the outline
+    series2.setFillColor(new Color(0, 0, 0, 0));
+
+    // Also give it a nice dotted-line apperance
+    BasicStroke baseLineStyle = new BasicStroke();
+    BasicStroke newLineStyle =
+        new BasicStroke(
+            2f,
+            baseLineStyle.getEndCap(),
+            baseLineStyle.getLineJoin(),
+            baseLineStyle.getMiterLimit(),
+            new float[] {5, 5},
+            baseLineStyle.getDashPhase());
+
+    series2.setLineStyle(newLineStyle);
+
+    return chart;
+  }
+
+  private List<Double> getGaussianData(int count) {
+
+    List<Double> data = new ArrayList<Double>(count);
+    Random r = new Random();
+    for (int i = 0; i < count; i++) {
+      data.add(r.nextGaussian() * 10);
+    }
+    return data;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - Stepped Bars with Line Styling";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/bar/BarChart11.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/bar/BarChart11.java
new file mode 100644
index 0000000000000000000000000000000000000000..3d5a9f60566d31d906f6ccac9bab5368d866eb12
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/bar/BarChart11.java
@@ -0,0 +1,88 @@
+package org.knowm.xchart.demo.charts.bar;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.util.Random;
+import org.knowm.xchart.CategoryChart;
+import org.knowm.xchart.CategoryChartBuilder;
+import org.knowm.xchart.CategorySeries;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.demo.charts.ExampleChart;
+
+/**
+ * Stacked Bar Chart
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>int categories as array
+ *   <li>customized data labels
+ */
+public class BarChart11 implements ExampleChart<CategoryChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<CategoryChart> exampleChart = new BarChart11();
+    CategoryChart chart = exampleChart.getChart();
+    new SwingWrapper<>(chart).displayChart();
+  }
+
+  private static int[] getRandomValues(int startRange, int endRange, int count) {
+
+    int[] values = new int[count];
+    Random rand = new Random();
+    for (int i = 0; i < count; i++) {
+      values[i] = rand.nextInt(endRange - startRange) + startRange;
+    }
+    return values;
+  }
+
+  private static int[] getLinearValues(int startRange, int endRange, int count) {
+
+    int[] values = new int[count];
+    int step = (endRange - startRange) / count;
+    for (int i = 0; i < count; i++) {
+      values[i] = step * i;
+    }
+    return values;
+  }
+
+  @Override
+  public CategoryChart getChart() {
+
+    // Create Chart
+    CategoryChart chart =
+        new CategoryChartBuilder()
+            .width(800)
+            .height(600)
+            .title(getClass().getSimpleName())
+            .xAxisTitle("Speed")
+            .yAxisTitle("Spin")
+            .build();
+
+    // Customize Chart
+    chart.getStyler().setPlotGridVerticalLinesVisible(false);
+    chart.getStyler().setLegendVisible(false);
+    chart.getStyler().setStacked(true);
+    chart.getStyler().setLabelsVisible(true);
+    chart.getStyler().setLabelsFont(new Font(Font.MONOSPACED, Font.BOLD, 13));
+    chart.getStyler().setLabelsFontColor(Color.WHITE);
+    chart.getStyler().setLabelsPosition(.5);
+    chart.getStyler().setLabelsFontColorAutomaticEnabled(false);
+    chart.getStyler().setLabelsRotation(45);
+
+    // Series
+    CategorySeries series1 =
+        chart.addSeries("series1", getLinearValues(0, 200, 6), getRandomValues(10, 50, 6));
+    CategorySeries series2 =
+        chart.addSeries("series2", getLinearValues(0, 200, 6), getRandomValues(10, 50, 6));
+
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - Stacked Stepped Bars with Customized Data Labels";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/bar/BarChart12.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/bar/BarChart12.java
new file mode 100644
index 0000000000000000000000000000000000000000..a4e23395f971735a367ec4fd70e8583d5ebb1d22
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/bar/BarChart12.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2024 Energía Plus. All rights reserved.
+ */
+
+package org.knowm.xchart.demo.charts.bar;
+
+import java.awt.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Random;
+import org.knowm.xchart.CategoryChart;
+import org.knowm.xchart.CategoryChartBuilder;
+import org.knowm.xchart.CategorySeries;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.demo.charts.ExampleChart;
+
+/**
+ * Stacked Bars with Overlapped Line Chart
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>bar series are stacked
+ *   <li>line series is overlapped
+ */
+public class BarChart12 implements ExampleChart<CategoryChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<CategoryChart> exampleChart = new BarChart12();
+    CategoryChart chart = exampleChart.getChart();
+    new SwingWrapper<>(chart).displayChart();
+  }
+
+  private static List<String> getMonths() {
+    return Arrays.asList(
+        "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
+  }
+
+  private static List<Double> getRandomValues(int count) {
+
+    List<Double> values = new ArrayList<>(count);
+    Random rand = new Random();
+    for (int i = 0; i < count; i++) {
+      values.add(rand.nextDouble() * 1000);
+    }
+    return values;
+  }
+
+  @Override
+  public CategoryChart getChart() {
+
+    // Create Chart
+    CategoryChart chart =
+        new CategoryChartBuilder()
+            .width(800)
+            .height(600)
+            .title(getClass().getSimpleName())
+            .xAxisTitle("Month")
+            .yAxisTitle("Consumption")
+            .build();
+
+    // Customize Chart
+    chart.getStyler().setPlotGridVerticalLinesVisible(false);
+    chart.getStyler().setLegendVisible(true);
+    chart.getStyler().setStacked(true);
+    chart
+        .getStyler()
+        .setSeriesColors(
+            new Color[] {
+              Color.decode("#2133D0"),
+              Color.decode("#FF3B47"),
+              Color.decode("#FFBD00"),
+              Color.DARK_GRAY
+            });
+
+    List<String> months = getMonths();
+    List<Double> period1Values = getRandomValues(12);
+    List<Double> period2Values = getRandomValues(12);
+    List<Double> period3Values = getRandomValues(12);
+    List<Double> averageValues = new ArrayList<>();
+    for (int i = 0; i < 12; i++) {
+      averageValues.add((period1Values.get(i) + period2Values.get(i) + period3Values.get(i)) / 3);
+    }
+
+    // Series
+    CategorySeries staked1 = chart.addSeries("Period 1", months, period1Values);
+    CategorySeries staked2 = chart.addSeries("Period 2", months, period2Values);
+    CategorySeries staked3 = chart.addSeries("Period 3", months, period3Values);
+    CategorySeries overlappedLine = chart.addSeries("Average", months, averageValues);
+    overlappedLine.setOverlapped(true);
+    overlappedLine.setChartCategorySeriesRenderStyle(CategorySeries.CategorySeriesRenderStyle.Line);
+
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - Stacked Bars with Overlapped Line Chart";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/box/BoxChart01.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/box/BoxChart01.java
new file mode 100644
index 0000000000000000000000000000000000000000..7534ceee1d1d0307b49a4e30c070f09b022bfa5f
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/box/BoxChart01.java
@@ -0,0 +1,45 @@
+package org.knowm.xchart.demo.charts.box;
+
+import java.util.Arrays;
+import org.knowm.xchart.BoxChart;
+import org.knowm.xchart.BoxChartBuilder;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.style.Styler.ChartTheme;
+
+/*
+ * Box Chart with 3 series
+ */
+public class BoxChart01 implements ExampleChart<BoxChart> {
+
+  public static void main(String[] args) {
+    ExampleChart<BoxChart> exampleChart = new BoxChart01();
+    BoxChart chart = exampleChart.getChart();
+    new SwingWrapper<BoxChart>(chart).displayChart();
+  }
+
+  @Override
+  public BoxChart getChart() {
+
+    // Create Chart
+    BoxChart chart =
+        new BoxChartBuilder()
+            .title("box plot demo")
+            .xAxisTitle("X")
+            .yAxisTitle("Y")
+            .theme(ChartTheme.GGPlot2)
+            .build();
+
+    // Series
+    chart.addSeries("aaa", Arrays.asList(40, 30, 20, 60, 50));
+    chart.addSeries("bbb", Arrays.asList(-20, -10, -30, -15, -25));
+    chart.addSeries("ccc", Arrays.asList(50, -20));
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + "- 3 Boxes";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/box/BoxChart02.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/box/BoxChart02.java
new file mode 100644
index 0000000000000000000000000000000000000000..66719604cc176aa655963d228590d386a5aa5013
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/box/BoxChart02.java
@@ -0,0 +1,52 @@
+package org.knowm.xchart.demo.charts.box;
+
+import java.util.Arrays;
+import org.knowm.xchart.BoxChart;
+import org.knowm.xchart.BoxChartBuilder;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.style.BoxStyler.BoxplotCalCulationMethod;
+import org.knowm.xchart.style.Styler.ChartTheme;
+
+/*
+ * Box Plot with 3 series
+ * plot data points
+ */
+public class BoxChart02 implements ExampleChart<BoxChart> {
+
+  public static void main(String[] args) {
+    ExampleChart<BoxChart> exampleChart = new BoxChart02();
+    BoxChart chart = exampleChart.getChart();
+    new SwingWrapper<BoxChart>(chart).displayChart();
+  }
+
+  @Override
+  public BoxChart getChart() {
+
+    // Create Chart
+    BoxChart chart =
+        new BoxChartBuilder()
+            .title("box plot show all points")
+            .xAxisTitle("X")
+            .yAxisTitle("Y")
+            .theme(ChartTheme.Matlab)
+            .build();
+
+    // Customize Chart
+    chart.getStyler().setBoxplotCalCulationMethod(BoxplotCalCulationMethod.N_LESS_1_PLUS_1);
+
+    // Series
+    chart.addSeries("aaa", Arrays.asList(1, 2, 3, 4, 5, 6));
+    chart.addSeries("bbb", Arrays.asList(1, 2, 3, 4, 5, 6, 17));
+    chart.addSeries("ccc", Arrays.asList(-10, -8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 20, 21));
+    chart.getStyler().setShowWithinAreaPoint(true);
+    chart.getStyler().setToolTipsEnabled(true);
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + "- Show normal points and abnormal points";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/box/BoxChart03.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/box/BoxChart03.java
new file mode 100644
index 0000000000000000000000000000000000000000..abdb2ed952cf479ab1eb772241becae2171a4b8e
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/box/BoxChart03.java
@@ -0,0 +1,51 @@
+package org.knowm.xchart.demo.charts.box;
+
+import java.util.Arrays;
+import org.knowm.xchart.BoxChart;
+import org.knowm.xchart.BoxChartBuilder;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.style.Styler.ChartTheme;
+
+/*
+ * Box Plot with 1 series
+ * and show ToolTips
+ * and Y-Axis is logarithmic
+ */
+public class BoxChart03 implements ExampleChart<BoxChart> {
+
+  public static void main(String[] args) {
+    ExampleChart<BoxChart> exampleChart = new BoxChart03();
+    BoxChart chart = exampleChart.getChart();
+    new SwingWrapper<BoxChart>(chart).displayChart();
+  }
+
+  @Override
+  public BoxChart getChart() {
+
+    // Create Chart
+    BoxChart chart =
+        new BoxChartBuilder()
+            .width(600)
+            .height(450)
+            .title("Y Axis Logarithmic-box plot demo")
+            .xAxisTitle("X")
+            .yAxisTitle("Y")
+            .theme(ChartTheme.XChart)
+            .build();
+
+    // Customize Chart
+    chart.getStyler().setToolTipsEnabled(true);
+    chart.getStyler().setYAxisLogarithmic(true);
+
+    // Series
+    chart.addSeries("aaa", Arrays.asList(10, 40, 80, 120, 350));
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - Logarithmic Y-Axis";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/bubble/BubbleChart01.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/bubble/BubbleChart01.java
new file mode 100644
index 0000000000000000000000000000000000000000..e5f829d0d82dc288d5deafe43a1612e741bc35c9
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/bubble/BubbleChart01.java
@@ -0,0 +1,65 @@
+package org.knowm.xchart.demo.charts.bubble;
+
+import org.knowm.xchart.BubbleChart;
+import org.knowm.xchart.BubbleChartBuilder;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.style.Styler;
+
+/**
+ * Basic Bubble Chart
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>Bubble Chart
+ *   <li>Legend Inside North with Horizontal Layout
+ */
+public class BubbleChart01 implements ExampleChart<BubbleChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<BubbleChart> exampleChart = new BubbleChart01();
+    BubbleChart chart = exampleChart.getChart();
+    new SwingWrapper<>(chart).displayChart();
+  }
+
+  @Override
+  public BubbleChart getChart() {
+
+    // Create Chart
+    BubbleChart chart =
+        new BubbleChartBuilder()
+            .width(800)
+            .height(600)
+            .title(getClass().getSimpleName())
+            .xAxisTitle("X")
+            .yAxisTitle("Y")
+            .build();
+    chart.getStyler().setLegendPosition(Styler.LegendPosition.InsideN);
+    chart.getStyler().setLegendLayout(Styler.LegendLayout.Horizontal);
+    chart.getStyler().setToolTipsEnabled(true);
+
+    // Customize Chart
+
+    // Series
+    double[] xData = new double[] {1.5, 2.6, 3.3, 4.9, 5.5, 6.3, 1, 2.0, 3.0, 4.0, 5, 6};
+    double[] yData = new double[] {10, 4, 7, 7.7, 7, 5.5, 10, 4, 7, 1, 7, 9};
+    double[] bubbleData = new double[] {17, 40, 50, 51, 26, 20, 66, 35, 80, 27, 29, 44};
+
+    double[] xData2 = new double[] {1, 2.0, 3.0, 4.0, 5, 6, 1.5, 2.6, 3.3, 4.9, 5.5, 6.3};
+    double[] yData2 = new double[] {1, 2, 3, 4, 5, 6, 10, 8.5, 4, 1, 4.7, 9};
+    double[] bubbleData2 = new double[] {37, 35, 80, 27, 29, 44, 57, 40, 50, 33, 26, 20};
+
+    chart.addSeries("A", xData, yData, bubbleData);
+    chart.addSeries("B", xData2, yData2, bubbleData2);
+
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - Basic Bubble Chart";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/date/DateChart01.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/date/DateChart01.java
new file mode 100644
index 0000000000000000000000000000000000000000..c79918fcceba4a23afd5b024a2d5f07e5303f9de
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/date/DateChart01.java
@@ -0,0 +1,92 @@
+package org.knowm.xchart.demo.charts.date;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Random;
+import java.util.TimeZone;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYChartBuilder;
+import org.knowm.xchart.XYSeries;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.style.Styler;
+import org.knowm.xchart.style.markers.SeriesMarkers;
+
+/**
+ * Millisecond Scale
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>Millisecond Scale
+ *   <li>LegendPosition.OutsideS
+ *   <li>Two YAxis Groups - both on left
+ *   <li>Zooming by dragging a selection box over area of interest
+ */
+public class DateChart01 implements ExampleChart<XYChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<XYChart> exampleChart = new DateChart01();
+    XYChart chart = exampleChart.getChart();
+    new SwingWrapper<>(chart).displayChart();
+  }
+
+  @Override
+  public XYChart getChart() {
+
+    // Create Chart
+    XYChart chart =
+        new XYChartBuilder().width(800).height(600).title(getClass().getSimpleName()).build();
+
+    // Customize Chart
+    chart.getStyler().setLegendPosition(Styler.LegendPosition.OutsideS);
+    chart.getStyler().setLegendLayout(Styler.LegendLayout.Horizontal);
+    chart.getStyler().setZoomEnabled(true);
+    //    chart.getStyler().setZoomResetButtomPosition(Styler.CardinalPosition.InsideS);
+    //    chart.getStyler().setZoomResetByDoubleClick(false);
+    //    chart.getStyler().setZoomResetByButton(true);
+    //    chart.getStyler().setZoomSelectionColor(new Color(0, 0, 192, 128));
+
+    // Series
+    Random random = new Random();
+
+    // generate data
+    List<Date> xData1 = new ArrayList<>();
+    List<Double> yData1 = new ArrayList<>();
+    List<Date> xData2 = new ArrayList<>();
+    List<Double> yData2 = new ArrayList<>();
+
+    DateFormat sdf = new SimpleDateFormat("HH:mm:ss.S");
+    sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
+    Date date = null;
+    for (int i = 1; i <= 14; i++) {
+
+      try {
+        date = sdf.parse("23:45:31." + (100 * i + random.nextInt(20)));
+      } catch (ParseException e) {
+        e.printStackTrace();
+      }
+      xData1.add(date);
+      xData2.add(date);
+      yData1.add(Math.random() * i);
+      yData2.add(Math.random() * i * 100);
+    }
+
+    XYSeries series = chart.addSeries("series 1", xData1, yData1);
+    series.setMarker(SeriesMarkers.NONE);
+    chart.addSeries("series 2", xData2, yData2).setMarker(SeriesMarkers.NONE).setYAxisGroup(1);
+
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - Millisecond Scale with Two Separate Y Axis Groups";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/date/DateChart02.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/date/DateChart02.java
new file mode 100644
index 0000000000000000000000000000000000000000..09d5ec86a0351c57cf5b3c1523a5441411b05c3d
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/date/DateChart02.java
@@ -0,0 +1,66 @@
+package org.knowm.xchart.demo.charts.date;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Random;
+import java.util.TimeZone;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYChartBuilder;
+import org.knowm.xchart.XYSeries.XYSeriesRenderStyle;
+import org.knowm.xchart.demo.charts.ExampleChart;
+
+/** Second Scale */
+public class DateChart02 implements ExampleChart<XYChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<XYChart> exampleChart = new DateChart02();
+    XYChart chart = exampleChart.getChart();
+    new SwingWrapper<>(chart).displayChart();
+  }
+
+  @Override
+  public XYChart getChart() {
+
+    // Create Chart
+    XYChart chart = new XYChartBuilder().width(800).height(600).title("Second Scale").build();
+
+    // Customize Chart
+    chart.getStyler().setLegendVisible(false);
+    chart.getStyler().setDefaultSeriesRenderStyle(XYSeriesRenderStyle.Area);
+
+    // Series
+    List<Date> xData = new ArrayList<>();
+    List<Double> yData = new ArrayList<>();
+
+    Random random = new Random();
+
+    DateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
+    sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
+    Date date = null;
+    for (int i = 1; i <= 14; i++) {
+      try {
+        date = sdf.parse("23:45:" + (5 * i + random.nextInt(2)) + "." + random.nextInt(1000));
+      } catch (ParseException e) {
+        e.printStackTrace();
+      }
+      xData.add(date);
+      yData.add(Math.random() * i);
+    }
+
+    chart.addSeries("blah", xData, yData);
+
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - Second Scale";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/date/DateChart03.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/date/DateChart03.java
new file mode 100644
index 0000000000000000000000000000000000000000..dee13e77879a7660ecb7d896504229959d078674
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/date/DateChart03.java
@@ -0,0 +1,90 @@
+package org.knowm.xchart.demo.charts.date;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Random;
+import java.util.TimeZone;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYChartBuilder;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.style.Styler;
+
+/**
+ * Minute Scale *
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>Minute Scale
+ *   <li>10^9 formatting
+ *   <li>LegendPosition.InsideS
+ *   <li>Two YAxis Groups - one on left, one on right
+ */
+public class DateChart03 implements ExampleChart<XYChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<XYChart> exampleChart = new DateChart03();
+    XYChart chart = exampleChart.getChart();
+    new SwingWrapper<>(chart).displayChart();
+  }
+
+  @Override
+  public XYChart getChart() {
+
+    // Create Chart
+    XYChart chart = new XYChartBuilder().width(800).height(600).title("Minute Scale").build();
+
+    // Customize Chart
+    chart.getStyler().setLegendPosition(Styler.LegendPosition.InsideS);
+    chart.getStyler().setYAxisGroupPosition(1, Styler.YAxisPosition.Right);
+
+    // Series
+    List<Date> xData1 = new ArrayList<>();
+    List<Double> yData1 = new ArrayList<>();
+    List<Date> xData2 = new ArrayList<>();
+    List<Double> yData2 = new ArrayList<>();
+
+    Random random = new Random();
+
+    DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd-HH:mm:ss.SSS");
+    sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
+    Date date = null;
+    for (int i = 1; i <= 14; i++) {
+      try {
+        date =
+            sdf.parse(
+                "2013-07-22-08:"
+                    + (5 * i + random.nextInt(2))
+                    + ":"
+                    + (random.nextInt(2))
+                    + "."
+                    + random.nextInt(1000));
+      } catch (ParseException e) {
+        e.printStackTrace();
+      }
+      // System.out.println(date.getTime());
+      // System.out.println(date.toString());
+      xData1.add(date);
+      xData2.add(date);
+      yData1.add(Math.random() * i * 1000000000);
+      yData2.add(Math.random() * i * 10);
+    }
+
+    chart.addSeries("series1", xData1, yData1).setYAxisGroup(1);
+    chart.addSeries("series2", xData2, yData2);
+
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - Minute Scale with Two Separate Y Axis Groups";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/date/DateChart04.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/date/DateChart04.java
new file mode 100644
index 0000000000000000000000000000000000000000..fde6e48e579e931e9939b129294968f660e6f910
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/date/DateChart04.java
@@ -0,0 +1,70 @@
+package org.knowm.xchart.demo.charts.date;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Random;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYChartBuilder;
+import org.knowm.xchart.demo.charts.ExampleChart;
+
+/**
+ * Hour Scale
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>Hiding Y-Axis Axis Ticks (labels, tick marks, tick line)
+ */
+public class DateChart04 implements ExampleChart<XYChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<XYChart> exampleChart = new DateChart04();
+    XYChart chart = exampleChart.getChart();
+    new SwingWrapper<>(chart).displayChart();
+  }
+
+  @Override
+  public XYChart getChart() {
+
+    // Create Chart
+    XYChart chart = new XYChartBuilder().width(800).height(600).title("Hour Scale").build();
+
+    // Customize Chart
+    chart.getStyler().setLegendVisible(false);
+    chart.getStyler().setYAxisTicksVisible(false);
+
+    // Series
+    List<Date> xData = new ArrayList<>();
+    List<Double> yData = new ArrayList<>();
+
+    Random random = new Random();
+
+    DateFormat sdf = new SimpleDateFormat("dd-HH");
+    Date date = null;
+    for (int i = 1; i <= 14; i++) {
+      try {
+        date = sdf.parse("25-" + (2 * i + random.nextInt(2)));
+      } catch (ParseException e) {
+        e.printStackTrace();
+      }
+      xData.add(date);
+      yData.add(Math.random() * i / 10000000000.0);
+    }
+
+    chart.addSeries("blah", xData, yData);
+
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - Hour Scale";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/date/DateChart05.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/date/DateChart05.java
new file mode 100644
index 0000000000000000000000000000000000000000..dacacca8269b4cc8b01ada19f0aa679add5ce66a
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/date/DateChart05.java
@@ -0,0 +1,64 @@
+package org.knowm.xchart.demo.charts.date;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Random;
+import java.util.TimeZone;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYChartBuilder;
+import org.knowm.xchart.demo.charts.ExampleChart;
+
+/** Day Scale */
+public class DateChart05 implements ExampleChart<XYChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<XYChart> exampleChart = new DateChart05();
+    XYChart chart = exampleChart.getChart();
+    new SwingWrapper<>(chart).displayChart();
+  }
+
+  @Override
+  public XYChart getChart() {
+
+    // Create Chart
+    XYChart chart = new XYChartBuilder().width(800).height(600).title("Day Scale").build();
+
+    // Customize Chart
+    chart.getStyler().setLegendVisible(false);
+
+    // Series
+    List<Date> xData = new ArrayList<>();
+    List<Double> yData = new ArrayList<>();
+
+    Random random = new Random();
+
+    DateFormat sdf = new SimpleDateFormat("MM-dd");
+    sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
+    Date date = null;
+    for (int i = 1; i <= 14; i++) {
+      try {
+        date = sdf.parse("02-" + (4 * i + random.nextInt(2)));
+      } catch (ParseException e) {
+        e.printStackTrace();
+      }
+      xData.add(date);
+      yData.add(Math.random() * i / -100000000);
+    }
+
+    chart.addSeries("blah", xData, yData);
+
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - Day Scale";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/date/DateChart06.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/date/DateChart06.java
new file mode 100644
index 0000000000000000000000000000000000000000..c60661bfc70ab3c0cb6982143823c91241e9d407
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/date/DateChart06.java
@@ -0,0 +1,183 @@
+package org.knowm.xchart.demo.charts.date;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Random;
+import java.util.TimeZone;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYChartBuilder;
+import org.knowm.xchart.demo.charts.ExampleChart;
+
+/**
+ * Month scale
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>Setting custom Y-Axis tick labels
+ */
+public class DateChart06 implements ExampleChart<XYChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<XYChart> exampleChart = new DateChart06();
+    XYChart chart = exampleChart.getChart();
+    new SwingWrapper<>(chart).displayChart();
+  }
+
+  @Override
+  public XYChart getChart() {
+
+    // Create Chart
+    XYChart chart = new XYChartBuilder().width(800).height(600).title("Month Scale").build();
+
+    // Customize Chart
+    chart.getStyler().setLegendVisible(false);
+
+    // Series
+    List<Date> xData = new ArrayList<>();
+    List<Double> yData = new ArrayList<>();
+
+    Random random = new Random();
+
+    DateFormat sdf = new SimpleDateFormat("yyyy-MM");
+    sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
+    Date date = null;
+    for (int i = 1; i <= 14; i++) {
+      try {
+        date = sdf.parse("2013-" + (2 * i + random.nextInt(1)));
+      } catch (ParseException e) {
+        e.printStackTrace();
+      }
+      xData.add(date);
+      yData.add(Math.random() * i);
+    }
+
+    chart.addSeries("blah", xData, yData);
+
+    chart
+        .getStyler()
+        .setyAxisTickLabelsFormattingFunction(x -> NumberWordConverter.convert(x.intValue()));
+
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - Month Scale with custom Y-Axis tick labels";
+  }
+
+  static class NumberWordConverter {
+
+    public static final String[] units = {
+      "",
+      "one",
+      "two",
+      "three",
+      "four",
+      "five",
+      "six",
+      "seven",
+      "eight",
+      "nine",
+      "ten",
+      "eleven",
+      "twelve",
+      "thirteen",
+      "fourteen",
+      "fifteen",
+      "sixteen",
+      "seventeen",
+      "eighteen",
+      "nineteen"
+    };
+
+    public static final String[] tens = {
+      "", // 0
+      "", // 1
+      "twenty", // 2
+      "thirty", // 3
+      "forty", // 4
+      "fifty", // 5
+      "sixty", // 6
+      "seventy", // 7
+      "eighty", // 8
+      "ninety" // 9
+    };
+
+    public static String convert(final int n) {
+      //      System.out.println("n = " + n);
+
+      if (n == 0) {
+        return "zero";
+      }
+
+      if (n < 0) {
+        return "minus " + convert(-n);
+      }
+
+      if (n < 20) {
+        return units[n];
+      }
+
+      if (n < 100) {
+        return tens[n / 10] + ((n % 10 != 0) ? " " : "") + units[n % 10];
+      }
+
+      if (n < 1000) {
+        return units[n / 100] + " hundred" + ((n % 100 != 0) ? " " : "") + convert(n % 100);
+      }
+
+      if (n < 1000000) {
+        return convert(n / 1000) + " thousand" + ((n % 1000 != 0) ? " " : "") + convert(n % 1000);
+      }
+
+      if (n < 1000000000) {
+        return convert(n / 1000000)
+            + " million"
+            + ((n % 1000000 != 0) ? " " : "")
+            + convert(n % 1000000);
+      }
+
+      return convert(n / 1000000000)
+          + " billion"
+          + ((n % 1000000000 != 0) ? " " : "")
+          + convert(n % 1000000000);
+    }
+
+    //    public static void main(final String[] args) {
+    //      final Random generator = new Random();
+    //
+    //      int n;
+    //      for (int i = 0; i < 20; i++) {
+    //        n = generator.nextInt(Integer.MAX_VALUE);
+    //
+    //        System.out.printf("%10d = '%s'%n", n, convert(n));
+    //      }
+    //
+    //      n = 1000;
+    //      System.out.printf("%10d = '%s'%n", n, convert(n));
+    //
+    //      n = 2000;
+    //      System.out.printf("%10d = '%s'%n", n, convert(n));
+    //
+    //      n = 10000;
+    //      System.out.printf("%10d = '%s'%n", n, convert(n));
+    //
+    //      n = 11000;
+    //      System.out.printf("%10d = '%s'%n", n, convert(n));
+    //
+    //      n = 999999999;
+    //      System.out.printf("%10d = '%s'%n", n, convert(n));
+    //
+    //      n = Integer.MAX_VALUE;
+    //      System.out.printf("%10d = '%s'%n", n, convert(n));
+    //    }
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/date/DateChart07.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/date/DateChart07.java
new file mode 100644
index 0000000000000000000000000000000000000000..4237638c399c957403bd82f258dc72d25a5a9ee6
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/date/DateChart07.java
@@ -0,0 +1,64 @@
+package org.knowm.xchart.demo.charts.date;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Random;
+import java.util.TimeZone;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYChartBuilder;
+import org.knowm.xchart.demo.charts.ExampleChart;
+
+/** Year scale */
+public class DateChart07 implements ExampleChart<XYChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<XYChart> exampleChart = new DateChart07();
+    XYChart chart = exampleChart.getChart();
+    new SwingWrapper<>(chart).displayChart();
+  }
+
+  @Override
+  public XYChart getChart() {
+
+    // Create Chart
+    XYChart chart = new XYChartBuilder().width(800).height(600).title("Year Scale").build();
+
+    // Customize Chart
+    chart.getStyler().setLegendVisible(false);
+
+    // Series
+    List<Date> xData = new ArrayList<>();
+    List<Double> yData = new ArrayList<>();
+
+    Random random = new Random();
+
+    DateFormat sdf = new SimpleDateFormat("yyyy-MM");
+    sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
+    Date date = null;
+    for (int i = 1; i <= 14; i++) {
+      try {
+        date = sdf.parse("" + (2001 + i) + "-" + random.nextInt(12));
+      } catch (ParseException e) {
+        e.printStackTrace();
+      }
+      xData.add(date);
+      yData.add(Math.random() * i);
+    }
+
+    chart.addSeries("blah", xData, yData);
+
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - Year Scale";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/date/DateChart08.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/date/DateChart08.java
new file mode 100644
index 0000000000000000000000000000000000000000..e7b7bf521564e81c33cd2a2ea4cbdc9ff2cfe991
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/date/DateChart08.java
@@ -0,0 +1,76 @@
+package org.knowm.xchart.demo.charts.date;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Random;
+import java.util.TimeZone;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYChartBuilder;
+import org.knowm.xchart.demo.charts.ExampleChart;
+
+/**
+ * Year scale
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>Rotated X-Axis labels
+ *   <li>Setting a custom date formatter String
+ *   <li>Smooth series
+ */
+public class DateChart08 implements ExampleChart<XYChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<XYChart> exampleChart = new DateChart08();
+    XYChart chart = exampleChart.getChart();
+    new SwingWrapper<XYChart>(chart).displayChart();
+  }
+
+  @Override
+  public XYChart getChart() {
+
+    // Create Chart
+    XYChart chart =
+        new XYChartBuilder().width(800).height(600).title(getClass().getSimpleName()).build();
+
+    // Customize Chart
+    chart.getStyler().setLegendVisible(false);
+    chart.getStyler().setXAxisLabelRotation(60);
+    chart.getStyler().setDatePattern("yyyy-MM-dd");
+
+    // Series
+    List<Date> xData = new ArrayList<>();
+    List<Double> yData = new ArrayList<>();
+
+    Random random = new Random();
+
+    DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+    sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
+    Date date = null;
+    for (int i = 1; i <= 14; i++) {
+      try {
+        date = sdf.parse("" + (2001 + i) + "-" + random.nextInt(12) + "-" + random.nextInt(28));
+      } catch (ParseException e) {
+        e.printStackTrace();
+      }
+      xData.add(date);
+      yData.add(Math.random() * i);
+    }
+
+    chart.addSeries("blah", xData, yData).setSmooth(true);
+
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - Rotated Tick Labels";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/date/DateChart09.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/date/DateChart09.java
new file mode 100644
index 0000000000000000000000000000000000000000..2a6fd56154d45529bd7ae428b410d22907e418cf
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/date/DateChart09.java
@@ -0,0 +1,81 @@
+package org.knowm.xchart.demo.charts.date;
+
+import java.time.LocalDateTime;
+import java.time.Month;
+import java.time.format.DateTimeFormatter;
+import java.util.List;
+import java.util.Random;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYChartBuilder;
+import org.knowm.xchart.demo.charts.ExampleChart;
+
+/**
+ * Year scale
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>Rotated 90 degrees X-Axis labels
+ *   <li>Setting custom X-Axis tick labels
+ *   <li>Setting custom cursor tool tip text
+ */
+public class DateChart09 implements ExampleChart<XYChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<XYChart> exampleChart = new DateChart09();
+    XYChart chart = exampleChart.getChart();
+    new SwingWrapper<>(chart).displayChart();
+  }
+
+  @Override
+  public XYChart getChart() {
+
+    // Create Chart
+    XYChart chart =
+        new XYChartBuilder().width(800).height(600).title(getClass().getSimpleName()).build();
+
+    // Customize Chart
+    chart.getStyler().setLegendVisible(false);
+    chart.getStyler().setXAxisLabelRotation(90);
+
+    // Series
+    List<Integer> xData = IntStream.range(0, 365).boxed().collect(Collectors.toList());
+    Random random = new Random();
+
+    List<Double> yData =
+        IntStream.range(0, xData.size())
+            .mapToDouble(x -> random.nextDouble())
+            .boxed()
+            .collect(Collectors.toList());
+
+    chart.addSeries("blah", xData, yData);
+
+    // set custom X-Axis tick labels
+    LocalDateTime startTime = LocalDateTime.of(2001, Month.JANUARY, 1, 0, 0, 0);
+    DateTimeFormatter xTickFormatter = DateTimeFormatter.ofPattern("LLL");
+    chart
+        .getStyler()
+        .setxAxisTickLabelsFormattingFunction(
+            x -> startTime.plusDays(x.longValue()).format(xTickFormatter));
+
+    // set custom cursor tool tip text
+    chart.getStyler().setCursorEnabled(true);
+    DateTimeFormatter cursorXFormatter = DateTimeFormatter.ofPattern("LLL dd");
+    chart
+        .getStyler()
+        .setCustomCursorXDataFormattingFunction(
+            x -> startTime.plusDays(x.longValue()).format(cursorXFormatter));
+
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - Custom Date Formatter Without Years";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/dial/DialChart01.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/dial/DialChart01.java
new file mode 100644
index 0000000000000000000000000000000000000000..1cba753c7e4d2d0c97bb750c9e7f350659990c9c
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/dial/DialChart01.java
@@ -0,0 +1,46 @@
+package org.knowm.xchart.demo.charts.dial;
+
+import org.knowm.xchart.DialChart;
+import org.knowm.xchart.DialChartBuilder;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.demo.charts.ExampleChart;
+
+/**
+ * Dial Chart
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>Dial Chart
+ *   <li>DialChartBuilder
+ */
+public class DialChart01 implements ExampleChart<DialChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<DialChart> exampleChart = new DialChart01();
+    DialChart chart = exampleChart.getChart();
+    new SwingWrapper<>(chart).displayChart();
+  }
+
+  @Override
+  public DialChart getChart() {
+
+    // Create Chart
+    DialChart chart =
+        new DialChartBuilder().width(800).height(600).title(getClass().getSimpleName()).build();
+
+    // Series
+    chart.addSeries("Rate", 0.9381, "93.81 %");
+    chart.getStyler().setToolTipsEnabled(true);
+    chart.getStyler().setLabelVisible(true);
+    chart.getStyler().setLegendVisible(false);
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - Basic Dial Chart";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/dial/DialChart02.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/dial/DialChart02.java
new file mode 100644
index 0000000000000000000000000000000000000000..43ee0d7f62bd25940b8e3033e00a58143a80ef2c
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/dial/DialChart02.java
@@ -0,0 +1,92 @@
+package org.knowm.xchart.demo.charts.dial;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Font;
+import org.knowm.xchart.DialChart;
+import org.knowm.xchart.DialChartBuilder;
+import org.knowm.xchart.DialSeries;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.style.Styler;
+
+/**
+ * Dial Chart
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>Dial Chart
+ *   <li>DialChartBuilder
+ *   <li>Highly customized
+ *   <li>GGPlot Theme
+ */
+public class DialChart02 implements ExampleChart<DialChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<DialChart> exampleChart = new DialChart02();
+    DialChart chart = exampleChart.getChart();
+    new SwingWrapper<>(chart).displayChart();
+  }
+
+  @Override
+  public DialChart getChart() {
+
+    // Create Chart
+    DialChart chart =
+        new DialChartBuilder()
+            .width(800)
+            .height(600)
+            .title(getClass().getSimpleName())
+            .theme(Styler.ChartTheme.XChart)
+            .build();
+
+    // Series
+    DialSeries series = chart.addSeries("Rate", 0.55, "55 %");
+
+    chart.getStyler().setToolTipsEnabled(true);
+    chart.getStyler().setLegendVisible(true);
+    chart.getStyler().setArcAngle(330);
+
+    chart.getStyler().setDonutThickness(.33);
+    chart.getStyler().setCircular(true);
+
+    // arrow
+    chart.getStyler().setArrowArcAngle(40);
+    chart.getStyler().setArrowArcPercentage(.05);
+    chart.getStyler().setArrowLengthPercentage(.5);
+    chart.getStyler().setArrowColor(Color.RED);
+
+    chart.getStyler().setAxisTickLabelsVisible(true);
+    chart.getStyler().setAxisTicksMarksVisible(true);
+    chart.getStyler().setAxisTickMarksColor(Color.BLACK);
+    chart.getStyler().setAxisTickMarksStroke(new BasicStroke(3.0f));
+    chart.getStyler().setAxisTitleVisible(true);
+    chart.getStyler().setAxisTitleFont(new Font(Font.MONOSPACED, Font.BOLD, 13));
+    chart.getStyler().setAxisTitlePadding(30);
+    chart.getStyler().setAxisTickValues(new double[] {0, 0.2, 0.4, 0.6, 0.8, 1});
+    chart.getStyler().setAxisTickLabels(new String[] {"0", "20", "40", "60", "80", "100"});
+
+    chart.getStyler().setLowerFrom(0);
+    chart.getStyler().setLowerTo(.1);
+    chart.getStyler().setLowerColor(Color.LIGHT_GRAY);
+    chart.getStyler().setMiddleFrom(.1);
+    chart.getStyler().setMiddleTo(.8);
+    chart.getStyler().setMiddleColor(Color.GRAY);
+    chart.getStyler().setUpperFrom(.8);
+    chart.getStyler().setUpperTo(1);
+    chart.getStyler().setUpperColor(Color.DARK_GRAY);
+
+    chart.getStyler().setLabelVisible(true);
+    chart.getStyler().setLabelFont(new Font(Font.MONOSPACED, Font.BOLD, 8));
+
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - Highly Customized Dial Chart";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/heatmap/HeatMapChart01.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/heatmap/HeatMapChart01.java
new file mode 100644
index 0000000000000000000000000000000000000000..da784c4a79afb49dcfe76ee71fe856898c25d279
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/heatmap/HeatMapChart01.java
@@ -0,0 +1,56 @@
+package org.knowm.xchart.demo.charts.heatmap;
+
+import java.util.Random;
+import org.knowm.xchart.HeatMapChart;
+import org.knowm.xchart.HeatMapChartBuilder;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.demo.charts.ExampleChart;
+
+/**
+ * Basic HeatMap Chart
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>Default Styler
+ *   <li>PlotContentSize = 1
+ *   <li>Show value
+ */
+public class HeatMapChart01 implements ExampleChart<HeatMapChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<HeatMapChart> exampleChart = new HeatMapChart01();
+    HeatMapChart chart = exampleChart.getChart();
+    new SwingWrapper<HeatMapChart>(chart).displayChart();
+  }
+
+  @Override
+  public HeatMapChart getChart() {
+
+    // Create Chart
+    HeatMapChart chart =
+        new HeatMapChartBuilder().width(1000).height(600).title(getClass().getSimpleName()).build();
+
+    chart.getStyler().setPlotContentSize(1);
+    chart.getStyler().setShowValue(true);
+
+    int[] xData = {1, 2, 3, 4};
+    int[] yData = {1, 2, 3};
+    int[][] heatData = new int[xData.length][yData.length];
+    Random random = new Random();
+    for (int i = 0; i < xData.length; i++) {
+      for (int j = 0; j < yData.length; j++) {
+        heatData[i][j] = random.nextInt(1000);
+      }
+    }
+    chart.addSeries("Basic HeatMap", xData, yData, heatData);
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + "- Basic HeatMap Chart";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/heatmap/HeatMapChart02.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/heatmap/HeatMapChart02.java
new file mode 100644
index 0000000000000000000000000000000000000000..f0300e47f77cf48ebea24315424b06d4988bfc9c
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/heatmap/HeatMapChart02.java
@@ -0,0 +1,80 @@
+package org.knowm.xchart.demo.charts.heatmap;
+
+import java.awt.Color;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import org.knowm.xchart.HeatMapChart;
+import org.knowm.xchart.HeatMapChartBuilder;
+import org.knowm.xchart.HeatMapSeries;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.demo.charts.ExampleChart;
+
+/**
+ * HeatMap large
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>Set rangeColors
+ *   <li>HeatMapSeries setMin
+ *   <li>HeatMapSeries setMax
+ */
+public class HeatMapChart02 implements ExampleChart<HeatMapChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<HeatMapChart> exampleChart = new HeatMapChart02();
+    HeatMapChart chart = exampleChart.getChart();
+    new SwingWrapper<HeatMapChart>(chart).displayChart();
+  }
+
+  @Override
+  public HeatMapChart getChart() {
+
+    // Create Chart
+    HeatMapChart chart =
+        new HeatMapChartBuilder().width(800).height(600).title(getClass().getSimpleName()).build();
+
+    //    chart.getStyler().setPlotContentSize(0.999);
+    //    chart.getStyler().setXAxisMaxLabelCount(10);
+
+    Color[] rangeColors = {new Color(255, 182, 193), new Color(255, 20, 147), new Color(139, 0, 0)};
+    chart.getStyler().setRangeColors(rangeColors);
+
+    List<String> xData = new ArrayList<>();
+    for (BigDecimal bd = new BigDecimal("-2.0");
+        bd.doubleValue() <= 2;
+        bd = bd.add(new BigDecimal("0.04"))) {
+      xData.add(bd.toString());
+    }
+    List<String> yData = new ArrayList<>();
+    for (BigDecimal bd = new BigDecimal("-2.0");
+        bd.doubleValue() <= 2;
+        bd = bd.add(new BigDecimal("0.02"))) {
+      yData.add(bd.toString());
+    }
+    List<Number[]> heatData = new ArrayList<>();
+    for (int i = 0; i < xData.size(); i++) {
+      for (int j = 0; j < yData.size(); j++) {
+        Number[] numbers = {
+          i,
+          j,
+          Math.sin(Double.parseDouble(xData.get(i))) * Math.sin(Double.parseDouble(yData.get(j)))
+        };
+        heatData.add(numbers);
+      }
+    }
+
+    HeatMapSeries heatMapSeries = chart.addSeries("heatMap", xData, yData, heatData);
+    heatMapSeries.setMin(-1);
+    heatMapSeries.setMax(1);
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - HeatMap Large";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/heatmap/HeatMapChart03.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/heatmap/HeatMapChart03.java
new file mode 100644
index 0000000000000000000000000000000000000000..dde588b2cdbb89be5929b32dd3e322f7c9aadab2
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/heatmap/HeatMapChart03.java
@@ -0,0 +1,94 @@
+package org.knowm.xchart.demo.charts.heatmap;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import org.knowm.xchart.HeatMapChart;
+import org.knowm.xchart.HeatMapChartBuilder;
+import org.knowm.xchart.HeatMapSeries;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.demo.charts.ExampleChart;
+
+/**
+ * HeatMap Piecewise
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>Set rangeColors 5 color
+ *   <li>Piecewise
+ *   <li>ShowValue
+ *   <li>HeatMapSeries setMin
+ *   <li>HeatMapSeries setMax
+ *   <li>ToolTipsEnabled
+ */
+public class HeatMapChart03 implements ExampleChart<HeatMapChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<HeatMapChart> exampleChart = new HeatMapChart03();
+    HeatMapChart chart = exampleChart.getChart();
+    new SwingWrapper<HeatMapChart>(chart).displayChart();
+  }
+
+  @Override
+  public HeatMapChart getChart() {
+
+    // Create Chart
+    HeatMapChart chart =
+        new HeatMapChartBuilder()
+            .width(1000)
+            .height(600)
+            .title("Sales per employee per weekday")
+            .xAxisTitle("Employee name")
+            .yAxisTitle("Working day")
+            .build();
+
+    chart.getStyler().setPlotContentSize(0.999);
+    chart.getStyler().setLegendFont(new Font(Font.SANS_SERIF, Font.PLAIN, 12));
+    chart.getStyler().setToolTipsEnabled(true);
+    chart.getStyler().setPiecewise(true);
+    chart.getStyler().setShowValue(true);
+
+    Color[] rangeColors = {Color.YELLOW, Color.CYAN, Color.GREEN, Color.BLUE, Color.RED};
+    chart.getStyler().setRangeColors(rangeColors);
+
+    List<String> xData = new ArrayList<String>();
+    xData.add("Tim");
+    xData.add("Tom");
+    xData.add("Lida");
+    xData.add("Mark");
+    xData.add("LiLei");
+    xData.add("Lukas");
+    xData.add("Marie");
+
+    List<String> yData = new ArrayList<String>();
+    yData.add("Monday");
+    yData.add("Tuesday");
+    yData.add("Wedesday");
+    yData.add("Thursday");
+    yData.add("Friday");
+
+    Number[][] data = {
+      {0, 0, 146}, {0, 1, 830}, {0, 2, 120}, {0, 3, 356}, {0, 4, 456}, {1, 0, 756}, {1, 1, 450},
+      {1, 2, 562}, {1, 3, 610}, {1, 4, 258}, {2, 0, 666}, {2, 1, 777}, {2, 2, 555}, {2, 3, 445},
+      {2, 4, 236}, {3, 0, 123}, {3, 1, 456}, {3, 2, 789}, {3, 3, 987}, {3, 4, 654}, {4, 0, 321},
+      {4, 1, 753}, {4, 2, 951}, {4, 3, 159}, {4, 4, 269}, {5, 0, 358}, {5, 1, 820}, {5, 2, 635},
+      {5, 3, 469}, {5, 4, 562}, {6, 0, 632}, {6, 1, 547}, {6, 2, 350}, {6, 3, 269}, {6, 4, 658}
+    };
+
+    HeatMapSeries heatMapSeries =
+        chart.addSeries("Sales per employee", xData, yData, Arrays.asList(data));
+    heatMapSeries.setMin(0);
+    heatMapSeries.setMax(1000);
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - HeatMap Piecewise";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/heatmap/HeatMapChart04.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/heatmap/HeatMapChart04.java
new file mode 100644
index 0000000000000000000000000000000000000000..bc42959047dfaf2622ee56f9aa15ccf63b702094
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/heatmap/HeatMapChart04.java
@@ -0,0 +1,201 @@
+package org.knowm.xchart.demo.charts.heatmap;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import org.knowm.xchart.HeatMapChart;
+import org.knowm.xchart.HeatMapChartBuilder;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.style.Styler.LegendLayout;
+import org.knowm.xchart.style.Styler.LegendPosition;
+
+/**
+ * HeatMap X-Axis Data Date Type
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>Set rangeColors 8 color
+ *   <li>DatePattern
+ *   <li>ShowValue
+ *   <li>LegendPosition.OutsideS
+ *   <li>LegendLayout.Horizontal
+ *   <li>ToolTipsEnabled
+ */
+public class HeatMapChart04 implements ExampleChart<HeatMapChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<HeatMapChart> exampleChart = new HeatMapChart04();
+    HeatMapChart chart = exampleChart.getChart();
+    new SwingWrapper<>(chart).displayChart();
+  }
+
+  @Override
+  public HeatMapChart getChart() {
+
+    // Create Chart
+    HeatMapChart chart =
+        new HeatMapChartBuilder()
+            .width(1000)
+            .height(600)
+            .title("24-hour temperature in four major cities")
+            .build();
+
+    chart.getStyler().setPlotContentSize(1);
+    chart.getStyler().setLegendFont(new Font(Font.SANS_SERIF, Font.PLAIN, 16));
+    chart.getStyler().setToolTipsEnabled(true);
+    chart.getStyler().setDatePattern("HH");
+    chart.getStyler().setShowValue(true);
+    chart.getStyler().setLegendPosition(LegendPosition.OutsideS);
+    chart.getStyler().setLegendLayout(LegendLayout.Horizontal);
+
+    Color[] rangeColors = {
+      new Color(255, 204, 153),
+      new Color(255, 204, 102),
+      new Color(255, 153, 51),
+      new Color(255, 80, 80),
+      new Color(255, 31, 0),
+      new Color(255, 0, 0),
+      new Color(204, 51, 0),
+      new Color(153, 51, 0)
+    };
+    chart.getStyler().setRangeColors(rangeColors);
+
+    DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+    List<Date> xData = new ArrayList<>();
+    Date startDate = null;
+    try {
+      startDate = df.parse("2020-04-17 00:00:00");
+    } catch (ParseException e) {
+      e.printStackTrace();
+    }
+    Calendar calendar = Calendar.getInstance();
+    calendar.setTime(startDate);
+    for (int i = 0; i < 24; i++) {
+      xData.add(calendar.getTime());
+      calendar.add(Calendar.HOUR, 1);
+    }
+    List<String> yData = new ArrayList<>();
+    yData.add("New York");
+    yData.add("Beijing");
+    yData.add("Tokyo");
+    yData.add("Paris");
+
+    Number[][] data = {
+      {0, 0, 7},
+      {1, 0, 8},
+      {2, 0, 9},
+      {3, 0, 9},
+      {4, 0, 9},
+      {5, 0, 8},
+      {6, 0, 8},
+      {7, 0, 8},
+      {8, 0, 7},
+      {9, 0, 6},
+      {10, 0, 4},
+      {11, 0, 4},
+      {12, 0, 4},
+      {13, 0, 4},
+      {14, 0, 3},
+      {15, 0, 3},
+      {16, 0, 3},
+      {17, 0, 2},
+      {18, 0, 2},
+      {19, 0, 2},
+      {20, 0, 3},
+      {21, 0, 5},
+      {22, 0, 6},
+      {23, 0, 7},
+      {0, 1, 12},
+      {1, 1, 11},
+      {2, 1, 11},
+      {3, 1, 11},
+      {4, 1, 11},
+      {5, 1, 12},
+      {6, 1, 13},
+      {7, 1, 15},
+      {8, 1, 16},
+      {9, 1, 18},
+      {10, 1, 18},
+      {11, 1, 19},
+      {12, 1, 20},
+      {13, 1, 21},
+      {14, 1, 21},
+      {15, 1, 21},
+      {16, 1, 21},
+      {17, 1, 19},
+      {18, 1, 17},
+      {19, 1, 13},
+      {20, 1, 13},
+      {21, 1, 12},
+      {22, 1, 12},
+      {23, 1, 11},
+      {0, 2, 10},
+      {1, 2, 10},
+      {2, 2, 10},
+      {3, 2, 10},
+      {4, 2, 10},
+      {5, 2, 10},
+      {6, 2, 10},
+      {7, 2, 11},
+      {8, 2, 12},
+      {9, 2, 12},
+      {10, 2, 13},
+      {11, 2, 14},
+      {12, 2, 14},
+      {13, 2, 14},
+      {14, 2, 14},
+      {15, 2, 14},
+      {16, 2, 14},
+      {17, 2, 14},
+      {18, 2, 14},
+      {19, 2, 14},
+      {20, 2, 14},
+      {21, 2, 14},
+      {22, 2, 14},
+      {23, 2, 14},
+      {0, 3, 8},
+      {1, 3, 7},
+      {2, 3, 5},
+      {3, 3, 5},
+      {4, 3, 4},
+      {5, 3, 3},
+      {6, 3, 3},
+      {7, 3, 3},
+      {8, 3, 4},
+      {9, 3, 6},
+      {10, 3, 8},
+      {11, 3, 12},
+      {12, 3, 14},
+      {13, 3, 14},
+      {14, 3, 15},
+      {15, 3, 15},
+      {16, 3, 15},
+      {17, 3, 15},
+      {18, 3, 15},
+      {19, 3, 14},
+      {20, 3, 11},
+      {21, 3, 10},
+      {22, 3, 8},
+      {23, 3, 6}
+    };
+
+    chart.addSeries("heatMap", xData, yData, Arrays.asList(data));
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - HeatMap X-Axis Data Date Type";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/heatmap/HeatMapChart05.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/heatmap/HeatMapChart05.java
new file mode 100644
index 0000000000000000000000000000000000000000..8849e0583c41fdb4413af1ca589c8fe09a47f77f
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/heatmap/HeatMapChart05.java
@@ -0,0 +1,100 @@
+package org.knowm.xchart.demo.charts.heatmap;
+
+import java.awt.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import org.knowm.xchart.HeatMapChart;
+import org.knowm.xchart.HeatMapChartBuilder;
+import org.knowm.xchart.HeatMapSeries;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.demo.charts.ExampleChart;
+
+/**
+ * HeatMap Piecewise
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>Set rangeColors 5 color
+ *   <li>Piecewise
+ *   <li>ShowValue
+ *   <li>HeatMapSeries setMin
+ *   <li>HeatMapSeries setMax
+ *   <li>ToolTipsEnabled
+ *   <li>Chain together styling methods
+ */
+public class HeatMapChart05 implements ExampleChart<HeatMapChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<HeatMapChart> exampleChart = new HeatMapChart05();
+    HeatMapChart chart = exampleChart.getChart();
+    new SwingWrapper<>(chart).displayChart();
+  }
+
+  @Override
+  public HeatMapChart getChart() {
+
+    // Create Chart
+    HeatMapChart chart =
+        new HeatMapChartBuilder()
+            .width(1000)
+            .height(600)
+            .title("Sales per employee per weekday")
+            .xAxisTitle("Employee name")
+            .yAxisTitle("Working day")
+            .build();
+
+    chart
+        .getStyler()
+        .setPlotContentSize(0.999)
+        .setLegendFont(new Font(Font.SANS_SERIF, Font.PLAIN, 12))
+        .setToolTipsEnabled(true);
+    chart
+        .getStyler()
+        .setPiecewise(true)
+        .setPiecewiseRanged(false)
+        .setShowValue(true)
+        .setHeatMapDecimalValueFormatter(x -> "\u2265 " + x);
+
+    Color[] rangeColors = {Color.YELLOW, Color.CYAN, Color.GREEN, Color.BLUE, Color.RED};
+    chart.getStyler().setRangeColors(rangeColors);
+
+    List<String> xData = new ArrayList<>();
+    xData.add("Tim");
+    xData.add("Tom");
+    xData.add("Lida");
+    xData.add("Mark");
+    xData.add("LiLei");
+    xData.add("Lukas");
+    xData.add("Marie");
+
+    List<String> yData = new ArrayList<>();
+    yData.add("Monday");
+    yData.add("Tuesday");
+    yData.add("Wednesday");
+    yData.add("Thursday");
+    yData.add("Friday");
+
+    Number[][] data = {
+      {0, 0, 146}, {0, 1, 830}, {0, 2, 120}, {0, 3, 356}, {0, 4, 456}, {1, 0, 756}, {1, 1, 450},
+      {1, 2, 562}, {1, 3, 610}, {1, 4, 258}, {2, 0, 666}, {2, 1, 777}, {2, 2, 555}, {2, 3, 445},
+      {2, 4, 236}, {3, 0, 123}, {3, 1, 456}, {3, 2, 789}, {3, 3, 987}, {3, 4, 654}, {4, 0, 321},
+      {4, 1, 753}, {4, 2, 951}, {4, 3, 159}, {4, 4, 269}, {5, 0, 358}, {5, 1, 820}, {5, 2, 635},
+      {5, 3, 469}, {5, 4, 562}, {6, 0, 632}, {6, 1, 547}, {6, 2, 350}, {6, 3, 269}, {6, 4, 658}
+    };
+
+    HeatMapSeries heatMapSeries =
+        chart.addSeries("Sales per employee", xData, yData, Arrays.asList(data));
+    heatMapSeries.setMin(0);
+    heatMapSeries.setMax(1000);
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - HeatMap Piecewise (custom legend formatting)";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/line/LineChart01.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/line/LineChart01.java
new file mode 100644
index 0000000000000000000000000000000000000000..b4eab9f41249a0de339973b477e30dfade2ba891
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/line/LineChart01.java
@@ -0,0 +1,72 @@
+package org.knowm.xchart.demo.charts.line;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYChartBuilder;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.style.Styler.LegendPosition;
+
+/**
+ * Logarithmic Y-Axis
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>Logarithmic Y-Axis
+ *   <li>Building a Chart with ChartBuilder
+ *   <li>Place legend at Inside-NW position
+ */
+public class LineChart01 implements ExampleChart<XYChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<XYChart> exampleChart = new LineChart01();
+    XYChart chart = exampleChart.getChart();
+    new SwingWrapper<>(chart).displayChart();
+  }
+
+  @Override
+  public XYChart getChart() {
+
+    // generates Log data
+    List<Integer> xData = new ArrayList<>();
+    List<Double> yData = new ArrayList<>();
+    for (int i = -3; i <= 3; i++) {
+      xData.add(i);
+      yData.add(Math.pow(10, i));
+    }
+
+    // Create Chart
+    XYChart chart =
+        new XYChartBuilder()
+            .width(800)
+            .height(600)
+            .title(getClass().getSimpleName())
+            .xAxisTitle("Power")
+            .yAxisTitle("Value")
+            .build();
+
+    // Customize Chart
+    chart.getStyler().setChartTitleVisible(true);
+    chart.getStyler().setLegendPosition(LegendPosition.InsideNW);
+    chart.getStyler().setYAxisLogarithmic(true);
+    chart.getStyler().setXAxisLabelRotation(45);
+
+    // chart.getStyler().setXAxisLabelAlignment(TextAlignment.Right);
+    // chart.getStyler().setXAxisLabelRotation(90);
+    // chart.getStyler().setXAxisLabelRotation(0);
+
+    // Series
+    chart.addSeries("10^x", xData, yData);
+
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " -  Logarithmic Y-Axis";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/line/LineChart02.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/line/LineChart02.java
new file mode 100644
index 0000000000000000000000000000000000000000..8a661348cc2614a2777179a341a2e8bbf28d623f
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/line/LineChart02.java
@@ -0,0 +1,81 @@
+package org.knowm.xchart.demo.charts.line;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYChartBuilder;
+import org.knowm.xchart.XYSeries;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.style.colors.XChartSeriesColors;
+import org.knowm.xchart.style.lines.SeriesLines;
+import org.knowm.xchart.style.markers.SeriesMarkers;
+
+/**
+ * Sine wave with customized series style
+ *
+ * <p>* Demonstrates the following:
+ *
+ * <ul>
+ *   <li>Customizing the series style properties
+ */
+public class LineChart02 implements ExampleChart<XYChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<XYChart> exampleChart = new LineChart02();
+    XYChart chart = exampleChart.getChart();
+    new SwingWrapper<>(chart).displayChart();
+  }
+
+  @Override
+  public XYChart getChart() {
+
+    // Create Chart
+    XYChart chart =
+        new XYChartBuilder().width(800).height(600).title(getClass().getSimpleName()).build();
+
+    // Customize Chart
+    chart.getStyler().setChartTitleVisible(false);
+    chart.getStyler().setLegendVisible(false);
+
+    // generates sine data
+    int size = 30;
+    List<Integer> xData = new ArrayList<>();
+    List<Double> yData = new ArrayList<>();
+    for (int i = 0; i <= size; i++) {
+      double radians = (Math.PI / (size / 2) * i);
+      xData.add(i - size / 2);
+      yData.add(-.000001 * Math.sin(radians));
+    }
+
+    // generates cos data
+    List<Integer> xData2 = new ArrayList<>();
+    List<Double> yData2 = new ArrayList<>();
+    for (int i = 0; i <= size; i++) {
+      double radians = (Math.PI / (size / 2) * i);
+      xData2.add(i - size / 2);
+      yData2.add(-.000001 * Math.cos(radians));
+    }
+
+    // Series
+    XYSeries series = chart.addSeries("y=sin(x)", xData, yData);
+    series.setLineColor(XChartSeriesColors.PURPLE);
+    series.setLineStyle(SeriesLines.DASH_DASH);
+    series.setMarkerColor(XChartSeriesColors.GREEN);
+    series.setMarker(SeriesMarkers.SQUARE);
+
+    series = chart.addSeries("y=cos(x)", xData2, yData2);
+    series.setLineColor(XChartSeriesColors.PINK);
+    series.setLineWidth(5);
+    series.setMarker(SeriesMarkers.NONE);
+
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - Customized Series Style";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/line/LineChart03.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/line/LineChart03.java
new file mode 100644
index 0000000000000000000000000000000000000000..4170536e51b5368a294698925e5c63e78efdc9d3
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/line/LineChart03.java
@@ -0,0 +1,112 @@
+package org.knowm.xchart.demo.charts.line;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYChartBuilder;
+import org.knowm.xchart.XYSeries;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.style.Styler.LegendPosition;
+import org.knowm.xchart.style.colors.ChartColor;
+import org.knowm.xchart.style.colors.XChartSeriesColors;
+import org.knowm.xchart.style.lines.SeriesLines;
+import org.knowm.xchart.style.markers.SeriesMarkers;
+
+/**
+ * Customized Chart
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>Extensive Chart Customization
+ */
+public class LineChart03 implements ExampleChart<XYChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<XYChart> exampleChart = new LineChart03();
+    XYChart chart = exampleChart.getChart();
+    new SwingWrapper<XYChart>(chart).displayChart();
+  }
+
+  @Override
+  public XYChart getChart() {
+
+    // Create Chart
+    XYChart chart =
+        new XYChartBuilder()
+            .width(800)
+            .height(600)
+            .title(getClass().getSimpleName())
+            .xAxisTitle("X")
+            .yAxisTitle("Y")
+            .build();
+
+    // Customize Chart
+    chart.getStyler().setPlotBackgroundColor(ChartColor.GREY.getColor());
+    chart.getStyler().setPlotGridLinesColor(new Color(255, 255, 255));
+    chart.getStyler().setChartBackgroundColor(Color.WHITE);
+    chart.getStyler().setLegendBackgroundColor(Color.PINK);
+    chart.getStyler().setChartFontColor(Color.MAGENTA);
+    chart.getStyler().setChartTitleBoxBackgroundColor(new Color(0, 222, 0));
+    chart.getStyler().setChartTitleBoxVisible(true);
+    chart.getStyler().setChartTitleBoxBorderColor(Color.BLACK);
+    chart.getStyler().setPlotGridLinesVisible(false);
+
+    chart.getStyler().setAxisTickPadding(20);
+
+    chart.getStyler().setAxisTickMarkLength(15);
+
+    chart.getStyler().setPlotMargin(20);
+
+    chart.getStyler().setChartTitleFont(new Font(Font.MONOSPACED, Font.BOLD, 24));
+    chart.getStyler().setLegendFont(new Font(Font.SERIF, Font.PLAIN, 18));
+    chart.getStyler().setLegendPosition(LegendPosition.InsideSE);
+    chart.getStyler().setLegendSeriesLineLength(12);
+    chart.getStyler().setAxisTitleFont(new Font(Font.SANS_SERIF, Font.ITALIC, 18));
+    chart.getStyler().setAxisTickLabelsFont(new Font(Font.SERIF, Font.PLAIN, 11));
+    chart.getStyler().setDatePattern("dd-MMM");
+    chart.getStyler().setDecimalPattern("#0.000");
+    chart.getStyler().setLocale(Locale.GERMAN);
+
+    // generates linear data
+    List<Date> xData = new ArrayList<Date>();
+    List<Double> yData = new ArrayList<Double>();
+
+    DateFormat sdf = new SimpleDateFormat("dd.MM.yyyy");
+    Date date = null;
+    for (int i = 1; i <= 10; i++) {
+
+      try {
+        date = sdf.parse(i + ".10.2008");
+      } catch (ParseException e) {
+        e.printStackTrace();
+      }
+      xData.add(date);
+      yData.add(Math.random() * i);
+    }
+
+    // Series
+    XYSeries series = chart.addSeries("Fake Data", xData, yData);
+    series.setLineColor(XChartSeriesColors.BLUE);
+    series.setMarkerColor(Color.ORANGE);
+    series.setMarker(SeriesMarkers.CIRCLE);
+    series.setLineStyle(SeriesLines.SOLID);
+
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - Extensive Chart Customization";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/line/LineChart04.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/line/LineChart04.java
new file mode 100644
index 0000000000000000000000000000000000000000..263c0a09a081961057c03c26d35de26c24877d13
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/line/LineChart04.java
@@ -0,0 +1,59 @@
+package org.knowm.xchart.demo.charts.line;
+
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYChartBuilder;
+import org.knowm.xchart.XYSeries;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.style.colors.XChartSeriesColors;
+import org.knowm.xchart.style.lines.SeriesLines;
+import org.knowm.xchart.style.markers.SeriesMarkers;
+
+/** Hundreds of Series on One Plot */
+public class LineChart04 implements ExampleChart<XYChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<XYChart> exampleChart = new LineChart04();
+    XYChart chart = exampleChart.getChart();
+    new SwingWrapper<XYChart>(chart).displayChart();
+  }
+
+  @Override
+  public XYChart getChart() {
+
+    // Create Chart
+    XYChart chart =
+        new XYChartBuilder()
+            .width(800)
+            .height(600)
+            .title(getClass().getSimpleName())
+            .xAxisTitle("X")
+            .yAxisTitle("Y")
+            .build();
+
+    // Customize Chart
+    chart.getStyler().setLegendVisible(false);
+
+    // Series
+    for (int i = 0; i < 200; i++) {
+      XYSeries series =
+          chart.addSeries(
+              "A" + i,
+              new double[] {Math.random() / 1000, Math.random() / 1000},
+              new double[] {Math.random() / -1000, Math.random() / -1000});
+      series.setLineColor(XChartSeriesColors.BLUE);
+      series.setLineStyle(SeriesLines.SOLID);
+      series.setMarker(SeriesMarkers.CIRCLE);
+      series.setMarkerColor(XChartSeriesColors.BLUE);
+    }
+
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - Hundreds of Series on One Plot";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/line/LineChart05.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/line/LineChart05.java
new file mode 100644
index 0000000000000000000000000000000000000000..6d5a6f5c507f9bc39cc4544ac6d7a191e905edb9
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/line/LineChart05.java
@@ -0,0 +1,84 @@
+package org.knowm.xchart.demo.charts.line;
+
+import java.awt.Color;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYChartBuilder;
+import org.knowm.xchart.XYSeries;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.style.Styler.LegendPosition;
+import org.knowm.xchart.style.lines.SeriesLines;
+import org.knowm.xchart.style.markers.SeriesMarkers;
+
+/**
+ * Scatter and Line
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>Customizing the series style properties
+ *   <li>Scatter and Line overlay
+ *   <li>Logarithmic Y-Axis
+ *   <li>An X-Axis min value clipping off the series
+ */
+public class LineChart05 implements ExampleChart<XYChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<XYChart> exampleChart = new LineChart05();
+    XYChart chart = exampleChart.getChart();
+    new SwingWrapper<>(chart).displayChart();
+  }
+
+  @Override
+  public XYChart getChart() {
+
+    // Create Chart
+    XYChart chart =
+        new XYChartBuilder()
+            .width(800)
+            .height(600)
+            .title(getClass().getSimpleName())
+            .xAxisTitle("X")
+            .yAxisTitle("Y")
+            .build();
+
+    // Customize Chart
+    chart.getStyler().setLegendPosition(LegendPosition.InsideSW);
+    chart.getStyler().setYAxisLogarithmic(true);
+    chart.getStyler().setYAxisMin(0.01);
+    chart.getStyler().setYAxisMax(1000.0);
+    chart.getStyler().setXAxisMin(2.0);
+    chart.getStyler().setXAxisMax(7.0);
+    chart.getStyler().setToolTipsEnabled(true);
+    //    chart.getStyler().setToolTipsAlwaysVisible(true);
+    //    chart.getStyler().setToolTipFont(new Font("Verdana", Font.BOLD, 12));
+    //    chart.getStyler().setToolTipHighlightColor(Color.CYAN);
+    //    chart.getStyler().setToolTipBorderColor(Color.BLACK);
+    //    chart.getStyler().setToolTipBackgroundColor(Color.LIGHT_GRAY);
+    //    chart.getStyler().setToolTipType(Styler.ToolTipType.xAndYLabels);
+
+    // Series
+    double[] xData = new double[] {0.0, 1.0, 2.0, 3.0, 4.0, 5, 6};
+    double[] yData = new double[] {106, 44, 26, 10, 7.5, 3.4, .88};
+    double[] yData2 = new double[] {102, 49, 23.6, 11.3, 5.4, 2.6, 1.25};
+
+    XYSeries series = chart.addSeries("A", xData, yData);
+    series.setLineStyle(SeriesLines.NONE);
+    series.setMarker(SeriesMarkers.DIAMOND);
+    series.setMarkerColor(Color.BLACK);
+
+    XYSeries series2 = chart.addSeries("B", xData, yData2);
+    series2.setMarker(SeriesMarkers.NONE);
+    series2.setLineStyle(SeriesLines.DASH_DASH);
+    series2.setLineColor(Color.BLACK);
+
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - Scatter and Line with Tool Tips";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/line/LineChart06.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/line/LineChart06.java
new file mode 100644
index 0000000000000000000000000000000000000000..e25fc447f3f09f19a2eb307b025e5e30c495174d
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/line/LineChart06.java
@@ -0,0 +1,62 @@
+package org.knowm.xchart.demo.charts.line;
+
+import java.awt.Color;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYChartBuilder;
+import org.knowm.xchart.XYSeries;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.style.lines.SeriesLines;
+import org.knowm.xchart.style.markers.SeriesMarkers;
+
+/**
+ * Logarithmic Y-Axis with Error Bars
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>Error Bars
+ *   <li>Logarithmic Y-Axis
+ *   <li>Setting min and max values for Y-Axis
+ *   <li>Multi-line series labels in legend
+ */
+public class LineChart06 implements ExampleChart<XYChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<XYChart> exampleChart = new LineChart06();
+    XYChart chart = exampleChart.getChart();
+    new SwingWrapper<>(chart).displayChart();
+  }
+
+  @Override
+  public XYChart getChart() {
+
+    // Create Chart
+    XYChart chart =
+        new XYChartBuilder().width(800).height(600).title(getClass().getSimpleName()).build();
+
+    // Customize Chart
+    chart.getStyler().setYAxisLogarithmic(true);
+    chart.getStyler().setYAxisMin(.08);
+    chart.getStyler().setYAxisMax(1000.0);
+    chart.getStyler().setErrorBarsColor(Color.black);
+
+    // Series
+    int[] xData = new int[] {0, 1, 2, 3, 4, 5, 6};
+    int[] yData1 = new int[] {100, 100, 100, 60, 10, 10, 10};
+    int[] errdata = new int[] {50, 20, 10, 52, 9, 2, 1};
+    XYSeries series1 = chart.addSeries("Error bar\ntest data", xData, yData1, errdata);
+    series1.setLineStyle(SeriesLines.SOLID);
+    series1.setMarker(SeriesMarkers.DIAMOND);
+    series1.setMarkerColor(Color.GREEN);
+
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - Logarithmic Y-Axis with Error Bars";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/line/LineChart07.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/line/LineChart07.java
new file mode 100644
index 0000000000000000000000000000000000000000..691944533d029f23760e1cc099d2566ed9a22de6
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/line/LineChart07.java
@@ -0,0 +1,195 @@
+package org.knowm.xchart.demo.charts.line;
+
+import java.util.Arrays;
+import java.util.List;
+import org.knowm.xchart.CategoryChart;
+import org.knowm.xchart.CategoryChartBuilder;
+import org.knowm.xchart.CategorySeries;
+import org.knowm.xchart.CategorySeries.CategorySeriesRenderStyle;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.style.Styler.ChartTheme;
+import org.knowm.xchart.style.Styler.LegendPosition;
+
+/**
+ * Category Chart with Line Rendering
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>A Line Chart created from multiple category series types
+ *   <li>GGPlot2 Theme
+ *   <li>disabling some series shown in legend
+ */
+public class LineChart07 implements ExampleChart<CategoryChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<CategoryChart> exampleChart = new LineChart07();
+    CategoryChart chart = exampleChart.getChart();
+    new SwingWrapper<>(chart).displayChart();
+  }
+
+  @Override
+  public CategoryChart getChart() {
+
+    // Create Chart
+    CategoryChart chart =
+        new CategoryChartBuilder()
+            .width(800)
+            .height(600)
+            .theme(ChartTheme.GGPlot2)
+            .title(getClass().getSimpleName())
+            .xAxisTitle("Threads")
+            .yAxisTitle("Executions")
+            .build();
+
+    // Customize Chart
+    chart.getStyler().setDefaultSeriesRenderStyle(CategorySeriesRenderStyle.Line);
+    chart.getStyler().setXAxisLabelRotation(270);
+    chart.getStyler().setLegendPosition(LegendPosition.OutsideE);
+    chart.getStyler().setAvailableSpaceFill(0);
+    chart.getStyler().setOverlapped(true);
+
+    // Declare data
+    List<String> xAxisKeys =
+        Arrays.asList(
+            "release-0.5",
+            "release-0.6",
+            "release-0.7",
+            "release-0.8",
+            "release-0.9",
+            "release-1.0.0",
+            "release-1.1.0",
+            "release-1.2.0",
+            "release-1.3.0",
+            "release-2.0.0",
+            "release-2.1.0",
+            "release-2.2.0",
+            "release-2.3.0",
+            "release-2.4.0",
+            "release-2.5.0",
+            "release-2.6.0",
+            "release-3.0.0",
+            "release-3.1.0",
+            "release-3.2.0",
+            "release-3.3.0",
+            "release-3.4.0",
+            "release-3.5.0",
+            "release-3.6.0",
+            "release-3.7.0",
+            "release-3.8.0",
+            "release-4.0.0",
+            "release-4.1.0",
+            "release-4.2.0",
+            "release-4.3.0",
+            "release-4.4.0",
+            "release-4.4.1",
+            "release-4.4.2");
+    String[] seriesNames =
+        new String[] {
+          "Threads:4",
+          "Threads:10",
+          "Threads:20",
+          "Threads:50",
+          "Threads:100",
+          "Threads:150",
+          "Threads:200",
+          "Threads:250",
+          "Threads:500",
+          "Threads:750",
+          "Threads:1000",
+          "Threads:1500",
+          "Threads:2000",
+          "Threads:2500"
+        };
+    Integer[][] dataPerSeries =
+        new Integer[][] {
+          {
+            117355, 117594, 117551, 117719, 116553, 117304, 118945, 119067, 117803, 118080, 117676,
+            118599, 118224, 119263, 119455, 119393, 117961, 119254, 118447, 119428, 118812, 117947,
+            119405, 119329, 117749, 119331, 119354, 119519, 118494, 119780, 119766, 119742
+          },
+          {
+            127914, 128835, 128953, 128893, 128830, 129012, 129235, 129424, 129400, 129477, 129065,
+            129103, 129150, 129434, 129000, 129467, 128994, 129167, 129849, 128702, 134439, 134221,
+            134277, 134393, 134390, 134581, 134263, 134641, 134672, 137880, 137675, 137943
+          },
+          {
+            133396, 133977, 133992, 133656, 134406, 134657, 135194, 135497, 134881, 134873, 135065,
+            135045, 134480, 135004, 135111, 134720, 134639, 135505, 135831, 135974, 140965, 140759,
+            140545, 139959, 141063, 141339, 140967, 140927, 141972, 160884, 163402, 164572
+          },
+          {
+            122376, 122236, 122861, 122806, 122775, 122619, 122505, 122585, 122742, 122847, 122660,
+            122705, 122852, 122847, 122909, 122788, 122861, 123396, 123430, 122847, 121103, 121013,
+            120936, 120901, 121096, 120931, 121160, 121112, 121145, 175077, 174483, 175787
+          },
+          {
+            120048, 120226, 120745, 120669, 120647, 120683, 120499, 120533, 120628, 121059, 120901,
+            120838, 120845, 120954, 120963, 121055, 120948, 121111, 121239, 121094, 121422, 121249,
+            120924, 120918, 121061, 121063, 121065, 121098, 121011, 173280, 173179, 172193
+          },
+          {
+            119712, 119766, 120053, 120217, 119954, 120080, 120167, 119898, 120065, 120253, 120153,
+            120103, 120070, 120446, 120347, 120223, 120261, 120629, 120576, 120541, 121405, 121481,
+            121461, 121387, 121295, 121597, 121592, 121593, 121576, 171415, 170628, 169878
+          },
+          {
+            119807, 120232, 119745, 119892, 120024, 119854, 119818, 119908, 119685, 119816, 119848,
+            119919, 119627, 119906, 120242, 119974, 120116, 120472, 120304, 120294, 121308, 121338,
+            121278, 121292, 121418, 121570, 121564, 121541, 121571, 170597, 170346, 170434
+          },
+          {
+            121283, 121580, 120720, 120553, 121146, 120016, 119994, 120194, 120149, 120239, 120238,
+            120031, 120016, 120314, 120023, 120408, 120315, 120711, 121046, 120850, 121192, 121315,
+            121198, 121224, 121396, 121398, 121636, 121412, 121252, 168489, 169774, 168750
+          },
+          {
+            121219, 121594, 122576, 122368, 122874, 121831, 121386, 121433, 121722, 121600, 121158,
+            121653, 121306, 121652, 121982, 121775, 121819, 122243, 122128, 122067, 125185, 124972,
+            125023, 125004, 125120, 125320, 125395, 125134, 124838, 168492, 167673, 167087
+          },
+          {
+            121576, 122197, 121660, 121673, 122047, 120863, 120715, 120542, 120934, 120936, 120448,
+            120823, 120546, 121150, 120863, 120946, 120865, 121273, 120848, 121210, 124867, 124927,
+            124863, 124610, 124633, 124881, 124887, 124626, 124814, 167504, 167717, 165026
+          },
+          {
+            121822, 121540, 121488, 122055, 121253, 120728, 120626, 120474, 119848, 120129, 120082,
+            120075, 120429, 120859, 121228, 120390, 120161, 121465, 121085, 120682, 124287, 124029,
+            124162, 124185, 124024, 124416, 124558, 124206, 124109, 166816, 167583, 164828
+          },
+          {
+            121094, 121594, 121273, 121495, 121638, 120419, 119611, 119406, 119381, 120053, 119591,
+            120080, 120071, 119709, 120008, 120469, 119417, 120327, 120510, 119873, 123192, 123085,
+            123388, 123298, 123260, 122982, 123465, 123267, 122856, 164366, 163919, 166612
+          },
+          {
+            120639, 120628, 121443, 121160, 121245, 119819, 119865, 119300, 119466, 119478, 119870,
+            119720, 119671, 120333, 119718, 119528, 119581, 120716, 120624, 119585, 121685, 121978,
+            123017, 121433, 122190, 122330, 122458, 122090, 122234, 161976, 163628, 158023
+          },
+          {
+            120242, 120674, 120091, 120299, 120662, 119885, 119480, 119269, 118983, 119290, 119304,
+            119161, 119875, 118830, 119517, 119980, 119502, 120883, 118953, 119461, 120753, 120526,
+            120967, 120244, 122381, 121084, 122404, 121761, 121546, 161230, 160123, 160534
+          }
+        };
+
+    // Series
+    for (int i = 0; i < seriesNames.length; i++) {
+      CategorySeries series =
+          chart.addSeries(seriesNames[i], xAxisKeys, Arrays.asList(dataPerSeries[i]));
+      series.setShowInLegend(i % 2 == 0);
+    }
+
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - Category Chart with Line Rendering";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/line/LineChart08.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/line/LineChart08.java
new file mode 100644
index 0000000000000000000000000000000000000000..8270099f82fa84f37d71a7e859e7616abc7cf7ab
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/line/LineChart08.java
@@ -0,0 +1,75 @@
+package org.knowm.xchart.demo.charts.line;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYChartBuilder;
+import org.knowm.xchart.XYSeries;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.style.Styler.LegendPosition;
+
+/**
+ * Logarithmic Y-Axis
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>Step renderer
+ *   <li>Logarithmic Y-Axis
+ *   <li>Building a Chart with ChartBuilder
+ *   <li>Place legend at Inside-NW position
+ */
+public class LineChart08 implements ExampleChart<XYChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<XYChart> exampleChart = new LineChart08();
+    XYChart chart = exampleChart.getChart();
+    new SwingWrapper<>(chart).displayChart();
+  }
+
+  @Override
+  public XYChart getChart() {
+
+    // generates Log data
+    List<Integer> xData = new ArrayList<>();
+    List<Double> yData = new ArrayList<>();
+    for (int i = -3; i <= 3; i++) {
+      xData.add(i);
+      yData.add(Math.pow(10, i));
+    }
+
+    // Create Chart
+    XYChart chart =
+        new XYChartBuilder()
+            .width(800)
+            .height(600)
+            .title(getClass().getSimpleName())
+            .xAxisTitle("Power")
+            .yAxisTitle("Value")
+            .build();
+
+    // Customize Chart
+    chart.getStyler().setChartTitleVisible(true);
+    chart.getStyler().setLegendPosition(LegendPosition.InsideNW);
+    chart.getStyler().setYAxisLogarithmic(true);
+    chart.getStyler().setXAxisLabelRotation(45);
+    chart.getStyler().setDefaultSeriesRenderStyle(XYSeries.XYSeriesRenderStyle.Step);
+
+    // chart.getStyler().setXAxisLabelAlignment(TextAlignment.Right);
+    // chart.getStyler().setXAxisLabelRotation(90);
+    // chart.getStyler().setXAxisLabelRotation(0);
+
+    // Series
+    XYSeries series = chart.addSeries("10^x", xData, yData);
+
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - Step Rendering";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/line/LineChart09.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/line/LineChart09.java
new file mode 100644
index 0000000000000000000000000000000000000000..5e41aee8631a210ad2f256a6a0ce741bf9b7c7ba
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/line/LineChart09.java
@@ -0,0 +1,72 @@
+package org.knowm.xchart.demo.charts.line;
+
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYChartBuilder;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.style.Styler;
+import org.knowm.xchart.style.Styler.LegendPosition;
+
+/**
+ * Cursor
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>Cursor
+ *   <li>Setting custom cursor tool tip text
+ *   <li>Building a Chart with ChartBuilder
+ */
+public class LineChart09 implements ExampleChart<XYChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<XYChart> exampleChart = new LineChart09();
+    XYChart chart = exampleChart.getChart();
+    new SwingWrapper<>(chart).displayChart();
+  }
+
+  @Override
+  public XYChart getChart() {
+
+    // Create Chart
+    XYChart chart =
+        new XYChartBuilder()
+            .width(800)
+            .height(600)
+            .title(getClass().getSimpleName())
+            .xAxisTitle("X")
+            .yAxisTitle("Y")
+            .build();
+
+    // Customize Chart
+    chart.getStyler().setLegendPosition(LegendPosition.OutsideE);
+    chart.getStyler().setAxisTitlesVisible(false);
+    chart.getStyler().setLegendPosition(LegendPosition.OutsideS);
+    chart.getStyler().setLegendLayout(Styler.LegendLayout.Horizontal);
+
+    chart.getStyler().setCursorEnabled(true);
+    //    chart.getStyler().setCursorColor(Color.GREEN);
+    //    chart.getStyler().setCursorLineWidth(30f);
+    //    chart.getStyler().setCursorFont(new Font("Verdana", Font.BOLD, 12));
+    //    chart.getStyler().setCursorFontColor(Color.ORANGE);
+    //    chart.getStyler().setCursorBackgroundColor(Color.BLUE);
+    //    chart.getStyler().setCustomCursorXDataFormattingFunction(x -> "hello xvalue: " + x);
+    //    chart
+    //        .getStyler()
+    //        .setCustomCursorYDataFormattingFunction(y -> "hello yvalue divided by 2: " + y / 2);
+
+    // Series
+    chart.addSeries("a", new double[] {0, 3, 5, 7, 9}, new double[] {-3, 5, 9, 6, 5});
+    chart.addSeries("b", new double[] {0, 2.7, 4.8, 6, 9}, new double[] {-1, 6, 4, 0, 4});
+    chart.addSeries("c", new double[] {0, 1.5, 5, 8, 9}, new double[] {-2, -1, 1, 0, 1});
+
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - Cursor";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/line/LineChart10.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/line/LineChart10.java
new file mode 100644
index 0000000000000000000000000000000000000000..92cf69f2798969fddbbf14f1fec39725ff6afa0e
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/line/LineChart10.java
@@ -0,0 +1,119 @@
+package org.knowm.xchart.demo.charts.line;
+
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import javax.imageio.ImageIO;
+import org.knowm.xchart.AnnotationImage;
+import org.knowm.xchart.AnnotationLine;
+import org.knowm.xchart.AnnotationText;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYChartBuilder;
+import org.knowm.xchart.XYSeries;
+import org.knowm.xchart.XYSeries.XYSeriesRenderStyle;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.style.markers.None;
+
+/**
+ * Annotations
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>Line Annotation
+ *   <li>Text Annotation
+ *   <li>Image Annotation
+ */
+public class LineChart10 implements ExampleChart<XYChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<XYChart> exampleChart = new LineChart10();
+    XYChart chart = exampleChart.getChart();
+    new SwingWrapper<>(chart).displayChart();
+  }
+
+  @Override
+  public XYChart getChart() {
+
+    // Create Chart
+    XYChart chart =
+        new XYChartBuilder()
+            .width(800)
+            .height(600)
+            .title(getClass().getSimpleName())
+            .xAxisTitle("X")
+            .yAxisTitle("Y")
+            .build();
+
+    // Customize Chart
+    chart.getStyler().setAxisTitlesVisible(false);
+    chart.getStyler().setDefaultSeriesRenderStyle(XYSeriesRenderStyle.Line);
+
+    double start = 0;
+    double end = 1;
+    double increment = 0.01;
+
+    List<Double> xData = new ArrayList<>();
+    List<Double> yData = new ArrayList<>();
+
+    double x = start;
+
+    while (x <= end) {
+
+      double y = Math.exp(2 * x - (7 * x * x * x));
+      xData.add(x);
+      yData.add(y);
+      x += increment;
+    }
+
+    // Series
+    XYSeries series = chart.addSeries("series1", xData, yData);
+    series.setMarker(new None());
+
+    // draw a horizontal line at series max point
+    AnnotationLine maxY = new AnnotationLine(series.getYMax(), false, false);
+    chart.addAnnotation(maxY);
+
+    // draw a horizontal line at series min point
+    AnnotationLine minY = new AnnotationLine(series.getYMin(), false, false);
+    chart.addAnnotation(minY);
+
+    // draw a vertical line at 0.45
+    AnnotationLine xLine = new AnnotationLine(0.45, true, false);
+    chart.addAnnotation(xLine);
+
+    // draw a vertical line at 100 pixels
+    AnnotationLine xLinePixel = new AnnotationLine(100, true, true);
+    chart.addAnnotation(xLinePixel);
+
+    //    chart.getStyler().setAnnotationLineColor(Color.GREEN);
+    //    chart.getStyler().setAnnotationLineStroke(new BasicStroke(3.0f));
+
+    // add text near to max line
+    AnnotationText maxText = new AnnotationText("Max", 0.0, series.getYMax(), false);
+    chart.addAnnotation(maxText);
+
+    //    chart.getStyler().setAnnotationTextFont(new Font(Font.MONOSPACED, Font.ITALIC, 8));
+    //    chart.getStyler().setAnnotationTextFontColor(Color.BLUE);
+
+    BufferedImage image = null;
+    try {
+      image = ImageIO.read(getClass().getResource("/XChart_64_64.png"));
+    } catch (IOException e) {
+      e.printStackTrace();
+    }
+    AnnotationImage annotationImage = new AnnotationImage(image, 0, 1, false);
+    chart.addAnnotation(annotationImage);
+
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - Annotations";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/ohlc/OHLCChart01.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/ohlc/OHLCChart01.java
new file mode 100644
index 0000000000000000000000000000000000000000..a90bdf8e4be7d029b8c87302bf54d487e544d21e
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/ohlc/OHLCChart01.java
@@ -0,0 +1,122 @@
+package org.knowm.xchart.demo.charts.ohlc;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import org.knowm.xchart.*;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.style.Styler;
+
+/**
+ * Demonstrates the following:
+ *
+ * <ul>
+ *   <li>Tooltips
+ *   <li>LegendPosition.OutsideS *
+ *   <li>default OHLCSeriesRenderStyle.Candle
+ */
+public class OHLCChart01 implements ExampleChart<OHLCChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<OHLCChart> exampleChart = new OHLCChart01();
+    OHLCChart chart = exampleChart.getChart();
+    new SwingWrapper<>(chart).displayChart();
+  }
+
+  public static void populateData(
+      List<Date> xData,
+      List<Double> openData,
+      List<Double> highData,
+      List<Double> lowData,
+      List<Double> closeData) {
+    // generate data
+    DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+    try {
+      Date date = sdf.parse("2017-01-01");
+      populateData(date, 5000.0, 20, xData, openData, highData, lowData, closeData);
+    } catch (ParseException e) {
+      e.printStackTrace();
+    }
+  }
+
+  // TODO move this? It's shared by all the OHLC classes
+  public static void populateData(
+      Date startDate,
+      double startPrice,
+      int count,
+      List<Date> xData,
+      List<Double> openData,
+      List<Double> highData,
+      List<Double> lowData,
+      List<Double> closeData) {
+    Calendar cal = Calendar.getInstance();
+    cal.setTime(startDate);
+    double data = startPrice;
+    for (int i = 1; i <= count; i++) {
+
+      // add 1 day
+      // startDate = new Date(startDate.getTime() + (1 * 1000 * 60 * 60 * 24));
+      // xData.add(startDate);
+      cal.add(Calendar.DATE, 1);
+      xData.add(cal.getTime());
+
+      double previous = data;
+
+      data = getNewClose(data, startPrice);
+
+      openData.add(previous);
+
+      highData.add(getHigh(Math.max(previous, data), startPrice));
+      lowData.add(getLow(Math.min(previous, data), startPrice));
+
+      closeData.add(data);
+    }
+  }
+
+  private static double getHigh(double close, double orig) {
+    return close + (orig * Math.random() * 0.02);
+  }
+
+  private static double getLow(double close, double orig) {
+    return close - (orig * Math.random() * 0.02);
+  }
+
+  private static double getNewClose(double close, double orig) {
+    return close + (orig * (Math.random() - 0.5) * 0.1);
+  }
+
+  @Override
+  public OHLCChart getChart() {
+
+    // Create Chart
+    OHLCChart chart = new OHLCChartBuilder().width(800).height(600).title("OHLCChart01").build();
+
+    // Customize Chart
+    chart.getStyler().setLegendPosition(Styler.LegendPosition.OutsideS);
+    chart.getStyler().setLegendLayout(Styler.LegendLayout.Horizontal);
+
+    List<Date> xData = new ArrayList<>();
+    List<Double> openData = new ArrayList<>();
+    List<Double> highData = new ArrayList<>();
+    List<Double> lowData = new ArrayList<>();
+    List<Double> closeData = new ArrayList<>();
+
+    populateData(xData, openData, highData, lowData, closeData);
+
+    xData = null;
+    chart.addSeries("Series", xData, openData, highData, lowData, closeData);
+    chart.getStyler().setToolTipsEnabled(true);
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - HiLo rendering";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/ohlc/OHLCChart02.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/ohlc/OHLCChart02.java
new file mode 100644
index 0000000000000000000000000000000000000000..4dcb0832bbbd1be89ad5fa0a12aaf09e5c407e0c
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/ohlc/OHLCChart02.java
@@ -0,0 +1,65 @@
+package org.knowm.xchart.demo.charts.ohlc;
+
+import java.awt.*;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import org.knowm.xchart.OHLCChart;
+import org.knowm.xchart.OHLCChartBuilder;
+import org.knowm.xchart.OHLCSeries;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.style.Styler;
+
+/**
+ * Demonstrates the following:
+ *
+ * <ul>
+ *   <li>Tooltips
+ *   <li>Candle render style green down, red up
+ *   <li>LegendPosition.OutsideS
+ *   <li>OHLCSeriesRenderStyle.HiLo
+ */
+public class OHLCChart02 implements ExampleChart<OHLCChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<OHLCChart> exampleChart = new OHLCChart02();
+    OHLCChart chart = exampleChart.getChart();
+    new SwingWrapper<>(chart).displayChart();
+  }
+
+  @Override
+  public OHLCChart getChart() {
+
+    // Create Chart
+    OHLCChart chart = new OHLCChartBuilder().width(800).height(600).title("OHLCChart02").build();
+
+    // Customize Chart
+    chart.getStyler().setLegendPosition(Styler.LegendPosition.OutsideS);
+    chart.getStyler().setLegendLayout(Styler.LegendLayout.Horizontal);
+    chart.getStyler().setDefaultSeriesRenderStyle(OHLCSeries.OHLCSeriesRenderStyle.HiLo);
+    chart.getStyler().setToolTipsEnabled(true);
+
+    List<Date> xData = new ArrayList<>();
+    List<Double> openData = new ArrayList<>();
+    List<Double> highData = new ArrayList<>();
+    List<Double> lowData = new ArrayList<>();
+    List<Double> closeData = new ArrayList<>();
+
+    OHLCChart01.populateData(xData, openData, highData, lowData, closeData);
+
+    chart
+        .addSeries("Series", xData, openData, highData, lowData, closeData)
+        .setUpColor(Color.RED)
+        .setDownColor(Color.GREEN);
+
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - Candle with custom colors";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/ohlc/OHLCChart03.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/ohlc/OHLCChart03.java
new file mode 100644
index 0000000000000000000000000000000000000000..68101f3ef1610f508d2e2631dac1509126aa7e88
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/ohlc/OHLCChart03.java
@@ -0,0 +1,88 @@
+package org.knowm.xchart.demo.charts.ohlc;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Random;
+import org.knowm.xchart.OHLCChart;
+import org.knowm.xchart.OHLCChartBuilder;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.style.Styler;
+
+/**
+ * Demonstrates the following:
+ *
+ * <ul>
+ *   <li>Tooltips
+ *   <li>LegendPosition.InsideS
+ *   <li>default OHLCSeriesRenderStyle.Candle
+ *   <li>3 series with Line render style
+ */
+public class OHLCChart03 implements ExampleChart<OHLCChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<OHLCChart> exampleChart = new OHLCChart03();
+    OHLCChart chart = exampleChart.getChart();
+    new SwingWrapper<>(chart).displayChart();
+  }
+
+  @Override
+  public OHLCChart getChart() {
+
+    // Create Chart
+    OHLCChart chart = new OHLCChartBuilder().width(800).height(600).title("OHLCChart03").build();
+
+    // Customize Chart
+    chart.getStyler().setLegendPosition(Styler.LegendPosition.InsideS);
+    chart.getStyler().setLegendLayout(Styler.LegendLayout.Horizontal);
+    chart.getStyler().setToolTipsEnabled(true);
+    chart.getStyler().setYAxisDecimalPattern("##.00");
+    //    chart.getStyler().setDefaultSeriesRenderStyle(OHLCSeries.OHLCSeriesRenderStyle.Line);
+    chart.getStyler().setToolTipsEnabled(true);
+
+    List<Date> xData = new ArrayList<>();
+    List<Double> openData = new ArrayList<>();
+    List<Double> highData = new ArrayList<>();
+    List<Double> lowData = new ArrayList<>();
+    List<Double> closeData = new ArrayList<>();
+
+    OHLCChart01.populateData(xData, openData, highData, lowData, closeData);
+    List<Long> volumeData = new ArrayList<>();
+    Random random = new Random();
+    for (int i = 0; i < xData.size(); i++) {
+      volumeData.add((long) random.nextInt(100000));
+    }
+    // TODO remove volume??
+    chart.addSeries("DAY K", xData, openData, highData, lowData, closeData, volumeData);
+    chart.addSeries("MA5", xData, calculateMA(5, closeData));
+    chart.addSeries("MA10", xData, calculateMA(10, closeData));
+    chart.addSeries("MA15", xData, calculateMA(15, closeData));
+
+    return chart;
+  }
+
+  private List<Double> calculateMA(int dayCount, List<Double> closeData) {
+
+    List<Double> result = new ArrayList<>();
+    for (int i = 0; i < closeData.size(); i++) {
+      if (i < dayCount) {
+        result.add(null);
+        continue;
+      }
+      double sum = 0.0;
+      for (int j = 0; j < dayCount; j++) {
+        sum += closeData.get(i - j);
+      }
+      result.add(sum / dayCount);
+    }
+    return result;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - Candle and lines";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/pie/PieChart01.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/pie/PieChart01.java
new file mode 100644
index 0000000000000000000000000000000000000000..765d7b0f80e2a03e2a40cd2ad5723b41c8f4c065
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/pie/PieChart01.java
@@ -0,0 +1,55 @@
+package org.knowm.xchart.demo.charts.pie;
+
+import org.knowm.xchart.PieChart;
+import org.knowm.xchart.PieChartBuilder;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.style.Styler;
+
+/**
+ * Pie Chart with 4 Slices
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>Pie Chart
+ *   <li>ChartBuilderPie
+ *   <li>Setting Non-circular to match container aspect ratio
+ *   <li>Legend outside south, with Horizontal Legend Layout
+ */
+public class PieChart01 implements ExampleChart<PieChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<PieChart> exampleChart = new PieChart01();
+    PieChart chart = exampleChart.getChart();
+    new SwingWrapper<>(chart).displayChart();
+  }
+
+  @Override
+  public PieChart getChart() {
+
+    // Create Chart
+    PieChart chart =
+        new PieChartBuilder().width(800).height(600).title(getClass().getSimpleName()).build();
+
+    // Customize Chart
+    chart.getStyler().setCircular(false);
+    chart.getStyler().setLegendPosition(Styler.LegendPosition.OutsideS);
+    chart.getStyler().setLegendLayout(Styler.LegendLayout.Horizontal);
+
+    // Series
+    chart.addSeries("Pennies", 100);
+    chart.addSeries("Nickels", 100);
+    chart.addSeries("Dimes", 100);
+    chart.addSeries("Quarters", 100);
+
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - Pie Chart with 4 Slices";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/pie/PieChart02.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/pie/PieChart02.java
new file mode 100644
index 0000000000000000000000000000000000000000..c742b77bc3c9d6bec1cfb49e67d302548a419e7c
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/pie/PieChart02.java
@@ -0,0 +1,68 @@
+package org.knowm.xchart.demo.charts.pie;
+
+import java.awt.Color;
+import org.knowm.xchart.PieChart;
+import org.knowm.xchart.PieChartBuilder;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.style.PieStyler.LabelType;
+
+/**
+ * Pie Chart Custom Color Palette
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>Pie Chart
+ *   <li>PieChartBuilder
+ *   <li>Custom series palette
+ *   <li>Value Annotations
+ *   <li>Tooltips
+ */
+public class PieChart02 implements ExampleChart<PieChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<PieChart> exampleChart = new PieChart02();
+    PieChart chart = exampleChart.getChart();
+    new SwingWrapper<>(chart).displayChart();
+  }
+
+  @Override
+  public PieChart getChart() {
+
+    // Create Chart
+    PieChart chart =
+        new PieChartBuilder().width(800).height(600).title(getClass().getSimpleName()).build();
+
+    // Customize Chart
+    Color[] sliceColors =
+        new Color[] {
+          new Color(224, 68, 14),
+          new Color(230, 105, 62),
+          new Color(236, 143, 110),
+          new Color(243, 180, 159),
+          new Color(246, 199, 182)
+        };
+    chart.getStyler().setSeriesColors(sliceColors);
+    chart.getStyler().setLabelType(LabelType.Value);
+    // chart.getStyler().setDecimalPattern("#0.000");
+    chart.getStyler().setToolTipsEnabled(true);
+    //    chart.getStyler().setToolTipsAlwaysVisible(true);
+
+    // Series
+    chart.addSeries("Gold", 24);
+    chart.addSeries("Silver", 21);
+    chart.addSeries("Platinum", 39);
+    chart.addSeries("Copper", 17);
+    chart.addSeries("Zinc", 40);
+
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - Pie Chart Custom Color Palette";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/pie/PieChart03.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/pie/PieChart03.java
new file mode 100644
index 0000000000000000000000000000000000000000..e0d72ec4ce83b1cf82070cdcf03bdfbe48c3a5bd
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/pie/PieChart03.java
@@ -0,0 +1,62 @@
+package org.knowm.xchart.demo.charts.pie;
+
+import org.knowm.xchart.PieChart;
+import org.knowm.xchart.PieChartBuilder;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.style.Styler.ChartTheme;
+
+/**
+ * Pie Chart GGPlot2 Theme
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>Pie Chart
+ *   <li>PieChartBuilder
+ *   <li>Matlab Theme
+ *   <li>custom start angle
+ *   <li>Custom labels distance outside of pie (>1.0)
+ */
+public class PieChart03 implements ExampleChart<PieChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<PieChart> exampleChart = new PieChart03();
+    PieChart chart = exampleChart.getChart();
+    new SwingWrapper<>(chart).displayChart();
+  }
+
+  @Override
+  public PieChart getChart() {
+
+    // Create Chart
+    PieChart chart =
+        new PieChartBuilder()
+            .width(800)
+            .height(600)
+            .title(getClass().getSimpleName())
+            .theme(ChartTheme.GGPlot2)
+            .build();
+
+    // Customize Chart
+    chart.getStyler().setLegendVisible(false);
+    chart.getStyler().setLabelsDistance(1.15);
+    chart.getStyler().setPlotContentSize(.7);
+    chart.getStyler().setStartAngleInDegrees(90);
+
+    // Series
+    chart.addSeries("Prague", 2);
+    chart.addSeries("Dresden", 4);
+    chart.addSeries("Munich", 34);
+    chart.addSeries("Hamburg", 22);
+    chart.addSeries("Berlin", 29);
+
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+    return getClass().getSimpleName() + " - Pie Chart GGPlot2 Theme";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/pie/PieChart04.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/pie/PieChart04.java
new file mode 100644
index 0000000000000000000000000000000000000000..2ee7f18b45e57997fa0afbec4147b1fc057ff39f
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/pie/PieChart04.java
@@ -0,0 +1,64 @@
+package org.knowm.xchart.demo.charts.pie;
+
+import org.knowm.xchart.PieChart;
+import org.knowm.xchart.PieChartBuilder;
+import org.knowm.xchart.PieSeries.PieSeriesRenderStyle;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.style.PieStyler.LabelType;
+
+/**
+ * Pie Chart with Donut Style
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>Donut Chart
+ *   <li>PieChartBuilder
+ *   <li>XChart Theme
+ *   <li>NameAndValue data labels
+ *   <li>Sum in center of pie
+ */
+public class PieChart04 implements ExampleChart<PieChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<PieChart> exampleChart = new PieChart04();
+    PieChart chart = exampleChart.getChart();
+    new SwingWrapper<>(chart).displayChart();
+  }
+
+  @Override
+  public PieChart getChart() {
+
+    // Create Chart
+    PieChart chart =
+        new PieChartBuilder().width(800).height(600).title(getClass().getSimpleName()).build();
+
+    // Customize Chart
+    chart.getStyler().setLegendVisible(false);
+    chart.getStyler().setDefaultSeriesRenderStyle(PieSeriesRenderStyle.Donut);
+    chart.getStyler().setLabelType(LabelType.NameAndValue);
+    // TODO make this relative to the inner and outer edge of the doughnut slice, not the center of
+    // the pie
+    chart.getStyler().setLabelsDistance(.82);
+    chart.getStyler().setPlotContentSize(.9);
+    chart.getStyler().setSumVisible(true);
+
+    // Series
+    chart.addSeries("A", 22);
+    chart.addSeries("B", 10);
+    chart.addSeries("C", 34);
+    chart.addSeries("D", 22);
+    chart.addSeries("E", 29);
+    chart.addSeries("F", 40);
+
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - Pie Chart with Donut Style";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/pie/PieChart05.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/pie/PieChart05.java
new file mode 100644
index 0000000000000000000000000000000000000000..8a17fa420496845f2ee8568ab4978d278ac0e311
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/pie/PieChart05.java
@@ -0,0 +1,62 @@
+package org.knowm.xchart.demo.charts.pie;
+
+import org.knowm.xchart.PieChart;
+import org.knowm.xchart.PieChartBuilder;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.style.PieStyler.LabelType;
+import org.knowm.xchart.style.Styler;
+
+/**
+ * Pie Chart - circle with border
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>Pie Chart
+ *   <li>PieChartBuilder
+ *   <li>Custom series palette
+ *   <li>Percentage Labels
+ *   <li>Custom pie slice border width
+ */
+public class PieChart05 implements ExampleChart<PieChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<PieChart> exampleChart = new PieChart05();
+    PieChart chart = exampleChart.getChart();
+    new SwingWrapper<>(chart).displayChart();
+  }
+
+  @Override
+  public PieChart getChart() {
+
+    // Create Chart
+    PieChart chart =
+        new PieChartBuilder()
+            .width(800)
+            .height(600)
+            .title(getClass().getSimpleName())
+            .theme(Styler.ChartTheme.Matlab)
+            .build();
+
+    // Customize Chart
+    chart.getStyler().setLabelType(LabelType.Percentage);
+    chart.getStyler().setSliceBorderWidth(10);
+    // chart.getStyler().setDecimalPattern("#0.000");
+
+    // Series
+    chart.addSeries("Married", 2889);
+    chart.addSeries("Single", 1932);
+    chart.addSeries("Widowed", 390);
+    chart.addSeries("Divorced", 789);
+
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - Pie Chart with Matlab Theme";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/radar/RadarChart01.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/radar/RadarChart01.java
new file mode 100644
index 0000000000000000000000000000000000000000..0134d84a9c8cce01255c5fc09ab74cf5b044e754
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/radar/RadarChart01.java
@@ -0,0 +1,62 @@
+package org.knowm.xchart.demo.charts.radar;
+
+import org.knowm.xchart.RadarChart;
+import org.knowm.xchart.RadarChartBuilder;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.style.Styler.LegendPosition;
+
+/**
+ * Radar Chart
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>Radar Chart
+ *   <li>RadarChartBuilder
+ *   <li>Tool tips
+ */
+public class RadarChart01 implements ExampleChart<RadarChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<RadarChart> exampleChart = new RadarChart01();
+    RadarChart chart = exampleChart.getChart();
+    new SwingWrapper<>(chart).displayChart();
+  }
+
+  @Override
+  public RadarChart getChart() {
+
+    // Create Chart
+    RadarChart chart =
+        new RadarChartBuilder().width(800).height(600).title(getClass().getSimpleName()).build();
+    chart.getStyler().setToolTipsEnabled(true);
+    chart.getStyler().setLegendPosition(LegendPosition.InsideSW);
+
+    // Series
+    chart.setRadiiLabels(
+        new String[] {
+          "Sales",
+          "Marketing",
+          "Development",
+          "Customer Support",
+          "Information Technology",
+          "Administration"
+        });
+    chart.addSeries(
+        "Old System",
+        new double[] {0.78, 0.85, 0.80, 0.82, 0.93, 0.92},
+        new String[] {"Lowest varible 78%", "85%", null, null, null, null});
+    chart.addSeries("New System", new double[] {0.67, 0.73, 0.97, 0.95, 0.93, 0.73});
+    chart.addSeries("Experimental System", new double[] {0.37, 0.93, 0.57, 0.55, 0.33, 0.73});
+
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - Basic Radar Chart";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/radar/RadarChart02.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/radar/RadarChart02.java
new file mode 100644
index 0000000000000000000000000000000000000000..68ce9c1ccf6185408d8f5ce21fc98def5efe8c6d
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/radar/RadarChart02.java
@@ -0,0 +1,87 @@
+package org.knowm.xchart.demo.charts.radar;
+
+import org.knowm.xchart.RadarChart;
+import org.knowm.xchart.RadarChartBuilder;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.style.RadarStyler;
+import org.knowm.xchart.style.Styler;
+import org.knowm.xchart.style.markers.SeriesMarkers;
+
+/**
+ * Radar Chart
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>Radar Chart
+ *   <li>Circular style
+ *   <li>Tool tips
+ *   <li>GGPlot2 Theme
+ */
+public class RadarChart02 implements ExampleChart<RadarChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<RadarChart> exampleChart = new RadarChart02();
+    RadarChart chart = exampleChart.getChart();
+    new SwingWrapper<>(chart).displayChart();
+  }
+
+  @Override
+  public RadarChart getChart() {
+
+    // Create Chart
+    RadarChart chart =
+        new RadarChartBuilder()
+            .width(800)
+            .height(600)
+            .title(getClass().getSimpleName())
+            .theme(Styler.ChartTheme.GGPlot2)
+            .build();
+    chart.getStyler().setToolTipsEnabled(true);
+    chart.getStyler().setRadarRenderStyle(RadarStyler.RadarRenderStyle.Circle);
+    chart.getStyler().setSeriesFilled(false);
+    chart.getStyler().setRadiiTickMarksCount(4);
+    chart.getStyler().setLegendPosition(Styler.LegendPosition.OutsideS);
+    chart.getStyler().setLegendLayout(Styler.LegendLayout.Horizontal);
+
+    // Series
+    chart.setRadiiLabels(
+        new String[] {
+          "Math",
+          "Biology",
+          "German",
+          "English",
+          "Chemistry",
+          "PhyEd",
+          "Band",
+          "Physics",
+          "Programming"
+        });
+    chart
+        .addSeries(
+            "Mark",
+            new double[] {
+              3.9 / 4.0, 2.9 / 4.0, 3.4 / 4.0, 2.8 / 4.0, 4 / 4.0, 2.4 / 4.0, 3.1 / 4.0, 2.9 / 4.0,
+              3.8 / 4.0
+            })
+        .setMarker(SeriesMarkers.NONE);
+    chart
+        .addSeries(
+            "Mary",
+            new double[] {
+              2.6 / 4.0, 3.3 / 4.0, 3.7 / 4.0, 3.1 / 4.0, 3.6 / 4.0, 3.2 / 4.0, 3.8 / 4.0,
+              3.7 / 4.0, 3.1 / 4.0
+            })
+        .setMarker(SeriesMarkers.NONE);
+
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - Circular Radar Chart with GGplot2 Theme";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/realtime/RealtimeChart01.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/realtime/RealtimeChart01.java
new file mode 100644
index 0000000000000000000000000000000000000000..e10f95b894f1eed37d3643b2f257eef8f9fc284e
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/realtime/RealtimeChart01.java
@@ -0,0 +1,114 @@
+package org.knowm.xchart.demo.charts.realtime;
+
+import java.util.List;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.CopyOnWriteArrayList;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYChartBuilder;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.demo.charts.RealtimeExampleChart;
+import org.knowm.xchart.style.Styler.ChartTheme;
+
+/**
+ * Real-time XY Chart
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>real-time chart updates with SwingWrapper
+ *   <li>Matlab Theme
+ */
+public class RealtimeChart01 implements ExampleChart<XYChart>, RealtimeExampleChart {
+
+  private XYChart xyChart;
+
+  private List<Double> yData;
+  public static final String SERIES_NAME = "series1";
+
+  public static void main(String[] args) {
+
+    // Setup the panel
+    final RealtimeChart01 realtimeChart01 = new RealtimeChart01();
+    realtimeChart01.go();
+  }
+
+  private void go() {
+
+    final SwingWrapper<XYChart> swingWrapper = new SwingWrapper<XYChart>(getChart());
+    swingWrapper.displayChart();
+
+    // Simulate a data feed
+    TimerTask chartUpdaterTask =
+        new TimerTask() {
+
+          @Override
+          public void run() {
+
+            updateData();
+
+            javax.swing.SwingUtilities.invokeLater(
+                new Runnable() {
+
+                  @Override
+                  public void run() {
+
+                    swingWrapper.repaintChart();
+                  }
+                });
+          }
+        };
+
+    Timer timer = new Timer();
+    timer.scheduleAtFixedRate(chartUpdaterTask, 0, 500);
+  }
+
+  @Override
+  public XYChart getChart() {
+
+    yData = getRandomData(5);
+
+    // Create Chart
+    xyChart =
+        new XYChartBuilder()
+            .width(500)
+            .height(400)
+            .theme(ChartTheme.Matlab)
+            .title("Real-time XY Chart")
+            .build();
+    xyChart.addSeries(SERIES_NAME, null, yData);
+
+    return xyChart;
+  }
+
+  public void updateData() {
+
+    // Get some new data
+    List<Double> newData = getRandomData(1);
+
+    yData.addAll(newData);
+
+    // Limit the total number of points
+    while (yData.size() > 20) {
+      yData.remove(0);
+    }
+
+    xyChart.updateXYSeries(SERIES_NAME, null, yData, null);
+  }
+
+  private List<Double> getRandomData(int numPoints) {
+
+    List<Double> data = new CopyOnWriteArrayList<Double>();
+    for (int i = 0; i < numPoints; i++) {
+      data.add(Math.random() * 100);
+    }
+    return data;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - Real-time XY Chart";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/realtime/RealtimeChart02.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/realtime/RealtimeChart02.java
new file mode 100644
index 0000000000000000000000000000000000000000..5deae834e6e2dd651ff8c22bba1270e548603a84
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/realtime/RealtimeChart02.java
@@ -0,0 +1,120 @@
+package org.knowm.xchart.demo.charts.realtime;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Timer;
+import java.util.TimerTask;
+import org.knowm.xchart.PieChart;
+import org.knowm.xchart.PieChartBuilder;
+import org.knowm.xchart.PieSeries.PieSeriesRenderStyle;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.demo.charts.RealtimeExampleChart;
+import org.knowm.xchart.style.PieStyler.LabelType;
+import org.knowm.xchart.style.Styler.ChartTheme;
+
+/**
+ * Real-time Pie Chart
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>real-time chart updates with SwingWrapper
+ *   <li>Matlab theme
+ *   <li>Pie Chart
+ */
+public class RealtimeChart02 implements ExampleChart<PieChart>, RealtimeExampleChart {
+
+  private PieChart pieChart;
+
+  public static void main(String[] args) {
+
+    // Setup the panel
+    final RealtimeChart02 realtimeChart01 = new RealtimeChart02();
+    realtimeChart01.go();
+  }
+
+  private void go() {
+
+    final SwingWrapper<PieChart> swingWrapper = new SwingWrapper<PieChart>(getChart());
+    swingWrapper.displayChart();
+
+    // Simulate a data feed
+    TimerTask chartUpdaterTask =
+        new TimerTask() {
+
+          @Override
+          public void run() {
+
+            updateData();
+
+            javax.swing.SwingUtilities.invokeLater(
+                new Runnable() {
+
+                  @Override
+                  public void run() {
+
+                    swingWrapper.repaintChart();
+                  }
+                });
+          }
+        };
+
+    Timer timer = new Timer();
+    timer.scheduleAtFixedRate(chartUpdaterTask, 0, 500);
+  }
+
+  @Override
+  public PieChart getChart() {
+
+    // Create Chart
+    pieChart =
+        new PieChartBuilder()
+            .width(500)
+            .height(400)
+            .theme(ChartTheme.Matlab)
+            .title("Real-time Pie Chart")
+            .build();
+
+    // Customize Chart
+    pieChart.getStyler().setLegendVisible(false);
+    pieChart.getStyler().setLabelType(LabelType.NameAndPercentage);
+    pieChart.getStyler().setLabelsDistance(1.22);
+    pieChart.getStyler().setPlotContentSize(.7);
+    pieChart.getStyler().setDefaultSeriesRenderStyle(PieSeriesRenderStyle.Donut);
+
+    Map<String, Number> pieData = getRandomData();
+    for (Entry<String, Number> entry : pieData.entrySet()) {
+      pieChart.addSeries(entry.getKey(), entry.getValue());
+    }
+    return pieChart;
+  }
+
+  public void updateData() {
+
+    Map<String, Number> pieData = getRandomData();
+    for (Entry<String, Number> entry : pieData.entrySet()) {
+      pieChart.updatePieSeries(entry.getKey(), entry.getValue());
+    }
+  }
+
+  private Map<String, Number> getRandomData() {
+
+    Map<String, Number> pieData = new HashMap<String, Number>();
+
+    pieData.put("A", Math.random() * 100);
+    pieData.put("B", Math.random() * 100);
+    pieData.put("C", Math.random() * 100);
+    pieData.put("D", Math.random() * 100);
+    pieData.put("E", Math.random() * 100);
+
+    return pieData;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - Real-time Pie Chart";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/realtime/RealtimeChart03.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/realtime/RealtimeChart03.java
new file mode 100644
index 0000000000000000000000000000000000000000..7533b9764dc0b4980dee4f5a41e4fac73207102e
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/realtime/RealtimeChart03.java
@@ -0,0 +1,140 @@
+package org.knowm.xchart.demo.charts.realtime;
+
+import java.util.List;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.CopyOnWriteArrayList;
+import javax.swing.JFrame;
+import javax.swing.WindowConstants;
+import org.knowm.xchart.XChartPanel;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYChartBuilder;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.demo.charts.RealtimeExampleChart;
+
+/**
+ * Real-time XY Chart with Error Bars
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>real-time chart updates with JFrame
+ *   <li>fixed window
+ *   <li>error bars
+ */
+public class RealtimeChart03 implements ExampleChart<XYChart>, RealtimeExampleChart {
+
+  private XYChart xyChart;
+
+  private List<Integer> xData = new CopyOnWriteArrayList<Integer>();
+  private final List<Double> yData = new CopyOnWriteArrayList<Double>();
+  private List<Double> errorBars = new CopyOnWriteArrayList<Double>();
+
+  public static final String SERIES_NAME = "series1";
+
+  public static void main(String[] args) {
+
+    // Setup the panel
+    final RealtimeChart03 realtimeChart03 = new RealtimeChart03();
+    final XChartPanel<XYChart> chartPanel = realtimeChart03.buildPanel();
+
+    // Schedule a job for the event-dispatching thread:
+    // creating and showing this application's GUI.
+    javax.swing.SwingUtilities.invokeLater(
+        new Runnable() {
+
+          @Override
+          public void run() {
+
+            // Create and set up the window.
+            JFrame frame = new JFrame("XChart");
+            frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
+            frame.add(chartPanel);
+
+            // Display the window.
+            frame.pack();
+            frame.setVisible(true);
+          }
+        });
+
+    // Simulate a data feed
+    TimerTask chartUpdaterTask =
+        new TimerTask() {
+
+          @Override
+          public void run() {
+
+            realtimeChart03.updateData();
+            chartPanel.revalidate();
+            chartPanel.repaint();
+          }
+        };
+
+    Timer timer = new Timer();
+    timer.scheduleAtFixedRate(chartUpdaterTask, 0, 500);
+  }
+
+  public XChartPanel<XYChart> buildPanel() {
+
+    return new XChartPanel<XYChart>(getChart());
+  }
+
+  @Override
+  public XYChart getChart() {
+
+    yData.add(0.0);
+    for (int i = 0; i < 50; i++) {
+      double lastPoint = yData.get(yData.size() - 1);
+      yData.add(getRandomWalk(lastPoint));
+    }
+    // generate X-Data
+    xData = new CopyOnWriteArrayList<Integer>();
+    for (int i = 1; i < yData.size() + 1; i++) {
+      xData.add(i);
+    }
+    // generate error bars
+    errorBars = new CopyOnWriteArrayList<Double>();
+    for (int i = 0; i < yData.size(); i++) {
+      errorBars.add(20 * Math.random());
+    }
+
+    // Create Chart
+    xyChart =
+        new XYChartBuilder()
+            .width(500)
+            .height(400)
+            .xAxisTitle("X")
+            .yAxisTitle("Y")
+            .title("Real-time XY Chart with Error Bars")
+            .build();
+
+    xyChart.addSeries(SERIES_NAME, xData, yData, errorBars);
+
+    return xyChart;
+  }
+
+  private Double getRandomWalk(double lastPoint) {
+
+    return lastPoint + (Math.random() * 100 - 50);
+  }
+
+  public void updateData() {
+
+    // Get some new data
+    double lastPoint = yData.get(yData.size() - 1);
+    yData.add(getRandomWalk(lastPoint));
+    yData.remove(0);
+
+    // update error bars
+    errorBars.add(20 * Math.random());
+    errorBars.remove(0);
+
+    xyChart.updateXYSeries(SERIES_NAME, xData, yData, errorBars);
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - Real-time XY Chart with Error Bars";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/realtime/RealtimeChart04.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/realtime/RealtimeChart04.java
new file mode 100644
index 0000000000000000000000000000000000000000..fa181adec2ee5a97706daca8330f6400025cae35
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/realtime/RealtimeChart04.java
@@ -0,0 +1,138 @@
+package org.knowm.xchart.demo.charts.realtime;
+
+import java.util.List;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.CopyOnWriteArrayList;
+import javax.swing.JFrame;
+import javax.swing.WindowConstants;
+import org.knowm.xchart.BubbleChart;
+import org.knowm.xchart.BubbleChartBuilder;
+import org.knowm.xchart.XChartPanel;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.demo.charts.RealtimeExampleChart;
+import org.knowm.xchart.style.Styler.ChartTheme;
+
+/**
+ * Real-time Bubble Chart
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>real-time chart updates
+ *   <li>multiple series
+ *   <li>Bubble chart
+ *   <li>GGPlot2 theme
+ */
+public class RealtimeChart04 implements ExampleChart<BubbleChart>, RealtimeExampleChart {
+
+  private BubbleChart bubbleChart;
+
+  private List<Double> yData;
+  private List<Double> bubbleData;
+  public static final String SERIES_NAME = "series1";
+
+  public static void main(String[] args) {
+
+    // Setup the panel
+    final RealtimeChart04 realtimeChart04 = new RealtimeChart04();
+    final XChartPanel<BubbleChart> chartPanel = realtimeChart04.buildPanel();
+
+    // Schedule a job for the event-dispatching thread:
+    // creating and showing this application's GUI.
+    javax.swing.SwingUtilities.invokeLater(
+        new Runnable() {
+
+          @Override
+          public void run() {
+
+            // Create and set up the window.
+            JFrame frame = new JFrame("XChart");
+            frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
+            frame.add(chartPanel);
+
+            // Display the window.
+            frame.pack();
+            frame.setVisible(true);
+          }
+        });
+
+    // Simulate a data feed
+    TimerTask chartUpdaterTask =
+        new TimerTask() {
+
+          @Override
+          public void run() {
+
+            realtimeChart04.updateData();
+            chartPanel.revalidate();
+            chartPanel.repaint();
+          }
+        };
+
+    Timer timer = new Timer();
+    timer.scheduleAtFixedRate(chartUpdaterTask, 0, 500);
+  }
+
+  public XChartPanel<BubbleChart> buildPanel() {
+
+    return new XChartPanel<BubbleChart>(getChart());
+  }
+
+  @Override
+  public BubbleChart getChart() {
+
+    yData = getRandomData(5);
+    bubbleData = getRandomData(5);
+
+    // Create Chart
+    bubbleChart =
+        new BubbleChartBuilder()
+            .width(500)
+            .height(400)
+            .theme(ChartTheme.GGPlot2)
+            .xAxisTitle("X")
+            .yAxisTitle("Y")
+            .title("Real-time Bubble Chart")
+            .build();
+
+    bubbleChart.addSeries(SERIES_NAME, null, yData, bubbleData);
+
+    return bubbleChart;
+  }
+
+  private List<Double> getRandomData(int numPoints) {
+
+    List<Double> data = new CopyOnWriteArrayList<Double>();
+    for (int i = 0; i < numPoints; i++) {
+      data.add(Math.random() * 100);
+    }
+    return data;
+  }
+
+  public void updateData() {
+
+    // Get some new data
+    List<Double> newData = getRandomData(1);
+    yData.addAll(newData);
+    // Limit the total number of points
+    while (yData.size() > 20) {
+      yData.remove(0);
+    }
+
+    // Get some new data
+    newData = getRandomData(1);
+    bubbleData.addAll(newData);
+    // Limit the total number of points
+    while (bubbleData.size() > 20) {
+      bubbleData.remove(0);
+    }
+    bubbleChart.updateBubbleSeries(SERIES_NAME, null, yData, bubbleData);
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - Real-time Bubble Chart";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/realtime/RealtimeChart05.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/realtime/RealtimeChart05.java
new file mode 100644
index 0000000000000000000000000000000000000000..0a8b5a02bf11bfefdb0a8a4487f99e4e325c6a00
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/realtime/RealtimeChart05.java
@@ -0,0 +1,119 @@
+package org.knowm.xchart.demo.charts.realtime;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Random;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.CopyOnWriteArrayList;
+import org.knowm.xchart.CategoryChart;
+import org.knowm.xchart.CategoryChartBuilder;
+import org.knowm.xchart.Histogram;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.demo.charts.RealtimeExampleChart;
+import org.knowm.xchart.style.Styler.ChartTheme;
+
+/**
+ * Real-time Category Chart
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>real-time chart updates with SwingWrapper
+ */
+public class RealtimeChart05 implements ExampleChart<CategoryChart>, RealtimeExampleChart {
+
+  private CategoryChart categoryChart;
+
+  private List<String> xData;
+  private List<Double> yData;
+  public static final String SERIES_NAME = "series1";
+
+  public static void main(String[] args) {
+
+    // Setup the panel
+    final RealtimeChart05 realtimeChart01 = new RealtimeChart05();
+    realtimeChart01.go();
+  }
+
+  private void go() {
+
+    final SwingWrapper<CategoryChart> swingWrapper = new SwingWrapper<CategoryChart>(getChart());
+    swingWrapper.displayChart();
+
+    // Simulate a data feed
+    TimerTask chartUpdaterTask =
+        new TimerTask() {
+
+          @Override
+          public void run() {
+
+            updateData();
+
+            javax.swing.SwingUtilities.invokeLater(
+                new Runnable() {
+
+                  @Override
+                  public void run() {
+
+                    swingWrapper.repaintChart();
+                  }
+                });
+          }
+        };
+
+    Timer timer = new Timer();
+    timer.scheduleAtFixedRate(chartUpdaterTask, 0, 500);
+  }
+
+  @Override
+  public CategoryChart getChart() {
+
+    xData =
+        new CopyOnWriteArrayList<String>(
+            Arrays.asList(new String[] {"Blue", "Red", "Green", "Yellow", "Orange"}));
+    Histogram histogram = new Histogram(getGaussianData(1000), 5, -10, 10);
+    yData = histogram.getyAxisData();
+
+    // Create Chart
+    categoryChart =
+        new CategoryChartBuilder()
+            .width(500)
+            .height(400)
+            .theme(ChartTheme.Matlab)
+            .title("Real-time Category Chart")
+            .build();
+
+    categoryChart.addSeries(SERIES_NAME, xData, yData);
+
+    return categoryChart;
+  }
+
+  public void updateData() {
+
+    // Get some new data
+
+    Histogram histogram = new Histogram(getGaussianData(1000), 5, -10, 10);
+    yData = histogram.getyAxisData();
+
+    categoryChart.updateCategorySeries(SERIES_NAME, xData, yData, null);
+  }
+
+  private List<Double> getGaussianData(int count) {
+
+    List<Double> data = new CopyOnWriteArrayList<Double>();
+    Random r = new Random();
+    for (int i = 0; i < count; i++) {
+      data.add(r.nextGaussian() * 5);
+      // data.add(r.nextDouble() * 60 - 30);
+    }
+    return data;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - Real-time Category Chart";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/realtime/RealtimeChart06.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/realtime/RealtimeChart06.java
new file mode 100644
index 0000000000000000000000000000000000000000..a603bd7b4f862eb63318063942ea4cb0c53d5c98
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/realtime/RealtimeChart06.java
@@ -0,0 +1,121 @@
+package org.knowm.xchart.demo.charts.realtime;
+
+import java.util.*;
+import org.knowm.xchart.*;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.demo.charts.RealtimeExampleChart;
+import org.knowm.xchart.demo.charts.ohlc.OHLCChart01;
+import org.knowm.xchart.style.Styler;
+import org.knowm.xchart.style.Styler.ChartTheme;
+
+/**
+ * Real-time OHLC Chart
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>real-time chart updates with SwingWrapper
+ *   <li>Matlab Theme
+ */
+public class RealtimeChart06 implements ExampleChart<OHLCChart>, RealtimeExampleChart {
+
+  private OHLCChart ohlcChart;
+
+  private List<Date> xData = new ArrayList<Date>();
+  private List<Double> openData = new ArrayList<Double>();
+  private List<Double> highData = new ArrayList<Double>();
+  private List<Double> lowData = new ArrayList<Double>();
+  private List<Double> closeData = new ArrayList<Double>();
+
+  public static final String SERIES_NAME = "series1";
+
+  public static void main(String[] args) {
+
+    // Setup the panel
+    final RealtimeChart06 realtimeChart01 = new RealtimeChart06();
+    realtimeChart01.go();
+  }
+
+  private void go() {
+
+    final SwingWrapper<OHLCChart> swingWrapper = new SwingWrapper<OHLCChart>(getChart());
+    swingWrapper.displayChart();
+
+    // Simulate a data feed
+    TimerTask chartUpdaterTask =
+        new TimerTask() {
+
+          @Override
+          public void run() {
+
+            updateData();
+
+            javax.swing.SwingUtilities.invokeLater(
+                new Runnable() {
+
+                  @Override
+                  public void run() {
+
+                    swingWrapper.repaintChart();
+                  }
+                });
+          }
+        };
+
+    Timer timer = new Timer();
+    timer.scheduleAtFixedRate(chartUpdaterTask, 0, 500);
+  }
+
+  @Override
+  public OHLCChart getChart() {
+
+    // Create Chart
+    ohlcChart =
+        new OHLCChartBuilder()
+            .width(800)
+            .height(600)
+            .title("Real-time Prices Chart")
+            .theme(ChartTheme.Matlab)
+            .build();
+
+    // Customize Chart
+    ohlcChart.getStyler().setLegendPosition(Styler.LegendPosition.OutsideS);
+    ohlcChart.getStyler().setLegendLayout(Styler.LegendLayout.Horizontal);
+    // generate data
+    OHLCChart01.populateData(xData, openData, highData, lowData, closeData);
+
+    ohlcChart.addSeries(SERIES_NAME, xData, openData, highData, lowData, closeData);
+
+    return ohlcChart;
+  }
+
+  public void updateData() {
+
+    OHLCChart01.populateData(
+        xData.get(xData.size() - 1),
+        closeData.get(closeData.size() - 1),
+        1,
+        xData,
+        openData,
+        highData,
+        lowData,
+        closeData);
+
+    // Limit the total number of points
+    while (xData.size() > 50) {
+      xData.remove(0);
+      openData.remove(0);
+      highData.remove(0);
+      lowData.remove(0);
+      closeData.remove(0);
+    }
+
+    ohlcChart.updateOHLCSeries(SERIES_NAME, xData, openData, highData, lowData, closeData);
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - Real-time OHLC Chart";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/scatter/ScatterChart01.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/scatter/ScatterChart01.java
new file mode 100644
index 0000000000000000000000000000000000000000..afe7cf99bfb2039d9dae6cf4add226312bdfcd5c
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/scatter/ScatterChart01.java
@@ -0,0 +1,69 @@
+package org.knowm.xchart.demo.charts.scatter;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Random;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYChartBuilder;
+import org.knowm.xchart.XYSeries;
+import org.knowm.xchart.XYSeries.XYSeriesRenderStyle;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.style.Styler;
+import org.knowm.xchart.style.markers.SeriesMarkers;
+
+/**
+ * Gaussian Blob
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>ChartType.Scatter
+ *   <li>Series data as a Set
+ *   <li>Setting marker size
+ *   <li>Formatting of negative numbers with large magnitude but small differences
+ *   <li>YAxis position on Right
+ *   <li>Cross type series marker
+ */
+public class ScatterChart01 implements ExampleChart<XYChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<XYChart> exampleChart = new ScatterChart01();
+    XYChart chart = exampleChart.getChart();
+    new SwingWrapper<>(chart).displayChart();
+  }
+
+  @Override
+  public XYChart getChart() {
+
+    // Create Chart
+    XYChart chart = new XYChartBuilder().width(800).height(600).build();
+
+    // Customize Chart
+    chart.getStyler().setDefaultSeriesRenderStyle(XYSeriesRenderStyle.Scatter);
+    chart.getStyler().setChartTitleVisible(false);
+    chart.getStyler().setLegendVisible(false);
+    chart.getStyler().setMarkerSize(16);
+    chart.getStyler().setYAxisGroupPosition(0, Styler.YAxisPosition.Right);
+    // Series
+    List<Double> xData = new LinkedList<Double>();
+    List<Double> yData = new LinkedList<Double>();
+    Random random = new Random();
+    int size = 1000;
+    for (int i = 0; i < size; i++) {
+      xData.add(random.nextGaussian() / 1000);
+      yData.add(-1000000 + random.nextGaussian());
+    }
+    XYSeries series = chart.addSeries("Gaussian Blob", xData, yData);
+    series.setMarker(SeriesMarkers.CROSS);
+
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - Gaussian Blob with Y Axis on Right";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/scatter/ScatterChart02.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/scatter/ScatterChart02.java
new file mode 100644
index 0000000000000000000000000000000000000000..7ad934ae9d7d4a9047bca26d42282da0c37342f6
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/scatter/ScatterChart02.java
@@ -0,0 +1,64 @@
+package org.knowm.xchart.demo.charts.scatter;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYChartBuilder;
+import org.knowm.xchart.XYSeries.XYSeriesRenderStyle;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.style.Styler.LegendPosition;
+
+/**
+ * Logarithmic Data
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>Scatter chart
+ *   <li>Logarithmic X-Axis
+ *   <li>Place legend at Inside-NW position
+ *   <li>Formatting of number with large magnitude but small differences
+ */
+public class ScatterChart02 implements ExampleChart<XYChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<XYChart> exampleChart = new ScatterChart02();
+    XYChart chart = exampleChart.getChart();
+    new SwingWrapper<XYChart>(chart).displayChart();
+  }
+
+  @Override
+  public XYChart getChart() {
+
+    // Create Chart
+    XYChart chart = new XYChartBuilder().width(800).height(600).title("Logarithmic Data").build();
+
+    // Customize Chart
+    chart.getStyler().setDefaultSeriesRenderStyle(XYSeriesRenderStyle.Scatter);
+    chart.getStyler().setXAxisLogarithmic(true);
+    chart.getStyler().setLegendPosition(LegendPosition.InsideN);
+
+    // Series
+    List<Double> xData = new ArrayList<Double>();
+    List<Double> yData = new ArrayList<Double>();
+    Random random = new Random();
+    int size = 400;
+    for (int i = 0; i < size; i++) {
+      double nextRandom = random.nextDouble();
+      xData.add(Math.pow(10, nextRandom * 10));
+      yData.add(1000000000.0 + nextRandom);
+    }
+    chart.addSeries("logarithmic data", xData, yData);
+
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - Logarithmic Data";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/scatter/ScatterChart03.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/scatter/ScatterChart03.java
new file mode 100644
index 0000000000000000000000000000000000000000..6a4fcaa8978007bd8e45599c62f21245768325b4
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/scatter/ScatterChart03.java
@@ -0,0 +1,53 @@
+package org.knowm.xchart.demo.charts.scatter;
+
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYChartBuilder;
+import org.knowm.xchart.XYSeries.XYSeriesRenderStyle;
+import org.knowm.xchart.demo.charts.ExampleChart;
+
+/**
+ * Single point
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>Single point
+ */
+public class ScatterChart03 implements ExampleChart<XYChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<XYChart> exampleChart = new ScatterChart03();
+    XYChart chart = exampleChart.getChart();
+    new SwingWrapper<XYChart>(chart).displayChart();
+  }
+
+  @Override
+  public XYChart getChart() {
+
+    // Create Chart
+    XYChart chart =
+        new XYChartBuilder()
+            .width(800)
+            .height(600)
+            .title("Single Point")
+            .xAxisTitle("X")
+            .yAxisTitle("Y")
+            .build();
+
+    // Customize Chart
+    chart.getStyler().setDefaultSeriesRenderStyle(XYSeriesRenderStyle.Scatter);
+
+    // Series
+    chart.addSeries("single point (1,1)", new double[] {1}, new double[] {1});
+
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - Single Point";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/scatter/ScatterChart04.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/scatter/ScatterChart04.java
new file mode 100644
index 0000000000000000000000000000000000000000..c1bcad899518f99c435892c5e6c475d6f20472f3
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/scatter/ScatterChart04.java
@@ -0,0 +1,96 @@
+package org.knowm.xchart.demo.charts.scatter;
+
+import java.awt.Color;
+import java.util.ArrayList;
+import java.util.List;
+import org.knowm.xchart.AnnotationTextPanel;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYChartBuilder;
+import org.knowm.xchart.XYSeries;
+import org.knowm.xchart.XYSeries.XYSeriesRenderStyle;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.style.markers.SeriesMarkers;
+
+/**
+ * Error Bars
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>Error Bars
+ *   <li>Using ChartBuilder to Make a Chart
+ *   <li>List<Number> data sets
+ *   <li>Setting Series Marker and Marker Color
+ *   <li>Using a custom decimal pattern
+ *   <li>InfoPanel
+ */
+public class ScatterChart04 implements ExampleChart<XYChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<XYChart> exampleChart = new ScatterChart04();
+    XYChart chart = exampleChart.getChart();
+    new SwingWrapper<>(chart).displayChart();
+  }
+
+  @Override
+  public XYChart getChart() {
+
+    // Create Chart
+    XYChart chart =
+        new XYChartBuilder()
+            .width(800)
+            .height(600)
+            .title("ScatterChart04")
+            .xAxisTitle("X")
+            .yAxisTitle("Y")
+            .build();
+
+    // Customize Chart
+    chart.getStyler().setDefaultSeriesRenderStyle(XYSeriesRenderStyle.Scatter);
+    chart.getStyler().setChartTitleVisible(false);
+    chart.getStyler().setLegendVisible(false);
+    chart.getStyler().setAxisTitlesVisible(false);
+    chart.getStyler().setXAxisDecimalPattern("0.0000000");
+
+    // InfoPanel
+    chart.addAnnotation(
+        new AnnotationTextPanel("Here are some words in an AnnotationTextPanel!", 40, 40, true));
+    chart.addAnnotation(
+        new AnnotationTextPanel("Here are some additional words", 0.000004, 4, false));
+    chart.addAnnotation(
+        new AnnotationTextPanel(
+            "Here are some additional words \n in the upper right-hand corner \n with multiple lines",
+            800,
+            600,
+            true));
+    //    chart.getStyler().setAnnotationTextPanelPadding(20);
+    //    chart.getStyler().setAnnotationTextPanelFont(new Font("Verdana", Font.BOLD, 12));
+    //    chart.getStyler().setAnnotationTextPanelBackgroundColor(Color.RED);
+    //    chart.getStyler().setAnnotationTextPanelBorderColor(Color.BLUE);
+    //    chart.getStyler().setAnnotationTextPanelFontColor(Color.GREEN);
+
+    // Series
+    int size = 10;
+    List<Double> xData = new ArrayList<>();
+    List<Double> yData = new ArrayList<>();
+    List<Double> errorBars = new ArrayList<>();
+    for (int i = 0; i <= size; i++) {
+      xData.add(((double) i) / 1000000);
+      yData.add(10 * Math.exp(-i));
+      errorBars.add(Math.random() + .3);
+    }
+    XYSeries series = chart.addSeries("10^(-x)", xData, yData, errorBars);
+    series.setMarkerColor(Color.RED);
+    series.setMarker(SeriesMarkers.SQUARE);
+
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - Error Bars";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/stick/StickChart01.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/stick/StickChart01.java
new file mode 100644
index 0000000000000000000000000000000000000000..679391057346e44092e27fba489af3992fd022e1
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/stick/StickChart01.java
@@ -0,0 +1,57 @@
+package org.knowm.xchart.demo.charts.stick;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.knowm.xchart.CategoryChart;
+import org.knowm.xchart.CategoryChartBuilder;
+import org.knowm.xchart.CategorySeries.CategorySeriesRenderStyle;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.style.Styler.LegendPosition;
+
+/**
+ * Basic Stick Chart
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>Stick category series render type
+ */
+public class StickChart01 implements ExampleChart<CategoryChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<CategoryChart> exampleChart = new StickChart01();
+    CategoryChart chart = exampleChart.getChart();
+    new SwingWrapper<CategoryChart>(chart).displayChart();
+  }
+
+  @Override
+  public CategoryChart getChart() {
+
+    // Create Chart
+    CategoryChart chart = new CategoryChartBuilder().width(800).height(600).title("Stick").build();
+
+    // Customize Chart
+    chart.getStyler().setChartTitleVisible(true);
+    chart.getStyler().setLegendPosition(LegendPosition.InsideNW);
+    chart.getStyler().setDefaultSeriesRenderStyle(CategorySeriesRenderStyle.Stick);
+
+    // Series
+    List<Integer> xData = new ArrayList<Integer>();
+    List<Integer> yData = new ArrayList<Integer>();
+    for (int i = -3; i <= 24; i++) {
+      xData.add(i);
+      yData.add(i);
+    }
+    chart.addSeries("data", xData, yData);
+
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - Basic Stick Chart";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/theme/MyCustomSeriesColors.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/theme/MyCustomSeriesColors.java
new file mode 100644
index 0000000000000000000000000000000000000000..16474be2120d2b3acf37ef0e81966ef372eba20e
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/theme/MyCustomSeriesColors.java
@@ -0,0 +1,25 @@
+package org.knowm.xchart.demo.charts.theme;
+
+import java.awt.Color;
+import org.knowm.xchart.style.colors.SeriesColors;
+
+public class MyCustomSeriesColors implements SeriesColors {
+
+  public static final Color GREEN = new Color(0, 205, 0, 180);
+  public static final Color RED = new Color(205, 0, 0, 180);
+  public static final Color BLACK = new Color(0, 0, 0, 180);
+
+  private final Color[] seriesColors;
+
+  /** Constructor */
+  public MyCustomSeriesColors() {
+
+    seriesColors = new Color[] {GREEN, RED, BLACK};
+  }
+
+  @Override
+  public Color[] getSeriesColors() {
+
+    return seriesColors;
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/theme/MyCustomTheme.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/theme/MyCustomTheme.java
new file mode 100644
index 0000000000000000000000000000000000000000..75fe17bff5519284d8edfff2bd95eabed53a0d9b
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/theme/MyCustomTheme.java
@@ -0,0 +1,126 @@
+package org.knowm.xchart.demo.charts.theme;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Font;
+import org.knowm.xchart.style.colors.ChartColor;
+import org.knowm.xchart.style.lines.XChartSeriesLines;
+import org.knowm.xchart.style.markers.Marker;
+import org.knowm.xchart.style.markers.XChartSeriesMarkers;
+import org.knowm.xchart.style.theme.AbstractBaseTheme;
+
+public class MyCustomTheme extends AbstractBaseTheme {
+
+  // Chart Style ///////////////////////////////
+
+  @Override
+  public Font getBaseFont() {
+
+    return new Font(Font.SERIF, Font.PLAIN, 10);
+  }
+
+  @Override
+  public Color getChartBackgroundColor() {
+
+    return ChartColor.DARK_GREY.getColor();
+  }
+
+  @Override
+  public Color getChartFontColor() {
+
+    return ChartColor.DARK_GREY.getColor();
+  }
+
+  @Override
+  public int getChartPadding() {
+
+    return 12;
+  }
+
+  @Override
+  public Color[] getSeriesColors() {
+
+    return new MyCustomSeriesColors().getSeriesColors();
+  }
+
+  @Override
+  public Marker[] getSeriesMarkers() {
+
+    return new XChartSeriesMarkers().getSeriesMarkers();
+  }
+
+  @Override
+  public BasicStroke[] getSeriesLines() {
+
+    return new XChartSeriesLines().getSeriesLines();
+  }
+
+  // Chart Title ///////////////////////////////
+
+  @Override
+  public Font getChartTitleFont() {
+
+    return getBaseFont().deriveFont(Font.BOLD).deriveFont(18f);
+  }
+
+  @Override
+  public boolean isChartTitleBoxVisible() {
+
+    return false;
+  }
+
+  @Override
+  public Color getChartTitleBoxBackgroundColor() {
+
+    return ChartColor.GREY.getColor();
+  }
+
+  @Override
+  public Color getChartTitleBoxBorderColor() {
+
+    return ChartColor.GREY.getColor();
+  }
+
+  // Chart Legend ///////////////////////////////
+
+  // Chart Axes ///////////////////////////////
+
+  @Override
+  public BasicStroke getAxisTickMarksStroke() {
+
+    return new BasicStroke(
+        1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 10.0f, new float[] {3.0f, 0.0f}, 0.0f);
+  }
+
+  // Chart Plot Area ///////////////////////////////
+
+  @Override
+  public boolean isPlotTicksMarksVisible() {
+
+    return false;
+  }
+
+  @Override
+  public BasicStroke getPlotGridLinesStroke() {
+
+    return new BasicStroke(
+        0.25f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 10.0f, new float[] {3.0f, 3.0f}, 0.0f);
+  }
+
+  // Category Charts ///////////////////////////////
+
+  // Pie Charts ///////////////////////////////
+
+  // Line, Scatter, Area Charts ///////////////////////////////
+
+  @Override
+  public int getMarkerSize() {
+
+    return 16;
+  }
+
+  // Error Bars ///////////////////////////////
+
+  // Annotations ///////////////////////////////
+
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/theme/ThemeChart01.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/theme/ThemeChart01.java
new file mode 100644
index 0000000000000000000000000000000000000000..7cbac25f1e9c397abd7368faedb901e25ed989c6
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/theme/ThemeChart01.java
@@ -0,0 +1,68 @@
+package org.knowm.xchart.demo.charts.theme;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYChartBuilder;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.style.Styler.ChartTheme;
+
+/**
+ * Default XChart Theme
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>Setting marker size
+ */
+public class ThemeChart01 implements ExampleChart<XYChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<XYChart> exampleChart = new ThemeChart01();
+    XYChart chart = exampleChart.getChart();
+    new SwingWrapper<XYChart>(chart).displayChart();
+  }
+
+  @Override
+  public XYChart getChart() {
+
+    // Create Chart
+    XYChart chart =
+        new XYChartBuilder()
+            .width(800)
+            .height(600)
+            .theme(ChartTheme.XChart)
+            .title("XChart Theme")
+            .xAxisTitle("X")
+            .yAxisTitle("Y")
+            .build();
+
+    // Customize Chart
+    chart.getStyler().setMarkerSize(11);
+
+    // Series
+    for (int i = 1; i <= 14; i++) {
+
+      // generates linear data
+      int b = 20;
+      List<Number> xData = new ArrayList<Number>();
+      List<Number> yData = new ArrayList<Number>();
+      for (int x = 0; x <= b; x++) {
+        xData.add(2 * x - b);
+        yData.add(2 * i * x - i * b);
+      }
+
+      String seriesName = "y=" + 2 * i + "x-" + i * b + "b";
+      chart.addSeries(seriesName, xData, yData);
+    }
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - Default XChart Theme";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/theme/ThemeChart02.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/theme/ThemeChart02.java
new file mode 100644
index 0000000000000000000000000000000000000000..88c64c6ccc628e62d774ac9ebd133c0801cf2e63
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/theme/ThemeChart02.java
@@ -0,0 +1,56 @@
+package org.knowm.xchart.demo.charts.theme;
+
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYChartBuilder;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.style.Styler.ChartTheme;
+
+/**
+ * GGPlot2 Theme
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>Building a Chart with ChartBuilder
+ *   <li>Applying the GGPlot2 Theme to the Chart
+ *   <li>Vertical and Horizontal Lines
+ */
+public class ThemeChart02 implements ExampleChart<XYChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<XYChart> exampleChart = new ThemeChart02();
+    XYChart chart = exampleChart.getChart();
+    new SwingWrapper<XYChart>(chart).displayChart();
+  }
+
+  @Override
+  public XYChart getChart() {
+
+    // Create Chart
+    XYChart chart =
+        new XYChartBuilder()
+            .width(800)
+            .height(600)
+            .theme(ChartTheme.GGPlot2)
+            .title("GGPlot2 Theme")
+            .xAxisTitle("X")
+            .yAxisTitle("Y")
+            .build();
+
+    // Customize Chart
+
+    // Series
+    chart.addSeries("vertical", new double[] {1, 1}, new double[] {-10, 10});
+    chart.addSeries("horizontal", new double[] {-10, 10}, new double[] {0, 0});
+
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - GGPlot2 Theme";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/theme/ThemeChart03.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/theme/ThemeChart03.java
new file mode 100644
index 0000000000000000000000000000000000000000..c47ad4452097dd126e34a0db3582d9ae6eb85675
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/theme/ThemeChart03.java
@@ -0,0 +1,95 @@
+package org.knowm.xchart.demo.charts.theme;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYChartBuilder;
+import org.knowm.xchart.XYSeries;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.style.Styler;
+import org.knowm.xchart.style.Styler.ChartTheme;
+import org.knowm.xchart.style.markers.SeriesMarkers;
+
+/**
+ * Matlab Theme
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>Building a Chart with ChartBuilder
+ *   <li>Applying the Matlab Theme to the Chart
+ *   <li>Generating Gaussian Bell Curve Data
+ */
+public class ThemeChart03 implements ExampleChart<XYChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<XYChart> exampleChart = new ThemeChart03();
+    XYChart chart = exampleChart.getChart();
+    new SwingWrapper<XYChart>(chart).displayChart();
+  }
+
+  @Override
+  public XYChart getChart() {
+
+    // Create Chart
+    XYChart chart =
+        new XYChartBuilder()
+            .width(800)
+            .height(600)
+            .theme(ChartTheme.Matlab)
+            .title("Matlab Theme")
+            .xAxisTitle("X")
+            .yAxisTitle("Y")
+            .build();
+
+    // Customize Chart
+    chart.getStyler().setPlotGridLinesVisible(false);
+    chart.getStyler().setXAxisTickMarkSpacingHint(100);
+    chart.getStyler().setToolTipsEnabled(true);
+
+    // Series
+    List<Integer> xData = new ArrayList<Integer>();
+    for (int i = 0; i < 640; i++) {
+      xData.add(i);
+    }
+    List<Double> y1Data = getYAxis(xData, 320, 160);
+    List<Double> y2Data = getYAxis(xData, 320, 320);
+    List<Double> y3Data = new ArrayList<Double>(xData.size());
+    for (int i = 0; i < 640; i++) {
+      y3Data.add(y1Data.get(i) - y2Data.get(i));
+    }
+
+    XYSeries series = chart.addSeries("Gaussian 1", xData, y1Data);
+    series.setMarker(SeriesMarkers.NONE);
+    series = chart.addSeries("Gaussian 2", xData, y2Data);
+    series.setMarker(SeriesMarkers.NONE);
+    series.setYAxisGroup(1); // default is group 0
+    series = chart.addSeries("Difference", xData, y3Data);
+    series.setMarker(SeriesMarkers.NONE);
+
+    chart.getStyler().setYAxisGroupPosition(1, Styler.YAxisPosition.Right);
+    chart.getStyler().setLegendPosition(Styler.LegendPosition.InsideS);
+
+    return chart;
+  }
+
+  private List<Double> getYAxis(List<Integer> xData, double mean, double std) {
+
+    List<Double> yData = new ArrayList<Double>(xData.size());
+
+    for (Integer integer : xData) {
+      yData.add(
+          (1 / (std * Math.sqrt(2 * Math.PI)))
+              * Math.exp(-(((integer - mean) * (integer - mean)) / ((2 * std * std)))));
+    }
+    return yData;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - Matlab Theme";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/theme/ThemeChart04.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/theme/ThemeChart04.java
new file mode 100644
index 0000000000000000000000000000000000000000..baa6c25168be7e64cc97669e85abc4aee0fc0ff6
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/theme/ThemeChart04.java
@@ -0,0 +1,71 @@
+package org.knowm.xchart.demo.charts.theme;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYChartBuilder;
+import org.knowm.xchart.demo.charts.ExampleChart;
+
+/**
+ * My Custom Theme
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>Using a custom class that implements Theme
+ */
+public class ThemeChart04 implements ExampleChart<XYChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<XYChart> exampleChart = new ThemeChart04();
+    XYChart chart = exampleChart.getChart();
+    new SwingWrapper<XYChart>(chart).displayChart();
+  }
+
+  @Override
+  public XYChart getChart() {
+
+    // Create Chart
+    XYChart chart =
+        new XYChartBuilder()
+            .width(800)
+            .height(600)
+            .title("My Custom Theme")
+            .xAxisTitle("X")
+            .yAxisTitle("Y")
+            .build();
+    chart.getStyler().setTheme(new MyCustomTheme());
+
+    // Customize Chart
+    chart.getStyler().setMarkerSize(11);
+
+    // Series
+    for (int i = 1; i <= 3; i++) {
+
+      // generates circle data
+      double x, y;
+      List<Number> xData = new ArrayList<Number>();
+      List<Number> yData = new ArrayList<Number>();
+
+      for (int j = 0; j < 360; j = j + 5) {
+
+        x = (double) i * Math.cos(Math.toRadians(j));
+        y = (double) i * Math.sin(Math.toRadians(j));
+        xData.add(x);
+        yData.add(y);
+      }
+
+      String seriesName = "r=" + i;
+      chart.addSeries(seriesName, xData, yData);
+    }
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - My Custom Theme";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/Example0.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/Example0.java
new file mode 100644
index 0000000000000000000000000000000000000000..f7d35fd51c62efb833da58b06a17a2d5d97d0a8e
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/Example0.java
@@ -0,0 +1,21 @@
+package org.knowm.xchart.standalone;
+
+import org.knowm.xchart.QuickChart;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+
+/** Creates a simple Chart using QuickChart */
+public class Example0 {
+
+  public static void main(String[] args) throws Exception {
+
+    double[] xData = new double[] {0.0, 1.0, 2.0};
+    double[] yData = new double[] {2.0, 1.0, 0.0};
+
+    // Create Chart
+    XYChart chart = QuickChart.getChart("Sample Chart", "X", "Y", "y(x)", xData, yData);
+
+    // Show it
+    new SwingWrapper(chart).displayChart();
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/Example1.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/Example1.java
new file mode 100644
index 0000000000000000000000000000000000000000..d72ec3e9ff4d960611712961ec74f8a247ac50b1
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/Example1.java
@@ -0,0 +1,40 @@
+package org.knowm.xchart.standalone;
+
+import org.knowm.xchart.BitmapEncoder;
+import org.knowm.xchart.BitmapEncoder.BitmapFormat;
+import org.knowm.xchart.VectorGraphicsEncoder;
+import org.knowm.xchart.VectorGraphicsEncoder.VectorGraphicsFormat;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYSeries;
+import org.knowm.xchart.style.markers.SeriesMarkers;
+
+/** Creates a simple Chart and saves it as a PNG and JPEG image file. */
+public class Example1 {
+
+  public static void main(String[] args) throws Exception {
+
+    double[] yData = new double[] {2.0, 1.0, 0.0};
+
+    // Create Chart
+    XYChart chart = new XYChart(500, 400);
+    chart.setTitle("Sample Chart");
+    chart.setXAxisTitle("X");
+    chart.setXAxisTitle("Y");
+    XYSeries series = chart.addSeries("y(x)", null, yData);
+    series.setMarker(SeriesMarkers.CIRCLE);
+
+    BitmapEncoder.saveBitmap(chart, "./Sample_Chart", BitmapFormat.PNG);
+    BitmapEncoder.saveBitmap(chart, "./Sample_Chart", BitmapFormat.JPG);
+    BitmapEncoder.saveJPGWithQuality(chart, "./Sample_Chart_With_Quality.jpg", 0.95f);
+    BitmapEncoder.saveBitmap(chart, "./Sample_Chart", BitmapFormat.BMP);
+    BitmapEncoder.saveBitmap(chart, "./Sample_Chart", BitmapFormat.GIF);
+
+    BitmapEncoder.saveBitmapWithDPI(chart, "./Sample_Chart_300_DPI", BitmapFormat.PNG, 300);
+    BitmapEncoder.saveBitmapWithDPI(chart, "./Sample_Chart_300_DPI", BitmapFormat.JPG, 300);
+    BitmapEncoder.saveBitmapWithDPI(chart, "./Sample_Chart_300_DPI", BitmapFormat.GIF, 300);
+
+    VectorGraphicsEncoder.saveVectorGraphic(chart, "./Sample_Chart", VectorGraphicsFormat.EPS);
+    VectorGraphicsEncoder.saveVectorGraphic(chart, "./Sample_Chart", VectorGraphicsFormat.PDF);
+    VectorGraphicsEncoder.saveVectorGraphic(chart, "./Sample_Chart", VectorGraphicsFormat.SVG);
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/Example2.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/Example2.java
new file mode 100644
index 0000000000000000000000000000000000000000..0068ae04c6ec31bbb92f5f19a79b382aee95e46a
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/Example2.java
@@ -0,0 +1,47 @@
+package org.knowm.xchart.standalone;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import org.knowm.xchart.*;
+import org.knowm.xchart.style.markers.SeriesMarkers;
+
+/** Create a Chart matrix */
+public class Example2 {
+
+  public static void main(String[] args) throws IOException {
+
+    int numCharts = 4;
+
+    List<XYChart> charts = new ArrayList<XYChart>();
+
+    for (int i = 0; i < numCharts; i++) {
+      XYChart chart =
+          new XYChartBuilder().xAxisTitle("X").yAxisTitle("Y").width(600).height(400).build();
+      chart.getStyler().setYAxisMin(-10.0);
+      chart.getStyler().setYAxisMax(10.0);
+      XYSeries series = chart.addSeries("" + i, null, getRandomWalk(200));
+      series.setMarker(SeriesMarkers.NONE);
+      charts.add(chart);
+    }
+    new SwingWrapper<XYChart>(charts).displayChartMatrix();
+
+    BitmapEncoder.saveBitmap(charts, 2, 2, "./Sample_Chart_Matrix", BitmapEncoder.BitmapFormat.PNG);
+  }
+
+  /**
+   * Generates a set of random walk data
+   *
+   * @param numPoints
+   * @return
+   */
+  private static double[] getRandomWalk(int numPoints) {
+
+    double[] y = new double[numPoints];
+    y[0] = 0;
+    for (int i = 1; i < y.length; i++) {
+      y[i] = y[i - 1] + Math.random() - .5;
+    }
+    return y;
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/JavaFXDemo.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/JavaFXDemo.java
new file mode 100644
index 0000000000000000000000000000000000000000..6cc4c41a457c8a17e2c56df5500f07335648b945
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/JavaFXDemo.java
@@ -0,0 +1,30 @@
+package org.knowm.xchart.standalone;
+
+import javafx.application.Application;
+import javafx.embed.swing.SwingNode;
+import javafx.scene.Scene;
+import javafx.scene.layout.StackPane;
+import javafx.stage.Stage;
+import javax.swing.JPanel;
+import org.knowm.xchart.XChartPanel;
+import org.knowm.xchart.demo.charts.area.AreaChart01;
+
+/** Class showing how to integrate a chart into a JavaFX Stage */
+public class JavaFXDemo extends Application {
+
+  public static void main(String[] args) {
+
+    launch(args);
+  }
+
+  @Override
+  public void start(Stage stage) {
+
+    final SwingNode swingNode = new SwingNode();
+    JPanel chartPanel = new XChartPanel(new AreaChart01().getChart());
+    swingNode.setContent(chartPanel);
+    Scene scene = new Scene(new StackPane(swingNode), 640, 480);
+    stage.setScene(scene);
+    stage.show();
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/SwingDemo.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/SwingDemo.java
new file mode 100644
index 0000000000000000000000000000000000000000..7f98dd46926db23160f59866c4180b7cb0b7a10a
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/SwingDemo.java
@@ -0,0 +1,45 @@
+package org.knowm.xchart.standalone;
+
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.WindowConstants;
+import org.knowm.xchart.XChartPanel;
+import org.knowm.xchart.demo.charts.area.AreaChart01;
+
+/** Class showing how to integrate a chart into a Swing JFrame */
+public class SwingDemo {
+
+  /**
+   * Create the GUI and show it. For thread safety, this method should be invoked from the event
+   * dispatch thread.
+   */
+  private static void createAndShowGUI() {
+
+    // Create and set up the window.
+    JFrame frame = new JFrame("XChart Swing Demo");
+    frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
+
+    // Add content to the window.
+    JPanel chartPanel = new XChartPanel(new AreaChart01().getChart());
+    frame.add(chartPanel);
+
+    // Display the window.
+    frame.pack();
+    frame.setVisible(true);
+  }
+
+  public static void main(String[] args) {
+
+    // Schedule a job for the event dispatch thread:
+    // creating and showing this application's GUI.
+    javax.swing.SwingUtilities.invokeLater(
+        new Runnable() {
+
+          @Override
+          public void run() {
+
+            createAndShowGUI();
+          }
+        });
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/csv/Export2Columns.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/csv/Export2Columns.java
new file mode 100644
index 0000000000000000000000000000000000000000..9f3d60b111b407a728d4239d02567c0dc0b4dfd9
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/csv/Export2Columns.java
@@ -0,0 +1,27 @@
+package org.knowm.xchart.standalone.csv;
+
+import org.knowm.xchart.CSVExporter;
+import org.knowm.xchart.CSVImporter;
+import org.knowm.xchart.CSVImporter.DataOrientation;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+
+public class Export2Columns {
+
+  public static void main(String[] args) throws Exception {
+
+    // import chart from a folder containing CSV files
+    XYChart chart =
+        CSVImporter.getChartFromCSVDir("./CSV/CSVChartColumns/", DataOrientation.Columns, 600, 600);
+
+    // export a single series
+    CSVExporter.writeCSVColumns(
+        chart.getSeriesMap().get("series1"), "./CSV/CSVChartColumnsExport/");
+
+    // export all series
+    CSVExporter.writeCSVColumns(chart, "./CSV/CSVChartColumnsExport/");
+
+    // Show it
+    new SwingWrapper<XYChart>(chart).displayChart();
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/csv/Export2Rows.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/csv/Export2Rows.java
new file mode 100644
index 0000000000000000000000000000000000000000..bd6226917a66b3511fdf8c84df4ad0b576998ca9
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/csv/Export2Rows.java
@@ -0,0 +1,26 @@
+package org.knowm.xchart.standalone.csv;
+
+import org.knowm.xchart.CSVExporter;
+import org.knowm.xchart.CSVImporter;
+import org.knowm.xchart.CSVImporter.DataOrientation;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+
+public class Export2Rows {
+
+  public static void main(String[] args) throws Exception {
+
+    // import chart from a folder containing CSV files
+    XYChart chart =
+        CSVImporter.getChartFromCSVDir("./CSV/CSVChartRows/", DataOrientation.Rows, 600, 400);
+
+    // export a single series
+    CSVExporter.writeCSVRows(chart.getSeriesMap().get("series1"), "./CSV/CSVChartRowsExport/");
+
+    // export all series
+    CSVExporter.writeCSVRows(chart, "./CSV/CSVChartRowsExport/");
+
+    // Show it
+    new SwingWrapper<XYChart>(chart).displayChart();
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/MultiYAxisTest.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/MultiYAxisTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..de046e604876e232aec8cee50edea2a0d397840d
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/MultiYAxisTest.java
@@ -0,0 +1,44 @@
+package org.knowm.xchart.standalone.issues;
+
+import java.util.LinkedList;
+import java.util.List;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYChartBuilder;
+import org.knowm.xchart.XYSeries;
+import org.knowm.xchart.style.Styler;
+import org.knowm.xchart.style.markers.SeriesMarkers;
+
+public class MultiYAxisTest {
+
+  public static void main(String[] args) throws Exception {
+
+    List<Double> timeData = new LinkedList<>();
+    List<Double> th1Data = new LinkedList<>();
+    List<Double> th2Data = new LinkedList<>();
+
+    // Generate data
+    for (int i = 0; i < 20; i++) {
+      timeData.add(2.5 * i);
+      th1Data.add(10.1 * i);
+      th2Data.add((1.1 * i) * (1.1 * i));
+    }
+    XYChart c = new XYChartBuilder().title("Test Data").xAxisTitle("Time").build();
+    c.setYAxisGroupTitle(0, "A");
+    c.setYAxisGroupTitle(1, "B");
+    c.getStyler().setYAxisGroupPosition(1, Styler.YAxisPosition.Right);
+    c.getStyler().setLegendPosition(Styler.LegendPosition.InsideNE);
+
+    // series 1
+    XYSeries s1 = c.addSeries("th1", timeData, th1Data);
+    s1.setYAxisGroup(0);
+    s1.setMarker(SeriesMarkers.NONE);
+
+    // series 2
+    XYSeries s2 = c.addSeries("th2", timeData, th2Data);
+    s2.setYAxisGroup(1);
+    s2.setMarker(SeriesMarkers.NONE);
+
+    new SwingWrapper<>(c).displayChart();
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForExtremeEdgeCaseData.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForExtremeEdgeCaseData.java
new file mode 100644
index 0000000000000000000000000000000000000000..a0fecdba4ad95048987c17c9d6d289a4a11190c0
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForExtremeEdgeCaseData.java
@@ -0,0 +1,24 @@
+package org.knowm.xchart.standalone.issues;
+
+import java.io.IOException;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYChartBuilder;
+
+public class TestForExtremeEdgeCaseData {
+
+  public static void main(String[] args) throws IOException {
+
+    final XYChart chart = new XYChartBuilder().build();
+
+    final double[] x = {1, 2, 3};
+    // final double[] y = { 40.16064257028113, 40.16064257028115, Double.NaN };
+    // final double[] y = { 40.16064257028113, 40.16064257028115, Double.NEGATIVE_INFINITY };
+    // final double[] y = { 40.16064257028113, 40.16064257028115, Double.POSITIVE_INFINITY };
+    // final double[] y = { 40.16064257028113, 40.16064257028115, -Double.MAX_VALUE + 1e308 };
+    final double[] y = {40.16064257028113, 40.16064257028115, -1 * Double.MAX_VALUE};
+
+    chart.addSeries("Values", x, y);
+    new SwingWrapper(chart).displayChart();
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue1.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue1.java
new file mode 100644
index 0000000000000000000000000000000000000000..57194ff7c5172d5110395e47a9841eef0f24c96c
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue1.java
@@ -0,0 +1,37 @@
+package org.knowm.xchart.standalone.issues;
+
+import java.util.Arrays;
+import java.util.List;
+import org.knowm.xchart.BitmapEncoder;
+import org.knowm.xchart.BitmapEncoder.BitmapFormat;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.internal.chartpart.Chart;
+
+/** Creates a list of Charts and saves it as a PNG file. */
+public class TestForIssue1 {
+
+  public static void main(String[] args) throws Exception {
+
+    List<Chart> charts =
+        Arrays.asList(
+            new Chart[] {
+              createChart("chart1", new double[] {2.0, 1.0, 0.0}),
+              createChart("chart2", new double[] {3.0, 4.0, 0.0}),
+              createChart("chart3", new double[] {4.0, 1.5, 0.0}),
+              createChart("chart4", new double[] {2.0, 3.0, 0.0}),
+              createChart("chart5", new double[] {4.0, 1.0, 0.0}),
+              createChart("chart6", new double[] {5.0, 2.0, 0.0})
+            });
+
+    BitmapEncoder.saveBitmap(charts, 2, 3, "./Sample_Charts", BitmapFormat.PNG);
+  }
+
+  private static XYChart createChart(String title, double[] yData) {
+    XYChart chart = new XYChart(300, 200);
+    chart.setTitle(title);
+    chart.setXAxisTitle("X");
+    chart.setXAxisTitle("Y");
+    chart.addSeries("y(x)", null, yData);
+    return chart;
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue106.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue106.java
new file mode 100644
index 0000000000000000000000000000000000000000..85a620c4a0c6134065e966cac49b1a228a780baf
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue106.java
@@ -0,0 +1,50 @@
+package org.knowm.xchart.standalone.issues;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.util.ArrayList;
+import java.util.List;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.demo.charts.area.AreaChart03;
+import org.knowm.xchart.style.Styler;
+
+public class TestForIssue106 {
+
+  public static void main(String[] args) {
+
+    ExampleChart<XYChart> alc = new AreaChart03();
+    List<XYChart> charts = new ArrayList<XYChart>();
+    {
+      XYChart chart = alc.getChart();
+      chart.setTitle("Default data labels");
+      chart.getStyler().setToolTipsEnabled(true);
+      charts.add(chart);
+    }
+    {
+      XYChart chart = alc.getChart();
+      chart.setTitle("No data label");
+      charts.add(chart);
+    }
+    {
+      // current default
+      XYChart chart = alc.getChart();
+      chart.getStyler().setToolTipsEnabled(true);
+      chart.getStyler().setToolTipBackgroundColor(Color.RED);
+      chart.getStyler().setToolTipType(Styler.ToolTipType.yLabels);
+      chart.setTitle("Red background");
+      charts.add(chart);
+    }
+    {
+      XYChart chart = alc.getChart();
+      chart.getStyler().setToolTipsEnabled(true);
+      chart.getStyler().setToolTipBorderColor(Color.BLUE);
+      chart.getStyler().setToolTipFont(new Font(Font.MONOSPACED, Font.PLAIN, 20));
+      chart.setTitle("Blue and custom Font");
+      charts.add(chart);
+    }
+
+    new SwingWrapper<XYChart>(charts).displayChartMatrix();
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue111.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue111.java
new file mode 100644
index 0000000000000000000000000000000000000000..f7f99d44620eff87361f1a12e5422693248d00b4
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue111.java
@@ -0,0 +1,21 @@
+package org.knowm.xchart.standalone.issues;
+
+import org.knowm.xchart.CategoryChart;
+import org.knowm.xchart.CategoryChartBuilder;
+import org.knowm.xchart.SwingWrapper;
+
+public class TestForIssue111 {
+
+  public static void main(String[] args) {
+
+    int[] x = new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13};
+    int[] y = new int[] {1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1};
+    // int[] x = new int[] { 0, 1, 2, 3, 4, 5, 6, 7 };
+    // int[] y = new int[] { 1, 0, 1, 0, 1, 0, 0, 0 };
+
+    CategoryChart chart = new CategoryChartBuilder().width(640).height(480).build();
+    chart.addSeries("test", x, y);
+    chart.getStyler().setLegendVisible(false);
+    new SwingWrapper(chart).displayChart();
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue127.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue127.java
new file mode 100644
index 0000000000000000000000000000000000000000..df8143ffe16745deee241c5d8944f3d99b6218b4
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue127.java
@@ -0,0 +1,51 @@
+package org.knowm.xchart.standalone.issues;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYChartBuilder;
+
+public class TestForIssue127 {
+
+  public static void main(String[] args) throws InterruptedException, ParseException {
+
+    int[] x = new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13};
+    int[] y = new int[] {1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1};
+    // int[] x = new int[] { 0, 1, 2, 3, 4, 5, 6, 7 };
+    // int[] y = new int[] { 1, 0, 1, 0, 1, 0, 0, 0 };
+
+    XYChart chart =
+        new XYChartBuilder().width(640).height(480).xAxisTitle("x").yAxisTitle("y").build();
+    chart.setTitle("TEst");
+    chart.getStyler().setLegendVisible(false);
+    new SwingWrapper(chart).displayChart();
+    Thread.sleep(1000);
+
+    chart.addSeries("test", x, y);
+    new SwingWrapper(chart).displayChart();
+    Thread.sleep(1000);
+
+    chart.removeSeries("test");
+    new SwingWrapper(chart).displayChart();
+    Thread.sleep(1000);
+
+    DateFormat sdf = new SimpleDateFormat("dd-HH-mm");
+
+    List<Date> xDate = new ArrayList<Date>();
+    xDate.add(sdf.parse("25-01-00"));
+    xDate.add(sdf.parse("25-02-00"));
+    xDate.add(sdf.parse("25-03-00"));
+    List<Double> yDate = new ArrayList<Double>();
+    yDate.add(2d);
+    yDate.add(3d);
+    yDate.add(5d);
+    chart.addSeries("test2", xDate, yDate);
+    new SwingWrapper(chart).displayChart();
+    Thread.sleep(1000);
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue139.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue139.java
new file mode 100644
index 0000000000000000000000000000000000000000..5db074fca847f555e7ef4f3b103c41a364bfe22d
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue139.java
@@ -0,0 +1,30 @@
+package org.knowm.xchart.standalone.issues;
+
+import org.knowm.xchart.CategoryChart;
+import org.knowm.xchart.CategoryChartBuilder;
+import org.knowm.xchart.CategorySeries.CategorySeriesRenderStyle;
+import org.knowm.xchart.SwingWrapper;
+
+public class TestForIssue139 {
+
+  public static void main(String[] args) {
+
+    int[] x = new int[] {0, 1, 2, 3, 4};
+    int[] a = new int[] {1, 3, 1, 2, 1};
+    int[] b = new int[] {2, 1, 1, 2, 2};
+    int[] c = new int[] {1, 1, 2, 3, 3};
+
+    CategoryChart chart = new CategoryChartBuilder().width(640).height(480).build();
+
+    chart.getStyler().setLegendVisible(false);
+    chart.getStyler().setDefaultSeriesRenderStyle(CategorySeriesRenderStyle.Stick);
+    // chart.getStyler().setBarsOverlapped(true);
+    chart.getStyler().setAvailableSpaceFill(.25);
+
+    chart.addSeries("A", x, a);
+    chart.addSeries("B", x, b);
+    chart.addSeries("C", x, c);
+
+    new SwingWrapper(chart).displayChart();
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue151.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue151.java
new file mode 100644
index 0000000000000000000000000000000000000000..c654d1900265a9b87d313989d777a0765a4691e6
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue151.java
@@ -0,0 +1,27 @@
+package org.knowm.xchart.standalone.issues;
+
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYChartBuilder;
+
+public class TestForIssue151 {
+
+  public static void main(String[] args) {
+
+    // Create Chart
+    XYChart chart = new XYChartBuilder().width(600).height(400).build();
+
+    // Customize Chart
+
+    // Series
+    double[] xData1 = new double[] {0, 1, 2, 3, 4, 5, 6, 10, 11, 12, 13, 14, 15};
+    double[] yData1 = new double[] {106, 44, 26, 10, 11, 19, 25, Double.NaN, 30, 21, 36, 32, 30};
+    double[] xData2 = new double[] {6, 7, 8, 9, 10, 11};
+    double[] yData2 = new double[] {25, 54, 43, 56, 33, 30};
+
+    chart.addSeries("A", xData1, yData1);
+    chart.addSeries("B", xData2, yData2);
+
+    new SwingWrapper<XYChart>(chart).displayChart();
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue158.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue158.java
new file mode 100644
index 0000000000000000000000000000000000000000..08715029faf3dc1d2037f32cd5b0cfd21408f400
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue158.java
@@ -0,0 +1,20 @@
+package org.knowm.xchart.standalone.issues;
+
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYSeries;
+
+public class TestForIssue158 {
+
+  public static void main(String[] args) throws Exception {
+
+    double[] xData = new double[] {0.0, 1.0, 2.0};
+    double[] yData = new double[] {2.0, 1.0, 0.0};
+
+    XYChart chart = new XYChart(500, 200);
+    XYSeries xySeries = chart.addSeries("Sample Chart", xData, yData);
+    xySeries.setEnabled(false);
+
+    new SwingWrapper(chart).displayChart();
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue159.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue159.java
new file mode 100644
index 0000000000000000000000000000000000000000..5f25dc8b9e1bbeb4b10687231534e60d7a9135e6
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue159.java
@@ -0,0 +1,73 @@
+package org.knowm.xchart.standalone.issues;
+
+import java.awt.Color;
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYChartBuilder;
+import org.knowm.xchart.XYSeries;
+import org.knowm.xchart.style.XYStyler;
+import org.knowm.xchart.style.colors.XChartSeriesColors;
+import org.knowm.xchart.style.lines.SeriesLines;
+import org.knowm.xchart.style.markers.SeriesMarkers;
+
+public class TestForIssue159 {
+
+  public static void main(String[] args) throws Exception {
+
+    XYChart chart =
+        new XYChartBuilder()
+            .width(800)
+            .height(600)
+            .title("LineChart")
+            .xAxisTitle("X")
+            .yAxisTitle("Y")
+            .build();
+
+    XYStyler styler = chart.getStyler();
+    styler.setChartTitleVisible(false);
+    styler.setLegendVisible(false);
+    //    styler.chartBackgroundColor = Color.white;
+    //
+    //    styler.axisTicksLineVisible = false;
+    //    styler.plotGridVerticalLinesVisible = false;
+
+    styler.setXAxisTitleVisible(false);
+    styler.setYAxisTitleVisible(false);
+
+    styler.setAxisTickLabelsColor(Color.darkGray);
+    styler.setDatePattern("MM-dd");
+    styler.setYAxisMin(2.0);
+    styler.setYAxisMax(10.0);
+    styler.setXAxisTickMarkSpacingHint(200);
+
+    List<Date> xData = new ArrayList<>();
+    List<Integer> yData = new ArrayList<>();
+
+    yData.add(1);
+    yData.add(2);
+    yData.add(4);
+    yData.add(8);
+    yData.add(10);
+
+    LocalDate localDate = LocalDate.now();
+    xData.add(Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant()));
+    xData.add(Date.from(localDate.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant()));
+    xData.add(Date.from(localDate.plusDays(2).atStartOfDay(ZoneId.systemDefault()).toInstant()));
+    xData.add(Date.from(localDate.plusDays(3).atStartOfDay(ZoneId.systemDefault()).toInstant()));
+    xData.add(Date.from(localDate.plusDays(4).atStartOfDay(ZoneId.systemDefault()).toInstant()));
+
+    // Series
+    XYSeries series = chart.addSeries("My Data", xData, yData);
+    series.setLineColor(XChartSeriesColors.RED);
+    series.setMarkerColor(Color.RED);
+    series.setMarker(SeriesMarkers.CIRCLE);
+    series.setLineStyle(SeriesLines.SOLID);
+
+    new SwingWrapper(chart).displayChart();
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue167.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue167.java
new file mode 100644
index 0000000000000000000000000000000000000000..ab4d9ecc74afe2a6dbc1cf1ce135159ddef8e5d4
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue167.java
@@ -0,0 +1,104 @@
+package org.knowm.xchart.standalone.issues;
+
+import java.util.Arrays;
+import org.knowm.xchart.CategoryChart;
+import org.knowm.xchart.CategoryChartBuilder;
+import org.knowm.xchart.CategorySeries;
+import org.knowm.xchart.CategorySeries.CategorySeriesRenderStyle;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.demo.charts.ExampleChart;
+
+public class TestForIssue167 implements ExampleChart<CategoryChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<CategoryChart> exampleChart = new TestForIssue167();
+    CategoryChart chart = exampleChart.getChart();
+    CategoryChart chart2 = ((TestForIssue167) exampleChart).getChart2();
+    new SwingWrapper<CategoryChart>(chart).displayChart();
+    new SwingWrapper<CategoryChart>(chart2).displayChart();
+  }
+
+  @Override
+  public CategoryChart getChart() {
+
+    // Create Chart
+    CategoryChart chart =
+        new CategoryChartBuilder()
+            .width(800)
+            .height(600)
+            .title("CategorySeriesRenderStyle-bug")
+            .xAxisTitle("Year")
+            .yAxisTitle("Data")
+            .build();
+
+    // Customize Chart
+    chart.getStyler().setLegendVisible(false);
+    chart.getStyler().setLabelsVisible(true);
+    chart.getStyler().setOverlapped(true);
+
+    // This is the setting that causes troubles. When setting this to .Bar
+    // it works just fine. Draws negative values but doesn't leave blank to
+    // nulls.
+    chart.getStyler().setDefaultSeriesRenderStyle(CategorySeriesRenderStyle.Line);
+
+    // Series
+    String[] years = new String[] {"2000", "2001", "2002", "2003", "2004"};
+    chart.addSeries(
+        "data 1", Arrays.asList(years), Arrays.asList(100.0, 110.0, 120.0, 130.0, 140.0));
+    chart.addSeries("data 2", Arrays.asList(years), Arrays.asList(50.0, 60.0, 70.0, null, 90.0));
+    chart.addSeries(
+        "data 3", Arrays.asList(years), Arrays.asList(-50.0, -60.0, -70.0, null, -90.0));
+    chart.addSeries(
+        "data 4", Arrays.asList(years), Arrays.asList(-100.0, -110.0, -120.0, -130.0, -140.0));
+
+    return chart;
+  }
+
+  public CategoryChart getChart2() {
+
+    // Create Chart
+    CategoryChart chart =
+        new CategoryChartBuilder()
+            .width(800)
+            .height(600)
+            .title("CategorySeriesRenderStyle-bug")
+            .xAxisTitle("Year")
+            .yAxisTitle("Data")
+            .build();
+
+    // Customize Chart
+    chart.getStyler().setLegendVisible(false);
+    chart.getStyler().setLabelsVisible(true);
+    chart.getStyler().setOverlapped(true);
+
+    // Series
+    String[] years = new String[] {"2000", "2001", "2002", "2003", "2004"};
+    CategorySeries data1 =
+        chart.addSeries(
+            "data 1", Arrays.asList(years), Arrays.asList(100.0, 110.0, 120.0, 130.0, 140.0));
+    CategorySeries data2 =
+        chart.addSeries(
+            "data 2", Arrays.asList(years), Arrays.asList(50.0, 60.0, 70.0, null, 90.0));
+    CategorySeries data3 =
+        chart.addSeries(
+            "data 3", Arrays.asList(years), Arrays.asList(-50.0, -60.0, -70.0, null, -90.0));
+    CategorySeries data4 =
+        chart.addSeries(
+            "data 4", Arrays.asList(years), Arrays.asList(-100.0, -110.0, -120.0, -130.0, -140.0));
+
+    // Now rendering is set individually to line per series. Draws nulls but
+    // all negatives are drawn as zero.
+    data1.setChartCategorySeriesRenderStyle(CategorySeriesRenderStyle.Line);
+    data2.setChartCategorySeriesRenderStyle(CategorySeriesRenderStyle.Line);
+    data3.setChartCategorySeriesRenderStyle(CategorySeriesRenderStyle.Line);
+    data4.setChartCategorySeriesRenderStyle(CategorySeriesRenderStyle.Line);
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName();
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue181.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue181.java
new file mode 100644
index 0000000000000000000000000000000000000000..1ac2346845e3d51da1b6214975294853dabc4322
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue181.java
@@ -0,0 +1,63 @@
+package org.knowm.xchart.standalone.issues;
+
+import java.awt.Color;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Random;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYChartBuilder;
+import org.knowm.xchart.XYSeries;
+import org.knowm.xchart.style.Styler;
+import org.knowm.xchart.style.lines.SeriesLines;
+import org.knowm.xchart.style.markers.Marker;
+import org.knowm.xchart.style.markers.SeriesMarkers;
+
+public class TestForIssue181 {
+
+  public static void main(String[] args) {
+
+    // Create Chart
+    XYChart chart =
+        new XYChartBuilder().width(500).height(350).theme(Styler.ChartTheme.Matlab).build();
+
+    // Series
+    List<Double> xData = new LinkedList<Double>();
+    List<Double> yData = new LinkedList<Double>();
+
+    Random random = new Random();
+    int size = 1000;
+    for (int i = 0; i < size; i++) {
+      xData.add(5 * random.nextGaussian());
+      yData.add(5 * random.nextGaussian());
+    }
+    chart.addSeries("Gaussian Blob", xData, yData);
+
+    XYSeries vertical = chart.addSeries("vertical", new double[] {5, 5}, new double[] {0, 10});
+    vertical.setShowInLegend(false);
+    vertical.setXYSeriesRenderStyle(XYSeries.XYSeriesRenderStyle.Line);
+    vertical.setLineStyle(SeriesLines.SOLID);
+    XYSeries horizontal = chart.addSeries("horizontal", new double[] {0, 10}, new double[] {5, 5});
+    horizontal.setShowInLegend(false);
+    horizontal.setXYSeriesRenderStyle(XYSeries.XYSeriesRenderStyle.Line);
+    horizontal.setLineStyle(SeriesLines.SOLID);
+
+    // Customize Chart
+    Marker[] markers = {
+      SeriesMarkers.SQUARE, SeriesMarkers.SQUARE, SeriesMarkers.DIAMOND, SeriesMarkers.DIAMOND
+    };
+    Color[] colors = {Color.GRAY, Color.BLUE, Color.RED, Color.RED};
+
+    chart.getStyler().setDefaultSeriesRenderStyle(XYSeries.XYSeriesRenderStyle.Scatter);
+    chart.getStyler().setLegendPosition(Styler.LegendPosition.OutsideE);
+    chart.getStyler().setMarkerSize(5);
+    chart.getStyler().setAxisTicksVisible(true);
+    chart.getStyler().setXAxisMin(0.0);
+    chart.getStyler().setYAxisMin(0.0);
+    chart.getStyler().setSeriesMarkers(markers);
+    chart.getStyler().setSeriesColors(colors);
+    chart.getStyler().setPlotGridLinesVisible(false);
+    chart.getStyler().setPlotContentSize(1.0);
+    new SwingWrapper<XYChart>(chart).displayChart();
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue189_1.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue189_1.java
new file mode 100644
index 0000000000000000000000000000000000000000..fd31a51e2d8f4d2146b8dd45809340cf929c8145
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue189_1.java
@@ -0,0 +1,47 @@
+package org.knowm.xchart.standalone.issues;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.knowm.xchart.RadarChart;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.demo.charts.radar.RadarChart01;
+import org.knowm.xchart.style.RadarStyler;
+
+/** Create a Chart matrix */
+public class TestForIssue189_1 {
+
+  public static void main(String[] args) {
+
+    ExampleChart<RadarChart> alc = new RadarChart01();
+    List<RadarChart> charts = new ArrayList<>();
+    {
+      RadarChart chart = alc.getChart();
+      chart.setTitle("Default radar chart");
+      charts.add(chart);
+    }
+    {
+      RadarChart chart = alc.getChart();
+      chart.setTitle("Radar chart with circle rendering");
+      chart.getStyler().setRadarRenderStyle(RadarStyler.RadarRenderStyle.Circle);
+      charts.add(chart);
+    }
+    {
+      // current default
+      RadarChart chart = alc.getChart();
+      chart.setTitle("Radar chart with 3 variables and start angle");
+      chart.getStyler().setToolTipsEnabled(true);
+      chart.getStyler().setStartAngleInDegrees(45);
+      chart.setRadiiLabels(new String[] {"Sales", "Marketing", "Development"});
+      charts.add(chart);
+    }
+    {
+      RadarChart chart = alc.getChart();
+      chart.setTitle("Radar chart with non circular rendering");
+      chart.getStyler().setCircular(false);
+      charts.add(chart);
+    }
+
+    new SwingWrapper<RadarChart>(charts).displayChartMatrix();
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue205.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue205.java
new file mode 100644
index 0000000000000000000000000000000000000000..444b97017872652f135623f43ec470d6cc69cf60
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue205.java
@@ -0,0 +1,50 @@
+package org.knowm.xchart.standalone.issues;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import org.knowm.xchart.CategoryChart;
+import org.knowm.xchart.CategoryChartBuilder;
+import org.knowm.xchart.Histogram;
+import org.knowm.xchart.SwingWrapper;
+
+public class TestForIssue205 {
+
+  public static void main(String[] args) throws IOException {
+
+    List<Double> myData = new ArrayList();
+    myData.add(10.0);
+    myData.add(20.0);
+    myData.add(10.0);
+    myData.add(30.0);
+    myData.add(40.0);
+    myData.add(20.0);
+    myData.add(30.0);
+    myData.add(10.0);
+    myData.add(40.0);
+    myData.add(50.0);
+    myData.add(10.0);
+    myData.add(10.0);
+
+    int numBins = 3;
+    Histogram histogram = new Histogram(myData, numBins);
+    CategoryChart chart =
+        new CategoryChartBuilder()
+            .width(800)
+            .height(600)
+            .title("Xchart Histogram")
+            .xAxisTitle("Mean")
+            .yAxisTitle("Count")
+            .build();
+    chart.getStyler().setAvailableSpaceFill(.96);
+    chart.getStyler().setOverlapped(false);
+    chart.addSeries("histogram ", histogram.getxAxisData(), histogram.getyAxisData());
+    //    BitmapEncoder.saveBitmap(chart, "\\MyHistogram", BitmapFormat.JPG);
+    new SwingWrapper<CategoryChart>(chart).displayChart();
+
+    System.out.println(
+        "Bins :" + histogram.getxAxisData()); // Bins :[16.666666666666668, 30.000000000000004,
+    // 43.333333333333336]
+    System.out.println("frequency :" + histogram.getyAxisData()); // frequency :[7.0, 2.0, 3.0]
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue210.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue210.java
new file mode 100644
index 0000000000000000000000000000000000000000..33928ec94fcd96cf242f3b33312183d8ff14a36b
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue210.java
@@ -0,0 +1,90 @@
+package org.knowm.xchart.standalone.issues;
+
+import java.util.ArrayList;
+import org.knowm.xchart.DialChart;
+import org.knowm.xchart.DialChartBuilder;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.demo.charts.ExampleChart;
+
+/**
+ * Dial Chart
+ *
+ * <p>Demonstrates the following:
+ *
+ * <ul>
+ *   <li>Dial Chart
+ *   <li>DialChartBuilder
+ */
+public class TestForIssue210 implements ExampleChart<DialChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<DialChart> exampleChart = new TestForIssue210();
+    ArrayList<DialChart> charts = new ArrayList<DialChart>();
+    {
+      DialChart chart = exampleChart.getChart();
+      chart.setTitle("Dial chart");
+      charts.add(chart);
+    }
+    {
+      DialChart chart = exampleChart.getChart();
+      chart.setTitle("Dial chart without green&red parts");
+      chart.getStyler().setUpperFrom(-1);
+      chart.getStyler().setLowerFrom(-1);
+
+      charts.add(chart);
+    }
+    {
+      DialChart chart = exampleChart.getChart();
+      chart.setTitle("Dial chart with custom ticks&labels");
+      chart.getStyler().setAxisTickValues(new double[] {.33, .45, .79});
+      chart.getStyler().setAxisTickLabels(new String[] {"min", "average", "max"});
+      charts.add(chart);
+    }
+    {
+      DialChart chart = exampleChart.getChart();
+      chart.setTitle("Dial chart with custom arrow");
+      chart.getStyler().setArrowLengthPercentage(1.05);
+      chart.getStyler().setArrowArcAngle(90);
+      chart.getStyler().setArrowArcPercentage(.03);
+      charts.add(chart);
+    }
+    {
+      DialChart chart = exampleChart.getChart();
+      chart.setTitle("Full circle dial chart");
+      chart.getStyler().setArcAngle(360);
+      // chart.getStyler().setDonutThickness(1);
+      chart.getStyler().setAxisTickLabelsVisible(false);
+      charts.add(chart);
+    }
+    {
+      DialChart chart = exampleChart.getChart();
+      chart.setTitle("Full circle dial chart without donut");
+      chart.getStyler().setArcAngle(360);
+      chart.getStyler().setDonutThickness(1);
+      chart.getStyler().setAxisTickLabelsVisible(false);
+      charts.add(chart);
+    }
+    new SwingWrapper<DialChart>(charts).displayChartMatrix();
+  }
+
+  @Override
+  public DialChart getChart() {
+
+    // Create Chart
+    DialChart chart = new DialChartBuilder().width(480).height(400).title("Dial Chart").build();
+
+    // Series
+    chart.addSeries("Rate", 0.9381, "93.81 %");
+    chart.getStyler().setToolTipsEnabled(true);
+    chart.getStyler().setLegendVisible(false);
+
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName();
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue240.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue240.java
new file mode 100644
index 0000000000000000000000000000000000000000..f4f8521572cb2fd79127a6943f3325b70af3c578
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue240.java
@@ -0,0 +1,110 @@
+package org.knowm.xchart.standalone.issues;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYChartBuilder;
+import org.knowm.xchart.XYSeries;
+import org.knowm.xchart.XYSeries.XYSeriesRenderStyle;
+
+public class TestForIssue240 {
+
+  private static final int WIDTH = 600;
+  private static final int HEIGHT = 500;
+
+  private static double[] xData = new double[] {1, 2, 3, 6, 7, 9, 12, 15, 17, 20};
+  private static double[] yData = new double[] {-5.6, 15, 36, 27, 89, 74, 25, 16, 14, 46};
+
+  public static void main(String[] args) throws IOException {
+
+    List<XYChart> charts = new ArrayList<>();
+    charts.add(getLineChart());
+    charts.add(getSmoothedLineChart());
+    charts.add(getSmoothedAreaChart());
+    charts.add(getLineAndSmoothedAreaChart());
+    // show
+    new SwingWrapper<>(charts).displayChartMatrix();
+  }
+
+  private static XYChart getLineChart() {
+    // Create Chart
+    XYChart chart =
+        new XYChartBuilder()
+            .width(WIDTH)
+            .height(HEIGHT)
+            .title("Line Chart")
+            .xAxisTitle("X")
+            .yAxisTitle("Y")
+            .build();
+
+    chart.getStyler().setLegendVisible(false);
+
+    chart.addSeries("line", xData, yData);
+    return chart;
+  }
+
+  private static XYChart getSmoothedLineChart() {
+    // Create Chart
+    XYChart chart =
+        new XYChartBuilder()
+            .width(WIDTH)
+            .height(HEIGHT)
+            .title("Smoothed Line Chart")
+            .xAxisTitle("X")
+            .yAxisTitle("Y")
+            .build();
+
+    chart.getStyler().setLegendVisible(false);
+
+    XYSeries series = chart.addSeries("smoothed line", xData, yData);
+    series.setSmooth(true);
+    return chart;
+  }
+
+  private static XYChart getSmoothedAreaChart() {
+    // Create Chart
+    XYChart chart =
+        new XYChartBuilder()
+            .width(WIDTH)
+            .height(HEIGHT)
+            .title("Smoothed Area Chart")
+            .xAxisTitle("X")
+            .yAxisTitle("Y")
+            .build();
+
+    chart.getStyler().setLegendVisible(false);
+
+    XYSeries series = chart.addSeries("smoothed area", xData, yData);
+    series.setSmooth(true);
+    series.setXYSeriesRenderStyle(XYSeriesRenderStyle.Area);
+    return chart;
+  }
+
+  private static XYChart getLineAndSmoothedAreaChart() {
+    // Create Chart
+    XYChart chart =
+        new XYChartBuilder()
+            .width(WIDTH)
+            .height(HEIGHT)
+            .title("Line And Smoothed Area Chart")
+            .xAxisTitle("X")
+            .yAxisTitle("Y")
+            .build();
+
+    chart.getStyler().setLegendVisible(true);
+
+    XYSeries series1 =
+        chart.addSeries(
+            "smoothed area",
+            xData,
+            new double[] {10, 2.5, 5.6, 7.8, Double.NaN, -17, 58, 39, Double.NaN, 20});
+    series1.setSmooth(true);
+    series1.setXYSeriesRenderStyle(XYSeriesRenderStyle.Area);
+
+    XYSeries series2 = chart.addSeries("line", xData, yData);
+    series2.setSmooth(false);
+    return chart;
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue243.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue243.java
new file mode 100644
index 0000000000000000000000000000000000000000..9544c972a44689b275294edc2ec6cf25f700e668
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue243.java
@@ -0,0 +1,23 @@
+package org.knowm.xchart.standalone.issues;
+
+import org.knowm.xchart.QuickChart;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.style.markers.Circle;
+
+public class TestForIssue243 {
+
+  public static void main(String[] args) throws Exception {
+
+    double[] xData = new double[] {1.0, 2.0};
+    double[] yData = new double[] {Double.NaN, 1.0};
+
+    // Create Chart
+    XYChart chart = QuickChart.getChart("Sample Chart", "X", "Y", "1", xData, yData);
+
+    chart.getSeriesMap().get("1").setMarker(new Circle());
+
+    // Show it
+    new SwingWrapper(chart).displayChart();
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue244.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue244.java
new file mode 100644
index 0000000000000000000000000000000000000000..6843dd2e8b3dbbd4b6f968006fa893befafa5cee
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue244.java
@@ -0,0 +1,140 @@
+package org.knowm.xchart.standalone.issues;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYChartBuilder;
+import org.knowm.xchart.internal.chartpart.Chart;
+import org.knowm.xchart.internal.series.Series;
+import org.knowm.xchart.style.AxesChartStyler;
+import org.knowm.xchart.style.Styler.LegendPosition;
+import org.knowm.xchart.style.Styler.YAxisPosition;
+
+public class TestForIssue244 {
+
+  static final int WIDTH = 465;
+  static final int HEIGHT = 320;
+
+  public static void main(String[] args) {
+
+    List<Chart> charts = new ArrayList<Chart>();
+    {
+      Chart chart = getLineChart();
+      chart.setTitle("Default axis");
+      charts.add(chart);
+    }
+    {
+      Chart chart = getLineChart();
+      chart.setTitle("sin(x) on second axis");
+      Series series = (Series) chart.getSeriesMap().get("y=sin(x)");
+      series.setYAxisGroup(1);
+      chart.setYAxisGroupTitle(1, "sin(x) [-1, 1]");
+      chart.setYAxisGroupTitle(0, "cos(x) [-10, 10]");
+      chart.getStyler().setYAxisGroupPosition(1, YAxisPosition.Right);
+      charts.add(chart);
+    }
+
+    {
+      Chart chart = getLineChart();
+      chart.setTitle("2 axis, default y max & y min");
+      Series series = (Series) chart.getSeriesMap().get("y=sin(x)");
+      series.setYAxisGroup(1);
+      chart.setYAxisGroupTitle(1, "sin(x) [-1, 1]");
+      chart.setYAxisGroupTitle(0, "cos(x) [-10, 10]");
+      chart.getStyler().setYAxisGroupPosition(1, YAxisPosition.Right);
+
+      AxesChartStyler styler = (AxesChartStyler) chart.getStyler();
+      styler.setYAxisMax(20.0);
+      styler.setYAxisMin(-20.0);
+
+      charts.add(chart);
+    }
+    {
+      Chart chart = getLineChart();
+      chart.setTitle("2 axis, max on group 0");
+      Series series = (Series) chart.getSeriesMap().get("y=sin(x)");
+      series.setYAxisGroup(1);
+      chart.setYAxisGroupTitle(1, "sin(x) [-1, 1]");
+      chart.setYAxisGroupTitle(0, "cos(x) [-10, 10]");
+      chart.getStyler().setYAxisGroupPosition(1, YAxisPosition.Right);
+
+      AxesChartStyler styler = (AxesChartStyler) chart.getStyler();
+      styler.setYAxisMax(0, 20.0);
+      styler.setYAxisMin(0, -20.0);
+
+      charts.add(chart);
+    }
+
+    {
+      Chart chart = getLineChart();
+      chart.setTitle("2 axis, max on group 0, 1");
+      Series series = (Series) chart.getSeriesMap().get("y=sin(x)");
+      series.setYAxisGroup(1);
+      chart.setYAxisGroupTitle(1, "sin(x) [-1, 1]");
+      chart.setYAxisGroupTitle(0, "cos(x) [-10, 10]");
+      chart.getStyler().setYAxisGroupPosition(1, YAxisPosition.Right);
+
+      AxesChartStyler styler = (AxesChartStyler) chart.getStyler();
+      styler.setYAxisMax(0, 20.0);
+      styler.setYAxisMin(0, -20.0);
+      styler.setYAxisMax(1, 2.0);
+      styler.setYAxisMin(1, -2.0);
+
+      charts.add(chart);
+    }
+
+    {
+      Chart chart = getLineChart();
+      chart.setTitle("2 axis, max on group 0, 1, and default max");
+      Series series = (Series) chart.getSeriesMap().get("y=sin(x)");
+      series.setYAxisGroup(1);
+      chart.setYAxisGroupTitle(1, "sin(x) [-1, 1]");
+      chart.setYAxisGroupTitle(0, "cos(x) [-10, 10]");
+      chart.getStyler().setYAxisGroupPosition(1, YAxisPosition.Right);
+
+      AxesChartStyler styler = (AxesChartStyler) chart.getStyler();
+      // these 2 lines will be overwritten by group max settings
+      styler.setYAxisMax(100.0);
+      styler.setYAxisMin(-100.0);
+
+      styler.setYAxisMax(0, 20.0);
+      styler.setYAxisMin(0, -20.0);
+      styler.setYAxisMax(1, 2.0);
+      styler.setYAxisMin(1, -2.0);
+
+      charts.add(chart);
+    }
+
+    new SwingWrapper(charts).displayChartMatrix();
+  }
+
+  static Chart getLineChart() {
+
+    XYChart chart =
+        new XYChartBuilder().width(WIDTH).height(HEIGHT).xAxisTitle("X").yAxisTitle("Y").build();
+
+    // Customize Chart
+    chart.getStyler().setToolTipsEnabled(true);
+    chart.getStyler().setLegendPosition(LegendPosition.InsideNW);
+    // generates sine data
+    int size = 30;
+    List<Integer> xData = new ArrayList<Integer>();
+    List<Double> yData = new ArrayList<Double>();
+    List<Integer> xData2 = new ArrayList<Integer>();
+    List<Double> yData2 = new ArrayList<Double>();
+    for (int i = 0; i <= size; i++) {
+      double radians = (Math.PI / (size / 2) * i);
+      int x = i - size / 2;
+      xData.add(x);
+      yData.add(-1 * Math.sin(radians));
+      xData2.add(x);
+      yData2.add(-10 * Math.cos(radians));
+    }
+
+    // Series
+    chart.addSeries("y=sin(x)", xData, yData);
+    chart.addSeries("y=cos(x)", xData2, yData2);
+    return chart;
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue249.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue249.java
new file mode 100644
index 0000000000000000000000000000000000000000..3bc1b70e132b2f0873b9c6cf5d362f1cf1bad8c3
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue249.java
@@ -0,0 +1,64 @@
+package org.knowm.xchart.standalone.issues;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.knowm.xchart.CategoryChart;
+import org.knowm.xchart.CategoryChartBuilder;
+import org.knowm.xchart.CategorySeries;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.style.Styler;
+
+/**
+ * Logarithmic Y-Axis Demonstrates the following:
+ *
+ * <ul>
+ *   <li>Logarithmic Y-Axis
+ *   <li>Building a Chart with ChartBuilder
+ *   <li>Place legend at Inside-NW position
+ */
+public class TestForIssue249 {
+
+  public static void main(String[] args) {
+
+    TestForIssue249 exampleChart = new TestForIssue249();
+    CategoryChart chart = exampleChart.getChart();
+    new SwingWrapper<CategoryChart>(chart).displayChart();
+  }
+
+  public CategoryChart getChart() {
+
+    // generates Log data
+    List<Integer> xData = new ArrayList<Integer>();
+    List<Double> yData = new ArrayList<Double>();
+    for (int i = -3; i <= 3; i++) {
+      xData.add(i);
+      yData.add(Math.pow(10, i));
+    }
+
+    // Create Chart
+    //    XYChart chart = new XYChartBuilder().width(800).height(600).title("Powers of
+    // Ten").xAxisTitle("Power").yAxisTitle("Value").build();
+    CategoryChart chart =
+        new CategoryChartBuilder()
+            .width(800)
+            .height(600)
+            .title("Powers of Ten")
+            .xAxisTitle("Power")
+            .yAxisTitle("Value")
+            .build();
+
+    // Customize Chart
+    chart.getStyler().setChartTitleVisible(true);
+    chart.getStyler().setLegendPosition(Styler.LegendPosition.InsideNW);
+    chart.getStyler().setYAxisLogarithmic(true);
+
+    chart.getStyler().setYAxisMin(0.001);
+    chart.getStyler().setYAxisMax(1000.0);
+
+    // Series
+    CategorySeries series = chart.addSeries("10^x", xData, yData);
+    series.setChartCategorySeriesRenderStyle(CategorySeries.CategorySeriesRenderStyle.Scatter);
+
+    return chart;
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue257.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue257.java
new file mode 100644
index 0000000000000000000000000000000000000000..9d5b4d34ab9ca35370cd8e48752ec8486073a7a8
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue257.java
@@ -0,0 +1,48 @@
+package org.knowm.xchart.standalone.issues;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.knowm.xchart.CategoryChart;
+import org.knowm.xchart.CategoryChartBuilder;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.style.Styler;
+
+public class TestForIssue257 {
+
+  public static void main(String[] args) {
+
+    CategoryChart chart =
+        new CategoryChartBuilder()
+            .width(500)
+            .height(500)
+            .title("Average Weight and Height per Team")
+            .xAxisTitle("Team_ID")
+            .build();
+    // Customize Chart
+
+    chart.getStyler().setPlotGridVerticalLinesVisible(false);
+    chart.getStyler().setStacked(true);
+    chart.getStyler().setPlotGridHorizontalLinesVisible(true);
+    chart.getStyler().setLegendPosition(Styler.LegendPosition.OutsideS);
+    chart.getStyler().setLegendLayout(Styler.LegendLayout.Horizontal);
+    List<String> category_values = new ArrayList<String>();
+
+    for (int i = 1; i <= 15; i++) {
+      category_values.add("K0" + i);
+    }
+
+    List<Double> height = new ArrayList<Double>();
+
+    List<Double> weight = new ArrayList<Double>();
+
+    for (int i = 1; i <= 15; i++) {
+      weight.add((double) (i + 45));
+      height.add((double) (i + 30));
+    }
+
+    chart.addSeries("Average_Height", category_values, height);
+    chart.addSeries("Average_Weight", category_values, weight);
+
+    new SwingWrapper<CategoryChart>(chart).displayChart();
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue27_1.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue27_1.java
new file mode 100644
index 0000000000000000000000000000000000000000..0828cabec81c5e659153986a213969b3262a22e8
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue27_1.java
@@ -0,0 +1,41 @@
+package org.knowm.xchart.standalone.issues;
+
+import java.awt.Color;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYSeries;
+import org.knowm.xchart.style.colors.XChartSeriesColors;
+import org.knowm.xchart.style.lines.SeriesLines;
+import org.knowm.xchart.style.markers.SeriesMarkers;
+
+public class TestForIssue27_1 {
+
+  public static void main(String[] args) throws Exception {
+
+    double[] xData = new double[] {0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0};
+    double[] yData1 = new double[] {100, 100, 100, 50, 50, 50, 50};
+    double[] errdata = new double[] {50, 20, 10, 50, 40, 20, 10};
+
+    double[] yData2 = new double[] {50, 80, 90, 0, 10, 30, 40};
+    double[] yData3 = new double[] {150, 120, 110, 100, 90, 70, 60};
+
+    XYChart mychart = new XYChart(900, 700);
+    mychart.getStyler().setYAxisMin(0.0);
+    mychart.getStyler().setYAxisMax(150.0);
+    mychart.getStyler().setErrorBarsColor(Color.black);
+    XYSeries series1 = mychart.addSeries("Error bar test data", xData, yData1, errdata);
+    XYSeries series2 = mychart.addSeries("Y+error", xData, yData2);
+    XYSeries series3 = mychart.addSeries("Y-error", xData, yData3);
+    series1.setLineStyle(SeriesLines.SOLID);
+    series1.setMarker(SeriesMarkers.DIAMOND);
+    series1.setMarkerColor(Color.MAGENTA);
+    series2.setLineStyle(SeriesLines.DASH_DASH);
+    series2.setMarker(SeriesMarkers.NONE);
+    series2.setLineColor(XChartSeriesColors.RED);
+    series3.setLineStyle(SeriesLines.DASH_DASH);
+    series3.setMarker(SeriesMarkers.NONE);
+    series3.setLineColor(XChartSeriesColors.RED);
+
+    new SwingWrapper(mychart).displayChart();
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue27_2.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue27_2.java
new file mode 100644
index 0000000000000000000000000000000000000000..1df52b5b0c05a87f24f0f2ab99e81379f1a6988e
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue27_2.java
@@ -0,0 +1,63 @@
+package org.knowm.xchart.standalone.issues;
+
+import java.awt.Color;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYSeries;
+import org.knowm.xchart.style.colors.XChartSeriesColors;
+import org.knowm.xchart.style.lines.SeriesLines;
+import org.knowm.xchart.style.markers.SeriesMarkers;
+
+public class TestForIssue27_2 {
+
+  public static void main(String[] args) throws Exception {
+
+    double[] xData = new double[] {0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0};
+
+    // double[] yData1 = new double[] { 100, 100, 100, 10, 10, 10, 10 };
+    double[] yData1 = new double[] {100, 100, 100, 60, 10, 10, 10};
+
+    double[] yData2 = new double[] {150, 120, 110, 112, 19, 12, 11};
+
+    double[] yData3 = new double[] {50, 80, 90, 8, 1, 8, 9};
+
+    // double[] errdata = new double[] { 1, .699, .301, 2, 1, .699, 0.301 };
+    double[] errdata = new double[] {50, 20, 10, 52, 9, 2, 1};
+
+    XYChart mychart = new XYChart(1200, 800);
+
+    mychart.getStyler().setYAxisLogarithmic(true); // set log or linear Y axis
+
+    mychart.getStyler().setYAxisMin(.08);
+
+    mychart.getStyler().setYAxisMax(1000.0);
+
+    mychart.getStyler().setErrorBarsColor(Color.black);
+
+    XYSeries series1 = mychart.addSeries("Error bar test data", xData, yData1, errdata);
+
+    XYSeries series2 = mychart.addSeries("Y+error", xData, yData2);
+
+    XYSeries series3 = mychart.addSeries("Y-error", xData, yData3);
+
+    series1.setLineStyle(SeriesLines.SOLID);
+
+    series1.setMarker(SeriesMarkers.DIAMOND);
+
+    series1.setMarkerColor(Color.MAGENTA);
+
+    series2.setLineStyle(SeriesLines.DASH_DASH);
+
+    series2.setMarker(SeriesMarkers.NONE);
+
+    series2.setLineColor(XChartSeriesColors.RED);
+
+    series3.setLineStyle(SeriesLines.DASH_DASH);
+
+    series3.setMarker(SeriesMarkers.NONE);
+
+    series3.setLineColor(XChartSeriesColors.RED);
+
+    new SwingWrapper(mychart).displayChart();
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue285.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue285.java
new file mode 100644
index 0000000000000000000000000000000000000000..77364211b75f544d3bca8670e2fa05ac3c2c760f
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue285.java
@@ -0,0 +1,29 @@
+package org.knowm.xchart.standalone.issues;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import org.knowm.xchart.PdfboxGraphicsEncoder;
+import org.knowm.xchart.demo.charts.area.AreaChart01;
+import org.knowm.xchart.demo.charts.area.AreaChart02;
+import org.knowm.xchart.demo.charts.line.LineChart01;
+import org.knowm.xchart.demo.charts.line.LineChart02;
+import org.knowm.xchart.demo.charts.pie.PieChart01;
+import org.knowm.xchart.demo.charts.pie.PieChart02;
+import org.knowm.xchart.internal.chartpart.Chart;
+
+public class TestForIssue285 {
+
+  public static void main(String[] args) throws IOException {
+
+    List<Chart> charts = new ArrayList<>();
+    charts.add(new AreaChart01().getChart());
+    charts.add(new AreaChart02().getChart());
+    charts.add(new LineChart01().getChart());
+    charts.add(new LineChart02().getChart());
+    charts.add(new PieChart01().getChart());
+    charts.add(new PieChart02().getChart());
+
+    PdfboxGraphicsEncoder.savePdfboxGraphics(charts, "./Multiple_Charts");
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue289.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue289.java
new file mode 100644
index 0000000000000000000000000000000000000000..4c7ff0fa6cd169ef1fb7612a5bcba3a241b4d6c3
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue289.java
@@ -0,0 +1,64 @@
+package org.knowm.xchart.standalone.issues;
+
+import java.awt.Color;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYChartBuilder;
+import org.knowm.xchart.XYSeries;
+import org.knowm.xchart.XYSeries.XYSeriesRenderStyle;
+import org.knowm.xchart.style.markers.SeriesMarkers;
+
+public class TestForIssue289 {
+
+  public static void main(String[] args) throws IOException {
+
+    // Create Chart
+    XYChart chart =
+        new XYChartBuilder()
+            .width(800)
+            .height(600)
+            .title("ScatterChart04")
+            .xAxisTitle("X")
+            .yAxisTitle("Y")
+            .build();
+
+    // Customize Chart
+    chart.getStyler().setDefaultSeriesRenderStyle(XYSeriesRenderStyle.Scatter);
+    chart.getStyler().setChartTitleVisible(false);
+    chart.getStyler().setLegendVisible(false);
+    chart.getStyler().setAxisTitlesVisible(false);
+    chart.getStyler().setXAxisDecimalPattern("0.0000000");
+
+    // Scatter Series w/ Error Bars
+    int size = 10;
+    List<Double> xData = new ArrayList<Double>();
+    List<Double> yData = new ArrayList<Double>();
+    List<Double> errorBars = new ArrayList<Double>();
+    for (int i = 0; i <= size; i++) {
+      xData.add(((double) i) / 1000000);
+      yData.add(10 * Math.exp(-i) + Math.random());
+      errorBars.add(Math.random() + .3);
+    }
+    XYSeries series = chart.addSeries("data", xData, yData, errorBars);
+    series.setMarkerColor(Color.RED);
+    series.setMarker(SeriesMarkers.SQUARE);
+
+    // Line Series
+    size = 100;
+    xData = new ArrayList<Double>();
+    yData = new ArrayList<Double>();
+    for (int i = 0; i <= size; i++) {
+      xData.add(((double) i) / 10000000);
+      yData.add(10 * Math.exp(-i / (double) 10));
+      errorBars.add(Math.random() + .3);
+    }
+    series = chart.addSeries("fit", xData, yData);
+    series.setMarker(SeriesMarkers.NONE);
+    series.setXYSeriesRenderStyle(XYSeriesRenderStyle.Line);
+
+    new SwingWrapper<XYChart>(chart).displayChart();
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue291.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue291.java
new file mode 100644
index 0000000000000000000000000000000000000000..48bc9a497890ee171ddabd92fe8002a741534b26
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue291.java
@@ -0,0 +1,90 @@
+package org.knowm.xchart.standalone.issues;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.swing.SwingUtilities;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYChartBuilder;
+import org.knowm.xchart.XYSeries;
+import org.knowm.xchart.style.Styler.LegendPosition;
+import org.knowm.xchart.style.Styler.YAxisPosition;
+
+public class TestForIssue291 {
+
+  static final int WIDTH = 465;
+  static final int HEIGHT = 320;
+
+  public static void main(String[] args) {
+
+    final XYChart chart = getLineChart();
+    chart.setTitle("sin(x) on second axis with title");
+    String seriesName = "y=sin(x)";
+    XYSeries series = (XYSeries) chart.getSeriesMap().get(seriesName);
+    series.setYAxisGroup(1);
+    chart.getStyler().setYAxisGroupPosition(1, YAxisPosition.Left);
+    chart.getStyler().setYAxisGroupPosition(0, YAxisPosition.Right);
+    chart.setYAxisGroupTitle(1, "sin(x)");
+
+    final SwingWrapper<XYChart> sw = new SwingWrapper<XYChart>(chart);
+
+    sw.displayChart();
+
+    boolean seriesShown = true;
+    while (true) {
+      try {
+        Thread.sleep(1000);
+      } catch (InterruptedException e) {
+        e.printStackTrace();
+      }
+
+      if (seriesShown) {
+        chart.removeSeries(seriesName);
+      } else {
+        double[] xData = series.getXData();
+        double[] yData = series.getYData();
+        chart.addSeries(seriesName, xData, yData);
+      }
+      seriesShown = !seriesShown;
+      try {
+        SwingUtilities.invokeAndWait(
+            new Runnable() {
+              public void run() {
+                sw.repaintChart();
+              }
+            });
+      } catch (Exception e) {
+        e.printStackTrace();
+      }
+    }
+  }
+
+  static XYChart getLineChart() {
+
+    XYChart chart =
+        new XYChartBuilder().width(WIDTH).height(HEIGHT).xAxisTitle("X").yAxisTitle("Y").build();
+
+    // Customize Chart
+    chart.getStyler().setToolTipsEnabled(true);
+    chart.getStyler().setLegendPosition(LegendPosition.InsideNW);
+    // generates sine data
+    int size = 30;
+    List<Integer> xData = new ArrayList<Integer>();
+    List<Double> yData = new ArrayList<Double>();
+    List<Integer> xData2 = new ArrayList<Integer>();
+    List<Double> yData2 = new ArrayList<Double>();
+    for (int i = 0; i <= size; i++) {
+      double radians = (Math.PI / (size / 2) * i);
+      int x = i - size / 2;
+      xData.add(x);
+      yData.add(-1 * Math.sin(radians));
+      xData2.add(x);
+      yData2.add(-10 * Math.cos(radians));
+    }
+
+    // Series
+    chart.addSeries("y=sin(x)", xData, yData);
+    chart.addSeries("y=cos(x)", xData2, yData2);
+    return chart;
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue308.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue308.java
new file mode 100644
index 0000000000000000000000000000000000000000..b3907834a6f3335ef0d580c192821dbad60847f8
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue308.java
@@ -0,0 +1,71 @@
+package org.knowm.xchart.standalone.issues;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import org.knowm.xchart.CategoryChart;
+import org.knowm.xchart.CategoryChartBuilder;
+import org.knowm.xchart.CategorySeries;
+import org.knowm.xchart.CategorySeries.CategorySeriesRenderStyle;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.style.Styler.ChartTheme;
+import org.knowm.xchart.style.Styler.LegendPosition;
+
+public class TestForIssue308 implements ExampleChart<CategoryChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<CategoryChart> exampleChart = new TestForIssue308();
+    CategoryChart chart = exampleChart.getChart();
+    new SwingWrapper<CategoryChart>(chart).displayChart();
+  }
+
+  @Override
+  public CategoryChart getChart() {
+
+    // Create Chart
+    CategoryChart chart =
+        new CategoryChartBuilder()
+            .width(800)
+            .height(600)
+            .title("Value vs. Letter")
+            .xAxisTitle("Letter")
+            .yAxisTitle("Value")
+            .theme(ChartTheme.GGPlot2)
+            .build();
+
+    // Customize Chart
+    chart.getStyler().setLegendPosition(LegendPosition.InsideNW);
+    chart.getStyler().setAvailableSpaceFill(.55);
+    chart.getStyler().setOverlapped(true);
+
+    // Series
+    chart.addSeries(
+        "China",
+        new ArrayList<String>(Arrays.asList(new String[] {"A", "B", "C", "D", "E"})),
+        new ArrayList<Number>(Arrays.asList(new Number[] {-11, -23, 20, 36, 20})),
+        new ArrayList<Number>(Arrays.asList(new Number[] {3, 3, 2, 1, 2})));
+    CategorySeries series2 =
+        chart.addSeries(
+            "Korea",
+            new ArrayList<String>(Arrays.asList(new String[] {"A", "B", "C", "D", "E"})),
+            new ArrayList<Number>(Arrays.asList(new Number[] {13, 15, -22, -28, 7})),
+            new ArrayList<Number>(Arrays.asList(new Number[] {3, 3, 2, 1, 2})));
+    series2.setChartCategorySeriesRenderStyle(CategorySeriesRenderStyle.Line);
+    CategorySeries series3 =
+        chart.addSeries(
+            "World Ave.",
+            new ArrayList<String>(Arrays.asList(new String[] {"A", "B", "C", "D", "E"})),
+            new ArrayList<Number>(Arrays.asList(new Number[] {30, 22, 18, -36, -32})),
+            new ArrayList<Number>(Arrays.asList(new Number[] {3, 3, 2, 1, 2})));
+    series3.setChartCategorySeriesRenderStyle(CategorySeriesRenderStyle.Scatter);
+
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName();
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue315.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue315.java
new file mode 100644
index 0000000000000000000000000000000000000000..d984db1120212ea24c82e81633aa0fbd0c9ab150
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue315.java
@@ -0,0 +1,51 @@
+package org.knowm.xchart.standalone.issues;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYSeries;
+import org.knowm.xchart.internal.chartpart.Chart;
+import org.knowm.xchart.style.Styler.LegendPosition;
+
+public class TestForIssue315 {
+
+  static final int WIDTH = 465;
+  static final int HEIGHT = 320;
+
+  static XYChart getChart(boolean group0Enabled, boolean group1Enabled) {
+
+    double[] xData = new double[] {0.0, 1.0, 2.0};
+    double[] yData = new double[] {2.0, 1.0, 0.0};
+    double[] yData2 = new double[] {10.0, 20.0, 15.0};
+    XYChart chart = new XYChart(500, 200);
+    XYSeries xySeries = chart.addSeries("Series 0", xData, yData);
+    xySeries.setYAxisGroup(0);
+    xySeries.setEnabled(group0Enabled);
+
+    XYSeries xySeries2 = chart.addSeries("Series 1", xData, yData2);
+    xySeries2.setYAxisGroup(1);
+    xySeries2.setEnabled(group1Enabled);
+    chart.getStyler().setLegendPosition(LegendPosition.OutsideS);
+    return chart;
+  }
+
+  public static void main(String[] args) {
+
+    List<Chart> charts = new ArrayList<Chart>();
+    boolean[] options = {true, false};
+    for (boolean g0 : options) {
+      for (boolean g1 : options) {
+        XYChart chart = getChart(g0, g1);
+        chart.setTitle(
+            "Series 0 "
+                + (g0 ? "enabled" : "disabled")
+                + " - Series 1 "
+                + (g1 ? "enabled" : "disabled"));
+        charts.add(chart);
+      }
+    }
+
+    new SwingWrapper(charts).displayChartMatrix();
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue325.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue325.java
new file mode 100644
index 0000000000000000000000000000000000000000..d606e21b2984530f6dc63f480aadee9d8dd3d0c2
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue325.java
@@ -0,0 +1,60 @@
+package org.knowm.xchart.standalone.issues;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.internal.chartpart.Chart;
+
+public class TestForIssue325 {
+
+  static final int WIDTH = 465;
+  static final int HEIGHT = 320;
+
+  public static void main(String[] args) {
+
+    List<Chart> charts = new ArrayList<>();
+    int[] multiples = {1, 1000};
+    int[] widths = {0, 15, 55};
+    for (int m : multiples) {
+      for (int width : widths) {
+        XYChart chart = getChart(m, width);
+        chart.setTitle("Multiple: " + m + " width: " + width);
+        charts.add(chart);
+      }
+    }
+
+    new SwingWrapper(charts, charts.size() / widths.length, widths.length).displayChartMatrix();
+
+    try {
+      // wait frame to appear
+      Thread.sleep(500);
+    } catch (InterruptedException e) {
+      e.printStackTrace();
+    }
+    for (int i = 0; i < charts.size(); i++) {
+      System.out.printf(
+          "%-30s on screen width: %5.2f%n",
+          charts.get(i).getTitle(), charts.get(i).getYAxisLeftWidth());
+    }
+  }
+
+  static XYChart getChart(int multiple, int yAxisLeftWidth) {
+
+    int size = 30;
+    List<Integer> xData = new ArrayList<>(size);
+    List<Double> yData = new ArrayList<>();
+    for (int i = 0; i <= size; i++) {
+      double radians = (Math.PI / (size / 2) * i);
+      xData.add(i * multiple);
+      yData.add(Math.sin(radians) * multiple);
+    }
+
+    XYChart chart = new XYChart(WIDTH, HEIGHT);
+    chart.addSeries("Series 0", xData, yData);
+
+    chart.getStyler().setLegendVisible(false);
+    chart.getStyler().setYAxisLeftWidthHint(yAxisLeftWidth);
+    return chart;
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue335.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue335.java
new file mode 100644
index 0000000000000000000000000000000000000000..1294b62d5979aef96299de9b2deabc45da731d74
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue335.java
@@ -0,0 +1,42 @@
+package org.knowm.xchart.standalone.issues;
+
+import org.knowm.xchart.RadarChart;
+import org.knowm.xchart.RadarChartBuilder;
+import org.knowm.xchart.RadarSeries;
+import org.knowm.xchart.SwingWrapper;
+
+public class TestForIssue335 {
+
+  public static void main(String[] args) {
+
+    RadarChart chart = new TestForIssue335().getChart();
+    new SwingWrapper<RadarChart>(chart).displayChart();
+  }
+
+  public RadarChart getChart() {
+
+    // Create Chart
+    RadarChart chart =
+        new RadarChartBuilder().width(800).height(600).title("TestForIssue335").build();
+    chart.getStyler().setToolTipsEnabled(true);
+    chart.getStyler().setSeriesFilled(false);
+
+    // Series
+    chart.setRadiiLabels(
+        new String[] {
+          "Sales",
+          "Marketing",
+          "Development",
+          "Customer Support",
+          "Information Technology",
+          "Administration"
+        });
+    RadarSeries oldSystemSeries =
+        chart.addSeries("Old System", new double[] {0.78, 0.85, 0.80, 0.82, 0.93, 0.92});
+    oldSystemSeries.setLineWidth(6);
+    RadarSeries newSystemSeries =
+        chart.addSeries("New System", new double[] {0.67, 0.73, 0.97, 0.95, 0.93, 0.73});
+    newSystemSeries.setLineWidth(4);
+    return chart;
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue349.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue349.java
new file mode 100644
index 0000000000000000000000000000000000000000..3304105412e1463a40ba16cec9977850cc203df9
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue349.java
@@ -0,0 +1,77 @@
+package org.knowm.xchart.standalone.issues;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.Arrays;
+import java.util.List;
+import javax.swing.JFrame;
+import javax.swing.WindowConstants;
+import org.knowm.xchart.XChartPanel;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYChartBuilder;
+import org.knowm.xchart.demo.charts.ExampleChart;
+
+public class TestForIssue349 implements ExampleChart<XYChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<XYChart> exampleChart = new TestForIssue349();
+    XYChart chart = exampleChart.getChart();
+    XChartPanel chartPanel = new XChartPanel(chart);
+
+    // Create and set up the window.
+    final JFrame frame = new JFrame("TestForIssue349");
+
+    // Schedule a job for the event-dispatching thread:
+    // creating and showing this application's GUI.
+    try {
+      javax.swing.SwingUtilities.invokeAndWait(
+          new Runnable() {
+
+            @Override
+            public void run() {
+
+              frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
+              frame.add(chartPanel);
+
+              // Display the window.
+              frame.pack();
+              frame.setVisible(true);
+            }
+          });
+    } catch (InterruptedException e) {
+      e.printStackTrace();
+    } catch (InvocationTargetException e) {
+      e.printStackTrace();
+    }
+  }
+
+  @Override
+  public XYChart getChart() {
+
+    // Create Chart
+    XYChart chart = new XYChartBuilder().width(800).height(600).build();
+
+    // Customize Chart
+    chart.getStyler().setChartTitleVisible(false);
+    chart.getStyler().setLegendVisible(false);
+    chart.getStyler().setZoomEnabled(true);
+
+    List<Integer> xData = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
+    List<Double> yData = Arrays.asList(1.1, 2.2, 7.3, 8.4, 4.5, 6.6, 2.7, 6.8, 4.9, 3.10);
+
+    List<Double> xData2 = Arrays.asList(4.5, 6.5, 7.5);
+    List<Double> yData2 = Arrays.asList(3.4, 5.6, 3.7);
+
+    // Series
+    chart.addSeries("1", xData, yData);
+    chart.addSeries("2", xData2, yData2);
+
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName();
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue363.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue363.java
new file mode 100644
index 0000000000000000000000000000000000000000..edf56f067164d63ecfcce19563dcaa27a0e5e470
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue363.java
@@ -0,0 +1,62 @@
+package org.knowm.xchart.standalone.issues;
+
+import org.knowm.xchart.PieChart;
+import org.knowm.xchart.PieChartBuilder;
+import org.knowm.xchart.PieSeries.PieSeriesRenderStyle;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.style.PieStyler.ClockwiseDirectionType;
+import org.knowm.xchart.style.PieStyler.LabelType;
+import org.knowm.xchart.style.colors.BaseSeriesColors;
+
+public class TestForIssue363 implements ExampleChart<PieChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<PieChart> exampleChart = new TestForIssue363();
+    PieChart chart = exampleChart.getChart();
+    new SwingWrapper<PieChart>(chart).displayChart();
+  }
+
+  @Override
+  public PieChart getChart() {
+
+    // Create Chart
+    PieChart chart =
+        new PieChartBuilder()
+            .width(800)
+            .height(600)
+            .title("PieChart - Pie Chart with Pie Style with DirectionType CLOCKWISE")
+            .build();
+
+    // Customize Chart
+    chart.getStyler().setLegendVisible(false);
+    chart.getStyler().setLabelType(LabelType.NameAndValue);
+    chart.getStyler().setLabelsDistance(.82);
+    chart.getStyler().setPlotContentSize(.9);
+    chart.getStyler().setDefaultSeriesRenderStyle(PieSeriesRenderStyle.Pie);
+    chart.getStyler().setClockwiseDirectionType(ClockwiseDirectionType.CLOCKWISE);
+    chart.getStyler().setDecimalPattern("#");
+
+    chart.getStyler().setSeriesColors(new BaseSeriesColors().getSeriesColors());
+
+    chart.getStyler().setSumVisible(true);
+    chart.getStyler().setSumFontSize(20f);
+
+    // Series
+    chart.addSeries("A", 22);
+    chart.addSeries("B", 10);
+    chart.addSeries("C", 34);
+    chart.addSeries("D", 22);
+    chart.addSeries("E", 29);
+    chart.addSeries("F", 40);
+
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName();
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue370.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue370.java
new file mode 100644
index 0000000000000000000000000000000000000000..8d191a435d64f8e4e96fe193328c165273d88b67
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue370.java
@@ -0,0 +1,59 @@
+package org.knowm.xchart.standalone.issues;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYChartBuilder;
+import org.knowm.xchart.XYSeries;
+import org.knowm.xchart.XYSeries.XYSeriesRenderStyle;
+import org.knowm.xchart.style.Styler.LegendPosition;
+
+public class TestForIssue370 {
+
+  public static void main(String[] args) {
+
+    TestForIssue370 testForIssue370 = new TestForIssue370();
+    List<XYChart> charts = new ArrayList<>();
+    charts.add(testForIssue370.getChart("Group yAxis DecimalPattern", false));
+    charts.add(testForIssue370.getChart("Group yAxis DecimalPattern Logarithmic", true));
+    new SwingWrapper<XYChart>(charts).displayChartMatrix();
+  }
+
+  public XYChart getChart(String title, boolean isYAxisLogarithmic) {
+
+    // Create Chart
+    XYChart chart =
+        new XYChartBuilder()
+            .width(600)
+            .height(400)
+            .title(title)
+            .xAxisTitle("X")
+            .yAxisTitle("Y")
+            .build();
+
+    // Customize Chart
+    chart.getStyler().setLegendPosition(LegendPosition.OutsideE);
+    chart.getStyler().setAxisTitlesVisible(false);
+    chart.getStyler().setDefaultSeriesRenderStyle(XYSeriesRenderStyle.Line);
+    chart.getStyler().setToolTipsEnabled(true);
+    chart.getStyler().setToolTipsAlwaysVisible(true);
+    chart.getStyler().setYAxisLogarithmic(isYAxisLogarithmic);
+
+    // Series
+    chart.addSeries("a", new double[] {1, 2, 3, 4, 5}, new double[] {400, 200, 300, 200, 100});
+    XYSeries b =
+        chart.addSeries(
+            "b",
+            new double[] {1, 2, 3, 4, 5},
+            new double[] {0.00012328, 0.0015467, 0.019879, 0.19859, 1.59681});
+    b.setYAxisGroup(1);
+    if (isYAxisLogarithmic) {
+      b.setYAxisDecimalPattern("#0.##E0");
+    } else {
+      b.setYAxisDecimalPattern("0.0000");
+    }
+
+    return chart;
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue390.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue390.java
new file mode 100644
index 0000000000000000000000000000000000000000..55f19e25f5d90c390672555c32c5f08b67b6f826
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue390.java
@@ -0,0 +1,76 @@
+package org.knowm.xchart.standalone.issues;
+
+import java.io.IOException;
+import java.util.Random;
+import org.knowm.xchart.BitmapEncoder;
+import org.knowm.xchart.BitmapEncoder.BitmapFormat;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYChartBuilder;
+import org.knowm.xchart.XYSeries.XYSeriesRenderStyle;
+import org.knowm.xchart.style.Styler.LegendPosition;
+
+public class TestForIssue390 {
+
+  public static void main(String[] args) throws IOException {
+
+    Random rand = new Random();
+
+    double min = 0;
+    double max = 20;
+    int nbServices = 20;
+    // int nbExpress = 1;
+    int nbInstances = 50;
+
+    long s = 24;
+    rand.setSeed(s);
+    /*double[][] coordinates = new double[nbServices][2];
+
+    for (int i = 0 ; i < coordinates.length; i++) {
+    }*/
+
+    final XYChart chart =
+        new XYChartBuilder()
+            .width(600)
+            .height(400)
+            .title("Augmentation du cout induit par livraison express")
+            .xAxisTitle("Proportion de colis express")
+            .yAxisTitle("Rapport cout/cout_opt")
+            .build();
+
+    // Customize Chart
+    chart.getStyler().setLegendVisible(true);
+    chart.getStyler().setLegendPosition(LegendPosition.InsideSW);
+    chart.getStyler().setDefaultSeriesRenderStyle(XYSeriesRenderStyle.Line);
+    // chart.getStyler().setYAxisMax(2.0);
+
+    /*XYDataset dataset = XYDataset.createDataset();
+    ChartFactory.createXYLineChart("Test", "X", "Y", dataset);*/
+
+    double[] xData = new double[nbServices];
+    for (int i = 0; i < xData.length; i++) {
+      xData[i] = (double) (i + 1) / nbServices;
+    }
+
+    double[][] results = new double[3][nbServices];
+
+    for (int t = 0; t < 3; t++) {
+      System.out.print("Simulations pour t=" + (t + 1) * 0.25 + "\n");
+
+      for (int n = 1; n < nbServices + 1; n++) {
+        results[t][n - 1] = 1.2;
+        // System.out.print(results[n-1] + "\n");
+      }
+
+      chart.addSeries(Double.toString(t), xData, results[t]);
+      /*System.out.print(xData.length + "\n");
+      System.out.print(results.length + "\n");*/
+      System.out.print(results[t][0] + " " + results[t][nbServices - 1] + "\n");
+    }
+
+    // Show it
+    new SwingWrapper(chart).displayChart();
+
+    BitmapEncoder.saveBitmap(chart, "./Sample_Chart", BitmapFormat.PNG);
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue402.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue402.java
new file mode 100644
index 0000000000000000000000000000000000000000..6703d5f97e5971161bd20605734c6ea4413fd4b6
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue402.java
@@ -0,0 +1,50 @@
+package org.knowm.xchart.standalone.issues;
+
+import org.knowm.xchart.CategoryChart;
+import org.knowm.xchart.CategoryChartBuilder;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.style.Styler.LegendPosition;
+
+public class TestForIssue402 implements ExampleChart<CategoryChart> {
+
+  public static void main(String[] args) {
+    ExampleChart<CategoryChart> exampleChart = new TestForIssue402();
+    CategoryChart chart = exampleChart.getChart();
+    new SwingWrapper<CategoryChart>(chart).displayChart();
+  }
+
+  @Override
+  public CategoryChart getChart() {
+
+    // Create Chart
+    CategoryChart chart =
+        new CategoryChartBuilder()
+            .width(800)
+            .height(600)
+            .title("TestForIssue402")
+            .xAxisTitle("x")
+            .yAxisTitle("y")
+            .build();
+
+    // Customize Chart
+    chart.getStyler().setLegendPosition(LegendPosition.OutsideE);
+    chart.getStyler().setStacked(true);
+    chart.getStyler().setLabelsVisible(true);
+    chart.getStyler().setShowStackSum(true);
+
+    // Series
+    chart.addSeries("a", new double[] {0, 1, 2, 3, 4}, new double[] {40, 35, -45, -60, -60});
+    chart.addSeries("b", new double[] {0, 1, 2, 3, 4}, new double[] {50, 65, 60, -70, 30});
+    chart.addSeries("c", new double[] {0, 1, 2, 3, 4}, new double[] {70, 45, -55, -80, 40});
+    chart.addSeries("d", new double[] {0, 1, 2, 3, 4}, new double[] {90, 80, 75, 75, 50});
+
+    return chart;
+  }
+
+  @Override
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName();
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue404.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue404.java
new file mode 100644
index 0000000000000000000000000000000000000000..e1b4e881243849e281df4a22132ed8de78a7bc52
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue404.java
@@ -0,0 +1,46 @@
+package org.knowm.xchart.standalone.issues;
+
+import java.awt.Color;
+import java.util.ArrayList;
+import java.util.List;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.demo.charts.date.DateChart03;
+import org.knowm.xchart.demo.charts.line.LineChart03;
+
+public class TestForIssue404 {
+
+  public static void main(String[] args) {
+
+    List<XYChart> charts = new ArrayList<>();
+
+    XYChart chart1 = new LineChart03().getChart();
+    chart1.setTitle("Set XY axis tickLabels and tickMarks color");
+    chart1.getStyler().setXAxisTitleColor(Color.RED);
+    chart1.getStyler().setYAxisTitleColor(Color.GREEN);
+    chart1.getStyler().setAxisTickLabelsColor(Color.PINK);
+    chart1.getStyler().setXAxisTickLabelsColor(Color.BLUE);
+    chart1.getStyler().setYAxisTickLabelsColor(Color.CYAN);
+    chart1.getStyler().setAxisTickMarksColor(Color.ORANGE);
+    chart1.getStyler().setXAxisTickMarksColor(Color.RED);
+    chart1.getStyler().setYAxisTickMarksColor(Color.YELLOW);
+    charts.add(chart1);
+
+    XYChart chart2 = new DateChart03().getChart();
+    chart2.setTitle("Set multiple Y-axis tickLabels and tickMarks colors");
+    chart2.setYAxisGroupTitle(1, "Y1");
+    chart2.setYAxisGroupTitle(0, "Y2");
+    chart2.getStyler().setYAxisGroupTitleColor(1, chart2.getStyler().getSeriesColors()[0]);
+    chart2.getStyler().setYAxisGroupTitleColor(0, chart2.getStyler().getSeriesColors()[1]);
+
+    chart2.getStyler().setXAxisTickLabelsColor(Color.YELLOW);
+    chart2.getStyler().setXAxisTickMarksColor(Color.BLUE);
+
+    chart2.getStyler().setYAxisGroupTickLabelsColorMap(1, Color.CYAN);
+    chart2.getStyler().setYAxisGroupTickLabelsColorMap(0, Color.RED);
+    chart2.getStyler().setYAxisGroupTickMarksColorMap(1, Color.ORANGE);
+    chart2.getStyler().setYAxisGroupTickMarksColorMap(0, Color.PINK);
+    charts.add(chart2);
+    new SwingWrapper<XYChart>(charts).displayChartMatrix();
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue405.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue405.java
new file mode 100644
index 0000000000000000000000000000000000000000..d222542b82aa5fbfe42d75cc08d7873963ed288d
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue405.java
@@ -0,0 +1,32 @@
+package org.knowm.xchart.standalone.issues;
+
+import java.awt.Color;
+import java.util.ArrayList;
+import java.util.List;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.demo.charts.date.DateChart03;
+import org.knowm.xchart.demo.charts.line.LineChart03;
+
+public class TestForIssue405 {
+
+  public static void main(String[] args) {
+
+    List<XYChart> charts = new ArrayList<>();
+
+    XYChart chart1 = new LineChart03().getChart();
+    chart1.setTitle("Set XY axis title font color");
+    chart1.getStyler().setXAxisTitleColor(Color.RED);
+    chart1.getStyler().setYAxisTitleColor(Color.GREEN);
+    charts.add(chart1);
+
+    XYChart chart2 = new DateChart03().getChart();
+    chart2.setTitle("Set multiple Y-axis title font colors");
+    chart2.setYAxisGroupTitle(1, "Y1");
+    chart2.setYAxisGroupTitle(0, "Y2");
+    chart2.getStyler().setYAxisGroupTitleColor(1, chart2.getStyler().getSeriesColors()[0]);
+    chart2.getStyler().setYAxisGroupTitleColor(0, chart2.getStyler().getSeriesColors()[1]);
+    charts.add(chart2);
+    new SwingWrapper<XYChart>(charts).displayChartMatrix();
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue410.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue410.java
new file mode 100644
index 0000000000000000000000000000000000000000..6b3f1b0d5b59034484be51a232941360fd26b53e
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue410.java
@@ -0,0 +1,23 @@
+package org.knowm.xchart.standalone.issues;
+
+import java.util.Arrays;
+import org.knowm.xchart.BoxChart;
+import org.knowm.xchart.BoxChartBuilder;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.style.Styler.ChartTheme;
+
+public class TestForIssue410 {
+
+  public static void main(String[] args) {
+
+    // Create Chart
+    BoxChart chart =
+        new BoxChartBuilder().title("TestForIssue410").theme(ChartTheme.GGPlot2).build();
+
+    chart.getStyler().setToolTipsEnabled(true);
+
+    // Series
+    chart.addSeries("boxOne", Arrays.asList(1000, 5000, 60000));
+    new SwingWrapper<BoxChart>(chart).displayChart();
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue416.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue416.java
new file mode 100644
index 0000000000000000000000000000000000000000..15417774af71217a45849b6089bbc730c2a8678f
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue416.java
@@ -0,0 +1,43 @@
+package org.knowm.xchart.standalone.issues;
+
+import java.awt.image.BufferedImage;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Timer;
+import java.util.TimerTask;
+import org.knowm.xchart.BitmapEncoder;
+import org.knowm.xchart.GifEncoder;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.demo.charts.realtime.RealtimeChart01;
+
+public class TestForIssue416 {
+
+  public static void main(String[] args) {
+
+    List<BufferedImage> images = new ArrayList<>();
+    RealtimeChart01 realtimeChart01 = new RealtimeChart01();
+    XYChart xyChart = realtimeChart01.getChart();
+    images.add(BitmapEncoder.getBufferedImage(xyChart));
+    TimerTask chartUpdaterTask =
+        new TimerTask() {
+
+          @Override
+          public void run() {
+
+            realtimeChart01.updateData();
+            images.add(BitmapEncoder.getBufferedImage(xyChart));
+          }
+        };
+
+    Timer timer = new Timer();
+    timer.scheduleAtFixedRate(chartUpdaterTask, 0, 500);
+
+    try {
+      Thread.sleep(10000);
+      timer.cancel();
+    } catch (InterruptedException e) {
+      e.printStackTrace();
+    }
+    GifEncoder.saveGif("./RealtimeChart_GIF", images, 0, 300);
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue433.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue433.java
new file mode 100644
index 0000000000000000000000000000000000000000..b3cbd1cdcb2a0a7ef465e587bffb5768df0ad393
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue433.java
@@ -0,0 +1,46 @@
+package org.knowm.xchart.standalone.issues;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYChartBuilder;
+
+public class TestForIssue433 {
+
+  public static void main(String[] args) throws ParseException {
+    DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+
+    List<Date> xData = new ArrayList<>();
+    xData.add(sdf.parse("1970-01-01 15:29:38"));
+    xData.add(sdf.parse("1970-01-01 15:29:39"));
+    xData.add(sdf.parse("1970-01-01 15:29:40"));
+    xData.add(sdf.parse("1970-01-01 15:29:41"));
+
+    List<Double> yData = new ArrayList<>();
+    yData.add(3.0);
+    yData.add(33.0);
+    yData.add(19.0);
+    yData.add(8.0);
+
+    // Create Chart
+    XYChart chart =
+        new XYChartBuilder()
+            .width(1400)
+            .height(200)
+            .title("TestForIssue 433 and 428")
+            .xAxisTitle("x")
+            .yAxisTitle("y")
+            .build();
+    chart.addSeries("test", xData, yData);
+
+    chart.getStyler().setDatePattern("HH:mm:ss");
+    chart.getStyler().setXAxisTickMarkSpacingHint(chart.getWidth() / (xData.size() - 1));
+
+    new SwingWrapper<>(chart).displayChart();
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue488.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue488.java
new file mode 100644
index 0000000000000000000000000000000000000000..297fa31cd9506444e10bcd1550a93866338dd1c1
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue488.java
@@ -0,0 +1,52 @@
+package org.knowm.xchart.standalone.issues;
+
+import java.awt.Color;
+import java.io.IOException;
+import org.knowm.xchart.BitmapEncoder;
+import org.knowm.xchart.PieChart;
+import org.knowm.xchart.PieChartBuilder;
+import org.knowm.xchart.style.Styler;
+
+public class TestForIssue488 {
+
+  public static void main(String[] args) throws IOException {
+    PieChart chart =
+        new PieChartBuilder().width(200).height(200).theme(Styler.ChartTheme.GGPlot2).build();
+    //    PieChart chart =
+    //        new PieChartBuilder().width(200).height(200).build();
+
+    chart.getStyler().setLegendVisible(false);
+
+    Color[] sliceColors =
+        new Color[] {
+          new Color(238, 88, 88), // red
+          new Color(213, 122, 212), // purple
+          new Color(106, 230, 182), // mentol
+          new Color(88, 155, 238) // baby blue
+        };
+
+    chart.getStyler().setSeriesColors(sliceColors);
+    chart.getStyler().setChartTitleBoxVisible(false);
+    chart.getStyler().setLabelsVisible(false);
+    chart.getStyler().setChartBackgroundColor(new Color(255, 255, 255));
+    //    chart.getStyler().setChartBackgroundColor(new Color(0, 255, 0, 25));
+    //    chart.getStyler().setChartBackgroundColor(new Color(0, 255, 0));
+    chart.getStyler().setPlotBackgroundColor(new Color(255, 255, 255));
+    //        chart.getStyler().setPlotBackgroundColor(new Color(0, 0, 255, 20));
+    //        chart.getStyler().setPlotBackgroundColor(new Color(0, 0, 255));
+    //    chart.getStyler().setChartBackgroundColor(new Color(255, 255, 255));
+    //    chart.getStyler().setChartBackgroundColor(new Color(0, 255, 0, 25));
+    //    chart.getStyler().setChartBackgroundColor(new Color(0, 255, 0));
+    //    chart.getStyler().setPlotBackgroundColor(new Color(255, 255, 255));
+    //        chart.getStyler().setPlotBackgroundColor(new Color(0, 0, 255, 20));
+    //        chart.getStyler().setPlotBackgroundColor(new Color(0, 0, 255));
+    chart.getStyler().setPlotBorderVisible(false);
+
+    chart.addSeries("laptop", 65);
+    chart.addSeries("pc", 10);
+    chart.addSeries("tel", 12.5);
+    chart.addSeries("winda", 12.5);
+    //    new SwingWrapper<>(chart).displayChart();
+    BitmapEncoder.saveBitmap(chart, "./Sample_Chart", BitmapEncoder.BitmapFormat.PNG);
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue527.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue527.java
new file mode 100644
index 0000000000000000000000000000000000000000..22853681fef08e9f34edb2d295a8de4f3ab751ea
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue527.java
@@ -0,0 +1,87 @@
+package org.knowm.xchart.standalone.issues;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.TimeZone;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYChartBuilder;
+import org.knowm.xchart.XYSeries;
+import org.knowm.xchart.style.Styler;
+import org.knowm.xchart.style.markers.SeriesMarkers;
+
+public class TestForIssue527 {
+
+  public static void main(String[] args) throws ParseException {
+    String series = "ABC";
+
+    List<Date> x = new ArrayList<>(); // List of dates
+    //    List<Integer> x = new ArrayList<Integer>(); // List of ints
+    List<Double> y = new ArrayList<>();
+
+    XYChart chart =
+        new XYChartBuilder()
+            .width(800)
+            .height(720)
+            .theme(Styler.ChartTheme.XChart)
+            .title("XChart")
+            .xAxisTitle("Date")
+            .yAxisTitle("%Diff ")
+            .build();
+    //    chart.getStyler().setPlotBackgroundColor(java.awt.Color.BLACK);
+    chart.getStyler().setPlotMargin(0);
+    chart.getStyler().setLegendPosition(Styler.LegendPosition.OutsideE);
+    chart.getStyler().setXAxisLabelRotation(90);
+    chart.getStyler().setYAxisGroupPosition(1, Styler.YAxisPosition.Right);
+
+    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+    sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
+    chart.getStyler().setTimezone(TimeZone.getTimeZone("UTC"));
+    //    chart.getStyler().setXAxisTickMarkSpacingHint(160);
+
+    x.add(sdf.parse("2020-10-19")); // dates on X
+    //    System.out.println("x.get(0).getTime() = " + x.get(0).getTime());
+    //    System.out.println("sdf.parse(\"2020-10-19\") = " +
+    // sdf.parse("2020-10-19").toGMTString());
+    x.add(sdf.parse("2020-10-20"));
+    x.add(sdf.parse("2020-10-21"));
+    x.add(sdf.parse("2020-10-22"));
+    x.add(sdf.parse("2020-10-23"));
+    x.add(sdf.parse("2020-10-24"));
+    x.add(sdf.parse("2020-10-25"));
+    x.add(sdf.parse("2020-10-26"));
+    x.add(sdf.parse("2020-10-27"));
+    x.add(sdf.parse("2020-10-28"));
+
+    //    x.add(0); // ints on X
+    //    x.add(1);
+    //    x.add(2);
+    //    x.add(3);
+    //    x.add(4);
+    //    x.add(5);
+    //    x.add(6);
+    //    x.add(7);
+    //    x.add(8);
+    //    x.add(9);
+
+    y.add(2.1);
+    y.add(4.5);
+    y.add(3.2);
+    y.add(5.6);
+    y.add(2.5);
+    y.add(3.8);
+    y.add(5.1);
+    y.add(7.4);
+    y.add(4.8);
+    y.add(2.7);
+
+    XYSeries xyseries = chart.addSeries(series, x, y);
+    xyseries.setMarker(SeriesMarkers.NONE);
+    xyseries.setYAxisGroup(1);
+    new SwingWrapper<>(chart).displayChart();
+    //      BitmapEncoder.saveBitmap(chart,"XChart.png", BitmapEncoder.BitmapFormat.PNG);
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue530.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue530.java
new file mode 100644
index 0000000000000000000000000000000000000000..a2dbde58b3a0b131b791fe85e9146acda522aa1b
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue530.java
@@ -0,0 +1,90 @@
+package org.knowm.xchart.standalone.issues;
+
+import java.awt.Color;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import org.knowm.xchart.CategoryChart;
+import org.knowm.xchart.CategoryChartBuilder;
+import org.knowm.xchart.CategorySeries;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.style.CategoryStyler;
+import org.knowm.xchart.style.Styler.LegendPosition;
+
+public class TestForIssue530 {
+
+  public static final SimpleDateFormat sdfMonthYear = new SimpleDateFormat("MM/yyyy");
+
+  public static void main(String[] args) {
+    try {
+      new SwingWrapper<CategoryChart>(getVolumesChart()).displayChart();
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+
+  public static CategoryChart getVolumesChart() throws Exception {
+    List<Date> dataX;
+    List<Double> dataY;
+    CategorySeries serie;
+    List<Date> xSerie = null;
+
+    // Création du graphe
+    CategoryChart chart =
+        new CategoryChartBuilder()
+            .width(800)
+            .height(500)
+            .title("Volumes")
+            .xAxisTitle("Période")
+            .yAxisTitle("Volumes")
+            .build();
+
+    // Définition du style du graphe
+    CategoryStyler styler = chart.getStyler();
+    styler.setLegendPosition(LegendPosition.InsideNW);
+    styler.setLegendBackgroundColor(Color.DARK_GRAY);
+    styler.setDatePattern("MM/yyyy");
+    styler.setXAxisTickMarkSpacingHint(50);
+    styler.setAntiAlias(true);
+    styler.setChartTitleBoxBorderColor(Color.LIGHT_GRAY);
+    styler.setToolTipsEnabled(true);
+    styler.setOverlapped(false);
+    styler.setStacked(true);
+    styler.setChartBackgroundColor(Color.DARK_GRAY);
+    styler.setChartFontColor(Color.LIGHT_GRAY);
+    styler.setAxisTickLabelsColor(Color.LIGHT_GRAY);
+    styler.setYAxisMax(Double.valueOf(100));
+
+    for (int i = 0; i < 3; i++) {
+      dataX = getSampleDataX(i);
+      dataY = getSampleDataY(i);
+      if (xSerie == null) {
+        xSerie = dataX;
+      }
+      serie = chart.addSeries("Serie " + i, xSerie, dataY);
+      serie.setChartCategorySeriesRenderStyle(CategorySeries.CategorySeriesRenderStyle.Bar);
+      System.out.println("Arrays.toString(dataY.toArray() = " + Arrays.toString(dataY.toArray()));
+    }
+    return chart;
+  }
+
+  public static List<java.util.Date> getSampleDataX(int i) throws Exception {
+    List<java.util.Date> xData = new CopyOnWriteArrayList<>();
+
+    xData.add(sdfMonthYear.parse("09/2020"));
+    xData.add(sdfMonthYear.parse("10/2020"));
+    xData.add(sdfMonthYear.parse("11/2020"));
+    return xData;
+  }
+
+  public static List<Double> getSampleDataY(int i) throws Exception {
+    List<Double> yData = new CopyOnWriteArrayList<Double>();
+
+    yData.add(Double.valueOf(20 + i));
+    yData.add(Double.valueOf(10 + i));
+    yData.add(Double.valueOf(30 + i));
+    return yData;
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue539.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue539.java
new file mode 100644
index 0000000000000000000000000000000000000000..6d89b9ca9326866b415882d053de64e8bc2e665d
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue539.java
@@ -0,0 +1,28 @@
+package org.knowm.xchart.standalone.issues;
+
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.style.Styler;
+
+public class TestForIssue539 {
+
+  public static void main(String[] args) {
+    XYChart myChart = new XYChart(800, 600);
+    myChart.setTitle("Multiple Y axes scale bug");
+    myChart.getStyler().setYAxisGroupPosition(0, Styler.YAxisPosition.Left);
+    myChart.getStyler().setYAxisGroupPosition(1, Styler.YAxisPosition.Right);
+    myChart.setXAxisTitle("x");
+    myChart.setYAxisGroupTitle(0, "group zero");
+    myChart.setYAxisGroupTitle(1, "group one");
+
+    myChart
+        .addSeries("series on group zero", new double[] {1, 2, 3}, new double[] {4, 5, 6})
+        .setYAxisGroup(0);
+
+    myChart
+        .addSeries("series on group one", new double[] {1, 2, 3}, new double[] {-100, -200, -300})
+        .setYAxisGroup(1);
+
+    new SwingWrapper<>(myChart).displayChart();
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue540.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue540.java
new file mode 100644
index 0000000000000000000000000000000000000000..5917f8a5fa87eff97dc67eb3d2a5b0089e86c3a4
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue540.java
@@ -0,0 +1,40 @@
+package org.knowm.xchart.standalone.issues;
+
+import java.awt.Color;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYChartBuilder;
+import org.knowm.xchart.XYSeries;
+import org.knowm.xchart.style.lines.SeriesLines;
+import org.knowm.xchart.style.markers.SeriesMarkers;
+
+public class TestForIssue540 {
+
+  public static void main(String[] args) {
+
+    TestForIssue540 exampleChart = new TestForIssue540();
+    XYChart chart = exampleChart.getChart();
+    new SwingWrapper<>(chart).displayChart();
+  }
+
+  public XYChart getChart() {
+
+    // Create Chart
+    XYChart chart = new XYChartBuilder().width(800).height(600).build();
+
+    // Customize Chart
+    chart.getStyler().setErrorBarsColor(Color.black);
+
+    // Series
+    int[] xData = new int[] {0, 1, 2, 3, 4, 5, 6};
+    int[] yData1 = new int[] {0, 1, 2, 3, 4, 5, 6};
+    //    int[] errdata = new int[] {0, 1, 2, 3, 4, 5, 6};
+    int[] errdata = new int[] {0, 2, 4, 6, 8, 10, 12};
+    XYSeries series1 = chart.addSeries("Error bar\ntest data", xData, yData1, errdata);
+    series1.setLineStyle(SeriesLines.SOLID);
+    series1.setMarker(SeriesMarkers.DIAMOND);
+    series1.setMarkerColor(Color.GREEN);
+
+    return chart;
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue544.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue544.java
new file mode 100644
index 0000000000000000000000000000000000000000..c5a3e7201338fc3cdfc7682f4db20410cf129a41
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue544.java
@@ -0,0 +1,41 @@
+package org.knowm.xchart.standalone.issues;
+
+import org.knowm.xchart.CategoryChart;
+import org.knowm.xchart.CategoryChartBuilder;
+import org.knowm.xchart.CategorySeries;
+import org.knowm.xchart.SwingWrapper;
+
+public class TestForIssue544 {
+
+  public static void main(String[] args) {
+
+    TestForIssue544 exampleChart = new TestForIssue544();
+    CategoryChart chart = exampleChart.getChart();
+    new SwingWrapper<CategoryChart>(chart).displayChart();
+  }
+
+  public CategoryChart getChart() {
+
+    // Create Chart
+    CategoryChart chart =
+        new CategoryChartBuilder()
+            .width(800)
+            .height(600)
+            .title("LineChart04")
+            .xAxisTitle("X")
+            .yAxisTitle("Y")
+            .build();
+
+    // Customize Chart
+    chart.getStyler().setLegendVisible(false);
+    CategorySeries series =
+        chart.addSeries("A", new double[] {0.0, 1.0, 3.0}, new double[] {0.0, 10.0, 20.0});
+    series.setEnabled(false);
+    return chart;
+  }
+
+  public String getExampleChartName() {
+
+    return getClass().getSimpleName() + " - Hundreds of Series on One Plot";
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue545.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue545.java
new file mode 100644
index 0000000000000000000000000000000000000000..0c5d0b7226adf91bda0bf6c33542e88fb8dd6539
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue545.java
@@ -0,0 +1,57 @@
+package org.knowm.xchart.standalone.issues;
+
+import java.awt.Font;
+import java.text.ParseException;
+import org.knowm.xchart.BubbleChart;
+import org.knowm.xchart.BubbleChartBuilder;
+import org.knowm.xchart.BubbleSeries;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.style.BubbleStyler;
+import org.knowm.xchart.style.Styler;
+
+public class TestForIssue545 {
+
+  public static void main(String[] args) throws ParseException {
+
+    BubbleChart chart = getBubbleChart();
+    new SwingWrapper(chart).displayChart();
+  }
+
+  public static BubbleChart getBubbleChart() {
+    double[] data = new double[] {1298.0, 0.0215, 279.0};
+
+    BubbleChart chart =
+        new BubbleChartBuilder()
+            .width(800)
+            .height(600)
+            .title("Some title")
+            .xAxisTitle("Volume")
+            .yAxisTitle("Rate")
+            .build();
+    setBubbleStyler(chart);
+    BubbleSeries bubbleSeries =
+        chart.addSeries(
+            "serieName", new double[] {data[0]}, new double[] {data[1]}, new double[] {data[2]});
+    //    bubbleSeries.setCustomToolTips(true);
+    //    String tooltip =
+    //        new DecimalFormat("#%").format(data[1]) + " (" + ((int) data[2]) + "/" + ((int)
+    // data[0]) + ")";
+    //
+    //    bubbleSeries.setToolTips(new String[] {tooltip});
+
+    return chart;
+  }
+
+  public static void setBubbleStyler(BubbleChart chart) {
+
+    BubbleStyler styler = chart.getStyler();
+    styler.setLegendPosition(Styler.LegendPosition.InsideSW);
+    styler.setLegendLayout(Styler.LegendLayout.Horizontal);
+    styler.setYAxisDecimalPattern("%");
+    styler.setXAxisTickMarkSpacingHint(50);
+    styler.setAntiAlias(true);
+    styler.setToolTipsEnabled(true);
+    styler.setToolTipsAlwaysVisible(true);
+    styler.setToolTipFont(new Font("SansSerif", Font.PLAIN, 14));
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue54_1.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue54_1.java
new file mode 100644
index 0000000000000000000000000000000000000000..3b1ccabda9a26ff117dbe65376e0d670bb61e585
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue54_1.java
@@ -0,0 +1,298 @@
+package org.knowm.xchart.standalone.issues;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Random;
+import org.knowm.xchart.BubbleChart;
+import org.knowm.xchart.BubbleChartBuilder;
+import org.knowm.xchart.CategoryChart;
+import org.knowm.xchart.CategoryChartBuilder;
+import org.knowm.xchart.CategorySeries.CategorySeriesRenderStyle;
+import org.knowm.xchart.Histogram;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYChartBuilder;
+import org.knowm.xchart.XYSeries.XYSeriesRenderStyle;
+import org.knowm.xchart.internal.chartpart.Chart;
+import org.knowm.xchart.internal.series.Series;
+import org.knowm.xchart.style.Styler;
+import org.knowm.xchart.style.Styler.ChartTheme;
+import org.knowm.xchart.style.Styler.LegendPosition;
+
+/** Create a Chart matrix */
+public class TestForIssue54_1 {
+
+  static final int WIDTH = 465;
+  static final int HEIGHT = 320;
+
+  public static void main(String[] args) {
+
+    List<Chart> charts = new ArrayList<Chart>();
+    {
+      Chart chart = getLineChart();
+      chart.setTitle("Default axis");
+      charts.add(chart);
+    }
+    {
+      Chart chart = getLineChart();
+      chart.setTitle("sin(x) on second axis with title");
+      Series series = (Series) chart.getSeriesMap().get("y=sin(x)");
+      series.setYAxisGroup(1);
+      chart.setYAxisGroupTitle(1, "sin(x)");
+      charts.add(chart);
+    }
+
+    {
+      Chart chart = getAreaChart();
+      chart.setTitle("Default axis");
+      charts.add(chart);
+    }
+    //    {
+    //      Chart chart = getAreaChart();
+    //      chart.setTitle("b on second axis");
+    //      Series series = (Series) chart.getSeriesMap().get("b");
+    //      series.setYAxisGroup(1);
+    //      charts.add(chart);
+    //    }
+    {
+      Chart chart = getAreaChart();
+      chart.setTitle("all different axis, b & c axis on right");
+      Series series = (Series) chart.getSeriesMap().get("b");
+      series.setYAxisGroup(1);
+      series = (Series) chart.getSeriesMap().get("c");
+      series.setYAxisGroup(2);
+      chart.getStyler().setYAxisGroupPosition(1, Styler.YAxisPosition.Right);
+      chart.getStyler().setYAxisGroupPosition(2, Styler.YAxisPosition.Right);
+      charts.add(chart);
+    }
+
+    {
+      Chart chart = getCaregoryChart();
+      chart.setTitle("Default axis");
+      charts.add(chart);
+    }
+    {
+      Chart chart = getCaregoryChart();
+      chart.setTitle("b on second axis, b on right");
+      Series series = (Series) chart.getSeriesMap().get("b");
+      series.setYAxisGroup(1);
+      chart.getStyler().setYAxisGroupPosition(1, Styler.YAxisPosition.Right);
+      charts.add(chart);
+    }
+
+    {
+      Chart chart = getCategoryLineChart();
+      chart.setTitle("Default axis");
+      charts.add(chart);
+    }
+    {
+      Chart chart = getCategoryLineChart();
+      chart.setTitle("b&d on second axis");
+      Series series = (Series) chart.getSeriesMap().get("b");
+      series.setYAxisGroup(1);
+      series = (Series) chart.getSeriesMap().get("d");
+      series.setYAxisGroup(1);
+      chart.getStyler().setYAxisGroupPosition(1, Styler.YAxisPosition.Right);
+      charts.add(chart);
+    }
+
+    {
+      Chart chart = getBubleChart();
+      chart.setTitle("Default axis");
+      charts.add(chart);
+    }
+    {
+      Chart chart = getBubleChart();
+      chart.setTitle("b on second axis");
+      Series series = (Series) chart.getSeriesMap().get("b");
+      series.setYAxisGroup(1);
+      charts.add(chart);
+    }
+
+    // Chart chart = charts.get(3);
+    // charts.clear();
+    // charts.add(chart);
+    {
+      Chart chart = getLineChart();
+      chart.setTitle("Default axis on right");
+      chart.getStyler().setYAxisGroupPosition(0, Styler.YAxisPosition.Right);
+      charts.add(chart);
+    }
+
+    new SwingWrapper(charts).displayChartMatrix();
+  }
+
+  static Chart getLineChart() {
+    XYChart chart =
+        new XYChartBuilder().width(WIDTH).height(HEIGHT).xAxisTitle("X").yAxisTitle("Y").build();
+
+    // Customize Chart
+    chart.getStyler().setToolTipsEnabled(true);
+    chart.getStyler().setLegendPosition(LegendPosition.InsideNW);
+    // generates sine data
+    int size = 30;
+    List<Integer> xData = new ArrayList<Integer>();
+    List<Double> yData = new ArrayList<Double>();
+    List<Integer> xData2 = new ArrayList<Integer>();
+    List<Double> yData2 = new ArrayList<Double>();
+    for (int i = 0; i <= size; i++) {
+      double radians = (Math.PI / (size / 2) * i);
+      int x = i - size / 2;
+      xData.add(x);
+      yData.add(-1 * Math.sin(radians));
+      xData2.add(x);
+      yData2.add(-10 * Math.cos(radians));
+    }
+
+    // Series
+    chart.addSeries("y=sin(x)", xData, yData);
+    chart.addSeries("y=cos(x)", xData2, yData2);
+    return chart;
+  }
+
+  static Chart getAreaChart() {
+
+    // Create Chart
+    XYChart chart =
+        new XYChartBuilder()
+            .width(WIDTH)
+            .height(HEIGHT)
+            .title("Area chart")
+            .xAxisTitle("X")
+            .yAxisTitle("Y")
+            .build();
+
+    // Customize Chart
+    chart.getStyler().setLegendPosition(LegendPosition.InsideNW);
+    chart.getStyler().setAxisTitlesVisible(true);
+    chart.setYAxisGroupTitle(0, "a");
+    chart.setYAxisGroupTitle(1, "b");
+    chart.setYAxisGroupTitle(2, "c");
+
+    chart.getStyler().setDefaultSeriesRenderStyle(XYSeriesRenderStyle.Area);
+    chart.getStyler().setToolTipsEnabled(true);
+
+    // Series
+    chart.addSeries("a", new double[] {0, 3, 6, 9, 12}, new double[] {-1, 5, 9, 6, 5});
+    chart.addSeries("b", new double[] {1, 4, 7, 10, 13}, new double[] {-10, 50, 90, 60, 50});
+    chart.addSeries("c", new double[] {2, 5, 8, 11, 14}, new double[] {-100, 500, 900, 600, 500});
+
+    return chart;
+  }
+
+  static CategoryChart getCaregoryChart() {
+
+    // Create Chart
+    CategoryChart chart =
+        new CategoryChartBuilder()
+            .width(WIDTH)
+            .height(HEIGHT)
+            .title("Score Histogram")
+            .xAxisTitle("Mean")
+            .yAxisTitle("Count")
+            .build();
+
+    // Customize Chart
+    chart.getStyler().setLegendPosition(LegendPosition.InsideNW);
+    chart.getStyler().setAvailableSpaceFill(.96);
+    chart.getStyler().setPlotGridVerticalLinesVisible(false);
+    chart.getStyler().setToolTipsEnabled(true);
+    chart.getStyler().setToolTipType(Styler.ToolTipType.yLabels);
+
+    // Series
+    List<Integer> data = getGaussianData(1000);
+    ArrayList<Integer> data2 = new ArrayList<Integer>(10000);
+    // Add each item twice
+    for (Integer val : data) {
+      data2.add(val);
+      data2.add(val);
+    }
+    Histogram histogram1 = new Histogram(data, 10, -30, 30);
+    chart.addSeries("a", histogram1.getxAxisData(), histogram1.getyAxisData());
+    Histogram histogram2 = new Histogram(data2, 10, -30, 30);
+    chart.addSeries("b", histogram2.getxAxisData(), histogram2.getyAxisData());
+
+    return chart;
+  }
+
+  static List<Integer> getGaussianData(int count) {
+
+    List<Integer> data = new ArrayList<Integer>(count);
+    Random r = new Random();
+    for (int i = 0; i < count; i++) {
+      data.add((int) (r.nextGaussian() * 10));
+    }
+    return data;
+  }
+
+  static CategoryChart getCategoryLineChart() {
+
+    // Create Chart
+    CategoryChart chart =
+        new CategoryChartBuilder()
+            .width(WIDTH)
+            .height(HEIGHT)
+            .theme(ChartTheme.GGPlot2)
+            .title("ThreadPool Benchmark")
+            .xAxisTitle("Threads")
+            .yAxisTitle("Executions")
+            .build();
+
+    // Customize Chart
+    chart.getStyler().setDefaultSeriesRenderStyle(CategorySeriesRenderStyle.Line);
+    chart.getStyler().setXAxisLabelRotation(270);
+    chart.getStyler().setLegendPosition(LegendPosition.OutsideE);
+    chart.getStyler().setAvailableSpaceFill(0);
+    chart.getStyler().setOverlapped(true);
+    chart.getStyler().setToolTipsEnabled(true);
+
+    // Declare data
+    List<String> xAxisKeys =
+        Arrays.asList("release-0.5", "release-0.6", "release-0.7", "release-0.8", "release-0.9");
+    String[] seriesNames = new String[] {"a", "b", "c", "d"};
+
+    Integer[][] dataPerSeries =
+        new Integer[][] {
+          {5, 20, 15, 25, 35},
+          {10, 40, 30, 50, 70},
+          {20, 80, 60, 100, 140},
+          {45, 121, 151, 231, 381},
+        };
+
+    // Series
+    for (int i = 0; i < seriesNames.length; i++) {
+      chart.addSeries(seriesNames[i], xAxisKeys, Arrays.asList(dataPerSeries[i]));
+    }
+
+    return chart;
+  }
+
+  static BubbleChart getBubleChart() {
+
+    // Create Chart
+    BubbleChart chart =
+        new BubbleChartBuilder()
+            .width(WIDTH)
+            .height(HEIGHT)
+            .title("BubbleChart01")
+            .xAxisTitle("X")
+            .yAxisTitle("Y")
+            .build();
+    chart.getStyler().setToolTipsEnabled(true);
+    // Customize Chart
+
+    // Series
+    double[] xData = new double[] {1, 2.0, 3.0, 4.0};
+    double[] yData = new double[] {10, 4, 7, 7.7};
+    double[] bubbleData = new double[] {17, 40, 50, 51};
+
+    double[] yData2 = new double[] {10, 20, 30, 40};
+    double[] bubbleData2 = new double[] {37, 35, 80, 27};
+
+    chart.addSeries("a", xData, yData, bubbleData);
+    chart.addSeries("b", xData, yData2, bubbleData2);
+
+    return chart;
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue54_2.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue54_2.java
new file mode 100644
index 0000000000000000000000000000000000000000..c067242dfe576623559bd001d4c2a08d0593a537
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue54_2.java
@@ -0,0 +1,60 @@
+package org.knowm.xchart.standalone.issues;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYChartBuilder;
+import org.knowm.xchart.internal.chartpart.Chart;
+import org.knowm.xchart.internal.series.Series;
+import org.knowm.xchart.style.Styler.LegendPosition;
+import org.knowm.xchart.style.Styler.YAxisPosition;
+
+/** Create a Chart matrix */
+public class TestForIssue54_2 {
+
+  static final int WIDTH = 465;
+  static final int HEIGHT = 320;
+
+  public static void main(String[] args) {
+
+    Chart chart = getLineChart();
+    chart.setTitle("sin(x) on second axis with title");
+    Series series = (Series) chart.getSeriesMap().get("y=sin(x)");
+    series.setYAxisGroup(1);
+    chart.getStyler().setYAxisGroupPosition(1, YAxisPosition.Left);
+    chart.getStyler().setYAxisGroupPosition(0, YAxisPosition.Right);
+    chart.setYAxisGroupTitle(1, "sin(x)");
+
+    new SwingWrapper(chart).displayChart();
+  }
+
+  static Chart getLineChart() {
+
+    XYChart chart =
+        new XYChartBuilder().width(WIDTH).height(HEIGHT).xAxisTitle("X").yAxisTitle("Y").build();
+
+    // Customize Chart
+    chart.getStyler().setToolTipsEnabled(true);
+    chart.getStyler().setLegendPosition(LegendPosition.InsideNW);
+    // generates sine data
+    int size = 30;
+    List<Integer> xData = new ArrayList<Integer>();
+    List<Double> yData = new ArrayList<Double>();
+    List<Integer> xData2 = new ArrayList<Integer>();
+    List<Double> yData2 = new ArrayList<Double>();
+    for (int i = 0; i <= size; i++) {
+      double radians = (Math.PI / (size / 2) * i);
+      int x = i - size / 2;
+      xData.add(x);
+      yData.add(-1 * Math.sin(radians));
+      xData2.add(x);
+      yData2.add(-10 * Math.cos(radians));
+    }
+
+    // Series
+    chart.addSeries("y=sin(x)", xData, yData);
+    chart.addSeries("y=cos(x)", xData2, yData2);
+    return chart;
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue582.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue582.java
new file mode 100644
index 0000000000000000000000000000000000000000..da00904a76a76907b87f86b6ca71e6d22f054c17
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue582.java
@@ -0,0 +1,56 @@
+package org.knowm.xchart.standalone.issues;
+
+import java.text.ParseException;
+import java.util.Locale;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYChartBuilder;
+
+public class TestForIssue582 {
+
+  public static void main(String[] args) throws ParseException {
+
+    XYChart chart = getXYChart();
+    new SwingWrapper(chart).displayChart();
+  }
+
+  public static XYChart getXYChart() {
+    XYChart chart =
+        new XYChartBuilder()
+            .width(720)
+            .height(480)
+            .title("Deadlock Example")
+            .xAxisTitle("Count")
+            .yAxisTitle("Value")
+            .build();
+
+    // If the decimal places in the pattern is fewer than the largest data value, the deadlock
+    // occurs.
+    //        chart.getStyler().setDecimalPattern("#0.0000");
+    //        chart.getStyler().setDecimalPattern("#,###.00");
+
+    //        chart.getStyler().setDecimalPattern("$ #0.00");
+    //        chart.getStyler().setDecimalPattern("$ #0.000");
+
+    chart.getStyler().setLocale(Locale.ITALIAN);
+    chart.getStyler().setDecimalPattern("#0.000");
+
+    double[] xValues = new double[] {1, 2, 3, 4, 5, 6, 7, 8};
+
+    // The only value here is 0.001 which is one decimal place below the pattern.
+    double[] yValues = new double[] {0.0, 0.0, 0.0, 0.0, 0.001, 0.0, 0.0, 0.0};
+    //      double[] yValues =  new double[] { 0.0, 0.0, 0.0, 0.0, -0.001, 0.0, 0.0, 0.0};
+    //      double[] yValues =  new double[] { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
+    //      double[] yValues =  new double[] { 0.0001, 0.0, 0.0, 0.0, -0.001, 0.0, 0.0, 0.0};
+    //      double[] yValues =  new double[] { -0.0002, -0.0002, -0.0002, -0.0002, -0.001, -0.0002,
+    // -0.0002, -0.0002};
+    //        double[] yValues = new double[]{0.0002, 0.0002, 0.0002, 0.0002, 0.0001, 0.0002,
+    // 0.0002, 0.0002};
+    //        double[] yValues =  new double[] { 20.0002, 20.0002, 20.0002, 20.0002, 20.001,
+    // 20.0002, 20.0002, 20.0002};
+
+    chart.addSeries("main", xValues, yValues);
+
+    return chart;
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue617.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue617.java
new file mode 100644
index 0000000000000000000000000000000000000000..004777f079e4cad85757aa7d6e48bb015a97f88e
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue617.java
@@ -0,0 +1,34 @@
+package org.knowm.xchart.standalone.issues;
+
+import java.text.ParseException;
+import org.knowm.xchart.CategoryChart;
+import org.knowm.xchart.CategoryChartBuilder;
+import org.knowm.xchart.CategorySeries;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.style.Styler;
+
+public class TestForIssue617 {
+
+  public static void main(String[] args) throws ParseException {
+
+    CategoryChart chart = getCategoryChart();
+    new SwingWrapper(chart).displayChart();
+  }
+
+  public static CategoryChart getCategoryChart() {
+    double[] xData = new double[] {0.0, 1.0, 2.0, 3.0, 4.0};
+    double[] yData = new double[] {1.0, 2, 3.0, 4, 2.5};
+    double[] yData2 = new double[] {2.2, 2, 1.2, 3.1, 2.7};
+
+    // Create Chart
+    CategoryChartBuilder builder = new CategoryChartBuilder();
+    builder.title("Sample Chart").xAxisTitle("X").yAxisTitle("Y").theme(Styler.ChartTheme.Matlab);
+    CategoryChart chart = builder.build();
+    chart.getStyler().setDefaultSeriesRenderStyle(CategorySeries.CategorySeriesRenderStyle.Area);
+
+    chart.addSeries("y(x)", xData, yData);
+    chart.addSeries("y2(x)", xData, yData2);
+
+    return chart;
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue628.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue628.java
new file mode 100644
index 0000000000000000000000000000000000000000..1cbdf84682a1efca2dcbad4d21cd8cc892621865
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue628.java
@@ -0,0 +1,40 @@
+package org.knowm.xchart.standalone.issues;
+
+import java.awt.BasicStroke;
+import java.text.ParseException;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYChartBuilder;
+
+public class TestForIssue628 {
+
+  public static void main(String[] args) throws ParseException {
+
+    XYChart chart = getCategoryChart();
+    new SwingWrapper(chart).displayChart();
+  }
+
+  public static XYChart getCategoryChart() {
+
+    double[] xData = new double[] {0.0, 1.0, 2.0, 3.0, 4.0};
+    double[] yData = new double[] {1.0, 2, 3.0, 4, 2.5};
+    double[] yData2 = new double[] {2.2, 2, 1.2, 3.1, 2.7};
+
+    // Create Chart
+    XYChart chart = new XYChartBuilder().width(400).height(600).build();
+    chart
+        .getStyler()
+        .setPlotGridLinesStroke(
+            new BasicStroke(
+                (float) 3.125,
+                BasicStroke.CAP_BUTT,
+                BasicStroke.JOIN_BEVEL,
+                10,
+                new float[] {6.25f, 6.25f},
+                0));
+    chart.addSeries("y(x)", xData, yData);
+    chart.addSeries("y2(x)", xData, yData2);
+
+    return chart;
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue653.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue653.java
new file mode 100644
index 0000000000000000000000000000000000000000..2737e6de891a67dd65dd8552b306702b69ee3ce2
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue653.java
@@ -0,0 +1,33 @@
+package org.knowm.xchart.standalone.issues;
+
+import java.text.ParseException;
+import org.knowm.xchart.CategoryChart;
+import org.knowm.xchart.CategoryChartBuilder;
+import org.knowm.xchart.CategorySeries;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.style.Styler;
+
+public class TestForIssue653 {
+
+  public static void main(String[] args) throws ParseException {
+
+    CategoryChart chart = getCategoryChart();
+    new SwingWrapper(chart).displayChart();
+  }
+
+  public static CategoryChart getCategoryChart() {
+    double[] xData = new double[] {0.0, 1.0, 2.0, 3.0, 4.0};
+    double[] yData = new double[] {1.0, 2, 3.0, 4, 2.5};
+
+    // Create Chart
+    CategoryChartBuilder builder = new CategoryChartBuilder();
+    builder.title("Sample Chart").xAxisTitle("X").yAxisTitle("Y").theme(Styler.ChartTheme.Matlab);
+    CategoryChart chart = builder.build();
+    chart.getStyler().setDefaultSeriesRenderStyle(CategorySeries.CategorySeriesRenderStyle.Line);
+    //            .setYAxisMin(0.0);
+
+    chart.addSeries("y(x)", xData, yData);
+
+    return chart;
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue707.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue707.java
new file mode 100644
index 0000000000000000000000000000000000000000..d0ef22056b7a617425c1ae7cf86aabac6411770e
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue707.java
@@ -0,0 +1,33 @@
+package org.knowm.xchart.standalone.issues;
+
+import java.text.ParseException;
+import org.knowm.xchart.CategoryChart;
+import org.knowm.xchart.CategoryChartBuilder;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.style.Styler;
+
+public class TestForIssue707 {
+
+  public static void main(String[] args) throws ParseException {
+
+    CategoryChart chart = getCategoryChart();
+    new SwingWrapper(chart).displayChart();
+  }
+
+  public static CategoryChart getCategoryChart() {
+    double[] xData = new double[] {0.0, 1.0, 2.0, 3.0, 4.0};
+    double[] yData = new double[] {2.0, 1.5, 4.0, 3.77, 2.5};
+
+    // Create Chart
+    CategoryChartBuilder builder = new CategoryChartBuilder();
+    builder.title("Sample Chart").xAxisTitle("X").yAxisTitle("Y").theme(Styler.ChartTheme.Matlab);
+    CategoryChart chart = builder.build();
+    // chart.getStyler().setYAxisMin(1.0);
+    chart.getStyler().setLabelsVisible(true);
+    // .setDefaultSeriesRenderStyle(CategorySeries.CategorySeriesRenderStyle.Line).
+
+    chart.addSeries("y(x)", xData, yData);
+
+    return chart;
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue826.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue826.java
new file mode 100644
index 0000000000000000000000000000000000000000..d40709b23fd3563822a6a33150801431018bf19f
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue826.java
@@ -0,0 +1,57 @@
+package org.knowm.xchart.standalone.issues;
+
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import org.knowm.xchart.CategoryChart;
+import org.knowm.xchart.CategoryChartBuilder;
+import org.knowm.xchart.SwingWrapper;
+
+public class TestForIssue826 {
+
+  public static void main(String[] args) throws ParseException {
+
+    CategoryChart chart = getChart();
+    new SwingWrapper(chart).displayChart();
+  }
+
+  public static CategoryChart getChart() {
+    final CategoryChart chart =
+        new CategoryChartBuilder().width(600).height(400).xAxisTitle("X").yAxisTitle("Y").build();
+
+    ArrayList<String> years =
+        new ArrayList<String>(
+            Arrays.asList(
+                new String[] {
+                  "2012", "2013", "2014", "2015", "2016", "2017", "2018", "2019", "2020", "2021",
+                  "2022"
+                }));
+
+    ArrayList<Number> yAData =
+        new ArrayList<Number>(
+            Arrays.asList(
+                new Number[] {
+                  4438887, 4365843, 4050498, 4757380, 4429130, 4692889, 4354087, 4530343, 4572770,
+                  4150489, 4487793
+                }));
+
+    ArrayList<Number> yBData =
+        new ArrayList<Number>(
+            Arrays.asList(
+                new Number[] {
+                  3198714, 3144079, 2859215, 3430605, 3839149, 4042579, 3741823, 3890162, 3731367,
+                  3751216, 4008249
+                }));
+
+    chart.getStyler().setOverlapped(true);
+    chart.getStyler().setYAxisDecimalPattern("###,###.##");
+
+    chart.addSeries("A", years, yAData);
+    chart.addSeries("B", years, yBData);
+
+    //    chart.getStyler().setYAxisMin(2600000.0);
+    chart.setTitle(Double.toString(2600000.0));
+
+    return chart;
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue83.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue83.java
new file mode 100644
index 0000000000000000000000000000000000000000..aae0bcf6f6ec4615fc3afecfb3d6e1cbb9910e6e
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue83.java
@@ -0,0 +1,126 @@
+package org.knowm.xchart.standalone.issues;
+
+import java.io.IOException;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.style.Styler;
+import org.knowm.xchart.style.Styler.LegendPosition;
+import org.knowm.xchart.style.lines.SeriesLines;
+
+public class TestForIssue83 {
+
+  public static void main(String[] args) throws IOException {
+
+    final XYChart chart = new XYChart(500, 580);
+    final Styler styleManager = chart.getStyler();
+    styleManager.setLegendPosition(LegendPosition.InsideNW);
+    styleManager.setLegendVisible(false);
+
+    final double[] keys = {
+      101.6829700157669,
+      102.4741546172069,
+      101.56112372430265,
+      102.29668967750219,
+      102.1156928915296,
+      102.96288807133006,
+      102.85820232291313,
+      102.70416779932134,
+      102.75666703633065,
+      102.54695164724063,
+      102.64530701963236,
+      101.42229521418183,
+      102.6239220187132,
+      102.65392830689318,
+      101.3519528210374,
+      102.29890454069181,
+      101.45011555581048,
+      102.80876656547879,
+      102.9487829236201,
+      102.65658212119392,
+      102.5621808062546,
+      102.54679368788584,
+      101.44415451644835,
+      101.52360532420516,
+      102.7494132740427,
+      103.03755466140984,
+      102.75544822301157,
+      102.47525429542132,
+      102.63811088590982,
+      102.59191775294347,
+      101.32048881637581,
+      101.44482698818119,
+      102.80932781766394,
+      101.38219670988731,
+      101.46941028338044,
+      102.66645765488023,
+      101.79878506072832,
+      102.12919834900144,
+      102.65694786373456,
+      101.34087876032368,
+      102.35962292551396,
+      102.73324077985815,
+      101.6331900389947,
+      102.68657071464266,
+      102.31073017053264,
+      102.95034563173265,
+      101.56466092390214,
+      101.44263290542328,
+      102.54872192620866,
+      101.51961724673545,
+      101.56592215239088,
+      102.62299979115573,
+      102.16037884019369,
+      102.76241468528423,
+      103.06247033542299,
+      102.50392407673121,
+      102.71485878177548,
+      102.30595719462644,
+      101.83799733593067,
+      102.64446820738182,
+      102.95845141559543,
+      101.44913643540103,
+      102.62302475018619,
+      101.35064046209624,
+      102.49385977096229,
+      102.47902987190186,
+      102.6192546853896,
+      101.31787966105605,
+      102.61902499800594,
+      102.75304600782704,
+      102.66323038080031,
+      102.62927538605312,
+      101.41262366698777,
+      103.06302964768331,
+      103.01984694209135,
+      101.54079454702787,
+      101.7432632007971,
+      102.64746484983125,
+      102.94083129713017,
+      101.38693917529486,
+      102.28688939180357,
+      101.77714391046378,
+      102.61582509980576,
+      102.889235861335,
+      102.50686276405479,
+      103.09822940528373,
+      102.58948098022869,
+      102.70749156936542,
+      102.64387765680111,
+      102.75465208779484,
+      102.36218073405826
+    };
+    final double[] values = {
+      40.37, 40.59, 40.31, 40.4, 40.39, 40.52, 40.47, 40.56, 40.46, 40.53, 40.58, 40.34, 40.55,
+      40.58, 40.33, 40.44, 40.36, 40.57, 40.48, 40.53, 40.55, 40.53, 40.3, 40.31, 40.45, 40.49,
+      40.47, 40.59, 40.55, 40.55, 40.35, 40.32, 40.57, 40.33, 40.34, 40.57, 40.38, 40.39, 40.53,
+      40.33, 40.41, 40.56, 40.37, 40.46, 40.44, 40.47, 40.31, 40.36, 40.55, 40.36, 40.31, 40.6,
+      40.39, 40.46, 40.49, 40.42, 40.58, 40.44, 40.38, 40.53, 40.5, 40.32, 40.6, 40.33, 40.41,
+      40.41, 40.53, 40.35, 40.57, 40.46, 40.56, 40.55, 40.34, 40.49, 40.51, 40.32, 40.37, 40.57,
+      40.5, 40.35, 40.43, 40.38, 40.58, 40.52, 40.59, 40.49, 40.55, 40.56, 40.53, 40.47, 40.41
+    };
+    chart.addSeries("Results", keys, values).setLineStyle(SeriesLines.NONE);
+
+    // BitmapEncoder.saveBitmap(chart, "example", BitmapFormat.PNG);
+    new SwingWrapper(chart).displayChart();
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue98.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue98.java
new file mode 100644
index 0000000000000000000000000000000000000000..cc52c1d74901e04524392b3bed0577c054c6d626
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue98.java
@@ -0,0 +1,1292 @@
+package org.knowm.xchart.standalone.issues;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Date;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.style.Styler.LegendPosition;
+import org.knowm.xchart.style.XYStyler;
+
+public class TestForIssue98 {
+
+  public static void main(String[] args) throws IOException {
+
+    final XYChart chart = new XYChart(1920, 1080);
+    XYStyler manager = chart.getStyler();
+    manager.setLegendPosition(LegendPosition.InsideNW);
+    manager.setYAxisLogarithmic(true);
+
+    final double[] vals = {
+      1000.0,
+      1011.2756666666667,
+      1011.2176666666667,
+      1010.9306666666666,
+      1010.8726666666666,
+      1020.4256666666668,
+      1020.051,
+      1019.993,
+      1044.9753333333333,
+      1046.7806666666665,
+      1046.7226666666666,
+      1046.4189999999999,
+      1046.351,
+      1050.153,
+      1049.7366666666667,
+      1049.5720000000001,
+      1076.0839999999998,
+      1075.9203333333332,
+      1072.3443333333335,
+      1072.1806666666666,
+      1071.9886666666666,
+      1071.824,
+      1071.42,
+      960.1533333333334,
+      959.5923333333334,
+      959.2996666666667,
+      959.1426666666666,
+      959.0046666666667,
+      955.6723333333334,
+      955.183,
+      954.8903333333333,
+      954.7333333333333,
+      954.6003333333333,
+      963.2423333333334,
+      975.7539999999999,
+      1001.1123333333333,
+      1023.4456666666667,
+      1006.2573333333333,
+      1005.771,
+      1015.633,
+      1028.1663333333333,
+      1028.1043333333332,
+      1027.809,
+      1027.741,
+      1038.212,
+      1051.3436666666666,
+      978.3113333333333,
+      977.905,
+      1003.2906666666667,
+      1003.2326666666667,
+      1013.0886666666667,
+      1025.592,
+      1025.4416666666666,
+      1052.5756666666666,
+      1052.4119999999998,
+      1057.9143333333334,
+      1057.7496666666666,
+      1057.5576666666666,
+      1057.393,
+      1085.7983333333332,
+      1085.6346666666668,
+      1115.3433333333332,
+      1108.9850000000001,
+      1108.5486666666666,
+      1134.5343333333333,
+      1134.359,
+      1174.7926666666667,
+      1174.6039999999998,
+      1150.874,
+      1150.6846666666665,
+      1150.4776666666667,
+      1150.2883333333334,
+      1178.2469999999998,
+      1168.3136666666667,
+      1167.6680000000001,
+      1167.324,
+      1167.117,
+      1102.2586666666666,
+      1039.1013333333333,
+      1038.652,
+      967.1510000000001,
+      944.079,
+      943.8756666666666,
+      943.7723333333333,
+      943.5863333333334,
+      953.2123333333334,
+      965.2956666666666,
+      965.1096666666667,
+      988.8873333333333,
+      979.981,
+      979.6063333333334,
+      1002.9306666666666,
+      1002.8726666666666,
+      1002.569,
+      1012.231,
+      1043.37,
+      1036.2066666666667,
+      1035.8953333333334,
+      1035.8233333333333,
+      1035.7603333333334,
+      976.5866666666667,
+      921.9079999999999,
+      921.5183333333333,
+      904.1596666666667,
+      904.035,
+      903.8779999999999,
+      903.8299999999999,
+      923.2616666666667,
+      931.5886666666667,
+      931.5356666666667,
+      926.365,
+      926.069,
+      925.9343333333334,
+      925.6323333333333,
+      911.302,
+      910.8533333333332,
+      910.7733333333333,
+      910.6673333333333,
+      910.4103333333333,
+      930.4780000000001,
+      939.6479999999999,
+      950.6279999999999,
+      950.284,
+      907.885,
+      907.7123333333334,
+      907.6173333333334,
+      915.9603333333333,
+      928.0053333333333,
+      927.9523333333334,
+      948.621,
+      948.563,
+      932.2950000000001,
+      932.1173333333334,
+      932.0223333333333,
+      942.303,
+      942.1253333333334,
+      942.0303333333334,
+      941.8526666666667,
+      892.5393333333334,
+      892.4146666666667,
+      845.9303333333334,
+      836.8453333333333,
+      836.5203333333334,
+      836.2833333333333,
+      836.1563333333334,
+      836.05,
+      854.1569999999999,
+      871.7223333333334,
+      878.9573333333333,
+      878.5626666666667,
+      878.4826666666667,
+      878.3766666666667,
+      886.6836666666667,
+      897.5070000000001,
+      897.4590000000001,
+      852.96,
+      825.7466666666667,
+      825.572,
+      825.515,
+      825.4136666666666,
+      825.213,
+      824.425,
+      823.8863333333334,
+      823.7850000000001,
+      845.6823333333334,
+      845.581,
+      845.4523333333333,
+      853.2683333333333,
+      863.185,
+      853.6243333333334,
+      853.3399999999999,
+      853.098,
+      852.971,
+      853.7296666666666,
+      853.4313333333333,
+      842.5066666666667,
+      842.4003333333334,
+      842.2733333333333,
+      850.0083333333334,
+      859.9300000000001,
+      859.8236666666667,
+      872.124,
+      872.076,
+      871.8523333333333,
+      871.8043333333333,
+      880.3383333333334,
+      890.3033333333333,
+      890.2553333333333,
+      879.5313333333334,
+      879.4833333333333,
+      879.2513333333334,
+      903.06,
+      903.012,
+      902.7716666666666,
+      872.6686666666667,
+      872.6206666666667,
+      872.3820000000001,
+      872.3340000000001,
+      872.2033333333334,
+      893.8330000000001,
+      893.5263333333334,
+      893.3966666666668,
+      893.0906666666667,
+      913.3136666666667,
+      895.8443333333333,
+      895.521,
+      895.3913333333334,
+      895.0853333333334,
+      875.5963333333333,
+      875.2729999999999,
+      875.0943333333333,
+      883.4333333333334,
+      894.3183333333334,
+      894.2703333333334,
+      873.4303333333334,
+      873.3823333333333,
+      873.1586666666667,
+      873.1106666666667,
+      891.4123333333333,
+      891.3643333333333,
+      909.9556666666667,
+      903.2476666666666,
+      903.1996666666666,
+      902.9493333333334,
+      902.9013333333334,
+      902.748,
+      905.739,
+      905.4000000000001,
+      905.2703333333334,
+      904.9593333333333,
+      904.9063333333334,
+      924.8563333333334,
+      922.106,
+      921.653,
+      921.3786666666667,
+      921.2166666666667,
+      942.0233333333333,
+      929.362,
+      929.0146666666667,
+      928.8286666666667,
+      950.219,
+      926.499,
+      926.313,
+      926.218,
+      926.037,
+      935.549,
+      931.8346666666666,
+      931.4666666666667,
+      931.3420000000001,
+      882.4713333333333,
+      840.2733333333333,
+      840.162,
+      857.9526666666667,
+      857.8413333333333,
+      857.7126666666667,
+      857.6013333333333,
+      868.6953333333333,
+      868.3969999999999,
+      886.7443333333333,
+      886.6246666666666,
+      886.4626666666667,
+      882.0706666666667,
+      881.903,
+      881.808,
+      881.76,
+      881.5213333333334,
+      881.3193333333334,
+      899.7246666666666,
+      892.202,
+      891.7683333333333,
+      875.818,
+      875.6553333333334,
+      875.5603333333333,
+      894.3706666666667,
+      894.3226666666667,
+      883.3873333333333,
+      883.2963333333335,
+      883.1063333333334,
+      883.0583333333334,
+      901.9533333333334,
+      910.0173333333333,
+      921.509,
+      921.1716666666666,
+      895.423,
+      895.237,
+      895.142,
+      894.966,
+      903.4639999999999,
+      915.2373333333333,
+      915.0563333333333,
+      938.144,
+      947.5073333333333,
+      947.3263333333334,
+      947.2313333333334,
+      947.0453333333334,
+      967.7750000000001,
+      967.637,
+      988.2513333333334,
+      1005.309,
+      1005.251,
+      1004.9556666666666,
+      1004.8876666666666,
+      1004.738,
+      1004.4226666666667,
+      988.5020000000001,
+      987.9593333333333,
+      1010.7533333333333,
+      1010.55,
+      1010.455,
+      1010.397,
+      981.7636666666667,
+      981.3706666666667,
+      981.2326666666667,
+      980.9083333333333,
+      929.8093333333334,
+      882.8616666666667,
+      882.5043333333333,
+      877.172,
+      877.0043333333333,
+      876.9093333333333,
+      876.8613333333333,
+      895.9656666666667,
+      883.1846666666667,
+      883.1366666666667,
+      882.8896666666667,
+      882.8416666666667,
+      882.76,
+      882.4143333333334,
+      900.89,
+      901.761,
+      901.3396666666667,
+      901.009,
+      921.3843333333334,
+      921.2033333333334,
+      929.1926666666667,
+      929.0116666666668,
+      928.9166666666667,
+      928.7306666666667,
+      944.4926666666667,
+      944.1179999999999,
+      943.937,
+      943.6846666666667,
+      945.6313333333333,
+      945.2606666666667,
+      945.0746666666666,
+      944.8223333333333,
+      954.1863333333333,
+      965.8213333333333,
+      965.6883333333333,
+      986.7763333333334,
+      969.5906666666666,
+      969.22,
+      960.201,
+      960.0630000000001,
+      959.896,
+      959.758,
+      949.3096666666667,
+      948.8103333333333,
+      948.4696666666666,
+      948.3746666666666,
+      952.2166666666667,
+      951.842,
+      951.656,
+      942.1283333333333,
+      941.9423333333334,
+      941.8473333333334,
+      941.6613333333333,
+      941.233,
+      907.6756666666666,
+      907.1853333333333,
+      906.863,
+      906.768,
+      836.7793333333334,
+      836.4403333333333,
+      836.2083333333333,
+      836.0813333333333,
+      853.55,
+      831.1616666666666,
+      830.8773333333334,
+      830.6453333333334,
+      830.5183333333333,
+      830.417,
+      839.023,
+      838.644,
+      838.5640000000001,
+      838.4046666666667,
+      838.2776666666666,
+      838.1763333333333,
+      845.7063333333333,
+      873.3776666666668,
+      872.7196666666666,
+      872.6716666666666,
+      894.4626666666667,
+      894.4146666666667,
+      894.1676666666667,
+      904.4826666666667,
+      904.1593333333333,
+      904.1113333333333,
+      947.0813333333333,
+      947.0333333333333,
+      946.773,
+      946.715,
+      967.4366666666667,
+      967.2506666666667,
+      937.969,
+      937.783,
+      937.6880000000001,
+      958.54,
+      958.354,
+      958.259,
+      970.1756666666668,
+      970.0376666666666,
+      969.8706666666667,
+      969.7326666666667,
+      969.2703333333334,
+      938.0266666666666,
+      937.5273333333334,
+      937.1866666666667,
+      937.0916666666667,
+      942.8743333333334,
+      942.4996666666666,
+      942.3136666666667,
+      951.5666666666666,
+      963.1366666666667,
+      962.9506666666666,
+      939.7896666666667,
+      939.6036666666666,
+      939.5086666666666,
+      939.3226666666667,
+      962.3613333333334,
+      962.1753333333334,
+      962.5656666666666,
+      962.3796666666667,
+      962.1936666666667,
+      962.107,
+      971.219,
+      983.479,
+      992.6759999999999,
+      1004.7909999999999,
+      1004.4163333333333,
+      949.3723333333334,
+      949.1863333333333,
+      949.0913333333333,
+      948.9103333333334,
+      948.477,
+      974.2223333333334,
+      923.8556666666667,
+      923.4503333333332,
+      922.7743333333333,
+      922.5983333333334,
+      922.5033333333333,
+      930.8773333333334,
+      942.4623333333333,
+      884.9406666666666,
+      884.628,
+      884.5083333333334,
+      906.5566666666666,
+      906.394,
+      928.3499999999999,
+      897.2966666666666,
+      897.1256666666667,
+      897.0306666666668,
+      916.1236666666666,
+      915.9956666666667,
+      936.4266666666667,
+      936.2456666666667,
+      927.29,
+      927.104,
+      948.3223333333333,
+      948.1363333333334,
+      948.0413333333333,
+      956.9723333333334,
+      968.3373333333334,
+      968.1513333333334,
+      988.8346666666666,
+      993.9546666666668,
+      993.8176666666667,
+      993.6423333333333,
+      1002.8943333333333,
+      1016.786,
+      1016.6356666666666,
+      1040.49,
+      1040.2723333333333,
+      1039.3923333333332,
+      1039.1696666666667,
+      1064.7469999999998,
+      1064.5253333333333,
+      1064.4053333333331,
+      1064.1826666666666,
+      1088.152,
+      1112.3743333333332,
+      1080.9446666666668,
+      1080.4943333333335,
+      1080.2676666666666,
+      1079.9903333333332,
+      1079.7636666666667,
+      1107.7613333333334,
+      1133.9333333333334,
+      1120.908,
+      1120.2973333333334,
+      1119.9533333333334,
+      1147.2440000000001,
+      1147.0553333333332,
+      1146.8266666666666,
+      1157.4886666666666,
+      1173.2053333333333,
+      1200.7966666666666,
+      1179.3806666666667,
+      1178.8636666666666,
+      1178.5963333333334,
+      1178.2939999999999,
+      1190.019,
+      1205.399,
+      1205.125,
+      1232.6363333333334,
+      1272.501,
+      1242.5213333333334,
+      1241.9703333333332,
+      1241.6796666666667,
+      1241.4363333333333,
+      1271.7973333333334,
+      1301.1653333333334,
+      1300.5803333333333,
+      1234.4313333333334,
+      1193.0936666666666,
+      1192.7973333333334,
+      1192.6523333333334,
+      1204.5383333333334,
+      1222.1833333333334,
+      1221.9053333333331,
+      1221.7603333333332,
+      1251.2066666666667,
+      1231.916,
+      1231.6246666666666,
+      1231.4796666666666,
+      1260.3926666666666,
+      1260.0963333333334,
+      1259.943,
+      1293.4623333333334,
+      1293.161,
+      1245.4996666666666,
+      1245.0866666666666,
+      1244.788,
+      1277.8366666666666,
+      1277.538,
+      1270.1956666666665,
+      1269.8943333333332,
+      1269.7343333333333,
+      1272.0863333333332,
+      1271.6643333333332,
+      1303.3553333333334,
+      1243.6709999999998,
+      1243.2516666666666,
+      1272.4346666666665,
+      1272.12,
+      1271.96,
+      1271.6346666666668,
+      1260.825,
+      1260.167,
+      1259.539,
+      1259.48,
+      1294.5543333333335,
+      1294.1173333333334,
+      1367.0713333333333,
+      1360.838,
+      1360.1779999999999,
+      1394.2823333333333,
+      1393.9163333333333,
+      1305.6979999999999,
+      1305.2203333333332,
+      1256.1926666666668,
+      1255.7456666666667,
+      1289.9153333333334,
+      1289.4773333333333,
+      1324.7426666666665,
+      1324.4,
+      1250.6689999999999,
+      1250.2126666666668,
+      1175.0436666666667,
+      1174.6256666666668,
+      1108.5096666666668,
+      1108.13,
+      1136.2556666666665,
+      1135.9826666666668,
+      1169.0146666666667,
+      1168.7283333333335,
+      1199.373,
+      1198.9733333333334,
+      1228.898,
+      1228.4733333333334,
+      1265.8213333333333,
+      1309.2793333333334,
+      1308.6236666666668,
+      1297.5133333333333,
+      1297.0596666666665,
+      1331.5026666666668,
+      1331.155,
+      1345.8339999999998,
+      1364.2673333333332,
+      1363.7863333333335,
+      1397.8653333333332,
+      1397.371,
+      1434.2403333333334,
+      1433.8519999999999,
+      1433.662,
+      1478.2993333333334,
+      1477.7693333333332,
+      1518.2013333333334,
+      1523.7976666666668,
+      1523.3876666666665,
+      1523.181,
+      1576.6873333333333,
+      1576.0983333333334,
+      1593.2533333333333,
+      1616.2183333333332,
+      1615.6036666666666,
+      1670.999,
+      1609.8566666666666,
+      1609.237,
+      1608.6086666666667,
+      1649.3069999999998,
+      1668.552,
+      1667.78,
+      1713.7266666666667,
+      1678.83,
+      1678.4616666666666,
+      1678.1646666666666
+    };
+    final long[] time = {
+      1363651237500L,
+      1363980712500L,
+      1364331637500L,
+      1364418037500L,
+      1364504437500L,
+      1364585512500L,
+      1364590837500L,
+      1365109237500L,
+      1365165262500L,
+      1365190312500L,
+      1365195637500L,
+      1365454837500L,
+      1365627637500L,
+      1365795112500L,
+      1365800437500L,
+      1366146037500L,
+      1366207462500L,
+      1366232437500L,
+      1366399912500L,
+      1366405237500L,
+      1366664437500L,
+      1366750837500L,
+      1366923637500L,
+      1367004712500L,
+      1367010037500L,
+      1367355637500L,
+      1367442037500L,
+      1367528437500L,
+      1367609512500L,
+      1367614837500L,
+      1367960437500L,
+      1368046837500L,
+      1368133237500L,
+      1368176362500L,
+      1368176662500L,
+      1368189262500L,
+      1368192862500L,
+      1368214312500L,
+      1368219637500L,
+      1368555562500L,
+      1368557962500L,
+      1368565237500L,
+      1368651637500L,
+      1368738037500L,
+      1368771262500L,
+      1368771862500L,
+      1368819112500L,
+      1368824437500L,
+      1369146262500L,
+      1369170037500L,
+      1369233562500L,
+      1369234762500L,
+      1369256437500L,
+      1369332862500L,
+      1369342837500L,
+      1369423912500L,
+      1369688437500L,
+      1369774837500L,
+      1369861237500L,
+      1369922962500L,
+      1369947637500L,
+      1370009062500L,
+      1370028712500L,
+      1370034037500L,
+      1370357962500L,
+      1370379637500L,
+      1370534062500L,
+      1370552437500L,
+      1370633512500L,
+      1370898037500L,
+      1370984437500L,
+      1371070837500L,
+      1371219562500L,
+      1371238312500L,
+      1371243637500L,
+      1371589237500L,
+      1371675637500L,
+      1371715762500L,
+      1371721162500L,
+      1371762037500L,
+      1371818062500L,
+      1371843112500L,
+      1371848437500L,
+      1372107637500L,
+      1372194037500L,
+      1372297762500L,
+      1372298062500L,
+      1372366837500L,
+      1372431562500L,
+      1372447912500L,
+      1372453237500L,
+      1372766962500L,
+      1372798837500L,
+      1372885237500L,
+      1372945762500L,
+      1373027662500L,
+      1373052712500L,
+      1373317237500L,
+      1373403637500L,
+      1373490037500L,
+      1373496562500L,
+      1373516062500L,
+      1373576437500L,
+      1373657512500L,
+      1373662837500L,
+      1373922037500L,
+      1374008437500L,
+      1374065287500L,
+      1374132862500L,
+      1374181237500L,
+      1374262312500L,
+      1374526837500L,
+      1374699637500L,
+      1374786037500L,
+      1374867112500L,
+      1374872437500L,
+      1375131637500L,
+      1375218037500L,
+      1375304437500L,
+      1375372762500L,
+      1375373587500L,
+      1375376362500L,
+      1375390837500L,
+      1375471912500L,
+      1375477237500L,
+      1375736437500L,
+      1375873687500L,
+      1375878862500L,
+      1375909237500L,
+      1375976662500L,
+      1375995637500L,
+      1376076712500L,
+      1376427637500L,
+      1376600437500L,
+      1376681512500L,
+      1376686837500L,
+      1376946037500L,
+      1377032437500L,
+      1377131062500L,
+      1377205237500L,
+      1377231262500L,
+      1377286312500L,
+      1377291637500L,
+      1377637237500L,
+      1377723637500L,
+      1377810037500L,
+      1377865162500L,
+      1377877162500L,
+      1377891112500L,
+      1377896437500L,
+      1378155637500L,
+      1378242037500L,
+      1378307587500L,
+      1378309462500L,
+      1378328437500L,
+      1378479562500L,
+      1378495912500L,
+      1378501237500L,
+      1378760437500L,
+      1378846837500L,
+      1378933237500L,
+      1379100712500L,
+      1379106037500L,
+      1379451637500L,
+      1379527462500L,
+      1379538037500L,
+      1379624437500L,
+      1379667862500L,
+      1379668462500L,
+      1379705512500L,
+      1379710837500L,
+      1380056437500L,
+      1380229237500L,
+      1380310312500L,
+      1380315637500L,
+      1380915112500L,
+      1381179637500L,
+      1381266037500L,
+      1381320562500L,
+      1381320787500L,
+      1381352437500L,
+      1381519912500L,
+      1381525237500L,
+      1381870837500L,
+      1381957237500L,
+      1381993387500L,
+      1381994362500L,
+      1382043637500L,
+      1382124712500L,
+      1382130037500L,
+      1382475637500L,
+      1382537062500L,
+      1382562037500L,
+      1382648437500L,
+      1382729512500L,
+      1382734837500L,
+      1383080437500L,
+      1383166837500L,
+      1383253237500L,
+      1383334312500L,
+      1383339637500L,
+      1383771637500L,
+      1383858037500L,
+      1383918262500L,
+      1383939112500L,
+      1383944437500L,
+      1384376437500L,
+      1384462837500L,
+      1384543912500L,
+      1384549237500L,
+      1384981237500L,
+      1385047762500L,
+      1385058562500L,
+      1385067637500L,
+      1385148712500L,
+      1385154037500L,
+      1385413237500L,
+      1385499637500L,
+      1385565262500L,
+      1385586037500L,
+      1385747362500L,
+      1385753512500L,
+      1385758837500L,
+      1386018037500L,
+      1386190837500L,
+      1386277237500L,
+      1386358312500L,
+      1386363637500L,
+      1386709237500L,
+      1386795637500L,
+      1386882037500L,
+      1386929662500L,
+      1386963112500L,
+      1386968437500L,
+      1387400437500L,
+      1387486837500L,
+      1387547062500L,
+      1387567912500L,
+      1387573237500L,
+      1387918837500L,
+      1388158162500L,
+      1388172712500L,
+      1388178037500L,
+      1388437237500L,
+      1388523637500L,
+      1388755687500L,
+      1388777512500L,
+      1388782837500L,
+      1389128437500L,
+      1389159862500L,
+      1389361387500L,
+      1389646837500L,
+      1389708562500L,
+      1389733237500L,
+      1389819637500L,
+      1389906037500L,
+      1389987112500L,
+      1389992437500L,
+      1390403662500L,
+      1390424437500L,
+      1390510837500L,
+      1390591912500L,
+      1390597237500L,
+      1390856437500L,
+      1390942837500L,
+      1391029237500L,
+      1391115637500L,
+      1391190262500L,
+      1391196712500L,
+      1391202037500L,
+      1391801512500L,
+      1392066037500L,
+      1392152437500L,
+      1392184162500L,
+      1392325237500L,
+      1392406312500L,
+      1392411637500L,
+      1392670837500L,
+      1392757237500L,
+      1392821362500L,
+      1392821662500L,
+      1392823162500L,
+      1392843637500L,
+      1393011112500L,
+      1393016437500L,
+      1393275637500L,
+      1393362037500L,
+      1393438762500L,
+      1393438987500L,
+      1393448437500L,
+      1393610362500L,
+      1393615912500L,
+      1393621237500L,
+      1393966837500L,
+      1394053237500L,
+      1394055862500L,
+      1394139637500L,
+      1394199262500L,
+      1394220712500L,
+      1394226037500L,
+      1394485237500L,
+      1394571637500L,
+      1394658037500L,
+      1394744437500L,
+      1394825512500L,
+      1394830837500L,
+      1395164062500L,
+      1395176437500L,
+      1395262837500L,
+      1395349237500L,
+      1395430312500L,
+      1395435637500L,
+      1395781237500L,
+      1395867637500L,
+      1395945862500L,
+      1395951862500L,
+      1395954037500L,
+      1396035112500L,
+      1396040437500L,
+      1396386037500L,
+      1396558837500L,
+      1396614862500L,
+      1396639912500L,
+      1396645237500L,
+      1396904437500L,
+      1396990837500L,
+      1397077237500L,
+      1397163637500L,
+      1397214562500L,
+      1397244712500L,
+      1397250037500L,
+      1397595637500L,
+      1397663662500L,
+      1397682037500L,
+      1397849512500L,
+      1397854837500L,
+      1398114037500L,
+      1398286837500L,
+      1398454312500L,
+      1398459637500L,
+      1398805237500L,
+      1398978037500L,
+      1399059112500L,
+      1399064437500L,
+      1399410037500L,
+      1399496437500L,
+      1399567987500L,
+      1399569862500L,
+      1399582837500L,
+      1399640062500L,
+      1399663912500L,
+      1399669237500L,
+      1400268712500L,
+      1400533237500L,
+      1400619637500L,
+      1400706037500L,
+      1400873512500L,
+      1400878837500L,
+      1401310837500L,
+      1401397237500L,
+      1401478312500L,
+      1401483637500L,
+      1402002037500L,
+      1402083112500L,
+      1402088437500L,
+      1402347637500L,
+      1402434037500L,
+      1402606837500L,
+      1402687912500L,
+      1402693237500L,
+      1403125237500L,
+      1403211637500L,
+      1403292712500L,
+      1403298037500L,
+      1403730037500L,
+      1403816437500L,
+      1403879962500L,
+      1403897512500L,
+      1403902837500L,
+      1404248437500L,
+      1404334837500L,
+      1404421237500L,
+      1404502312500L,
+      1404507637500L,
+      1404766837500L,
+      1404853237500L,
+      1404939637500L,
+      1405026037500L,
+      1405091362500L,
+      1405091587500L,
+      1405112437500L,
+      1405458037500L,
+      1405519462500L,
+      1405544437500L,
+      1405630837500L,
+      1405711912500L,
+      1405717237500L,
+      1406149237500L,
+      1406301862500L,
+      1406322037500L,
+      1406581237500L,
+      1406667637500L,
+      1406708962500L,
+      1406754037500L,
+      1406921512500L,
+      1406926837500L,
+      1407186037500L,
+      1407252862500L,
+      1407272437500L,
+      1407358837500L,
+      1407526312500L,
+      1407531637500L,
+      1407790837500L,
+      1407877237500L,
+      1408050037500L,
+      1408131112500L,
+      1408136437500L,
+      1408568437500L,
+      1408654837500L,
+      1408735912500L,
+      1408741237500L,
+      1409086837500L,
+      1409119762500L,
+      1409120962500L,
+      1409173237500L,
+      1409340712500L,
+      1409346037500L,
+      1409691637500L,
+      1409778037500L,
+      1409833762500L,
+      1409864437500L,
+      1409945512500L,
+      1409950837500L,
+      1410296437500L,
+      1410382837500L,
+      1410431962500L,
+      1410432862500L,
+      1410461062500L,
+      1410461287500L,
+      1410469237500L,
+      1410550312500L,
+      1410555637500L,
+      1410814837500L,
+      1410901237500L,
+      1411074037500L,
+      1411130062500L,
+      1411130962500L,
+      1411155112500L,
+      1411160437500L,
+      1411506037500L,
+      1411592437500L,
+      1411648387500L,
+      1411709362500L,
+      1411759912500L,
+      1411765237500L,
+      1412197237500L,
+      1412215162500L,
+      1412283637500L,
+      1412340262500L,
+      1412364712500L,
+      1412370037500L,
+      1412629237500L,
+      1412777662500L,
+      1412802037500L,
+      1412887762500L,
+      1412888437500L,
+      1412969512500L,
+      1413234037500L,
+      1413309262500L,
+      1413320437500L,
+      1413406837500L,
+      1413452662500L,
+      1413452887500L,
+      1413493237500L,
+      1413546862500L,
+      1413574312500L,
+      1413579637500L,
+      1413838837500L,
+      1413896662500L,
+      1413896962500L,
+      1413925237500L,
+      1413986587500L,
+      1414098037500L,
+      1414179112500L,
+      1414443637500L,
+      1414506262500L,
+      1414530037500L,
+      1414616437500L,
+      1414702837500L,
+      1414758862500L,
+      1414759462500L,
+      1414783912500L,
+      1414789237500L,
+      1415134837500L,
+      1415221237500L,
+      1415307637500L,
+      1415367262500L,
+      1415367562500L,
+      1415388712500L,
+      1415394037500L,
+      1415739637500L,
+      1415800462500L,
+      1415826037500L,
+      1415912437500L,
+      1415914162500L,
+      1415914462500L,
+      1415982562500L,
+      1415993512500L,
+      1415998837500L,
+      1416344437500L,
+      1416430837500L,
+      1416467587500L,
+      1416468862500L,
+      1416517237500L,
+      1416566362500L,
+      1416576862500L,
+      1416598312500L,
+      1416603637500L,
+      1416949237500L,
+      1417035637500L,
+      1417101562500L,
+      1417101862500L,
+      1417122037500L,
+      1417188562500L,
+      1417203112500L,
+      1417208437500L,
+      1417467637500L,
+      1417530862500L,
+      1417531462500L,
+      1417554037500L,
+      1417640437500L,
+      1417786462500L,
+      1417807912500L,
+      1417813237500L,
+      1418072437500L,
+      1418137687500L,
+      1418158837500L,
+      1418245237500L,
+      1418306062500L,
+      1418331637500L,
+      1418412712500L,
+      1418418037500L,
+      1418763637500L,
+      1418843062500L,
+      1418936437500L,
+      1419017512500L,
+      1419022837500L,
+      1419282037500L,
+      1419622312500L,
+      1419973237500L,
+      1420202062500L,
+      1420227112500L,
+      1420232437500L,
+      1420568362500L,
+      1420578037500L,
+      1420664437500L,
+      1420750837500L,
+      1420831912500L,
+      1420837237500L,
+      1421182837500L,
+      1421269237500L,
+      1421323762500L,
+      1421355637500L,
+      1421412262500L,
+      1421436712500L,
+      1421442037500L,
+      1421757262500L,
+      1421787637500L,
+      1421852662500L,
+      1421874037500L,
+      1422041512500L,
+      1422046837500L,
+      1422373162500L,
+      1422392437500L,
+      1422471862500L,
+      1422478837500L,
+      1422549262500L,
+      1422565237500L,
+      1422626062500L,
+      1422910837500L,
+      1422985762500L,
+      1422997237500L,
+      1423046962500L,
+      1423083637500L,
+      1423143262500L,
+      1423170037500L,
+      1423229662500L,
+      1423515637500L,
+      1423580362500L,
+      1423602037500L,
+      1423732762500L,
+      1423751662500L,
+      1423774837500L,
+      1423855912500L,
+      1424120437500L,
+      1424165962500L,
+      1424206837500L,
+      1424259862500L,
+      1424260162500L,
+      1424293237500L,
+      1424339662500L,
+      1424379637500L,
+      1424426962500L,
+      1424811637500L,
+      1424898037500L,
+      1424959462500L,
+      1424984437500L,
+      1425042562500L,
+      1425065512500L,
+      1425070837500L,
+      1425330037500L,
+      1425389662500L,
+      1425416437500L,
+      1425564787500L,
+      1425570562500L,
+      1425589237500L,
+      1425649462500L,
+      1425670312500L,
+      1425675637500L,
+      1426107637500L,
+      1426156162500L,
+      1426163662500L,
+      1426194037500L,
+      1426248562500L,
+      1426275112500L,
+      1426280437500L,
+      1426539637500L
+    };
+
+    final ArrayList<Double> values = new ArrayList<Double>(vals.length);
+    for (double v : vals) {
+      values.add(v);
+    }
+
+    final ArrayList<Date> dates = new ArrayList<Date>(time.length);
+    for (long t : time) {
+      dates.add(new Date(t));
+    }
+
+    chart.addSeries("Values", dates, values);
+    new SwingWrapper(chart).displayChart();
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/readme/AdvancedExample.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/readme/AdvancedExample.java
new file mode 100644
index 0000000000000000000000000000000000000000..e005fd8da27cf55e459d4cc283983c4f4eee5c20
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/readme/AdvancedExample.java
@@ -0,0 +1,65 @@
+package org.knowm.xchart.standalone.readme;
+
+import java.awt.BorderLayout;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.SwingConstants;
+import javax.swing.WindowConstants;
+import org.knowm.xchart.XChartPanel;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYChartBuilder;
+import org.knowm.xchart.XYSeries.XYSeriesRenderStyle;
+import org.knowm.xchart.style.Styler.LegendPosition;
+
+public class AdvancedExample {
+
+  public static void main(String[] args) {
+
+    // Create Chart
+    final XYChart chart =
+        new XYChartBuilder()
+            .width(600)
+            .height(400)
+            .title("Area Chart")
+            .xAxisTitle("X")
+            .yAxisTitle("Y")
+            .build();
+
+    // Customize Chart
+    chart.getStyler().setLegendPosition(LegendPosition.InsideNE);
+    chart.getStyler().setDefaultSeriesRenderStyle(XYSeriesRenderStyle.Area);
+
+    // Series
+    chart.addSeries("a", new double[] {0, 3, 5, 7, 9}, new double[] {-3, 5, 9, 6, 5});
+    chart.addSeries("b", new double[] {0, 2, 4, 6, 9}, new double[] {-1, 6, 4, 0, 4});
+    chart.addSeries("c", new double[] {0, 1, 3, 8, 9}, new double[] {-2, -1, 1, 0, 1});
+
+    // Schedule a job for the event-dispatching thread:
+    // creating and showing this application's GUI.
+    javax.swing.SwingUtilities.invokeLater(
+        new Runnable() {
+
+          @Override
+          public void run() {
+
+            // Create and set up the window.
+            JFrame frame = new JFrame("Advanced Example");
+            frame.setLayout(new BorderLayout());
+            frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
+
+            // chart
+            JPanel chartPanel = new XChartPanel<XYChart>(chart);
+            frame.add(chartPanel, BorderLayout.CENTER);
+
+            // label
+            JLabel label = new JLabel("Blah blah blah.", SwingConstants.CENTER);
+            frame.add(label, BorderLayout.SOUTH);
+
+            // Display the window.
+            frame.pack();
+            frame.setVisible(true);
+          }
+        });
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/readme/IntermediateExample.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/readme/IntermediateExample.java
new file mode 100644
index 0000000000000000000000000000000000000000..3b377bf8049e6762c228c08a0a17190d1e67ede5
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/readme/IntermediateExample.java
@@ -0,0 +1,54 @@
+package org.knowm.xchart.standalone.readme;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Random;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYChartBuilder;
+import org.knowm.xchart.XYSeries;
+import org.knowm.xchart.XYSeries.XYSeriesRenderStyle;
+import org.knowm.xchart.style.Styler.LegendPosition;
+import org.knowm.xchart.style.markers.SeriesMarkers;
+
+public class IntermediateExample {
+
+  static final Random random = new Random();
+
+  public static void main(String[] args) {
+
+    // Create Chart
+    XYChart chart =
+        new XYChartBuilder()
+            .width(600)
+            .height(500)
+            .title("Gaussian Blobs")
+            .xAxisTitle("X")
+            .yAxisTitle("Y")
+            .build();
+
+    // Customize Chart
+    chart.getStyler().setDefaultSeriesRenderStyle(XYSeriesRenderStyle.Scatter);
+    chart.getStyler().setChartTitleVisible(false);
+    chart.getStyler().setLegendPosition(LegendPosition.InsideSW);
+    chart.getStyler().setMarkerSize(16);
+
+    // Series
+    chart.addSeries("Gaussian Blob 1", getGaussian(1000, 1, 10), getGaussian(1000, 1, 10));
+    XYSeries series =
+        chart.addSeries("Gaussian Blob 2", getGaussian(1000, 1, 10), getGaussian(1000, 0, 5));
+    series.setMarker(SeriesMarkers.DIAMOND);
+
+    new SwingWrapper(chart).displayChart();
+  }
+
+  private static List<Double> getGaussian(int number, double mean, double std) {
+
+    List<Double> seriesData = new LinkedList<Double>();
+    for (int i = 0; i < number; i++) {
+      seriesData.add(mean + std * random.nextGaussian());
+    }
+
+    return seriesData;
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/readme/SimpleRealTime.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/readme/SimpleRealTime.java
new file mode 100644
index 0000000000000000000000000000000000000000..2666b48c4da9aba1fb5bba60e2123da9ef74bb0e
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/readme/SimpleRealTime.java
@@ -0,0 +1,48 @@
+package org.knowm.xchart.standalone.readme;
+
+import org.knowm.xchart.QuickChart;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+
+/** Creates a simple real-time chart */
+public class SimpleRealTime {
+
+  public static void main(String[] args) throws Exception {
+
+    double phase = 0;
+    double[][] initdata = getSineData(phase);
+
+    // Create Chart
+    final XYChart chart =
+        QuickChart.getChart(
+            "Simple XChart Real-time Demo", "Radians", "Sine", "sine", initdata[0], initdata[1]);
+
+    // Show it
+    final SwingWrapper<XYChart> sw = new SwingWrapper<XYChart>(chart);
+    sw.displayChart();
+
+    while (true) {
+
+      phase += 2 * Math.PI * 2 / 20.0;
+
+      Thread.sleep(100);
+
+      final double[][] data = getSineData(phase);
+
+      chart.updateXYSeries("sine", data[0], data[1], null);
+      sw.repaintChart();
+    }
+  }
+
+  private static double[][] getSineData(double phase) {
+
+    double[] xData = new double[100];
+    double[] yData = new double[100];
+    for (int i = 0; i < xData.length; i++) {
+      double radians = phase + (2 * Math.PI / xData.length * i);
+      xData[i] = radians;
+      yData[i] = Math.sin(radians);
+    }
+    return new double[][] {xData, yData};
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/readme/SimplestExample.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/readme/SimplestExample.java
new file mode 100644
index 0000000000000000000000000000000000000000..84568646bc0bbc4216f0472dafd652964c5d19e2
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/readme/SimplestExample.java
@@ -0,0 +1,29 @@
+package org.knowm.xchart.standalone.readme;
+
+import java.io.IOException;
+import org.knowm.xchart.BitmapEncoder;
+import org.knowm.xchart.BitmapEncoder.BitmapFormat;
+import org.knowm.xchart.QuickChart;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+
+public class SimplestExample {
+
+  public static void main(String[] args) throws IOException {
+
+    double[] xData = new double[] {0.0, 1.0, 2.0};
+    double[] yData = new double[] {2.0, 1.0, 0.0};
+
+    // Create Chart
+    XYChart chart = QuickChart.getChart("Sample Chart", "X", "Y", "y(x)", xData, yData);
+
+    // Show it
+    new SwingWrapper<XYChart>(chart).displayChart();
+
+    // Save it
+    BitmapEncoder.saveBitmap(chart, "./Sample_Chart", BitmapFormat.PNG);
+
+    // or save it in high-res
+    BitmapEncoder.saveBitmapWithDPI(chart, "./Sample_Chart_300_DPI", BitmapFormat.PNG, 300);
+  }
+}
diff --git a/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/readme/SwingWorkerRealTime.java b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/readme/SwingWorkerRealTime.java
new file mode 100644
index 0000000000000000000000000000000000000000..de208cbcdfa5354fa6751a8b1e8c3250908b2157
--- /dev/null
+++ b/XChart/xchart-demo/src/main/java/org/knowm/xchart/standalone/readme/SwingWorkerRealTime.java
@@ -0,0 +1,101 @@
+package org.knowm.xchart.standalone.readme;
+
+import java.util.LinkedList;
+import java.util.List;
+import javax.swing.SwingWorker;
+import org.knowm.xchart.QuickChart;
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+
+/** Creates a real-time chart using SwingWorker */
+public class SwingWorkerRealTime {
+
+  MySwingWorker mySwingWorker;
+  SwingWrapper<XYChart> sw;
+  XYChart chart;
+
+  public static void main(String[] args) throws Exception {
+
+    SwingWorkerRealTime swingWorkerRealTime = new SwingWorkerRealTime();
+    swingWorkerRealTime.go();
+  }
+
+  private void go() {
+
+    // Create Chart
+    chart =
+        QuickChart.getChart(
+            "SwingWorker XChart Real-time Demo",
+            "Time",
+            "Value",
+            "randomWalk",
+            new double[] {0},
+            new double[] {0});
+    chart.getStyler().setLegendVisible(false);
+    chart.getStyler().setXAxisTicksVisible(false);
+
+    // Show it
+    sw = new SwingWrapper<XYChart>(chart);
+    sw.displayChart();
+
+    mySwingWorker = new MySwingWorker();
+    mySwingWorker.execute();
+  }
+
+  private class MySwingWorker extends SwingWorker<Boolean, double[]> {
+
+    final LinkedList<Double> fifo = new LinkedList<Double>();
+
+    public MySwingWorker() {
+
+      fifo.add(0.0);
+    }
+
+    @Override
+    protected Boolean doInBackground() throws Exception {
+
+      while (!isCancelled()) {
+
+        fifo.add(fifo.get(fifo.size() - 1) + Math.random() - .5);
+        if (fifo.size() > 500) {
+          fifo.removeFirst();
+        }
+
+        double[] array = new double[fifo.size()];
+        for (int i = 0; i < fifo.size(); i++) {
+          array[i] = fifo.get(i);
+        }
+        publish(array);
+
+        try {
+          Thread.sleep(5);
+        } catch (InterruptedException e) {
+          // eat it. caught when interrupt is called
+          System.out.println("MySwingWorker shut down.");
+        }
+      }
+
+      return true;
+    }
+
+    @Override
+    protected void process(List<double[]> chunks) {
+
+      System.out.println("number of chunks: " + chunks.size());
+
+      double[] mostRecentDataSet = chunks.get(chunks.size() - 1);
+
+      chart.updateXYSeries("randomWalk", null, mostRecentDataSet, null);
+      sw.repaintChart();
+
+      long start = System.currentTimeMillis();
+      long duration = System.currentTimeMillis() - start;
+      try {
+        Thread.sleep(40 - duration); // 40 ms ==> 25fps
+        // Thread.sleep(400 - duration); // 40 ms ==> 2.5fps
+      } catch (InterruptedException e) {
+        System.out.println("InterruptedException occurred.");
+      }
+    }
+  }
+}
diff --git a/XChart/xchart-demo/src/test/java/org/knowm/xchart/DemoChartsTest.java b/XChart/xchart-demo/src/test/java/org/knowm/xchart/DemoChartsTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..20d3458ac1cae3c768106c5c0eb06e0c488eb241
--- /dev/null
+++ b/XChart/xchart-demo/src/test/java/org/knowm/xchart/DemoChartsTest.java
@@ -0,0 +1,55 @@
+package org.knowm.xchart;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.stream.Stream;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.knowm.xchart.demo.DemoChartsUtil;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.demo.charts.date.DateChart01;
+import org.knowm.xchart.internal.chartpart.Chart;
+import org.knowm.xchart.internal.chartpart.Cursor;
+import org.knowm.xchart.internal.chartpart.ToolTips;
+import org.knowm.xchart.internal.series.Series;
+import org.knowm.xchart.style.Styler;
+import org.knowm.xchart.style.XYStyler;
+
+public class DemoChartsTest {
+
+  private static final Collection<Class<?>> SKIPPED_EXAMPLE_CHARTS =
+      Arrays.asList(
+          // because it uses ChartZoom which is hard to institate in tests, esp. in headless
+          // environment
+          DateChart01.class);
+
+  public static Stream<ExampleChart<Chart<Styler, Series>>> chartDemos() {
+    return DemoChartsUtil.getAllDemoCharts().stream()
+        .filter(chart -> !SKIPPED_EXAMPLE_CHARTS.contains(chart.getClass()));
+  }
+
+  @ParameterizedTest
+  @MethodSource("chartDemos")
+  public void shouldNotFailWhenRenderingAsBitmap(ExampleChart<Chart<Styler, Series>> exampleChart)
+      throws IOException {
+    // given
+    Chart chart = exampleChart.getChart();
+    configureInteractiveFeatures(chart);
+
+    // when
+    BitmapEncoder.saveBitmap(chart, new ByteArrayOutputStream(), BitmapEncoder.BitmapFormat.PNG);
+
+    // test
+
+    // Don't fail
+  }
+
+  private void configureInteractiveFeatures(Chart chart) {
+    new ToolTips(chart);
+    if (chart instanceof XYChart && chart.getStyler() instanceof XYStyler) {
+      new Cursor(chart);
+    }
+  }
+}
diff --git a/XChart/xchart/pom.xml b/XChart/xchart/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..9d90b02224d1e1f6df4f6c63865a5c8003b6abf2
--- /dev/null
+++ b/XChart/xchart/pom.xml
@@ -0,0 +1,36 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+
+    <prerequisites>
+        <maven>3.9.0</maven>
+    </prerequisites>
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.knowm.xchart</groupId>
+        <artifactId>xchart-parent</artifactId>
+        <version>3.8.9-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>xchart</artifactId>
+
+    <name>XChart</name>
+    <description>The core XChart library</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>de.erichseifert.vectorgraphics2d</groupId>
+            <artifactId>VectorGraphics2D</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>de.rototor.pdfbox</groupId>
+            <artifactId>graphics2d</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.madgag</groupId>
+            <artifactId>animated-gif-lib</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/AnnotationImage.java b/XChart/xchart/src/main/java/org/knowm/xchart/AnnotationImage.java
new file mode 100644
index 0000000000000000000000000000000000000000..723f06f279b5210e2c1d555bf29d8fcf0df76987
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/AnnotationImage.java
@@ -0,0 +1,69 @@
+package org.knowm.xchart;
+
+import java.awt.Graphics2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
+import org.knowm.xchart.internal.chartpart.Annotation;
+import org.knowm.xchart.internal.chartpart.Chart;
+
+public class AnnotationImage extends Annotation {
+
+  // internal
+  private BufferedImage image;
+  protected double x;
+  protected double y;
+
+  /**
+   * Constructor
+   *
+   * @param image
+   * @param x
+   * @param y
+   * @param isValueInScreenSpace
+   */
+  public AnnotationImage(BufferedImage image, double x, double y, boolean isValueInScreenSpace) {
+    super(isValueInScreenSpace);
+    this.image = image;
+    this.x = x;
+    this.y = y;
+  }
+
+  public void init(Chart chart) {
+
+    super.init(chart);
+  }
+
+  @Override
+  public void paint(Graphics2D g) {
+
+    if (!isVisible) {
+      return;
+    }
+
+    int xOffset;
+    int yOffset;
+
+    if (isValueInScreenSpace) {
+      xOffset = (int) x - image.getWidth() / 2;
+      yOffset = chart.getHeight() - (int) y - image.getWidth() / 2;
+    } else {
+      xOffset = (int) (getXAxisScreenValue(x) + 0.5) - image.getWidth() / 2;
+      yOffset = (int) (getYAxisScreenValue(y) + 0.5) - image.getHeight() / 2;
+    }
+    g.drawImage(image, xOffset, yOffset, null);
+
+    bounds = new Rectangle2D.Double(xOffset, yOffset, image.getWidth(), image.getHeight());
+  }
+
+  public void setImage(BufferedImage image) {
+    this.image = image;
+  }
+
+  public void setX(double x) {
+    this.x = x;
+  }
+
+  public void setY(double y) {
+    this.y = y;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/AnnotationLine.java b/XChart/xchart/src/main/java/org/knowm/xchart/AnnotationLine.java
new file mode 100644
index 0000000000000000000000000000000000000000..d64a7550cc7958c28c51383d2e27d94c53caa154
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/AnnotationLine.java
@@ -0,0 +1,73 @@
+package org.knowm.xchart;
+
+import java.awt.Graphics2D;
+import java.awt.geom.Rectangle2D;
+import org.knowm.xchart.internal.chartpart.Annotation;
+
+public class AnnotationLine extends Annotation {
+
+  private final boolean isVertical;
+  private double value;
+
+  /**
+   * Constructor
+   *
+   * @param value
+   * @param isVertical
+   * @param isValueInScreenSpace
+   */
+  public AnnotationLine(double value, boolean isVertical, boolean isValueInScreenSpace) {
+    super(isValueInScreenSpace);
+    this.value = value;
+    this.isVertical = isVertical;
+  }
+
+  @Override
+  public void paint(Graphics2D g) {
+
+    if (!isVisible) {
+      return;
+    }
+
+    int lineWidth = (int) styler.getAnnotationLineStroke().getLineWidth();
+
+    int x1 = 0, x2 = 0, y1 = 0, y2 = 0;
+
+    if (isVertical) {
+      y1 = getYAxisScreenValueForMax() + lineWidth / 2;
+      y2 = getYAxisScreenValueForMin() - lineWidth / 2;
+    } else {
+      x1 = getXAxisScreenValueForMin() + lineWidth / 2;
+      x2 = getXAxisScreenValueForMax() - lineWidth / 2;
+    }
+
+    if (isValueInScreenSpace) {
+      if (isVertical) {
+        x1 = (int) value;
+        x2 = x1;
+      } else {
+        y1 = chart.getHeight() - (int) value;
+        y2 = y1;
+      }
+    } else {
+      if (isVertical) {
+        x1 = getXAxisScreenValue(value);
+        x2 = x1;
+      } else {
+        y1 = getYAxisScreenValue(value);
+        y2 = y1;
+      }
+    }
+
+    g.setStroke(styler.getAnnotationLineStroke());
+    g.setColor(styler.getAnnotationLineColor());
+    g.drawLine(x1, y1, x2, y2);
+
+    bounds =
+        new Rectangle2D.Double(x1, y1, Math.max(x2 - x1, lineWidth), Math.max(y2 - y1, lineWidth));
+  }
+
+  public void setValue(double value) {
+    this.value = value;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/AnnotationText.java b/XChart/xchart/src/main/java/org/knowm/xchart/AnnotationText.java
new file mode 100644
index 0000000000000000000000000000000000000000..3438780852a978f0dc75736291a8c6cce9422bb2
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/AnnotationText.java
@@ -0,0 +1,87 @@
+package org.knowm.xchart;
+
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.Shape;
+import java.awt.font.FontRenderContext;
+import java.awt.font.TextLayout;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Rectangle2D;
+import org.knowm.xchart.internal.chartpart.Annotation;
+
+public class AnnotationText extends Annotation {
+
+  private String text;
+  protected double x;
+  protected double y;
+
+  /**
+   * Constructor
+   *
+   * @param text
+   * @param x
+   * @param y
+   * @param isValueInScreenSpace
+   */
+  public AnnotationText(String text, double x, double y, boolean isValueInScreenSpace) {
+    super(isValueInScreenSpace);
+    this.text = text;
+    this.x = x;
+    this.y = y;
+  }
+
+  @Override
+  public void paint(Graphics2D g) {
+
+    if (!isVisible) {
+      return;
+    }
+
+    Object oldHint = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
+    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+
+    g.setColor(styler.getAnnotationTextFontColor());
+    g.setFont(styler.getAnnotationTextFont());
+
+    FontRenderContext frc = g.getFontRenderContext();
+    TextLayout tl = new TextLayout(text, styler.getAnnotationTextFont(), frc);
+    Shape shape = tl.getOutline(null);
+
+    Rectangle2D textBounds = shape.getBounds2D();
+
+    double xOffset;
+    double yOffset;
+
+    if (isValueInScreenSpace) {
+      xOffset = x - textBounds.getWidth() / 2;
+      yOffset = chart.getHeight() - y + textBounds.getHeight() / 2;
+    } else {
+      xOffset = getXAxisScreenValue(x) - textBounds.getWidth() / 2;
+      yOffset = getYAxisScreenValue(y) + textBounds.getHeight() / 2;
+    }
+
+    AffineTransform orig = g.getTransform();
+    AffineTransform at = new AffineTransform();
+    at.translate(xOffset, yOffset);
+    g.transform(at);
+    g.fill(shape);
+    g.setTransform(orig);
+
+    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, oldHint);
+
+    bounds =
+        new Rectangle2D.Double(xOffset, yOffset, textBounds.getWidth(), textBounds.getHeight());
+  }
+
+  public void setText(String text) {
+    this.text = text;
+  }
+
+  public void setX(double x) {
+    this.x = x;
+  }
+
+  public void setY(double y) {
+    this.y = y;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/AnnotationTextPanel.java b/XChart/xchart/src/main/java/org/knowm/xchart/AnnotationTextPanel.java
new file mode 100644
index 0000000000000000000000000000000000000000..e7ee080182fbf97b5fcc1753e96dec431e9b5fad
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/AnnotationTextPanel.java
@@ -0,0 +1,157 @@
+package org.knowm.xchart;
+
+import java.awt.Font;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.Shape;
+import java.awt.font.FontRenderContext;
+import java.awt.font.TextLayout;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Rectangle2D;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import org.knowm.xchart.internal.chartpart.Annotation;
+import org.knowm.xchart.internal.chartpart.Chart;
+
+public class AnnotationTextPanel extends Annotation {
+
+  private static final int MULTI_LINE_SPACE = 3;
+
+  private List<String> lines;
+  protected double x;
+  protected double y;
+
+  /**
+   * Constructor
+   *
+   * @param lines
+   * @param x
+   * @param y
+   * @param isValueInScreenSpace
+   */
+  public AnnotationTextPanel(String lines, double x, double y, boolean isValueInScreenSpace) {
+    super(isValueInScreenSpace);
+    this.lines = Arrays.asList(lines.split("\\n"));
+    this.x = x;
+    this.y = y;
+  }
+
+  public void init(Chart chart) {
+
+    super.init(chart);
+  }
+
+  @Override
+  public void paint(Graphics2D g) {
+
+    if (!isVisible) {
+      return;
+    }
+
+    // determine text content max width
+    double contentMaxWidth = 0;
+
+    // determine total content height
+    double contentHeight = 0;
+
+    Map<String, Rectangle2D> textBounds = getTextBounds(lines);
+
+    double entryHeight = 0; // could be multi-line
+    for (Map.Entry<String, Rectangle2D> entry : textBounds.entrySet()) {
+      entryHeight += entry.getValue().getHeight() + MULTI_LINE_SPACE;
+      contentMaxWidth = Math.max(contentMaxWidth, entry.getValue().getWidth());
+    }
+
+    entryHeight -= MULTI_LINE_SPACE; // subtract away the bottom MULTI_LINE_SPACE
+    contentHeight += entryHeight;
+
+    // determine content width
+    double contentWidth = styler.getAnnotationTextPanelPadding() + contentMaxWidth;
+
+    double width = contentWidth + 2 * styler.getAnnotationTextPanelPadding();
+    double height = contentHeight + 2 * styler.getAnnotationTextPanelPadding();
+
+    double xOffset;
+    double yOffset;
+
+    if (isValueInScreenSpace) {
+      xOffset = x;
+      yOffset = chart.getHeight() - height - y - 1;
+    } else {
+      xOffset = getXAxisScreenValue(x);
+      yOffset = getYAxisScreenValue(y) - height - 1;
+    }
+
+    xOffset = Math.min(xOffset, (chart.getWidth() - width - 1));
+    yOffset = Math.max(yOffset, 1);
+
+    bounds = new Rectangle2D.Double(xOffset, yOffset, width, height); // 0 indicates not sure yet.
+
+    // Draw info panel box background and border
+    Shape rect = new Rectangle2D.Double(xOffset, yOffset, width, height);
+    g.setColor(styler.getAnnotationTextPanelBackgroundColor());
+    g.fill(rect);
+    g.setStroke(SOLID_STROKE);
+    g.setColor(styler.getAnnotationTextPanelBorderColor());
+    g.draw(rect);
+
+    // Draw text onto panel box
+    Object oldHint = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
+    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+
+    g.setColor(styler.getAnnotationTextPanelFontColor());
+    g.setFont(styler.getAnnotationTextPanelFont());
+
+    xOffset = xOffset + styler.getAnnotationTextPanelPadding();
+    yOffset = yOffset + styler.getAnnotationTextPanelPadding();
+
+    double multiLineOffset = 0.0;
+
+    for (Map.Entry<String, Rectangle2D> entry : textBounds.entrySet()) {
+
+      double lineHeight = entry.getValue().getHeight();
+
+      FontRenderContext frc = g.getFontRenderContext();
+      TextLayout tl = new TextLayout(entry.getKey(), styler.getAnnotationTextPanelFont(), frc);
+      Shape shape = tl.getOutline(null);
+      AffineTransform orig = g.getTransform();
+      AffineTransform at = new AffineTransform();
+      at.translate(xOffset, yOffset + lineHeight + multiLineOffset);
+      g.transform(at);
+      g.fill(shape);
+      g.setTransform(orig);
+
+      multiLineOffset += lineHeight + MULTI_LINE_SPACE;
+    }
+    //    System.out.println("bounds = " + bounds);
+    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, oldHint);
+  }
+
+  private Map<String, Rectangle2D> getTextBounds(List<String> lines) {
+
+    Font infoPanelFont = styler.getAnnotationTextPanelFont();
+    Map<String, Rectangle2D> textBounds = new LinkedHashMap<>(lines.size());
+    for (String line : lines) {
+      TextLayout textLayout =
+          new TextLayout(line, infoPanelFont, new FontRenderContext(null, true, false));
+      Shape shape = textLayout.getOutline(null);
+      Rectangle2D bounds = shape.getBounds2D();
+      textBounds.put(line, bounds);
+    }
+    return textBounds;
+  }
+
+  public void setLines(List<String> lines) {
+    this.lines = lines;
+  }
+
+  public void setX(double x) {
+    this.x = x;
+  }
+
+  public void setY(double y) {
+    this.y = y;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/BitmapEncoder.java b/XChart/xchart/src/main/java/org/knowm/xchart/BitmapEncoder.java
new file mode 100644
index 0000000000000000000000000000000000000000..0e958b45d4d29dd1355ec8f92752c182c24cbd8d
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/BitmapEncoder.java
@@ -0,0 +1,311 @@
+package org.knowm.xchart;
+
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.geom.AffineTransform;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import javax.imageio.IIOImage;
+import javax.imageio.ImageIO;
+import javax.imageio.ImageTypeSpecifier;
+import javax.imageio.ImageWriteParam;
+import javax.imageio.ImageWriter;
+import javax.imageio.metadata.IIOInvalidTreeException;
+import javax.imageio.metadata.IIOMetadata;
+import javax.imageio.metadata.IIOMetadataNode;
+import javax.imageio.stream.FileImageOutputStream;
+import org.knowm.xchart.internal.chartpart.Chart;
+
+/** A helper class with static methods for saving Charts as bitmaps */
+public final class BitmapEncoder {
+
+  /** Constructor - Private constructor to prevent instantiation */
+  private BitmapEncoder() {}
+
+  /**
+   * Only adds the extension of the BitmapFormat to the filename if the filename doesn't already
+   * have it.
+   *
+   * @param fileName
+   * @param bitmapFormat
+   * @return filename (if extension already exists), otherwise;: filename + "." + extension
+   */
+  public static String addFileExtension(String fileName, BitmapFormat bitmapFormat) {
+
+    final String newFileExtension = "." + bitmapFormat.toString().toLowerCase();
+    final String fileNameWithFileExtension;
+    if (fileName.length() < newFileExtension.length()
+        || !fileName
+            .substring(fileName.length() - newFileExtension.length())
+            .equalsIgnoreCase(newFileExtension)) {
+      fileNameWithFileExtension = fileName + newFileExtension;
+    } else {
+      // This is to ensure the lower-case for the extension
+      fileNameWithFileExtension = fileName.substring(0, fileName.length() - newFileExtension.length()) + newFileExtension;
+    }
+    return fileNameWithFileExtension;
+  }
+
+  /**
+   * Save a Chart as an image file
+   *
+   * @param chart
+   * @param fileName
+   * @param bitmapFormat
+   * @throws IOException
+   */
+  public static <T extends Chart<?, ?>> void saveBitmap(
+      T chart, String fileName, BitmapFormat bitmapFormat) throws IOException {
+
+    try (OutputStream out = new FileOutputStream(addFileExtension(fileName, bitmapFormat))) {
+      saveBitmap(chart, out, bitmapFormat);
+    }
+  }
+
+  /**
+   * Write a Chart into a given stream. Does not close the target stream automatically at the end of
+   * the operation
+   *
+   * @param chart
+   * @param targetStream
+   * @param bitmapFormat
+   * @throws IOException
+   */
+  public static <T extends Chart<?, ?>> void saveBitmap(
+      T chart, OutputStream targetStream, BitmapFormat bitmapFormat) throws IOException {
+
+    BufferedImage bufferedImage = getBufferedImage(chart);
+    ImageIO.write(bufferedImage, bitmapFormat.toString().toLowerCase(), targetStream);
+  }
+
+  /**
+   * Save list of Charts as an image file. Function assumes that all charts are the same size
+   * (width, height). Number of charts should equal rows multiplied by cols.
+   *
+   * @param charts
+   * @param rows number of rows
+   * @param cols number of columns
+   * @param fileName
+   * @param bitmapFormat
+   * @throws IOException
+   */
+  public static <T extends Chart<?, ?>> void saveBitmap(
+      List<T> charts,
+      Integer rows,
+      Integer cols,
+      String fileName,
+      BitmapEncoder.BitmapFormat bitmapFormat)
+      throws IOException {
+
+    try (OutputStream out = new FileOutputStream(addFileExtension(fileName, bitmapFormat))) {
+      saveBitmap(charts, rows, cols, out, bitmapFormat);
+    }
+  }
+
+  /**
+   * Save list of Charts into a given stream. Does not close the target stream automatically at the
+   * end of the operation. Function assumes that all charts are the same size (width, height).
+   * Number of charts should equal rows multiplied by cols.
+   *
+   * @param charts
+   * @param rows number of rows
+   * @param cols number of columns
+   * @param targetStream
+   * @param bitmapFormat
+   * @throws IOException
+   */
+  public static <T extends Chart<?, ?>> void saveBitmap(
+      List<T> charts,
+      Integer rows,
+      Integer cols,
+      OutputStream targetStream,
+      BitmapEncoder.BitmapFormat bitmapFormat)
+      throws IOException {
+
+    List<BufferedImage> chartImages = new LinkedList<>();
+    for (T c : charts) chartImages.add(getBufferedImage(c));
+
+    BufferedImage bufferedImage = mergeImages(chartImages, rows, cols);
+    ImageIO.write(bufferedImage, bitmapFormat.toString().toLowerCase(), targetStream);
+  }
+
+  /**
+   * Save a chart as a PNG with a custom DPI. The default DPI is 72, which is fine for displaying
+   * charts on a computer monitor, but for printing charts, a DPI of around 300 is much better.
+   *
+   * @param chart
+   * @param fileName
+   * @param DPI
+   * @throws IOException
+   */
+  public static <T extends Chart<?, ?>> void saveBitmapWithDPI(
+      T chart, String fileName, BitmapFormat bitmapFormat, int DPI) throws IOException {
+
+    double scaleFactor = DPI / 72.0;
+
+    BufferedImage bufferedImage =
+        new BufferedImage(
+            (int) (chart.getWidth() * scaleFactor),
+            (int) (chart.getHeight() * scaleFactor),
+            BufferedImage.TYPE_INT_RGB);
+
+    Graphics2D graphics2D = bufferedImage.createGraphics();
+
+    AffineTransform at = graphics2D.getTransform();
+    at.scale(scaleFactor, scaleFactor);
+    graphics2D.setTransform(at);
+
+    chart.paint(graphics2D, chart.getWidth(), chart.getHeight());
+    Iterator<ImageWriter> writers =
+        ImageIO.getImageWritersByFormatName(bitmapFormat.toString().toLowerCase());
+    if (writers.hasNext()) {
+      ImageWriter writer = writers.next();
+      // instantiate an ImageWriteParam object with default compression options
+      ImageWriteParam iwp = writer.getDefaultWriteParam();
+
+      ImageTypeSpecifier typeSpecifier =
+          ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB);
+      IIOMetadata metadata = writer.getDefaultImageMetadata(typeSpecifier, iwp);
+      if (metadata.isReadOnly() || !metadata.isStandardMetadataFormatSupported()) {
+        throw new IllegalArgumentException(
+            "It is not possible to set the DPI on a bitmap with "
+                + bitmapFormat
+                + " format!! Try another format.");
+      }
+
+      setDPI(metadata, DPI);
+
+      File file = new File(addFileExtension(fileName, bitmapFormat));
+
+      try (FileImageOutputStream output = new FileImageOutputStream(file)) {
+        writer.setOutput(output);
+        IIOImage image = new IIOImage(bufferedImage, null, metadata);
+        writer.write(null, image, iwp);
+        writer.dispose();
+      }
+    }
+  }
+
+  /**
+   * Sets the metadata correctly
+   *
+   * @param metadata
+   * @param DPI
+   * @throws IIOInvalidTreeException
+   */
+  private static void setDPI(IIOMetadata metadata, int DPI) throws IIOInvalidTreeException {
+
+    // for PNG, it's dots per millimeter
+    double dotsPerMilli = 1.0 * DPI / 10 / 2.54;
+
+    IIOMetadataNode horiz = new IIOMetadataNode("HorizontalPixelSize");
+    horiz.setAttribute("value", Double.toString(dotsPerMilli));
+
+    IIOMetadataNode vert = new IIOMetadataNode("VerticalPixelSize");
+    vert.setAttribute("value", Double.toString(dotsPerMilli));
+
+    IIOMetadataNode dim = new IIOMetadataNode("Dimension");
+    dim.appendChild(horiz);
+    dim.appendChild(vert);
+
+    IIOMetadataNode root = new IIOMetadataNode("javax_imageio_1.0");
+    root.appendChild(dim);
+
+    metadata.mergeTree("javax_imageio_1.0", root);
+  }
+
+  /**
+   * Save a Chart as a JPEG file
+   *
+   * @param chart
+   * @param fileName
+   * @param quality - a float between 0 and 1 (1 = maximum quality)
+   * @throws IOException
+   */
+  public static <T extends Chart<?, ?>> void saveJPGWithQuality(
+      T chart, String fileName, float quality) throws IOException {
+
+    BufferedImage bufferedImage = getBufferedImage(chart);
+
+    Iterator<ImageWriter> iter = ImageIO.getImageWritersByFormatName("jpeg");
+    ImageWriter writer = iter.next();
+    // instantiate an ImageWriteParam object with default compression options
+    ImageWriteParam iwp = writer.getDefaultWriteParam();
+    iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
+    iwp.setCompressionQuality(quality);
+    File file = new File(fileName);
+
+    try (FileImageOutputStream output = new FileImageOutputStream(file)) {
+      writer.setOutput(output);
+      IIOImage image = new IIOImage(bufferedImage, null, null);
+      writer.write(null, image, iwp);
+      writer.dispose();
+    }
+  }
+
+  /**
+   * Generates a byte[] for a given chart
+   *
+   * @param chart
+   * @return a byte[] for a given chart
+   * @throws IOException
+   */
+  public static <T extends Chart<?, ?>> byte[] getBitmapBytes(T chart, BitmapFormat bitmapFormat)
+      throws IOException {
+
+    BufferedImage bufferedImage = getBufferedImage(chart);
+
+    byte[] imageInBytes;
+
+    try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+      ImageIO.write(bufferedImage, bitmapFormat.toString().toLowerCase(), baos);
+      baos.flush();
+      imageInBytes = baos.toByteArray();
+    }
+    return imageInBytes;
+  }
+
+  public static <T extends Chart<?, ?>> BufferedImage getBufferedImage(T chart) {
+
+    BufferedImage bufferedImage =
+        new BufferedImage(chart.getWidth(), chart.getHeight(), BufferedImage.TYPE_INT_RGB);
+    Graphics2D graphics2D = bufferedImage.createGraphics();
+    chart.paint(graphics2D, chart.getWidth(), chart.getHeight());
+    return bufferedImage;
+  }
+
+  private static BufferedImage mergeImages(List<BufferedImage> images, Integer rows, Integer cols) {
+
+    BufferedImage first = images.get(0);
+    int singleImageWidth = first.getWidth();
+    int singleImageHeight = first.getHeight();
+    int totalWidth = singleImageWidth * cols;
+    int totalHeight = singleImageHeight * rows;
+    BufferedImage mergedImage =
+        new BufferedImage(totalWidth, totalHeight, BufferedImage.TYPE_INT_ARGB);
+
+    Graphics g = mergedImage.getGraphics();
+    for (int row = 0; row < rows; row++) {
+      for (int col = 0; col < cols; col++) {
+        BufferedImage image = images.get(row * cols + col);
+        g.drawImage(image, col * singleImageWidth, row * singleImageHeight, null);
+      }
+    }
+
+    return mergedImage;
+  }
+
+  public enum BitmapFormat {
+    PNG,
+    JPG,
+    BMP,
+    GIF
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/BoxChart.java b/XChart/xchart/src/main/java/org/knowm/xchart/BoxChart.java
new file mode 100644
index 0000000000000000000000000000000000000000..af29f35ea0c93f6e17ed68206a917dfb9b9ac590
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/BoxChart.java
@@ -0,0 +1,160 @@
+package org.knowm.xchart;
+
+import java.awt.Graphics2D;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import org.knowm.xchart.internal.Utils;
+import org.knowm.xchart.internal.chartpart.AxisPair;
+import org.knowm.xchart.internal.chartpart.Chart;
+import org.knowm.xchart.internal.chartpart.Legend_Marker;
+import org.knowm.xchart.internal.chartpart.Plot_Box;
+import org.knowm.xchart.internal.series.Series.DataType;
+import org.knowm.xchart.internal.style.SeriesColorMarkerLineStyle;
+import org.knowm.xchart.internal.style.SeriesColorMarkerLineStyleCycler;
+import org.knowm.xchart.style.BoxStyler;
+import org.knowm.xchart.style.Styler.ChartTheme;
+import org.knowm.xchart.style.theme.Theme;
+
+public class BoxChart extends Chart<BoxStyler, BoxSeries> {
+
+  private final List<String> xData = new ArrayList<>();
+
+  protected BoxChart(int width, int height) {
+
+    super(width, height, new BoxStyler());
+    axisPair = new AxisPair<BoxStyler, BoxSeries>(this);
+    plot = new Plot_Box<BoxStyler, BoxSeries>(this);
+    legend = new Legend_Marker<BoxStyler, BoxSeries>(this);
+  }
+
+  public BoxChart(int width, int height, Theme theme) {
+
+    this(width, height);
+    styler.setTheme(theme);
+    // Box chart Legend does not show
+    styler.setLegendVisible(false);
+  }
+
+  public BoxChart(int width, int height, ChartTheme chartTheme) {
+    this(width, height, chartTheme.newInstance(chartTheme));
+  }
+
+  public BoxChart(BoxChartBuilder chartBuilder) {
+    this(chartBuilder.width, chartBuilder.height, chartBuilder.chartTheme);
+    setTitle(chartBuilder.title);
+    setXAxisTitle(chartBuilder.xAxisTitle);
+    setYAxisTitle(chartBuilder.yAxisTitle);
+  }
+
+  public BoxSeries addSeries(String seriesName, int[] yData) {
+
+    return addSeries(seriesName, Utils.getNumberListFromIntArray(yData));
+  }
+
+  public BoxSeries addSeries(String seriesName, double[] yData) {
+
+    return addSeries(seriesName, Utils.getNumberListFromDoubleArray(yData));
+  }
+
+  public BoxSeries addSeries(String seriesName, List<? extends Number> yData) {
+
+    // Sanity checks
+    sanityCheck(seriesName, yData);
+    xData.add(seriesName);
+    BoxSeries series = new BoxSeries(seriesName, xData, yData, null, DataType.String);
+    seriesMap.put(seriesName, series);
+    return series;
+  }
+
+  private void sanityCheck(String seriesName, List<? extends Number> yData) {
+
+    if (seriesMap.containsKey(seriesName)) {
+      throw new IllegalArgumentException(
+          "Series name > "
+              + seriesName
+              + " < has already been used. Use unique names for each series!!!");
+    }
+
+    sanityCheckYData(yData);
+  }
+
+  private void sanityCheckYData(List<? extends Number> yData) {
+
+    if (yData == null) {
+      throw new IllegalArgumentException("Y-Axis data connot be null !!!");
+    }
+    if (yData.size() == 0) {
+      throw new IllegalArgumentException("Y-Axis data connot be empyt !!!");
+    }
+    if (yData.contains(null)) {
+      throw new IllegalArgumentException("Y-Axis data cannot contain null !!!");
+    }
+  }
+
+  public BoxSeries updateBoxSeries(String seriesName, int[] newYData) {
+
+    return updateBoxSeries(seriesName, Utils.getNumberListFromIntArray(newYData));
+  }
+
+  public BoxSeries updateBoxSeries(String seriesName, double[] newYData) {
+
+    return updateBoxSeries(seriesName, Utils.getNumberListFromDoubleArray(newYData));
+  }
+
+  public BoxSeries updateBoxSeries(String seriesName, List<? extends Number> newYData) {
+
+    Map<String, BoxSeries> seriesMap = getSeriesMap();
+    BoxSeries series = seriesMap.get(seriesName);
+
+    if (series == null) {
+      throw new IllegalArgumentException("Series name > " + seriesName + " < not found !!!");
+    }
+    sanityCheckYData(newYData);
+    series.replaceData(newYData);
+    return series;
+  }
+
+  private void setSeriesStyles() {
+
+    SeriesColorMarkerLineStyleCycler seriesColorMarkerLineStyleCycler =
+        new SeriesColorMarkerLineStyleCycler(
+            getStyler().getSeriesColors(),
+            getStyler().getSeriesMarkers(),
+            getStyler().getSeriesLines());
+    SeriesColorMarkerLineStyle seriesColorMarkerLineStyle =
+        seriesColorMarkerLineStyleCycler.getNextSeriesColorMarkerLineStyle();
+    for (BoxSeries series : getSeriesMap().values()) {
+
+      if (series.getLineStyle() == null) { // wasn't set manually
+        series.setLineStyle(seriesColorMarkerLineStyle.getStroke());
+      }
+      if (series.getLineColor() == null) { // wasn't set manually
+        series.setLineColor(seriesColorMarkerLineStyle.getColor());
+      }
+      if (series.getFillColor() == null) { // wasn't set manually
+        series.setFillColor(seriesColorMarkerLineStyle.getColor());
+      }
+      if (series.getMarker() == null) { // wasn't set manually
+        series.setMarker(seriesColorMarkerLineStyle.getMarker());
+      }
+      if (series.getMarkerColor() == null) { // wasn't set manually
+        series.setMarkerColor(seriesColorMarkerLineStyle.getColor());
+      }
+    }
+  }
+
+  @Override
+  public void paint(Graphics2D g, int width, int height) {
+
+    setWidth(width);
+    setHeight(height);
+    setSeriesStyles();
+    paintBackground(g);
+
+    axisPair.paint(g);
+    plot.paint(g);
+    chartTitle.paint(g);
+    annotations.forEach(x -> x.paint(g));
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/BoxChartBuilder.java b/XChart/xchart/src/main/java/org/knowm/xchart/BoxChartBuilder.java
new file mode 100644
index 0000000000000000000000000000000000000000..54315094aad79eb36af8ab74aa11a52d02629c2b
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/BoxChartBuilder.java
@@ -0,0 +1,29 @@
+package org.knowm.xchart;
+
+import org.knowm.xchart.internal.ChartBuilder;
+
+public class BoxChartBuilder extends ChartBuilder<BoxChartBuilder, BoxChart> {
+
+  String xAxisTitle = "";
+  String yAxisTitle = "";
+
+  public BoxChartBuilder() {}
+
+  public BoxChartBuilder xAxisTitle(String xAxisTitle) {
+
+    this.xAxisTitle = xAxisTitle;
+    return this;
+  }
+
+  public BoxChartBuilder yAxisTitle(String yAxisTitle) {
+
+    this.yAxisTitle = yAxisTitle;
+    return this;
+  }
+
+  @Override
+  public BoxChart build() {
+
+    return new BoxChart(this);
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/BoxSeries.java b/XChart/xchart/src/main/java/org/knowm/xchart/BoxSeries.java
new file mode 100644
index 0000000000000000000000000000000000000000..1df934cd375d0a2df58a696a190ad902011fd651
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/BoxSeries.java
@@ -0,0 +1,24 @@
+package org.knowm.xchart;
+
+import java.util.List;
+import org.knowm.xchart.internal.chartpart.RenderableSeries.LegendRenderType;
+import org.knowm.xchart.internal.series.AxesChartSeriesCategory;
+
+public class BoxSeries extends AxesChartSeriesCategory {
+
+  public BoxSeries(
+      String name,
+      List<?> xData,
+      List<? extends Number> yData,
+      List<? extends Number> extraValues,
+      DataType xAxisDataType) {
+
+    super(name, xData, yData, extraValues, xAxisDataType);
+  }
+
+  @Override
+  public LegendRenderType getLegendRenderType() {
+
+    return null;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/BubbleChart.java b/XChart/xchart/src/main/java/org/knowm/xchart/BubbleChart.java
new file mode 100644
index 0000000000000000000000000000000000000000..e5e987a6f09963610f4fc1834ddf8b69d89ca4ae
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/BubbleChart.java
@@ -0,0 +1,261 @@
+package org.knowm.xchart;
+
+import java.awt.Graphics2D;
+import java.util.List;
+import java.util.Map;
+import org.knowm.xchart.internal.Utils;
+import org.knowm.xchart.internal.chartpart.AxisPair;
+import org.knowm.xchart.internal.chartpart.Chart;
+import org.knowm.xchart.internal.chartpart.Legend_Bubble;
+import org.knowm.xchart.internal.chartpart.Plot_Bubble;
+import org.knowm.xchart.internal.style.SeriesColorMarkerLineStyle;
+import org.knowm.xchart.internal.style.SeriesColorMarkerLineStyleCycler;
+import org.knowm.xchart.style.BubbleStyler;
+import org.knowm.xchart.style.Styler.ChartTheme;
+import org.knowm.xchart.style.theme.Theme;
+
+public class BubbleChart extends Chart<BubbleStyler, BubbleSeries> {
+
+  /**
+   * Constructor - the default Chart Theme will be used (XChartTheme)
+   *
+   * @param width
+   * @param height
+   */
+  public BubbleChart(int width, int height) {
+
+    super(width, height, new BubbleStyler());
+    axisPair = new AxisPair<BubbleStyler, BubbleSeries>(this);
+    plot = new Plot_Bubble<BubbleStyler, BubbleSeries>(this);
+    legend = new Legend_Bubble<BubbleStyler, BubbleSeries>(this);
+  }
+
+  /**
+   * Constructor
+   *
+   * @param width
+   * @param height
+   * @param theme - pass in a instance of Theme class, probably a custom Theme.
+   */
+  public BubbleChart(int width, int height, Theme theme) {
+
+    this(width, height);
+    styler.setTheme(theme);
+  }
+
+  /**
+   * Constructor
+   *
+   * @param width
+   * @param height
+   * @param chartTheme - pass in the desired ChartTheme enum
+   */
+  public BubbleChart(int width, int height, ChartTheme chartTheme) {
+
+    this(width, height, chartTheme.newInstance(chartTheme));
+  }
+
+  /**
+   * Constructor
+   *
+   * @param chartBuilder
+   */
+  public BubbleChart(BubbleChartBuilder chartBuilder) {
+
+    this(chartBuilder.width, chartBuilder.height, chartBuilder.chartTheme);
+    setTitle(chartBuilder.title);
+    setXAxisTitle(chartBuilder.xAxisTitle);
+    setYAxisTitle(chartBuilder.yAxisTitle);
+  }
+
+  /**
+   * Add a series for a Bubble type chart using using double arrays
+   *
+   * @param seriesName
+   * @param xData the X-Axis data
+   * @param yData the Y-Axis data
+   * @param bubbleData the bubble data
+   * @return A Series object that you can set properties on
+   */
+  public BubbleSeries addSeries(
+      String seriesName,
+      List<? extends Number> xData,
+      List<? extends Number> yData,
+      List<? extends Number> bubbleData) {
+
+    return addSeries(
+        seriesName,
+        Utils.getDoubleArrayFromNumberList(xData),
+        Utils.getDoubleArrayFromNumberList(yData),
+        Utils.getDoubleArrayFromNumberList(bubbleData));
+  }
+
+  /**
+   * Add a series for a Bubble type chart using using Lists
+   *
+   * @param seriesName
+   * @param xData the X-Axis data
+   * @param yData the Y-Axis data
+   * @param bubbleData the bubble data
+   * @return
+   */
+  public BubbleSeries addSeries(
+      String seriesName, double[] xData, double[] yData, double[] bubbleData) {
+
+    // Sanity checks
+    sanityCheck(seriesName, xData, yData, bubbleData);
+
+    BubbleSeries series;
+    if (xData != null) {
+
+      // Sanity check
+      if (xData.length != yData.length) {
+        throw new IllegalArgumentException("X and Y-Axis sizes are not the same!!!");
+      }
+
+      series = new BubbleSeries(seriesName, xData, yData, bubbleData);
+    } else { // generate xData
+      series =
+          new BubbleSeries(
+              seriesName, Utils.getGeneratedDataAsArray(yData.length), yData, bubbleData);
+    }
+
+    seriesMap.put(seriesName, series);
+
+    return series;
+  }
+
+  /**
+   * Update a series by updating the X-Axis, Y-Axis and bubble data
+   *
+   * @param seriesName
+   * @param newXData - set null to be automatically generated as a list of increasing Integers
+   *     starting from 1 and ending at the size of the new Y-Axis data list.
+   * @param newYData
+   * @param newBubbleData - set null if there are no error bars
+   * @return
+   */
+  public BubbleSeries updateBubbleSeries(
+      String seriesName,
+      List<?> newXData,
+      List<? extends Number> newYData,
+      List<? extends Number> newBubbleData) {
+
+    return updateBubbleSeries(
+        seriesName,
+        Utils.getDoubleArrayFromNumberList(newXData),
+        Utils.getDoubleArrayFromNumberList(newYData),
+        Utils.getDoubleArrayFromNumberList(newBubbleData));
+  }
+
+  /**
+   * Update a series by updating the X-Axis, Y-Axis and bubble data
+   *
+   * @param seriesName
+   * @param newXData - set null to be automatically generated as a list of increasing Integers
+   *     starting from 1 and ending at the size of the new Y-Axis data list.
+   * @param newYData
+   * @param newBubbleData - set null if there are no error bars
+   * @return
+   */
+  public BubbleSeries updateBubbleSeries(
+      String seriesName, double[] newXData, double[] newYData, double[] newBubbleData) {
+
+    Map<String, BubbleSeries> seriesMap = getSeriesMap();
+    BubbleSeries series = seriesMap.get(seriesName);
+    if (series == null) {
+      throw new IllegalArgumentException("Series name >" + seriesName + "< not found!!!");
+    }
+    if (newXData == null) {
+      double[] generatedXData = Utils.getGeneratedDataAsArray(newYData.length);
+      series.replaceData(generatedXData, newYData, newBubbleData);
+    } else {
+      series.replaceData(newXData, newYData, newBubbleData);
+    }
+
+    return series;
+  }
+
+  ///////////////////////////////////////////////////
+  // Internal Members and Methods ///////////////////
+  ///////////////////////////////////////////////////
+
+  private void sanityCheck(String seriesName, double[] xData, double[] yData, double[] bubbleData) {
+
+    if (seriesMap.containsKey(seriesName)) {
+      throw new IllegalArgumentException(
+          "Series name >"
+              + seriesName
+              + "< has already been used. Use unique names for each series!!!");
+    }
+    if (yData == null) {
+      throw new IllegalArgumentException("Y-Axis data cannot be null!!! >" + seriesName);
+    }
+    if (yData.length == 0) {
+      throw new IllegalArgumentException("Y-Axis data cannot be empty!!! >" + seriesName);
+    }
+    if (bubbleData == null) {
+      throw new IllegalArgumentException("Bubble data cannot be null!!! >" + seriesName);
+    }
+    if (bubbleData.length == 0) {
+      throw new IllegalArgumentException("Bubble data cannot be empty!!! >" + seriesName);
+    }
+    if (xData != null && xData.length == 0) {
+      throw new IllegalArgumentException("X-Axis data cannot be empty!!! >" + seriesName);
+    }
+    if (bubbleData.length != yData.length) {
+      throw new IllegalArgumentException(
+          "Bubble Data and Y-Axis sizes are not the same!!! >" + seriesName);
+    }
+  }
+
+  @Override
+  public void paint(Graphics2D g, int width, int height) {
+
+    setWidth(width);
+    setHeight(height);
+
+    // set the series types if they are not set. Legend and Plot need it.
+    for (BubbleSeries bubbleSeries : getSeriesMap().values()) {
+      BubbleSeries.BubbleSeriesRenderStyle seriesType =
+          bubbleSeries.getBubbleSeriesRenderStyle(); // would be directly set
+      if (seriesType == null) { // wasn't overridden, use default from Style Manager
+        bubbleSeries.setBubbleSeriesRenderStyle(getStyler().getDefaultSeriesRenderStyle());
+      }
+    }
+    setSeriesStyles();
+
+    paintBackground(g);
+
+    axisPair.paint(g);
+    plot.paint(g);
+    chartTitle.paint(g);
+    legend.paint(g);
+    annotations.forEach(x -> x.paint(g));
+  }
+
+  /** set the series color based on theme */
+  private void setSeriesStyles() {
+
+    SeriesColorMarkerLineStyleCycler seriesColorMarkerLineStyleCycler =
+        new SeriesColorMarkerLineStyleCycler(
+            getStyler().getSeriesColors(),
+            getStyler().getSeriesMarkers(),
+            getStyler().getSeriesLines());
+    for (BubbleSeries series : getSeriesMap().values()) {
+
+      SeriesColorMarkerLineStyle seriesColorMarkerLineStyle =
+          seriesColorMarkerLineStyleCycler.getNextSeriesColorMarkerLineStyle();
+
+      if (series.getLineStyle() == null) { // wasn't set manually
+        series.setLineStyle(seriesColorMarkerLineStyle.getStroke());
+      }
+      if (series.getLineColor() == null) { // wasn't set manually
+        series.setLineColor(seriesColorMarkerLineStyle.getColor());
+      }
+      if (series.getFillColor() == null) { // wasn't set manually
+        series.setFillColor(seriesColorMarkerLineStyle.getColor());
+      }
+    }
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/BubbleChartBuilder.java b/XChart/xchart/src/main/java/org/knowm/xchart/BubbleChartBuilder.java
new file mode 100644
index 0000000000000000000000000000000000000000..ebdd14cefcedec933178f539b2b16511b7bc1cf7
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/BubbleChartBuilder.java
@@ -0,0 +1,34 @@
+package org.knowm.xchart;
+
+import org.knowm.xchart.internal.ChartBuilder;
+
+public class BubbleChartBuilder extends ChartBuilder<BubbleChartBuilder, BubbleChart> {
+
+  String xAxisTitle = "";
+  String yAxisTitle = "";
+
+  public BubbleChartBuilder() {}
+
+  public BubbleChartBuilder xAxisTitle(String xAxisTitle) {
+
+    this.xAxisTitle = xAxisTitle;
+    return this;
+  }
+
+  public BubbleChartBuilder yAxisTitle(String yAxisTitle) {
+
+    this.yAxisTitle = yAxisTitle;
+    return this;
+  }
+
+  /**
+   * return fully built BubbleChart
+   *
+   * @return a BubbleChart
+   */
+  @Override
+  public BubbleChart build() {
+
+    return new BubbleChart(this);
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/BubbleSeries.java b/XChart/xchart/src/main/java/org/knowm/xchart/BubbleSeries.java
new file mode 100644
index 0000000000000000000000000000000000000000..3f62f86bb304cfec88983ba5f1932b889bad73d9
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/BubbleSeries.java
@@ -0,0 +1,57 @@
+package org.knowm.xchart;
+
+import org.knowm.xchart.internal.chartpart.RenderableSeries;
+import org.knowm.xchart.internal.chartpart.RenderableSeries.LegendRenderType;
+import org.knowm.xchart.internal.series.NoMarkersSeries;
+
+/** A Series containing X, Y and bubble size data to be plotted on a Chart */
+public class BubbleSeries extends NoMarkersSeries {
+
+  private BubbleSeriesRenderStyle bubbleSeriesRenderStyle = null;
+
+  /**
+   * Constructor
+   *
+   * @param name
+   * @param xData
+   * @param yData
+   * @param bubbleSizes
+   */
+  public BubbleSeries(String name, double[] xData, double[] yData, double[] bubbleSizes) {
+
+    super(name, xData, yData, bubbleSizes, DataType.Number);
+  }
+
+  public BubbleSeriesRenderStyle getBubbleSeriesRenderStyle() {
+
+    return bubbleSeriesRenderStyle;
+  }
+
+  public void setBubbleSeriesRenderStyle(BubbleSeriesRenderStyle bubbleSeriesRenderStyle) {
+
+    this.bubbleSeriesRenderStyle = bubbleSeriesRenderStyle;
+  }
+
+  @Override
+  public LegendRenderType getLegendRenderType() {
+
+    return bubbleSeriesRenderStyle.getLegendRenderType();
+  }
+
+  public enum BubbleSeriesRenderStyle implements RenderableSeries {
+    Round(LegendRenderType.Box);
+
+    private final LegendRenderType legendRenderType;
+
+    BubbleSeriesRenderStyle(LegendRenderType legendRenderType) {
+
+      this.legendRenderType = legendRenderType;
+    }
+
+    @Override
+    public LegendRenderType getLegendRenderType() {
+
+      return legendRenderType;
+    }
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/CSVExporter.java b/XChart/xchart/src/main/java/org/knowm/xchart/CSVExporter.java
new file mode 100644
index 0000000000000000000000000000000000000000..03485bd1a3c6cd0377be4c45c003319f38a33633
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/CSVExporter.java
@@ -0,0 +1,141 @@
+package org.knowm.xchart;
+
+import java.io.*;
+
+/**
+ * This class is used to export Chart data to a folder containing one or more CSV files. The parent
+ * folder's name is the title of the chart. Each series becomes a CSV file in the folder. The
+ * series' name becomes the CSV files' name.
+ */
+public class CSVExporter {
+
+  /**
+   * Export all XYChart series as rows in separate CSV files.
+   *
+   * @param chart
+   * @param path2Dir
+   */
+  public static void writeCSVRows(XYChart chart, String path2Dir) {
+
+    for (XYSeries xySeries : chart.getSeriesMap().values()) {
+      writeCSVRows(xySeries, path2Dir);
+    }
+  }
+
+  /**
+   * Export a XYChart series into rows in a CSV file.
+   *
+   * @param series
+   * @param path2Dir - ex. "./path/to/directory/" *make sure you have the '/' on the end
+   */
+  public static void writeCSVRows(XYSeries series, String path2Dir) {
+
+    File newFile = new File(path2Dir + series.getName() + ".csv");
+    Writer out = null;
+    try {
+
+      out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(newFile), "UTF8"));
+      String csv = join(series.getXData(), ",") + System.getProperty("line.separator");
+      out.write(csv);
+      csv = join(series.getYData(), ",") + System.getProperty("line.separator");
+      out.write(csv);
+      if (series.getExtraValues() != null) {
+        csv = join(series.getExtraValues(), ",") + System.getProperty("line.separator");
+        out.write(csv);
+      }
+    } catch (Exception e) {
+      e.printStackTrace();
+    } finally {
+      if (out != null) {
+        try {
+          out.flush();
+          out.close();
+        } catch (IOException e) {
+          // NOP
+        }
+      }
+    }
+  }
+
+  /**
+   * Joins a series into an entire row of comma separated values.
+   *
+   * @param seriesData
+   * @param separator
+   * @return
+   */
+  private static String join(double[] seriesData, String separator) {
+
+    // two or more elements
+    StringBuilder sb = new StringBuilder(256); // Java default is 16, probably too small
+    sb.append(seriesData[0]);
+    for (int i = 1; i < seriesData.length; i++) {
+
+      if (separator != null) {
+        sb.append(separator);
+      }
+
+      sb.append(seriesData[i]);
+    }
+    return sb.toString();
+  }
+
+  /**
+   * Export all XYChart series as columns in separate CSV files.
+   *
+   * @param chart
+   * @param path2Dir
+   */
+  public static void writeCSVColumns(XYChart chart, String path2Dir) {
+
+    for (XYSeries xySeries : chart.getSeriesMap().values()) {
+      writeCSVColumns(xySeries, path2Dir);
+    }
+  }
+
+  /**
+   * Export a Chart series in columns in a CSV file.
+   *
+   * @param series
+   * @param path2Dir - ex. "./path/to/directory/" *make sure you have the '/' on the end
+   */
+  public static void writeCSVColumns(XYSeries series, String path2Dir) {
+
+    File newFile = new File(path2Dir + series.getName() + ".csv");
+    Writer out = null;
+    try {
+
+      out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(newFile), "UTF8"));
+      double[] xData = series.getXData();
+      double[] yData = series.getYData();
+      double[] errorBarData = series.getExtraValues();
+      for (int i = 0; i < xData.length; i++) {
+
+        StringBuilder sb = new StringBuilder();
+        sb.append(xData[i]).append(",");
+        sb.append(yData[i]).append(",");
+        if (errorBarData != null) {
+          sb.append(errorBarData[i]).append(",");
+        }
+        sb.setLength(sb.length() - 1);
+        sb.append(System.getProperty("line.separator"));
+
+        // String csv = xDataPoint + "," + yDataPoint + errorBarValue == null ? "" : ("," +
+        // errorBarValue) + System.getProperty("line.separator");
+        // String csv = + yDataPoint + System.getProperty("line.separator");
+        out.write(sb.toString());
+      }
+    } catch (Exception e) {
+      e.printStackTrace();
+    } finally {
+      if (out != null) {
+        try {
+          out.flush();
+          out.close();
+        } catch (IOException e) {
+          // NOP
+        }
+      }
+    }
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/CSVImporter.java b/XChart/xchart/src/main/java/org/knowm/xchart/CSVImporter.java
new file mode 100644
index 0000000000000000000000000000000000000000..27e2bde1faba0f7e5904431de617d45028454bee
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/CSVImporter.java
@@ -0,0 +1,275 @@
+package org.knowm.xchart;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import org.knowm.xchart.style.Styler.ChartTheme;
+
+/**
+ * This class is used to create a Chart object from a folder containing one or more CSV files. The
+ * parent folder's name becomes the title of the chart. Each CSV file in the folder becomes a series
+ * on the chart. the CSV file's name becomes the series' name.
+ */
+public class CSVImporter {
+
+  /**
+   * @param path2Directory
+   * @param dataOrientation
+   * @param width
+   * @param height
+   * @param chartTheme
+   * @return
+   */
+  public static XYChart getChartFromCSVDir(
+      String path2Directory,
+      DataOrientation dataOrientation,
+      int width,
+      int height,
+      ChartTheme chartTheme) {
+
+    // 1. get the directory, name chart the dir name
+    XYChart chart;
+    if (chartTheme != null) {
+      chart = new XYChart(width, height, chartTheme);
+    } else {
+      chart = new XYChart(width, height);
+    }
+
+    // 2. get all the csv files in the dir
+    File[] csvFiles = getAllFiles(path2Directory, ".*.csv");
+
+    // 3. create a series for each file, naming the series the file name
+    for (File csvFile : csvFiles) {
+      String[] xAndYData;
+      if (dataOrientation == DataOrientation.Rows) {
+        xAndYData = getSeriesDataFromCSVRows(csvFile);
+      } else {
+        xAndYData = getSeriesDataFromCSVColumns(csvFile);
+      }
+
+      if (xAndYData[2] == null || xAndYData[2].trim().equalsIgnoreCase("")) {
+        chart.addSeries(
+            csvFile.getName().substring(0, csvFile.getName().indexOf(".csv")),
+            getAxisData(xAndYData[0]),
+            getAxisData(xAndYData[1]));
+      } else {
+        chart.addSeries(
+            csvFile.getName().substring(0, csvFile.getName().indexOf(".csv")),
+            getAxisData(xAndYData[0]),
+            getAxisData(xAndYData[1]),
+            getAxisData(xAndYData[2]));
+      }
+    }
+
+    return chart;
+  }
+
+  public static SeriesData getSeriesDataFromCSVFile(
+      String path2CSVFile, DataOrientation dataOrientation) {
+
+    // 1. get csv file in the dir
+    File csvFile = new File(path2CSVFile);
+
+    // 2. Create Series
+    String[] xAndYData;
+    if (dataOrientation == DataOrientation.Rows) {
+      xAndYData = getSeriesDataFromCSVRows(csvFile);
+    } else {
+      xAndYData = getSeriesDataFromCSVColumns(csvFile);
+    }
+    return new SeriesData(
+        getAxisData(xAndYData[0]),
+        getAxisData(xAndYData[1]),
+        csvFile.getName().substring(0, csvFile.getName().indexOf(".csv")));
+  }
+
+  /**
+   * @param path2Directory
+   * @param dataOrientation
+   * @param width
+   * @param height
+   * @return
+   */
+  public static XYChart getChartFromCSVDir(
+      String path2Directory, DataOrientation dataOrientation, int width, int height) {
+
+    return getChartFromCSVDir(path2Directory, dataOrientation, width, height, null);
+  }
+
+  /**
+   * Get the series's data from a file
+   *
+   * @param csvFile
+   * @return
+   */
+  private static String[] getSeriesDataFromCSVRows(File csvFile) {
+
+    String[] xAndYData = new String[3];
+
+    BufferedReader bufferedReader = null;
+    try {
+      int counter = 0;
+      String line;
+      bufferedReader = new BufferedReader(new FileReader(csvFile));
+      while ((line = bufferedReader.readLine()) != null) {
+        xAndYData[counter++] = line;
+      }
+    } catch (Exception e) {
+      System.out.println("Exception while reading csv file: " + e);
+    } finally {
+      if (bufferedReader != null) {
+        try {
+          bufferedReader.close();
+        } catch (IOException e) {
+          e.printStackTrace();
+        }
+      }
+    }
+    return xAndYData;
+  }
+
+  /**
+   * @param csvFile
+   * @return
+   */
+  private static String[] getSeriesDataFromCSVColumns(File csvFile) {
+
+    String[] xAndYData = new String[3];
+    xAndYData[0] = "";
+    xAndYData[1] = "";
+    xAndYData[2] = "";
+
+    BufferedReader bufferedReader = null;
+    try {
+      String line;
+      bufferedReader = new BufferedReader(new FileReader(csvFile));
+      while ((line = bufferedReader.readLine()) != null) {
+        String[] dataArray = line.split(",");
+        xAndYData[0] += dataArray[0] + ",";
+        xAndYData[1] += dataArray[1] + ",";
+        if (dataArray.length > 2) {
+          xAndYData[2] += dataArray[2] + ",";
+        }
+      }
+    } catch (Exception e) {
+      System.out.println("Exception while reading csv file: " + e);
+    } finally {
+      if (bufferedReader != null) {
+        try {
+          bufferedReader.close();
+        } catch (IOException e) {
+          e.printStackTrace();
+        }
+      }
+    }
+    return xAndYData;
+  }
+
+  /**
+   * @param stringData
+   * @return
+   */
+  private static List<Number> getAxisData(String stringData) {
+
+    List<Number> axisData = new ArrayList<Number>();
+    String[] stringDataArray = stringData.split(",");
+    for (String dataPoint : stringDataArray) {
+      try {
+        Double value = Double.parseDouble(dataPoint);
+        axisData.add(value);
+      } catch (NumberFormatException e) {
+        System.out.println("Error parsing >" + dataPoint + "< !");
+        throw (e);
+      }
+    }
+    return axisData;
+  }
+
+  /**
+   * This method returns the files found in the given directory matching the given regular
+   * expression.
+   *
+   * @param dirName - ex. "./path/to/directory/" *make sure you have the '/' on the end
+   * @param regex - ex. ".*.csv"
+   * @return File[] - an array of files
+   */
+  private static File[] getAllFiles(String dirName, String regex) {
+
+    File[] allFiles = getAllFiles(dirName);
+
+    List<File> matchingFiles = new ArrayList<File>();
+
+    for (File allFile : allFiles) {
+
+      if (allFile.getName().matches(regex)) {
+        matchingFiles.add(allFile);
+      }
+    }
+
+    return matchingFiles.toArray(new File[matchingFiles.size()]);
+  }
+
+  /**
+   * This method returns the Files found in the given directory
+   *
+   * @param dirName - ex. "./path/to/directory/" *make sure you have the '/' on the end
+   * @return File[] - an array of files
+   */
+  private static File[] getAllFiles(String dirName) {
+
+    File dir = new File(dirName);
+
+    File[] files = dir.listFiles(); // returns files and folders
+
+    if (files != null) {
+      List<File> filteredFiles = new ArrayList<File>();
+      for (File file : files) {
+
+        if (file.isFile()) {
+          filteredFiles.add(file);
+        }
+      }
+      return filteredFiles.toArray(new File[filteredFiles.size()]);
+    } else {
+      System.out.println(dirName + " does not denote a valid directory!");
+      return new File[0];
+    }
+  }
+
+  public enum DataOrientation {
+    Rows,
+    Columns
+  }
+
+  public static class SeriesData {
+
+    private final List<Number> xAxisData;
+    private final List<Number> yAxisData;
+    private final String seriesName;
+
+    public SeriesData(List<Number> xAxisData, List<Number> yAxisData, String seriesName) {
+
+      this.xAxisData = xAxisData;
+      this.yAxisData = yAxisData;
+      this.seriesName = seriesName;
+    }
+
+    public List<Number> getxAxisData() {
+
+      return xAxisData;
+    }
+
+    public List<Number> getyAxisData() {
+
+      return yAxisData;
+    }
+
+    public String getSeriesName() {
+
+      return seriesName;
+    }
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/CategoryChart.java b/XChart/xchart/src/main/java/org/knowm/xchart/CategoryChart.java
new file mode 100644
index 0000000000000000000000000000000000000000..30c3487c82e573639b1a8bd8b779787e427fa6d8
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/CategoryChart.java
@@ -0,0 +1,346 @@
+package org.knowm.xchart;
+
+import java.awt.Graphics2D;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import org.knowm.xchart.internal.Utils;
+import org.knowm.xchart.internal.chartpart.AxisPair;
+import org.knowm.xchart.internal.chartpart.Chart;
+import org.knowm.xchart.internal.chartpart.Legend_Marker;
+import org.knowm.xchart.internal.chartpart.Plot_Category;
+import org.knowm.xchart.internal.series.Series.DataType;
+import org.knowm.xchart.internal.style.SeriesColorMarkerLineStyle;
+import org.knowm.xchart.internal.style.SeriesColorMarkerLineStyleCycler;
+import org.knowm.xchart.style.CategoryStyler;
+import org.knowm.xchart.style.Styler.ChartTheme;
+import org.knowm.xchart.style.theme.Theme;
+
+public class CategoryChart extends Chart<CategoryStyler, CategorySeries> {
+
+  /**
+   * Constructor - the default Chart Theme will be used (XChartTheme)
+   *
+   * @param width
+   * @param height
+   */
+  public CategoryChart(int width, int height) {
+
+    super(width, height, new CategoryStyler());
+    axisPair = new AxisPair<CategoryStyler, CategorySeries>(this);
+    plot = new Plot_Category<CategoryStyler, CategorySeries>(this);
+    legend = new Legend_Marker<CategoryStyler, CategorySeries>(this);
+  }
+
+  /**
+   * Constructor
+   *
+   * @param width
+   * @param height
+   * @param theme - pass in a instance of Theme class, probably a custom Theme.
+   */
+  public CategoryChart(int width, int height, Theme theme) {
+
+    this(width, height);
+    styler.setTheme(theme);
+  }
+
+  /**
+   * Constructor
+   *
+   * @param width
+   * @param height
+   * @param chartTheme - pass in the desired ChartTheme enum
+   */
+  public CategoryChart(int width, int height, ChartTheme chartTheme) {
+
+    this(width, height, chartTheme.newInstance(chartTheme));
+  }
+
+  /**
+   * Constructor
+   *
+   * @param chartBuilder
+   */
+  public CategoryChart(CategoryChartBuilder chartBuilder) {
+
+    this(chartBuilder.width, chartBuilder.height, chartBuilder.chartTheme);
+    setTitle(chartBuilder.title);
+    setXAxisTitle(chartBuilder.xAxisTitle);
+    setYAxisTitle(chartBuilder.yAxisTitle);
+  }
+
+  /**
+   * Add a series for a Category type chart using using double arrays
+   *
+   * @param seriesName
+   * @param xData the X-Axis data
+   * @param yData the Y-Axis data
+   * @return A Series object that you can set properties on
+   */
+  public CategorySeries addSeries(String seriesName, double[] xData, double[] yData) {
+
+    return addSeries(seriesName, xData, yData, null);
+  }
+
+  /**
+   * Add a series for a Category type chart using using double arrays with error bars
+   *
+   * @param seriesName
+   * @param xData the X-Axis data
+   * @param yData the Y-Axis data
+   * @param errorBars the error bar data
+   * @return A Series object that you can set properties on
+   */
+  public CategorySeries addSeries(
+      String seriesName, double[] xData, double[] yData, double[] errorBars) {
+
+    return addSeries(
+        seriesName,
+        Utils.getNumberListFromDoubleArray(xData),
+        Utils.getNumberListFromDoubleArray(yData),
+        Utils.getNumberListFromDoubleArray(errorBars));
+  }
+
+  /**
+   * Add a series for a Category type chart using using int arrays
+   *
+   * @param seriesName
+   * @param xData the X-Axis data
+   * @param yData the Y-Axis data
+   * @return A Series object that you can set properties on
+   */
+  public CategorySeries addSeries(String seriesName, int[] xData, int[] yData) {
+
+    return addSeries(seriesName, xData, yData, null);
+  }
+
+  /**
+   * Add a series for a Category type chart using int arrays with error bars
+   *
+   * @param seriesName
+   * @param xData the X-Axis data
+   * @param yData the Y-Axis data
+   * @param errorBars the error bar data
+   * @return A Series object that you can set properties on
+   */
+  public CategorySeries addSeries(String seriesName, int[] xData, int[] yData, int[] errorBars) {
+
+    return addSeries(
+        seriesName,
+        Utils.getNumberListFromIntArray(xData),
+        Utils.getNumberListFromIntArray(yData),
+        Utils.getNumberListFromIntArray(errorBars));
+  }
+
+  /**
+   * Add a series for a Category type chart using Lists
+   *
+   * @param seriesName
+   * @param xData the X-Axis data
+   * @param yData the Y-Axis data
+   * @return A Series object that you can set properties on
+   */
+  public CategorySeries addSeries(String seriesName, List<?> xData, List<? extends Number> yData) {
+
+    return addSeries(seriesName, xData, yData, null);
+  }
+
+  /**
+   * Add a series for a Category type chart using Lists with error bars
+   *
+   * @param seriesName
+   * @param xData the X-Axis data
+   * @param yData the Y-Axis data
+   * @param errorBars the error bar data
+   * @return A Series object that you can set properties on
+   */
+  public CategorySeries addSeries(
+      String seriesName,
+      List<?> xData,
+      List<? extends Number> yData,
+      List<? extends Number> errorBars) {
+
+    // Sanity checks
+    sanityCheck(seriesName, xData, yData, errorBars);
+
+    CategorySeries series;
+    if (xData != null) {
+
+      // Sanity check
+      if (xData.size() != yData.size()) {
+        throw new IllegalArgumentException("X and Y-Axis sizes are not the same!!!");
+      }
+
+    } else { // generate xData
+      xData = Utils.getGeneratedDataAsList(yData.size());
+    }
+    series = new CategorySeries(seriesName, xData, yData, errorBars, getDataType(xData));
+
+    seriesMap.put(seriesName, series);
+
+    return series;
+  }
+
+  private DataType getDataType(List<?> data) {
+
+    DataType axisType;
+
+    Iterator<?> itr = data.iterator();
+    Object dataPoint = itr.next();
+    if (dataPoint instanceof Number) {
+      axisType = DataType.Number;
+    } else if (dataPoint instanceof Date) {
+      axisType = DataType.Date;
+    } else if (dataPoint instanceof String) {
+      axisType = DataType.String;
+    } else {
+      throw new IllegalArgumentException(
+          "Series data must be either Number, Date or String type!!!");
+    }
+    return axisType;
+  }
+
+  /**
+   * Update a series by updating the X-Axis, Y-Axis and error bar data
+   *
+   * @param seriesName
+   * @param newXData - set null to be automatically generated as a list of increasing Integers
+   *     starting from 1 and ending at the size of the new Y-Axis data list.
+   * @param newYData
+   * @param newErrorBarData - set null if there are no error bars
+   * @return
+   */
+  public CategorySeries updateCategorySeries(
+      String seriesName,
+      List<?> newXData,
+      List<? extends Number> newYData,
+      List<? extends Number> newErrorBarData) {
+
+    Map<String, CategorySeries> seriesMap = getSeriesMap();
+    CategorySeries series = seriesMap.get(seriesName);
+    if (series == null) {
+      throw new IllegalArgumentException("Series name >" + seriesName + "< not found!!!");
+    }
+    if (newXData == null) {
+      // generate X-Data
+      List<Integer> generatedXData = new ArrayList<Integer>();
+      for (int i = 1; i <= newYData.size(); i++) {
+        generatedXData.add(i);
+      }
+      series.replaceData(generatedXData, newYData, newErrorBarData);
+    } else {
+      series.replaceData(newXData, newYData, newErrorBarData);
+    }
+
+    return series;
+  }
+
+  /**
+   * Update a series by updating the X-Axis, Y-Axis and error bar data
+   *
+   * @param seriesName
+   * @param newXData - set null to be automatically generated as a list of increasing Integers
+   *     starting from 1 and ending at the size of the new Y-Axis data list.
+   * @param newYData
+   * @param newErrorBarData - set null if there are no error bars
+   * @return
+   */
+  public CategorySeries updateCategorySeries(
+      String seriesName, double[] newXData, double[] newYData, double[] newErrorBarData) {
+
+    return updateCategorySeries(
+        seriesName,
+        Utils.getNumberListFromDoubleArray(newXData),
+        Utils.getNumberListFromDoubleArray(newYData),
+        Utils.getNumberListFromDoubleArray(newErrorBarData));
+  }
+
+  ///////////////////////////////////////////////////
+  // Internal Members and Methods ///////////////////
+  ///////////////////////////////////////////////////
+
+  private void sanityCheck(
+      String seriesName,
+      List<?> xData,
+      List<? extends Number> yData,
+      List<? extends Number> errorBars) {
+
+    if (seriesMap.containsKey(seriesName)) {
+      throw new IllegalArgumentException(
+          "Series name >"
+              + seriesName
+              + "< has already been used. Use unique names for each series!!!");
+    }
+    if (yData == null) {
+      throw new IllegalArgumentException("Y-Axis data cannot be null!!!");
+    }
+    if (yData.size() == 0) {
+      throw new IllegalArgumentException("Y-Axis data cannot be empty!!!");
+    }
+    if (xData != null && xData.size() == 0) {
+      throw new IllegalArgumentException("X-Axis data cannot be empty!!!");
+    }
+    if (errorBars != null && errorBars.size() != yData.size()) {
+      throw new IllegalArgumentException("Error bars and Y-Axis sizes are not the same!!!");
+    }
+  }
+
+  @Override
+  public void paint(Graphics2D g, int width, int height) {
+
+    setWidth(width);
+    setHeight(height);
+
+    // set the series render styles if they are not set. Legend and Plot need it.
+    for (CategorySeries seriesCategory : getSeriesMap().values()) {
+      CategorySeries.CategorySeriesRenderStyle seriesType =
+          seriesCategory.getChartCategorySeriesRenderStyle(); // would be directly set
+      if (seriesType == null) { // wasn't overridden, use default from Style Manager
+        seriesCategory.setChartCategorySeriesRenderStyle(getStyler().getDefaultSeriesRenderStyle());
+      }
+    }
+    setSeriesStyles();
+
+    paintBackground(g);
+
+    axisPair.paint(g);
+    plot.paint(g);
+    chartTitle.paint(g);
+    legend.paint(g);
+    annotations.forEach(x -> x.paint(g));
+  }
+
+  /** set the series color, marker and line style based on theme */
+  private void setSeriesStyles() {
+
+    SeriesColorMarkerLineStyleCycler seriesColorMarkerLineStyleCycler =
+        new SeriesColorMarkerLineStyleCycler(
+            getStyler().getSeriesColors(),
+            getStyler().getSeriesMarkers(),
+            getStyler().getSeriesLines());
+    for (CategorySeries series : getSeriesMap().values()) {
+
+      SeriesColorMarkerLineStyle seriesColorMarkerLineStyle =
+          seriesColorMarkerLineStyleCycler.getNextSeriesColorMarkerLineStyle();
+
+      if (series.getLineStyle() == null) { // wasn't set manually
+        series.setLineStyle(seriesColorMarkerLineStyle.getStroke());
+      }
+      if (series.getLineColor() == null) { // wasn't set manually
+        series.setLineColor(seriesColorMarkerLineStyle.getColor());
+      }
+      if (series.getFillColor() == null) { // wasn't set manually
+        series.setFillColor(seriesColorMarkerLineStyle.getColor());
+      }
+      if (series.getMarker() == null) { // wasn't set manually
+        series.setMarker(seriesColorMarkerLineStyle.getMarker());
+      }
+      if (series.getMarkerColor() == null) { // wasn't set manually
+        series.setMarkerColor(seriesColorMarkerLineStyle.getColor());
+      }
+    }
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/CategoryChartBuilder.java b/XChart/xchart/src/main/java/org/knowm/xchart/CategoryChartBuilder.java
new file mode 100644
index 0000000000000000000000000000000000000000..c2510817c6a9f28eb4262725d12bb09c08a3f1e7
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/CategoryChartBuilder.java
@@ -0,0 +1,34 @@
+package org.knowm.xchart;
+
+import org.knowm.xchart.internal.ChartBuilder;
+
+public class CategoryChartBuilder extends ChartBuilder<CategoryChartBuilder, CategoryChart> {
+
+  String xAxisTitle = "";
+  String yAxisTitle = "";
+
+  public CategoryChartBuilder() {}
+
+  public CategoryChartBuilder xAxisTitle(String xAxisTitle) {
+
+    this.xAxisTitle = xAxisTitle;
+    return this;
+  }
+
+  public CategoryChartBuilder yAxisTitle(String yAxisTitle) {
+
+    this.yAxisTitle = yAxisTitle;
+    return this;
+  }
+
+  /**
+   * return fully built Chart_Category
+   *
+   * @return a CategoryChart
+   */
+  @Override
+  public CategoryChart build() {
+
+    return new CategoryChart(this);
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/CategorySeries.java b/XChart/xchart/src/main/java/org/knowm/xchart/CategorySeries.java
new file mode 100644
index 0000000000000000000000000000000000000000..d284e8ec98e2405b21f3073248c9c1a6020108c3
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/CategorySeries.java
@@ -0,0 +1,88 @@
+package org.knowm.xchart;
+
+import java.util.List;
+import org.knowm.xchart.internal.chartpart.RenderableSeries;
+import org.knowm.xchart.internal.chartpart.RenderableSeries.LegendRenderType;
+import org.knowm.xchart.internal.series.AxesChartSeriesCategory;
+import org.knowm.xchart.internal.series.Series;
+
+/** A Series containing category data to be plotted on a Chart */
+public class CategorySeries extends AxesChartSeriesCategory {
+
+  private boolean isOverlapped = false;
+
+  private CategorySeriesRenderStyle chartCategorySeriesRenderStyle = null;
+
+  /**
+   * Constructor
+   *
+   * @param name
+   * @param xData
+   * @param yData
+   * @param errorBars
+   * @param axisType
+   */
+  public CategorySeries(
+      String name,
+      List<?> xData,
+      List<? extends Number> yData,
+      List<? extends Number> errorBars,
+      Series.DataType axisType) {
+
+    super(name, xData, yData, errorBars, axisType);
+  }
+
+  public CategorySeriesRenderStyle getChartCategorySeriesRenderStyle() {
+
+    return chartCategorySeriesRenderStyle;
+  }
+
+  public CategorySeries setChartCategorySeriesRenderStyle(
+      CategorySeriesRenderStyle categorySeriesRenderStyle) {
+
+    this.chartCategorySeriesRenderStyle = categorySeriesRenderStyle;
+    return this;
+  }
+
+  public boolean isOverlapped() {
+    return isOverlapped;
+  }
+
+  public CategorySeries setOverlapped(boolean overlapped) {
+    isOverlapped = overlapped;
+    return this;
+  }
+
+  @Override
+  public LegendRenderType getLegendRenderType() {
+
+    return chartCategorySeriesRenderStyle.getLegendRenderType();
+  }
+
+  public enum CategorySeriesRenderStyle implements RenderableSeries {
+    Line(LegendRenderType.Line),
+
+    Area(LegendRenderType.Line),
+
+    Scatter(LegendRenderType.Scatter),
+
+    SteppedBar(LegendRenderType.Box),
+
+    Bar(LegendRenderType.BoxNoOutline),
+
+    Stick(LegendRenderType.Line);
+
+    private final LegendRenderType legendRenderType;
+
+    CategorySeriesRenderStyle(LegendRenderType legendRenderType) {
+
+      this.legendRenderType = legendRenderType;
+    }
+
+    @Override
+    public LegendRenderType getLegendRenderType() {
+
+      return legendRenderType;
+    }
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/DialChart.java b/XChart/xchart/src/main/java/org/knowm/xchart/DialChart.java
new file mode 100644
index 0000000000000000000000000000000000000000..04b8740bba8898ba6a58182f3513c2179bd1e997
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/DialChart.java
@@ -0,0 +1,121 @@
+package org.knowm.xchart;
+
+import java.awt.Graphics2D;
+import org.knowm.xchart.internal.chartpart.Chart;
+import org.knowm.xchart.internal.chartpart.Legend_Pie;
+import org.knowm.xchart.internal.chartpart.Plot_Dial;
+import org.knowm.xchart.style.DialStyler;
+import org.knowm.xchart.style.Styler.ChartTheme;
+import org.knowm.xchart.style.theme.Theme;
+
+public class DialChart extends Chart<DialStyler, DialSeries> {
+
+  /**
+   * Constructor - the default Chart Theme will be used (XChartTheme)
+   *
+   * @param width
+   * @param height
+   */
+  public DialChart(int width, int height) {
+
+    super(width, height, new DialStyler());
+    plot = new Plot_Dial<DialStyler, DialSeries>(this);
+    legend = new Legend_Pie<DialStyler, DialSeries>(this);
+  }
+
+  /**
+   * Constructor
+   *
+   * @param width
+   * @param height
+   * @param theme - pass in a instance of Theme class, probably a custom Theme.
+   */
+  public DialChart(int width, int height, Theme theme) {
+
+    this(width, height);
+    styler.setTheme(theme);
+  }
+
+  /**
+   * Constructor
+   *
+   * @param width
+   * @param height
+   * @param chartTheme - pass in the desired ChartTheme enum
+   */
+  public DialChart(int width, int height, ChartTheme chartTheme) {
+
+    this(width, height, chartTheme.newInstance(chartTheme));
+  }
+
+  /**
+   * Constructor
+   *
+   * @param chartBuilder
+   */
+  public DialChart(DialChartBuilder chartBuilder) {
+
+    this(chartBuilder.width, chartBuilder.height, chartBuilder.chartTheme);
+    setTitle(chartBuilder.title);
+  }
+
+  /**
+   * Add a series for a Dial type chart
+   *
+   * @param seriesName
+   * @param value
+   * @return
+   */
+  public DialSeries addSeries(String seriesName, double value) {
+
+    return addSeries(seriesName, value, null);
+  }
+
+  /**
+   * Add a series for a Dial type chart
+   *
+   * @param seriesName
+   * @param value
+   * @param label
+   * @return
+   */
+  public DialSeries addSeries(String seriesName, double value, String label) {
+
+    // Sanity checks
+    sanityCheck(seriesName, value);
+
+    DialSeries series = new DialSeries(seriesName, value, label);
+
+    seriesMap.clear(); // only allow one series per dial chart
+    seriesMap.put(seriesName, series);
+
+    return series;
+  }
+
+  private void sanityCheck(String seriesName, double value) {
+
+    if (seriesMap.containsKey(seriesName)) {
+      throw new IllegalArgumentException(
+          "Series name >"
+              + seriesName
+              + "< has already been used. Use unique names for each series!!!");
+    }
+    if (value < 0 || value > 1) {
+      throw new IllegalArgumentException("Value must be in [0, 1] range!!!");
+    }
+  }
+
+  @Override
+  public void paint(Graphics2D g, int width, int height) {
+
+    setWidth(width);
+    setHeight(height);
+
+    paintBackground(g);
+
+    plot.paint(g);
+    chartTitle.paint(g);
+    //    legend.paint(g); // no legend for dial charts
+    annotations.forEach(x -> x.paint(g));
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/DialChartBuilder.java b/XChart/xchart/src/main/java/org/knowm/xchart/DialChartBuilder.java
new file mode 100644
index 0000000000000000000000000000000000000000..a9214b0c38dc430085ad13fa001e9a8ff44d3716
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/DialChartBuilder.java
@@ -0,0 +1,14 @@
+package org.knowm.xchart;
+
+import org.knowm.xchart.internal.ChartBuilder;
+
+public class DialChartBuilder extends ChartBuilder<DialChartBuilder, DialChart> {
+
+  public DialChartBuilder() {}
+
+  @Override
+  public DialChart build() {
+
+    return new DialChart(this);
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/DialSeries.java b/XChart/xchart/src/main/java/org/knowm/xchart/DialSeries.java
new file mode 100644
index 0000000000000000000000000000000000000000..d984a81a0092e669e3e8c7fc08d07ff51f9f6e1c
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/DialSeries.java
@@ -0,0 +1,44 @@
+package org.knowm.xchart;
+
+import org.knowm.xchart.internal.chartpart.RenderableSeries.LegendRenderType;
+import org.knowm.xchart.internal.series.Series;
+
+/** A Series containing Radar data to be plotted on a Chart */
+public class DialSeries extends Series {
+
+  private double value;
+  private final String label;
+
+  /**
+   * @param label Adds custom label for series. If label is null, it is automatically calculated.
+   */
+  public DialSeries(String name, double value, String label) {
+
+    super(name);
+    this.value = value;
+    this.label = label;
+  }
+
+  public double getValue() {
+
+    return value;
+  }
+
+  public void setValue(double value) {
+
+    this.value = value;
+  }
+
+  public String getLabel() {
+
+    return label;
+  }
+
+  // TODO solve this with class/interface heirarchy instead
+  @Override
+  public LegendRenderType getLegendRenderType() {
+
+    // Dial charts don't have a legend
+    return null;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/GifEncoder.java b/XChart/xchart/src/main/java/org/knowm/xchart/GifEncoder.java
new file mode 100644
index 0000000000000000000000000000000000000000..4b2839d0169658a2f51c60e71fe05ddee7eaf092
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/GifEncoder.java
@@ -0,0 +1,41 @@
+package org.knowm.xchart;
+
+import com.madgag.gif.fmsware.AnimatedGifEncoder;
+import java.awt.image.BufferedImage;
+import java.util.List;
+import org.knowm.xchart.internal.Utils;
+
+/** A helper class with static methods for saving Charts as a GIF file */
+public class GifEncoder {
+
+  private static final String GIF_FILE_EXTENSION = ".gif";
+
+  /**
+   * images saved as GIF file, repeated countless times with 100ms delay
+   *
+   * @param filePath GIF file path
+   * @param images Multiple BufferedImages for Chart
+   */
+  public static void saveGif(String filePath, List<BufferedImage> images) {
+    saveGif(filePath, images, 0, 100);
+  }
+
+  /**
+   * images saved as GIF file, Set repeat times and delay time
+   *
+   * @param filePath GIF file path
+   * @param images Multiple BufferedImages for Chart
+   * @param repeat repeat times, less than 0 does not repeat,0 countless times
+   * @param delay delay time in milliseconds
+   */
+  public static void saveGif(String filePath, List<BufferedImage> images, int repeat, int delay) {
+    AnimatedGifEncoder gif = new AnimatedGifEncoder();
+    gif.setRepeat(repeat);
+    gif.start(Utils.addFileExtension(filePath, GIF_FILE_EXTENSION));
+    gif.setDelay(delay);
+    for (BufferedImage image : images) {
+      gif.addFrame(image);
+    }
+    gif.finish();
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/HeatMapChart.java b/XChart/xchart/src/main/java/org/knowm/xchart/HeatMapChart.java
new file mode 100644
index 0000000000000000000000000000000000000000..14f37f2da5eee590f4531e0366c3f2de5fccea9e
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/HeatMapChart.java
@@ -0,0 +1,241 @@
+package org.knowm.xchart;
+
+import java.awt.Graphics2D;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import org.knowm.xchart.internal.chartpart.AxisPair;
+import org.knowm.xchart.internal.chartpart.Chart;
+import org.knowm.xchart.internal.chartpart.Legend_HeatMap;
+import org.knowm.xchart.internal.chartpart.Plot_HeatMap;
+import org.knowm.xchart.style.HeatMapStyler;
+import org.knowm.xchart.style.Styler.ChartTheme;
+import org.knowm.xchart.style.theme.Theme;
+
+public class HeatMapChart extends Chart<HeatMapStyler, HeatMapSeries> {
+
+  private HeatMapSeries heatMapSeries;
+
+  /**
+   * Constructor - the default Chart Theme will be used (XChartTheme)
+   *
+   * @param width
+   * @param height
+   */
+  public HeatMapChart(int width, int height) {
+
+    super(width, height, new HeatMapStyler());
+
+    axisPair = new AxisPair<HeatMapStyler, HeatMapSeries>(this);
+    plot = new Plot_HeatMap<HeatMapStyler, HeatMapSeries>(this);
+    legend = new Legend_HeatMap<HeatMapStyler, HeatMapSeries>(this);
+  }
+
+  /**
+   * Constructor
+   *
+   * @param width
+   * @param height
+   * @param theme - pass in a instance of Theme class, probably a custom Theme.
+   */
+  public HeatMapChart(int width, int height, Theme theme) {
+
+    this(width, height);
+    styler.setTheme(theme);
+  }
+
+  /**
+   * Constructor
+   *
+   * @param width
+   * @param height
+   * @param chartTheme - pass in the desired ChartTheme enum
+   */
+  public HeatMapChart(int width, int height, ChartTheme chartTheme) {
+
+    this(width, height, chartTheme.newInstance(chartTheme));
+  }
+
+  /**
+   * Constructor
+   *
+   * @param heatMapChartBuilder
+   */
+  public HeatMapChart(HeatMapChartBuilder heatMapChartBuilder) {
+
+    this(heatMapChartBuilder.width, heatMapChartBuilder.height, heatMapChartBuilder.chartTheme);
+    setTitle(heatMapChartBuilder.title);
+    setXAxisTitle(heatMapChartBuilder.xAxisTitle);
+    setYAxisTitle(heatMapChartBuilder.yAxisTitle);
+  }
+
+  /**
+   * Add a series for a HeatMap type chart using int arrays
+   *
+   * @param seriesName
+   * @param xData the X-Axis data
+   * @param yData the Y-Axis data
+   * @param heatData the heat data
+   * @return
+   */
+  public HeatMapSeries addSeries(String seriesName, int[] xData, int[] yData, int[][] heatData) {
+
+    return addSeries(seriesName, arrayToList(xData), arrayToList(yData), arrayToList(heatData));
+  }
+
+  /**
+   * Add a series for a HeatMap type chart using List&lt;?&gt;
+   *
+   * @param seriesName
+   * @param xData the X-Axis data
+   * @param yData Y-Axis data
+   * @param heatData the heat data
+   * @return
+   */
+  public HeatMapSeries addSeries(
+      String seriesName, List<?> xData, List<?> yData, List<Number[]> heatData) {
+
+    if (heatMapSeries != null) {
+      throw new RuntimeException("HeatMapSeries can only be added once!!!");
+    }
+    sanityCheck(xData, yData, heatData);
+    heatMapSeries = new HeatMapSeries(seriesName, xData, yData, heatData);
+    seriesMap.put(seriesName, heatMapSeries);
+    return heatMapSeries;
+  }
+
+  /**
+   * Update a series by updating the X-Axis, Y-Axis and heat data
+   *
+   * @param seriesName
+   * @param xData
+   * @param yData
+   * @param heatData heat data value, {{1,5,3,7,...},{8,4,5,8,...},{1,9,12,15,...},...}
+   * @return
+   */
+  public HeatMapSeries updateSeries(String seriesName, int[] xData, int[] yData, int[][] heatData) {
+
+    return updateSeries(seriesName, arrayToList(xData), arrayToList(yData), arrayToList(heatData));
+  }
+
+  /**
+   * Update a series by updating the X-Axis, Y-Axis and heat data
+   *
+   * @param seriesName
+   * @param xData
+   * @param yData
+   * @param heatData heat data, {[0,0,1],[0,1,3],[0,2,2],[0,3,18],[1,0,26],[1,1,6],[1,2,7],...}
+   * @return
+   */
+  public HeatMapSeries updateSeries(
+      String seriesName, List<?> xData, List<?> yData, List<Number[]> heatData) {
+
+    Map<String, HeatMapSeries> seriesMap = getSeriesMap();
+    HeatMapSeries series = seriesMap.get(seriesName);
+    if (series == null) {
+      throw new IllegalArgumentException("Series name >" + seriesName + "< not found!!!");
+    }
+
+    series.replaceData(xData, yData, heatData);
+    return series;
+  }
+
+  public HeatMapSeries getHeatMapSeries() {
+
+    return heatMapSeries;
+  }
+
+  @Override
+  public void paint(Graphics2D g, int width, int height) {
+
+    if (heatMapSeries == null) {
+      return;
+    }
+    setWidth(width);
+    setHeight(height);
+
+    prepareForPaint();
+    // setSeriesStyles();
+
+    paintBackground(g);
+
+    axisPair.paint(g);
+    plot.paint(g);
+    chartTitle.paint(g);
+    legend.paint(g);
+    annotations.forEach(x -> x.paint(g));
+  }
+
+  private List<Integer> arrayToList(int[] data) {
+
+    List<Integer> list = new ArrayList<>();
+    for (int datum : data) {
+      list.add(datum);
+    }
+    return list;
+  }
+
+  private List<Number[]> arrayToList(int[][] heatData) {
+
+    List<Number[]> list = new ArrayList<>();
+    Number[] numbers = null;
+    int[] array = null;
+    for (int i = 0; i < heatData.length; i++) {
+      array = heatData[i];
+      for (int j = 0; j < array.length; j++) {
+        numbers = new Number[3];
+        numbers[0] = i;
+        numbers[1] = j;
+        numbers[2] = heatData[i][j];
+        list.add(numbers);
+      }
+    }
+    return list;
+  }
+
+  private void sanityCheck(List<?> xData, List<?> yData, List<Number[]> heatData) {
+
+    if (xData == null) {
+      throw new IllegalArgumentException("X-Axis data cannot be null!!!");
+    }
+    if (xData.size() == 0) {
+      throw new IllegalArgumentException("X-Axis data cannot be empty!!!");
+    }
+    if (yData == null) {
+      throw new IllegalArgumentException("Y-Axis data cannot be null!!!");
+    }
+    if (yData.size() == 0) {
+      throw new IllegalArgumentException("Y-Axis data cannot be empty!!!");
+    }
+    if (heatData == null) {
+      throw new IllegalArgumentException("Heat data cannot be null!!!");
+    }
+    if (heatData.size() == 0) {
+      throw new IllegalArgumentException("Heat data cannot be empty!!!");
+    }
+    for (Number[] numbers : heatData) {
+      if (numbers != null) {
+        if (numbers.length != 3) {
+          throw new IllegalArgumentException("Heat data column length is not equal to 3!!!");
+        }
+        if (numbers[0] == null || numbers[1] == null || numbers[2] == null) {
+          throw new IllegalArgumentException(
+              "All values in the heat data column cannot be null!!!");
+        }
+        if (numbers[0].intValue() < 0 || numbers[1].intValue() < 0) {
+          throw new IllegalArgumentException("numbers[0] and numbers[1] cannot be less than 0!!!");
+        }
+      }
+    }
+  }
+
+  private void prepareForPaint() {
+    if (styler.getMin() != Double.MIN_VALUE) {
+      heatMapSeries.setMin(styler.getMin());
+    }
+
+    if (styler.getMax() != Double.MAX_VALUE) {
+      heatMapSeries.setMax(styler.getMax());
+    }
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/HeatMapChartBuilder.java b/XChart/xchart/src/main/java/org/knowm/xchart/HeatMapChartBuilder.java
new file mode 100644
index 0000000000000000000000000000000000000000..3b800fe57464b42371b6ffd165111e838dda5335
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/HeatMapChartBuilder.java
@@ -0,0 +1,34 @@
+package org.knowm.xchart;
+
+import org.knowm.xchart.internal.ChartBuilder;
+
+public class HeatMapChartBuilder extends ChartBuilder<HeatMapChartBuilder, HeatMapChart> {
+
+  String xAxisTitle = "";
+  String yAxisTitle = "";
+
+  public HeatMapChartBuilder() {}
+
+  public HeatMapChartBuilder xAxisTitle(String xAxisTitle) {
+
+    this.xAxisTitle = xAxisTitle;
+    return this;
+  }
+
+  public HeatMapChartBuilder yAxisTitle(String yAxisTitle) {
+
+    this.yAxisTitle = yAxisTitle;
+    return this;
+  }
+
+  /**
+   * return fully built HeatMapChart
+   *
+   * @return a HeatMapChart
+   */
+  @Override
+  public HeatMapChart build() {
+
+    return new HeatMapChart(this);
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/HeatMapSeries.java b/XChart/xchart/src/main/java/org/knowm/xchart/HeatMapSeries.java
new file mode 100644
index 0000000000000000000000000000000000000000..1347927530bf6a124041072035c520e6dae57eef
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/HeatMapSeries.java
@@ -0,0 +1,157 @@
+package org.knowm.xchart;
+
+import java.util.*;
+import org.knowm.xchart.internal.chartpart.RenderableSeries.LegendRenderType;
+import org.knowm.xchart.internal.series.AxesChartSeries;
+
+/** A Series containing X, Y and heatData data to be plotted on a Chart */
+public class HeatMapSeries extends AxesChartSeries {
+
+  List<?> xData;
+
+  List<?> yData;
+
+  List<? extends Number[]> heatData;
+
+  // heatData value min
+  double min;
+
+  // heatData value max
+  double max;
+
+  /**
+   * Constructor
+   *
+   * @param name
+   * @param xData
+   * @param yData
+   * @param heatData
+   */
+  protected HeatMapSeries(String name, List<?> xData, List<?> yData, List<Number[]> heatData) {
+
+    super(name, getDataType(xData), getDataType(yData));
+    this.xData = xData;
+    this.yData = yData;
+    this.heatData = heatData;
+    calculateMinMax();
+  }
+
+  public void replaceData(List<?> xData, List<?> yData, List<Number[]> heatData) {
+
+    this.xData = xData;
+    this.yData = yData;
+    this.heatData = heatData;
+    calculateMinMax();
+  }
+
+  @Override
+  protected void calculateMinMax() {
+
+    min = Double.MAX_VALUE;
+    max = Double.MIN_VALUE;
+    Number number = null;
+    for (Number[] numbers : heatData) {
+      if (numbers == null) {
+        continue;
+      }
+      number = numbers[2];
+      if (number != null) {
+        if (min > number.doubleValue()) {
+          min = number.doubleValue();
+        }
+        if (max < number.doubleValue()) {
+          max = number.doubleValue();
+        }
+      }
+    }
+
+    xMin = getMin(xData, xMin);
+    xMax = getMax(xData, xMax);
+    yMin = getMin(yData, yMin);
+    yMax = getMax(yData, yMax);
+  }
+
+  private static double getMin(List<?> list, double defaultValue) {
+    if (list.isEmpty() || !(list.get(0) instanceof Number)) {
+      return defaultValue;
+    }
+    return list.stream()
+        .map(x -> (Number) x)
+        .min(Comparator.comparing(Number::doubleValue))
+        .orElse(defaultValue)
+        .doubleValue();
+  }
+
+  private static double getMax(List<?> list, double defaultValue) {
+    if (list.isEmpty() || !(list.get(0) instanceof Number)) {
+      return defaultValue;
+    }
+    return list.stream()
+        .map(x -> (Number) x)
+        .max(Comparator.comparing(Number::doubleValue))
+        .orElse(defaultValue)
+        .doubleValue();
+  }
+
+  @Override
+  public LegendRenderType getLegendRenderType() {
+
+    return null;
+  }
+
+  private static DataType getDataType(List<?> data) {
+
+    DataType axisType;
+
+    Iterator<?> itr = data.iterator();
+    Object dataPoint = itr.next();
+    if (dataPoint instanceof Number) {
+      axisType = DataType.Number;
+    } else if (dataPoint instanceof Date) {
+      axisType = DataType.Date;
+    } else if (dataPoint instanceof String) {
+      axisType = DataType.String;
+    } else {
+      throw new IllegalArgumentException(
+          "Series data must be either Number, Date or String type!!!");
+    }
+    return axisType;
+  }
+
+  public List<?> getXData() {
+
+    return xData;
+  }
+
+  public List<?> getYData() {
+
+    return yData;
+  }
+
+  public List<? extends Number[]> getHeatData() {
+
+    return heatData;
+  }
+
+  public double getMin() {
+
+    return min;
+  }
+
+  public HeatMapSeries setMin(double min) {
+
+    this.min = min;
+    return this;
+  }
+
+  public double getMax() {
+
+    return max;
+  }
+
+  public HeatMapSeries setMax(double max) {
+
+    this.max = max;
+    return this;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/Histogram.java b/XChart/xchart/src/main/java/org/knowm/xchart/Histogram.java
new file mode 100644
index 0000000000000000000000000000000000000000..63a84590117ef248d7ce0a9b9681a7b4cadb9355
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/Histogram.java
@@ -0,0 +1,155 @@
+package org.knowm.xchart;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+/** This class can be used to create histogram */
+public class Histogram {
+
+  private final Collection<? extends Number> originalData;
+  private final int numBins;
+  private final double min;
+  private final double max;
+  private List<Double> xAxisData; // bin centers
+  private List<Double> yAxisData; // frequency counts
+
+  /**
+   * Constructor
+   *
+   * @param data
+   * @param numBins
+   */
+  public Histogram(Collection<? extends Number> data, int numBins) {
+
+    // Sanity checks
+    sanityCheck(data, numBins);
+
+    this.numBins = numBins;
+    this.originalData = data;
+
+    Double tempMax = -Double.MAX_VALUE;
+    Double tempMin = Double.MAX_VALUE;
+    for (Number number : data) {
+      double value = number.doubleValue();
+      if (value > tempMax) {
+        tempMax = value;
+      }
+      if (value < tempMin) {
+        tempMin = value;
+      }
+    }
+    max = tempMax;
+    min = tempMin;
+
+    init();
+  }
+
+  /**
+   * Constructor
+   *
+   * @param data
+   * @param numBins
+   * @param min
+   * @param max
+   */
+  public Histogram(Collection<? extends Number> data, int numBins, double min, double max) {
+
+    // Sanity checks
+    sanityCheck(data, numBins);
+    if (max < min) {
+      throw new IllegalArgumentException("max cannot be less than min!!!");
+    }
+
+    this.numBins = numBins;
+    this.originalData = data;
+    this.min = min;
+    this.max = max;
+
+    init();
+  }
+
+  private void sanityCheck(Collection<? extends Number> data, int numBins) {
+
+    if (data == null) {
+      throw new IllegalArgumentException("Histogram data cannot be null!!!");
+    }
+    if (data.isEmpty()) {
+      throw new IllegalArgumentException("Histogram data cannot be empty!!!");
+    }
+    if (data.contains(null)) {
+      throw new IllegalArgumentException("Histogram data cannot contain null!!!");
+    }
+
+    if (numBins <= 0) {
+      throw new IllegalArgumentException("Histogram numBins cannot be less than or equal to 0!!!");
+    }
+  }
+
+  private void init() {
+
+    double[] tempYAxisData = new double[numBins];
+    final double binSize = (max - min) / numBins;
+
+    // y axis data
+    Iterator<? extends Number> itr = originalData.iterator();
+    double doubleValue = 0.0;
+    int bin = 0;
+    while (itr.hasNext()) {
+
+      doubleValue = itr.next().doubleValue();
+
+      /* this data is smaller than min, or this data point is bigger than max */
+      if (doubleValue < min || doubleValue > max) {
+        continue;
+      }
+      bin = (int) ((doubleValue - min) / binSize); // changed this from numBins
+      if (bin < numBins) {
+        tempYAxisData[bin] += 1;
+      } else { // the value falls exactly on the max value
+        tempYAxisData[bin - 1] += 1;
+      }
+    }
+    yAxisData = new ArrayList<Double>(numBins);
+    for (double d : tempYAxisData) {
+      yAxisData.add(d);
+    }
+
+    // x axis data
+    xAxisData = new ArrayList<Double>(numBins);
+    for (int i = 0; i < numBins; i++) {
+      xAxisData.add(((i * (max - min)) / numBins + min) + binSize / 2);
+    }
+  }
+
+  public List<Double> getxAxisData() {
+
+    return xAxisData;
+  }
+
+  public List<Double> getyAxisData() {
+
+    return yAxisData;
+  }
+
+  public Collection<? extends Number> getOriginalData() {
+
+    return originalData;
+  }
+
+  public int getNumBins() {
+
+    return numBins;
+  }
+
+  public double getMin() {
+
+    return min;
+  }
+
+  public double getMax() {
+
+    return max;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/OHLCChart.java b/XChart/xchart/src/main/java/org/knowm/xchart/OHLCChart.java
new file mode 100644
index 0000000000000000000000000000000000000000..530e53e5be94640e065ab6d4dc91e10331541078
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/OHLCChart.java
@@ -0,0 +1,868 @@
+package org.knowm.xchart;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.Graphics2D;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import org.knowm.xchart.OHLCSeries.OHLCSeriesRenderStyle;
+import org.knowm.xchart.internal.Utils;
+import org.knowm.xchart.internal.chartpart.AxisPair;
+import org.knowm.xchart.internal.chartpart.Chart;
+import org.knowm.xchart.internal.chartpart.Legend_OHLC;
+import org.knowm.xchart.internal.chartpart.Plot_OHLC;
+import org.knowm.xchart.internal.series.Series.DataType;
+import org.knowm.xchart.internal.style.SeriesColorMarkerLineStyle;
+import org.knowm.xchart.internal.style.SeriesColorMarkerLineStyleCycler;
+import org.knowm.xchart.style.OHLCStyler;
+import org.knowm.xchart.style.Styler.ChartTheme;
+import org.knowm.xchart.style.theme.Theme;
+
+public class OHLCChart extends Chart<OHLCStyler, OHLCSeries> {
+
+  /**
+   * Constructor - the default Chart Theme will be used (XChartTheme)
+   *
+   * @param width
+   * @param height
+   */
+  public OHLCChart(int width, int height) {
+
+    super(width, height, new OHLCStyler());
+
+    axisPair = new AxisPair<OHLCStyler, OHLCSeries>(this);
+    plot = new Plot_OHLC<OHLCStyler, OHLCSeries>(this);
+    legend = new Legend_OHLC<OHLCStyler, OHLCSeries>(this);
+  }
+
+  /**
+   * Constructor
+   *
+   * @param width
+   * @param height
+   * @param theme - pass in a instance of Theme class, probably a custom Theme.
+   */
+  public OHLCChart(int width, int height, Theme theme) {
+
+    this(width, height);
+    styler.setTheme(theme);
+    styler.setToolTipBackgroundColor(new Color(210, 210, 210));
+    styler.setToolTipFont(new Font(Font.SANS_SERIF, Font.PLAIN, 12));
+  }
+
+  /**
+   * Constructor
+   *
+   * @param width
+   * @param height
+   * @param chartTheme - pass in the desired ChartTheme enum
+   */
+  public OHLCChart(int width, int height, ChartTheme chartTheme) {
+
+    this(width, height, chartTheme.newInstance(chartTheme));
+  }
+
+  /**
+   * Constructor
+   *
+   * @param chartBuilder
+   */
+  public OHLCChart(OHLCChartBuilder chartBuilder) {
+
+    this(chartBuilder.width, chartBuilder.height, chartBuilder.chartTheme);
+    setTitle(chartBuilder.title);
+    setXAxisTitle(chartBuilder.xAxisTitle);
+    setYAxisTitle(chartBuilder.yAxisTitle);
+  }
+
+  /**
+   * Add a series for a OHLC type chart using using float arrays
+   *
+   * @param seriesName
+   * @param openData the open data
+   * @param highData the high data
+   * @param lowData the low data
+   * @param closeData the close data
+   * @return A Series object that you can set properties on
+   */
+  public OHLCSeries addSeries(
+      String seriesName, float[] openData, float[] highData, float[] lowData, float[] closeData) {
+
+    return addSeries(seriesName, null, openData, highData, lowData, closeData);
+  }
+
+  /**
+   * Add a series for a OHLC type chart using using float arrays
+   *
+   * @param seriesName
+   * @param xData the x-axis data
+   * @param openData the open data
+   * @param highData the high data
+   * @param lowData the low data
+   * @param closeData the close data
+   * @return A Series object that you can set properties on
+   */
+  public OHLCSeries addSeries(
+      String seriesName,
+      float[] xData,
+      float[] openData,
+      float[] highData,
+      float[] lowData,
+      float[] closeData) {
+
+    return addSeries(
+        seriesName,
+        Utils.getDoubleArrayFromFloatArray(xData),
+        Utils.getDoubleArrayFromFloatArray(openData),
+        Utils.getDoubleArrayFromFloatArray(highData),
+        Utils.getDoubleArrayFromFloatArray(lowData),
+        Utils.getDoubleArrayFromFloatArray(closeData),
+        null,
+        DataType.Number);
+  }
+
+  /**
+   * Add a series for a OHLC type chart using using float arrays
+   *
+   * @param seriesName
+   * @param xData the x-axis data
+   * @param openData the open data
+   * @param highData the high data
+   * @param lowData the low data
+   * @param closeData the close data
+   * @param volumeData the volume data
+   * @return A Series object that you can set properties on
+   */
+  public OHLCSeries addSeries(
+      String seriesName,
+      float[] xData,
+      float[] openData,
+      float[] highData,
+      float[] lowData,
+      float[] closeData,
+      float[] volumeData) {
+
+    return addSeries(
+        seriesName,
+        Utils.getDoubleArrayFromFloatArray(xData),
+        Utils.getDoubleArrayFromFloatArray(openData),
+        Utils.getDoubleArrayFromFloatArray(highData),
+        Utils.getDoubleArrayFromFloatArray(lowData),
+        Utils.getDoubleArrayFromFloatArray(closeData),
+        Utils.getLongArrayFromFloatArray(volumeData),
+        DataType.Number);
+  }
+
+  /**
+   * Add a series for a OHLC type chart using using int arrays
+   *
+   * @param seriesName
+   * @param openData the open data
+   * @param highData the high data
+   * @param lowData the low data
+   * @param closeData the close data
+   * @return A Series object that you can set properties on
+   */
+  public OHLCSeries addSeries(
+      String seriesName, int[] openData, int[] highData, int[] lowData, int[] closeData) {
+
+    return addSeries(seriesName, null, openData, highData, lowData, closeData);
+  }
+
+  /**
+   * Add a series for a OHLC type chart using using int arrays
+   *
+   * @param seriesName
+   * @param xData the x-axis data
+   * @param openData the open data
+   * @param highData the high data
+   * @param lowData the low data
+   * @param closeData the close data
+   * @return A Series object that you can set properties on
+   */
+  public OHLCSeries addSeries(
+      String seriesName,
+      int[] xData,
+      int[] openData,
+      int[] highData,
+      int[] lowData,
+      int[] closeData) {
+
+    return addSeries(
+        seriesName,
+        Utils.getDoubleArrayFromIntArray(xData),
+        Utils.getDoubleArrayFromIntArray(openData),
+        Utils.getDoubleArrayFromIntArray(highData),
+        Utils.getDoubleArrayFromIntArray(lowData),
+        Utils.getDoubleArrayFromIntArray(closeData),
+        null,
+        DataType.Number);
+  }
+
+  /**
+   * Add a series for a OHLC type chart using using int arrays
+   *
+   * @param seriesName
+   * @param xData the x-axis data
+   * @param openData the open data
+   * @param highData the high data
+   * @param lowData the low data
+   * @param closeData the close data
+   * @param volumeData the volume data
+   * @return A Series object that you can set properties on
+   */
+  public OHLCSeries addSeries(
+      String seriesName,
+      int[] xData,
+      int[] openData,
+      int[] highData,
+      int[] lowData,
+      int[] closeData,
+      int[] volumeData) {
+
+    return addSeries(
+        seriesName,
+        Utils.getDoubleArrayFromIntArray(xData),
+        Utils.getDoubleArrayFromIntArray(openData),
+        Utils.getDoubleArrayFromIntArray(highData),
+        Utils.getDoubleArrayFromIntArray(lowData),
+        Utils.getDoubleArrayFromIntArray(closeData),
+        Utils.getLongArrayFromIntArray(volumeData),
+        DataType.Number);
+  }
+
+  /**
+   * Add a series for a OHLC type chart using Lists
+   *
+   * @param seriesName
+   * @param xData the x-axis data
+   * @param openData the open data
+   * @param highData the high data
+   * @param lowData the low data
+   * @param closeData the close data
+   * @return A Series object that you can set properties on
+   */
+  public OHLCSeries addSeries(
+      String seriesName,
+      List<?> xData,
+      List<? extends Number> openData,
+      List<? extends Number> highData,
+      List<? extends Number> lowData,
+      List<? extends Number> closeData) {
+
+    DataType dataType = getDataType(xData);
+    switch (dataType) {
+      case Date:
+        return addSeries(
+            seriesName,
+            Utils.getDoubleArrayFromDateList(xData),
+            Utils.getDoubleArrayFromNumberList(openData),
+            Utils.getDoubleArrayFromNumberList(highData),
+            Utils.getDoubleArrayFromNumberList(lowData),
+            Utils.getDoubleArrayFromNumberList(closeData),
+            null,
+            DataType.Date);
+
+      default:
+        return addSeries(
+            seriesName,
+            Utils.getDoubleArrayFromNumberList(xData),
+            Utils.getDoubleArrayFromNumberList(openData),
+            Utils.getDoubleArrayFromNumberList(highData),
+            Utils.getDoubleArrayFromNumberList(lowData),
+            Utils.getDoubleArrayFromNumberList(closeData),
+            null,
+            DataType.Number);
+    }
+  }
+
+  /**
+   * Add a series for a OHLC type chart using Lists
+   *
+   * @param seriesName
+   * @param xData the x-axis data
+   * @param openData the open data
+   * @param highData the high data
+   * @param lowData the low data
+   * @param closeData the close data
+   * @param volumeData the volume data
+   * @return A Series object that you can set properties on
+   */
+  public OHLCSeries addSeries(
+      String seriesName,
+      List<?> xData,
+      List<? extends Number> openData,
+      List<? extends Number> highData,
+      List<? extends Number> lowData,
+      List<? extends Number> closeData,
+      List<? extends Number> volumeData) {
+
+    DataType dataType = getDataType(xData);
+    switch (dataType) {
+      case Date:
+        return addSeries(
+            seriesName,
+            Utils.getDoubleArrayFromDateList(xData),
+            Utils.getDoubleArrayFromNumberList(openData),
+            Utils.getDoubleArrayFromNumberList(highData),
+            Utils.getDoubleArrayFromNumberList(lowData),
+            Utils.getDoubleArrayFromNumberList(closeData),
+            Utils.getLongArrayFromNumberList(volumeData),
+            DataType.Date);
+
+      default:
+        return addSeries(
+            seriesName,
+            Utils.getDoubleArrayFromNumberList(xData),
+            Utils.getDoubleArrayFromNumberList(openData),
+            Utils.getDoubleArrayFromNumberList(highData),
+            Utils.getDoubleArrayFromNumberList(lowData),
+            Utils.getDoubleArrayFromNumberList(closeData),
+            Utils.getLongArrayFromNumberList(volumeData),
+            DataType.Number);
+    }
+  }
+
+  /**
+   * Add a series for a OHLC type chart using Lists
+   *
+   * @param seriesName
+   * @param openData the open data
+   * @param highData the high data
+   * @param lowData the low data
+   * @param closeData the close data
+   * @return A Series object that you can set properties on
+   */
+  public OHLCSeries addSeries(
+      String seriesName,
+      List<? extends Number> openData,
+      List<? extends Number> highData,
+      List<? extends Number> lowData,
+      List<? extends Number> closeData) {
+
+    return addSeries(seriesName, null, openData, highData, lowData, closeData);
+  }
+
+  /**
+   * Add a series for a Line type chart using int arrays
+   *
+   * @param seriesName
+   * @param xData the X-Axis data
+   * @param yData the Y-Axis data
+   * @return A Series object that you can set properties on
+   */
+  public OHLCSeries addSeries(String seriesName, int[] xData, int[] yData) {
+
+    return addSeries(
+        seriesName,
+        Utils.getDoubleArrayFromIntArray(xData),
+        Utils.getDoubleArrayFromIntArray(yData),
+        DataType.Number);
+  }
+
+  /**
+   * Add a series for a Line type chart using float arrays
+   *
+   * @param seriesName
+   * @param xData the X-Axis data
+   * @param yData the Y-Axis data
+   * @return A Series object that you can set properties on
+   */
+  public OHLCSeries addSeries(String seriesName, float[] xData, float[] yData) {
+
+    return addSeries(
+        seriesName,
+        Utils.getDoubleArrayFromFloatArray(xData),
+        Utils.getDoubleArrayFromFloatArray(yData),
+        DataType.Number);
+  }
+
+  /**
+   * Add a series for a Line type chart using double arrays
+   *
+   * @param seriesName
+   * @param xData the X-Axis data
+   * @param yData the Y-Axis data
+   * @return A Series object that you can set properties on
+   */
+  public OHLCSeries addSeries(String seriesName, double[] xData, double[] yData) {
+
+    return addSeries(seriesName, xData, yData, DataType.Number);
+  }
+
+  /**
+   * Add a series for a Line type chart using Lists
+   *
+   * @param seriesName
+   * @param xData the X-Axis data
+   * @param yData the Y-Axis data
+   * @return A Series object that you can set properties on
+   */
+  public OHLCSeries addSeries(String seriesName, List<?> xData, List<? extends Number> yData) {
+
+    DataType dataType = getDataType(xData);
+    switch (dataType) {
+      case Date:
+        return addSeries(
+            seriesName,
+            Utils.getDoubleArrayFromDateList(xData),
+            Utils.getDoubleArrayFromNumberList(yData),
+            getDataType(xData));
+
+      default:
+        return addSeries(
+            seriesName,
+            Utils.getDoubleArrayFromNumberList(xData),
+            Utils.getDoubleArrayFromNumberList(yData),
+            getDataType(xData));
+    }
+  }
+
+  private DataType getDataType(List<?> data) {
+
+    if (data == null || data.isEmpty()) {
+      return DataType.Number; // It will be autogenerated
+    }
+
+    DataType axisType;
+
+    Iterator<?> itr = data.iterator();
+    Object dataPoint = itr.next();
+    if (dataPoint instanceof Number) {
+      axisType = DataType.Number;
+    } else if (dataPoint instanceof Date) {
+      axisType = DataType.Date;
+    } else {
+      throw new IllegalArgumentException("Series data must be either Number or Date type!!!");
+    }
+    return axisType;
+  }
+
+  public OHLCSeries addSeries(
+      String seriesName,
+      double[] xData,
+      double[] openData,
+      double[] highData,
+      double[] lowData,
+      double[] closeData) {
+
+    return addSeries(
+        seriesName, xData, openData, highData, lowData, closeData, null, DataType.Number);
+  }
+
+  /**
+   * Add a series for a OHLC type chart using using double arrays
+   *
+   * @param seriesName
+   * @param openData the open data
+   * @param highData the high data
+   * @param lowData the low data
+   * @param closeData the close data
+   * @return A Series object that you can set properties on
+   */
+  public OHLCSeries addSeries(
+      String seriesName,
+      double[] openData,
+      double[] highData,
+      double[] lowData,
+      double[] closeData) {
+
+    return addSeries(seriesName, null, openData, highData, lowData, closeData);
+  }
+
+  /**
+   * Add a series for a OHLC type chart using using double arrays
+   *
+   * @param seriesName
+   * @param xData the x-axis data
+   * @param openData the open data
+   * @param highData the high data
+   * @param lowData the low data
+   * @param closeData the close data
+   * @param volumeData the volume data
+   * @return A Series object that you can set properties on
+   */
+  public OHLCSeries addSeries(
+      String seriesName,
+      double[] xData,
+      double[] openData,
+      double[] highData,
+      double[] lowData,
+      double[] closeData,
+      long[] volumeData) {
+
+    return addSeries(
+        seriesName, xData, openData, highData, lowData, closeData, volumeData, DataType.Number);
+  }
+
+  private OHLCSeries addSeries(
+      String seriesName,
+      double[] xData,
+      double[] openData,
+      double[] highData,
+      double[] lowData,
+      double[] closeData,
+      long[] volumeData,
+      DataType dataType) {
+
+    if (seriesMap.containsKey(seriesName)) {
+      throw new IllegalArgumentException(
+          "Series name >"
+              + seriesName
+              + "< has already been used. Use unique names for each series!!!");
+    }
+
+    // Sanity checks
+    sanityCheck(seriesName, openData, highData, lowData, closeData, volumeData);
+
+    final double[] xDataToUse;
+    if (xData != null) {
+      // Sanity check
+      checkDataLengths(seriesName, "X-Axis", "Close", xData, closeData);
+
+      xDataToUse = xData;
+    } else { // generate xData
+      xDataToUse = Utils.getGeneratedDataAsArray(closeData.length);
+    }
+    OHLCSeries series =
+        new OHLCSeries(
+            seriesName, xDataToUse, openData, highData, lowData, closeData, volumeData, dataType);
+    seriesMap.put(seriesName, series);
+
+    return series;
+  }
+
+  private OHLCSeries addSeries(
+      String seriesName, double[] xData, double[] yData, DataType dataType) {
+
+    if (seriesMap.containsKey(seriesName)) {
+      throw new IllegalArgumentException(
+          "Series name >"
+              + seriesName
+              + "< has already been used. Use unique names for each series!!!");
+    }
+
+    final double[] xDataToUse;
+    if (xData != null) {
+      // Sanity check
+      checkDataLengths(seriesName, "X-Axis", "Y-Axis", xData, yData);
+
+      xDataToUse = xData;
+    } else { // generate xData
+      xDataToUse = Utils.getGeneratedDataAsArray(yData.length);
+    }
+    OHLCSeries series = new OHLCSeries(seriesName, xDataToUse, yData, dataType);
+    seriesMap.put(seriesName, series);
+    return series;
+  }
+
+  /**
+   * Update a series by updating the xData, openData, highData, lowData and closeData
+   *
+   * @param seriesName
+   * @param newXData - set null to be automatically generated as a list of increasing Integers
+   *     starting from 1 and ending at the size of the new Y-Axis data list.
+   * @param newOpenData
+   * @param newHighData
+   * @param newLowData
+   * @param newCloseData
+   * @return
+   */
+  public OHLCSeries updateOHLCSeries(
+      String seriesName,
+      List<?> newXData,
+      List<? extends Number> newOpenData,
+      List<? extends Number> newHighData,
+      List<? extends Number> newLowData,
+      List<? extends Number> newCloseData) {
+
+    DataType dataType = getDataType(newXData);
+    switch (dataType) {
+      case Date:
+        return updateOHLCSeries(
+            seriesName,
+            Utils.getDoubleArrayFromDateList(newXData),
+            Utils.getDoubleArrayFromNumberList(newOpenData),
+            Utils.getDoubleArrayFromNumberList(newHighData),
+            Utils.getDoubleArrayFromNumberList(newLowData),
+            Utils.getDoubleArrayFromNumberList(newCloseData));
+
+      default:
+        return updateOHLCSeries(
+            seriesName,
+            Utils.getDoubleArrayFromNumberList(newXData),
+            Utils.getDoubleArrayFromNumberList(newOpenData),
+            Utils.getDoubleArrayFromNumberList(newHighData),
+            Utils.getDoubleArrayFromNumberList(newLowData),
+            Utils.getDoubleArrayFromNumberList(newCloseData));
+    }
+  }
+
+  public OHLCSeries updateOHLCSeries(
+      String seriesName,
+      List<?> newXData,
+      List<? extends Number> newOpenData,
+      List<? extends Number> newHighData,
+      List<? extends Number> newLowData,
+      List<? extends Number> newCloseData,
+      List<? extends Number> volumeData) {
+
+    DataType dataType = getDataType(newXData);
+    switch (dataType) {
+      case Date:
+        return updateOHLCSeries(
+            seriesName,
+            Utils.getDoubleArrayFromDateList(newXData),
+            Utils.getDoubleArrayFromNumberList(newOpenData),
+            Utils.getDoubleArrayFromNumberList(newHighData),
+            Utils.getDoubleArrayFromNumberList(newLowData),
+            Utils.getDoubleArrayFromNumberList(newCloseData),
+            Utils.getLongArrayFromNumberList(volumeData));
+
+      default:
+        return updateOHLCSeries(
+            seriesName,
+            Utils.getDoubleArrayFromNumberList(newXData),
+            Utils.getDoubleArrayFromNumberList(newOpenData),
+            Utils.getDoubleArrayFromNumberList(newHighData),
+            Utils.getDoubleArrayFromNumberList(newLowData),
+            Utils.getDoubleArrayFromNumberList(newCloseData),
+            Utils.getLongArrayFromNumberList(volumeData));
+    }
+  }
+
+  /**
+   * Update a series by updating the xData, openData, highData, lowData and closeData
+   *
+   * @param seriesName
+   * @param newXData - set null to be automatically generated as a list of increasing Integers
+   *     starting from 1 and ending at the size of the new Y-Axis data list.
+   * @param newOpenData
+   * @param newHighData
+   * @param newLowData
+   * @param newCloseData
+   * @return
+   */
+  public OHLCSeries updateOHLCSeries(
+      String seriesName,
+      double[] newXData,
+      double[] newOpenData,
+      double[] newHighData,
+      double[] newLowData,
+      double[] newCloseData) {
+
+    return updateOHLCSeries(
+        seriesName, newXData, newOpenData, newHighData, newLowData, newCloseData, null);
+  }
+
+  /**
+   * Update a series by updating the xData, openData, highData, lowData,closeData and volumeData
+   *
+   * @param seriesName
+   * @param newXData - set null to be automatically generated as a list of increasing Integers
+   *     starting from 1 and ending at the size of the new Y-Axis data list.
+   * @param newOpenData
+   * @param newHighData
+   * @param newLowData
+   * @param newCloseData
+   * @param newVolumeData
+   * @return
+   */
+  public OHLCSeries updateOHLCSeries(
+      String seriesName,
+      double[] newXData,
+      double[] newOpenData,
+      double[] newHighData,
+      double[] newLowData,
+      double[] newCloseData,
+      long[] newVolumeData) {
+
+    sanityCheck(seriesName, newOpenData, newHighData, newLowData, newCloseData, newVolumeData);
+
+    Map<String, OHLCSeries> seriesMap = getSeriesMap();
+    OHLCSeries series = seriesMap.get(seriesName);
+    if (series == null) {
+      throw new IllegalArgumentException("Series name >" + seriesName + "< not found!!!");
+    }
+    final double[] xDataToUse;
+    if (newXData != null) {
+      // Sanity check
+      checkDataLengths(seriesName, "X-Axis", "Close", newXData, newCloseData);
+      xDataToUse = newXData;
+    } else {
+      xDataToUse = Utils.getGeneratedDataAsArray(newCloseData.length);
+    }
+
+    series.replaceData(
+        xDataToUse, newOpenData, newHighData, newLowData, newCloseData, newVolumeData);
+
+    return series;
+  }
+
+  /**
+   * Update a series by updating the X-Axis and Y-Axis data
+   *
+   * @param seriesName
+   * @param newXData
+   * @param newYData
+   * @return
+   */
+  public OHLCSeries updateOHLCSeries(
+      String seriesName, List<?> newXData, List<? extends Number> newYData) {
+
+    DataType dataType = getDataType(newXData);
+    switch (dataType) {
+      case Date:
+        return updateOHLCSeries(
+            seriesName,
+            Utils.getDoubleArrayFromDateList(newXData),
+            Utils.getDoubleArrayFromNumberList(newYData));
+
+      default:
+        return updateOHLCSeries(
+            seriesName,
+            Utils.getDoubleArrayFromNumberList(newXData),
+            Utils.getDoubleArrayFromNumberList(newYData));
+    }
+  }
+
+  /**
+   * Update a series by updating the X-Axis and Y-Axis data
+   *
+   * @param seriesName
+   * @param newXData
+   * @param newYData
+   * @return
+   */
+  public OHLCSeries updateOHLCSeries(String seriesName, double[] newXData, double[] newYData) {
+
+    Map<String, OHLCSeries> seriesMap = getSeriesMap();
+    OHLCSeries series = seriesMap.get(seriesName);
+    if (series == null) {
+      throw new IllegalArgumentException("Series name >" + seriesName + "< not found!!!");
+    }
+    final double[] xDataToUse;
+    if (newXData != null) {
+      // Sanity check
+      checkDataLengths(seriesName, "newXData", "newYData", newXData, newYData);
+      xDataToUse = newXData;
+    } else {
+      xDataToUse = Utils.getGeneratedDataAsArray(newYData.length);
+    }
+
+    series.replaceData(xDataToUse, newYData);
+    return series;
+  }
+
+  private void checkData(String seriesName, String dataName, double[] data) {
+
+    if (data == null) {
+      throw new IllegalArgumentException(dataName + " data cannot be null!!! >" + seriesName);
+    }
+    if (data.length == 0) {
+      throw new IllegalArgumentException(dataName + " data cannot be empty!!! >" + seriesName);
+    }
+  }
+
+  private void checkDataLengths(
+      String seriesName, String data1Name, String data2Name, double[] data1, double[] data2) {
+
+    if (data1.length != data2.length) {
+      throw new IllegalArgumentException(
+          data1Name + " and " + data2Name + " sizes are not the same!!! >" + seriesName);
+    }
+  }
+
+  ///////////////////////////////////////////////////
+  // Internal Members and Methods ///////////////////
+  ///////////////////////////////////////////////////
+
+  private void sanityCheck(
+      String seriesName,
+      double[] openData,
+      double[] highData,
+      double[] lowData,
+      double[] closeData,
+      long[] volumeData) {
+
+    checkData(seriesName, "Open", openData);
+    checkData(seriesName, "High", highData);
+    checkData(seriesName, "Low", lowData);
+    checkData(seriesName, "Close", closeData);
+
+    checkDataLengths(seriesName, "Open", "Close", openData, closeData);
+    checkDataLengths(seriesName, "High", "Close", highData, closeData);
+    checkDataLengths(seriesName, "Low", "Close", lowData, closeData);
+    if (volumeData != null) {
+      if (volumeData.length != closeData.length) {
+        throw new IllegalArgumentException(
+            "Volume and Close sizes are not the same!!! >" + seriesName);
+      }
+    }
+  }
+
+  @Override
+  public void paint(Graphics2D g, int width, int height) {
+
+    setWidth(width);
+    setHeight(height);
+
+    // set the series render styles if they are not set. Legend and Plot need it.
+    for (OHLCSeries series : getSeriesMap().values()) {
+      OHLCSeries.OHLCSeriesRenderStyle renderStyle =
+          series.getOhlcSeriesRenderStyle(); // would be directly set
+      if (renderStyle == null) { // wasn't overridden, use default from Style Manager
+        series.setOhlcSeriesRenderStyle(getStyler().getDefaultSeriesRenderStyle());
+      }
+    }
+    setSeriesStyles();
+
+    paintBackground(g);
+
+    axisPair.paint(g);
+    plot.paint(g);
+    chartTitle.paint(g);
+    legend.paint(g);
+    annotations.forEach(x -> x.paint(g));
+  }
+
+  /** set the series color, marker and line style based on theme */
+  private void setSeriesStyles() {
+
+    SeriesColorMarkerLineStyleCycler seriesColorMarkerLineStyleCycler =
+        new SeriesColorMarkerLineStyleCycler(
+            getStyler().getSeriesColors(),
+            getStyler().getSeriesMarkers(),
+            getStyler().getSeriesLines());
+    for (OHLCSeries series : getSeriesMap().values()) {
+
+      SeriesColorMarkerLineStyle seriesColorMarkerLineStyle =
+          seriesColorMarkerLineStyleCycler.getNextSeriesColorMarkerLineStyle();
+
+      if (series.getLineStyle() == null) { // wasn't set manually
+        series.setLineStyle(seriesColorMarkerLineStyle.getStroke());
+      }
+      if (series.getOhlcSeriesRenderStyle() == OHLCSeriesRenderStyle.Line
+          && series.getLineColor() == null) { // wasn't set manually
+        series.setLineColor(seriesColorMarkerLineStyle.getColor());
+      }
+      if (series.getFillColor() == null) { // wasn't set manually
+        series.setFillColor(seriesColorMarkerLineStyle.getColor());
+      }
+      if (series.getMarker() == null) { // wasn't set manually
+        series.setMarker(seriesColorMarkerLineStyle.getMarker());
+      }
+      if (series.getMarkerColor() == null) { // wasn't set manually
+        series.setMarkerColor(seriesColorMarkerLineStyle.getColor());
+      }
+      if (series.getUpColor() == null) { // wasn't set manually
+        series.setUpColor(new Color(19, 179, 70));
+      }
+      if (series.getDownColor() == null) { // wasn't set manually
+        series.setDownColor(new Color(242, 39, 42));
+      }
+    }
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/OHLCChartBuilder.java b/XChart/xchart/src/main/java/org/knowm/xchart/OHLCChartBuilder.java
new file mode 100644
index 0000000000000000000000000000000000000000..219ba040d7312e49914e58715511adbb009198b3
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/OHLCChartBuilder.java
@@ -0,0 +1,34 @@
+package org.knowm.xchart;
+
+import org.knowm.xchart.internal.ChartBuilder;
+
+public class OHLCChartBuilder extends ChartBuilder<OHLCChartBuilder, OHLCChart> {
+
+  String xAxisTitle = "";
+  String yAxisTitle = "";
+
+  public OHLCChartBuilder() {}
+
+  public OHLCChartBuilder xAxisTitle(String xAxisTitle) {
+
+    this.xAxisTitle = xAxisTitle;
+    return this;
+  }
+
+  public OHLCChartBuilder yAxisTitle(String yAxisTitle) {
+
+    this.yAxisTitle = yAxisTitle;
+    return this;
+  }
+
+  /**
+   * return fully built XYChart
+   *
+   * @return a XYChart
+   */
+  @Override
+  public OHLCChart build() {
+
+    return new OHLCChart(this);
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/OHLCSeries.java b/XChart/xchart/src/main/java/org/knowm/xchart/OHLCSeries.java
new file mode 100644
index 0000000000000000000000000000000000000000..99205ff3874dfbd304e88ea07588fcae1fced965
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/OHLCSeries.java
@@ -0,0 +1,314 @@
+package org.knowm.xchart;
+
+import java.awt.Color;
+import org.knowm.xchart.internal.chartpart.RenderableSeries;
+import org.knowm.xchart.internal.chartpart.RenderableSeries.LegendRenderType;
+import org.knowm.xchart.internal.series.MarkerSeries;
+
+public class OHLCSeries extends MarkerSeries {
+
+  private double[] xData; // can be Number or Date(epochtime)
+  private double[] openData;
+  private double[] highData;
+  private double[] lowData;
+  private double[] closeData;
+  private long[] volumeData;
+  private double[] yData;
+  private OHLCSeriesRenderStyle ohlcSeriesRenderStyle;
+
+  /** Up Color */
+  private Color upColor;
+
+  /** Down Color */
+  private Color downColor;
+
+  /**
+   * Constructor
+   *
+   * @param name
+   * @param xData
+   * @param openData
+   * @param highData
+   * @param lowData
+   * @param closeData
+   */
+  public OHLCSeries(
+      String name,
+      double[] xData,
+      double[] openData,
+      double[] highData,
+      double[] lowData,
+      double[] closeData,
+      DataType xAxisDataType) {
+
+    this(name, xData, openData, highData, lowData, closeData, null, xAxisDataType);
+  }
+
+  /**
+   * Constructor
+   *
+   * @param name
+   * @param xData
+   * @param openData
+   * @param highData
+   * @param lowData
+   * @param closeData
+   * @param volumeData
+   */
+  public OHLCSeries(
+      String name,
+      double[] xData,
+      double[] openData,
+      double[] highData,
+      double[] lowData,
+      double[] closeData,
+      long[] volumeData,
+      DataType xAxisDataType) {
+
+    super(name, xAxisDataType);
+    this.xData = xData;
+    this.openData = openData;
+    this.highData = highData;
+    this.lowData = lowData;
+    this.closeData = closeData;
+    this.volumeData = volumeData;
+    calculateMinMax();
+  }
+
+  /**
+   * Constructor
+   *
+   * @param name
+   * @param xData
+   * @param yData
+   * @param xAxisDataType
+   */
+  public OHLCSeries(String name, double[] xData, double[] yData, DataType xAxisDataType) {
+
+    super(name, xAxisDataType);
+    this.xData = xData;
+    this.yData = yData;
+    this.ohlcSeriesRenderStyle = OHLCSeriesRenderStyle.Line;
+    calculateMinMax();
+  }
+
+  public OHLCSeriesRenderStyle getOhlcSeriesRenderStyle() {
+
+    return ohlcSeriesRenderStyle;
+  }
+
+  public OHLCSeries setOhlcSeriesRenderStyle(OHLCSeriesRenderStyle ohlcSeriesRenderStyle) {
+
+    if (yData == null && ohlcSeriesRenderStyle == OHLCSeriesRenderStyle.Line) {
+      throw new IllegalArgumentException(
+          "Series name >"
+              + this.getName()
+              + "<, yData is equal to null and cannot be set to OHLCSeriesRenderStyle.Line");
+    }
+    if (yData != null && ohlcSeriesRenderStyle != OHLCSeriesRenderStyle.Line) {
+      throw new IllegalArgumentException(
+          "Series name >"
+              + this.getName()
+              + "<, yData is not equal to null and can only be set to OHLCSeriesRenderStyle.Line");
+    }
+    this.ohlcSeriesRenderStyle = ohlcSeriesRenderStyle;
+    return this;
+  }
+
+  public Color getUpColor() {
+
+    return upColor;
+  }
+
+  /**
+   * Set the up color of the series
+   *
+   * @param color
+   */
+  public OHLCSeries setUpColor(java.awt.Color color) {
+
+    this.upColor = color;
+    return this;
+  }
+
+  public Color getDownColor() {
+
+    return downColor;
+  }
+
+  /**
+   * Set the down color of the series
+   *
+   * @param color
+   */
+  public OHLCSeries setDownColor(java.awt.Color color) {
+
+    this.downColor = color;
+    return this;
+  }
+
+  @Override
+  public LegendRenderType getLegendRenderType() {
+
+    return ohlcSeriesRenderStyle.getLegendRenderType();
+  }
+
+  /**
+   * This is an internal method which shouldn't be called from client code. Use {@link
+   * org.knowm.xchart.OHLCChart#updateOHLCSeries} instead!
+   *
+   * @param newXData
+   * @param newOpenData
+   * @param newHighData
+   * @param newLowData
+   * @param newCloseData
+   */
+  void replaceData(
+      double[] newXData,
+      double[] newOpenData,
+      double[] newHighData,
+      double[] newLowData,
+      double[] newCloseData) {
+
+    replaceData(newXData, newOpenData, newHighData, newLowData, newCloseData, null);
+  }
+
+  /**
+   * This is an internal method which shouldn't be called from client code. Use {@link
+   * org.knowm.xchart.OHLCChart#updateOHLCSeries} instead!
+   *
+   * @param newXData
+   * @param newOpenData
+   * @param newHighData
+   * @param newLowData
+   * @param newCloseData
+   * @param newVolumeData
+   */
+  void replaceData(
+      double[] newXData,
+      double[] newOpenData,
+      double[] newHighData,
+      double[] newLowData,
+      double[] newCloseData,
+      long[] newVolumeData) {
+
+    // Sanity check should already by done
+    this.xData = newXData;
+    this.openData = newOpenData;
+    this.highData = newHighData;
+    this.lowData = newLowData;
+    this.closeData = newCloseData;
+    this.volumeData = newVolumeData;
+    calculateMinMax();
+  }
+
+  /**
+   * This is an internal method which shouldn't be called from client code. Use {@link
+   * org.knowm.xchart.OHLCChart#updateOHLCSeries} instead!
+   *
+   * @param newXData
+   * @param newYData
+   */
+  void replaceData(double[] newXData, double[] newYData) {
+
+    this.xData = newXData;
+    this.yData = newYData;
+    calculateMinMax();
+  }
+
+  /**
+   * Finds the min and max of a dataset
+   *
+   * @param lows
+   * @param highs
+   * @return
+   */
+  private double[] findMinMax(double[] lows, double[] highs) {
+
+    double min = Double.MAX_VALUE;
+    double max = -Double.MAX_VALUE;
+
+    for (int i = 0; i < highs.length; i++) {
+
+      if (!Double.isNaN(highs[i]) && highs[i] > max) {
+        max = highs[i];
+      }
+      if (!Double.isNaN(lows[i]) && lows[i] < min) {
+        min = lows[i];
+      }
+    }
+
+    return new double[] {min, max};
+  }
+
+  @Override
+  protected void calculateMinMax() {
+
+    double[] xMinMax = findMinMax(xData, xData);
+    xMin = xMinMax[0];
+    xMax = xMinMax[1];
+    final double[] yMinMax;
+    if (yData == null) {
+      yMinMax = findMinMax(lowData, highData);
+    } else {
+      yMinMax = findMinMax(yData, yData);
+    }
+    yMin = yMinMax[0];
+    yMax = yMinMax[1];
+  }
+
+  public double[] getXData() {
+
+    return xData;
+  }
+
+  public double[] getOpenData() {
+
+    return openData;
+  }
+
+  public double[] getHighData() {
+
+    return highData;
+  }
+
+  public double[] getLowData() {
+
+    return lowData;
+  }
+
+  public double[] getCloseData() {
+
+    return closeData;
+  }
+
+  // TODO remove this??
+  public long[] getVolumeData() {
+
+    return volumeData;
+  }
+
+  public double[] getYData() {
+
+    return yData;
+  }
+
+  public enum OHLCSeriesRenderStyle implements RenderableSeries {
+    Candle(LegendRenderType.Line),
+    HiLo(LegendRenderType.Line),
+    Line(LegendRenderType.Line);
+
+    private final LegendRenderType legendRenderType;
+
+    OHLCSeriesRenderStyle(LegendRenderType legendRenderType) {
+
+      this.legendRenderType = legendRenderType;
+    }
+
+    @Override
+    public LegendRenderType getLegendRenderType() {
+
+      return legendRenderType;
+    }
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/PdfboxGraphicsEncoder.java b/XChart/xchart/src/main/java/org/knowm/xchart/PdfboxGraphicsEncoder.java
new file mode 100644
index 0000000000000000000000000000000000000000..d01e5d2bc6964f3eca365b9ca22c1ffb7776f21b
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/PdfboxGraphicsEncoder.java
@@ -0,0 +1,142 @@
+package org.knowm.xchart;
+
+import de.rototor.pdfbox.graphics2d.PdfBoxGraphics2D;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.PDPageContentStream;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
+import org.knowm.xchart.internal.chartpart.Chart;
+
+/** A helper class with static methods for saving Charts as a PDF file */
+public class PdfboxGraphicsEncoder {
+
+  private static final String PDF_FILE_EXTENSION = ".pdf";
+
+  /** Constructor - Private constructor to prevent instantiation */
+  private PdfboxGraphicsEncoder() {}
+
+  /**
+   * Write a chart to a file
+   *
+   * @param chart Chart
+   * @param fileName file name path
+   * @throws IOException
+   */
+  public static void savePdfboxGraphics(Chart chart, String fileName) throws IOException {
+
+    savePdfboxGraphics(chart, new File(addFileExtension(fileName)));
+  }
+
+  /**
+   * Write a chart to a file
+   *
+   * @param chart Chart
+   * @param file File
+   * @throws IOException
+   */
+  public static void savePdfboxGraphics(Chart chart, File file) throws IOException {
+
+    savePdfboxGraphics(chart, new BufferedOutputStream(new FileOutputStream(file)));
+  }
+
+  /**
+   * Write a chart to an OutputStream
+   *
+   * @param chart Chart
+   * @param os OutputStream
+   * @throws IOException
+   */
+  public static void savePdfboxGraphics(Chart chart, OutputStream os) throws IOException {
+
+    List<Chart> charts = new ArrayList<>();
+    charts.add(chart);
+    savePdfboxGraphics(charts, os);
+  }
+
+  /**
+   * Write multiple charts to a file
+   *
+   * @param charts List&lt;? extends Chart&gt;
+   * @param fileName file name path
+   * @throws IOException
+   */
+  public static void savePdfboxGraphics(List<? extends Chart> charts, String fileName)
+      throws IOException {
+
+    savePdfboxGraphics(charts, new File(addFileExtension(fileName)));
+  }
+
+  /**
+   * Write multiple charts to a file
+   *
+   * @param charts List&lt;? extends Chart&gt;
+   * @param file File
+   * @throws IOException
+   */
+  public static void savePdfboxGraphics(List<? extends Chart> charts, File file)
+      throws IOException {
+
+    savePdfboxGraphics(charts, new BufferedOutputStream(new FileOutputStream(file)));
+  }
+
+  /**
+   * Write multiple charts to an OutputStream
+   *
+   * @param charts List&lt;? extends Chart&gt;
+   * @param os OutputStream
+   * @throws IOException
+   */
+  public static void savePdfboxGraphics(List<? extends Chart> charts, OutputStream os)
+      throws IOException {
+
+    PDDocument document = new PDDocument();
+    PDRectangle mediaBox = null;
+    PDPage page = null;
+    PDPageContentStream contentStream = null;
+    PdfBoxGraphics2D pdfBoxGraphics2D = null;
+    PDFormXObject xform = null;
+    for (Chart chart : charts) {
+      mediaBox = new PDRectangle(chart.getWidth(), chart.getHeight());
+      page = new PDPage(mediaBox);
+      // add page
+      document.addPage(page);
+      pdfBoxGraphics2D = new PdfBoxGraphics2D(document, chart.getWidth(), chart.getHeight());
+      chart.paint(pdfBoxGraphics2D, chart.getWidth(), chart.getHeight());
+      pdfBoxGraphics2D.dispose();
+      xform = pdfBoxGraphics2D.getXFormObject();
+
+      contentStream = new PDPageContentStream(document, page);
+      contentStream.drawForm(xform);
+      contentStream.close();
+    }
+
+    document.save(os);
+    document.close();
+  }
+
+  /**
+   * Only adds the extension of the ".pdf" to the filename if the filename doesn't already have it.
+   *
+   * @param fileName
+   * @return filename (if extension already exists), otherwise;: filename + ".pdf"
+   */
+  private static String addFileExtension(String fileName) {
+
+    String fileNameWithFileExtension = fileName;
+    if (fileName.length() <= PDF_FILE_EXTENSION.length()
+        || !fileName
+            .substring(fileName.length() - PDF_FILE_EXTENSION.length(), fileName.length())
+            .equalsIgnoreCase(PDF_FILE_EXTENSION)) {
+      fileNameWithFileExtension = fileName + PDF_FILE_EXTENSION;
+    }
+    return fileNameWithFileExtension;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/PieChart.java b/XChart/xchart/src/main/java/org/knowm/xchart/PieChart.java
new file mode 100644
index 0000000000000000000000000000000000000000..74cc65723b810e03fcdfa4420d981d2e2c27971d
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/PieChart.java
@@ -0,0 +1,149 @@
+package org.knowm.xchart;
+
+import java.awt.Graphics2D;
+import java.util.Map;
+import org.knowm.xchart.internal.chartpart.Chart;
+import org.knowm.xchart.internal.chartpart.Legend_Pie;
+import org.knowm.xchart.internal.chartpart.Plot_Pie;
+import org.knowm.xchart.internal.series.Series;
+import org.knowm.xchart.internal.style.SeriesColorMarkerLineStyle;
+import org.knowm.xchart.internal.style.SeriesColorMarkerLineStyleCycler;
+import org.knowm.xchart.style.PieStyler;
+import org.knowm.xchart.style.Styler.ChartTheme;
+import org.knowm.xchart.style.theme.Theme;
+
+public class PieChart extends Chart<PieStyler, PieSeries> {
+
+  /**
+   * Constructor - the default Chart Theme will be used (XChartTheme)
+   *
+   * @param width
+   * @param height
+   */
+  public PieChart(int width, int height) {
+
+    super(width, height, new PieStyler());
+    plot = new Plot_Pie<PieStyler, PieSeries>(this);
+    legend = new Legend_Pie<PieStyler, PieSeries>(this);
+  }
+
+  /**
+   * Constructor
+   *
+   * @param width
+   * @param height
+   * @param theme - pass in a instance of Theme class, probably a custom Theme.
+   */
+  public PieChart(int width, int height, Theme theme) {
+
+    this(width, height);
+    styler.setTheme(theme);
+  }
+
+  /**
+   * Constructor
+   *
+   * @param width
+   * @param height
+   * @param chartTheme - pass in the desired ChartTheme enum
+   */
+  public PieChart(int width, int height, ChartTheme chartTheme) {
+
+    this(width, height, chartTheme.newInstance(chartTheme));
+  }
+
+  /**
+   * Constructor
+   *
+   * @param chartBuilder
+   */
+  public PieChart(PieChartBuilder chartBuilder) {
+
+    this(chartBuilder.width, chartBuilder.height, chartBuilder.chartTheme);
+    setTitle(chartBuilder.title);
+  }
+
+  /**
+   * Add a series for a Pie type chart
+   *
+   * @param seriesName
+   * @param value
+   * @return
+   */
+  public PieSeries addSeries(String seriesName, Number value) {
+
+    PieSeries series = new PieSeries(seriesName, value);
+
+    if (seriesMap.containsKey(seriesName)) {
+      throw new IllegalArgumentException(
+          "Series name >"
+              + seriesName
+              + "< has already been used. Use unique names for each series!!!");
+    }
+    seriesMap.put(seriesName, series);
+
+    return series;
+  }
+
+  /**
+   * Update a series by updating the pie slide value
+   *
+   * @param seriesName
+   * @param value
+   * @return
+   */
+  public PieSeries updatePieSeries(String seriesName, Number value) {
+
+    Map<String, PieSeries> seriesMap = getSeriesMap();
+    PieSeries series = seriesMap.get(seriesName);
+    if (series == null) {
+      throw new IllegalArgumentException("Series name >" + seriesName + "< not found!!!");
+    }
+    series.replaceData(value);
+
+    return series;
+  }
+
+  @Override
+  public void paint(Graphics2D g, int width, int height) {
+
+    setWidth(width);
+    setHeight(height);
+
+    // set the series types if they are not set. Legend and Plot need it.
+    for (PieSeries seriesPie : getSeriesMap().values()) {
+      PieSeries.PieSeriesRenderStyle seriesType =
+          seriesPie.getChartPieSeriesRenderStyle(); // would be directly set
+      if (seriesType == null) { // wasn't overridden, use default from Style Manager
+        seriesPie.setChartPieSeriesRenderStyle(getStyler().getDefaultSeriesRenderStyle());
+      }
+    }
+    setSeriesStyles();
+
+    paintBackground(g);
+
+    plot.paint(g);
+    chartTitle.paint(g);
+    legend.paint(g);
+    annotations.forEach(x -> x.paint(g));
+  }
+
+  /** set the series color based on theme */
+  private void setSeriesStyles() {
+
+    SeriesColorMarkerLineStyleCycler seriesColorMarkerLineStyleCycler =
+        new SeriesColorMarkerLineStyleCycler(
+            getStyler().getSeriesColors(),
+            getStyler().getSeriesMarkers(),
+            getStyler().getSeriesLines());
+    for (Series series : getSeriesMap().values()) {
+
+      SeriesColorMarkerLineStyle seriesColorMarkerLineStyle =
+          seriesColorMarkerLineStyleCycler.getNextSeriesColorMarkerLineStyle();
+
+      if (series.getFillColor() == null) { // wasn't set manually
+        series.setFillColor(seriesColorMarkerLineStyle.getColor());
+      }
+    }
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/PieChartBuilder.java b/XChart/xchart/src/main/java/org/knowm/xchart/PieChartBuilder.java
new file mode 100644
index 0000000000000000000000000000000000000000..0db21964c0926e88a54c96afdd55569ecfda5285
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/PieChartBuilder.java
@@ -0,0 +1,19 @@
+package org.knowm.xchart;
+
+import org.knowm.xchart.internal.ChartBuilder;
+
+public class PieChartBuilder extends ChartBuilder<PieChartBuilder, PieChart> {
+
+  public PieChartBuilder() {}
+
+  /**
+   * return fully built ChartPie
+   *
+   * @return a ChartPie
+   */
+  @Override
+  public PieChart build() {
+
+    return new PieChart(this);
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/PieSeries.java b/XChart/xchart/src/main/java/org/knowm/xchart/PieSeries.java
new file mode 100644
index 0000000000000000000000000000000000000000..6480a191e382af7f89764b510d66a26a87beb159
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/PieSeries.java
@@ -0,0 +1,67 @@
+package org.knowm.xchart;
+
+import org.knowm.xchart.internal.chartpart.RenderableSeries.LegendRenderType;
+import org.knowm.xchart.internal.series.Series;
+
+/** A Series containing Pie data to be plotted on a Chart */
+public class PieSeries extends Series {
+
+  private PieSeriesRenderStyle chartPieSeriesRenderStyle = null;
+  private Number value;
+
+  /**
+   * Constructor
+   *
+   * @param name
+   * @param value
+   */
+  public PieSeries(String name, Number value) {
+
+    super(name);
+    this.value = value;
+  }
+
+  /**
+   * *This is an internal method which shouldn't be called from client code. Use
+   * PieChart.updatePieSeries instead!
+   *
+   * @param value
+   */
+  public void replaceData(Number value) {
+
+    this.value = value;
+  }
+
+  public PieSeriesRenderStyle getChartPieSeriesRenderStyle() {
+
+    return chartPieSeriesRenderStyle;
+  }
+
+  public PieSeries setChartPieSeriesRenderStyle(PieSeriesRenderStyle chartPieSeriesRenderStyle) {
+
+    this.chartPieSeriesRenderStyle = chartPieSeriesRenderStyle;
+    return this;
+  }
+
+  public Number getValue() {
+
+    return value;
+  }
+
+  public void setValue(Number value) {
+
+    this.value = value;
+  }
+
+  @Override
+  public LegendRenderType getLegendRenderType() {
+
+    // Pie charts are always rendered as a Box in the legend
+    return null;
+  }
+
+  public enum PieSeriesRenderStyle {
+    Pie(),
+    Donut();
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/QuickChart.java b/XChart/xchart/src/main/java/org/knowm/xchart/QuickChart.java
new file mode 100644
index 0000000000000000000000000000000000000000..a689779dc61a4277fe3e75eea6bd9aeff7cc67ed
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/QuickChart.java
@@ -0,0 +1,115 @@
+package org.knowm.xchart;
+
+import java.util.List;
+import org.knowm.xchart.style.markers.SeriesMarkers;
+
+/** A convenience class for making Charts with one line of code */
+public final class QuickChart {
+
+  private static final int WIDTH = 600;
+  private static final int HEIGHT = 400;
+
+  /** private Constructor */
+  private QuickChart() {}
+
+  /**
+   * Creates a Chart with default style
+   *
+   * @param chartTitle the Chart title
+   * @param xTitle The X-Axis title
+   * @param yTitle The Y-Axis title
+   * @param seriesName The name of the series
+   * @param xData An array containing the X-Axis data
+   * @param yData An array containing Y-Axis data
+   * @return a Chart Object
+   */
+  public static XYChart getChart(
+      String chartTitle,
+      String xTitle,
+      String yTitle,
+      String seriesName,
+      double[] xData,
+      double[] yData) {
+
+    double[][] yData2d = {yData};
+    if (seriesName == null) {
+      return getChart(chartTitle, xTitle, yTitle, null, xData, yData2d);
+    } else {
+      return getChart(chartTitle, xTitle, yTitle, new String[] {seriesName}, xData, yData2d);
+    }
+  }
+
+  /**
+   * Creates a Chart with multiple Series for the same X-Axis data with default style
+   *
+   * @param chartTitle the Chart title
+   * @param xTitle The X-Axis title
+   * @param yTitle The Y-Axis title
+   * @param seriesNames An array of the name of the multiple series
+   * @param xData An array containing the X-Axis data
+   * @param yData An array of double arrays containing multiple Y-Axis data
+   * @return a Chart Object
+   */
+  public static XYChart getChart(
+      String chartTitle,
+      String xTitle,
+      String yTitle,
+      String[] seriesNames,
+      double[] xData,
+      double[][] yData) {
+
+    // Create Chart
+    XYChart chart = new XYChart(WIDTH, HEIGHT);
+
+    // Customize Chart
+    chart.setTitle(chartTitle);
+    chart.setXAxisTitle(xTitle);
+    chart.setYAxisTitle(yTitle);
+
+    // Series
+    for (int i = 0; i < yData.length; i++) {
+      XYSeries series;
+      if (seriesNames != null) {
+        series = chart.addSeries(seriesNames[i], xData, yData[i]);
+      } else {
+        chart.getStyler().setLegendVisible(false);
+        series = chart.addSeries(" " + i, xData, yData[i]);
+      }
+      series.setMarker(SeriesMarkers.NONE);
+    }
+    return chart;
+  }
+
+  /**
+   * Creates a Chart with default style
+   *
+   * @param chartTitle the Chart title
+   * @param xTitle The X-Axis title
+   * @param yTitle The Y-Axis title
+   * @param seriesName The name of the series
+   * @param xData A Collection containing the X-Axis data
+   * @param yData A Collection containing Y-Axis data
+   * @return a Chart Object
+   */
+  public static XYChart getChart(
+      String chartTitle,
+      String xTitle,
+      String yTitle,
+      String seriesName,
+      List<? extends Number> xData,
+      List<? extends Number> yData) {
+
+    // Create Chart
+    XYChart chart = new XYChart(WIDTH, HEIGHT);
+
+    // Customize Chart
+    chart.setTitle(chartTitle);
+    chart.setXAxisTitle(xTitle);
+    chart.setYAxisTitle(yTitle);
+
+    XYSeries series = chart.addSeries(seriesName, xData, yData);
+    series.setMarker(SeriesMarkers.NONE);
+
+    return chart;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/RadarChart.java b/XChart/xchart/src/main/java/org/knowm/xchart/RadarChart.java
new file mode 100644
index 0000000000000000000000000000000000000000..8f03c760041089ef02ab5ff4de38e510c2f7933f
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/RadarChart.java
@@ -0,0 +1,188 @@
+package org.knowm.xchart;
+
+import java.awt.Graphics2D;
+import org.knowm.xchart.internal.chartpart.Chart;
+import org.knowm.xchart.internal.chartpart.Legend_Marker;
+import org.knowm.xchart.internal.chartpart.Plot_Radar;
+import org.knowm.xchart.internal.style.SeriesColorMarkerLineStyle;
+import org.knowm.xchart.internal.style.SeriesColorMarkerLineStyleCycler;
+import org.knowm.xchart.style.RadarStyler;
+import org.knowm.xchart.style.Styler.ChartTheme;
+import org.knowm.xchart.style.theme.Theme;
+
+public class RadarChart extends Chart<RadarStyler, RadarSeries> {
+
+  private String[] radiiLabels;
+
+  /**
+   * Constructor - the default Chart Theme will be used (XChartTheme)
+   *
+   * @param width
+   * @param height
+   */
+  public RadarChart(int width, int height) {
+
+    super(width, height, new RadarStyler());
+    plot = new Plot_Radar<>(this);
+    legend = new Legend_Marker<RadarStyler, RadarSeries>(this);
+  }
+
+  /**
+   * Constructor
+   *
+   * @param width
+   * @param height
+   * @param theme - pass in a instance of Theme class, probably a custom Theme.
+   */
+  public RadarChart(int width, int height, Theme theme) {
+
+    this(width, height);
+    styler.setTheme(theme);
+  }
+
+  /**
+   * Constructor
+   *
+   * @param width
+   * @param height
+   * @param chartTheme - pass in the desired ChartTheme enum
+   */
+  public RadarChart(int width, int height, ChartTheme chartTheme) {
+
+    this(width, height, chartTheme.newInstance(chartTheme));
+  }
+
+  /**
+   * Constructor
+   *
+   * @param radarChartBuilder
+   */
+  public RadarChart(RadarChartBuilder radarChartBuilder) {
+
+    this(radarChartBuilder.width, radarChartBuilder.height, radarChartBuilder.chartTheme);
+    setTitle(radarChartBuilder.title);
+  }
+
+  public String[] getRadiiLabels() {
+
+    return radiiLabels;
+  }
+
+  /**
+   * Sets the radii labels
+   *
+   * @param radiiLabels
+   */
+  public void setRadiiLabels(String[] radiiLabels) {
+
+    this.radiiLabels = radiiLabels;
+  }
+
+  /**
+   * Add a series for a Radar type chart
+   *
+   * @param seriesName
+   * @param values
+   * @return
+   */
+  public RadarSeries addSeries(String seriesName, double[] values) {
+
+    return addSeries(seriesName, values, null);
+  }
+
+  /**
+   * Add a series for a Radar type chart
+   *
+   * @param seriesName
+   * @param values
+   * @param tooltipOverrides
+   * @return
+   */
+  public RadarSeries addSeries(String seriesName, double[] values, String[] tooltipOverrides) {
+
+    // Sanity checks
+    sanityCheck(seriesName, values, tooltipOverrides);
+
+    RadarSeries series = new RadarSeries(seriesName, values, tooltipOverrides);
+
+    seriesMap.put(seriesName, series);
+
+    return series;
+  }
+
+  private void sanityCheck(String seriesName, double[] values, String[] annotations) {
+
+    if (radiiLabels == null) {
+      throw new IllegalArgumentException("Variable labels cannot be null!!!");
+    }
+
+    if (seriesMap.containsKey(seriesName)) {
+      throw new IllegalArgumentException(
+          "Series name >"
+              + seriesName
+              + "< has already been used. Use unique names for each series!!!");
+    }
+    if (values == null) {
+      throw new IllegalArgumentException("Values data cannot be null!!!");
+    }
+    if (values.length < radiiLabels.length) {
+      throw new IllegalArgumentException("Too few values!!!");
+    }
+    for (double d : values) {
+      if (d < 0 || d > 1) {
+        throw new IllegalArgumentException("Values must be in [0, 1] range!!!");
+      }
+    }
+
+    if (annotations != null && annotations.length < radiiLabels.length) {
+      throw new IllegalArgumentException("Too few tool tips!!!");
+    }
+  }
+
+  @Override
+  public void paint(Graphics2D g, int width, int height) {
+
+    setWidth(width);
+    setHeight(height);
+
+    setSeriesStyles();
+
+    paintBackground(g);
+
+    plot.paint(g);
+    chartTitle.paint(g);
+    legend.paint(g);
+    annotations.forEach(x -> x.paint(g));
+  }
+
+  /** set the series color based on theme */
+  private void setSeriesStyles() {
+
+    SeriesColorMarkerLineStyleCycler seriesColorMarkerLineStyleCycler =
+        new SeriesColorMarkerLineStyleCycler(
+            getStyler().getSeriesColors(),
+            getStyler().getSeriesMarkers(),
+            getStyler().getSeriesLines());
+    for (RadarSeries series : getSeriesMap().values()) {
+
+      SeriesColorMarkerLineStyle seriesColorMarkerLineStyle =
+          seriesColorMarkerLineStyleCycler.getNextSeriesColorMarkerLineStyle();
+
+      if (series.getLineStyle() == null) { // wasn't set manually
+        series.setLineStyle(seriesColorMarkerLineStyle.getStroke());
+      }
+      if (series.getLineColor() == null) { // wasn't set manually
+        series.setLineColor(seriesColorMarkerLineStyle.getColor());
+      }
+      if (series.getFillColor() == null) { // wasn't set manually
+        series.setFillColor(seriesColorMarkerLineStyle.getColor());
+      }
+      if (series.getMarker() == null) { // wasn't set manually
+        series.setMarker(seriesColorMarkerLineStyle.getMarker());
+      }
+      if (series.getMarkerColor() == null) { // wasn't set manually
+        series.setMarkerColor(seriesColorMarkerLineStyle.getColor());
+      }
+    }
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/RadarChartBuilder.java b/XChart/xchart/src/main/java/org/knowm/xchart/RadarChartBuilder.java
new file mode 100644
index 0000000000000000000000000000000000000000..d1589f4b76374a84a8d98ef5d700877f9ec9de98
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/RadarChartBuilder.java
@@ -0,0 +1,14 @@
+package org.knowm.xchart;
+
+import org.knowm.xchart.internal.ChartBuilder;
+
+public class RadarChartBuilder extends ChartBuilder<RadarChartBuilder, RadarChart> {
+
+  public RadarChartBuilder() {}
+
+  @Override
+  public RadarChart build() {
+
+    return new RadarChart(this);
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/RadarSeries.java b/XChart/xchart/src/main/java/org/knowm/xchart/RadarSeries.java
new file mode 100644
index 0000000000000000000000000000000000000000..289a1f75e0b05e0df49cd09439a1542b67d151c2
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/RadarSeries.java
@@ -0,0 +1,160 @@
+package org.knowm.xchart;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import org.knowm.xchart.internal.chartpart.RenderableSeries.LegendRenderType;
+import org.knowm.xchart.internal.series.MarkerSeries;
+import org.knowm.xchart.style.markers.Marker;
+
+/** A Series containing Radar data to be plotted on a Chart */
+public class RadarSeries extends MarkerSeries {
+
+  /** Line Style */
+  private BasicStroke stroke;
+
+  /** Line Color */
+  private Color lineColor;
+
+  /** Line Width */
+  private float lineWidth;
+
+  /** Marker */
+  private Marker marker;
+
+  /** Marker Color */
+  private Color markerColor;
+
+  private double[] values;
+  private String[] tooltipOverrides;
+
+  // TODO refactor tooltips override
+  /**
+   * @param tooltipOverrides Adds custom tooltipOverrides for series. If tooltipOverrides is null,
+   *     they are automatically generated.
+   */
+  public RadarSeries(String name, double[] values, String[] tooltipOverrides) {
+
+    super(name, DataType.Number);
+    this.values = values;
+    this.tooltipOverrides = tooltipOverrides;
+  }
+
+  public double[] getValues() {
+
+    return values;
+  }
+
+  public void setValues(double[] values) {
+
+    this.values = values;
+  }
+
+  public String[] getTooltipOverrides() {
+
+    return tooltipOverrides;
+  }
+
+  @Override
+  protected void calculateMinMax() {}
+
+  public BasicStroke getLineStyle() {
+
+    return stroke;
+  }
+
+  /**
+   * Set the line style of the series
+   *
+   * @param basicStroke
+   */
+  public RadarSeries setLineStyle(BasicStroke basicStroke) {
+
+    stroke = basicStroke;
+    if (this.lineWidth > 0.0f) {
+      stroke =
+          new BasicStroke(
+              lineWidth,
+              this.stroke.getEndCap(),
+              this.stroke.getLineJoin(),
+              this.stroke.getMiterLimit(),
+              this.stroke.getDashArray(),
+              this.stroke.getDashPhase());
+    }
+    return this;
+  }
+
+  public Color getLineColor() {
+
+    return lineColor;
+  }
+
+  /**
+   * Set the line color of the series
+   *
+   * @param color
+   */
+  public RadarSeries setLineColor(java.awt.Color color) {
+
+    this.lineColor = color;
+    return this;
+  }
+
+  public float getLineWidth() {
+
+    return lineWidth;
+  }
+
+  /**
+   * Set the line width of the series
+   *
+   * @param lineWidth
+   */
+  public RadarSeries setLineWidth(float lineWidth) {
+
+    this.lineWidth = lineWidth;
+    return this;
+  }
+
+  public Marker getMarker() {
+
+    return marker;
+  }
+
+  /**
+   * Sets the marker for the series
+   *
+   * @param marker
+   */
+  public RadarSeries setMarker(Marker marker) {
+
+    this.marker = marker;
+    return this;
+  }
+
+  public Color getMarkerColor() {
+
+    return markerColor;
+  }
+
+  /**
+   * Sets the marker color for the series
+   *
+   * @param color
+   */
+  public RadarSeries setMarkerColor(java.awt.Color color) {
+
+    this.markerColor = color;
+    return this;
+  }
+
+  @Override
+  public LegendRenderType getLegendRenderType() {
+
+    return LegendRenderType.Line;
+  }
+
+  public void setTooltipOverrides(String[] tooltipOverrides) {
+
+    this.tooltipOverrides = tooltipOverrides;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/SwingWrapper.java b/XChart/xchart/src/main/java/org/knowm/xchart/SwingWrapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..c3e80cc1e9399cfb090aa4ddb4a517400c2c8dea
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/SwingWrapper.java
@@ -0,0 +1,199 @@
+package org.knowm.xchart;
+
+import java.awt.GridLayout;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.List;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.WindowConstants;
+import org.knowm.xchart.internal.chartpart.Chart;
+
+/** A convenience class used to display a Chart in a barebones Swing application */
+public class SwingWrapper<T extends Chart<?, ?>> {
+
+  private final List<XChartPanel<T>> chartPanels = new ArrayList<XChartPanel<T>>();
+  private String windowTitle = "XChart";
+  private boolean isCentered = true;
+  private List<T> charts = new ArrayList<T>();
+  private int numRows;
+  private int numColumns;
+
+  /**
+   * Constructor
+   *
+   * @param chart
+   */
+  public SwingWrapper(T chart) {
+
+    this.charts.add(chart);
+  }
+
+  /**
+   * Constructor - The number of rows and columns will be calculated automatically Constructor
+   *
+   * @param charts
+   */
+  public SwingWrapper(List<T> charts) {
+
+    this.charts = charts;
+
+    this.numRows = (int) (Math.sqrt(charts.size()) + .5);
+    this.numColumns = (int) ((double) charts.size() / this.numRows + 1);
+  }
+
+  /**
+   * Constructor
+   *
+   * @param charts
+   * @param numRows - the number of rows
+   * @param numColumns - the number of columns
+   */
+  public SwingWrapper(List<T> charts, int numRows, int numColumns) {
+
+    this.charts = charts;
+    this.numRows = numRows;
+    this.numColumns = numColumns;
+  }
+
+  /** Display the chart in a Swing JFrame */
+  public JFrame displayChart() {
+
+    // Create and set up the window.
+    final JFrame frame = new JFrame(windowTitle);
+
+    // Schedule a job for the event-dispatching thread:
+    // creating and showing this application's GUI.
+    try {
+      javax.swing.SwingUtilities.invokeAndWait(
+          new Runnable() {
+
+            @Override
+            public void run() {
+
+              frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
+              XChartPanel<T> chartPanel = new XChartPanel<T>(charts.get(0));
+              chartPanels.add(chartPanel);
+              frame.add(chartPanel);
+
+              // Display the window.
+              frame.pack();
+              if (isCentered) {
+                frame.setLocationRelativeTo(null);
+              }
+              frame.setVisible(true);
+            }
+          });
+    } catch (InterruptedException e) {
+      e.printStackTrace();
+    } catch (InvocationTargetException e) {
+      e.printStackTrace();
+    }
+
+    return frame;
+  }
+
+  /** Display the chart in a Swing JFrame */
+  public JFrame displayChartMatrix() {
+
+    // Create and set up the window.
+    final JFrame frame = new JFrame(windowTitle);
+
+    // Schedule a job for the event-dispatching thread:
+    // creating and showing this application's GUI.
+    javax.swing.SwingUtilities.invokeLater(
+        new Runnable() {
+
+          @Override
+          public void run() {
+
+            frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
+            frame.getContentPane().setLayout(new GridLayout(numRows, numColumns));
+
+            for (T chart : charts) {
+              if (chart != null) {
+                XChartPanel<T> chartPanel = new XChartPanel<T>(chart);
+                chartPanels.add(chartPanel);
+                frame.add(chartPanel);
+              } else {
+                JPanel chartPanel = new JPanel();
+                frame.getContentPane().add(chartPanel);
+              }
+            }
+
+            // Display the window.
+            frame.pack();
+            if (isCentered) {
+              frame.setLocationRelativeTo(null);
+            }
+            frame.setVisible(true);
+          }
+        });
+
+    return frame;
+  }
+
+  /**
+   * Get the default XChartPanel. This is the only one for single panel chart displays and the first
+   * panel in matrix chart displays
+   *
+   * @return the XChartPanel
+   */
+  public XChartPanel<T> getXChartPanel() {
+
+    return getXChartPanel(0);
+  }
+
+  /**
+   * Repaint the default XChartPanel. This is the only one for single panel chart displays and the
+   * first panel in matrix chart displays
+   */
+  public void repaintChart() {
+
+    repaintChart(0);
+  }
+
+  /**
+   * Get the XChartPanel given the provided index.
+   *
+   * @param index
+   * @return the XChartPanel
+   */
+  public XChartPanel<T> getXChartPanel(int index) {
+
+    return chartPanels.get(index);
+  }
+
+  /**
+   * Repaint the XChartPanel given the provided index.
+   *
+   * @param index
+   */
+  public void repaintChart(int index) {
+
+    chartPanels.get(index).revalidate();
+    chartPanels.get(index).repaint();
+  }
+
+  /**
+   * Set the Window in the center of screen
+   *
+   * @param isCentered
+   * @return
+   */
+  public SwingWrapper isCentered(boolean isCentered) {
+    this.isCentered = isCentered;
+    return this;
+  }
+
+  /**
+   * Set the Window Title
+   *
+   * @param windowTitle
+   * @return
+   */
+  public SwingWrapper setTitle(String windowTitle) {
+    this.windowTitle = windowTitle;
+    return this;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/VectorGraphicsEncoder.java b/XChart/xchart/src/main/java/org/knowm/xchart/VectorGraphicsEncoder.java
new file mode 100644
index 0000000000000000000000000000000000000000..f937d8dd4a189690f6e1f80b45ec9e20986d5a83
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/VectorGraphicsEncoder.java
@@ -0,0 +1,110 @@
+package org.knowm.xchart;
+
+import de.erichseifert.vectorgraphics2d.Document;
+import de.erichseifert.vectorgraphics2d.Processor;
+import de.erichseifert.vectorgraphics2d.VectorGraphics2D;
+import de.erichseifert.vectorgraphics2d.eps.EPSProcessor;
+import de.erichseifert.vectorgraphics2d.intermediate.CommandSequence;
+import de.erichseifert.vectorgraphics2d.svg.SVGProcessor;
+import de.erichseifert.vectorgraphics2d.util.PageSize;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import org.knowm.xchart.internal.chartpart.Chart;
+
+/** A helper class with static methods for saving Charts as vectors */
+public final class VectorGraphicsEncoder {
+
+  /** Constructor - Private constructor to prevent instantiation */
+  private VectorGraphicsEncoder() {}
+
+  /** Write a chart to a file. */
+  public static void saveVectorGraphic(
+      Chart chart, String fileName, VectorGraphicsFormat vectorGraphicsFormat) throws IOException {
+    FileOutputStream file = new FileOutputStream(addFileExtension(fileName, vectorGraphicsFormat));
+
+    try {
+      saveVectorGraphic(chart, file, vectorGraphicsFormat);
+    } finally {
+      file.close();
+    }
+  }
+
+  /** Write a chart to an OutputStream. */
+  public static void saveVectorGraphic(
+      Chart chart, OutputStream os, VectorGraphicsFormat vectorGraphicsFormat) throws IOException {
+    final Processor p;
+
+    switch (vectorGraphicsFormat) {
+      case EPS:
+        p = new EPSProcessor();
+        break;
+      case PDF:
+        p = new PDFBoxProcessor();
+        break;
+      case SVG:
+        p = new SVGProcessor();
+        break;
+
+      default:
+        throw new UnsupportedOperationException(
+            "Unsupported vector graphics format: " + vectorGraphicsFormat);
+    }
+
+    if (VectorGraphicsFormat.PDF != vectorGraphicsFormat) {
+      VectorGraphics2D vg2d = new VectorGraphics2D();
+      //    vg2d.draw(new Rectangle2D.Double(0.0, 0.0, chart.getWidth(), chart.getHeight()));
+      CommandSequence commands = vg2d.getCommands();
+
+      chart.paint(vg2d, chart.getWidth(), chart.getHeight());
+
+      PageSize pageSize = new PageSize(0.0, 0.0, chart.getWidth(), chart.getHeight());
+      Document doc = p.getDocument(commands, pageSize);
+      doc.writeTo(os);
+    } else {
+      ((PDFBoxProcessor) p).savePdf(chart, os);
+    }
+  }
+
+  /**
+   * Only adds the extension of the VectorGraphicsFormat to the filename if the filename doesn't
+   * already have it.
+   *
+   * @param fileName
+   * @param vectorGraphicsFormat
+   * @return filename (if extension already exists), otherwise;: filename + "." + extension
+   */
+  public static String addFileExtension(
+      String fileName, VectorGraphicsFormat vectorGraphicsFormat) {
+
+    String fileNameWithFileExtension = fileName;
+    final String newFileExtension = "." + vectorGraphicsFormat.toString().toLowerCase();
+    if (fileName.length() <= newFileExtension.length()
+        || !fileName
+            .substring(fileName.length() - newFileExtension.length(), fileName.length())
+            .equalsIgnoreCase(newFileExtension)) {
+      fileNameWithFileExtension = fileName + newFileExtension;
+    }
+    return fileNameWithFileExtension;
+  }
+
+  public enum VectorGraphicsFormat {
+    EPS,
+    PDF,
+    SVG
+  }
+
+  private static class PDFBoxProcessor implements Processor {
+
+    @Override
+    public Document getDocument(CommandSequence arg0, PageSize arg1) {
+
+      return null;
+    }
+
+    public void savePdf(Chart chart, OutputStream os) throws IOException {
+
+      PdfboxGraphicsEncoder.savePdfboxGraphics(chart, os);
+    }
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/XChartPanel.java b/XChart/xchart/src/main/java/org/knowm/xchart/XChartPanel.java
new file mode 100644
index 0000000000000000000000000000000000000000..c84939b541bd730019f6abaf69932ad6560674a0
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/XChartPanel.java
@@ -0,0 +1,562 @@
+package org.knowm.xchart;
+
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Toolkit;
+import java.awt.event.ActionEvent;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.print.PageFormat;
+import java.awt.print.Paper;
+import java.awt.print.Printable;
+import java.awt.print.PrinterException;
+import java.awt.print.PrinterJob;
+import java.io.File;
+import java.io.IOException;
+import javax.swing.AbstractAction;
+import javax.swing.JFileChooser;
+import javax.swing.JLabel;
+import javax.swing.JMenuItem;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JTextField;
+import javax.swing.KeyStroke;
+import javax.swing.UIManager;
+import javax.swing.filechooser.FileFilter;
+import org.knowm.xchart.BitmapEncoder.BitmapFormat;
+import org.knowm.xchart.VectorGraphicsEncoder.VectorGraphicsFormat;
+import org.knowm.xchart.internal.chartpart.Chart;
+import org.knowm.xchart.internal.chartpart.ChartZoom;
+import org.knowm.xchart.internal.chartpart.Cursor;
+import org.knowm.xchart.internal.chartpart.ToolTips;
+import org.knowm.xchart.style.XYStyler;
+
+/**
+ * A Swing JPanel that contains a Chart
+ *
+ * <p>Right-click + Save As... or ctrl+S pops up a Save As dialog box for saving the chart as PNG,
+ * JPEG, etc. file.
+ */
+public class XChartPanel<T extends Chart<?, ?>> extends JPanel {
+
+  private final T chart;
+  private final Dimension preferredSize;
+  private String saveAsString = "Save As...";
+  private String exportAsString = "Export To...";
+  private String printString = "Print...";
+  private String resetString = "Reset Zoom";
+  private ToolTips toolTips = null;
+
+  /**
+   * Constructor
+   *
+   * @param chart
+   */
+  public XChartPanel(final T chart) {
+
+    this.chart = chart;
+    preferredSize = new Dimension(chart.getWidth(), chart.getHeight());
+
+    // Right-click listener for saving chart
+    this.addMouseListener(new PopUpMenuClickListener());
+
+    // Control+S key listener for saving chart
+    KeyStroke ctrlS =
+        KeyStroke.getKeyStroke(KeyEvent.VK_S, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask());
+    this.getInputMap(WHEN_IN_FOCUSED_WINDOW).put(ctrlS, "save");
+    this.getActionMap().put("save", new SaveAction());
+
+    // Control+E key listener for saving chart
+    KeyStroke ctrlE =
+        KeyStroke.getKeyStroke(KeyEvent.VK_E, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask());
+    this.getInputMap(WHEN_IN_FOCUSED_WINDOW).put(ctrlE, "export");
+    this.getActionMap().put("export", new ExportAction());
+
+    // Control+P key listener for printing chart
+    KeyStroke ctrlP =
+        KeyStroke.getKeyStroke(KeyEvent.VK_P, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask());
+    this.getInputMap(WHEN_IN_FOCUSED_WINDOW).put(ctrlP, "print");
+    this.getActionMap().put("print", new PrintAction());
+
+    // Mouse Listener for Zoom. Only available for XYCharts
+    if (chart instanceof XYChart && ((XYStyler) chart.getStyler()).isZoomEnabled()) {
+      ChartZoom chartZoom =
+          new ChartZoom((XYChart) chart, (XChartPanel<XYChart>) this, resetString);
+      this.addMouseListener(chartZoom); // for clicking
+      this.addMouseMotionListener(chartZoom); // for moving
+    }
+
+    // Mouse motion listener for Cursor
+    if (chart instanceof XYChart && ((XYStyler) chart.getStyler()).isCursorEnabled()) {
+      this.addMouseMotionListener(new Cursor(chart));
+    }
+
+    // Mouse motion listener for Tooltips
+    if (chart.getStyler().isToolTipsEnabled()) {
+      toolTips = new ToolTips(chart);
+      this.addMouseMotionListener(toolTips); // for moving
+    }
+
+    // Recalculate Tooltips at component resize
+    this.addComponentListener(
+        new ComponentAdapter() {
+          public void componentResized(ComponentEvent ev) {
+            if (chart.getStyler().isToolTipsEnabled()) {
+              XChartPanel.this.removeMouseListener(toolTips);
+              toolTips = new ToolTips(chart);
+              XChartPanel.this.addMouseMotionListener(toolTips);
+            }
+          }
+        });
+  }
+
+  /**
+   * Set the "Save As..." String if you want to localize it.
+   *
+   * @param saveAsString
+   */
+  public void setSaveAsString(String saveAsString) {
+
+    this.saveAsString = saveAsString;
+  }
+
+  /**
+   * Set the "Export As..." String if you want to localize it.
+   *
+   * @param exportAsString
+   */
+  public void setExportAsString(String exportAsString) {
+
+    this.exportAsString = exportAsString;
+  }
+
+  /**
+   * Set the "Print..." String if you want to localize it.
+   *
+   * @param printString
+   */
+  public void setPrintString(String printString) {
+
+    this.printString = printString;
+  }
+
+  /**
+   * Set the "Reset" String if you want to localize it. This is on the button which resets the zoom
+   * feature.
+   *
+   * @param resetString
+   */
+  public void setResetString(String resetString) {
+
+    this.resetString = resetString;
+  }
+
+  @Override
+  protected void paintComponent(Graphics g) {
+
+    super.paintComponent(g);
+
+    Graphics2D g2d = (Graphics2D) g.create();
+    chart.paint(g2d, getWidth(), getHeight());
+    g2d.dispose();
+  }
+
+  public T getChart() {
+
+    return this.chart;
+  }
+
+  @Override
+  public Dimension getPreferredSize() {
+
+    return preferredSize;
+  }
+
+  private void showPrintDialog() {
+
+    PrinterJob printJob = PrinterJob.getPrinterJob();
+    if (printJob.printDialog()) {
+      try {
+        // Page format
+        PageFormat pageFormat = printJob.defaultPage();
+        Paper paper = pageFormat.getPaper();
+        if (this.getWidth() > this.getHeight()) {
+          pageFormat.setOrientation(PageFormat.LANDSCAPE);
+          paper.setImageableArea(0, 0, pageFormat.getHeight(), pageFormat.getWidth());
+        } else {
+          paper.setImageableArea(0, 0, pageFormat.getWidth(), pageFormat.getHeight());
+        }
+        pageFormat.setPaper(paper);
+        pageFormat = printJob.validatePage(pageFormat);
+
+        String jobName = "XChart " + chart.getTitle().trim();
+        printJob.setJobName(jobName);
+
+        printJob.setPrintable(new Printer(this), pageFormat);
+        printJob.print();
+      } catch (PrinterException e) {
+        e.printStackTrace();
+      }
+    }
+  }
+
+  private void showSaveAsDialog() {
+
+    UIManager.put("FileChooser.saveButtonText", "Save");
+    UIManager.put("FileChooser.fileNameLabelText", "File Name:");
+    JFileChooser fileChooser = new JFileChooser();
+    FileFilter pngFileFilter = new SuffixSaveFilter("png"); // default
+    fileChooser.addChoosableFileFilter(pngFileFilter);
+    fileChooser.addChoosableFileFilter(new SuffixSaveFilter("jpg"));
+    fileChooser.addChoosableFileFilter(new SuffixSaveFilter("bmp"));
+    fileChooser.addChoosableFileFilter(new SuffixSaveFilter("gif"));
+
+    // VectorGraphics2D is optional, so if it's on the classpath, allow saving charts as vector
+    // graphic
+    try {
+      Class.forName("de.erichseifert.vectorgraphics2d.VectorGraphics2D");
+      // it exists on the classpath
+      fileChooser.addChoosableFileFilter(new SuffixSaveFilter("svg"));
+      fileChooser.addChoosableFileFilter(new SuffixSaveFilter("eps"));
+    } catch (ClassNotFoundException e) {
+      // it does not exist on the classpath
+    }
+    try {
+      Class.forName("de.rototor.pdfbox.graphics2d.PdfBoxGraphics2D");
+      // it exists on the classpath
+      fileChooser.addChoosableFileFilter(new SuffixSaveFilter("pdf"));
+    } catch (ClassNotFoundException e) {
+      // it does not exist on the classpath
+    }
+
+    fileChooser.setAcceptAllFileFilterUsed(false);
+
+    fileChooser.setFileFilter(pngFileFilter);
+
+    if (fileChooser.showSaveDialog(this) == JFileChooser.APPROVE_OPTION) {
+
+      if (fileChooser.getSelectedFile() != null) {
+        File theFileToSave = fileChooser.getSelectedFile();
+        try {
+          if (fileChooser.getFileFilter() == null) {
+            BitmapEncoder.saveBitmap(chart, theFileToSave.getCanonicalPath(), BitmapFormat.PNG);
+          } else if (fileChooser.getFileFilter().getDescription().equals("*.jpg,*.JPG")) {
+            BitmapEncoder.saveJPGWithQuality(
+                chart,
+                BitmapEncoder.addFileExtension(theFileToSave.getCanonicalPath(), BitmapFormat.JPG),
+                1.0f);
+          } else if (fileChooser.getFileFilter().getDescription().equals("*.png,*.PNG")) {
+            BitmapEncoder.saveBitmap(chart, theFileToSave.getCanonicalPath(), BitmapFormat.PNG);
+          } else if (fileChooser.getFileFilter().getDescription().equals("*.bmp,*.BMP")) {
+            BitmapEncoder.saveBitmap(chart, theFileToSave.getCanonicalPath(), BitmapFormat.BMP);
+          } else if (fileChooser.getFileFilter().getDescription().equals("*.gif,*.GIF")) {
+            BitmapEncoder.saveBitmap(chart, theFileToSave.getCanonicalPath(), BitmapFormat.GIF);
+          } else if (fileChooser.getFileFilter().getDescription().equals("*.svg,*.SVG")) {
+            VectorGraphicsEncoder.saveVectorGraphic(
+                chart, theFileToSave.getCanonicalPath(), VectorGraphicsFormat.SVG);
+          } else if (fileChooser.getFileFilter().getDescription().equals("*.eps,*.EPS")) {
+            VectorGraphicsEncoder.saveVectorGraphic(
+                chart, theFileToSave.getCanonicalPath(), VectorGraphicsFormat.EPS);
+          } else if (fileChooser.getFileFilter().getDescription().equals("*.pdf,*.PDF")) {
+            VectorGraphicsEncoder.saveVectorGraphic(
+                chart, theFileToSave.getCanonicalPath(), VectorGraphicsFormat.PDF);
+          }
+        } catch (IOException e) {
+          e.printStackTrace();
+        }
+      }
+    }
+  }
+
+  private void showExportAsDialog() {
+
+    UIManager.put("FileChooser.saveButtonText", "Export");
+    UIManager.put("FileChooser.fileNameLabelText", "Export To:");
+    UIManager.put("FileChooser.fileNameLabelMnemonic", "Export To:");
+    JFileChooser fileChooser = new JFileChooser();
+    fileChooser.setCurrentDirectory(new File(System.getProperty("user.home")));
+    disableLabel(fileChooser.getComponents());
+    disableTextField(fileChooser.getComponents());
+    fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
+    fileChooser.setFileFilter(
+        new FileFilter() {
+
+          @Override
+          public boolean accept(File f) {
+
+            return f.isDirectory();
+          }
+
+          @Override
+          public String getDescription() {
+
+            return "Any Directory";
+          }
+        });
+    fileChooser.setAcceptAllFileFilterUsed(false);
+    fileChooser.setDialogTitle("Export");
+
+    if (fileChooser.showSaveDialog(this) == JFileChooser.APPROVE_OPTION) {
+
+      File theFileToSave = null;
+      if (fileChooser.getSelectedFile() != null) {
+        if (fileChooser.getSelectedFile().exists()) {
+          theFileToSave = fileChooser.getSelectedFile();
+        } else {
+          theFileToSave = new File(fileChooser.getSelectedFile().getParent());
+        }
+      }
+
+      try {
+        CSVExporter.writeCSVColumns(
+            (XYChart) chart, theFileToSave.getCanonicalPath() + File.separatorChar);
+      } catch (IOException e) {
+        e.printStackTrace();
+      }
+    }
+  }
+
+  private void disableTextField(Component[] comp) {
+    for (Component component : comp) {
+      //            System.out.println(component.toString());
+      if (component instanceof JPanel) {
+        disableTextField(((JPanel) component).getComponents());
+      } else if (component instanceof JTextField) {
+        component.setVisible(false);
+        return;
+      }
+    }
+  }
+
+  private void disableLabel(Component[] comp) {
+    for (Component component : comp) {
+      //      System.out.println(comp[x].toString());
+      if (component instanceof JPanel) {
+        disableLabel(((JPanel) component).getComponents());
+      } else if (component instanceof JLabel) {
+        //        System.out.println(comp[x].toString());
+        component.setVisible(false);
+        return;
+      }
+    }
+  }
+
+  private class SaveAction extends AbstractAction {
+
+    public SaveAction() {
+
+      super("save");
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+
+      showSaveAsDialog();
+    }
+  }
+
+  private class ExportAction extends AbstractAction {
+
+    public ExportAction() {
+
+      super("export");
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+
+      showExportAsDialog();
+    }
+  }
+
+  private class PrintAction extends AbstractAction {
+
+    public PrintAction() {
+
+      super("print");
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+
+      showPrintDialog();
+    }
+  }
+
+  /**
+   * File filter based on the suffix of a file. This file filter accepts all files that end with
+   * .suffix or the capitalized suffix.
+   */
+  private static class SuffixSaveFilter extends FileFilter {
+
+    private final String suffix;
+
+    /**
+     * @param suffix This file filter accepts all files that end with .suffix or the capitalized
+     *     suffix.
+     */
+    public SuffixSaveFilter(String suffix) {
+
+      this.suffix = suffix;
+    }
+
+    @Override
+    public boolean accept(File f) {
+
+      if (f.isDirectory()) {
+        return true;
+      }
+
+      String s = f.getName();
+
+      return s.endsWith("." + suffix) || s.endsWith("." + suffix.toUpperCase());
+    }
+
+    @Override
+    public String getDescription() {
+
+      return "*." + suffix + ",*." + suffix.toUpperCase();
+    }
+  }
+
+  private class PopUpMenuClickListener extends MouseAdapter {
+
+    @Override
+    public void mousePressed(MouseEvent e) {
+
+      if (e.isPopupTrigger()) {
+        doPop(e);
+      }
+    }
+
+    @Override
+    public void mouseReleased(MouseEvent e) {
+
+      if (e.isPopupTrigger()) {
+        doPop(e);
+      }
+    }
+
+    private void doPop(MouseEvent e) {
+
+      XChartPanelPopupMenu menu = new XChartPanelPopupMenu();
+      menu.show(e.getComponent(), e.getX(), e.getY());
+      menu.getGraphics().dispose();
+    }
+  }
+
+  private class XChartPanelPopupMenu extends JPopupMenu {
+
+    final JMenuItem saveAsMenuItem;
+    final JMenuItem printMenuItem;
+    JMenuItem exportAsMenuItem;
+
+    public XChartPanelPopupMenu() {
+
+      saveAsMenuItem = new JMenuItem(saveAsString);
+      saveAsMenuItem.addMouseListener(
+          new MouseListener() {
+
+            @Override
+            public void mouseReleased(MouseEvent e) {
+
+              showSaveAsDialog();
+            }
+
+            @Override
+            public void mousePressed(MouseEvent e) {}
+
+            @Override
+            public void mouseExited(MouseEvent e) {}
+
+            @Override
+            public void mouseEntered(MouseEvent e) {}
+
+            @Override
+            public void mouseClicked(MouseEvent e) {}
+          });
+      add(saveAsMenuItem);
+
+      printMenuItem = new JMenuItem(printString);
+      printMenuItem.addMouseListener(
+          new MouseListener() {
+
+            @Override
+            public void mouseReleased(MouseEvent e) {
+
+              showPrintDialog();
+            }
+
+            @Override
+            public void mousePressed(MouseEvent e) {}
+
+            @Override
+            public void mouseExited(MouseEvent e) {}
+
+            @Override
+            public void mouseEntered(MouseEvent e) {}
+
+            @Override
+            public void mouseClicked(MouseEvent e) {}
+          });
+      add(printMenuItem);
+
+      if (chart instanceof XYChart) {
+        exportAsMenuItem = new JMenuItem(exportAsString);
+        exportAsMenuItem.addMouseListener(
+            new MouseListener() {
+
+              @Override
+              public void mouseReleased(MouseEvent e) {
+
+                showExportAsDialog();
+              }
+
+              @Override
+              public void mousePressed(MouseEvent e) {}
+
+              @Override
+              public void mouseExited(MouseEvent e) {}
+
+              @Override
+              public void mouseEntered(MouseEvent e) {}
+
+              @Override
+              public void mouseClicked(MouseEvent e) {}
+            });
+        add(exportAsMenuItem);
+      }
+    }
+  }
+
+  public static class Printer implements Printable {
+    private final Component component;
+
+    Printer(Component c) {
+      component = c;
+    }
+
+    @Override
+    public int print(Graphics graphics, PageFormat pageFormat, int pageIndex) {
+      if (pageIndex > 0) {
+        return NO_SUCH_PAGE;
+      }
+
+      Graphics2D g2 = (Graphics2D) graphics;
+      g2.translate(pageFormat.getImageableX(), pageFormat.getImageableY());
+      double sx = pageFormat.getImageableWidth() / component.getWidth();
+      double sy = pageFormat.getImageableHeight() / component.getHeight();
+      g2.scale(sx, sy);
+
+      component.printAll(g2);
+
+      return PAGE_EXISTS;
+    }
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/XYChart.java b/XChart/xchart/src/main/java/org/knowm/xchart/XYChart.java
new file mode 100644
index 0000000000000000000000000000000000000000..38dcf72aecff50d7a353d9582272175e9fc67bd1
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/XYChart.java
@@ -0,0 +1,453 @@
+package org.knowm.xchart;
+
+import java.awt.Graphics2D;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import org.knowm.xchart.internal.Utils;
+import org.knowm.xchart.internal.chartpart.AxisPair;
+import org.knowm.xchart.internal.chartpart.Chart;
+import org.knowm.xchart.internal.chartpart.Legend_Marker;
+import org.knowm.xchart.internal.chartpart.Plot_XY;
+import org.knowm.xchart.internal.series.Series.DataType;
+import org.knowm.xchart.internal.style.SeriesColorMarkerLineStyle;
+import org.knowm.xchart.internal.style.SeriesColorMarkerLineStyleCycler;
+import org.knowm.xchart.style.Styler.ChartTheme;
+import org.knowm.xchart.style.XYStyler;
+import org.knowm.xchart.style.theme.Theme;
+
+public class XYChart extends Chart<XYStyler, XYSeries> {
+
+  /**
+   * Constructor - the default Chart Theme will be used (XChartTheme)
+   *
+   * @param width
+   * @param height
+   */
+  public XYChart(int width, int height) {
+
+    super(width, height, new XYStyler());
+
+    axisPair = new AxisPair<XYStyler, XYSeries>(this);
+    plot = new Plot_XY<XYStyler, XYSeries>(this);
+    legend = new Legend_Marker<XYStyler, XYSeries>(this);
+  }
+
+  /**
+   * Constructor
+   *
+   * @param width
+   * @param height
+   * @param theme - pass in an instance of Theme class, probably a custom Theme.
+   */
+  public XYChart(int width, int height, Theme theme) {
+
+    this(width, height);
+    styler.setTheme(theme);
+  }
+
+  /**
+   * Constructor
+   *
+   * @param width
+   * @param height
+   * @param chartTheme - pass in the desired ChartTheme enum
+   */
+  public XYChart(int width, int height, ChartTheme chartTheme) {
+
+    this(width, height, chartTheme.newInstance(chartTheme));
+  }
+
+  /**
+   * Constructor
+   *
+   * @param chartBuilder
+   */
+  public XYChart(XYChartBuilder chartBuilder) {
+
+    this(chartBuilder.width, chartBuilder.height, chartBuilder.chartTheme);
+    setTitle(chartBuilder.title);
+    setXAxisTitle(chartBuilder.xAxisTitle);
+    setYAxisTitle(chartBuilder.yAxisTitle);
+  }
+
+  /**
+   * Add a series for a X-Y type chart using using double arrays
+   *
+   * @param seriesName
+   * @param yData the Y-Axis data
+   * @return A Series object that you can set properties on
+   */
+  public XYSeries addSeries(String seriesName, double[] yData) {
+
+    return addSeries(seriesName, null, yData);
+  }
+
+  /**
+   * Add a series for a X-Y type chart using using double arrays
+   *
+   * @param seriesName
+   * @param xData the X-Axis data
+   * @param yData the Y-Axis data
+   * @return A Series object that you can set properties on
+   */
+  public XYSeries addSeries(String seriesName, double[] xData, double[] yData) {
+
+    return addSeries(seriesName, xData, yData, null, DataType.Number);
+  }
+
+  /**
+   * Add a series for a X-Y type chart using using float arrays
+   *
+   * @param seriesName
+   * @param yData the Y-Axis data
+   * @return A Series object that you can set properties on
+   */
+  public XYSeries addSeries(String seriesName, float[] yData) {
+
+    return addSeries(seriesName, null, yData, null);
+  }
+
+  /**
+   * Add a series for a X-Y type chart using using float arrays
+   *
+   * @param seriesName
+   * @param xData the X-Axis data
+   * @param yData the Y-Axis data
+   * @return A Series object that you can set properties on
+   */
+  public XYSeries addSeries(String seriesName, float[] xData, float[] yData) {
+
+    return addSeries(seriesName, xData, yData, null);
+  }
+
+  /**
+   * Add a series for a X-Y type chart using using float arrays with error bars
+   *
+   * @param seriesName
+   * @param xData the X-Axis data
+   * @param yData the Y-Axis data
+   * @param errorBars the error bar data
+   * @return A Series object that you can set properties on
+   */
+  public XYSeries addSeries(String seriesName, float[] xData, float[] yData, float[] errorBars) {
+
+    return addSeries(
+        seriesName,
+        Utils.getDoubleArrayFromFloatArray(xData),
+        Utils.getDoubleArrayFromFloatArray(yData),
+        Utils.getDoubleArrayFromFloatArray(errorBars),
+        DataType.Number);
+  }
+
+  /**
+   * Add a series for a X-Y type chart using using int arrays
+   *
+   * @param seriesName
+   * @param yData the Y-Axis data
+   * @return A Series object that you can set properties on
+   */
+  public XYSeries addSeries(String seriesName, int[] yData) {
+
+    return addSeries(seriesName, null, yData, null);
+  }
+
+  /**
+   * Add a series for a X-Y type chart using using int arrays
+   *
+   * @param seriesName
+   * @param xData the X-Axis data
+   * @param yData the Y-Axis data
+   * @return A Series object that you can set properties on
+   */
+  public XYSeries addSeries(String seriesName, int[] xData, int[] yData) {
+
+    return addSeries(seriesName, xData, yData, null);
+  }
+
+  /**
+   * Add a series for a X-Y type chart using using int arrays with error bars
+   *
+   * @param seriesName
+   * @param xData the X-Axis data
+   * @param yData the Y-Axis data
+   * @param errorBars the error bar data
+   * @return A Series object that you can set properties on
+   */
+  public XYSeries addSeries(String seriesName, int[] xData, int[] yData, int[] errorBars) {
+
+    return addSeries(
+        seriesName,
+        Utils.getDoubleArrayFromIntArray(xData),
+        Utils.getDoubleArrayFromIntArray(yData),
+        Utils.getDoubleArrayFromIntArray(errorBars),
+        DataType.Number);
+  }
+
+  /**
+   * Add a series for a X-Y type chart using Lists
+   *
+   * @param seriesName
+   * @param xData the X-Axis data
+   * @param yData the Y-Axis data
+   * @return A Series object that you can set properties on
+   */
+  public XYSeries addSeries(String seriesName, List<?> xData, List<? extends Number> yData) {
+
+    return addSeries(seriesName, xData, yData, null);
+  }
+
+  /**
+   * Add a series for a X-Y type chart using Lists
+   *
+   * @param seriesName
+   * @param yData the Y-Axis data
+   * @return A Series object that you can set properties on
+   */
+  public XYSeries addSeries(String seriesName, List<? extends Number> yData) {
+
+    return addSeries(seriesName, null, yData, null);
+  }
+
+  /**
+   * Add a series for a X-Y type chart using Lists
+   *
+   * @param seriesName
+   * @param xData the X-Axis data
+   * @param yData the Y-Axis data
+   * @param errorBars the error bar data
+   * @return A Series object that you can set properties on
+   */
+  public XYSeries addSeries(
+      String seriesName,
+      List<?> xData,
+      List<? extends Number> yData,
+      List<? extends Number> errorBars) {
+
+    DataType dataType = getDataType(xData);
+    switch (dataType) {
+      case Date:
+        return addSeries(
+            seriesName,
+            Utils.getDoubleArrayFromDateList(xData),
+            Utils.getDoubleArrayFromNumberList(yData),
+            Utils.getDoubleArrayFromNumberList(errorBars),
+            DataType.Date);
+
+      default:
+        return addSeries(
+            seriesName,
+            Utils.getDoubleArrayFromNumberList(xData),
+            Utils.getDoubleArrayFromNumberList(yData),
+            Utils.getDoubleArrayFromNumberList(errorBars),
+            DataType.Number);
+    }
+  }
+
+  // TODO make this an interface method??
+  private DataType getDataType(List<?> data) {
+
+    if (data == null || data.isEmpty()) {
+      return DataType.Number; // It will be autogenerated
+    }
+
+    DataType axisType;
+
+    Iterator<?> itr = data.iterator();
+    Object dataPoint = itr.next();
+    if (dataPoint instanceof Number) {
+      axisType = DataType.Number;
+    } else if (dataPoint instanceof Date) {
+      axisType = DataType.Date;
+    } else {
+      throw new IllegalArgumentException("Series data must be either Number or Date type!!!");
+    }
+    return axisType;
+  }
+
+  public XYSeries addSeries(String seriesName, double[] xData, double[] yData, double[] errorBars) {
+
+    return addSeries(seriesName, xData, yData, errorBars, DataType.Number);
+  }
+
+  /**
+   * Add a series for a X-Y type chart using Lists with error bars
+   *
+   * @param seriesName
+   * @param xData the X-Axis data
+   * @param yData the Y-Axis data
+   * @param errorBars the error bar data
+   * @return A Series object that you can set properties on
+   */
+  private XYSeries addSeries(
+      String seriesName, double[] xData, double[] yData, double[] errorBars, DataType dataType) {
+
+    // Sanity checks
+    sanityCheck(seriesName, xData, yData, errorBars);
+
+    XYSeries series;
+    if (xData != null) {
+
+      // Sanity check
+      if (xData.length != yData.length) {
+        throw new IllegalArgumentException("X and Y-Axis sizes are not the same!!!");
+      }
+
+      series = new XYSeries(seriesName, xData, yData, errorBars, dataType);
+    } else { // generate xData
+      series =
+          new XYSeries(
+              seriesName, Utils.getGeneratedDataAsArray(yData.length), yData, errorBars, dataType);
+    }
+
+    seriesMap.put(seriesName, series);
+
+    return series;
+  }
+
+  /**
+   * Update a series by updating the X-Axis, Y-Axis and error bar data
+   *
+   * @param seriesName
+   * @param newXData - set null to be automatically generated as a list of increasing Integers
+   *     starting from 1 and ending at the size of the new Y-Axis data list.
+   * @param newYData
+   * @param newErrorBarData - set null if there are no error bars
+   * @return
+   */
+  public XYSeries updateXYSeries(
+      String seriesName,
+      List<?> newXData,
+      List<? extends Number> newYData,
+      List<? extends Number> newErrorBarData) {
+
+    DataType dataType = getDataType(newXData);
+    switch (dataType) {
+      case Date:
+        return updateXYSeries(
+            seriesName,
+            Utils.getDoubleArrayFromDateList(newXData),
+            Utils.getDoubleArrayFromNumberList(newYData),
+            Utils.getDoubleArrayFromNumberList(newErrorBarData));
+
+      default:
+        return updateXYSeries(
+            seriesName,
+            Utils.getDoubleArrayFromNumberList(newXData),
+            Utils.getDoubleArrayFromNumberList(newYData),
+            Utils.getDoubleArrayFromNumberList(newErrorBarData));
+    }
+  }
+
+  /**
+   * Update a series by updating the X-Axis, Y-Axis and error bar data
+   *
+   * @param seriesName
+   * @param newXData - set null to be automatically generated as a list of increasing Integers
+   *     starting from 1 and ending at the size of the new Y-Axis data list.
+   * @param newYData
+   * @param newErrorBarData - set null if there are no error bars
+   * @return
+   */
+  public XYSeries updateXYSeries(
+      String seriesName, double[] newXData, double[] newYData, double[] newErrorBarData) {
+
+    Map<String, XYSeries> seriesMap = getSeriesMap();
+    XYSeries series = seriesMap.get(seriesName);
+    if (series == null) {
+      throw new IllegalArgumentException("Series name >" + seriesName + "< not found!!!");
+    }
+    if (newXData == null) {
+      double[] generatedXData = Utils.getGeneratedDataAsArray(newYData.length);
+      series.replaceData(generatedXData, newYData, newErrorBarData);
+    } else {
+      series.replaceData(newXData, newYData, newErrorBarData);
+    }
+
+    return series;
+  }
+
+  ///////////////////////////////////////////////////
+  // Internal Members and Methods ///////////////////
+  ///////////////////////////////////////////////////
+
+  private void sanityCheck(String seriesName, double[] xData, double[] yData, double[] errorBars) {
+
+    if (seriesMap.containsKey(seriesName)) {
+      throw new IllegalArgumentException(
+          "Series name >"
+              + seriesName
+              + "< has already been used. Use unique names for each series!!!");
+    }
+    if (yData == null) {
+      throw new IllegalArgumentException("Y-Axis data cannot be null!!! >" + seriesName);
+    }
+    if (yData.length == 0) {
+      throw new IllegalArgumentException("Y-Axis data cannot be empty!!! >" + seriesName);
+    }
+    if (xData != null && xData.length == 0) {
+      throw new IllegalArgumentException("X-Axis data cannot be empty!!! >" + seriesName);
+    }
+    if (errorBars != null && errorBars.length != yData.length) {
+      throw new IllegalArgumentException(
+          "Error bars and Y-Axis sizes are not the same!!! >" + seriesName);
+    }
+  }
+
+  @Override
+  public void paint(Graphics2D g, int width, int height) {
+
+    setWidth(width);
+    setHeight(height);
+
+    // set the series render styles if they are not set. Legend and Plot need it.
+    for (XYSeries xySeries : getSeriesMap().values()) {
+      XYSeries.XYSeriesRenderStyle chartXYSeriesRenderStyle =
+          xySeries.getXYSeriesRenderStyle(); // would be directly set
+      if (chartXYSeriesRenderStyle == null) { // wasn't overridden, use default from Style Manager
+        xySeries.setXYSeriesRenderStyle(getStyler().getDefaultSeriesRenderStyle());
+      }
+    }
+    setSeriesStyles();
+
+    paintBackground(g);
+
+    axisPair.paint(g);
+    plot.paint(g);
+    chartTitle.paint(g);
+    legend.paint(g);
+    annotations.forEach(x -> x.paint(g));
+  }
+
+  /** set the series color, marker and line style based on theme */
+  private void setSeriesStyles() {
+
+    SeriesColorMarkerLineStyleCycler seriesColorMarkerLineStyleCycler =
+        new SeriesColorMarkerLineStyleCycler(
+            getStyler().getSeriesColors(),
+            getStyler().getSeriesMarkers(),
+            getStyler().getSeriesLines());
+    for (XYSeries series : getSeriesMap().values()) {
+
+      SeriesColorMarkerLineStyle seriesColorMarkerLineStyle =
+          seriesColorMarkerLineStyleCycler.getNextSeriesColorMarkerLineStyle();
+
+      if (series.getLineStyle() == null) { // wasn't set manually
+        series.setLineStyle(seriesColorMarkerLineStyle.getStroke());
+      }
+      if (series.getLineColor() == null) { // wasn't set manually
+        series.setLineColor(seriesColorMarkerLineStyle.getColor());
+      }
+      if (series.getFillColor() == null) { // wasn't set manually
+        series.setFillColor(seriesColorMarkerLineStyle.getColor());
+      }
+      if (series.getMarker() == null) { // wasn't set manually
+        series.setMarker(seriesColorMarkerLineStyle.getMarker());
+      }
+      if (series.getMarkerColor() == null) { // wasn't set manually
+        series.setMarkerColor(seriesColorMarkerLineStyle.getColor());
+      }
+    }
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/XYChartBuilder.java b/XChart/xchart/src/main/java/org/knowm/xchart/XYChartBuilder.java
new file mode 100644
index 0000000000000000000000000000000000000000..c74c0e3449345bef2c68b52fd755d10df77aad1f
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/XYChartBuilder.java
@@ -0,0 +1,34 @@
+package org.knowm.xchart;
+
+import org.knowm.xchart.internal.ChartBuilder;
+
+public class XYChartBuilder extends ChartBuilder<XYChartBuilder, XYChart> {
+
+  String xAxisTitle = "";
+  String yAxisTitle = "";
+
+  public XYChartBuilder() {}
+
+  public XYChartBuilder xAxisTitle(String xAxisTitle) {
+
+    this.xAxisTitle = xAxisTitle;
+    return this;
+  }
+
+  public XYChartBuilder yAxisTitle(String yAxisTitle) {
+
+    this.yAxisTitle = yAxisTitle;
+    return this;
+  }
+
+  /**
+   * return fully built XYChart
+   *
+   * @return a XYChart
+   */
+  @Override
+  public XYChart build() {
+
+    return new XYChart(this);
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/XYSeries.java b/XChart/xchart/src/main/java/org/knowm/xchart/XYSeries.java
new file mode 100644
index 0000000000000000000000000000000000000000..6a38597d9a29fb1c0c73901a20694b2ea940ae0e
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/XYSeries.java
@@ -0,0 +1,79 @@
+package org.knowm.xchart;
+
+import org.knowm.xchart.internal.chartpart.RenderableSeries;
+import org.knowm.xchart.internal.chartpart.RenderableSeries.LegendRenderType;
+import org.knowm.xchart.internal.series.AxesChartSeriesNumericalNoErrorBars;
+
+/** A Series containing X and Y data to be plotted on a Chart */
+public class XYSeries extends AxesChartSeriesNumericalNoErrorBars {
+
+  private XYSeriesRenderStyle xySeriesRenderStyle = null;
+  // smooth curve
+  private boolean smooth;
+
+  /**
+   * Constructor
+   *
+   * @param name
+   * @param xData
+   * @param yData
+   * @param errorBars
+   */
+  public XYSeries(
+      String name, double[] xData, double[] yData, double[] errorBars, DataType axisType) {
+
+    super(name, xData, yData, errorBars, axisType);
+  }
+
+  public XYSeriesRenderStyle getXYSeriesRenderStyle() {
+
+    return xySeriesRenderStyle;
+  }
+
+  public XYSeries setXYSeriesRenderStyle(XYSeriesRenderStyle chartXYSeriesRenderStyle) {
+
+    this.xySeriesRenderStyle = chartXYSeriesRenderStyle;
+    return this;
+  }
+
+  @Override
+  public LegendRenderType getLegendRenderType() {
+
+    return xySeriesRenderStyle.getLegendRenderType();
+  }
+
+  public boolean isSmooth() {
+    return smooth;
+  }
+
+  public void setSmooth(boolean smooth) {
+    this.smooth = smooth;
+  }
+
+  public enum XYSeriesRenderStyle implements RenderableSeries {
+    Line(LegendRenderType.Line),
+
+    Area(LegendRenderType.Line),
+
+    Step(LegendRenderType.Line),
+
+    StepArea(LegendRenderType.Line),
+
+    PolygonArea(LegendRenderType.Box),
+
+    Scatter(LegendRenderType.Scatter);
+
+    private final LegendRenderType legendRenderType;
+
+    XYSeriesRenderStyle(LegendRenderType legendRenderType) {
+
+      this.legendRenderType = legendRenderType;
+    }
+
+    @Override
+    public LegendRenderType getLegendRenderType() {
+
+      return legendRenderType;
+    }
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/ChartBuilder.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/ChartBuilder.java
new file mode 100644
index 0000000000000000000000000000000000000000..19735bc46fe7940f7b750e0f295518dfa98b71fd
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/ChartBuilder.java
@@ -0,0 +1,43 @@
+package org.knowm.xchart.internal;
+
+import org.knowm.xchart.internal.chartpart.Chart;
+import org.knowm.xchart.style.Styler.ChartTheme;
+
+/** A "Builder" to make creating charts easier */
+public abstract class ChartBuilder<T extends ChartBuilder<?, ?>, C extends Chart> {
+
+  public int width = 800;
+  public int height = 600;
+  public String title = "";
+
+  public ChartTheme chartTheme = ChartTheme.XChart;
+
+  /** Constructor */
+  protected ChartBuilder() {}
+
+  public T width(int width) {
+
+    this.width = width;
+    return (T) this;
+  }
+
+  public T height(int height) {
+
+    this.height = height;
+    return (T) this;
+  }
+
+  public T title(String title) {
+
+    this.title = title;
+    return (T) this;
+  }
+
+  public T theme(ChartTheme chartTheme) {
+
+    this.chartTheme = chartTheme;
+    return (T) this;
+  }
+
+  public abstract C build();
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/Utils.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/Utils.java
new file mode 100644
index 0000000000000000000000000000000000000000..a7d98d38f4f8d008f2b9159f1ec4a071e588c576
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/Utils.java
@@ -0,0 +1,200 @@
+package org.knowm.xchart.internal;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+public class Utils {
+
+  /** Private Constructor */
+  private Utils() {}
+
+  /**
+   * Gets the offset for the beginning of the tick marks
+   *
+   * @param workingSpace
+   * @param tickSpace
+   * @return
+   */
+  public static double getTickStartOffset(double workingSpace, double tickSpace) {
+
+    double marginSpace = workingSpace - tickSpace;
+    return marginSpace / 2.0;
+  }
+
+  public static double pow(double base, int exponent) {
+
+    if (exponent > 0) {
+      return Math.pow(base, exponent);
+    } else {
+      return 1.0 / Math.pow(base, -1 * exponent);
+    }
+  }
+
+  public static List<Double> getNumberListFromDoubleArray(double[] data) {
+
+    if (data == null) {
+      return null;
+    }
+
+    List<Double> dataNumber;
+    dataNumber = new ArrayList<Double>();
+    for (double d : data) {
+      dataNumber.add(d);
+    }
+    return dataNumber;
+  }
+
+  public static List<Double> getNumberListFromIntArray(int[] data) {
+
+    if (data == null) {
+      return null;
+    }
+
+    List<Double> dataNumber;
+    dataNumber = new ArrayList<Double>();
+    for (double d : data) {
+      dataNumber.add(d);
+    }
+    return dataNumber;
+  }
+
+  public static List<Double> getGeneratedDataAsList(int length) {
+
+    List<Double> generatedData = new ArrayList<>();
+    for (int i = 1; i < length + 1; i++) {
+      generatedData.add((double) i);
+    }
+    return generatedData;
+  }
+
+  public static double[] getDoubleArrayFromFloatArray(float[] data) {
+
+    if (data == null) {
+      return null;
+    }
+    double[] doubles = new double[data.length];
+
+    for (int i = 0; i < data.length; i++) {
+      doubles[i] = data[i];
+    }
+    return doubles;
+  }
+
+  public static double[] getDoubleArrayFromIntArray(int[] data) {
+
+    if (data == null) {
+      return null;
+    }
+    double[] doubles = new double[data.length];
+
+    for (int i = 0; i < data.length; i++) {
+      doubles[i] = data[i];
+    }
+    return doubles;
+  }
+
+  public static double[] getDoubleArrayFromNumberList(List<?> data) {
+
+    if (data == null) {
+      return null;
+    }
+    double[] doubles = new double[data.size()];
+
+    int i = 0;
+    for (Object number : data) {
+      if (number == null) {
+        doubles[i++] = Double.NaN;
+      } else {
+        doubles[i++] = ((Number) number).doubleValue();
+      }
+    }
+    return doubles;
+  }
+
+  public static double[] getDoubleArrayFromDateList(List<?> data) {
+
+    if (data == null) {
+      return null;
+    }
+    double[] doubles = new double[data.size()];
+
+    int i = 0;
+    for (Object date : data) {
+      doubles[i++] = ((Date) date).getTime();
+    }
+    return doubles;
+  }
+
+  public static double[] getGeneratedDataAsArray(int length) {
+
+    double[] generatedData = new double[length];
+    for (int i = 0; i < length; i++) {
+      generatedData[i] = ((double) i + 1);
+    }
+    return generatedData;
+  }
+
+  public static long[] getLongArrayFromIntArray(int[] data) {
+
+    if (data == null) {
+      return null;
+    }
+    long[] longs = new long[data.length];
+
+    for (int i = 0; i < data.length; i++) {
+      longs[i] = data[i];
+    }
+    return longs;
+  }
+
+  public static long[] getLongArrayFromFloatArray(float[] data) {
+
+    if (data == null) {
+      return null;
+    }
+    long[] longs = new long[data.length];
+
+    for (int i = 0; i < data.length; i++) {
+      longs[i] = (long) data[i];
+    }
+    return longs;
+  }
+
+  public static long[] getLongArrayFromNumberList(List<?> data) {
+
+    if (data == null) {
+      return null;
+    }
+    long[] longs = new long[data.size()];
+
+    int i = 0;
+    for (Object number : data) {
+      if (number == null) {
+        longs[i++] = 0;
+      } else {
+        longs[i++] = ((Number) number).longValue();
+      }
+    }
+    return longs;
+  }
+
+  /**
+   * Only adds the extension of the fileExtension to the filename if the filename doesn't already
+   * have it.
+   *
+   * @param fileName File name
+   * @param fileExtension File extension
+   * @return filename (if extension already exists), otherwise;: filename + fileExtension
+   */
+  public static String addFileExtension(String fileName, String fileExtension) {
+    String fileNameWithFileExtension = fileName;
+    if (fileName.length() <= fileExtension.length()
+        || !fileName
+            .substring(fileName.length() - fileExtension.length())
+            .equalsIgnoreCase(fileExtension)) {
+      fileNameWithFileExtension = fileName + fileExtension;
+    }
+    return fileNameWithFileExtension;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Annotation.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Annotation.java
new file mode 100644
index 0000000000000000000000000000000000000000..00091acadbdef161c8827c02e1d2f049a68ef680
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Annotation.java
@@ -0,0 +1,58 @@
+package org.knowm.xchart.internal.chartpart;
+
+import java.awt.geom.Rectangle2D;
+import org.knowm.xchart.style.Styler;
+
+public abstract class Annotation implements ChartPart {
+
+  protected boolean isVisible = true;
+  protected boolean isValueInScreenSpace;
+
+  protected Chart chart;
+  protected Styler styler;
+  protected Rectangle2D bounds;
+
+  public Annotation(boolean isValueInScreenSpace) {
+    this.isValueInScreenSpace = isValueInScreenSpace;
+  }
+
+  public void init(Chart chart) {
+
+    this.chart = chart;
+    this.styler = chart.getStyler();
+  }
+
+  @Override
+  public Rectangle2D getBounds() {
+
+    return bounds;
+  }
+
+  public void setVisible(boolean visible) {
+    isVisible = visible;
+  }
+
+  protected int getXAxisScreenValue(double chartSpaceValue) {
+    return (int) chart.getXAxis().getScreenValue(chartSpaceValue);
+  }
+
+  protected int getYAxisScreenValue(double chartSpaceValue) {
+    return (int) chart.getYAxis().getScreenValue(chartSpaceValue);
+  }
+
+  protected int getXAxisScreenValueForMax() {
+    return (int) chart.getPlot().plotSurface.getBounds().getMaxX();
+  }
+
+  protected int getXAxisScreenValueForMin() {
+    return (int) chart.getPlot().plotSurface.getBounds().getMinX();
+  }
+
+  protected int getYAxisScreenValueForMax() {
+    return (int) chart.getPlot().plotSurface.getBounds().getMaxY();
+  }
+
+  protected int getYAxisScreenValueForMin() {
+    return (int) chart.getPlot().plotSurface.getBounds().getMinY();
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Axis.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Axis.java
new file mode 100644
index 0000000000000000000000000000000000000000..8a08d2fc3838271b61979c9cb029e9a22bbdcb24
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Axis.java
@@ -0,0 +1,770 @@
+package org.knowm.xchart.internal.chartpart;
+
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.Shape;
+import java.awt.font.FontRenderContext;
+import java.awt.font.TextLayout;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Rectangle2D;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.knowm.xchart.CategoryChart;
+import org.knowm.xchart.CategorySeries;
+import org.knowm.xchart.HeatMapChart;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYSeries;
+import org.knowm.xchart.internal.Utils;
+import org.knowm.xchart.internal.series.AxesChartSeries;
+import org.knowm.xchart.internal.series.AxesChartSeriesCategory;
+import org.knowm.xchart.internal.series.Series;
+import org.knowm.xchart.internal.series.Series.DataType;
+import org.knowm.xchart.style.AxesChartStyler;
+import org.knowm.xchart.style.BoxStyler;
+import org.knowm.xchart.style.CategoryStyler;
+import org.knowm.xchart.style.HeatMapStyler;
+import org.knowm.xchart.style.Styler.LegendPosition;
+import org.knowm.xchart.style.Styler.YAxisPosition;
+import org.knowm.xchart.style.XYStyler;
+
+/** Axis */
+public class Axis<ST extends AxesChartStyler, S extends AxesChartSeries> implements ChartPart {
+
+  private final Chart<ST, S> chart;
+  private final Rectangle2D.Double bounds;
+
+  private final ST axesChartStyler;
+
+  /** the axis title */
+  private final AxisTitle<ST, S> axisTitle;
+
+  /** the axis tick */
+  private final AxisTick<ST, S> axisTick;
+
+  /** the axis direction */
+  private final Direction direction;
+
+  /** the axis group index * */
+  private final int index;
+
+  /** the dataType */
+  private Series.DataType dataType;
+
+  /** the axis tick calculator */
+  private AxisTickCalculator axisTickCalculator;
+
+  private double min;
+  private double max;
+
+  /**
+   * Constructor
+   *
+   * @param chart the Chart
+   * @param direction the axis direction (X or Y)
+   * @param index the y-axis index (not relevant for x-axes)
+   */
+  public Axis(Chart<ST, S> chart, Direction direction, int index) {
+
+    this.chart = chart;
+    this.axesChartStyler = chart.getStyler();
+
+    this.direction = direction;
+    this.index = index;
+    bounds = new Rectangle2D.Double();
+    axisTitle =
+        new AxisTitle<ST, S>(chart, direction, direction == Direction.Y ? this : null, index);
+    axisTick = new AxisTick<ST, S>(chart, direction, direction == Direction.Y ? this : null);
+  }
+
+  /** Reset the default min and max values in preparation for calculating the actual min and max */
+  void resetMinMax() {
+
+    min = Double.MAX_VALUE;
+    max = -1 * Double.MAX_VALUE;
+  }
+
+  /**
+   * @param min
+   * @param max
+   */
+  void addMinMax(double min, double max) {
+
+    // System.out.println(min);
+    // System.out.println(max);
+    // NaN indicates String axis data, so min and max play no role
+    if (Double.isNaN(this.min) || min < this.min) {
+      this.min = min;
+    }
+    if (Double.isNaN(this.max) || max > this.max) {
+      this.max = max;
+    }
+
+    // System.out.println(this.min);
+    // System.out.println(this.max);
+  }
+
+  public void preparePaint() {
+
+    double legendHeightOffset = 0;
+    if (axesChartStyler.isLegendVisible()
+        && axesChartStyler.getLegendPosition() == LegendPosition.OutsideS)
+      legendHeightOffset = chart.getLegend().getBounds().getHeight();
+
+    // determine Axis bounds
+    if (direction == Direction.Y) { // Y-Axis - gets called first
+
+      // calculate paint zone
+      // ----
+      // |
+      // |
+      // |
+      // |
+      // ----
+      // double xOffset = chart.getAxisPair().getYAxisXOffset();
+      double xOffset = 0; // this will be updated on AxisPair.paint() method
+      // double yOffset = chart.getChartTitle().getBounds().getHeight() < .1 ?
+      // axesChartStyler.getChartPadding() : chart.getChartTitle().getBounds().getHeight()
+      // + axesChartStyler.getChartPadding();
+      double yOffset =
+          chart.getChartTitle().getBounds().getHeight() + axesChartStyler.getChartPadding();
+
+      /////////////////////////
+      int i = 1; // just twice through is all it takes
+      double width = 60; // arbitrary, final width depends on Axis tick labels
+      double height;
+      do {
+        // System.out.println("width before: " + width);
+
+        double legendWidthOffset = 0;
+        if (axesChartStyler.isLegendVisible()
+            && axesChartStyler.getLegendPosition() == LegendPosition.OutsideE)
+          legendWidthOffset = axesChartStyler.getChartPadding();
+
+        double approximateXAxisWidth =
+            chart.getWidth()
+                - width // y-axis approx. width
+                - (axesChartStyler.getLegendPosition() == LegendPosition.OutsideE
+                    ? chart.getLegend().getBounds().getWidth()
+                    : 0)
+                - 2 * axesChartStyler.getChartPadding()
+                - (axesChartStyler.isYAxisTicksVisible() ? (axesChartStyler.getPlotMargin()) : 0)
+                - legendWidthOffset;
+
+        height =
+            chart.getHeight()
+                - yOffset
+                - chart.getXAxis().getXAxisHeightHint(approximateXAxisWidth)
+                - axesChartStyler.getPlotMargin()
+                - axesChartStyler.getChartPadding()
+                - legendHeightOffset;
+
+        width = getYAxisWidthHint(height);
+        //         System.out.println("width after: " + width);
+
+        // System.out.println("height: " + height);
+
+      } while (i-- > 0);
+
+      /////////////////////////
+
+      // bounds = new Rectangle2D.Double(xOffset, yOffset, width, height);
+      bounds.setRect(xOffset, yOffset, width, height);
+
+    } else { // X-Axis
+
+      // calculate paint zone
+      // |____________________|
+
+      Rectangle2D leftYAxisBounds = chart.getAxisPair().getLeftYAxisBounds();
+      Rectangle2D rightYAxisBounds = chart.getAxisPair().getRightYAxisBounds();
+
+      double maxYAxisY =
+          Math.max(
+              leftYAxisBounds.getY() + leftYAxisBounds.getHeight(),
+              rightYAxisBounds.getY() + rightYAxisBounds.getHeight());
+      double xOffset = leftYAxisBounds.getWidth() + leftYAxisBounds.getX();
+      double yOffset = maxYAxisY + axesChartStyler.getPlotMargin() - legendHeightOffset;
+
+      double legendWidth = 0;
+      if (axesChartStyler.getLegendPosition() == LegendPosition.OutsideE
+          && axesChartStyler.isLegendVisible()) {
+        legendWidth = chart.getLegend().getBounds().getWidth() + axesChartStyler.getChartPadding();
+      }
+      double width =
+          chart.getWidth()
+              - leftYAxisBounds.getWidth() // y-axis was already painted
+              - rightYAxisBounds.getWidth() // y-axis was already painted
+              - leftYAxisBounds.getX() // use left y-axis x instead of padding
+              - 1 * axesChartStyler.getChartPadding() // right y-axis padding
+
+              // - tickMargin is included in left & right y axis bounds
+
+              - legendWidth;
+
+      // double height = this.getXAxisHeightHint(width);
+      // System.out.println("height: " + height);
+      // the Y-Axis was already draw at this point so we know how much vertical room is left for the
+      // X-Axis
+      double height =
+          chart.getHeight()
+              - maxYAxisY
+              - axesChartStyler.getChartPadding()
+              - axesChartStyler.getPlotMargin();
+      // System.out.println("height2: " + height2);
+
+      bounds.setRect(xOffset, yOffset, width, height);
+    }
+  }
+
+  @Override
+  public void paint(Graphics2D g) {
+
+    Object oldHint = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
+    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+
+    // determine Axis bounds
+    if (direction == Direction.Y) { // Y-Axis - gets called first
+
+      /////////////////////////
+
+      // fill in Axis with sub-components
+      boolean onRight = axesChartStyler.getYAxisGroupPosistion(index) == YAxisPosition.Right;
+      if (onRight) {
+        axisTick.paint(g);
+        axisTitle.paint(g);
+      } else {
+        axisTitle.paint(g);
+        axisTick.paint(g);
+      }
+
+      // now we know the real bounds width after ticks and title are painted
+      bounds.width =
+          (axesChartStyler.isYAxisTitleVisible() ? axisTitle.getBounds().getWidth() : 0)
+              + axisTick.getBounds().getWidth();
+      // g.setColor(Color.yellow);
+      // g.draw(bounds);
+
+    } else { // X-Axis
+
+      // calculate paint zone
+      // |____________________|
+
+      // g.setColor(Color.yellow);
+      // g.draw(bounds);
+
+      // now paint the X-Axis given the above paint zone
+      this.axisTickCalculator = getAxisTickCalculator(bounds.getWidth());
+      axisTitle.paint(g);
+      axisTick.paint(g);
+    }
+
+    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, oldHint);
+  }
+
+  /**
+   * The vertical Y-Axis is drawn first, but to know the lower bounds of it, we need to know how
+   * high the X-Axis paint zone is going to be. Since the tick labels could be rotated, we need to
+   * actually determine the tick labels first to get an idea of how tall the X-Axis tick labels will
+   * be.
+   *
+   * @return the x-axis height hint
+   */
+  private double getXAxisHeightHint(double workingSpace) {
+
+    // Axis title
+    double titleHeight = 0.0;
+    if (chart.getXAxisTitle() != null
+        && !chart.getXAxisTitle().trim().equalsIgnoreCase("")
+        && axesChartStyler.isXAxisTitleVisible()) {
+      TextLayout textLayout =
+          new TextLayout(
+              chart.getXAxisTitle(),
+              axesChartStyler.getAxisTitleFont(),
+              new FontRenderContext(null, true, false));
+      Rectangle2D rectangle = textLayout.getBounds();
+      titleHeight = rectangle.getHeight() + axesChartStyler.getAxisTitlePadding();
+    }
+
+    this.axisTickCalculator = getAxisTickCalculator(workingSpace);
+
+    // Axis tick labels
+    double axisTickLabelsHeight = 0.0;
+    if (axesChartStyler.isXAxisTicksVisible()) {
+
+      // get some real tick labels
+      // System.out.println("XAxisHeightHint");
+      // System.out.println("workingSpace: " + workingSpace);
+
+      String sampleLabel = "";
+      // find the longest String in all the labels
+      for (int i = 0; i < axisTickCalculator.getTickLabels().size(); i++) {
+        // System.out.println("label: " + axisTickCalculator.getTickLabels().get(i));
+        if (axisTickCalculator.getTickLabels().get(i) != null
+            && axisTickCalculator.getTickLabels().get(i).length() > sampleLabel.length()) {
+          sampleLabel = axisTickCalculator.getTickLabels().get(i);
+        }
+      }
+      // System.out.println("sampleLabel: " + sampleLabel);
+
+      // get the height of the label including rotation
+      TextLayout textLayout =
+          new TextLayout(
+              sampleLabel.length() == 0 ? " " : sampleLabel,
+              axesChartStyler.getAxisTickLabelsFont(),
+              new FontRenderContext(null, true, false));
+      AffineTransform rot =
+          axesChartStyler.getXAxisLabelRotation() == 0
+              ? null
+              : AffineTransform.getRotateInstance(
+                  -1 * Math.toRadians(axesChartStyler.getXAxisLabelRotation()));
+      Shape shape = textLayout.getOutline(rot);
+      Rectangle2D rectangle = shape.getBounds();
+
+      axisTickLabelsHeight =
+          rectangle.getHeight()
+              + axesChartStyler.getAxisTickPadding()
+              + axesChartStyler.getAxisTickMarkLength();
+    }
+    return titleHeight + axisTickLabelsHeight;
+  }
+
+  private double getYAxisWidthHint(double workingSpace) {
+
+    // Axis title
+    double titleHeight = 0.0;
+    String yAxisTitle = chart.getYAxisGroupTitle(index);
+    if (yAxisTitle != null
+        && !yAxisTitle.trim().equalsIgnoreCase("")
+        && axesChartStyler.isYAxisTitleVisible()) {
+      TextLayout textLayout =
+          new TextLayout(
+              yAxisTitle,
+              axesChartStyler.getAxisTitleFont(),
+              new FontRenderContext(null, true, false));
+      Rectangle2D rectangle = textLayout.getBounds();
+      titleHeight = rectangle.getHeight() + axesChartStyler.getAxisTitlePadding();
+    }
+
+    this.axisTickCalculator = getAxisTickCalculator(workingSpace);
+
+    // Axis tick labels
+    double axisTickLabelsHeight = 0.0;
+    if (axesChartStyler.isYAxisTicksVisible()) {
+
+      // get some real tick labels
+      // System.out.println("XAxisHeightHint");
+      // System.out.println("workingSpace: " + workingSpace);
+
+      String sampleLabel = "";
+      // find the longest String in all the labels
+      for (int i = 0; i < axisTickCalculator.getTickLabels().size(); i++) {
+        if (axisTickCalculator.getTickLabels().get(i) != null
+            && axisTickCalculator.getTickLabels().get(i).length() > sampleLabel.length()) {
+          sampleLabel = axisTickCalculator.getTickLabels().get(i);
+        }
+      }
+
+      // get the height of the label including rotation
+      TextLayout textLayout =
+          new TextLayout(
+              sampleLabel.length() == 0 ? " " : sampleLabel,
+              axesChartStyler.getAxisTickLabelsFont(),
+              new FontRenderContext(null, true, false));
+      Rectangle2D rectangle = textLayout.getBounds();
+
+      axisTickLabelsHeight =
+          rectangle.getWidth()
+              + axesChartStyler.getAxisTickPadding()
+              + axesChartStyler.getAxisTickMarkLength();
+    }
+    return titleHeight + axisTickLabelsHeight;
+  }
+
+  private AxisTickCalculator getAxisTickCalculator(double workingSpace) {
+    if (getDirection() == Direction.X) {
+      return getAxisTickCalculatorForX(workingSpace);
+    } else {
+      return getAxisTickCalculatorForY(workingSpace);
+    }
+  }
+
+  private AxisTickCalculator getAxisTickCalculatorForY(double workingSpace) {
+    List<Double> yData = new ArrayList<>();
+    if (axesChartStyler instanceof HeatMapStyler) {
+      List<?> categories = ((HeatMapChart) chart).getHeatMapSeries().getYData();
+      yData =
+          categories.stream()
+              .filter(Objects::nonNull)
+              .filter(it -> it instanceof Number)
+              .mapToDouble(it -> ((Number) it).doubleValue())
+              .boxed()
+              .collect(Collectors.toList());
+    } else if (axesChartStyler instanceof CategoryStyler) {
+      Set<Double> uniqueYData = new LinkedHashSet<>();
+      for (CategorySeries categorySeries : ((CategoryChart) chart).getSeriesMap().values()) {
+        uniqueYData.addAll(
+            categorySeries.getYData().stream()
+                .filter(Objects::nonNull)
+                .mapToDouble(Number::doubleValue)
+                .boxed()
+                .collect(Collectors.toList()));
+      }
+      yData.addAll(uniqueYData);
+    } else if (axesChartStyler instanceof XYStyler) {
+      Set<Double> uniqueYData = new LinkedHashSet<>();
+      for (XYSeries xySeries : ((XYChart) chart).getSeriesMap().values()) {
+        uniqueYData.addAll(Arrays.stream(xySeries.getYData()).boxed().collect(Collectors.toList()));
+      }
+      yData.addAll(uniqueYData);
+    }
+
+    if (axesChartStyler.getyAxisTickLabelsFormattingFunction() != null) {
+      if (!yData.isEmpty()) {
+        return new AxisTickCalculator_Callback(
+            axesChartStyler.getyAxisTickLabelsFormattingFunction(),
+            getDirection(),
+            workingSpace,
+            min,
+            max,
+            yData,
+            axesChartStyler);
+      }
+      return new AxisTickCalculator_Callback(
+          axesChartStyler.getyAxisTickLabelsFormattingFunction(),
+          getDirection(),
+          workingSpace,
+          min,
+          max,
+          axesChartStyler);
+
+    } else if (axesChartStyler.isYAxisLogarithmic() && getDataType() != DataType.Date) {
+
+      return new AxisTickCalculator_Logarithmic(
+          getDirection(), workingSpace, min, max, axesChartStyler, getYIndex());
+    } else if (axesChartStyler instanceof HeatMapStyler) {
+
+      List<?> categories = ((HeatMapChart) chart).getHeatMapSeries().getYData();
+      DataType axisType = chart.getAxisPair().getYAxis().getDataType();
+
+      return new AxisTickCalculator_Category(
+          getDirection(), workingSpace, categories, axisType, axesChartStyler);
+    } else {
+      if (!yData.isEmpty()) {
+        return new AxisTickCalculator_Number(
+            getDirection(), workingSpace, min, max, yData, axesChartStyler);
+      }
+      return new AxisTickCalculator_Number(
+          getDirection(), workingSpace, min, max, axesChartStyler, getYIndex());
+    }
+  }
+
+  private AxisTickCalculator_ getAxisTickCalculatorForX(double workingSpace) {
+    List<Double> xData = new ArrayList<>();
+    if (axesChartStyler instanceof HeatMapStyler) {
+      List<?> categories = ((HeatMapChart) chart).getHeatMapSeries().getXData();
+      xData =
+          categories.stream()
+              .filter(Objects::nonNull)
+              .filter(it -> it instanceof Number)
+              .mapToDouble(it -> ((Number) it).doubleValue())
+              .boxed()
+              .collect(Collectors.toList());
+    } else if (axesChartStyler instanceof CategoryStyler) {
+      Set<Double> uniqueXData = new LinkedHashSet<>();
+      for (CategorySeries categorySeries : ((CategoryChart) chart).getSeriesMap().values()) {
+        List<Double> numericCategoryXData =
+            categorySeries.getXData().stream()
+                .filter(Objects::nonNull)
+                .filter(x -> x instanceof Number)
+                .mapToDouble(x -> ((Number) x).doubleValue())
+                .boxed()
+                .collect(Collectors.toList());
+        uniqueXData.addAll(numericCategoryXData);
+      }
+      xData.addAll(uniqueXData);
+    } else if (axesChartStyler instanceof XYStyler) {
+      Set<Double> uniqueXData = new LinkedHashSet<>();
+      for (XYSeries xySeries : ((XYChart) chart).getSeriesMap().values()) {
+        uniqueXData.addAll(Arrays.stream(xySeries.getXData()).boxed().collect(Collectors.toList()));
+      }
+      xData.addAll(uniqueXData);
+    }
+
+    if (axesChartStyler.getxAxisTickLabelsFormattingFunction() != null) {
+      if (!xData.isEmpty()) { // TODO why would this be empty?
+        return new AxisTickCalculator_Callback(
+            axesChartStyler.getxAxisTickLabelsFormattingFunction(),
+            getDirection(),
+            workingSpace,
+            min,
+            max,
+            xData,
+            axesChartStyler);
+      }
+      return new AxisTickCalculator_Callback(
+          axesChartStyler.getxAxisTickLabelsFormattingFunction(),
+          getDirection(),
+          workingSpace,
+          min,
+          max,
+          axesChartStyler);
+
+    } else if (axesChartStyler instanceof CategoryStyler || axesChartStyler instanceof BoxStyler) {
+
+      // TODO Cleanup? More elegant way?
+      AxesChartSeriesCategory axesChartSeries =
+          (AxesChartSeriesCategory) chart.getSeriesMap().values().iterator().next();
+      List<?> categories = (List<?>) axesChartSeries.getXData();
+      DataType axisType = chart.getAxisPair().getXAxis().getDataType();
+
+      return new AxisTickCalculator_Category(
+          getDirection(), workingSpace, categories, axisType, axesChartStyler);
+
+    } else if (getDataType() == DataType.Date && !(axesChartStyler instanceof HeatMapStyler)) {
+
+      return new AxisTickCalculator_Date(getDirection(), workingSpace, min, max, axesChartStyler);
+
+    } else if (axesChartStyler.isXAxisLogarithmic()) {
+
+      return new AxisTickCalculator_Logarithmic(
+          getDirection(), workingSpace, min, max, axesChartStyler);
+
+    } else if (axesChartStyler instanceof HeatMapStyler) {
+
+      List<?> categories = ((HeatMapChart) chart).getHeatMapSeries().getXData();
+      DataType axisType = chart.getAxisPair().getXAxis().getDataType();
+
+      return new AxisTickCalculator_Category(
+          getDirection(), workingSpace, categories, axisType, axesChartStyler);
+    } else {
+      if (!xData.isEmpty()) {
+        return new AxisTickCalculator_Number(
+            getDirection(), workingSpace, min, max, xData, axesChartStyler);
+      }
+      return new AxisTickCalculator_Number(getDirection(), workingSpace, min, max, axesChartStyler);
+    }
+  }
+
+  Series.DataType getDataType() {
+
+    return dataType;
+  }
+
+  // Getters /////////////////////////////////////////////////
+
+  public void setDataType(Series.DataType dataType) {
+
+    if (dataType != null && this.dataType != null && this.dataType != dataType) {
+      throw new IllegalArgumentException(
+          "Different Axes (e.g. Date, Number, String) cannot be mixed on the same chart!!");
+    }
+    this.dataType = dataType;
+  }
+
+  double getMin() {
+
+    return min;
+  }
+
+  void setMin(double min) {
+
+    this.min = min;
+  }
+
+  double getMax() {
+
+    return max;
+  }
+
+  void setMax(double max) {
+
+    this.max = max;
+  }
+
+  AxisTick<ST, S> getAxisTick() {
+
+    return axisTick;
+  }
+
+  private Direction getDirection() {
+
+    return direction;
+  }
+
+  AxisTitle<ST, S> getAxisTitle() {
+
+    return axisTitle;
+  }
+
+  public AxisTickCalculator getAxisTickCalculator() {
+
+    return this.axisTickCalculator;
+  }
+
+  @Override
+  public Rectangle2D getBounds() {
+
+    return bounds;
+  }
+
+  public int getYIndex() {
+
+    return index;
+  }
+
+  /**
+   * Converts a chart coordinate value to screen coordinate. Same as AxisTickCalculators
+   * calculation.
+   *
+   * @param chartPoint value in chart coordinate system
+   * @return Coordinate of screen. eg: MouseEvent.getX(), MouseEvent.getY()
+   */
+  // TODO check these method out and make non public??
+  public double getScreenValue(double chartPoint) {
+
+    double minVal = min;
+    double maxVal = max;
+
+    // min & max is not set in category charts with string labels
+    if (min > max) {
+      if (getDirection() == Direction.X) {
+        if (axesChartStyler instanceof CategoryStyler) {
+          AxesChartSeriesCategory axesChartSeries =
+              (AxesChartSeriesCategory) chart.getSeriesMap().values().iterator().next();
+          int count = axesChartSeries.getXData().size();
+          minVal = 0;
+          maxVal = count;
+        }
+      }
+    }
+
+    double workingSpace;
+    double startOffset;
+    boolean isLog;
+    if (direction == Direction.X) {
+      startOffset = bounds.getX();
+      workingSpace = bounds.getWidth();
+      isLog = axesChartStyler.isXAxisLogarithmic();
+    } else {
+      startOffset = 0; // bounds.getY();
+      workingSpace = bounds.getHeight();
+      isLog = axesChartStyler.isYAxisLogarithmic();
+    }
+
+    // a check if all axis data are the exact same values
+    if (min == max) {
+      return workingSpace / 2;
+    }
+
+    // tick space - a percentage of the working space available for ticks
+    double tickSpace = axesChartStyler.getPlotContentSize() * workingSpace; // in plot space
+
+    // this prevents an infinite loop when the plot gets sized really small.
+    if (tickSpace < axesChartStyler.getXAxisTickMarkSpacingHint()) {
+      return workingSpace / 2;
+    }
+
+    // where the tick should begin in the working space in pixels
+    double margin = Utils.getTickStartOffset(workingSpace, tickSpace);
+
+    minVal = isLog ? Math.log10(minVal) : minVal;
+    maxVal = isLog ? Math.log10(maxVal) : maxVal;
+    chartPoint = isLog ? Math.log10(chartPoint) : chartPoint;
+    double tickLabelPosition =
+        startOffset + margin + ((chartPoint - minVal) / (maxVal - minVal) * tickSpace);
+
+    if (direction == Direction.Y) {
+      tickLabelPosition = bounds.getHeight() - tickLabelPosition + bounds.getY();
+    }
+    return tickLabelPosition;
+  }
+
+  public double getScreenValueForMin() {
+    return getScreenValue(min);
+  }
+
+  public double getScreenValueForMax() {
+    return getScreenValue(max);
+  }
+
+  /**
+   * Converts a screen coordinate to chart coordinate value. Reverses the AxisTickCalculators
+   * calculation.
+   *
+   * @param screenPoint Coordinate of screen. eg: MouseEvent.getX(), MouseEvent.getY()
+   * @return value in chart coordinate system
+   */
+  public double getChartValue(double screenPoint) {
+
+    // a check if all axis data are the exact same values
+    if (min == max) {
+      return min;
+    }
+
+    double minVal = min;
+    double maxVal = max;
+
+    // min & max is not set in category charts with string labels
+    if (min > max) {
+      if (getDirection() == Direction.X) {
+        if (axesChartStyler instanceof CategoryStyler) {
+          AxesChartSeriesCategory axesChartSeries =
+              (AxesChartSeriesCategory) chart.getSeriesMap().values().iterator().next();
+          int count = axesChartSeries.getXData().size();
+          minVal = 0;
+          maxVal = count;
+        }
+      }
+    }
+
+    double workingSpace;
+    double startOffset;
+    boolean isLog;
+    if (direction == Direction.X) {
+      startOffset = bounds.getX();
+      workingSpace = bounds.getWidth();
+      isLog = axesChartStyler.isXAxisLogarithmic();
+    } else {
+      startOffset = 0; // bounds.getY();
+      workingSpace = bounds.getHeight();
+      screenPoint = bounds.getHeight() - screenPoint + bounds.getY(); // y increments top to bottom
+      isLog = axesChartStyler.isYAxisLogarithmic();
+    }
+
+    // tick space - a percentage of the working space available for ticks
+    double tickSpace = axesChartStyler.getPlotContentSize() * workingSpace; // in plot space
+
+    // this prevents an infinite loop when the plot gets sized really small.
+    if (tickSpace < axesChartStyler.getXAxisTickMarkSpacingHint()) {
+      return minVal;
+    }
+
+    // where the tick should begin in the working space in pixels
+    double margin = Utils.getTickStartOffset(workingSpace, tickSpace);
+
+    // given tickLabelPositon (screenPoint) find value
+    // double tickLabelPosition =
+    //       margin + ((value - min) / (max - min) * tickSpace);
+
+    minVal = isLog ? Math.log10(minVal) : minVal;
+    maxVal = isLog ? Math.log10(maxVal) : maxVal;
+    double value = ((screenPoint - margin - startOffset) * (maxVal - minVal) / tickSpace) + minVal;
+    value = isLog ? Math.pow(10, value) : value;
+    return value;
+  }
+
+  /** An axis direction */
+  public enum Direction {
+
+    /** the constant to represent X axis */
+    X,
+
+    /** the constant to represent Y axis */
+    Y
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/AxisPair.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/AxisPair.java
new file mode 100644
index 0000000000000000000000000000000000000000..4f9bd3ecec2abaf8464f17e542b67b02ae808ea9
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/AxisPair.java
@@ -0,0 +1,532 @@
+package org.knowm.xchart.internal.chartpart;
+
+import java.awt.Graphics2D;
+import java.awt.geom.Rectangle2D;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.TreeMap;
+import org.knowm.xchart.CategorySeries.CategorySeriesRenderStyle;
+import org.knowm.xchart.internal.series.AxesChartSeries;
+import org.knowm.xchart.internal.series.AxesChartSeriesCategory;
+import org.knowm.xchart.style.AxesChartStyler;
+import org.knowm.xchart.style.BoxStyler;
+import org.knowm.xchart.style.CategoryStyler;
+import org.knowm.xchart.style.Styler.LegendPosition;
+import org.knowm.xchart.style.Styler.YAxisPosition;
+
+public class AxisPair<ST extends AxesChartStyler, S extends AxesChartSeries> implements ChartPart {
+
+  private final Chart<ST, S> chart;
+
+  private final Axis<ST, S> xAxis;
+  private final Axis<ST, S> yAxis;
+  private final TreeMap<Integer, Axis<ST, S>> yAxisMap;
+  private final Rectangle2D.Double leftYAxisBounds;
+  private final Rectangle2D.Double rightYAxisBounds;
+  private Axis<ST, S> leftMainYAxis;
+  private Axis<ST, S> rightMainYAxis;
+
+  /**
+   * Constructor
+   *
+   * @param chart
+   */
+  public AxisPair(Chart<ST, S> chart) {
+
+    this.chart = chart;
+
+    // add axes
+    xAxis = new Axis<ST, S>(chart, Axis.Direction.X, 0);
+    yAxis = new Axis<ST, S>(chart, Axis.Direction.Y, 0);
+    yAxisMap = new TreeMap<Integer, Axis<ST, S>>();
+    yAxisMap.put(0, yAxis);
+    leftYAxisBounds = new Rectangle2D.Double();
+    rightYAxisBounds = new Rectangle2D.Double();
+  }
+
+  @Override
+  public void paint(Graphics2D g) {
+
+    prepareForPaint();
+
+    leftMainYAxis = null;
+    rightMainYAxis = null;
+
+    ST styler = chart.getStyler();
+
+    final int chartPadding = styler.getChartPadding();
+    final int paddingBetweenAxes = chartPadding;
+
+    int tickMargin = (styler.isYAxisTicksVisible() ? (styler.getPlotMargin()) : 0);
+    leftYAxisBounds.width = 0;
+    // draw left sided axises
+    int leftCount = 0;
+    double leftStart = chartPadding;
+
+    int desiredLeftYAxisWidth = styler.getYAxisLeftWidthHint();
+    // calculate width first
+    if (desiredLeftYAxisWidth > 0) {
+      double widthEstimation = 0;
+      for (Entry<Integer, Axis<ST, S>> e : yAxisMap.entrySet()) {
+        Axis<ST, S> ya = e.getValue();
+        if (styler.getYAxisGroupPosistion(e.getKey()) == YAxisPosition.Right) {
+          continue;
+        }
+        ya.preparePaint();
+        Rectangle2D.Double bounds = (java.awt.geom.Rectangle2D.Double) ya.getBounds();
+        // add padding before axis
+        double width = bounds.getWidth();
+        widthEstimation += width;
+        leftCount++;
+      }
+
+      if (leftCount > 1) {
+        widthEstimation += (leftCount - 1) * paddingBetweenAxes;
+      }
+      widthEstimation += leftCount * tickMargin;
+
+      if (widthEstimation < desiredLeftYAxisWidth) {
+        leftStart = desiredLeftYAxisWidth - widthEstimation;
+      }
+
+      leftCount = 0;
+    }
+    double leftStartFirst = leftStart;
+
+    for (Entry<Integer, Axis<ST, S>> e : yAxisMap.entrySet()) {
+      Axis<ST, S> ya = e.getValue();
+      if (styler.getYAxisGroupPosistion(e.getKey()) == YAxisPosition.Right) {
+        continue;
+      }
+      if (e.getKey() == 0) {
+
+        // draw main axis group rightmost
+        continue;
+      }
+      ya.preparePaint();
+      Rectangle2D.Double bounds = (java.awt.geom.Rectangle2D.Double) ya.getBounds();
+      // add padding before axis
+      bounds.x = leftStart;
+      ya.paint(g);
+      double width = bounds.getWidth();
+      leftStart += paddingBetweenAxes + width + tickMargin;
+      leftYAxisBounds.width += width;
+      leftCount++;
+      leftMainYAxis = ya;
+    }
+
+    if (styler.getYAxisGroupPosistion(0) != YAxisPosition.Right) {
+      yAxis.preparePaint();
+      Rectangle2D.Double bounds = (java.awt.geom.Rectangle2D.Double) yAxis.getBounds();
+      // add padding before axis
+      bounds.x = leftStart;
+      yAxis.paint(g);
+      double width = bounds.getWidth();
+      leftStart += paddingBetweenAxes + width + tickMargin;
+      leftYAxisBounds.width += width;
+      leftCount++;
+      leftMainYAxis = yAxis;
+    }
+
+    if (leftCount > 1) {
+      leftYAxisBounds.width += (leftCount - 1) * paddingBetweenAxes;
+    }
+    leftYAxisBounds.width += leftCount * tickMargin;
+
+    rightYAxisBounds.width = 0;
+
+    double legendWidth = 0;
+    if (styler.getLegendPosition() == LegendPosition.OutsideE && styler.isLegendVisible()) {
+      legendWidth = chart.getLegend().getBounds().getWidth() + styler.getChartPadding();
+    }
+    double rightEnd = chart.getWidth() - legendWidth - chartPadding;
+
+    rightYAxisBounds.x = rightEnd;
+
+    int rightCount = 0;
+
+    // traverse reverse
+    for (Entry<Integer, Axis<ST, S>> e : yAxisMap.descendingMap().entrySet()) {
+      Axis<ST, S> ya = e.getValue();
+      if (styler.getYAxisGroupPosistion(e.getKey()) != YAxisPosition.Right) {
+        continue;
+      }
+      if (e.getKey() == 0) {
+
+        // draw main axis group leftmost
+        continue;
+      }
+      ya.preparePaint();
+      Rectangle2D.Double bounds = (java.awt.geom.Rectangle2D.Double) ya.getBounds();
+      double aproxWidth = bounds.getWidth();
+      double xOffset = rightEnd - aproxWidth;
+      bounds.x = xOffset;
+      rightYAxisBounds.x = xOffset;
+      ya.paint(g);
+      // double width = bounds.getWidth();
+      // we already draw the axis, so actual width is not necessary
+      rightYAxisBounds.width += aproxWidth;
+
+      rightEnd -= paddingBetweenAxes + aproxWidth + tickMargin;
+      rightCount++;
+      rightMainYAxis = ya;
+    }
+
+    if (styler.getYAxisGroupPosistion(0) == YAxisPosition.Right) {
+      yAxis.preparePaint();
+      Rectangle2D.Double bounds = (java.awt.geom.Rectangle2D.Double) yAxis.getBounds();
+      double aproxWidth = bounds.getWidth();
+      double xOffset = rightEnd - aproxWidth;
+      bounds.x = xOffset;
+      rightYAxisBounds.x = xOffset;
+      yAxis.paint(g);
+      // double width = bounds.getWidth();
+      // we already draw the axis, so actual width is not necessary
+      rightYAxisBounds.width += aproxWidth;
+
+      rightEnd -= paddingBetweenAxes + aproxWidth + tickMargin;
+      rightCount++;
+      rightMainYAxis = yAxis;
+    }
+    if (leftMainYAxis == null) {
+      leftMainYAxis = yAxis;
+    }
+    if (rightMainYAxis == null) {
+      rightMainYAxis = yAxis;
+    }
+
+    if (rightCount > 1) {
+      rightYAxisBounds.width += (rightCount - 1) * paddingBetweenAxes;
+    }
+    rightYAxisBounds.width += rightCount * tickMargin;
+
+    // fill left & right bounds
+    Rectangle2D.Double bounds = (java.awt.geom.Rectangle2D.Double) yAxis.getBounds();
+    leftYAxisBounds.x = leftStartFirst;
+    leftYAxisBounds.y = bounds.y;
+    leftYAxisBounds.height = bounds.height;
+
+    // rightYAxisBounds.x -= (styler.isYAxisTicksVisible() ? (styler.getPlotMargin()) : 0);
+
+    rightYAxisBounds.y = bounds.y;
+    rightYAxisBounds.height = bounds.height;
+
+    xAxis.preparePaint();
+    xAxis.paint(g);
+    // Utils.printBounds("x axis", xAxis.getBounds());
+    // Utils.printBounds("left Y axis", leftYAxisBounds);
+    // for (Entry<Integer, Axis<AxesChartStyler, AxesChartSeries>> e : yAxisMap.entrySet()) {
+    // Axis<AxesChartStyler, AxesChartSeries> ya = e.getValue();
+    // if (styler.getYAxisGroupPosistion(e.getKey()) != YAxisPosition.Right) {
+    // Utils.printBounds(" y axis " + e.getKey(), ya.getBounds());
+    // }
+    // }
+    // Utils.printBounds("right Y axis", rightYAxisBounds);
+    // for (Entry<Integer, Axis<AxesChartStyler, AxesChartSeries>> e : yAxisMap.entrySet()) {
+    // Axis<AxesChartStyler, AxesChartSeries> ya = e.getValue();
+    // if (styler.getYAxisGroupPosistion(e.getKey()) == YAxisPosition.Right) {
+    // Utils.printBounds(" y axis " + e.getKey(), ya.getBounds());
+    // }
+    // }
+  }
+
+  private void prepareForPaint() {
+
+    yAxisMap.clear();
+    yAxisMap.put(0, yAxis);
+    boolean mainYAxisUsed = false;
+    if (chart.getSeriesMap() != null) {
+      for (S series : chart.getSeriesMap().values()) {
+        if (!series.isEnabled()) {
+          continue;
+        }
+        int yIndex = series.getYAxisGroup();
+        if (!mainYAxisUsed && yIndex == 0) {
+          mainYAxisUsed = true;
+        }
+        if (yAxisMap.containsKey(yIndex)) {
+          continue;
+        }
+        yAxisMap.put(yIndex, new Axis<ST, S>(chart, Axis.Direction.Y, yIndex));
+      }
+    }
+
+    // set the axis data types, making sure all are compatible
+    xAxis.setDataType(null);
+    for (Axis<ST, S> ya : yAxisMap.values()) {
+      ya.setDataType(null);
+    }
+    for (S series : chart.getSeriesMap().values()) {
+      xAxis.setDataType(series.getxAxisDataType());
+      if (!series.isEnabled()) {
+        continue;
+      }
+
+      getYAxis(series.getYAxisGroup()).setDataType(series.getyAxisDataType());
+      if (!mainYAxisUsed) {
+        yAxis.setDataType(series.getyAxisDataType());
+      }
+
+      if (series.getYAxisDecimalPattern() != null) {
+        chart
+            .getStyler()
+            .putYAxisGroupDecimalPatternMap(
+                series.getYAxisGroup(), series.getYAxisDecimalPattern());
+      }
+    }
+
+    // calculate axis min and max
+    xAxis.resetMinMax();
+    for (Axis<ST, S> ya : yAxisMap.values()) {
+      ya.resetMinMax();
+    }
+
+    // if no series, we still want to plot an empty plot with axes. Since there are no min and max
+    // with no series added, we just fake it arbitrarily.
+    if (chart.getSeriesMap() == null || chart.getSeriesMap().size() < 1) {
+      setDefaultAxisMinMax();
+    } else {
+      int disabledCount = 0; // maybe all are disabled, so we check this condition
+      for (S series : chart.getSeriesMap().values()) {
+        // add min/max to axes
+        // System.out.println(series.getxMin());
+        // System.out.println(series.getxMax());
+        // System.out.println(series.getyMin());
+        // System.out.println(series.getyMax());
+        // System.out.println("****");
+        if (!series.isEnabled()) {
+          disabledCount++;
+          continue;
+        }
+        xAxis.addMinMax(series.getXMin(), series.getXMax());
+
+        getYAxis(series.getYAxisGroup()).addMinMax(series.getYMin(), series.getYMax());
+        if (!mainYAxisUsed) {
+          yAxis.addMinMax(series.getYMin(), series.getYMax());
+        }
+      }
+      if (disabledCount == chart.getSeriesMap().values().size()) {
+        setDefaultAxisMinMax();
+      }
+    }
+
+    overrideMinMaxForXAxis();
+    for (Axis<ST, S> ya : yAxisMap.values()) {
+      overrideMinMaxForYAxis(ya);
+    }
+
+    // logarithmic sanity check
+    if (chart.getStyler().isXAxisLogarithmic() && xAxis.getMin() <= 0.0) {
+      throw new IllegalArgumentException(
+          "Series data (accounting for error bars too) cannot be less or equal to zero for a logarithmic X-Axis!!!");
+    }
+    if (chart.getStyler().isYAxisLogarithmic()) {
+      for (Axis<ST, S> ya : yAxisMap.values()) {
+        if (ya.getMin() <= 0.0) {
+          // System.out.println(getMin());
+          throw new IllegalArgumentException(
+              "Series data (accounting for error bars too) cannot be less or equal to zero for a logarithmic Y-Axis!!!");
+        }
+      }
+    }
+    // infinity checks
+    if (xAxis.getMin() == Double.POSITIVE_INFINITY || xAxis.getMax() == Double.POSITIVE_INFINITY) {
+      throw new IllegalArgumentException(
+          "Series data (accounting for error bars too) cannot be equal to Double.POSITIVE_INFINITY!!!");
+    }
+    for (Axis<ST, S> ya : yAxisMap.values()) {
+      if (ya.getMin() == Double.POSITIVE_INFINITY || ya.getMax() == Double.POSITIVE_INFINITY) {
+        throw new IllegalArgumentException(
+            "Series data (accounting for error bars too) cannot be equal to Double.POSITIVE_INFINITY!!!");
+      }
+      if (ya.getMin() == Double.NEGATIVE_INFINITY || ya.getMax() == Double.NEGATIVE_INFINITY) {
+        throw new IllegalArgumentException(
+            "Series data (accounting for error bars too) cannot be equal to Double.NEGATIVE_INFINITY!!!");
+      }
+    }
+
+    if (xAxis.getMin() == Double.NEGATIVE_INFINITY || xAxis.getMax() == Double.NEGATIVE_INFINITY) {
+      throw new IllegalArgumentException(
+          "Series data (accounting for error bars too) cannot be equal to Double.NEGATIVE_INFINITY!!!");
+    }
+  }
+
+  /**
+   * Sets a default minimum and maximum on all axes, for cases where there are no series to compute
+   * a range from.
+   */
+  private void setDefaultAxisMinMax() {
+    double xMin = chart.getStyler().isXAxisLogarithmic() ? 0.1 : -1.0;
+    double yMin = chart.getStyler().isYAxisLogarithmic() ? 0.1 : -1.0;
+    xAxis.addMinMax(xMin, 1);
+    for (Axis<ST, S> ya : yAxisMap.values()) {
+      ya.addMinMax(yMin, 1);
+    }
+  }
+
+  Axis<ST, S> getYAxis(int yIndex) {
+
+    return yAxisMap.get(yIndex);
+  }
+
+  /** Here we can add special case min max calculations and take care of manual min max settings. */
+  private void overrideMinMaxForXAxis() {
+
+    double overrideXAxisMinValue = xAxis.getMin();
+    double overrideXAxisMaxValue = xAxis.getMax();
+    // override min and maxValue if specified
+    if (chart.getStyler().getXAxisMin() != null) {
+
+      overrideXAxisMinValue = chart.getStyler().getXAxisMin();
+    }
+    if (chart.getStyler().getXAxisMax() != null) {
+
+      overrideXAxisMaxValue = chart.getStyler().getXAxisMax();
+    }
+    xAxis.setMin(overrideXAxisMinValue);
+    xAxis.setMax(overrideXAxisMaxValue);
+  }
+
+  private void overrideMinMaxForYAxis(Axis yAxis) {
+
+    double overrideYAxisMinValue = yAxis.getMin();
+    double overrideYAxisMaxValue = yAxis.getMax();
+
+    if (chart.getStyler() instanceof CategoryStyler) {
+
+      CategoryStyler categoryStyler = (CategoryStyler) chart.getStyler();
+      if (categoryStyler.getDefaultSeriesRenderStyle() == CategorySeriesRenderStyle.Bar
+          || categoryStyler.getDefaultSeriesRenderStyle() == CategorySeriesRenderStyle.Stick) {
+
+        // if stacked, we need to completely re-calculate min and max.
+        if (categoryStyler.isStacked()) {
+
+          AxesChartSeriesCategory axesChartSeries =
+              (AxesChartSeriesCategory) chart.getSeriesMap().values().iterator().next();
+          List<?> categories = (List<?>) axesChartSeries.getXData();
+
+          int numCategories = categories.size();
+          double[] accumulatedStackOffsetPos = new double[numCategories];
+          double[] accumulatedStackOffsetNeg = new double[numCategories];
+
+          for (S series : chart.getSeriesMap().values()) {
+
+            AxesChartSeriesCategory axesChartSeriesCategory = (AxesChartSeriesCategory) series;
+
+            if (!series.isEnabled()) {
+              continue;
+            }
+
+            int categoryCounter = 0;
+            Iterator<? extends Number> yItr = axesChartSeriesCategory.getYData().iterator();
+            while (yItr.hasNext()) {
+
+              Number next = yItr.next();
+              // skip when a value is null
+              if (next == null) {
+                categoryCounter++;
+                continue;
+              }
+
+              if (next.doubleValue() > 0) {
+                accumulatedStackOffsetPos[categoryCounter] += next.doubleValue();
+              } else if (next.doubleValue() < 0) {
+                accumulatedStackOffsetNeg[categoryCounter] += next.doubleValue();
+              }
+              categoryCounter++;
+            }
+          }
+
+          double max = accumulatedStackOffsetPos[0];
+          for (int i = 1; i < accumulatedStackOffsetPos.length; i++) {
+            if (accumulatedStackOffsetPos[i] > max) {
+              max = accumulatedStackOffsetPos[i];
+            }
+          }
+
+          double min = accumulatedStackOffsetNeg[0];
+          for (int i = 1; i < accumulatedStackOffsetNeg.length; i++) {
+            if (accumulatedStackOffsetNeg[i] < min) {
+              min = accumulatedStackOffsetNeg[i];
+            }
+          }
+
+          overrideYAxisMaxValue = max;
+          overrideYAxisMinValue = min;
+          // System.out.println("overrideYAxisMaxValue: " + overrideYAxisMaxValue);
+          // System.out.println("overrideYAxisMinValue: " + overrideYAxisMinValue);
+        }
+
+        // override min/max value for bar charts' Y-Axis
+        // There is a special case where it's desired to anchor the axis min or max to zero, like in
+        // the case of bar and stick charts.
+        if (yAxis.getMin() > 0.0) {
+          overrideYAxisMinValue = 0.0;
+        }
+        if (yAxis.getMax() < 0.0) {
+          overrideYAxisMaxValue = 0.0;
+        }
+      }
+    }
+
+    // override min and maxValue if specified
+
+    if (!(chart.getStyler() instanceof BoxStyler)) {
+
+      // min
+      if (chart.getStyler().getYAxisMin(yAxis.getYIndex()) != null) {
+        overrideYAxisMinValue = chart.getStyler().getYAxisMin(yAxis.getYIndex());
+      } else if (chart.getStyler().getYAxisMin() != null) {
+        overrideYAxisMinValue = chart.getStyler().getYAxisMin();
+      }
+      // max
+      if (chart.getStyler().getYAxisMax(yAxis.getYIndex()) != null) {
+        overrideYAxisMaxValue = chart.getStyler().getYAxisMax(yAxis.getYIndex());
+      } else if (chart.getStyler().getYAxisMax() != null) {
+        overrideYAxisMaxValue = chart.getStyler().getYAxisMax();
+      }
+    }
+
+    yAxis.setMin(overrideYAxisMinValue);
+    yAxis.setMax(overrideYAxisMaxValue);
+  }
+
+  // Getters & Setters /////////////////////////////////////////////////
+
+  public Axis<ST, S> getXAxis() {
+
+    return xAxis;
+  }
+
+  Axis<ST, S> getYAxis() {
+
+    return yAxis;
+  }
+
+  @Override
+  public Rectangle2D getBounds() {
+
+    return null; // should never be called
+  }
+
+  Rectangle2D.Double getLeftYAxisBounds() {
+
+    return leftYAxisBounds;
+  }
+
+  Rectangle2D.Double getRightYAxisBounds() {
+
+    return rightYAxisBounds;
+  }
+
+  Axis<ST, S> getLeftMainYAxis() {
+
+    return leftMainYAxis;
+  }
+
+  Axis<ST, S> getRightMainYAxis() {
+
+    return rightMainYAxis;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/AxisTick.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/AxisTick.java
new file mode 100644
index 0000000000000000000000000000000000000000..3fdc4e895aad0b3f3660db0d3b07166e39ac989a
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/AxisTick.java
@@ -0,0 +1,92 @@
+package org.knowm.xchart.internal.chartpart;
+
+import java.awt.*;
+import java.awt.geom.Rectangle2D;
+import org.knowm.xchart.internal.chartpart.Axis.Direction;
+import org.knowm.xchart.internal.series.AxesChartSeries;
+import org.knowm.xchart.style.AxesChartStyler;
+
+/** An axis tick */
+public class AxisTick<ST extends AxesChartStyler, S extends AxesChartSeries> implements ChartPart {
+
+  private final Chart<ST, S> chart;
+  private final Direction direction;
+
+  /** the axisticklabels */
+  private final AxisTickLabels<ST, S> axisTickLabels;
+
+  /** the axistickmarks */
+  private final AxisTickMarks<ST, S> axisTickMarks;
+
+  private Rectangle2D bounds;
+
+  /**
+   * Constructor
+   *
+   * @param chart
+   * @param direction
+   * @param yAxis
+   */
+  AxisTick(Chart<ST, S> chart, Direction direction, Axis yAxis) {
+
+    this.chart = chart;
+    this.direction = direction;
+    axisTickLabels = new AxisTickLabels<ST, S>(chart, direction, yAxis);
+    axisTickMarks = new AxisTickMarks<ST, S>(chart, direction, yAxis);
+  }
+
+  @Override
+  public Rectangle2D getBounds() {
+
+    return bounds;
+  }
+
+  @Override
+  public void paint(Graphics2D g) {
+
+    if (direction == Axis.Direction.Y && chart.getStyler().isYAxisTicksVisible()) {
+
+      axisTickLabels.paint(g);
+      axisTickMarks.paint(g);
+
+      bounds =
+          new Rectangle2D.Double(
+              axisTickLabels.getBounds().getX(),
+              axisTickLabels.getBounds().getY(),
+              axisTickLabels.getBounds().getWidth()
+                  + chart.getStyler().getAxisTickPadding()
+                  + axisTickMarks.getBounds().getWidth(),
+              axisTickMarks.getBounds().getHeight());
+
+      // g.setColor(Color.red);
+      // g.draw(bounds);
+
+    } else if (direction == Axis.Direction.X && chart.getStyler().isXAxisTicksVisible()) {
+
+      axisTickLabels.paint(g);
+      axisTickMarks.paint(g);
+
+      bounds =
+          new Rectangle2D.Double(
+              axisTickMarks.getBounds().getX(),
+              axisTickMarks.getBounds().getY(),
+              axisTickLabels.getBounds().getWidth(),
+              axisTickMarks.getBounds().getHeight()
+                  + chart.getStyler().getAxisTickPadding()
+                  + axisTickLabels.getBounds().getHeight());
+
+      // g.setColor(Color.red);
+      // g.draw(bounds);
+
+    } else {
+      bounds = new Rectangle2D.Double();
+    }
+  }
+
+  // Getters /////////////////////////////////////////////////
+
+  AxisTickLabels<ST, S> getAxisTickLabels() {
+
+    return axisTickLabels;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/AxisTickCalculator.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/AxisTickCalculator.java
new file mode 100644
index 0000000000000000000000000000000000000000..cf60abdeb40577c6becb93ed3ce5abcfd7abac10
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/AxisTickCalculator.java
@@ -0,0 +1,12 @@
+package org.knowm.xchart.internal.chartpart;
+
+import java.text.Format;
+import java.util.List;
+
+public interface AxisTickCalculator {
+  List<Double> getTickLocations();
+
+  List<String> getTickLabels();
+
+  Format getAxisFormat();
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/AxisTickCalculator_.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/AxisTickCalculator_.java
new file mode 100644
index 0000000000000000000000000000000000000000..5af53f95c187a7cee5b3bc705ba9e3fbcf7b186f
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/AxisTickCalculator_.java
@@ -0,0 +1,435 @@
+package org.knowm.xchart.internal.chartpart;
+
+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.math.BigDecimal;
+import java.math.MathContext;
+import java.math.RoundingMode;
+import java.text.*;
+import java.util.*;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import org.knowm.xchart.internal.Utils;
+import org.knowm.xchart.internal.chartpart.Axis.Direction;
+import org.knowm.xchart.style.AxesChartStyler;
+
+public abstract class AxisTickCalculator_ implements AxisTickCalculator {
+
+  /** the List of tick label position in pixels */
+  final List<Double> tickLocations = new LinkedList<>();
+
+  /** the List of tick label values */
+  final List<String> tickLabels = new LinkedList<>();
+
+  final Direction axisDirection;
+
+  final double workingSpace;
+
+  final double minValue;
+
+  final double maxValue;
+
+  List<Double> axisValues;
+
+  final AxesChartStyler styler;
+
+  Format axisFormat;
+
+  /**
+   * Constructor
+   *
+   * @param axisDirection
+   * @param workingSpace
+   * @param minValue
+   * @param maxValue
+   * @param styler
+   */
+  AxisTickCalculator_(
+      Direction axisDirection,
+      double workingSpace,
+      double minValue,
+      double maxValue,
+      AxesChartStyler styler) {
+
+    this.axisDirection = axisDirection;
+    this.workingSpace = workingSpace;
+    this.minValue = minValue;
+    this.maxValue = maxValue;
+    this.styler = styler;
+  }
+
+  AxisTickCalculator_(
+      Direction axisDirection,
+      double workingSpace,
+      double minValue,
+      double maxValue,
+      List<Double> axisValues,
+      AxesChartStyler styler) {
+    this.axisDirection = axisDirection;
+    this.workingSpace = workingSpace;
+    Set<Double> axisValuesWithMinMax = new LinkedHashSet<>();
+    axisValuesWithMinMax.add(minValue);
+    axisValuesWithMinMax.addAll(axisValues);
+    axisValuesWithMinMax.add(maxValue);
+    this.axisValues = new ArrayList<>(axisValuesWithMinMax);
+    this.minValue = minValue;
+    this.maxValue = maxValue;
+    this.styler = styler;
+  }
+
+  /**
+   * Gets the first position
+   *
+   * @param gridStep
+   * @return
+   */
+  double getFirstPosition(double gridStep) {
+
+    // System.out.println("******");
+
+    //    System.out.println("minValue = " + minValue);
+    //    System.out.println("(minValue % gridStep) = " + (minValue % gridStep));
+
+    return minValue - (minValue % gridStep) - gridStep;
+  }
+
+  // TODO make these non-public??
+  public List<Double> getTickLocations() {
+
+    return tickLocations;
+  }
+
+  public List<String> getTickLabels() {
+
+    return tickLabels;
+  }
+
+  /**
+   * Given the generated tickLabels, will they fit side-by-side without overlapping each other and
+   * looking bad? Sometimes the given tickSpacingHint is simply too small.
+   *
+   * @param tickLabels
+   * @param tickSpacingHint
+   * @return
+   */
+  boolean willLabelsFitInTickSpaceHint(List<String> tickLabels, int tickSpacingHint) {
+
+    String sampleLabel = "Y";
+    if (Direction.X.equals(this.axisDirection)) {
+      // find the longest String in all the labels
+      for (String tickLabel : tickLabels) {
+        if (tickLabel != null && tickLabel.length() > sampleLabel.length()) {
+          sampleLabel = tickLabel;
+        }
+      }
+    }
+    // System.out.println("longestLabel: " + sampleLabel);
+
+    TextLayout textLayout =
+        new TextLayout(
+            sampleLabel, styler.getAxisTickLabelsFont(), new FontRenderContext(null, true, false));
+    AffineTransform rot =
+        styler.getXAxisLabelRotation() == 0
+            ? null
+            : AffineTransform.getRotateInstance(
+                -1 * Math.toRadians(styler.getXAxisLabelRotation()));
+    Shape shape = textLayout.getOutline(rot);
+    Rectangle2D rectangle = shape.getBounds();
+    double largestLabelWidth =
+        Direction.X.equals(this.axisDirection) ? rectangle.getWidth() : rectangle.getHeight();
+    // System.out.println("largestLabelWidth: " + largestLabelWidth);
+    // System.out.println("tickSpacingHint: " + tickSpacingHint);
+
+    // if (largestLabelWidth * 1.1 >= tickSpacingHint) {
+    // System.out.println("WILL NOT FIT!!!");
+    // }
+
+    return (largestLabelWidth * 1.1 < tickSpacingHint);
+  }
+
+  public Format getAxisFormat() {
+
+    return axisFormat;
+  }
+
+  protected void calculate() {
+
+    // System.out.println("calculate");
+
+    // a check if all axis data are the exact same values
+    if (minValue == maxValue) {
+      tickLabels.add(getAxisFormat().format(BigDecimal.valueOf(maxValue).doubleValue()));
+      tickLocations.add(workingSpace / 2.0);
+      return;
+    }
+
+    // a check for no data
+    if (minValue > maxValue && minValue == Double.MAX_VALUE) {
+      tickLabels.add(getAxisFormat().format(0.0));
+      tickLocations.add(workingSpace / 2.0);
+      return;
+    }
+
+    // tick space - a percentage of the working space available for ticks
+    double tickSpace = styler.getPlotContentSize() * workingSpace; // in plot space
+
+    // this prevents an infinite loop when the plot gets sized really small.
+    if (axisDirection == Direction.X && tickSpace < styler.getXAxisTickMarkSpacingHint()) {
+      return;
+    }
+    if (axisDirection == Direction.Y && tickSpace < styler.getYAxisTickMarkSpacingHint()) {
+      return;
+    }
+
+    // this prevents an infinite loop when the axis number formatter "rounds" all axis ticks to the
+    // same value i.e. "#0.00" for 0.0, 0.0001, 0.0002
+    // issue #582
+    if (isNumberFormatChoppingDecimals(maxValue, minValue)) {
+      //      System.out.println("returning");
+      return;
+    }
+
+    // where the tick should begin in the working space in pixels
+    double margin =
+        Utils.getTickStartOffset(
+            workingSpace,
+            tickSpace); // in plot space double gridStep = getGridStepForDecimal(tickSpace);
+    // the span of the data
+    double span = Math.abs(Math.min((maxValue - minValue), Double.MAX_VALUE - 1)); // in data space
+
+    if (axisValues != null && areValuesEquallySpaced(axisValues)) {
+      calculateForEquallySpacedAxisValues(tickSpace, margin);
+      return;
+    }
+
+    //////////////////////////
+
+    int tickSpacingHint =
+        (axisDirection == Direction.X
+                ? styler.getXAxisTickMarkSpacingHint()
+                : styler.getYAxisTickMarkSpacingHint())
+            - 5;
+
+    // for very short plots, squeeze some more ticks in than normal into the Y-Axis
+    if (axisDirection == Direction.Y && tickSpace < 160) {
+      tickSpacingHint = 25 - 5;
+    }
+
+    int gridStepInChartSpace;
+
+    do {
+
+      //       System.out.println("calculating ticks...");
+      tickLabels.clear();
+      tickLocations.clear();
+
+      tickSpacingHint += 5;
+
+      // System.out.println("tickSpacingHint: " + tickSpacingHint);
+
+      // gridStepHint --> significand * 10 ** exponent
+      // e.g. 724.1 --> 7.241 * 10 ** 2
+      double significand = span / tickSpace * tickSpacingHint;
+      int exponent = 0;
+      if (significand == 0) {
+        exponent = 1;
+      } else if (significand < 1) {
+        while (significand < 1) {
+          significand *= 10.0;
+          exponent--;
+        }
+      } else {
+        while (significand >= 10 || significand == Double.NEGATIVE_INFINITY) {
+          significand /= 10.0;
+          exponent++;
+        }
+      }
+
+      // calculate the grid step width hint.
+      double gridStep;
+      if (significand > 7.5) {
+        // gridStep = 10.0 * 10 ** exponent
+        gridStep = 10.0 * Utils.pow(10, exponent);
+      } else if (significand > 3.5) {
+        // gridStep = 5.0 * 10 ** exponent
+        gridStep = 5.0 * Utils.pow(10, exponent);
+      } else if (significand > 1.5) {
+        // gridStep = 2.0 * 10 ** exponent
+        gridStep = 2.0 * Utils.pow(10, exponent);
+      } else {
+        // gridStep = 1.0 * 10 ** exponent
+        gridStep = Utils.pow(10, exponent);
+      }
+
+      //////////////////////////
+      // System.out.println("******************");
+      // System.out.println("gridStep: " + gridStep);
+      // System.out.println("***gridStepInChartSpace: " + gridStep / span * tickSpace);
+      gridStepInChartSpace = (int) (gridStep / span * tickSpace);
+      // System.out.println("gridStepInChartSpace: " + gridStepInChartSpace);
+      BigDecimal gridStepBigDecimal = new BigDecimal(gridStep, MathContext.DECIMAL64);
+      // BigDecimal gridStepBigDecimal = BigDecimal.valueOf(gridStep);
+      int scale = Math.min(10, gridStepBigDecimal.scale());
+      // int scale = gridStepBigDecimal.scale();
+      // System.out.println("scale: " + scale);
+      // int scale = gridStepBigDecimal.scale();
+      BigDecimal cleanedGridStep0 =
+          gridStepBigDecimal
+              .setScale(scale, RoundingMode.HALF_UP)
+              .stripTrailingZeros(); // chop off any double imprecision
+      BigDecimal cleanedGridStep =
+          cleanedGridStep0
+              .setScale(scale, RoundingMode.HALF_DOWN)
+              .stripTrailingZeros(); // chop off any double imprecision
+      // System.out.println("cleanedGridStep: " + cleanedGridStep);
+
+      BigDecimal firstPosition = null;
+      double firstPositionAsDouble = getFirstPosition(cleanedGridStep.doubleValue());
+      if (Double.isNaN(firstPositionAsDouble)) {
+        // This happens when the data values are almost the same but differ by a very tiny amount.
+        // The solution for now is to create a single axis label and tick at the average value
+        tickLabels.add(getAxisFormat().format(BigDecimal.valueOf((maxValue + minValue) / 2.0)));
+        double averageValue = (maxValue + minValue) / 2.0;
+        tickLocations.add(workingSpace / 2.0);
+        return;
+      } else if (firstPositionAsDouble == Double.NEGATIVE_INFINITY) {
+        firstPosition = BigDecimal.valueOf(-1 * Double.MAX_VALUE);
+      } else {
+        try {
+          firstPosition = BigDecimal.valueOf(firstPositionAsDouble);
+        } catch (java.lang.NumberFormatException e) {
+
+          System.out.println(
+              "Some debug stuff. This happens once in a blue moon, and I don't know why.");
+          System.out.println("scale: " + scale);
+          System.out.println("exponent: " + exponent);
+          System.out.println("gridStep: " + gridStep);
+          System.out.println("cleanedGridStep: " + cleanedGridStep);
+          System.out.println("cleanedGridStep.doubleValue(): " + cleanedGridStep.doubleValue());
+          System.out.println(
+              "NumberFormatException caused by this number: "
+                  + getFirstPosition(cleanedGridStep.doubleValue()));
+        }
+      }
+
+      // System.out.println("firstPosition: " + firstPosition); // chop off any double imprecision
+      BigDecimal cleanedFirstPosition =
+          firstPosition
+              .setScale(10, RoundingMode.HALF_UP)
+              .stripTrailingZeros(); // chop off any double imprecision
+      //      System.out.println("cleanedFirstPosition: " + cleanedFirstPosition);
+
+      // generate all tickLabels and tickLocations from the first to last position
+      for (BigDecimal value = cleanedFirstPosition;
+          value.compareTo(
+                  BigDecimal.valueOf(
+                      (maxValue + 2 * cleanedGridStep.doubleValue()) == Double.POSITIVE_INFINITY
+                          ? Double.MAX_VALUE
+                          : maxValue + 2 * cleanedGridStep.doubleValue()))
+              < 0;
+          value = value.add(cleanedGridStep)) {
+
+        // if (value.compareTo(BigDecimal.valueOf(maxValue)) <= 0 &&
+        // value.compareTo(BigDecimal.valueOf(minValue)) >= 0) {
+        // System.out.println(value);
+        String tickLabel = getAxisFormat().format(value.doubleValue());
+        // System.out.println(tickLabel);
+        tickLabels.add(tickLabel);
+
+        // here we convert tickPosition finally to plot space, i.e. pixels
+        double tickLabelPosition =
+            margin + ((value.doubleValue() - minValue) / (maxValue - minValue) * tickSpace);
+        tickLocations.add(tickLabelPosition);
+        // }
+
+      }
+    } while (!areAllTickLabelsUnique(tickLabels)
+        || !willLabelsFitInTickSpaceHint(tickLabels, gridStepInChartSpace));
+  }
+
+  private boolean areValuesEquallySpaced(List<Double> values) {
+    if (values.size() < 2) {
+      return false;
+    }
+    double space = values.get(1) - values.get(0);
+    double threshold = .0001;
+    if (threshold > Math.abs(maxValue - minValue)) {
+      return false;
+    }
+    return IntStream.range(1, values.size())
+        .mapToDouble(i -> values.get(i) - values.get(i - 1))
+        .allMatch(x -> Math.abs(x - space) < threshold);
+  }
+
+  /**
+   * Calculates the ticks so that they only appear at positions where data is available.
+   *
+   * @param tickSpace a percentage of the working space available for ticks
+   * @param margin where the tick should begin in the working space in pixels
+   */
+  private void calculateForEquallySpacedAxisValues(double tickSpace, double margin) {
+    if (axisValues == null) {
+      throw new IllegalStateException("No axis values.");
+    }
+    int gridStepInChartSpace;
+    int tickValuesHint = 0;
+    List<Double> tickLabelValues;
+    double tickLabelMaxValue;
+    double tickLabelMinValue;
+    do {
+      tickValuesHint++;
+      tickLabels.clear();
+      int finalTickValuesHint = tickValuesHint;
+      tickLabelValues =
+          IntStream.range(0, axisValues.size())
+              .filter(it -> it % finalTickValuesHint == 0)
+              .mapToDouble(axisValues::get)
+              .boxed()
+              .collect(Collectors.toList());
+      tickLabelMaxValue = tickLabelValues.stream().mapToDouble(x -> x).max().orElse(maxValue);
+      tickLabelMinValue = tickLabelValues.stream().mapToDouble(x -> x).min().orElse(minValue);
+      tickLabels.addAll(
+          tickLabelValues.stream()
+              .map(x -> getAxisFormat().format(x))
+              .collect(Collectors.toList()));
+      // the span of the data
+      double span =
+          Math.abs(
+              Math.min(
+                  (tickLabelMaxValue - tickLabelMinValue), Double.MAX_VALUE - 1)); // in data space
+      double gridStep = span / (tickLabelValues.size() - 1);
+
+      gridStepInChartSpace = (int) (gridStep / span * tickSpace);
+    } while (!areAllTickLabelsUnique(tickLabels)
+        || !willLabelsFitInTickSpaceHint(tickLabels, gridStepInChartSpace));
+
+    tickLocations.clear();
+    tickLocations.addAll(
+        tickLabelValues.stream()
+            .map(value -> margin + ((value - minValue) / (maxValue - minValue) * tickSpace))
+            .collect(Collectors.toList()));
+  }
+
+  boolean areAllTickLabelsUnique(List<?> tickLabels) {
+    return new LinkedHashSet<>(tickLabels).size() == tickLabels.size();
+  }
+
+  private boolean isNumberFormatChoppingDecimals(double axisMax, double axisMin) {
+
+    //        System.out.println("axisMax = " + axisMax);
+    //        System.out.println("axisMin = " + axisMin);
+    String formattedMaxValue = getAxisFormat().format(axisMax);
+    //        System.out.println("formattedMaxValue = " + formattedMaxValue);
+    String formattedMinValue = getAxisFormat().format(axisMin);
+    //        System.out.println("formattedMinValue = " + formattedMinValue);
+    // if formatted number lost its decimals due to formatter
+    if (formattedMaxValue.equals(formattedMinValue)) {
+      return true;
+    }
+    return false;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/AxisTickCalculator_Callback.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/AxisTickCalculator_Callback.java
new file mode 100644
index 0000000000000000000000000000000000000000..26d736a0a264ece902d2de8ff7e63419131c8870
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/AxisTickCalculator_Callback.java
@@ -0,0 +1,48 @@
+package org.knowm.xchart.internal.chartpart;
+
+import java.util.List;
+import java.util.function.Function;
+import org.knowm.xchart.internal.chartpart.Axis.Direction;
+import org.knowm.xchart.style.AxesChartStyler;
+
+/**
+ * This class encapsulates the logic to generate the axis tick mark and axis tick label data for
+ * rendering the axis ticks for custom axes
+ */
+class AxisTickCalculator_Callback extends AxisTickCalculator_ {
+
+  /**
+   * Constructor
+   *
+   * @param axisDirection
+   * @param workingSpace
+   * @param minValue
+   * @param maxValue
+   * @param styler
+   */
+  public AxisTickCalculator_Callback(
+      Function<Double, String> formattingCallback,
+      Direction axisDirection,
+      double workingSpace,
+      double minValue,
+      double maxValue,
+      AxesChartStyler styler) {
+
+    super(axisDirection, workingSpace, minValue, maxValue, styler);
+    axisFormat = new Formatter_Custom(formattingCallback);
+    calculate();
+  }
+
+  AxisTickCalculator_Callback(
+      Function<Double, String> formattingCallback,
+      Direction axisDirection,
+      double workingSpace,
+      double minValue,
+      double maxValue,
+      List<Double> axisValues,
+      AxesChartStyler styler) {
+    super(axisDirection, workingSpace, minValue, maxValue, axisValues, styler);
+    axisFormat = new Formatter_Custom(formattingCallback);
+    calculate();
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/AxisTickCalculator_Category.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/AxisTickCalculator_Category.java
new file mode 100644
index 0000000000000000000000000000000000000000..5532f81d928c2a59fccac0ca2b8ae081c8f98c31
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/AxisTickCalculator_Category.java
@@ -0,0 +1,111 @@
+package org.knowm.xchart.internal.chartpart;
+
+import java.math.BigDecimal;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import org.knowm.xchart.internal.Utils;
+import org.knowm.xchart.internal.chartpart.Axis.Direction;
+import org.knowm.xchart.internal.series.Series;
+import org.knowm.xchart.style.AxesChartStyler;
+
+/**
+ * This class encapsulates the logic to generate the axis tick mark and axis tick label data for
+ * rendering the axis ticks for String axes
+ */
+class AxisTickCalculator_Category extends AxisTickCalculator_ {
+
+  /**
+   * Constructor
+   *
+   * @param axisDirection
+   * @param workingSpace
+   * @param categories
+   * @param axisType
+   * @param styler
+   */
+  public AxisTickCalculator_Category(
+      Direction axisDirection,
+      double workingSpace,
+      List<?> categories,
+      Series.DataType axisType,
+      AxesChartStyler styler) {
+
+    super(axisDirection, workingSpace, Double.NaN, Double.NaN, styler);
+
+    calculate(categories, axisType);
+  }
+
+  private void calculate(List<?> categories, Series.DataType axisType) {
+
+    // tick space - a percentage of the working space available for ticks
+    double tickSpace = styler.getPlotContentSize() * workingSpace; // in plot space
+    // System.out.println("workingSpace: " + workingSpace);
+    // System.out.println("tickSpace: " + tickSpace);
+
+    // where the tick should begin in the working space in pixels
+    double margin = Utils.getTickStartOffset(workingSpace, tickSpace);
+    // System.out.println("Margin: " + margin);
+
+    // generate all tickLabels and tickLocations from the first to last position
+    double gridStep = (tickSpace / categories.size());
+    // System.out.println("GridStep: " + gridStep);
+    double firstPosition = gridStep / 2.0;
+
+    // Compute the spacing between categories when there are more than wanted
+
+    int xAxisMaxLabelCount = styler.getXAxisMaxLabelCount();
+
+    if (xAxisMaxLabelCount == 1) {
+      throw new IllegalArgumentException("Unsupported max label count equal to 1");
+    }
+
+    if (0 < xAxisMaxLabelCount && xAxisMaxLabelCount < categories.size()) {
+      List<Object> sparseCategories = new ArrayList<>();
+      double step = categories.size() / (double) (xAxisMaxLabelCount - 1);
+      for (double stepIdx = 0; Math.round(stepIdx) < categories.size(); stepIdx += step) {
+        int idx = (int) Math.round(stepIdx);
+        Object label = categories.get(idx);
+        sparseCategories.add(label);
+      }
+
+      Object lastLabel = categories.get(categories.size() - 1);
+      sparseCategories.add(lastLabel);
+      categories = sparseCategories;
+
+      gridStep = (tickSpace / (categories.size() - 1));
+      firstPosition = 0;
+    }
+
+    // set up String formatters that may be encountered
+    if (axisType == Series.DataType.String) {
+      axisFormat = new Formatter_String();
+    } else if (axisType == Series.DataType.Number) {
+      axisFormat = new Formatter_Number(styler, axisDirection, minValue, maxValue);
+    } else if (axisType == Series.DataType.Date) {
+      if (styler.getDatePattern() == null) {
+        throw new RuntimeException("You need to set the Date Formatting Pattern!!!");
+      }
+      SimpleDateFormat simpleDateformat =
+          new SimpleDateFormat(styler.getDatePattern(), styler.getLocale());
+      simpleDateformat.setTimeZone(styler.getTimezone());
+      axisFormat = simpleDateformat;
+    }
+
+    int counter = 0;
+
+    for (Object category : categories) {
+      if (axisType == Series.DataType.String) {
+        tickLabels.add(category.toString());
+      } else if (axisType == Series.DataType.Number) {
+        tickLabels.add(axisFormat.format(new BigDecimal(category.toString()).doubleValue()));
+      } else if (axisType == Series.DataType.Date) {
+        tickLabels.add(axisFormat.format((((Date) category).getTime())));
+      }
+
+      double tickLabelPosition = margin + firstPosition + gridStep * counter++;
+      tickLocations.add(tickLabelPosition);
+    }
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/AxisTickCalculator_Date.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/AxisTickCalculator_Date.java
new file mode 100644
index 0000000000000000000000000000000000000000..cc467f47f7bcfed7ff7e1ab8692a37ea93c40cd5
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/AxisTickCalculator_Date.java
@@ -0,0 +1,304 @@
+package org.knowm.xchart.internal.chartpart;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import org.knowm.xchart.internal.Utils;
+import org.knowm.xchart.internal.chartpart.Axis.Direction;
+import org.knowm.xchart.style.AxesChartStyler;
+
+/**
+ * This class encapsulates the logic to generate the axis tick mark and axis tick label data for
+ * rendering the axis ticks for date axes
+ */
+class AxisTickCalculator_Date extends AxisTickCalculator_ {
+
+  private static final long MILLIS_SCALE = TimeUnit.MILLISECONDS.toMillis(1L);
+  private static final long SEC_SCALE = TimeUnit.SECONDS.toMillis(1L);
+  private static final long MIN_SCALE = TimeUnit.MINUTES.toMillis(1L);
+  private static final long HOUR_SCALE = TimeUnit.HOURS.toMillis(1L);
+  private static final long DAY_SCALE = TimeUnit.DAYS.toMillis(1L);
+  private static final long MONTH_SCALE = TimeUnit.DAYS.toMillis(1L) * 30;
+  // private static final long QUARTER_SCALE = TimeUnit.DAYS.toMillis(1L) * 120;
+  private static final long YEAR_SCALE = TimeUnit.DAYS.toMillis(1L) * 365;
+
+  private static final List<TimeSpan> timeSpans = new ArrayList<>();
+
+  static {
+    timeSpans.add(new TimeSpan(MILLIS_SCALE, 1, "ss.SSS"));
+    timeSpans.add(new TimeSpan(MILLIS_SCALE, 2, "ss.SSS"));
+    timeSpans.add(new TimeSpan(MILLIS_SCALE, 5, "ss.SSS"));
+    timeSpans.add(new TimeSpan(MILLIS_SCALE, 10, "ss.SSS"));
+    timeSpans.add(new TimeSpan(MILLIS_SCALE, 50, "ss.SS"));
+    timeSpans.add(new TimeSpan(MILLIS_SCALE, 100, "ss.SS"));
+    timeSpans.add(new TimeSpan(MILLIS_SCALE, 200, "ss.SS"));
+    timeSpans.add(new TimeSpan(MILLIS_SCALE, 500, "ss.SS"));
+
+    timeSpans.add(new TimeSpan(SEC_SCALE, 1, "ss.SS"));
+    timeSpans.add(new TimeSpan(SEC_SCALE, 2, "ss.S"));
+    timeSpans.add(new TimeSpan(SEC_SCALE, 5, "ss.S"));
+    timeSpans.add(new TimeSpan(SEC_SCALE, 10, "HH:mm:ss"));
+    timeSpans.add(new TimeSpan(SEC_SCALE, 15, "HH:mm:ss"));
+    timeSpans.add(new TimeSpan(SEC_SCALE, 20, "HH:mm:ss"));
+    timeSpans.add(new TimeSpan(SEC_SCALE, 30, "HH:mm:ss"));
+
+    timeSpans.add(new TimeSpan(MIN_SCALE, 1, "HH:mm:ss"));
+    timeSpans.add(new TimeSpan(MIN_SCALE, 2, "HH:mm:ss"));
+    timeSpans.add(new TimeSpan(MIN_SCALE, 5, "HH:mm:ss"));
+    timeSpans.add(new TimeSpan(MIN_SCALE, 10, "HH:mm"));
+    timeSpans.add(new TimeSpan(MIN_SCALE, 15, "HH:mm"));
+    timeSpans.add(new TimeSpan(MIN_SCALE, 20, "HH:mm"));
+    timeSpans.add(new TimeSpan(MIN_SCALE, 30, "HH:mm"));
+
+    timeSpans.add(new TimeSpan(HOUR_SCALE, 1, "HH:mm"));
+    timeSpans.add(new TimeSpan(HOUR_SCALE, 2, "HH:mm"));
+    timeSpans.add(new TimeSpan(HOUR_SCALE, 4, "HH:mm"));
+    timeSpans.add(new TimeSpan(HOUR_SCALE, 8, "HH:mm"));
+    timeSpans.add(new TimeSpan(HOUR_SCALE, 12, "HH:mm"));
+
+    timeSpans.add(new TimeSpan(DAY_SCALE, 1, "EEE HH:mm"));
+    timeSpans.add(new TimeSpan(DAY_SCALE, 2, "EEE HH:mm"));
+    timeSpans.add(new TimeSpan(DAY_SCALE, 3, "EEE HH:mm"));
+    timeSpans.add(new TimeSpan(DAY_SCALE, 5, "MM-dd"));
+    timeSpans.add(new TimeSpan(DAY_SCALE, 10, "MM-dd"));
+    timeSpans.add(new TimeSpan(DAY_SCALE, 15, "MM-dd"));
+
+    timeSpans.add(new TimeSpan(MONTH_SCALE, 1, "MM-dd"));
+    timeSpans.add(new TimeSpan(MONTH_SCALE, 2, "MM-dd"));
+    timeSpans.add(new TimeSpan(MONTH_SCALE, 3, "MM-dd"));
+    timeSpans.add(new TimeSpan(MONTH_SCALE, 4, "MM-dd"));
+    timeSpans.add(new TimeSpan(MONTH_SCALE, 6, "yyyy-MM"));
+
+    timeSpans.add(new TimeSpan(YEAR_SCALE, 1, "yyyy-MM"));
+    timeSpans.add(new TimeSpan(YEAR_SCALE, 2, "yyyy-MM"));
+    timeSpans.add(new TimeSpan(YEAR_SCALE, 5, "yyyy"));
+    timeSpans.add(new TimeSpan(YEAR_SCALE, 10, "yyyy"));
+    timeSpans.add(new TimeSpan(YEAR_SCALE, 20, "yyyy"));
+    timeSpans.add(new TimeSpan(YEAR_SCALE, 100, "yyyy"));
+    timeSpans.add(new TimeSpan(YEAR_SCALE, 500, "yyyy"));
+    timeSpans.add(new TimeSpan(YEAR_SCALE, 1000, "yyyy"));
+  }
+
+  /**
+   * Constructor
+   *
+   * @param axisDirection
+   * @param workingSpace
+   * @param minValue
+   * @param maxValue
+   * @param styler
+   */
+  public AxisTickCalculator_Date(
+      Direction axisDirection,
+      double workingSpace,
+      double minValue,
+      double maxValue,
+      AxesChartStyler styler) {
+
+    super(axisDirection, workingSpace, minValue, maxValue, styler);
+
+    calculate();
+  }
+
+  @Override
+  protected void calculate() {
+
+    // tick space - a percentage of the working space available for ticks
+    double tickSpace = styler.getPlotContentSize() * workingSpace; // in plot space
+
+    // this prevents an infinite loop when the plot gets sized really small.
+    if (tickSpace < styler.getXAxisTickMarkSpacingHint()) {
+      // System.out.println("Returning!");
+      return;
+    }
+
+    // minValue & maxValue is not set
+    if (minValue > maxValue && minValue == Double.MAX_VALUE) {
+      String datePattern = timeSpans.get(0).getDatePattern();
+      if (styler.getDatePattern() != null) {
+        datePattern = styler.getDatePattern();
+      }
+
+      SimpleDateFormat simpleDateformat = new SimpleDateFormat(datePattern, styler.getLocale());
+      simpleDateformat.setTimeZone(styler.getTimezone());
+      axisFormat = simpleDateformat;
+
+      tickLabels.add(axisFormat.format(0.0));
+      tickLocations.add(workingSpace / 2.0);
+      return;
+    }
+
+    // where the tick should begin in the working space in pixels
+    double margin =
+        Utils.getTickStartOffset(
+            workingSpace,
+            tickSpace); // in plot space double gridStep = getGridStepForDecimal(tickSpace);
+
+    // the span of the data
+    long span = (long) Math.abs(maxValue - minValue); // in data space
+    // System.out.println("span: " + span);
+
+    // Generate the labels first, see if they "look" OK and reiterate with an increased
+    // tickSpacingHint
+    int tickSpacingHint =
+        (axisDirection == Direction.X
+                ? styler.getXAxisTickMarkSpacingHint()
+                : styler.getYAxisTickMarkSpacingHint())
+            - 5;
+    int gridStepInChartSpace;
+
+    // System.out.println("calculating ticks...");
+    long gridStepHint = (long) (span / tickSpace * tickSpacingHint); // in time units (ms)
+    // System.out.println("gridStepHint: " + gridStepHint);
+
+    //////////////////////////////////////////////
+
+    // iterate forward until the matching timespan is found
+    int index = 0;
+    for (int i = 0; i < timeSpans.size() - 1; i++) {
+
+      if (span
+          < ((timeSpans.get(i).getUnitAmount() * timeSpans.get(i).getMagnitude()
+                  + timeSpans.get(i + 1).getUnitAmount() * timeSpans.get(i + 1).getMagnitude())
+              / 2.0)) {
+        index = i;
+        break;
+      }
+    }
+    //    TimeSpan timeSpan1 = timeSpans.get(index);
+    //    System.out.println("timeSpan1 = " + timeSpan1);
+
+    // use the pattern from the first timeSpan
+    String datePattern = timeSpans.get(index).getDatePattern();
+    // System.out.println("index: " + index);
+
+    // iterate BACKWARDS from previous point until the appropriate timespan is found for the
+    // gridStepHint
+    for (int i = index - 1; i > 0; i--) {
+
+      if (gridStepHint > timeSpans.get(i).getUnitAmount() * timeSpans.get(i).getMagnitude()) {
+        index = i;
+        break;
+      }
+    }
+
+    //////////////////////////////////////////////
+
+    // now increase the timespan until one is found where all the labels fit nicely. It will often
+    // be the first one.
+    index--;
+    int fallbackindex = index;
+    boolean skip, force = false;
+    do {
+      skip = false;
+
+      tickLabels.clear();
+      tickLocations.clear();
+
+      double gridStep =
+          timeSpans.get(++index).getUnitAmount()
+              * timeSpans.get(index).getMagnitude(); // in time units (ms)
+
+      gridStepInChartSpace = (int) (gridStep / span * tickSpace);
+      if (gridStepInChartSpace < 10 && index < timeSpans.size() - 1) {
+        skip = true;
+        continue;
+      }
+      //      TimeSpan timeSpan2 = timeSpans.get(index);
+      //      System.out.println("timeSpan2 = " + timeSpan2);
+
+      // System.out.println("gridStepInChartSpace: " + gridStepInChartSpace);
+
+      double firstPosition = getFirstPosition(gridStep);
+      //      System.out.println("firstPosition = " + firstPosition);
+      //      System.out.println("   " + new Date((long) firstPosition).toGMTString());
+
+      // Define Date Pattern
+      // override pattern if one was explicitly given
+      if (styler.getDatePattern() != null) {
+        datePattern = styler.getDatePattern();
+      }
+      // System.out.println("datePattern: " + datePattern);
+
+      SimpleDateFormat simpleDateformat = new SimpleDateFormat(datePattern, styler.getLocale());
+      simpleDateformat.setTimeZone(styler.getTimezone());
+      //      simpleDateformat.setTimeZone(TimeZone.getTimeZone("UTC"));
+      axisFormat = simpleDateformat;
+
+      // generate all tickLabels and tickLocations from the first to last position
+      for (double value = firstPosition;
+          value <= maxValue + 2 * gridStep;
+          value = value + gridStep) {
+
+        tickLabels.add(axisFormat.format(value));
+        //        System.out.println("ticklabel date = " + new Date((long) value).toGMTString());
+        // here we convert tickPosition finally to plot space, i.e. pixels
+        double tickLabelPosition =
+            margin + ((value - minValue) / (maxValue - minValue) * tickSpace);
+        // System.out.println("tickLabelPosition: " + tickLabelPosition);
+        tickLocations.add(tickLabelPosition);
+        // }
+      }
+      //      System.out.println("************");
+      if (index >= timeSpans.size() - 1) {
+        // Nothing matches, as mentioned above, first one is usually the best fit, force it!
+        force = true;
+        index = fallbackindex;
+        continue; // We can't exit just yet, we need to do the calculations above again
+      }
+      if (force) break; // We don't care anymore if it's a match or not, just use it
+    } while (skip
+        || !areAllTickLabelsUnique(tickLabels)
+        || !willLabelsFitInTickSpaceHint(tickLabels, gridStepInChartSpace));
+    //    System.out.println("are ticklabels unique? " + areAllTickLabelsUnique(tickLabels));
+  }
+
+  static class TimeSpan {
+
+    private final long unitAmount;
+    private final int magnitude;
+    private final String datePattern;
+
+    /**
+     * Constructor
+     *
+     * @param unitAmount
+     * @param magnitude
+     * @param datePattern
+     */
+    public TimeSpan(long unitAmount, int magnitude, String datePattern) {
+
+      this.unitAmount = unitAmount;
+      this.magnitude = magnitude;
+      this.datePattern = datePattern;
+    }
+
+    public long getUnitAmount() {
+
+      return unitAmount;
+    }
+
+    public int getMagnitude() {
+
+      return magnitude;
+    }
+
+    public String getDatePattern() {
+
+      return datePattern;
+    }
+
+    @Override
+    public String toString() {
+
+      return "TimeSpan [unitAmount="
+          + unitAmount
+          + ", magnitude="
+          + magnitude
+          + ", datePattern="
+          + datePattern
+          + "]";
+    }
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/AxisTickCalculator_Logarithmic.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/AxisTickCalculator_Logarithmic.java
new file mode 100644
index 0000000000000000000000000000000000000000..74eb8864e68ea132e52c32f178e7ecdb7aa2a367
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/AxisTickCalculator_Logarithmic.java
@@ -0,0 +1,164 @@
+package org.knowm.xchart.internal.chartpart;
+
+import java.math.BigDecimal;
+import org.knowm.xchart.internal.Utils;
+import org.knowm.xchart.internal.chartpart.Axis.Direction;
+import org.knowm.xchart.style.AxesChartStyler;
+
+/**
+ * This class encapsulates the logic to generate the axis tick mark and axis tick label data for
+ * rendering the axis ticks for logarithmic axes
+ */
+class AxisTickCalculator_Logarithmic extends AxisTickCalculator_ {
+
+  private final Formatter_LogNumber formatterLogNumber;
+
+  /**
+   * Constructor
+   *
+   * @param axisDirection
+   * @param workingSpace
+   * @param minValue
+   * @param maxValue
+   * @param styler
+   */
+  public AxisTickCalculator_Logarithmic(
+      Direction axisDirection,
+      double workingSpace,
+      double minValue,
+      double maxValue,
+      AxesChartStyler styler) {
+
+    super(axisDirection, workingSpace, minValue, maxValue, styler);
+    formatterLogNumber = new Formatter_LogNumber(styler, axisDirection);
+    axisFormat = formatterLogNumber;
+    calculate();
+  }
+
+  /**
+   * Constructor
+   *
+   * @param axisDirection
+   * @param workingSpace
+   * @param minValue
+   * @param maxValue
+   * @param styler
+   * @param yIndex
+   */
+  public AxisTickCalculator_Logarithmic(
+      Direction axisDirection,
+      double workingSpace,
+      double minValue,
+      double maxValue,
+      AxesChartStyler styler,
+      int yIndex) {
+
+    super(axisDirection, workingSpace, minValue, maxValue, styler);
+    formatterLogNumber = new Formatter_LogNumber(styler, axisDirection, yIndex);
+    axisFormat = formatterLogNumber;
+    calculate();
+  }
+
+  @Override
+  protected void calculate() {
+
+    // a check if all axis data are the exact same values
+    if (minValue == maxValue) {
+      tickLabels.add(formatterLogNumber.format(BigDecimal.valueOf(maxValue).doubleValue()));
+      tickLocations.add(workingSpace / 2.0);
+      return;
+    }
+
+    // tick space - a percentage of the working space available for ticks
+    double tickSpace = styler.getPlotContentSize() * workingSpace; // in plot space
+
+    // this prevents an infinite loop when the plot gets sized really small.
+    if (tickSpace < styler.getXAxisTickMarkSpacingHint()) {
+      return;
+    }
+
+    // where the tick should begin in the working space in pixels
+    double margin =
+        Utils.getTickStartOffset(
+            workingSpace,
+            tickSpace); // in plot space double gridStep = getGridStepForDecimal(tickSpace);
+
+    // System.out.println("minValue: " + minValue);
+    // System.out.println("maxValue: " + maxValue);
+    int logMin = (int) Math.floor(Math.log10(minValue));
+    int logMax = (int) Math.ceil(Math.log10(maxValue));
+    // System.out.println("logMin: " + logMin);
+    // System.out.println("logMax: " + logMax);
+
+    // if (axisDirection == Direction.Y && styler.getYAxisMin() != null) {
+    // logMin = (int) (Math.log10(styler.getYAxisMin())); // no floor
+    // }
+    // if (axisDirection == Direction.Y && styler.getYAxisMax() != null) {
+    // logMax = (int) (Math.log10(styler.getYAxisMax())); // no floor
+    // }
+    // if (axisDirection == Direction.X && styler.getXAxisMin() != null) {
+    // logMin = (int) (Math.log10(styler.getXAxisMin())); // no floor
+    // }
+    // if (axisDirection == Direction.X && styler.getXAxisMax() != null) {
+    // logMax = (int) (Math.log10(styler.getXAxisMax())); // no floor
+    // }
+
+    int firstPosition = 1;
+    // System.out.println("firstPosition: " + firstPosition);
+    double tickStep = Utils.pow(10, logMin - 1);
+
+    boolean axisDecadeOnly =
+        (axisDirection == Direction.X)
+            ? styler.isXAxisLogarithmicDecadeOnly()
+            : styler.isYAxisLogarithmicDecadeOnly();
+
+    for (int i = logMin; i <= logMax; i++) { // for each decade
+
+      // System.out.println("tickStep: " + tickStep);
+      // System.out.println("firstPosition: " + firstPosition);
+      // System.out.println("i: " + i);
+      // System.out.println("Utils.pow(10, i): " + Utils.pow(10, i));
+
+      for (int j = firstPosition; j <= 10; j++) {
+        double tickValue = Math.pow(10, i) * j;
+
+        // System.out.println("tickValue: " + tickValue);
+        // System.out.println(Math.log10(tickValue) % 1);
+
+        if (tickValue < minValue - tickStep) {
+          // System.out.println("continue");
+          continue;
+        }
+
+        if (tickValue > maxValue + tickStep) {
+          // System.out.println("break");
+          break;
+        }
+
+        // only add labels for the decades
+        if (!axisDecadeOnly || j == 1 || j == 10) {
+          tickLabels.add(formatterLogNumber.format(tickValue));
+        } else {
+          tickLabels.add(null);
+        }
+
+        // add all the tick marks though
+        double tickLabelPosition =
+            (int)
+                (margin
+                    + (Math.log10(tickValue) - Math.log10(minValue))
+                        / (Math.log10(maxValue) - Math.log10(minValue))
+                        * tickSpace);
+        tickLocations.add(tickLabelPosition);
+      }
+      tickStep = Math.pow(10, i);
+      firstPosition = 2;
+    }
+    if (tickLocations.size() <= 1) {
+      tickLabels.add(formatterLogNumber.format(minValue));
+      tickLocations.add(margin);
+      tickLabels.add(formatterLogNumber.format(maxValue));
+      tickLocations.add(margin + tickSpace);
+    }
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/AxisTickCalculator_Number.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/AxisTickCalculator_Number.java
new file mode 100644
index 0000000000000000000000000000000000000000..e20103f64a869f56bdaf80e3ceeaf2fcf3295871
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/AxisTickCalculator_Number.java
@@ -0,0 +1,73 @@
+package org.knowm.xchart.internal.chartpart;
+
+import java.util.List;
+import org.knowm.xchart.internal.chartpart.Axis.Direction;
+import org.knowm.xchart.style.AxesChartStyler;
+
+/**
+ * This class encapsulates the logic to generate the axis tick mark and axis tick label data for
+ * rendering the axis ticks for decimal axes
+ */
+class AxisTickCalculator_Number extends AxisTickCalculator_ {
+
+  private final Formatter_Number formatterNumber;
+
+  /**
+   * Constructor
+   *
+   * @param axisDirection
+   * @param workingSpace
+   * @param minValue
+   * @param maxValue
+   * @param styler
+   */
+  public AxisTickCalculator_Number(
+      Direction axisDirection,
+      double workingSpace,
+      double minValue,
+      double maxValue,
+      AxesChartStyler styler) {
+
+    super(axisDirection, workingSpace, minValue, maxValue, styler);
+    formatterNumber = new Formatter_Number(styler, axisDirection, minValue, maxValue);
+    axisFormat = formatterNumber;
+    calculate();
+  }
+
+  AxisTickCalculator_Number(
+      Direction axisDirection,
+      double workingSpace,
+      double minValue,
+      double maxValue,
+      List<Double> axisValues,
+      AxesChartStyler styler) {
+    super(axisDirection, workingSpace, minValue, maxValue, axisValues, styler);
+    formatterNumber = new Formatter_Number(styler, axisDirection, minValue, maxValue);
+    axisFormat = formatterNumber;
+    calculate();
+  }
+
+  /**
+   * Constructor
+   *
+   * @param axisDirection
+   * @param workingSpace
+   * @param minValue
+   * @param maxValue
+   * @param styler
+   * @param yIndex
+   */
+  public AxisTickCalculator_Number(
+      Direction axisDirection,
+      double workingSpace,
+      double minValue,
+      double maxValue,
+      AxesChartStyler styler,
+      int yIndex) {
+
+    super(axisDirection, workingSpace, minValue, maxValue, styler);
+    formatterNumber = new Formatter_Number(styler, axisDirection, minValue, maxValue, yIndex);
+    axisFormat = formatterNumber;
+    calculate();
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/AxisTickLabels.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/AxisTickLabels.java
new file mode 100644
index 0000000000000000000000000000000000000000..cc27876956d04601dae378631367cfa424d14922
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/AxisTickLabels.java
@@ -0,0 +1,268 @@
+package org.knowm.xchart.internal.chartpart;
+
+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;
+import org.knowm.xchart.internal.chartpart.Axis.Direction;
+import org.knowm.xchart.internal.series.AxesChartSeries;
+import org.knowm.xchart.style.AxesChartStyler;
+import org.knowm.xchart.style.Styler.YAxisPosition;
+
+/** Axis tick labels */
+public class AxisTickLabels<ST extends AxesChartStyler, S extends AxesChartSeries>
+    implements ChartPart {
+
+  private final Chart<ST, S> chart;
+  private final Direction direction;
+  private final Axis yAxis;
+  private Rectangle2D bounds;
+
+  /**
+   * Constructor
+   *
+   * @param chart
+   * @param direction
+   */
+  AxisTickLabels(Chart<ST, S> chart, Direction direction, Axis yAxis) {
+
+    this.chart = chart;
+    this.direction = direction;
+    this.yAxis = yAxis;
+  }
+
+  @Override
+  public void paint(Graphics2D g) {
+
+    ST styler = chart.getStyler();
+    g.setFont(styler.getAxisTickLabelsFont());
+
+    if (direction == Axis.Direction.Y && styler.isYAxisTicksVisible()) { // Y-Axis
+
+      g.setColor(styler.getYAxisGroupTickLabelsColorMap(yAxis.getYIndex()));
+      boolean onRight = styler.getYAxisGroupPosistion(yAxis.getYIndex()) == YAxisPosition.Right;
+
+      double xOffset;
+      if (onRight) {
+        xOffset =
+            yAxis.getBounds().getX()
+                + (styler.isYAxisTicksVisible()
+                    ? styler.getAxisTickMarkLength() + styler.getAxisTickPadding()
+                    : 0);
+      } else {
+        double xWidth = yAxis.getAxisTitle().getBounds().getWidth();
+        xOffset = yAxis.getAxisTitle().getBounds().getX() + xWidth;
+      }
+      double yOffset = yAxis.getBounds().getY();
+      double height = yAxis.getBounds().getHeight();
+      double maxTickLabelWidth = 0;
+      Map<Double, TextLayout> axisLabelTextLayouts = new HashMap<Double, TextLayout>();
+
+      for (int i = 0; i < yAxis.getAxisTickCalculator().getTickLabels().size(); i++) {
+
+        String tickLabel = yAxis.getAxisTickCalculator().getTickLabels().get(i);
+        //         System.out.println("** " + tickLabel);
+        double tickLocation = yAxis.getAxisTickCalculator().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 axisLabelTextLayout =
+              new TextLayout(tickLabel, styler.getAxisTickLabelsFont(), frc);
+          Rectangle2D tickLabelBounds = axisLabelTextLayout.getBounds();
+          double boundWidth = tickLabelBounds.getWidth();
+          if (boundWidth > maxTickLabelWidth) {
+            maxTickLabelWidth = boundWidth;
+          }
+          axisLabelTextLayouts.put(tickLocation, axisLabelTextLayout);
+        }
+      }
+
+      for (Map.Entry<Double, TextLayout> tick : axisLabelTextLayouts.entrySet()) {
+        final Double tickLocation = tick.getKey();
+        final TextLayout axisLabelTextLayout = tick.getValue();
+
+        Shape shape = axisLabelTextLayout.getOutline(null);
+        Rectangle2D tickLabelBounds = shape.getBounds();
+
+        double flippedTickLocation = yOffset + height - tickLocation;
+
+        AffineTransform orig = g.getTransform();
+        AffineTransform at = new AffineTransform();
+        double boundWidth = tickLabelBounds.getWidth();
+        double xPos;
+        switch (styler.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
+      bounds = new Rectangle2D.Double(xOffset, yOffset, maxTickLabelWidth, height);
+      // g.setColor(Color.blue);
+      // g.draw(bounds);
+
+    }
+    // X-Axis
+    else if (direction == Axis.Direction.X && styler.isXAxisTicksVisible()) {
+
+      g.setColor(styler.getXAxisTickLabelsColor());
+      double xOffset = chart.getXAxis().getBounds().getX();
+      double yOffset = chart.getXAxis().getAxisTitle().getBounds().getY();
+      double width = chart.getXAxis().getBounds().getWidth();
+      double maxTickLabelHeight = 0;
+
+      // determine maxTickLabelY
+      int maxTickLabelY = 0;
+      for (int i = 0; i < chart.getXAxis().getAxisTickCalculator().getTickLabels().size(); i++) {
+
+        String tickLabel = chart.getXAxis().getAxisTickCalculator().getTickLabels().get(i);
+        // System.out.println("tickLabel: " + tickLabel);
+        double tickLocation = chart.getXAxis().getAxisTickCalculator().getTickLocations().get(i);
+        double shiftedTickLocation = xOffset + tickLocation;
+
+        // discard null and out of bounds labels
+        if (tickLabel != null
+            && shiftedTickLocation > xOffset
+            && shiftedTickLocation < xOffset + width) {
+          // some are null for logarithmic axes
+
+          FontRenderContext frc = g.getFontRenderContext();
+          TextLayout textLayout = new TextLayout(tickLabel, styler.getAxisTickLabelsFont(), frc);
+          // System.out.println(textLayout.getOutline(null).getBounds().toString());
+
+          // Shape shape = v.getOutline();
+          AffineTransform rot =
+              AffineTransform.getRotateInstance(
+                  -1 * Math.toRadians(styler.getXAxisLabelRotation()), 0, 0);
+          Shape shape = textLayout.getOutline(rot);
+          Rectangle2D tickLabelBounds = shape.getBounds2D();
+          if (tickLabelBounds.getBounds().height > maxTickLabelY) {
+            maxTickLabelY = tickLabelBounds.getBounds().height;
+          }
+        }
+      }
+
+      // System.out.println("axisTick.getTickLabels().size(): " + axisTick.getTickLabels().size());
+      for (int i = 0; i < chart.getXAxis().getAxisTickCalculator().getTickLabels().size(); i++) {
+
+        String tickLabel = chart.getXAxis().getAxisTickCalculator().getTickLabels().get(i);
+        // System.out.println("tickLabel: " + tickLabel);
+        double tickLocation = chart.getXAxis().getAxisTickCalculator().getTickLocations().get(i);
+        double shiftedTickLocation = xOffset + tickLocation;
+
+        // discard null and out of bounds labels
+        if (tickLabel != null
+            && shiftedTickLocation > xOffset
+            && shiftedTickLocation < xOffset + width) { // some are null for logarithmic axes
+
+          FontRenderContext frc = g.getFontRenderContext();
+          TextLayout textLayout = new TextLayout(tickLabel, styler.getAxisTickLabelsFont(), frc);
+          // System.out.println(textLayout.getOutline(null).getBounds().toString());
+
+          // Shape shape = v.getOutline();
+          AffineTransform rot =
+              AffineTransform.getRotateInstance(
+                  -1 * Math.toRadians(styler.getXAxisLabelRotation()), 0, 0);
+          Shape shape = textLayout.getOutline(rot);
+          Rectangle2D tickLabelBounds = shape.getBounds2D();
+
+          int tickLabelY = tickLabelBounds.getBounds().height;
+          int yAlignmentOffset;
+          switch (styler.getXAxisLabelAlignmentVertical()) {
+            case Right:
+              yAlignmentOffset = maxTickLabelY - tickLabelY;
+              break;
+            case Centre:
+              yAlignmentOffset = (maxTickLabelY - tickLabelY) / 2;
+              break;
+            case Left:
+            default:
+              yAlignmentOffset = 0;
+          }
+
+          AffineTransform orig = g.getTransform();
+          AffineTransform at = new AffineTransform();
+          double xPos;
+          switch (styler.getXAxisLabelAlignment()) {
+            case Left:
+              xPos = shiftedTickLocation;
+              break;
+            case Right:
+              xPos = shiftedTickLocation - tickLabelBounds.getWidth();
+              break;
+            case Centre:
+            default:
+              xPos = shiftedTickLocation - tickLabelBounds.getWidth() / 2.0;
+          }
+          //          System.out.println("tickLabelBounds: " + tickLabelBounds.toString());
+          double shiftX =
+              -1
+                  * tickLabelBounds.getX()
+                  * Math.sin(Math.toRadians(styler.getXAxisLabelRotation()));
+          double shiftY =
+              -1 * (tickLabelBounds.getY() + tickLabelBounds.getHeight() + yAlignmentOffset);
+          // System.out.println(shiftX);
+          // System.out.println("shiftY: " + shiftY);
+          at.translate(xPos + shiftX, yOffset + shiftY);
+
+          if (xPos > 0 && xPos + tickLabelBounds.getWidth() < chart.getWidth()) {
+            g.transform(at);
+            g.fill(shape);
+            g.setTransform(orig);
+
+            //            // debug box
+            //            g.setColor(Color.MAGENTA);
+            //            g.draw(
+            //                new Rectangle2D.Double(
+            //                    xPos,
+            //                    yOffset - tickLabelBounds.getHeight(),
+            //                    tickLabelBounds.getWidth(),
+            //                    tickLabelBounds.getHeight()));
+            //            g.setColor(chart.getStyler().getAxisTickLabelsColor());
+          }
+          //          else { // discarding based on the outside edges of the tick labels
+          //            System.out.println("discarding: " + tickLabel);
+          //          }
+          if (tickLabelBounds.getHeight() > maxTickLabelHeight) {
+            maxTickLabelHeight = tickLabelBounds.getHeight();
+          }
+        }
+        //        else {// discarding based on the center of the tick labels
+        //          System.out.println("discarding: " + tickLabel);
+        //        }
+      }
+
+      // bounds
+      bounds =
+          new Rectangle2D.Double(xOffset, yOffset - maxTickLabelHeight, width, maxTickLabelHeight);
+      //      g.setColor(Color.blue);
+      //      g.draw(bounds);
+
+    } else {
+      bounds = new Rectangle2D.Double();
+    }
+  }
+
+  @Override
+  public Rectangle2D getBounds() {
+
+    return bounds;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/AxisTickMarks.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/AxisTickMarks.java
new file mode 100644
index 0000000000000000000000000000000000000000..4bc06e5ca6a1d102b4495f418046a2bc42e972c9
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/AxisTickMarks.java
@@ -0,0 +1,164 @@
+package org.knowm.xchart.internal.chartpart;
+
+import java.awt.*;
+import java.awt.geom.Line2D;
+import java.awt.geom.Rectangle2D;
+import org.knowm.xchart.internal.chartpart.Axis.Direction;
+import org.knowm.xchart.internal.series.AxesChartSeries;
+import org.knowm.xchart.style.AxesChartStyler;
+import org.knowm.xchart.style.Styler.YAxisPosition;
+
+/** Axis tick marks. This includes the little tick marks and the line that hugs the plot area. */
+public class AxisTickMarks<ST extends AxesChartStyler, S extends AxesChartSeries>
+    implements ChartPart {
+
+  private final Chart<ST, S> chart;
+  private final Direction direction;
+  private final Axis yAxis;
+  private Rectangle2D bounds;
+
+  /**
+   * Constructor
+   *
+   * @param chart
+   * @param direction
+   */
+  AxisTickMarks(Chart<ST, S> chart, Direction direction, Axis yAxis) {
+
+    this.chart = chart;
+    this.direction = direction;
+    this.yAxis = yAxis;
+  }
+
+  @Override
+  public void paint(Graphics2D g) {
+
+    ST styler = chart.getStyler();
+    g.setStroke(styler.getAxisTickMarksStroke());
+
+    if (direction == Axis.Direction.Y && styler.isYAxisTicksVisible()) { // Y-Axis
+
+      g.setColor(styler.getYAxisGroupTickMarksColorMap(yAxis.getYIndex()));
+      int axisTickMarkLength = styler.getAxisTickMarkLength();
+
+      boolean onRight = styler.getYAxisGroupPosistion(yAxis.getYIndex()) == YAxisPosition.Right;
+
+      Rectangle2D yAxisBounds = yAxis.getBounds();
+      Rectangle2D axisTickLabelBounds = yAxis.getAxisTick().getAxisTickLabels().getBounds();
+      double xOffset;
+      double lineXOffset;
+      if (onRight) {
+        xOffset = axisTickLabelBounds.getX() - styler.getAxisTickPadding() - axisTickMarkLength;
+        lineXOffset = xOffset;
+      } else {
+        xOffset =
+            axisTickLabelBounds.getX()
+                + axisTickLabelBounds.getWidth()
+                + styler.getAxisTickPadding();
+        lineXOffset = xOffset + axisTickMarkLength;
+      }
+
+      double yOffset = yAxisBounds.getY();
+
+      // bounds
+      bounds =
+          new Rectangle2D.Double(
+              xOffset,
+              yOffset,
+              chart.getStyler().getAxisTickMarkLength(),
+              yAxis.getBounds().getHeight());
+      // g.setColor(Color.yellow);
+      // g.draw(bounds);
+
+      // tick marks
+      if (styler.isAxisTicksMarksVisible()) {
+
+        for (int i = 0; i < yAxis.getAxisTickCalculator().getTickLabels().size(); i++) {
+
+          double tickLocation = yAxis.getAxisTickCalculator().getTickLocations().get(i);
+          double flippedTickLocation = yOffset + yAxisBounds.getHeight() - tickLocation;
+          if (flippedTickLocation > bounds.getY()
+              && flippedTickLocation < bounds.getY() + bounds.getHeight()) {
+
+            Shape line =
+                new Line2D.Double(
+                    xOffset,
+                    flippedTickLocation,
+                    xOffset + axisTickMarkLength,
+                    flippedTickLocation);
+            g.draw(line);
+          }
+        }
+      }
+
+      // Line
+      if (styler.isAxisTicksLineVisible()) {
+
+        Shape line =
+            new Line2D.Double(lineXOffset, yOffset, lineXOffset, yOffset + yAxisBounds.getHeight());
+        g.draw(line);
+      }
+    }
+    // X-Axis
+    else if (direction == Axis.Direction.X && styler.isXAxisTicksVisible()) {
+
+      g.setColor(styler.getXAxisTickMarksColor());
+      int axisTickMarkLength = styler.getAxisTickMarkLength();
+      double xOffset = chart.getXAxis().getBounds().getX();
+      double yOffset =
+          chart.getXAxis().getAxisTick().getAxisTickLabels().getBounds().getY()
+              - styler.getAxisTickPadding();
+
+      // bounds
+      bounds =
+          new Rectangle2D.Double(
+              xOffset,
+              yOffset - axisTickMarkLength,
+              chart.getXAxis().getBounds().getWidth(),
+              axisTickMarkLength);
+      // g.setColor(Color.yellow);
+      // g.draw(bounds);
+
+      // tick marks
+      if (styler.isAxisTicksMarksVisible()) {
+
+        for (int i = 0; i < chart.getXAxis().getAxisTickCalculator().getTickLabels().size(); i++) {
+
+          double tickLocation = chart.getXAxis().getAxisTickCalculator().getTickLocations().get(i);
+          double shiftedTickLocation = xOffset + tickLocation;
+
+          if (shiftedTickLocation > bounds.getX()
+              && shiftedTickLocation < bounds.getX() + bounds.getWidth()) {
+
+            Shape line =
+                new Line2D.Double(
+                    shiftedTickLocation,
+                    yOffset,
+                    xOffset + tickLocation,
+                    yOffset - axisTickMarkLength);
+            g.draw(line);
+          }
+        }
+      }
+
+      // Line
+      if (styler.isAxisTicksLineVisible()) {
+
+        g.setStroke(styler.getAxisTickMarksStroke());
+        g.drawLine(
+            (int) xOffset,
+            (int) (yOffset - axisTickMarkLength),
+            (int) (xOffset + chart.getXAxis().getBounds().getWidth()),
+            (int) (yOffset - axisTickMarkLength));
+      }
+    } else {
+      bounds = new Rectangle2D.Double();
+    }
+  }
+
+  @Override
+  public Rectangle2D getBounds() {
+
+    return bounds;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/AxisTitle.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/AxisTitle.java
new file mode 100644
index 0000000000000000000000000000000000000000..0bd140aa1fdcca586d5cbf02cd963bbbe5c2ccfc
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/AxisTitle.java
@@ -0,0 +1,170 @@
+package org.knowm.xchart.internal.chartpart;
+
+import java.awt.*;
+import java.awt.font.FontRenderContext;
+import java.awt.font.TextLayout;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Rectangle2D;
+import org.knowm.xchart.internal.chartpart.Axis.Direction;
+import org.knowm.xchart.internal.series.Series;
+import org.knowm.xchart.style.AxesChartStyler;
+import org.knowm.xchart.style.Styler.YAxisPosition;
+
+/** AxisTitle */
+public class AxisTitle<ST extends AxesChartStyler, S extends Series> implements ChartPart {
+
+  private final Chart<ST, S> chart;
+  private final Direction direction;
+  private final Axis yAxis;
+  private final int yIndex;
+  private Rectangle2D bounds;
+
+  /**
+   * Constructor
+   *
+   * @param chart the Chart
+   * @param direction the Direction
+   */
+  AxisTitle(Chart<ST, S> chart, Direction direction, Axis yAxis, int yIndex) {
+
+    this.chart = chart;
+    this.direction = direction;
+    this.yAxis = yAxis;
+    this.yIndex = yIndex;
+  }
+
+  @Override
+  public void paint(Graphics2D g) {
+
+    bounds = new Rectangle2D.Double();
+
+    g.setColor(chart.getStyler().getChartFontColor());
+    g.setFont(chart.getStyler().getAxisTitleFont());
+
+    if (direction == Axis.Direction.Y) {
+
+      String yAxisTitle = chart.getYAxisGroupTitle(yIndex);
+      if (yAxisTitle != null
+          && !yAxisTitle.trim().equalsIgnoreCase("")
+          && chart.getStyler().isYAxisTitleVisible()) {
+
+        if (chart.getStyler().getYAxisGroupTitleColor(yIndex) != null) {
+          g.setColor(chart.getStyler().getYAxisGroupTitleColor(yIndex));
+        }
+        FontRenderContext frc = g.getFontRenderContext();
+        TextLayout nonRotatedTextLayout =
+            new TextLayout(yAxisTitle, chart.getStyler().getAxisTitleFont(), frc);
+        Rectangle2D nonRotatedRectangle = nonRotatedTextLayout.getBounds();
+
+        // ///////////////////////////////////////////////
+
+        boolean onRight =
+            chart.getStyler().getYAxisGroupPosistion(yAxis.getYIndex()) == YAxisPosition.Right;
+        int xOffset;
+        if (onRight) {
+          xOffset =
+              (int)
+                  (yAxis.getAxisTick().getBounds().getX()
+                      + yAxis.getAxisTick().getBounds().getWidth()
+                      + nonRotatedRectangle.getHeight());
+        } else {
+          xOffset = (int) (yAxis.getBounds().getX() + nonRotatedRectangle.getHeight());
+        }
+
+        int yOffset =
+            (int)
+                ((yAxis.getBounds().getHeight() + nonRotatedRectangle.getWidth()) / 2.0
+                    + yAxis.getBounds().getY());
+
+        AffineTransform rot = AffineTransform.getRotateInstance(-1 * Math.PI / 2, 0, 0);
+        Shape shape = nonRotatedTextLayout.getOutline(rot);
+
+        AffineTransform orig = g.getTransform();
+        AffineTransform at = new AffineTransform();
+
+        at.translate(xOffset, yOffset);
+        g.transform(at);
+        g.fill(shape);
+        g.setTransform(orig);
+
+        // ///////////////////////////////////////////////
+        // System.out.println(nonRotatedRectangle.getHeight());
+
+        // bounds
+        bounds =
+            new Rectangle2D.Double(
+                xOffset - nonRotatedRectangle.getHeight(),
+                yOffset - nonRotatedRectangle.getWidth(),
+                nonRotatedRectangle.getHeight() + chart.getStyler().getAxisTitlePadding(),
+                nonRotatedRectangle.getWidth());
+        // g.setColor(Color.blue);
+        // g.draw(bounds);
+      } else {
+        bounds =
+            new Rectangle2D.Double(
+                yAxis.getBounds().getX(),
+                yAxis.getBounds().getY(),
+                0,
+                yAxis.getBounds().getHeight());
+      }
+    } else {
+
+      if (chart.getXAxisTitle() != null
+          && !chart.getXAxisTitle().trim().equalsIgnoreCase("")
+          && chart.getStyler().isXAxisTitleVisible()) {
+
+        if (chart.getStyler().getXAxisTitleColor() != null) {
+          g.setColor(chart.getStyler().getXAxisTitleColor());
+        }
+        FontRenderContext frc = g.getFontRenderContext();
+        TextLayout textLayout =
+            new TextLayout(chart.getXAxisTitle(), chart.getStyler().getAxisTitleFont(), frc);
+        Rectangle2D rectangle = textLayout.getBounds();
+        // System.out.println(rectangle);
+
+        double xOffset =
+            chart.getXAxis().getBounds().getX()
+                + (chart.getXAxis().getBounds().getWidth() - rectangle.getWidth()) / 2.0;
+        double yOffset =
+            chart.getXAxis().getBounds().getY()
+                + chart.getXAxis().getBounds().getHeight()
+                - rectangle.getHeight();
+
+        // textLayout.draw(g, (float) xOffset, (float) (yOffset - rectangle.getY()));
+        Shape shape = textLayout.getOutline(null);
+        AffineTransform orig = g.getTransform();
+        AffineTransform at = new AffineTransform();
+        at.translate((float) xOffset, (float) (yOffset - rectangle.getY()));
+        g.transform(at);
+        g.fill(shape);
+        g.setTransform(orig);
+
+        bounds =
+            new Rectangle2D.Double(
+                xOffset,
+                yOffset - chart.getStyler().getAxisTitlePadding(),
+                rectangle.getWidth(),
+                rectangle.getHeight() + chart.getStyler().getAxisTitlePadding());
+        // g.setColor(Color.blue);
+        // g.draw(bounds);
+
+      } else {
+        bounds =
+            new Rectangle2D.Double(
+                chart.getXAxis().getBounds().getX(),
+                chart.getXAxis().getBounds().getY() + chart.getXAxis().getBounds().getHeight(),
+                chart.getXAxis().getBounds().getWidth(),
+                0);
+        // g.setColor(Color.blue);
+        // g.draw(bounds);
+
+      }
+    }
+  }
+
+  @Override
+  public Rectangle2D getBounds() {
+
+    return bounds;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/BoxPlotData.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/BoxPlotData.java
new file mode 100644
index 0000000000000000000000000000000000000000..fba87c8bc0aeafaf33051152412fa03cfc7691a2
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/BoxPlotData.java
@@ -0,0 +1,24 @@
+package org.knowm.xchart.internal.chartpart;
+
+/**
+ * Box plot data information(Upper whisker, Upper quartile, Middle quartile, Lower quartile, Lower
+ * whisker)
+ */
+// TODO remove these BoxPlot DTOs
+public class BoxPlotData {
+
+  /** Upper whisker */
+  protected double upper;
+
+  /** Upper quartile */
+  protected double q3;
+
+  /** Middle quartile */
+  protected double median;
+
+  /** Lower quartile */
+  protected double q1;
+
+  /** Lower whisker */
+  protected double lower;
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/BoxPlotDataCalculator.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/BoxPlotDataCalculator.java
new file mode 100644
index 0000000000000000000000000000000000000000..20b06e3a9b478d8700c9e96f660f8e088aece843
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/BoxPlotDataCalculator.java
@@ -0,0 +1,128 @@
+package org.knowm.xchart.internal.chartpart;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import org.knowm.xchart.internal.series.AxesChartSeries;
+import org.knowm.xchart.internal.series.AxesChartSeriesCategory;
+import org.knowm.xchart.style.AxesChartStyler;
+import org.knowm.xchart.style.BoxStyler;
+import org.knowm.xchart.style.BoxStyler.BoxplotCalCulationMethod;
+
+/**
+ * Calculate box plot data information for all series of BoxChart.
+ *
+ * @param <ST> BoxPlotStyler
+ * @param <S> BoxSeries
+ */
+public class BoxPlotDataCalculator<ST extends AxesChartStyler, S extends AxesChartSeries> {
+
+  public List<BoxPlotData> calculate(Map<String, S> seriesMap, ST boxPlotStyler) {
+
+    // Box plot data information for all series
+    List<BoxPlotData> boxPlotDataList = new ArrayList<>();
+    BoxPlotData boxPlotData = null;
+    List<Double> data = null;
+    Collection<? extends Number> yData = null;
+    Iterator<? extends Number> yDataIterator = null;
+    Number next = null;
+    for (S series : seriesMap.values()) {
+      if (!series.isEnabled()) {
+        continue;
+      }
+
+      yData = ((AxesChartSeriesCategory) series).getYData();
+      yDataIterator = yData.iterator();
+      data = new ArrayList<>();
+      while (yDataIterator.hasNext()) {
+        next = yDataIterator.next();
+        if (next != null) {
+          data.add(next.doubleValue());
+        }
+      }
+
+      Collections.sort(data);
+      boxPlotData = calculate(data, boxPlotStyler);
+      boxPlotDataList.add(boxPlotData);
+    }
+    return boxPlotDataList;
+  }
+
+  private BoxPlotData calculate(List<Double> data, ST boxPlotStyler) {
+
+    BoxPlotData boxPlotData = new BoxPlotData();
+    int n = data.size();
+    BoxplotCalCulationMethod boxplotCalCulationMethod =
+        ((BoxStyler) boxPlotStyler).getBoxplotCalCulationMethod();
+    double q1P = 0.0;
+    double q2P = 0.0;
+    double q3P = 0.0;
+    double four = 4d;
+    if (BoxplotCalCulationMethod.N_PLUS_1.equals(boxplotCalCulationMethod)) {
+      q1P = (n + 1) / four;
+      q2P = 2 * (n + 1) / four;
+      q3P = 3 * (n + 1) / four;
+    } else if (BoxplotCalCulationMethod.N_LESS_1.equals(boxplotCalCulationMethod)) {
+      q1P = (n - 1) / four;
+      q2P = 2 * (n - 1) / four;
+      q3P = 3 * (n - 1) / four;
+    } else if (BoxplotCalCulationMethod.NP.equals(boxplotCalCulationMethod)) {
+      q1P = n / four;
+      q2P = 2 * n / four;
+      q3P = 3 * n / four;
+    } else if (BoxplotCalCulationMethod.N_LESS_1_PLUS_1.equals(boxplotCalCulationMethod)) {
+      q1P = (n - 1) / four + 1;
+      q2P = 2 * (n - 1) / four + 1;
+      q3P = 3 * (n - 1) / four + 1;
+    }
+
+    boxPlotData.q1 = getQuartile(data, q1P, boxplotCalCulationMethod);
+    boxPlotData.median = getQuartile(data, q2P, boxplotCalCulationMethod);
+    boxPlotData.q3 = getQuartile(data, q3P, boxplotCalCulationMethod);
+
+    // Interquartile range, IQR = Q3 - Q1
+    double irq = boxPlotData.q3 - boxPlotData.q1;
+
+    // Lower whisker, lower = Q1 - 1.5 * IQR
+    boxPlotData.lower = boxPlotData.q1 - 1.5 * irq;
+    if (boxPlotData.lower < data.get(0)) {
+      boxPlotData.lower = data.get(0);
+    }
+
+    // Upper whisker, upper = Q3 + 1.5 * IQR
+    boxPlotData.upper = boxPlotData.q3 + 1.5 * irq;
+    if (boxPlotData.upper > data.get(data.size() - 1)) {
+      boxPlotData.upper = data.get(data.size() - 1);
+    }
+    return boxPlotData;
+  }
+
+  private static double getQuartile(
+      List<Double> data, double qiP, BoxplotCalCulationMethod boxplotCalCulationMethod) {
+
+    int previousItem = (int) Math.floor(qiP);
+    int previousItem_index = previousItem == 0 ? 0 : previousItem - 1;
+    int nextItem = (int) Math.ceil(qiP);
+    int nextItem_index = data.size() == 1 ? 0 : nextItem - 1;
+    final double qi;
+    if (BoxplotCalCulationMethod.NP == boxplotCalCulationMethod) {
+      if (previousItem == nextItem) {
+        qi = (data.get(previousItem_index) + data.get(nextItem_index)) / 2;
+      } else {
+        qi = data.get(nextItem_index);
+      }
+    } else {
+      if (previousItem == nextItem) {
+        qi = data.get(previousItem_index);
+      } else {
+        qi =
+            data.get(previousItem_index) * (nextItem - qiP)
+                + data.get(nextItem_index) * (qiP - previousItem);
+      }
+    }
+    return qi;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Chart.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Chart.java
new file mode 100644
index 0000000000000000000000000000000000000000..fc4e6e4d3599bc6fb1459614c50292ebca2cb356
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Chart.java
@@ -0,0 +1,308 @@
+package org.knowm.xchart.internal.chartpart;
+
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.Shape;
+import java.awt.geom.Rectangle2D;
+import java.text.DecimalFormat;
+import java.text.Format;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.function.Function;
+import org.knowm.xchart.internal.series.Series;
+import org.knowm.xchart.style.AxesChartStyler;
+import org.knowm.xchart.style.Styler;
+
+/** An XChart Chart */
+public abstract class Chart<ST extends Styler, S extends Series> {
+
+  protected final ST styler;
+  protected final ChartTitle<ST, S> chartTitle;
+  protected final Map<String, S> seriesMap = new LinkedHashMap<>();
+  protected final ArrayList<ChartPart> annotations = new ArrayList<>();
+
+  /** Chart Parts */
+  // TODO maybe move this to a secondary abstract class for inheritors with axes. Pie charts don't
+  // have an axis for example
+  protected AxisPair axisPair;
+
+  protected Plot_<ST, S> plot;
+  protected Legend_<ST, S> legend;
+
+  /** Meta Data */
+  private int width;
+
+  private int height;
+  private String title = "";
+  // TODO maybe move these to a secondary abstract class for inheritors with axes. Pie charts don't
+  // have an axis for example
+  private String xAxisTitle = "";
+  private String yAxisTitle = "";
+
+  // TODO Does this belong here for all chart types?
+  private final Map<Integer, String> yAxisGroupTitleMap = new HashMap<>();
+
+  /**
+   * Constructor
+   *
+   * @param width
+   * @param height
+   * @param styler
+   */
+  protected Chart(int width, int height, ST styler) {
+
+    this.width = width;
+    this.height = height;
+    this.styler = styler;
+
+    // TODO move this out??
+    this.chartTitle = new ChartTitle<ST, S>(this);
+  }
+
+  public abstract void paint(Graphics2D g, int width, int height);
+
+  protected void paintBackground(Graphics2D g) {
+
+    // paint chart main background
+    g.setRenderingHint(
+        RenderingHints.KEY_ANTIALIASING,
+        styler.getAntiAlias()
+            ? RenderingHints.VALUE_ANTIALIAS_ON
+            : RenderingHints.VALUE_ANTIALIAS_OFF); // global rendering hint
+    g.setColor(styler.getChartBackgroundColor());
+    Shape rect = new Rectangle2D.Double(0, 0, getWidth(), getHeight());
+    g.fill(rect);
+  }
+
+  /**
+   * Gets the Chart's styler, which can be used to customize the Chart's appearance
+   *
+   * @return the styler
+   */
+  public ST getStyler() {
+
+    return styler;
+  }
+
+  public S removeSeries(String seriesName) {
+
+    return seriesMap.remove(seriesName);
+  }
+
+  /** Getters and Setters */
+  public int getWidth() {
+
+    return width;
+  }
+
+  protected void setWidth(int width) {
+
+    this.width = width;
+  }
+
+  public int getHeight() {
+
+    return height;
+  }
+
+  protected void setHeight(int height) {
+
+    this.height = height;
+  }
+
+  // TODO remove public
+  public String getTitle() {
+
+    return title;
+  }
+
+  public void setTitle(String title) {
+
+    this.title = title;
+  }
+
+  public String getXAxisTitle() {
+
+    return xAxisTitle;
+  }
+
+  public void setXAxisTitle(String xAxisTitle) {
+
+    this.xAxisTitle = xAxisTitle;
+  }
+
+  public String getYAxisTitle() {
+
+    // TODO just call the getYAxisGroupTitle method passing in the 0th index
+    return yAxisTitle;
+  }
+
+  public void setYAxisTitle(String yAxisTitle) {
+
+    this.yAxisTitle = yAxisTitle;
+  }
+
+  // TODO these related methods don't make sense for all chart types
+  public String getYAxisGroupTitle(int yAxisGroup) {
+
+    String title = yAxisGroupTitleMap.get(yAxisGroup);
+    if (title == null) {
+      return yAxisTitle;
+    }
+    return title;
+  }
+
+  public void setYAxisGroupTitle(int yAxisGroup, String yAxisTitle) {
+
+    yAxisGroupTitleMap.put(yAxisGroup, yAxisTitle);
+  }
+
+  public void addAnnotation(Annotation annotation) {
+
+    annotations.add(annotation);
+    annotation.init(this);
+  }
+
+  /**
+   * @Deprecated - use styler instead
+   *
+   * @param customFormattingFunction
+   */
+  public void setCustomXAxisTickLabelsFormatter(Function<Double, String> customFormattingFunction) {
+    AxesChartStyler axesChartStyler = (AxesChartStyler) (styler);
+    axesChartStyler.setxAxisTickLabelsFormattingFunction(customFormattingFunction);
+  }
+
+  /**
+   * @Deprecated - use styler instead
+   *
+   * @param customFormattingFunction
+   */
+  public void setCustomYAxisTickLabelsFormatter(Function<Double, String> customFormattingFunction) {
+    AxesChartStyler axesChartStyler = (AxesChartStyler) (styler);
+    axesChartStyler.setyAxisTickLabelsFormattingFunction(customFormattingFunction);
+  }
+
+  /** Chart Parts Getters */
+  ChartTitle<ST, S> getChartTitle() {
+
+    return chartTitle;
+  }
+
+  Legend_<ST, S> getLegend() {
+
+    return legend;
+  }
+
+  Plot_<ST, S> getPlot() {
+
+    return plot;
+  }
+
+  Axis getXAxis() {
+
+    return axisPair.getXAxis();
+  }
+
+  Axis getYAxis() {
+
+    return axisPair.getYAxis();
+  }
+
+  Axis getYAxis(int yIndex) {
+
+    return axisPair.getYAxis(yIndex);
+  }
+
+  AxisPair getAxisPair() {
+
+    return axisPair;
+  }
+
+  Format getXAxisFormat() {
+    return axisPair.getXAxis().getAxisTickCalculator().getAxisFormat();
+  }
+
+  Format getYAxisFormat() {
+    return axisPair.getYAxis().getAxisTickCalculator().getAxisFormat();
+  }
+
+  // TODO investigate this
+  Format getYAxisFormat(String yAxisDecimalPattern) {
+    final Format format;
+    if (yAxisDecimalPattern != null) {
+      format = new DecimalFormat(yAxisDecimalPattern);
+    } else {
+      format = axisPair.getYAxis().getAxisTickCalculator().getAxisFormat();
+    }
+    return format;
+  }
+
+  public double getChartXFromCoordinate(int screenX) {
+
+    if (axisPair == null) {
+      return Double.NaN;
+    }
+    return axisPair.getXAxis().getChartValue(screenX);
+  }
+
+  public double getChartYFromCoordinate(int screenY) {
+
+    if (axisPair == null) {
+      return Double.NaN;
+    }
+    return axisPair.getYAxis().getChartValue(screenY);
+  }
+
+  public double getChartYFromCoordinate(int screenY, int yIndex) {
+
+    if (axisPair == null) {
+      return Double.NaN;
+    }
+    return axisPair.getYAxis(yIndex).getChartValue(screenY);
+  }
+
+  public double getScreenXFromChart(double xValue) {
+
+    if (axisPair == null) {
+      return Double.NaN;
+    }
+    return axisPair.getXAxis().getScreenValue(xValue);
+  }
+
+  public double getScreenYFromChart(double yValue) {
+
+    if (axisPair == null) {
+      return Double.NaN;
+    }
+    return axisPair.getYAxis().getScreenValue(yValue);
+  }
+
+  public double getScreenYFromChart(double yValue, int yIndex) {
+
+    if (axisPair == null) {
+      return Double.NaN;
+    }
+    return axisPair.getYAxis(yIndex).getScreenValue(yValue);
+  }
+
+  //  ArrayList<ChartPart> getAnnotations() {
+  //
+  //    return annotations;
+  //  }
+
+  // TODO remove public
+  public double getYAxisLeftWidth() {
+
+    java.awt.geom.Rectangle2D.Double bounds = getAxisPair().getLeftYAxisBounds();
+    return bounds.width + bounds.x;
+  }
+
+  // TODO remove this?
+  public Map<String, S> getSeriesMap() {
+
+    return seriesMap;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/ChartButton.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/ChartButton.java
new file mode 100644
index 0000000000000000000000000000000000000000..d146798ad160cc38c3b444b932645c58c20d751a
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/ChartButton.java
@@ -0,0 +1,215 @@
+package org.knowm.xchart.internal.chartpart;
+
+import java.awt.Graphics2D;
+import java.awt.Rectangle;
+import java.awt.RenderingHints;
+import java.awt.Shape;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.font.FontRenderContext;
+import java.awt.font.TextLayout;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Rectangle2D;
+import javax.swing.event.EventListenerList;
+import org.knowm.xchart.XChartPanel;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.style.Styler;
+
+/**
+ * A button that can be used on the chart for whatever function. For example the ChartZoom class
+ * uses this to reset the zoom function. When it is clicked it fires its actionPerformed action and
+ * whoever is listening to it can react to it.
+ */
+// TODO tie this to the styler properties
+public class ChartButton extends MouseAdapter implements ChartPart {
+
+  private final Chart chart;
+  private final Styler styler;
+  private Rectangle bounds;
+
+  // properties
+
+  protected String text;
+  boolean visible = true;
+
+  private ActionEvent action;
+  private final EventListenerList listenerList = new EventListenerList();
+
+  protected double xOffset = 0;
+  protected double yOffset = 0;
+
+  // internal
+  private Shape buttonRect;
+
+  /**
+   * Constructor
+   *
+   * @param xyChart
+   * @param xChartPanel
+   * @param text
+   */
+  public ChartButton(XYChart xyChart, XChartPanel<XYChart> xChartPanel, String text) {
+
+    this.text = text;
+
+    chart = xyChart;
+    styler = chart.getStyler();
+
+    xChartPanel.addMouseListener(this);
+    xChartPanel.addMouseMotionListener(this);
+  }
+
+  public void addActionListener(ActionListener l) {
+
+    listenerList.add(ActionListener.class, l);
+  }
+
+  public void removeActionListener(ActionListener l) {
+
+    listenerList.remove(ActionListener.class, l);
+  }
+
+  @Override
+  public Rectangle2D getBounds() {
+
+    return bounds;
+  }
+
+  @Override
+  public void mouseClicked(MouseEvent e) {
+
+    if (!visible) {
+      return;
+    }
+
+    if (buttonRect == null) {
+      return;
+    }
+    if (buttonRect.contains(e.getX(), e.getY())) {
+      fireActionPerformed();
+    }
+  }
+
+  /** Notify listeners that this button was clicked or interacted with in some way */
+  private void fireActionPerformed() {
+
+    Object[] listeners = listenerList.getListenerList();
+    for (int i = listeners.length - 2; i >= 0; i -= 2) {
+      if (action == null) {
+        action = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, text);
+      }
+
+      ((ActionListener) listeners[i + 1]).actionPerformed(action);
+    }
+  }
+
+  @Override
+  public void paint(Graphics2D g) {
+
+    if (!visible) {
+      return;
+    }
+
+    bounds = g.getClipBounds();
+
+    double boundsWidth = bounds.getWidth();
+    if (boundsWidth < 30) {
+      return;
+    }
+
+    Object oldHint = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
+    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+
+    g.setColor(styler.getChartButtonFontColor());
+    g.setFont(styler.getChartButtonFont());
+
+    FontRenderContext frc = g.getFontRenderContext();
+    TextLayout tl = new TextLayout(text, styler.getChartButtonFont(), frc);
+    Shape shape = tl.getOutline(null);
+
+    Rectangle2D textBounds = shape.getBounds2D();
+    calculatePosition(textBounds);
+    double textHeight = textBounds.getHeight();
+    double textWidth = textBounds.getWidth();
+
+    buttonRect =
+        new Rectangle2D.Double(
+            xOffset,
+            yOffset,
+            textWidth + styler.getChartButtonMargin() * 2,
+            textHeight + styler.getChartButtonMargin() * 2);
+    g.setColor(styler.getChartButtonBackgroundColor());
+    g.fill(buttonRect);
+    g.setStroke(SOLID_STROKE);
+    g.setColor(styler.getChartButtonBorderColor());
+    g.draw(buttonRect);
+
+    double startx = xOffset + styler.getChartButtonMargin();
+    double starty = yOffset + styler.getChartButtonMargin();
+
+    g.setColor(styler.getChartButtonFontColor());
+
+    AffineTransform orig = g.getTransform();
+    AffineTransform at = new AffineTransform();
+    at.translate(startx, starty + textHeight);
+    g.transform(at);
+    g.fill(shape);
+    g.setTransform(orig);
+
+    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, oldHint);
+  }
+
+  private void calculatePosition(Rectangle2D textBounds) {
+
+    double textHeight = textBounds.getHeight();
+    double textWidth = textBounds.getWidth();
+    double widthAdjustment = textWidth + styler.getChartButtonMargin() * 3;
+    double heightAdjustment = textHeight + styler.getChartButtonMargin() * 3;
+
+    double boundsWidth = bounds.getWidth();
+    double boundsHeight = bounds.getHeight();
+
+    switch (styler.getChartButtonPosition()) {
+      case InsideNW:
+        xOffset = bounds.getX() + styler.getChartButtonMargin();
+        yOffset = bounds.getY() + styler.getChartButtonMargin();
+        break;
+      case InsideNE:
+        xOffset = bounds.getX() + boundsWidth - widthAdjustment;
+        yOffset = bounds.getY() + styler.getChartButtonMargin();
+        break;
+      case InsideSE:
+        xOffset = bounds.getX() + boundsWidth - widthAdjustment;
+        yOffset = bounds.getY() + boundsHeight - heightAdjustment;
+        break;
+      case InsideSW:
+        xOffset = bounds.getX() + styler.getChartButtonMargin();
+        yOffset = bounds.getY() + boundsHeight - heightAdjustment;
+        break;
+      case InsideN:
+        xOffset = bounds.getX() + boundsWidth / 2 - textWidth / 2 - styler.getChartButtonMargin();
+        yOffset = bounds.getY() + styler.getChartButtonMargin();
+        break;
+      case InsideS:
+        xOffset = bounds.getX() + boundsWidth / 2 - textWidth / 2 - styler.getChartButtonMargin();
+        yOffset = bounds.getY() + boundsHeight - heightAdjustment;
+        break;
+      default:
+        break;
+    }
+  }
+
+  //   SETTERS
+
+  void setText(String text) {
+
+    this.text = text;
+  }
+
+  void setVisible(boolean visible) {
+
+    this.visible = visible;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/ChartPart.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/ChartPart.java
new file mode 100644
index 0000000000000000000000000000000000000000..721ba77018dcef92c9387e1581811e10379efb13
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/ChartPart.java
@@ -0,0 +1,16 @@
+package org.knowm.xchart.internal.chartpart;
+
+import java.awt.*;
+import java.awt.geom.Rectangle2D;
+
+/** All components of a chart that need to be painted should implement this interface */
+public interface ChartPart {
+
+  BasicStroke SOLID_STROKE =
+      new BasicStroke(
+          1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 10.0f, new float[] {3.0f, 0.0f}, 0.0f);
+
+  Rectangle2D getBounds();
+
+  void paint(final Graphics2D g);
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/ChartTitle.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/ChartTitle.java
new file mode 100644
index 0000000000000000000000000000000000000000..d7c16590469af7a54f1b444fd2857d4f610488fb
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/ChartTitle.java
@@ -0,0 +1,134 @@
+package org.knowm.xchart.internal.chartpart;
+
+import java.awt.*;
+import java.awt.font.FontRenderContext;
+import java.awt.font.TextLayout;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Rectangle2D;
+import org.knowm.xchart.internal.series.Series;
+import org.knowm.xchart.style.Styler;
+
+/** Chart Title */
+public class ChartTitle<ST extends Styler, S extends Series> implements ChartPart {
+
+  private final Chart<ST, S> chart;
+  private Rectangle2D bounds;
+
+  /**
+   * Constructor
+   *
+   * @param chart
+   */
+  public ChartTitle(Chart<ST, S> chart) {
+
+    this.chart = chart;
+  }
+
+  @Override
+  public void paint(Graphics2D g) {
+
+    g.setFont(chart.getStyler().getChartTitleFont());
+
+    if (!chart.getStyler().isChartTitleVisible() || chart.getTitle().length() == 0) {
+      return;
+    }
+
+    Object oldHint = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
+    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+
+    // create rectangle first for sizing
+    FontRenderContext frc = g.getFontRenderContext();
+    TextLayout textLayout =
+        new TextLayout(chart.getTitle(), chart.getStyler().getChartTitleFont(), frc);
+    Rectangle2D textBounds = textLayout.getBounds();
+
+    double xOffset = chart.getPlot().getBounds().getX(); // of plot left edge
+    double yOffset = chart.getStyler().getChartPadding();
+
+    // title box
+    if (chart.getStyler().isChartTitleBoxVisible()) {
+
+      // paint the chart title box
+      double chartTitleBoxWidth = chart.getPlot().getBounds().getWidth();
+      double chartTitleBoxHeight =
+          textBounds.getHeight() + 2 * chart.getStyler().getChartTitlePadding();
+
+      g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
+      Shape rect =
+          new Rectangle2D.Double(xOffset, yOffset, chartTitleBoxWidth, chartTitleBoxHeight);
+      g.setColor(chart.getStyler().getChartTitleBoxBackgroundColor());
+      g.fill(rect);
+      g.setColor(chart.getStyler().getChartTitleBoxBorderColor());
+      g.draw(rect);
+    }
+
+    // paint title
+    xOffset =
+        chart.getPlot().getBounds().getX()
+            + (chart.getPlot().getBounds().getWidth() - textBounds.getWidth()) / 2.0;
+    yOffset =
+        chart.getStyler().getChartPadding()
+            + textBounds.getHeight()
+            + chart.getStyler().getChartTitlePadding();
+
+    g.setColor(chart.getStyler().getChartFontColor());
+    Shape shape = textLayout.getOutline(null);
+    AffineTransform orig = g.getTransform();
+    AffineTransform at = new AffineTransform();
+    at.translate(xOffset, yOffset);
+    g.transform(at);
+    g.fill(shape);
+    g.setTransform(orig);
+
+    double width = 2 * chart.getStyler().getChartTitlePadding() + textBounds.getWidth();
+    double height = 2 * chart.getStyler().getChartTitlePadding() + textBounds.getHeight();
+    bounds =
+        new Rectangle2D.Double(
+            xOffset - chart.getStyler().getChartTitlePadding(),
+            yOffset - textBounds.getHeight() - chart.getStyler().getChartTitlePadding(),
+            width,
+            height);
+    // g.setColor(Color.blue);
+    // g.draw(bounds);
+
+    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, oldHint);
+  }
+
+  /**
+   * get the height of the chart title including the chart title padding
+   *
+   * @return a Rectangle2D defining the height of the chart title including the chart title padding
+   */
+  private Rectangle2D getBoundsHint() {
+
+    if (chart.getStyler().isChartTitleVisible() && chart.getTitle().length() > 0) {
+
+      TextLayout textLayout =
+          new TextLayout(
+              chart.getTitle(),
+              chart.getStyler().getChartTitleFont(),
+              new FontRenderContext(null, true, false));
+      Rectangle2D rectangle = textLayout.getBounds();
+      double width = 2 * chart.getStyler().getChartTitlePadding() + rectangle.getWidth();
+      double height = 2 * chart.getStyler().getChartTitlePadding() + rectangle.getHeight();
+
+      return new Rectangle2D.Double(
+          Double.NaN, Double.NaN, width, height); // Double.NaN indicates not sure yet.
+    } else {
+      return new Rectangle2D
+          .Double(); // Constructs a new Rectangle2D, initialized to location (0, 0) and size (0,
+      // 0).
+    }
+  }
+
+  @Override
+  public Rectangle2D getBounds() {
+
+    if (bounds
+        == null) { // was not drawn fully yet, just need the height hint. The Plot object will be
+      // asking for it.
+      bounds = getBoundsHint();
+    }
+    return bounds;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/ChartZoom.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/ChartZoom.java
new file mode 100644
index 0000000000000000000000000000000000000000..98d28b5c047ecac44fccd5e3de17fdba5ed45ee2
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/ChartZoom.java
@@ -0,0 +1,244 @@
+package org.knowm.xchart.internal.chartpart;
+
+import java.awt.Graphics2D;
+import java.awt.Rectangle;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.geom.Rectangle2D;
+import org.knowm.xchart.XChartPanel;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYSeries;
+
+public class ChartZoom extends MouseAdapter implements ChartPart, ActionListener {
+
+  protected final XChartPanel<XYChart> xChartPanel;
+  protected final XYChart xyChart;
+  protected Rectangle bounds;
+
+  protected final ChartButton resetButton;
+
+  protected int x1, x2;
+  protected boolean filtered;
+
+  /**
+   * Constructor
+   *
+   * @param xChartPanel
+   * @param resetString
+   */
+  public ChartZoom(XYChart xyChart, XChartPanel<XYChart> xChartPanel, String resetString) {
+
+    x1 = -1;
+    x2 = -1;
+
+    this.xChartPanel = xChartPanel;
+    this.xyChart = xyChart;
+    xyChart.plot.plotContent.setChartZoom(this);
+
+    resetButton = new ChartButton(xyChart, xChartPanel, resetString);
+    resetButton.addActionListener(this);
+    resetButton.setVisible(false);
+  }
+
+  protected void resetZoom() {
+
+    resetFilter();
+    filtered = false;
+    resetButton.setVisible(false);
+
+    x1 = -1;
+    x2 = -1;
+    repaint();
+  }
+
+  private void repaint() {
+
+    xChartPanel.invalidate();
+    xChartPanel.repaint();
+  }
+
+  @Override
+  public Rectangle2D getBounds() {
+
+    return bounds;
+  }
+
+  @Override
+  public void paint(Graphics2D g) {
+
+    //    here either 1. the mouse was released and the chart was zoomed so we need the reset button
+    // or 2. nothing should be drawn or 3. the zoom area
+    //    should be drawn
+
+    if (resetButton.visible && (x1 == -1 || x2 == -1)) { //
+      resetButton.paint(g);
+    } else if (x1 == -1 || x2 == -1) {
+      return;
+    } else {
+      g.setColor(xyChart.getStyler().getZoomSelectionColor());
+      int xStart = Math.min(x1, x2);
+      int width = Math.abs(x1 - x2);
+      bounds = g.getClipBounds();
+      g.fillRect(xStart, 0, width, (int) (bounds.height + bounds.getY()));
+    }
+  }
+
+  public void mousePressed(MouseEvent e) {
+
+    x1 = e.getX();
+    repaint();
+  }
+
+  public void mouseDragged(MouseEvent e) {
+
+    x2 = e.getX();
+    repaint();
+  }
+
+  public void mouseReleased(MouseEvent e) {
+
+    //    System.out.println("Mouse released");
+    if (!isOverlapping()) {
+      x1 = -1;
+      x2 = -1;
+      return;
+    }
+
+    if (bounds != null && x2 != -1) {
+      int smallPoint;
+      int bigPoint;
+      if (x2 < x1) {
+        smallPoint = x2;
+        bigPoint = x1;
+      } else {
+        smallPoint = x1;
+        bigPoint = x2;
+      }
+
+      filtered = filterXByScreen(smallPoint, bigPoint);
+      resetButton.setVisible(filtered && xyChart.getStyler().isZoomResetByButton());
+    }
+
+    x1 = -1;
+    x2 = -1;
+    repaint();
+  }
+
+  public boolean filterXByScreen(int screenXmin, int screenXmax) {
+
+    // convert screen coordinates to axis values
+    double minValue = xyChart.axisPair.getXAxis().getChartValue(screenXmin);
+    double maxValue = xyChart.axisPair.getXAxis().getChartValue(screenXmax);
+    boolean filtered = false;
+    if (isOnePointSeleted(minValue, maxValue)) {
+      for (XYSeries series : xyChart.getSeriesMap().values()) {
+        boolean f = series.filterXByValue(minValue, maxValue);
+        if (f) {
+          filtered = true;
+        }
+      }
+    } else {
+      if (!isAllPointsSelected()) {
+        filtered = true;
+      }
+    }
+    return filtered;
+  }
+
+  /**
+   * Is there a point selected in all series.
+   *
+   * @param minValue
+   * @param maxValue
+   * @return
+   */
+  private boolean isOnePointSeleted(double minValue, double maxValue) {
+
+    boolean isOnePointSeleted = false;
+    double[] xData = null;
+    for (XYSeries series : xyChart.getSeriesMap().values()) {
+      xData = series.getXData();
+      for (double x : xData) {
+        if (x >= minValue && x <= maxValue) {
+          isOnePointSeleted = true;
+          break;
+        }
+      }
+    }
+    return isOnePointSeleted;
+  }
+
+  public void resetFilter() {
+
+    for (XYSeries series : xyChart.getSeriesMap().values()) {
+      series.resetFilter();
+    }
+  }
+
+  public void filterXByIndex(int startIndex, int endIndex) {
+
+    for (XYSeries series : xyChart.getSeriesMap().values()) {
+      series.filterXByIndex(startIndex, endIndex);
+    }
+  }
+
+  /**
+   * Whether all points are selected in all series.
+   *
+   * @return
+   */
+  private boolean isAllPointsSelected() {
+
+    boolean isAllPointsSelected = true;
+    for (XYSeries series : xyChart.getSeriesMap().values()) {
+      if (!series.isAllXData()) {
+        isAllPointsSelected = false;
+        break;
+      }
+    }
+    return isAllPointsSelected;
+  }
+
+  @Override
+  public void mouseClicked(MouseEvent e) {
+
+    if (!filtered) {
+      return;
+    }
+    if (xyChart.getStyler().isZoomResetByDoubleClick() && e.getClickCount() == 2) {
+      resetZoom();
+      return;
+    }
+  }
+
+  @Override
+  public void actionPerformed(ActionEvent e) {
+
+    // reset button pressed
+    resetZoom();
+  }
+
+  /**
+   * Whether the selectZoom overlaps with the chart.plot
+   *
+   * @return true:overlapping, false: No overlap
+   */
+  private boolean isOverlapping() {
+
+    boolean isOverlapping = false;
+    double start = x1;
+    double end = x2;
+    if (x1 > x2) {
+      start = x2;
+      end = x1;
+    }
+    // If the two intervals overlap, then largest beginning must be smaller than the smallest ending
+    if (Math.max(start, xyChart.plot.bounds.getX())
+        < Math.min(end, xyChart.plot.bounds.getX() + xyChart.plot.bounds.getWidth())) {
+      isOverlapping = true;
+    }
+    return isOverlapping;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Cursor.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Cursor.java
new file mode 100644
index 0000000000000000000000000000000000000000..5ff750ff016e6221754f3f45499d645daa9ce780
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Cursor.java
@@ -0,0 +1,290 @@
+package org.knowm.xchart.internal.chartpart;
+
+import java.awt.BasicStroke;
+import java.awt.Graphics2D;
+import java.awt.Shape;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.font.FontRenderContext;
+import java.awt.font.TextLayout;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.Line2D;
+import java.awt.geom.Rectangle2D;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.knowm.xchart.internal.series.MarkerSeries;
+import org.knowm.xchart.internal.series.Series;
+import org.knowm.xchart.style.XYStyler;
+
+/** Cursor movement to display matching point data information. */
+public class Cursor extends MouseAdapter implements ChartPart {
+
+  private static final int LINE_SPACING = 5;
+
+  private static final int MOUSE_SPACING = 15;
+
+  private final List<DataPoint> dataPointList = new ArrayList<>();
+  private final List<DataPoint> matchingDataPointList = new ArrayList<>();
+
+  private final Chart chart;
+  private final XYStyler styler;
+
+  private final Map<String, Series> seriesMap;
+
+  private double mouseX;
+  private double mouseY;
+  private double startX;
+  private double startY;
+  private double textHeight;
+
+  /**
+   * Constructor
+   *
+   * @param chart
+   */
+  public Cursor(Chart chart) {
+
+    this.chart = chart;
+    this.styler = (XYStyler) chart.getStyler();
+    PlotContent_XY plotContent_xy = (PlotContent_XY) (chart.plot.plotContent);
+    plotContent_xy.setCursor(this);
+
+    // clear lists
+    dataPointList.clear();
+
+    this.seriesMap = chart.getSeriesMap();
+  }
+
+  @Override
+  public void mouseMoved(MouseEvent e) {
+
+    //    // don't draw anything
+    //    if (!styler.isCursorEnabled() || seriesMap == null) {
+    //      return;
+    //    }
+
+    mouseX = e.getX();
+    mouseY = e.getY();
+    if (isMouseOutOfPlotContent()) {
+
+      if (matchingDataPointList.size() > 0) {
+        matchingDataPointList.clear();
+        e.getComponent().repaint();
+      }
+      return;
+    }
+    calculateMatchingDataPoints();
+    e.getComponent().repaint();
+  }
+
+  private boolean isMouseOutOfPlotContent() {
+
+    boolean isMouseOut = false;
+    if (!chart.plot.plotContent.getBounds().contains(mouseX, mouseY)) {
+      isMouseOut = true;
+    }
+    return isMouseOut;
+  }
+
+  @Override
+  public Rectangle2D getBounds() {
+    return null;
+  }
+
+  @Override
+  public void paint(Graphics2D g) {
+
+    //    if (!styler.isCursorEnabled()) {
+    //      return;
+    //    }
+
+    if (matchingDataPointList.size() > 0) {
+      DataPoint firstDataPoint = matchingDataPointList.get(0);
+
+      TextLayout xValueTextLayout =
+          new TextLayout(
+              firstDataPoint.xValue,
+              styler.getCursorFont(),
+              new FontRenderContext(null, true, false));
+      textHeight = xValueTextLayout.getBounds().getHeight();
+
+      paintVerticalLine(g, firstDataPoint);
+
+      paintBackGround(g, xValueTextLayout);
+
+      paintDataPointInfo(g, xValueTextLayout);
+    }
+  }
+
+  private void paintVerticalLine(Graphics2D g, DataPoint dataPoint) {
+
+    BasicStroke stroke =
+        new BasicStroke(styler.getCursorLineWidth(), BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);
+    g.setStroke(stroke);
+    g.setColor(styler.getCursorColor());
+    Line2D.Double line = new Line2D.Double();
+    line.setLine(
+        dataPoint.x,
+        chart.plot.plotContent.getBounds().getY(),
+        dataPoint.x,
+        chart.plot.plotContent.getBounds().getY() + chart.plot.plotContent.getBounds().getHeight());
+    g.draw(line);
+  }
+
+  private void paintBackGround(Graphics2D g, TextLayout xValueTextLayout) {
+
+    double maxLinewidth = xValueTextLayout.getBounds().getWidth();
+    TextLayout dataPointTextLayout = null;
+    Rectangle2D dataPointRectangle = null;
+    for (DataPoint dataPoint : matchingDataPointList) {
+      dataPointTextLayout =
+          new TextLayout(
+              dataPoint.seriesName + ": " + dataPoint.yValue,
+              styler.getCursorFont(),
+              new FontRenderContext(null, true, false));
+      dataPointRectangle = dataPointTextLayout.getBounds();
+      if (maxLinewidth < dataPointRectangle.getWidth()) {
+        maxLinewidth = dataPointRectangle.getWidth();
+      }
+    }
+
+    double backgroundWidth = styler.getCursorFont().getSize() + maxLinewidth + 3 * LINE_SPACING;
+    double backgroundHeight =
+        textHeight * (1 + matchingDataPointList.size())
+            + (2 + matchingDataPointList.size()) * LINE_SPACING;
+
+    startX = mouseX;
+    startY = mouseY;
+    if (mouseX + MOUSE_SPACING + backgroundWidth
+        > chart.plot.plotContent.getBounds().getX()
+            + chart.plot.plotContent.getBounds().getWidth()) {
+      startX = mouseX - backgroundWidth - MOUSE_SPACING;
+    }
+
+    if (mouseY + MOUSE_SPACING + backgroundHeight
+        > chart.plot.plotContent.getBounds().getY()
+            + chart.plot.plotContent.getBounds().getHeight()) {
+      startY = mouseY - backgroundHeight - MOUSE_SPACING;
+    }
+
+    g.setColor(styler.getCursorBackgroundColor());
+    g.fillRect(
+        (int) startX + MOUSE_SPACING,
+        (int) startY + MOUSE_SPACING,
+        (int) (backgroundWidth),
+        (int) (backgroundHeight));
+  }
+
+  private void paintDataPointInfo(Graphics2D g, TextLayout xValueTextLayout) {
+
+    AffineTransform orig = g.getTransform();
+    AffineTransform at = new AffineTransform();
+    at.translate(
+        startX + MOUSE_SPACING + LINE_SPACING, startY + textHeight + MOUSE_SPACING + LINE_SPACING);
+    g.transform(at);
+    g.setColor(styler.getCursorFontColor());
+    g.fill(xValueTextLayout.getOutline(null));
+
+    MarkerSeries series = null;
+    TextLayout dataPointTextLayout = null;
+    Shape circle = null;
+    for (DataPoint dataPoint : matchingDataPointList) {
+      at = new AffineTransform();
+      at.translate(0, textHeight + LINE_SPACING);
+      g.transform(at);
+      series = (MarkerSeries) seriesMap.get(dataPoint.seriesName);
+      if (series == null) {
+        continue;
+      }
+      g.setColor(series.getMarkerColor());
+      circle = new Ellipse2D.Double(0, -textHeight, textHeight, textHeight);
+      g.fill(circle);
+
+      at = new AffineTransform();
+      at.translate(textHeight + LINE_SPACING, 0);
+      g.transform(at);
+      g.setColor(styler.getCursorFontColor());
+      dataPointTextLayout =
+          new TextLayout(
+              dataPoint.seriesName + ": " + dataPoint.yValue,
+              styler.getCursorFont(),
+              new FontRenderContext(null, true, false));
+      g.fill(dataPointTextLayout.getOutline(null));
+
+      at = new AffineTransform();
+      at.translate(-textHeight - LINE_SPACING, 0);
+      g.transform(at);
+    }
+    g.setTransform(orig);
+  }
+
+  void addData(double xOffset, double yOffset, String xValue, String yValue, String seriesName) {
+
+    DataPoint dataPoint = new DataPoint(xOffset, yOffset, xValue, yValue, seriesName);
+    dataPointList.add(dataPoint);
+  }
+
+  /** One DataPoint per series, keep the DataPoint closest to mouseX */
+  private void calculateMatchingDataPoints() {
+
+    List<DataPoint> dataPoints = new ArrayList<>();
+    for (DataPoint dataPoint : dataPointList) {
+      if (dataPoint.shape.contains(mouseX, dataPoint.shape.getBounds().getCenterY())
+          && chart.plot.plotContent.getBounds().getY() < mouseY
+          && chart.plot.plotContent.getBounds().getY()
+                  + chart.plot.plotContent.getBounds().getHeight()
+              > mouseY) {
+        dataPoints.add(dataPoint);
+      }
+    }
+
+    if (dataPoints.size() > 0) {
+      Map<String, DataPoint> map = new HashMap<>();
+      String seriesName = "";
+      for (DataPoint dataPoint : dataPoints) {
+        seriesName = dataPoint.seriesName;
+        if (map.containsKey(seriesName)) {
+          if (Math.abs(dataPoint.x - mouseX) < Math.abs(map.get(seriesName).x - mouseX)) {
+            map.put(seriesName, dataPoint);
+          }
+        } else {
+          map.put(seriesName, dataPoint);
+        }
+      }
+      matchingDataPointList.clear();
+      matchingDataPointList.addAll(map.values());
+    }
+  }
+
+  private static class DataPoint {
+
+    // edge detection
+    private static final int MARGIN = 5;
+
+    // Used to determine the point that the mouse has passed vertically
+    final Shape shape;
+    final double x;
+    final double y;
+    final String xValue;
+    final String yValue;
+    final String seriesName;
+
+    public DataPoint(double x, double y, String xValue, String yValue, String seriesName) {
+
+      double halfSize = MARGIN * 1.5;
+      double markerSize = MARGIN * 3;
+
+      this.x = x;
+      this.y = y;
+      this.shape =
+          new Ellipse2D.Double(this.x - halfSize, this.y - halfSize, markerSize, markerSize);
+
+      this.xValue = xValue;
+      this.yValue = yValue;
+      this.seriesName = seriesName;
+    }
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Formatter_Custom.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Formatter_Custom.java
new file mode 100644
index 0000000000000000000000000000000000000000..3e61838fcb90eea7a7e0991886da154d43e60f7b
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Formatter_Custom.java
@@ -0,0 +1,27 @@
+package org.knowm.xchart.internal.chartpart;
+
+import java.text.FieldPosition;
+import java.text.Format;
+import java.text.ParsePosition;
+import java.util.function.Function;
+
+public class Formatter_Custom extends Format {
+
+  private final Function<Double, String> customFormattingFunction;
+
+  public Formatter_Custom(Function<Double, String> customFormattingFunction) {
+    this.customFormattingFunction = customFormattingFunction;
+  }
+
+  @Override
+  public StringBuffer format(Object o, StringBuffer stringBuffer, FieldPosition fieldPosition) {
+    Number number = (Number) o;
+    stringBuffer.append(customFormattingFunction.apply(number.doubleValue()));
+    return stringBuffer;
+  }
+
+  @Override
+  public Object parseObject(String s, ParsePosition parsePosition) {
+    return null;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Formatter_LogNumber.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Formatter_LogNumber.java
new file mode 100644
index 0000000000000000000000000000000000000000..9110d17cbbb0c28681388e9b710cdb554db37204
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Formatter_LogNumber.java
@@ -0,0 +1,77 @@
+package org.knowm.xchart.internal.chartpart;
+
+import java.text.*;
+import org.knowm.xchart.style.AxesChartStyler;
+
+class Formatter_LogNumber extends Format {
+
+  private final AxesChartStyler styler;
+  private final Axis.Direction axisDirection;
+  private final NumberFormat numberFormat;
+  private int yIndex;
+
+  /** Constructor */
+  public Formatter_LogNumber(AxesChartStyler styler, Axis.Direction axisDirection) {
+
+    this.styler = styler;
+    this.axisDirection = axisDirection;
+    numberFormat = NumberFormat.getNumberInstance(styler.getLocale());
+  }
+
+  /**
+   * Constructor
+   *
+   * @param styler
+   * @param axisDirection
+   * @param yIndex
+   */
+  public Formatter_LogNumber(AxesChartStyler styler, Axis.Direction axisDirection, int yIndex) {
+
+    this.styler = styler;
+    this.axisDirection = axisDirection;
+    this.yIndex = yIndex;
+    numberFormat = NumberFormat.getNumberInstance(styler.getLocale());
+  }
+
+  @Override
+  public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) {
+
+    double number = (Double) obj;
+
+    String decimalPattern;
+
+    if (axisDirection == Axis.Direction.X && styler.getXAxisDecimalPattern() != null) {
+
+      decimalPattern = styler.getXAxisDecimalPattern();
+    } else if (axisDirection == Axis.Direction.Y
+        && (styler.getYAxisGroupDecimalPatternMap().get(yIndex) != null
+            || styler.getYAxisDecimalPattern() != null)) {
+      if (styler.getYAxisGroupDecimalPatternMap().get(yIndex) != null) {
+        decimalPattern = styler.getYAxisGroupDecimalPatternMap().get(yIndex);
+      } else {
+        decimalPattern = styler.getYAxisDecimalPattern();
+      }
+    } else if (styler.getDecimalPattern() != null) {
+
+      decimalPattern = styler.getDecimalPattern();
+    } else {
+      if (Math.abs(number) > 1000.0 || Math.abs(number) < 0.001) {
+        decimalPattern = "0E0";
+      } else {
+        decimalPattern = "0.###";
+      }
+    }
+
+    DecimalFormat normalFormat = (DecimalFormat) numberFormat;
+    normalFormat.applyPattern(decimalPattern);
+    toAppendTo.append(normalFormat.format(number));
+
+    return toAppendTo;
+  }
+
+  @Override
+  public Object parseObject(String source, ParsePosition pos) {
+
+    return null;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Formatter_Number.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Formatter_Number.java
new file mode 100644
index 0000000000000000000000000000000000000000..a3f70da684aeb1cd05cc77c43081d60765d019ea
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Formatter_Number.java
@@ -0,0 +1,147 @@
+package org.knowm.xchart.internal.chartpart;
+
+import java.math.BigDecimal;
+import java.text.*;
+import org.knowm.xchart.style.AxesChartStyler;
+
+class Formatter_Number extends Format {
+
+  private final AxesChartStyler styler;
+  private final Axis.Direction axisDirection;
+  private final double min;
+  private final double max;
+  private final NumberFormat numberFormat;
+  private int yIndex;
+
+  /** Constructor */
+  public Formatter_Number(
+      AxesChartStyler styler, Axis.Direction axisDirection, double min, double max) {
+
+    this.styler = styler;
+    this.axisDirection = axisDirection;
+    this.min = min;
+    this.max = max;
+    numberFormat = NumberFormat.getNumberInstance(styler.getLocale());
+  }
+
+  /**
+   * Constructor
+   *
+   * @param styler
+   * @param axisDirection
+   * @param min
+   * @param max
+   * @param yIndex
+   */
+  public Formatter_Number(
+      AxesChartStyler styler, Axis.Direction axisDirection, double min, double max, int yIndex) {
+
+    this.styler = styler;
+    this.axisDirection = axisDirection;
+    this.min = min;
+    this.max = max;
+    this.yIndex = yIndex;
+    numberFormat = NumberFormat.getNumberInstance(styler.getLocale());
+  }
+
+  private String getFormatPattern(double value) {
+
+    // System.out.println("value: " + value);
+    // System.out.println("min: " + min);
+    // System.out.println("max: " + max);
+
+    // some special cases first
+    if (BigDecimal.valueOf(value).compareTo(BigDecimal.ZERO) == 0) {
+      return "0";
+    }
+
+    double difference = max - min;
+    int placeOfDifference;
+    if (difference == 0.0) {
+      placeOfDifference = 0;
+    } else {
+      placeOfDifference = (int) Math.floor(Math.log(difference) / Math.log(10));
+    }
+    int placeOfValue;
+    if (value == 0.0) {
+      placeOfValue = 0;
+    } else {
+      placeOfValue = (int) Math.floor(Math.log(value) / Math.log(10));
+    }
+
+    // System.out.println("difference: " + difference);
+    // System.out.println("placeOfDifference: " + placeOfDifference);
+    // System.out.println("placeOfValue: " + placeOfValue);
+
+    if (placeOfDifference <= 4 && placeOfDifference >= -4) {
+      // System.out.println("getNormalDecimalPattern");
+      return getNormalDecimalPatternPositive(placeOfValue, placeOfDifference);
+    } else {
+      // System.out.println("getScientificDecimalPattern");
+      return "0.###############E0";
+    }
+  }
+
+  private String getNormalDecimalPatternPositive(int placeOfValue, int placeOfDifference) {
+
+    int maxNumPlaces = 15;
+    StringBuilder sb = new StringBuilder();
+    for (int i = maxNumPlaces - 1; i >= -1 * maxNumPlaces; i--) {
+
+      if (i >= 0 && (i < placeOfValue)) {
+        sb.append("0");
+      } else if (i < 0 && (i > placeOfValue)) {
+        sb.append("0");
+      } else {
+        sb.append("#");
+      }
+      if (i % 3 == 0 && i > 0) {
+        sb.append(",");
+      }
+      if (i == 0) {
+        sb.append(".");
+      }
+    }
+    // System.out.println(sb.toString());
+    return sb.toString();
+  }
+
+  @Override
+  public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) {
+
+    // BigDecimal number = (BigDecimal) obj;
+    Number number = (Number) obj;
+
+    String decimalPattern;
+
+    if (axisDirection == Axis.Direction.X && styler.getXAxisDecimalPattern() != null) {
+
+      decimalPattern = styler.getXAxisDecimalPattern();
+    } else if (axisDirection == Axis.Direction.Y
+        && (styler.getYAxisGroupDecimalPatternMap().get(yIndex) != null
+            || styler.getYAxisDecimalPattern() != null)) {
+      if (styler.getYAxisGroupDecimalPatternMap().get(yIndex) != null) {
+        decimalPattern = styler.getYAxisGroupDecimalPatternMap().get(yIndex);
+      } else {
+        decimalPattern = styler.getYAxisDecimalPattern();
+      }
+    } else if (styler.getDecimalPattern() != null) {
+      decimalPattern = styler.getDecimalPattern();
+    } else {
+      decimalPattern = getFormatPattern(number.doubleValue());
+    }
+    // System.out.println(decimalPattern);
+
+    DecimalFormat normalFormat = (DecimalFormat) numberFormat;
+    normalFormat.applyPattern(decimalPattern);
+    toAppendTo.append(normalFormat.format(number));
+
+    return toAppendTo;
+  }
+
+  @Override
+  public Object parseObject(String source, ParsePosition pos) {
+
+    return null;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Formatter_String.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Formatter_String.java
new file mode 100644
index 0000000000000000000000000000000000000000..d89310f38400f4bbddaffd766f5cf6c4a02a52d2
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Formatter_String.java
@@ -0,0 +1,28 @@
+package org.knowm.xchart.internal.chartpart;
+
+import java.text.FieldPosition;
+import java.text.Format;
+import java.text.ParsePosition;
+
+/** Dummy Format class for labels */
+class Formatter_String extends Format {
+
+  /** Constructor */
+  public Formatter_String() {}
+
+  @Override
+  public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) {
+
+    String string = obj.toString();
+
+    toAppendTo.append(string);
+
+    return toAppendTo;
+  }
+
+  @Override
+  public Object parseObject(String source, ParsePosition pos) {
+
+    return null;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Legend_.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Legend_.java
new file mode 100644
index 0000000000000000000000000000000000000000..f39a99f490b6f76d6374973799d58e8d6e9ddbee
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Legend_.java
@@ -0,0 +1,384 @@
+package org.knowm.xchart.internal.chartpart;
+
+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.LinkedHashMap;
+import java.util.Map;
+import org.knowm.xchart.internal.chartpart.RenderableSeries.LegendRenderType;
+import org.knowm.xchart.internal.series.Series;
+import org.knowm.xchart.style.Styler;
+
+public abstract class Legend_<ST extends Styler, S extends Series> implements ChartPart {
+
+  static final int BOX_SIZE = 20;
+  static final int BOX_OUTLINE_WIDTH = 5;
+  private static final int LEGEND_MARGIN = 6;
+  private static final int MULTI_LINE_SPACE = 3;
+  final Chart<ST, S> chart;
+  double xOffset = 0;
+  double yOffset = 0;
+  private Rectangle2D bounds;
+
+  /**
+   * Constructor
+   *
+   * @param chart
+   */
+  Legend_(Chart<ST, S> chart) {
+
+    this.chart = chart;
+  }
+
+  protected abstract double getSeriesLegendRenderGraphicHeight(S series);
+
+  protected abstract void doPaint(Graphics2D g);
+
+  @Override
+  public void paint(Graphics2D g) {
+
+    if (!chart.getStyler().isLegendVisible()) {
+      return;
+    }
+
+    if (chart.getSeriesMap().isEmpty()) {
+      return;
+    }
+
+    // if the area to draw a chart on is so small, don't even bother
+    if (chart.getPlot().getBounds().getWidth() < 30) {
+      return;
+    }
+
+    // We call get bounds hint because sometimes the Axis object needs it to know it's bounds (if
+    // Legend is outside Plot). If it's null, we just need to calulate it before painting, because
+    // the paint
+    // methods needs the bounds.
+    // if (bounds == null) { // No other part asked for the bounds yet. Probably because it's an
+    // "inside" legend location
+    if (chart.getStyler().getLegendLayout() == Styler.LegendLayout.Vertical) {
+      bounds =
+          getBoundsHintVertical(); // Actually, the only information contained in this bounds is the
+      // width and height.
+    } else {
+      bounds =
+          getBoundsHintHorizontal(); // Actually, the only information contained in this bounds is
+      // the width and height.
+    }
+
+    // legend draw position
+    double height = bounds.getHeight();
+
+    switch (chart.getStyler().getLegendPosition()) {
+      case OutsideE:
+        xOffset = chart.getWidth() - bounds.getWidth() - LEGEND_MARGIN;
+        yOffset =
+            chart.getPlot().getBounds().getY()
+                + (chart.getPlot().getBounds().getHeight() - bounds.getHeight()) / 2.0;
+        break;
+      case InsideNW:
+        xOffset = chart.getPlot().getBounds().getX() + LEGEND_MARGIN;
+        yOffset = chart.getPlot().getBounds().getY() + LEGEND_MARGIN;
+        break;
+      case InsideNE:
+        xOffset =
+            chart.getPlot().getBounds().getX()
+                + chart.getPlot().getBounds().getWidth()
+                - bounds.getWidth()
+                - LEGEND_MARGIN;
+        yOffset = chart.getPlot().getBounds().getY() + LEGEND_MARGIN;
+        break;
+      case InsideSE:
+        xOffset =
+            chart.getPlot().getBounds().getX()
+                + chart.getPlot().getBounds().getWidth()
+                - bounds.getWidth()
+                - LEGEND_MARGIN;
+        yOffset =
+            chart.getPlot().getBounds().getY()
+                + chart.getPlot().getBounds().getHeight()
+                - bounds.getHeight()
+                - LEGEND_MARGIN;
+        break;
+      case InsideSW:
+        xOffset = chart.getPlot().getBounds().getX() + LEGEND_MARGIN;
+        yOffset =
+            chart.getPlot().getBounds().getY()
+                + chart.getPlot().getBounds().getHeight()
+                - bounds.getHeight()
+                - LEGEND_MARGIN;
+        break;
+      case InsideN:
+        xOffset =
+            chart.getPlot().getBounds().getX()
+                + (chart.getPlot().getBounds().getWidth() - bounds.getWidth()) / 2
+                + LEGEND_MARGIN;
+        yOffset = chart.getPlot().getBounds().getY() + LEGEND_MARGIN;
+        break;
+      case InsideS:
+        xOffset =
+            chart.getPlot().getBounds().getX()
+                + (chart.getPlot().getBounds().getWidth() - bounds.getWidth()) / 2
+                + LEGEND_MARGIN;
+        yOffset =
+            chart.getPlot().getBounds().getY()
+                + chart.getPlot().getBounds().getHeight()
+                - bounds.getHeight()
+                - LEGEND_MARGIN;
+        break;
+      case OutsideS:
+        xOffset =
+            chart.getPlot().getBounds().getX()
+                + (chart.getPlot().getBounds().getWidth() - bounds.getWidth()) / 2.0;
+        yOffset = chart.getHeight() - bounds.getHeight() - LEGEND_MARGIN;
+        break;
+
+      default:
+        break;
+    }
+
+    // draw legend box background and border
+    Shape rect = new Rectangle2D.Double(xOffset, yOffset, bounds.getWidth(), height);
+    g.setColor(chart.getStyler().getLegendBackgroundColor());
+    g.fill(rect);
+    g.setStroke(SOLID_STROKE);
+    g.setColor(chart.getStyler().getLegendBorderColor());
+    g.draw(rect);
+
+    doPaint(g);
+
+    // bounds
+    // bounds = new Rectangle2D.Double(xOffset, yOffset, bounds.getWidth(), bounds.getHeight());
+    // g.setColor(Color.blue);
+    // g.draw(bounds);
+  }
+
+  /** determine the width and height of the chart legend */
+  private Rectangle2D getBoundsHintVertical() {
+
+    if (!chart.getStyler().isLegendVisible()) {
+      return new Rectangle2D
+          .Double(); // Constructs a new Rectangle2D, initialized to location (0, 0) and size (0,
+      // 0).
+    }
+
+    boolean containsBox = false;
+
+    // determine legend text content max width
+    double legendTextContentMaxWidth = 0;
+
+    // determine total legend content height
+    double legendContentHeight = 0;
+
+    Map<String, S> map = chart.getSeriesMap();
+    for (S series : map.values()) {
+
+      if (!series.isShowInLegend()) {
+        continue;
+      }
+      if (!series.isEnabled()) {
+        continue;
+      }
+
+      Map<String, Rectangle2D> seriesTextBounds = getSeriesTextBounds(series);
+
+      double legendEntryHeight = 0; // could be multi-line
+      for (Map.Entry<String, Rectangle2D> entry : seriesTextBounds.entrySet()) {
+        legendEntryHeight += entry.getValue().getHeight() + MULTI_LINE_SPACE;
+        legendTextContentMaxWidth =
+            Math.max(legendTextContentMaxWidth, entry.getValue().getWidth());
+      }
+
+      legendEntryHeight -= MULTI_LINE_SPACE; // subtract away the bottom MULTI_LINE_SPACE
+      legendEntryHeight = Math.max(legendEntryHeight, (getSeriesLegendRenderGraphicHeight(series)));
+
+      legendContentHeight += legendEntryHeight + chart.getStyler().getLegendPadding();
+
+      if (series.getLegendRenderType() == LegendRenderType.Box) {
+        containsBox = true;
+      }
+    }
+
+    // determine legend content width
+    double legendContentWidth;
+    if (!containsBox) {
+      legendContentWidth =
+          chart.getStyler().getLegendSeriesLineLength()
+              + chart.getStyler().getLegendPadding()
+              + legendTextContentMaxWidth;
+    } else {
+      legendContentWidth =
+          BOX_SIZE + chart.getStyler().getLegendPadding() + legendTextContentMaxWidth;
+    }
+
+    // Legend Box
+    double width = legendContentWidth + 2 * chart.getStyler().getLegendPadding();
+    double height = legendContentHeight + chart.getStyler().getLegendPadding();
+
+    return new Rectangle2D.Double(0, 0, width, height); // 0 indicates not sure yet.
+  }
+
+  /** determine the width and height of the chart legend with horizontal layout */
+  private Rectangle2D getBoundsHintHorizontal() {
+
+    if (!chart.getStyler().isLegendVisible()) {
+      return new Rectangle2D
+          .Double(); // Constructs a new Rectangle2D, initialized to location (0, 0) and size (0,
+      // 0).
+    }
+
+    // determine legend text content max height
+    double legendTextContentMaxHeight = 0;
+
+    // determine total legend content width
+    double legendContentWidth = 0;
+
+    Map<String, S> map = chart.getSeriesMap();
+    for (S series : map.values()) {
+
+      if (!series.isShowInLegend()) {
+        continue;
+      }
+      if (!series.isEnabled()) {
+        continue;
+      }
+
+      Map<String, Rectangle2D> seriesTextBounds = getSeriesTextBounds(series);
+
+      double legendEntryHeight = 0; // could be multi-line
+      double legendEntryMaxWidth = 0; // could be multi-line
+      for (Map.Entry<String, Rectangle2D> entry : seriesTextBounds.entrySet()) {
+        legendEntryHeight += entry.getValue().getHeight() + MULTI_LINE_SPACE;
+        legendEntryMaxWidth = Math.max(legendEntryMaxWidth, entry.getValue().getWidth());
+      }
+
+      legendEntryHeight -= MULTI_LINE_SPACE; // subtract away the bottom MULTI_LINE_SPACE
+      legendTextContentMaxHeight =
+          Math.max(legendEntryHeight, getSeriesLegendRenderGraphicHeight(series));
+
+      legendContentWidth += legendEntryMaxWidth + chart.getStyler().getLegendPadding();
+
+      if (series.getLegendRenderType() == LegendRenderType.Line) {
+        legendContentWidth =
+            chart.getStyler().getLegendSeriesLineLength()
+                + chart.getStyler().getLegendPadding()
+                + legendContentWidth;
+      } else {
+        legendContentWidth = BOX_SIZE + chart.getStyler().getLegendPadding() + legendContentWidth;
+      }
+    }
+
+    // Legend Box
+    double width = legendContentWidth + chart.getStyler().getLegendPadding();
+    double height = legendTextContentMaxHeight + chart.getStyler().getLegendPadding() * 2;
+
+    return new Rectangle2D.Double(0, 0, width, height); // 0 indicates not sure yet.
+  }
+
+  /**
+   * Normally each legend entry just has one line of text, but it can be made multi-line by adding
+   * "\\n". This method returns a Map for each single legend entry, which is normally just a Map
+   * with one single entry.
+   *
+   * @param series
+   * @return
+   */
+  Map<String, Rectangle2D> getSeriesTextBounds(S series) {
+
+    // FontMetrics fontMetrics = g.getFontMetrics(getChartPainter().getstyler().getLegendFont());
+    // float fontDescent = fontMetrics.getDescent();
+
+    String lines[] = series.getLabel().split("\\n");
+    Map<String, Rectangle2D> seriesTextBounds =
+        new LinkedHashMap<String, Rectangle2D>(lines.length);
+    for (String line : lines) {
+      TextLayout textLayout =
+          new TextLayout(
+              line, chart.getStyler().getLegendFont(), new FontRenderContext(null, true, false));
+      Shape shape = textLayout.getOutline(null);
+      Rectangle2D bounds = shape.getBounds2D();
+      // System.out.println(tl.getAscent());
+      // System.out.println(tl.getDescent());
+      // System.out.println(tl.getBounds());
+      // seriesTextBounds.put(line, new Rectangle2D.Double(bounds.getX(), bounds.getY(),
+      // bounds.getWidth(), bounds.getHeight() - tl.getDescent()));
+      // seriesTextBounds.put(line, new Rectangle2D.Double(bounds.getX(), bounds.getY(),
+      // bounds.getWidth(), tl.getAscent()));
+      seriesTextBounds.put(line, bounds);
+    }
+    return seriesTextBounds;
+  }
+
+  float getLegendEntryHeight(Map<String, Rectangle2D> seriesTextBounds, int markerSize) {
+
+    float legendEntryHeight = 0;
+    for (Map.Entry<String, Rectangle2D> entry : seriesTextBounds.entrySet()) {
+      legendEntryHeight += entry.getValue().getHeight() + MULTI_LINE_SPACE;
+    }
+    legendEntryHeight -= MULTI_LINE_SPACE;
+
+    legendEntryHeight = Math.max(legendEntryHeight, markerSize);
+
+    return legendEntryHeight;
+  }
+
+  float getLegendEntryWidth(Map<String, Rectangle2D> seriesTextBounds, int markerSize) {
+
+    float legendEntryWidth = 0;
+    for (Map.Entry<String, Rectangle2D> entry : seriesTextBounds.entrySet()) {
+      legendEntryWidth = Math.max(legendEntryWidth, (float) entry.getValue().getWidth());
+    }
+
+    return legendEntryWidth + markerSize + chart.getStyler().getLegendPadding();
+  }
+
+  void paintSeriesText(
+      Graphics2D g,
+      Map<String, Rectangle2D> seriesTextBounds,
+      int markerSize,
+      double x,
+      double starty) {
+
+    g.setColor(chart.getStyler().getChartFontColor());
+    g.setFont(chart.getStyler().getLegendFont());
+
+    double multiLineOffset = 0.0;
+
+    for (Map.Entry<String, Rectangle2D> entry : seriesTextBounds.entrySet()) {
+
+      double height = entry.getValue().getHeight();
+      double centerOffsetY = (Math.max(markerSize, height) - height) / 2.0;
+
+      FontRenderContext frc = g.getFontRenderContext();
+      TextLayout tl = new TextLayout(entry.getKey(), chart.getStyler().getLegendFont(), frc);
+      Shape shape = tl.getOutline(null);
+      AffineTransform orig = g.getTransform();
+      AffineTransform at = new AffineTransform();
+      at.translate(x, starty + height + centerOffsetY + multiLineOffset);
+      g.transform(at);
+      g.fill(shape);
+      g.setTransform(orig);
+
+      // // debug box
+      // Rectangle2D boundsTemp = new Rectangle2D.Double(x, starty + centerOffsetY,
+      // entry.getValue().getWidth(), height);
+      // g.setColor(Color.blue);
+      // g.draw(boundsTemp);
+      multiLineOffset += height + MULTI_LINE_SPACE;
+    }
+  }
+
+  @Override
+  public Rectangle2D getBounds() {
+
+    if (chart.getStyler().getLegendLayout() == Styler.LegendLayout.Vertical) {
+      return getBoundsHintVertical(); // Actually, the only information contained in this bounds is
+      // the width and height.
+    } else {
+      return getBoundsHintHorizontal(); // Actually, the only information contained in this bounds
+      // is the width and height.
+    }
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Legend_Bubble.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Legend_Bubble.java
new file mode 100644
index 0000000000000000000000000000000000000000..4188adaf2ac0324892db5237cd148287a463020c
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Legend_Bubble.java
@@ -0,0 +1,84 @@
+package org.knowm.xchart.internal.chartpart;
+
+import java.awt.*;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.Rectangle2D;
+import java.util.Map;
+import org.knowm.xchart.internal.chartpart.RenderableSeries.LegendRenderType;
+import org.knowm.xchart.internal.series.AxesChartSeries;
+import org.knowm.xchart.style.AxesChartStyler;
+import org.knowm.xchart.style.Styler;
+
+public class Legend_Bubble<ST extends AxesChartStyler, S extends AxesChartSeries>
+    extends Legend_<ST, S> {
+
+  private final ST axesChartStyler;
+
+  /**
+   * Constructor
+   *
+   * @param chart
+   */
+  public Legend_Bubble(Chart<ST, S> chart) {
+
+    super(chart);
+    axesChartStyler = chart.getStyler();
+  }
+
+  @Override
+  public void doPaint(Graphics2D g) {
+
+    // Draw legend content inside legend box
+    double startx = xOffset + chart.getStyler().getLegendPadding();
+    double starty = yOffset + chart.getStyler().getLegendPadding();
+
+    Object oldHint = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
+    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+
+    Map<String, S> map = chart.getSeriesMap();
+    for (S series : map.values()) {
+
+      if (!series.isShowInLegend()) {
+        continue;
+      }
+      if (!series.isEnabled()) {
+        continue;
+      }
+
+      Map<String, Rectangle2D> seriesTextBounds = getSeriesTextBounds(series);
+      float legendEntryHeight = getLegendEntryHeight(seriesTextBounds, BOX_SIZE);
+
+      // paint little circle
+      Shape rectSmall = new Ellipse2D.Double(startx, starty, BOX_SIZE, BOX_SIZE);
+      g.setColor(series.getFillColor());
+      g.fill(rectSmall);
+      g.setStroke(series.getLineStyle());
+      g.setColor(series.getLineColor());
+      g.draw(rectSmall);
+
+      // paint series text
+      final double x = startx + BOX_SIZE + chart.getStyler().getLegendPadding();
+      paintSeriesText(g, seriesTextBounds, BOX_SIZE, x, starty);
+
+      if (chart.getStyler().getLegendLayout() == Styler.LegendLayout.Vertical) {
+        starty += legendEntryHeight + chart.getStyler().getLegendPadding();
+      } else {
+        int markerWidth = BOX_SIZE;
+        if (series.getLegendRenderType() == LegendRenderType.Line) {
+          markerWidth = chart.getStyler().getLegendSeriesLineLength();
+        }
+        float legendEntryWidth = getLegendEntryWidth(seriesTextBounds, markerWidth);
+        startx += legendEntryWidth + chart.getStyler().getLegendPadding();
+      }
+    }
+    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, oldHint);
+  }
+
+  @Override
+  public double getSeriesLegendRenderGraphicHeight(S series) {
+
+    return series.getLegendRenderType() == LegendRenderType.Box
+        ? BOX_SIZE
+        : axesChartStyler.getMarkerSize();
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Legend_HeatMap.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Legend_HeatMap.java
new file mode 100644
index 0000000000000000000000000000000000000000..4d4965230bf61767a421b27846ae3e9e7a8d7d40
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Legend_HeatMap.java
@@ -0,0 +1,440 @@
+package org.knowm.xchart.internal.chartpart;
+
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.LinearGradientPaint;
+import java.awt.RenderingHints;
+import java.awt.Shape;
+import java.awt.font.FontRenderContext;
+import java.awt.font.TextLayout;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.text.DecimalFormat;
+import java.text.Format;
+import java.util.function.BiFunction;
+import org.knowm.xchart.HeatMapChart;
+import org.knowm.xchart.HeatMapSeries;
+import org.knowm.xchart.style.HeatMapStyler;
+import org.knowm.xchart.style.Styler;
+
+public class Legend_HeatMap<ST extends HeatMapStyler, S extends HeatMapSeries>
+    extends Legend_<ST, S> {
+
+  private static final int LEGEND_MARGIN = 6;
+
+  private static final String SPLIT = " ~ ";
+
+  private Format format = new DecimalFormat("");
+
+  public Legend_HeatMap(Chart<ST, S> chart) {
+
+    super(chart);
+  }
+
+  @Override
+  public void paint(Graphics2D g) {
+
+    if (!chart.getStyler().isLegendVisible()) {
+      return;
+    }
+
+    if (chart.getSeriesMap().isEmpty()) {
+      return;
+    }
+
+    // if the area to draw a chart on is so small, don't even bother
+    if (chart.getPlot().getBounds().getWidth() < 30) {
+      return;
+    }
+    Rectangle2D bounds = getBounds();
+
+    switch (chart.getStyler().getLegendPosition()) {
+      case OutsideE:
+        xOffset = chart.getWidth() - bounds.getWidth() - LEGEND_MARGIN;
+        yOffset =
+            chart.getPlot().getBounds().getY()
+                + (chart.getPlot().getBounds().getHeight() - bounds.getHeight()) / 2.0;
+        break;
+      case OutsideS:
+        xOffset =
+            chart.getPlot().getBounds().getX()
+                + (chart.getPlot().getBounds().getWidth() - bounds.getWidth()) / 2.0;
+        yOffset = chart.getHeight() - bounds.getHeight() - LEGEND_MARGIN;
+        break;
+
+      default:
+        break;
+    }
+
+    // draw legend box background and border
+    Shape rect = new Rectangle2D.Double(xOffset, yOffset, bounds.getWidth(), bounds.getHeight());
+    g.setColor(chart.getStyler().getLegendBackgroundColor());
+    g.fill(rect);
+    g.setStroke(SOLID_STROKE);
+    g.setColor(chart.getStyler().getLegendBorderColor());
+    g.draw(rect);
+
+    doPaint(g);
+  }
+
+  @Override
+  public void doPaint(Graphics2D g) {
+    applyFormatting();
+
+    // Draw legend content inside legend box
+    double startx = xOffset + chart.getStyler().getLegendPadding();
+    double starty = yOffset + chart.getStyler().getLegendPadding();
+
+    Object oldHint = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
+    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+
+    Color[] rangeColors = chart.getStyler().getRangeColors();
+    HeatMapSeries heatMapSeries = ((HeatMapChart) chart).getHeatMapSeries();
+    if (chart.getStyler().isPiecewise()) {
+      paintPiecewise(g, startx, starty, rangeColors, heatMapSeries);
+    } else {
+      paintGradient(g, startx, starty, rangeColors, heatMapSeries);
+    }
+    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, oldHint);
+  }
+
+  @Override
+  public double getSeriesLegendRenderGraphicHeight(S series) {
+
+    return 0;
+  }
+
+  @Override
+  public Rectangle2D getBounds() {
+
+    applyFormatting();
+    double weight = 0;
+    double height = 0;
+    HeatMapSeries heatMapSeries = ((HeatMapChart) chart).getHeatMapSeries();
+    double min = heatMapSeries.getMin();
+    double max = heatMapSeries.getMax();
+    if (chart.getStyler().isPiecewise()) {
+      int splitNumber = chart.getStyler().getSplitNumber();
+      double step = (max - min) / splitNumber;
+      String text = "";
+      TextLayout textLayout = null;
+      BiFunction<Double, Double, String> formattingFunction =
+          chart.getStyler().isPiecewiseRanged()
+              ? (lower, upper) -> format.format(lower) + SPLIT + format.format(upper)
+              : (lower, upper) -> format.format(lower);
+      for (int i = 0; i < splitNumber; i++) {
+        if (i == 0) {
+          text = formattingFunction.apply(min, min + step);
+        } else if (i == splitNumber - 1) {
+          text = formattingFunction.apply(min + step * i, max);
+        } else {
+          text = formattingFunction.apply(min + step * i, min + step * (i + 1));
+        }
+        textLayout =
+            new TextLayout(
+                text, chart.getStyler().getLegendFont(), new FontRenderContext(null, true, false));
+
+        if (chart.getStyler().getLegendLayout() == Styler.LegendLayout.Vertical) {
+          weight = Math.max(weight, textLayout.getBounds().getWidth());
+          height +=
+              chart.getStyler().getLegendFont().getSize() + chart.getStyler().getLegendPadding();
+        } else {
+          weight +=
+              BOX_SIZE
+                  + chart.getStyler().getLegendPadding()
+                  + textLayout.getBounds().getWidth()
+                  + chart.getStyler().getLegendPadding();
+        }
+      }
+      if (chart.getStyler().getLegendLayout() == Styler.LegendLayout.Vertical) {
+        weight =
+            chart.getStyler().getLegendPadding()
+                + BOX_SIZE
+                + chart.getStyler().getLegendPadding()
+                + weight
+                + chart.getStyler().getLegendPadding();
+        height += chart.getStyler().getLegendPadding();
+      } else {
+        weight += chart.getStyler().getLegendPadding();
+        height =
+            chart.getStyler().getLegendPadding()
+                + chart.getStyler().getLegendFont().getSize()
+                + chart.getStyler().getLegendPadding();
+      }
+    } else {
+
+      TextLayout textLayoutMin =
+          new TextLayout(
+              min + "",
+              chart.getStyler().getLegendFont(),
+              new FontRenderContext(null, true, false));
+
+      TextLayout textLayoutMax =
+          new TextLayout(
+              max + "",
+              chart.getStyler().getLegendFont(),
+              new FontRenderContext(null, true, false));
+
+      if (chart.getStyler().getLegendLayout() == Styler.LegendLayout.Vertical) {
+        weight =
+            chart.getStyler().getLegendPadding()
+                + chart.getStyler().getGradientColorColumnWeight()
+                + chart.getStyler().getLegendPadding()
+                + Math.max(
+                    textLayoutMin.getBounds().getWidth(), textLayoutMax.getBounds().getWidth())
+                + chart.getStyler().getLegendPadding();
+        height =
+            chart.getStyler().getLegendPadding()
+                + chart.getStyler().getLegendFont().getSize()
+                + chart.getStyler().getGradientColorColumnHeight()
+                + chart.getStyler().getLegendFont().getSize()
+                + chart.getStyler().getLegendPadding();
+
+      } else {
+        weight =
+            chart.getStyler().getLegendPadding()
+                + textLayoutMin.getBounds().getWidth()
+                + chart.getStyler().getGradientColorColumnHeight()
+                + textLayoutMax.getBounds().getWidth()
+                + chart.getStyler().getLegendPadding();
+        height =
+            chart.getStyler().getLegendPadding()
+                + chart.getStyler().getLegendFont().getSize()
+                + chart.getStyler().getLegendPadding()
+                + chart.getStyler().getGradientColorColumnWeight()
+                + chart.getStyler().getLegendPadding();
+      }
+    }
+
+    return new Rectangle2D.Double(0, 0, weight, height);
+  }
+
+  private void paintPiecewise(
+      Graphics2D g,
+      double startx,
+      double starty,
+      Color[] rangeColors,
+      HeatMapSeries heatMapSeries) {
+
+    int splitNumber = chart.getStyler().getSplitNumber();
+    TextLayout textLayout = null;
+    Rectangle2D boxRect = null;
+    String text = "";
+    double min = heatMapSeries.getMin();
+    double max = heatMapSeries.getMax();
+    double step = (max - min) / splitNumber;
+    double y = 0;
+    AffineTransform orig = g.getTransform();
+    AffineTransform at = null;
+    Color splitColor = null;
+    int beginColorIndex = 0;
+    int endColorIndex = 1;
+    Color beginColor = null;
+    Color endColor = null;
+    int red = 0;
+    int green = 0;
+    int blue = 0;
+    double index = 0;
+    BiFunction<Double, Double, String> formattingFunction =
+        chart.getStyler().isPiecewiseRanged()
+            ? (lower, upper) -> format.format(lower) + SPLIT + format.format(upper)
+            : (lower, upper) -> format.format(lower);
+    for (int i = 0; i < splitNumber; i++) {
+      index = (double) i / splitNumber * rangeColors.length;
+      if (i == 0) {
+        text = formattingFunction.apply(min, min + step);
+        splitColor = rangeColors[0];
+      } else if (i == splitNumber - 1) {
+        text = formattingFunction.apply(min + step * i, max);
+        splitColor = rangeColors[rangeColors.length - 1];
+      } else {
+        text = formattingFunction.apply(min + step * i, min + step * (i + 1));
+        beginColorIndex = (int) index;
+        if (rangeColors.length != 1) {
+          endColorIndex = beginColorIndex + 1;
+        } else {
+          endColorIndex = beginColorIndex;
+        }
+
+        beginColor = rangeColors[beginColorIndex];
+        endColor = rangeColors[endColorIndex];
+        red =
+            (int)
+                (beginColor.getRed()
+                    + (index - (int) index) * (endColor.getRed() - beginColor.getRed()));
+        green =
+            (int)
+                (beginColor.getGreen()
+                    + (index - (int) index) * (endColor.getGreen() - beginColor.getGreen()));
+        blue =
+            (int)
+                (beginColor.getBlue()
+                    + (index - (int) index) * (endColor.getBlue() - beginColor.getBlue()));
+        splitColor = new Color(red, green, blue);
+      }
+
+      textLayout =
+          new TextLayout(
+              text, chart.getStyler().getLegendFont(), new FontRenderContext(null, true, false));
+
+      if (chart.getStyler().getLegendLayout() == Styler.LegendLayout.Vertical) {
+        y =
+            starty
+                + chart.getStyler().getLegendPadding() * (splitNumber - i - 1)
+                + chart.getStyler().getLegendFont().getSize() * (splitNumber - i - 1);
+      } else {
+        if (i > 0) {
+          startx += BOX_SIZE + chart.getStyler().getLegendPadding();
+        }
+        y = starty;
+      }
+      boxRect = new Rectangle2D.Double(startx, y, BOX_SIZE, textLayout.getBounds().getHeight());
+      g.setColor(splitColor);
+      g.fill(boxRect);
+
+      at = new AffineTransform();
+      at.translate(
+          startx + BOX_SIZE + chart.getStyler().getLegendPadding(),
+          y + textLayout.getBounds().getHeight());
+      g.transform(at);
+      g.setColor(chart.getStyler().getChartFontColor());
+      g.setFont(chart.getStyler().getLegendFont());
+      g.fill(textLayout.getOutline(null));
+      g.setTransform(orig);
+      if (chart.getStyler().getLegendLayout() == Styler.LegendLayout.Horizontal) {
+        startx += textLayout.getBounds().getWidth() + chart.getStyler().getLegendPadding();
+      }
+    }
+  }
+
+  private void paintGradient(
+      Graphics2D g,
+      double startx,
+      double starty,
+      Color[] rangeColors,
+      HeatMapSeries heatMapSeries) {
+
+    TextLayout textLayoutMin =
+        new TextLayout(
+            heatMapSeries.getMin() + "",
+            chart.getStyler().getLegendFont(),
+            new FontRenderContext(null, true, false));
+    Point2D start = null;
+    Point2D end = null;
+    Rectangle2D rect = null;
+    // paint gradient color Column
+    if (chart.getStyler().getLegendLayout() == Styler.LegendLayout.Vertical) {
+      start = new Point2D.Double(startx, starty + chart.getStyler().getGradientColorColumnHeight());
+      end = new Point2D.Double(startx, starty + chart.getStyler().getLegendFont().getSize());
+      rect =
+          new Rectangle2D.Double(
+              startx,
+              starty + chart.getStyler().getLegendFont().getSize(),
+              chart.getStyler().getGradientColorColumnWeight(),
+              chart.getStyler().getGradientColorColumnHeight());
+    } else {
+      start =
+          new Point2D.Double(
+              startx + textLayoutMin.getBounds().getWidth(),
+              starty
+                  + chart.getStyler().getLegendFont().getSize()
+                  + chart.getStyler().getLegendPadding());
+      end =
+          new Point2D.Double(
+              startx
+                  + textLayoutMin.getBounds().getWidth()
+                  + chart.getStyler().getGradientColorColumnHeight(),
+              starty
+                  + chart.getStyler().getLegendFont().getSize()
+                  + chart.getStyler().getLegendPadding());
+
+      rect =
+          new Rectangle2D.Double(
+              startx + textLayoutMin.getBounds().getWidth(),
+              starty
+                  + chart.getStyler().getLegendFont().getSize()
+                  + chart.getStyler().getLegendPadding(),
+              chart.getStyler().getGradientColorColumnHeight(),
+              chart.getStyler().getGradientColorColumnWeight());
+    }
+
+    float[] fractions = new float[rangeColors.length];
+    for (int i = 0; i < rangeColors.length; i++) {
+      if (i == 0) {
+        fractions[i] = 0;
+      } else if (i == rangeColors.length - 1) {
+        fractions[i] = 1;
+      } else {
+        fractions[i] = (float) i / (rangeColors.length - 1);
+      }
+    }
+    LinearGradientPaint lgp = new LinearGradientPaint(start, end, fractions, rangeColors);
+    g.setPaint(lgp);
+    g.fill(rect);
+
+    TextLayout textLayoutMax =
+        new TextLayout(
+            heatMapSeries.getMax() + "",
+            chart.getStyler().getLegendFont(),
+            new FontRenderContext(null, true, false));
+
+    double tx = 0;
+    double ty = 0;
+    // paint max
+    AffineTransform orig = g.getTransform();
+    AffineTransform at = new AffineTransform();
+    if (chart.getStyler().getLegendLayout() == Styler.LegendLayout.Vertical) {
+      tx =
+          startx
+              + chart.getStyler().getGradientColorColumnWeight()
+              + chart.getStyler().getLegendPadding();
+      ty = starty + textLayoutMax.getBounds().getHeight();
+    } else {
+      tx =
+          startx
+              + textLayoutMin.getBounds().getWidth()
+              + chart.getStyler().getGradientColorColumnHeight();
+      ty = starty + chart.getStyler().getLegendFont().getSize();
+    }
+    at.translate(tx, ty);
+    g.transform(at);
+    g.setColor(chart.getStyler().getChartFontColor());
+    g.setFont(chart.getStyler().getLegendFont());
+    g.fill(textLayoutMax.getOutline(null));
+    g.setTransform(orig);
+
+    // paint min
+    at = new AffineTransform();
+    if (chart.getStyler().getLegendLayout() == Styler.LegendLayout.Vertical) {
+      tx =
+          startx
+              + chart.getStyler().getGradientColorColumnWeight()
+              + chart.getStyler().getLegendPadding();
+      ty =
+          starty
+              + chart.getStyler().getGradientColorColumnHeight()
+              + chart.getStyler().getLegendFont().getSize() * 2;
+    } else {
+      tx = startx;
+      ty = starty + chart.getStyler().getLegendFont().getSize();
+    }
+    at.translate(tx, ty);
+    g.transform(at);
+    g.setColor(chart.getStyler().getChartFontColor());
+    g.setFont(chart.getStyler().getLegendFont());
+    g.fill(textLayoutMin.getOutline(null));
+    g.setTransform(orig);
+  }
+
+  private void applyFormatting() {
+    if (chart.getStyler().getHeatMapDecimalValueFormatter() != null) {
+      format = new Formatter_Custom(chart.getStyler().getHeatMapDecimalValueFormatter());
+    } else {
+      format = new DecimalFormat("");
+      if (chart.getStyler().getHeatMapValueDecimalPattern() != null) {
+        ((DecimalFormat) format).applyPattern(chart.getStyler().getHeatMapValueDecimalPattern());
+      }
+    }
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Legend_Marker.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Legend_Marker.java
new file mode 100644
index 0000000000000000000000000000000000000000..5716304f245ca505fdc420ab0a6988729802713d
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Legend_Marker.java
@@ -0,0 +1,165 @@
+package org.knowm.xchart.internal.chartpart;
+
+import java.awt.*;
+import java.awt.geom.Line2D;
+import java.awt.geom.Path2D;
+import java.awt.geom.Rectangle2D;
+import java.util.Map;
+import org.knowm.xchart.internal.chartpart.RenderableSeries.LegendRenderType;
+import org.knowm.xchart.internal.series.MarkerSeries;
+import org.knowm.xchart.style.Styler;
+import org.knowm.xchart.style.lines.SeriesLines;
+
+public class Legend_Marker<ST extends Styler, S extends MarkerSeries> extends Legend_<ST, S> {
+
+  private final ST axesChartStyler;
+
+  /**
+   * Constructor
+   *
+   * @param chart
+   */
+  public Legend_Marker(Chart<ST, S> chart) {
+
+    super(chart);
+    axesChartStyler = chart.getStyler();
+  }
+
+  @Override
+  public void doPaint(Graphics2D g) {
+
+    // Draw legend content inside legend box
+    double startx = xOffset + chart.getStyler().getLegendPadding();
+    double starty = yOffset + chart.getStyler().getLegendPadding();
+
+    Object oldHint = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
+    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+
+    Map<String, S> map = chart.getSeriesMap();
+    for (S series : map.values()) {
+
+      if (!series.isShowInLegend()) {
+        continue;
+      }
+      if (!series.isEnabled()) {
+        continue;
+      }
+
+      Map<String, Rectangle2D> seriesTextBounds = getSeriesTextBounds(series);
+      float legendEntryHeight =
+          getLegendEntryHeight(
+              seriesTextBounds,
+              ((series.getLegendRenderType() == LegendRenderType.Line
+                      || series.getLegendRenderType() == LegendRenderType.Scatter)
+                  ? axesChartStyler.getMarkerSize()
+                  : BOX_SIZE));
+
+      // paint line and marker
+      if (series.getLegendRenderType() == LegendRenderType.Line
+          || series.getLegendRenderType() == LegendRenderType.Scatter) {
+
+        // paint line
+        if (series.getLegendRenderType() == LegendRenderType.Line
+            && series.getLineStyle() != SeriesLines.NONE) {
+          g.setColor(series.getLineColor());
+          g.setStroke(series.getLineStyle());
+          Shape line =
+              new Line2D.Double(
+                  startx,
+                  starty + legendEntryHeight / 2.0,
+                  startx + chart.getStyler().getLegendSeriesLineLength(),
+                  starty + legendEntryHeight / 2.0);
+          g.draw(line);
+        }
+
+        // paint marker
+        if (series.getMarker() != null) {
+          g.setColor(series.getMarkerColor());
+          series
+              .getMarker()
+              .paint(
+                  g,
+                  startx + chart.getStyler().getLegendSeriesLineLength() / 2.0,
+                  starty + legendEntryHeight / 2.0,
+                  axesChartStyler.getMarkerSize());
+        }
+      } else { // bar/pie type series
+
+        // paint inner box
+        Shape rectSmall = new Rectangle2D.Double(startx, starty, BOX_SIZE, BOX_SIZE);
+        g.setColor(series.getFillColor());
+        g.fill(rectSmall);
+
+        // Draw outline
+        if (series.getLegendRenderType() != LegendRenderType.BoxNoOutline) {
+
+          // paint outer box
+          g.setColor(series.getLineColor());
+
+          // Only respect the existing stroke width up to BOX_OUTLINE_WIDTH, as the legend box is
+          // very small.
+          // Note the simplified conversion of line width from user space to device space.
+          BasicStroke existingLineStyle = series.getLineStyle();
+          BasicStroke newLineStyle =
+              new BasicStroke(
+                  Math.min(existingLineStyle.getLineWidth(), BOX_OUTLINE_WIDTH * 0.5f),
+                  existingLineStyle.getEndCap(),
+                  existingLineStyle.getLineJoin(),
+                  existingLineStyle.getMiterLimit(),
+                  existingLineStyle.getDashArray(),
+                  existingLineStyle.getDashPhase());
+
+          g.setPaint(series.getLineColor());
+          g.setStroke(newLineStyle);
+
+          Path2D.Double outlinePath = new Path2D.Double();
+
+          double lineOffset = existingLineStyle.getLineWidth() * 0.5;
+          outlinePath.moveTo(startx + lineOffset, starty + lineOffset);
+          outlinePath.lineTo(startx + lineOffset, starty + BOX_SIZE - lineOffset);
+          outlinePath.lineTo(startx + BOX_SIZE - lineOffset, starty + BOX_SIZE - lineOffset);
+          outlinePath.lineTo(startx + BOX_SIZE - lineOffset, starty + lineOffset);
+          outlinePath.closePath();
+
+          g.draw(outlinePath);
+        }
+      }
+
+      // paint series text
+      if (series.getLegendRenderType() == LegendRenderType.Line
+          || series.getLegendRenderType() == LegendRenderType.Scatter) {
+
+        double x =
+            startx
+                + chart.getStyler().getLegendSeriesLineLength()
+                + chart.getStyler().getLegendPadding();
+        paintSeriesText(g, seriesTextBounds, axesChartStyler.getMarkerSize(), x, starty);
+      } else { // bar/pie type series
+
+        double x = startx + BOX_SIZE + chart.getStyler().getLegendPadding();
+        paintSeriesText(g, seriesTextBounds, BOX_SIZE, x, starty);
+      }
+
+      if (chart.getStyler().getLegendLayout() == Styler.LegendLayout.Vertical) {
+        starty += legendEntryHeight + chart.getStyler().getLegendPadding();
+      } else {
+        int markerWidth = BOX_SIZE;
+        if (series.getLegendRenderType() == LegendRenderType.Line) {
+          markerWidth = chart.getStyler().getLegendSeriesLineLength();
+        }
+        float legendEntryWidth = getLegendEntryWidth(seriesTextBounds, markerWidth);
+        startx += legendEntryWidth + chart.getStyler().getLegendPadding();
+      }
+    }
+    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, oldHint);
+  }
+
+  @Override
+  public double getSeriesLegendRenderGraphicHeight(S series) {
+
+    return (series.getLegendRenderType() == LegendRenderType.Box
+            || series.getLegendRenderType() == LegendRenderType.BoxNoOutline)
+        ? BOX_SIZE
+        : axesChartStyler.getMarkerSize();
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Legend_OHLC.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Legend_OHLC.java
new file mode 100644
index 0000000000000000000000000000000000000000..88b544511369e23eed33d47ec38e88bc7e834ec0
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Legend_OHLC.java
@@ -0,0 +1,123 @@
+package org.knowm.xchart.internal.chartpart;
+
+import java.awt.*;
+import java.awt.geom.Line2D;
+import java.awt.geom.Rectangle2D;
+import java.util.Map;
+import org.knowm.xchart.OHLCSeries;
+import org.knowm.xchart.OHLCSeries.OHLCSeriesRenderStyle;
+import org.knowm.xchart.internal.chartpart.RenderableSeries.LegendRenderType;
+import org.knowm.xchart.style.OHLCStyler;
+import org.knowm.xchart.style.Styler;
+import org.knowm.xchart.style.lines.SeriesLines;
+
+public class Legend_OHLC<ST extends OHLCStyler, S extends OHLCSeries> extends Legend_<ST, S> {
+
+  private final ST axesChartStyler;
+
+  /**
+   * Constructor
+   *
+   * @param chart
+   */
+  public Legend_OHLC(Chart<ST, S> chart) {
+
+    super(chart);
+    axesChartStyler = chart.getStyler();
+  }
+
+  @Override
+  public void doPaint(Graphics2D g) {
+
+    // Draw legend content inside legend box
+    double startx = xOffset + chart.getStyler().getLegendPadding();
+    double starty = yOffset + chart.getStyler().getLegendPadding();
+
+    Object oldHint = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
+    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+
+    Map<String, S> map = chart.getSeriesMap();
+    for (S series : map.values()) {
+
+      if (!series.isShowInLegend()) {
+        continue;
+      }
+      if (!series.isEnabled()) {
+        continue;
+      }
+
+      Map<String, Rectangle2D> seriesTextBounds = getSeriesTextBounds(series);
+      float legendEntryHeight =
+          getLegendEntryHeight(seriesTextBounds, axesChartStyler.getMarkerSize());
+
+      if (series.getOhlcSeriesRenderStyle() != OHLCSeriesRenderStyle.Line) {
+
+        Shape rectSmall =
+            new Rectangle2D.Double(
+                startx,
+                starty + legendEntryHeight / 2.0 - BOX_SIZE / 2,
+                chart.getStyler().getLegendSeriesLineLength(),
+                BOX_SIZE);
+        if (series.getLineColor() == null) {
+          g.setColor(series.getUpColor());
+        } else {
+          g.setColor(series.getLineColor());
+        }
+        g.fill(rectSmall);
+      }
+
+      // paint line
+      if (series.getOhlcSeriesRenderStyle() == OHLCSeriesRenderStyle.Line
+          && series.getLegendRenderType() == LegendRenderType.Line
+          && series.getLineStyle() != SeriesLines.NONE) {
+        g.setColor(series.getLineColor());
+        g.setStroke(series.getLineStyle());
+        Shape line =
+            new Line2D.Double(
+                startx,
+                starty + legendEntryHeight / 2.0,
+                startx + chart.getStyler().getLegendSeriesLineLength(),
+                starty + legendEntryHeight / 2.0);
+        g.draw(line);
+      }
+
+      // paint marker
+      if (series.getOhlcSeriesRenderStyle() == OHLCSeriesRenderStyle.Line
+          && series.getMarker() != null) {
+        g.setColor(series.getMarkerColor());
+        series
+            .getMarker()
+            .paint(
+                g,
+                startx + chart.getStyler().getLegendSeriesLineLength() / 2.0,
+                starty + legendEntryHeight / 2.0,
+                axesChartStyler.getMarkerSize());
+      }
+
+      // paint series text
+      double x =
+          startx
+              + chart.getStyler().getLegendSeriesLineLength()
+              + chart.getStyler().getLegendPadding();
+      paintSeriesText(g, seriesTextBounds, axesChartStyler.getMarkerSize(), x, starty);
+
+      if (chart.getStyler().getLegendLayout() == Styler.LegendLayout.Vertical) {
+        starty += legendEntryHeight + chart.getStyler().getLegendPadding();
+      } else {
+        int markerWidth = chart.getStyler().getLegendSeriesLineLength();
+        float legendEntryWidth = getLegendEntryWidth(seriesTextBounds, markerWidth);
+        startx += legendEntryWidth + chart.getStyler().getLegendPadding();
+      }
+    }
+    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, oldHint);
+  }
+
+  @Override
+  public double getSeriesLegendRenderGraphicHeight(S series) {
+
+    return (series.getLegendRenderType() == LegendRenderType.Box
+            || series.getLegendRenderType() == LegendRenderType.BoxNoOutline)
+        ? BOX_SIZE
+        : axesChartStyler.getMarkerSize();
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Legend_Pie.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Legend_Pie.java
new file mode 100644
index 0000000000000000000000000000000000000000..4e5907caf33d48b10382e4640b74ccd6dd14461b
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Legend_Pie.java
@@ -0,0 +1,73 @@
+package org.knowm.xchart.internal.chartpart;
+
+import java.awt.*;
+import java.awt.geom.Rectangle2D;
+import java.util.Map;
+import org.knowm.xchart.internal.series.Series;
+import org.knowm.xchart.style.Styler;
+
+public class Legend_Pie<ST extends Styler, S extends Series> extends Legend_<ST, S> {
+
+  /**
+   * Constructor
+   *
+   * @param chart
+   */
+  public Legend_Pie(Chart<ST, S> chart) {
+
+    super(chart);
+  }
+
+  @Override
+  public void doPaint(Graphics2D g) {
+
+    // Draw legend content inside legend box
+    double startx = xOffset + chart.getStyler().getLegendPadding();
+    double starty = yOffset + chart.getStyler().getLegendPadding();
+
+    Object oldHint = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
+    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+
+    Map<String, S> map = chart.getSeriesMap();
+    for (S series : map.values()) {
+
+      if (!series.isShowInLegend()) {
+        continue;
+      }
+      if (!series.isEnabled()) {
+        continue;
+      }
+
+      Map<String, Rectangle2D> seriesTextBounds = getSeriesTextBounds(series);
+      float legendEntryHeight = getLegendEntryHeight(seriesTextBounds, BOX_SIZE);
+
+      // paint little box
+      Shape rectSmall = new Rectangle2D.Double(startx, starty, BOX_SIZE, BOX_SIZE);
+      g.setColor(series.getFillColor());
+      g.fill(rectSmall);
+
+      // paint series text
+      final double x = startx + BOX_SIZE + chart.getStyler().getLegendPadding();
+      paintSeriesText(g, seriesTextBounds, BOX_SIZE, x, starty);
+
+      if (chart.getStyler().getLegendLayout() == Styler.LegendLayout.Vertical) {
+        starty += legendEntryHeight + chart.getStyler().getLegendPadding();
+      } else {
+        int markerWidth = BOX_SIZE;
+        if (series.getLegendRenderType() == RenderableSeries.LegendRenderType.Line) {
+          markerWidth = chart.getStyler().getLegendSeriesLineLength();
+        }
+        float legendEntryWidth = getLegendEntryWidth(seriesTextBounds, markerWidth);
+        startx += legendEntryWidth + chart.getStyler().getLegendPadding();
+      }
+    }
+
+    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, oldHint);
+  }
+
+  @Override
+  public double getSeriesLegendRenderGraphicHeight(S series) {
+
+    return BOX_SIZE;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotContent_.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotContent_.java
new file mode 100644
index 0000000000000000000000000000000000000000..16e43f767acfbafa45703f093c3020d65cb47ed4
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotContent_.java
@@ -0,0 +1,101 @@
+package org.knowm.xchart.internal.chartpart;
+
+import java.awt.BasicStroke;
+import java.awt.Graphics2D;
+import java.awt.geom.Path2D;
+import java.awt.geom.Rectangle2D;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.internal.series.Series;
+import org.knowm.xchart.style.Styler;
+import org.knowm.xchart.style.XYStyler;
+
+public abstract class PlotContent_<ST extends Styler, S extends Series> implements ChartPart {
+
+  final Chart<ST, S> chart;
+  ToolTips toolTips; // tooltips are available for Category, OHLC and XY charts
+  ChartZoom chartZoom;
+  //  Cursor cursor;
+
+  // TODO create a PlotContent_Axes class to put this in.
+  static final BasicStroke ERROR_BAR_STROKE =
+      new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL);
+
+  /**
+   * Constructor
+   *
+   * @param chart - The Chart
+   */
+  PlotContent_(Chart<ST, S> chart) {
+
+    this.chart = chart;
+  }
+
+  protected abstract void doPaint(Graphics2D g);
+
+  @Override
+  public void paint(Graphics2D g) {
+
+    Rectangle2D bounds = getBounds();
+    // g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
+    // g.setColor(Color.red);
+    // g.draw(bounds);
+
+    // if the area to draw a chart on is so small, don't even bother
+    if (bounds.getWidth() < 30) {
+      return;
+    }
+
+    java.awt.Shape saveClip = g.getClip();
+    // this is for preventing the series to be drawn outside the plot area if min and max is
+    // overridden to fall inside the data range
+    if (saveClip != null) {
+      g.setClip(bounds.createIntersection(saveClip.getBounds2D()));
+    } else {
+      g.setClip(bounds);
+    }
+
+    if (chart.getStyler().isToolTipsEnabled() && toolTips != null) {
+      toolTips.clearData();
+    }
+
+    doPaint(g);
+
+    // after painting the plot content, paint the tooltip(s) if necessary
+    if (chart.getStyler().isToolTipsEnabled() && toolTips != null) {
+      toolTips.paint(g);
+    }
+
+    // TODO put this in PlotContent_XY.
+    if (chart instanceof XYChart && ((XYStyler) chart.getStyler()).isZoomEnabled()) {
+      chartZoom.paint(g);
+    }
+
+    g.setClip(saveClip);
+  }
+
+  @Override
+  public Rectangle2D getBounds() {
+
+    return chart.getPlot().getBounds();
+  }
+
+  /** Closes a path for area charts if one is available. */
+  void closePath(
+      Graphics2D g, Path2D.Double path, double previousX, Rectangle2D bounds, double yTopMargin) {
+
+    if (path != null) {
+      double yBottomOfArea = getBounds().getY() + getBounds().getHeight() - yTopMargin;
+      path.lineTo(previousX, yBottomOfArea);
+      path.closePath();
+      g.fill(path);
+    }
+  }
+
+  public void setToolTips(ToolTips toolTips) {
+    this.toolTips = toolTips;
+  }
+
+  public void setChartZoom(ChartZoom chartZoom) {
+    this.chartZoom = chartZoom;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotContent_Box.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotContent_Box.java
new file mode 100644
index 0000000000000000000000000000000000000000..1f201af25a92b9288ca3de307f9c8549b52fc2e2
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotContent_Box.java
@@ -0,0 +1,262 @@
+package org.knowm.xchart.internal.chartpart;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.Shape;
+import java.awt.geom.Area;
+import java.awt.geom.Line2D;
+import java.awt.geom.Rectangle2D;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import org.knowm.xchart.BoxSeries;
+import org.knowm.xchart.internal.Utils;
+import org.knowm.xchart.style.BoxStyler;
+
+public class PlotContent_Box<ST extends BoxStyler, S extends BoxSeries>
+    extends PlotContent_<ST, S> {
+
+  private final ST boxPlotStyler;
+  private double yMax;
+  private double yMin;
+  private double xLeftMargin;
+  private double yTopMargin;
+  private double yTickSpace;
+  private double xOffset;
+  private double yOffset;
+
+  PlotContent_Box(Chart<ST, S> chart) {
+
+    super(chart);
+    boxPlotStyler = chart.getStyler();
+  }
+
+  @Override
+  protected void doPaint(Graphics2D g) {
+
+    // X-Axis
+    double xTickSpace = boxPlotStyler.getPlotContentSize() * getBounds().getWidth();
+    xLeftMargin = Utils.getTickStartOffset((int) getBounds().getWidth(), xTickSpace);
+    // Y-Axis
+    yTickSpace = boxPlotStyler.getPlotContentSize() * getBounds().getHeight();
+    yTopMargin = Utils.getTickStartOffset((int) getBounds().getHeight(), yTickSpace);
+    boolean toolTipsEnabled = chart.getStyler().isToolTipsEnabled();
+    double gridStep = xTickSpace / chart.getSeriesMap().size();
+
+    BoxPlotDataCalculator<ST, S> boxPlotDataCalculator = new BoxPlotDataCalculator<>();
+    // Calculate box plot data for all series
+    List<BoxPlotData> boxPlotDataList =
+        boxPlotDataCalculator.calculate(chart.getSeriesMap(), boxPlotStyler);
+    BoxPlotData boxPlotData = null;
+    int boxPlotCounter = -1;
+    for (S series : chart.getSeriesMap().values()) {
+
+      if (!series.isEnabled()) {
+        continue;
+      }
+      boxPlotCounter++;
+      boxPlotData = boxPlotDataList.get(boxPlotCounter);
+      yMin = chart.getYAxis(series.getYAxisGroup()).getMin();
+      yMax = chart.getYAxis(series.getYAxisGroup()).getMax();
+
+      if (boxPlotStyler.isYAxisLogarithmic()) {
+        yMin = Math.log10(yMin);
+        yMax = Math.log10(yMax);
+      }
+      // data points
+      Collection<? extends Number> yData = series.getYData();
+      Iterator<? extends Number> yItr = yData.iterator();
+      while (yItr.hasNext()) {
+
+        Number next = yItr.next();
+
+        double yOrig = next.doubleValue();
+        double y;
+
+        if (boxPlotStyler.isYAxisLogarithmic()) {
+          y = Math.log10(yOrig);
+        } else {
+          y = yOrig;
+        }
+        double yTransfrom =
+            getBounds().getHeight() - (yTopMargin + (y - yMin) / (yMax - yMin) * yTickSpace);
+
+        // a check if all y data are the exact same values
+        if (Math.abs(yMax - yMin) / 5 == 0.0) {
+          yTransfrom = getBounds().getHeight() / 2.0;
+        }
+        xOffset = getBounds().getX() + xLeftMargin + boxPlotCounter * gridStep + gridStep / 2.0;
+        yOffset = getBounds().getY() + yTransfrom;
+
+        // Points drawn outside box plot area, not within the lower limit to the upper limit
+        if (yOrig > boxPlotData.upper || yOrig < boxPlotData.lower) {
+
+          Shape outPointLine1 =
+              new Line2D.Double(
+                  xOffset - boxPlotStyler.getMarkerSize(),
+                  yOffset,
+                  xOffset + boxPlotStyler.getMarkerSize(),
+                  yOffset);
+          Shape outPointLine2 =
+              new Line2D.Double(
+                  xOffset,
+                  yOffset - boxPlotStyler.getMarkerSize(),
+                  xOffset,
+                  yOffset + boxPlotStyler.getMarkerSize());
+          g.setColor(Color.RED);
+          g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
+          g.draw(outPointLine1);
+          g.draw(outPointLine2);
+
+          if (toolTipsEnabled) {
+            toolTips.addData(
+                xOffset,
+                yOffset,
+                series.getName()
+                    + ":"
+                    + System.lineSeparator()
+                    + chart.getYAxisFormat().format(yOrig));
+          }
+        } else if (chart.getStyler().getShowWithinAreaPoint()) {
+
+          // Points drawn in box plot area, between lower limit and upper limit
+          g.setColor(series.getMarkerColor());
+          series.getMarker().paint(g, xOffset, yOffset, boxPlotStyler.getMarkerSize());
+
+          if (toolTipsEnabled) {
+            toolTips.addData(
+                xOffset,
+                yOffset,
+                series.getName()
+                    + ":"
+                    + System.lineSeparator()
+                    + chart.getYAxisFormat().format(yOrig));
+          }
+        }
+      }
+
+      drawBoxPlot(g, series.getName(), boxPlotData);
+    }
+  }
+
+  private void drawBoxPlot(Graphics2D g, String seriesName, BoxPlotData boxPlotData) {
+
+    double q1YOffset =
+        getBounds().getY()
+            + getBounds().getHeight()
+            - (yTopMargin
+                + ((boxPlotStyler.isYAxisLogarithmic()
+                            ? Math.log10(boxPlotData.q1)
+                            : boxPlotData.q1)
+                        - yMin)
+                    / (yMax - yMin)
+                    * yTickSpace);
+    double medianYOffset =
+        getBounds().getY()
+            + getBounds().getHeight()
+            - (yTopMargin
+                + ((boxPlotStyler.isYAxisLogarithmic()
+                            ? Math.log10(boxPlotData.median)
+                            : boxPlotData.median)
+                        - yMin)
+                    / (yMax - yMin)
+                    * yTickSpace);
+    double q3YOffset =
+        getBounds().getY()
+            + getBounds().getHeight()
+            - (yTopMargin
+                + ((boxPlotStyler.isYAxisLogarithmic()
+                            ? Math.log10(boxPlotData.q3)
+                            : boxPlotData.q3)
+                        - yMin)
+                    / (yMax - yMin)
+                    * yTickSpace);
+    double upperYOffset =
+        getBounds().getY()
+            + getBounds().getHeight()
+            - (yTopMargin
+                + ((boxPlotStyler.isYAxisLogarithmic()
+                            ? Math.log10(boxPlotData.upper)
+                            : boxPlotData.upper)
+                        - yMin)
+                    / (yMax - yMin)
+                    * yTickSpace);
+    double lowerYOffset =
+        getBounds().getY()
+            + getBounds().getHeight()
+            - (yTopMargin
+                + ((boxPlotStyler.isYAxisLogarithmic()
+                            ? Math.log10(boxPlotData.lower)
+                            : boxPlotData.lower)
+                        - yMin)
+                    / (yMax - yMin)
+                    * yTickSpace);
+    Shape middleline =
+        new Line2D.Double(
+            xOffset - xLeftMargin, medianYOffset, xOffset + xLeftMargin, medianYOffset);
+    Shape maxLine =
+        new Line2D.Double(
+            xOffset - (xLeftMargin / 2.0),
+            upperYOffset,
+            xOffset + (xLeftMargin / 2.0),
+            upperYOffset);
+    Shape minLine =
+        new Line2D.Double(
+            xOffset - (xLeftMargin / 2.0),
+            lowerYOffset,
+            xOffset + (xLeftMargin / 2.0),
+            lowerYOffset);
+    Shape upLine = new Line2D.Double(xOffset, upperYOffset, xOffset, q3YOffset);
+    Shape lowLine = new Line2D.Double(xOffset, lowerYOffset, xOffset, q1YOffset);
+    Rectangle2D rect =
+        new Rectangle2D.Double(
+            xOffset - xLeftMargin, q3YOffset, 2.0 * xLeftMargin, q1YOffset - q3YOffset);
+    g.setColor(Color.BLUE);
+    g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
+    g.draw(rect);
+    g.setColor(Color.RED);
+    g.draw(middleline);
+    g.setColor(Color.BLACK);
+    g.draw(maxLine);
+    g.draw(minLine);
+    g.setStroke(
+        new BasicStroke(
+            1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND, 6.0f, new float[] {4f, 0f, 4f}, 6f));
+    g.draw(upLine);
+    g.draw(lowLine);
+
+    Area area = new Area();
+    area.add(new Area(maxLine.getBounds()));
+    area.add(new Area(maxLine.getBounds()));
+    area.add(new Area(minLine.getBounds()));
+    area.add(new Area(upLine.getBounds()));
+    area.add(new Area(lowLine.getBounds()));
+    area.add(new Area(rect.getBounds()));
+
+    if (boxPlotStyler.isToolTipsEnabled()) {
+      toolTips.addData(
+          area,
+          xOffset,
+          yOffset,
+          10,
+          seriesName
+              + ":"
+              + System.lineSeparator()
+              + "upper: "
+              + chart.getYAxisFormat().format(boxPlotData.upper)
+              + System.lineSeparator()
+              + "q3: "
+              + chart.getYAxisFormat().format(boxPlotData.q3)
+              + System.lineSeparator()
+              + "median: "
+              + chart.getYAxisFormat().format(boxPlotData.median)
+              + System.lineSeparator()
+              + "q1: "
+              + chart.getYAxisFormat().format(boxPlotData.q1)
+              + System.lineSeparator()
+              + "lower: "
+              + chart.getYAxisFormat().format(boxPlotData.lower));
+    }
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotContent_Bubble.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotContent_Bubble.java
new file mode 100644
index 0000000000000000000000000000000000000000..5f7c1b1059ee349e8a6a1b753f39fbd39d3676ef
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotContent_Bubble.java
@@ -0,0 +1,147 @@
+package org.knowm.xchart.internal.chartpart;
+
+import java.awt.*;
+import java.awt.geom.Ellipse2D;
+import java.util.Map;
+import org.knowm.xchart.BubbleSeries;
+import org.knowm.xchart.internal.Utils;
+import org.knowm.xchart.style.BubbleStyler;
+
+public class PlotContent_Bubble<ST extends BubbleStyler, S extends BubbleSeries>
+    extends PlotContent_<ST, S> {
+
+  private final ST stylerBubble;
+
+  /**
+   * Constructor
+   *
+   * @param chart
+   */
+  PlotContent_Bubble(Chart<ST, S> chart) {
+
+    super(chart);
+    stylerBubble = chart.getStyler();
+  }
+
+  @Override
+  public void doPaint(Graphics2D g) {
+
+    // X-Axis
+    double xTickSpace = stylerBubble.getPlotContentSize() * getBounds().getWidth();
+    double xLeftMargin = Utils.getTickStartOffset((int) getBounds().getWidth(), xTickSpace);
+
+    // Y-Axis
+    double yTickSpace = stylerBubble.getPlotContentSize() * getBounds().getHeight();
+    double yTopMargin = Utils.getTickStartOffset((int) getBounds().getHeight(), yTickSpace);
+
+    double xMin = chart.getXAxis().getMin();
+    double xMax = chart.getXAxis().getMax();
+
+    // logarithmic
+    if (stylerBubble.isXAxisLogarithmic()) {
+      xMin = Math.log10(xMin);
+      xMax = Math.log10(xMax);
+    }
+
+    Map<String, S> map = chart.getSeriesMap();
+    for (S series : map.values()) {
+
+      if (!series.isEnabled()) {
+        continue;
+      }
+
+      double yMin = chart.getYAxis(series.getYAxisGroup()).getMin();
+      double yMax = chart.getYAxis(series.getYAxisGroup()).getMax();
+      if (stylerBubble.isYAxisLogarithmic()) {
+        yMin = Math.log10(yMin);
+        yMax = Math.log10(yMax);
+      }
+
+      // data points
+
+      for (int i = 0; i < series.getXData().length; i++) {
+
+        double x = series.getXData()[i];
+        // System.out.println(x);
+        if (stylerBubble.isXAxisLogarithmic()) {
+          x = Math.log10(x);
+        }
+        // System.out.println(x);
+
+        if (Double.isNaN(series.getYData()[i])) {
+
+          // previousX = -Double.MAX_VALUE;
+          // previousY = -Double.MAX_VALUE;
+          continue;
+        }
+
+        double yOrig = series.getYData()[i];
+
+        double y;
+
+        // System.out.println(y);
+        if (stylerBubble.isYAxisLogarithmic()) {
+          y = Math.log10(yOrig);
+        } else {
+          y = yOrig;
+        }
+        // System.out.println(y);
+
+        double xTransform = xLeftMargin + ((x - xMin) / (xMax - xMin) * xTickSpace);
+        double yTransform =
+            getBounds().getHeight() - (yTopMargin + (y - yMin) / (yMax - yMin) * yTickSpace);
+
+        // a check if all x data are the exact same values
+        if (Math.abs(xMax - xMin) / 5 == 0.0) {
+          xTransform = getBounds().getWidth() / 2.0;
+        }
+
+        // a check if all y data are the exact same values
+        if (Math.abs(yMax - yMin) / 5 == 0.0) {
+          yTransform = getBounds().getHeight() / 2.0;
+        }
+
+        double xOffset = getBounds().getX() + xTransform;
+        double yOffset = getBounds().getY() + yTransform;
+        // System.out.println(xTransform);
+        // System.out.println(xOffset);
+        // System.out.println(yTransform);
+        // System.out.println(yOffset);
+        // System.out.println("---");
+
+        // previousX = xOffset;
+        // previousY = yOffset;
+
+        // paint bubbles
+        if (series.getExtraValues() != null) {
+
+          double bubbleSize = series.getExtraValues()[i];
+
+          // Draw it
+          Shape bubble =
+              new Ellipse2D.Double(
+                  xOffset - bubbleSize / 2, yOffset - bubbleSize / 2, bubbleSize, bubbleSize);
+          // set bubble color
+          g.setColor(series.getFillColor());
+          g.fill(bubble);
+
+          // set bubble color
+          g.setColor(series.getLineColor());
+          g.setStroke(series.getLineStyle());
+          g.draw(bubble);
+
+          // add tooltips
+          if (chart.getStyler().isToolTipsEnabled()) {
+            toolTips.addData(
+                bubble,
+                xOffset,
+                yOffset,
+                0,
+                chart.getXAxisFormat().format(x),
+                chart.getYAxisFormat().format(yOrig));
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotContent_Category_Bar.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotContent_Category_Bar.java
new file mode 100644
index 0000000000000000000000000000000000000000..0b0eeedbcc1c0f64136fa9c59a0cfe78504330b7
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotContent_Category_Bar.java
@@ -0,0 +1,639 @@
+package org.knowm.xchart.internal.chartpart;
+
+import java.awt.*;
+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.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import org.knowm.xchart.CategorySeries;
+import org.knowm.xchart.CategorySeries.CategorySeriesRenderStyle;
+import org.knowm.xchart.internal.Utils;
+import org.knowm.xchart.style.CategoryStyler;
+import org.knowm.xchart.style.lines.SeriesLines;
+
+public class PlotContent_Category_Bar<ST extends CategoryStyler, S extends CategorySeries>
+    extends PlotContent_<ST, S> {
+
+  private final ST stylerCategory;
+
+  /**
+   * Constructor
+   *
+   * @param chart
+   */
+  PlotContent_Category_Bar(Chart<ST, S> chart) {
+
+    super(chart);
+    this.stylerCategory = chart.getStyler();
+  }
+
+  @Override
+  public void doPaint(Graphics2D g) {
+
+    // X-Axis
+    double xTickSpace = stylerCategory.getPlotContentSize() * getBounds().getWidth();
+    // System.out.println("xTickSpace: " + xTickSpace);
+    double xLeftMargin = Utils.getTickStartOffset(getBounds().getWidth(), xTickSpace);
+    // System.out.println("xLeftMargin: " + xLeftMargin);
+    Map<String, S> seriesMap = chart.getSeriesMap();
+    int numCategories = seriesMap.values().iterator().next().getXData().size();
+    double gridStep = xTickSpace / numCategories;
+    // System.out.println("gridStep: " + gridStep);
+
+    // Y-Axis
+    double yMin = chart.getYAxis().getMin();
+    double yMax = chart.getYAxis().getMax();
+
+    // figure out the general form of the chart
+    final int chartForm; // 1=positive, -1=negative, 0=span
+    if (yMin > 0.0 && yMax > 0.0) {
+      chartForm = 1; // positive chart
+    } else if (yMin < 0.0 && yMax < 0.0) {
+      chartForm = -1; // negative chart
+    } else {
+      chartForm = 0; // span chart
+    }
+    // System.out.println(yMin);
+    // System.out.println(yMax);
+    // System.out.println("chartForm: " + chartForm);
+
+    double yTickSpace = stylerCategory.getPlotContentSize() * getBounds().getHeight();
+
+    double yTopMargin = Utils.getTickStartOffset(getBounds().getHeight(), yTickSpace);
+
+    // plot series
+    int seriesCounter = 0;
+    double[] accumulatedStackOffsetPos = new double[numCategories];
+    double[] accumulatedStackOffsetNeg = new double[numCategories];
+    double[] accumulatedStackOffsetTotalYOffset = new double[numCategories];
+
+    for (S series : seriesMap.values()) {
+
+      if (!series.isEnabled()) {
+        continue;
+      }
+
+      yMin = chart.getYAxis(series.getYAxisGroup()).getMin();
+      yMax = chart.getYAxis(series.getYAxisGroup()).getMax();
+      if (stylerCategory.isYAxisLogarithmic()) {
+        yMin = Math.log10(yMin);
+        yMax = Math.log10(yMax);
+      }
+
+      // for line series
+      double previousX = -Double.MAX_VALUE;
+      double previousY = -Double.MAX_VALUE;
+
+      Iterator<?> xItr = series.getXData().iterator();
+      Iterator<? extends Number> yItr = series.getYData().iterator();
+      Iterator<? extends Number> ebItr = null;
+      Collection<? extends Number> errorBars = series.getExtraValues();
+      if (errorBars != null) {
+        ebItr = errorBars.iterator();
+      }
+
+      // Stepped bars are drawn in chunks
+      // rather than for each inidivdual bar
+      ArrayList<Point2D.Double> steppedPath = null;
+      ArrayList<Point2D.Double> steppedReturnPath = null;
+      Path2D.Double path = null;
+      int categoryCounter = 0;
+      while (yItr.hasNext()) {
+
+        Number next = yItr.next();
+        // skip when a value is null
+        if (next == null) {
+
+          //          // for area charts
+          //          closePath(g, path, previousX, getBounds(), yTopMargin);
+          //          path = null;
+
+          previousX = -Double.MAX_VALUE;
+          previousY = -Double.MAX_VALUE;
+          categoryCounter++;
+          continue;
+        }
+        Object nextCat = xItr.next();
+
+        double yOrig = next.doubleValue();
+        double y;
+        if (stylerCategory.isYAxisLogarithmic()) {
+          y = Math.log10(yOrig);
+        } else {
+          y = yOrig;
+        }
+
+        double yTop = 0.0;
+        double yBottom = 0.0;
+        switch (chartForm) {
+          case 1: // positive chart
+            // check for points off the chart draw area due to a custom yMin
+            if (y < yMin) {
+              categoryCounter++;
+              continue;
+            }
+            yTop = y;
+            yBottom = yMin;
+            if (stylerCategory.isStacked()) {
+              yTop += accumulatedStackOffsetPos[categoryCounter];
+              yBottom += accumulatedStackOffsetPos[categoryCounter];
+              accumulatedStackOffsetPos[categoryCounter] += (yTop - yBottom);
+            }
+            break;
+          case -1: // negative chart
+            // check for points off the chart draw area due to a custom yMin
+            if (y > yMax) {
+              categoryCounter++;
+              continue;
+            }
+            yTop = yMax;
+            yBottom = y;
+            if (stylerCategory.isStacked()) {
+              yTop -= accumulatedStackOffsetNeg[categoryCounter];
+              yBottom -= accumulatedStackOffsetNeg[categoryCounter];
+              accumulatedStackOffsetNeg[categoryCounter] += (yTop - yBottom);
+            }
+            break;
+          case 0: // span chart
+            if (y >= 0.0) { // positive
+              yTop = y;
+              if (series.getChartCategorySeriesRenderStyle() == CategorySeriesRenderStyle.Bar
+                  || series.getChartCategorySeriesRenderStyle() == CategorySeriesRenderStyle.Stick
+                  || series.getChartCategorySeriesRenderStyle()
+                      == CategorySeriesRenderStyle.SteppedBar) {
+                yBottom = 0.0;
+              } else {
+                yBottom = y;
+              }
+              if (stylerCategory.isStacked() && !series.isOverlapped()) {
+                yTop += accumulatedStackOffsetPos[categoryCounter];
+                yBottom += accumulatedStackOffsetPos[categoryCounter];
+                accumulatedStackOffsetPos[categoryCounter] += (yTop - yBottom);
+              }
+            } else {
+              if (series.getChartCategorySeriesRenderStyle() == CategorySeriesRenderStyle.Bar
+                  || series.getChartCategorySeriesRenderStyle() == CategorySeriesRenderStyle.Stick
+                  || series.getChartCategorySeriesRenderStyle()
+                      == CategorySeriesRenderStyle.SteppedBar) {
+                yTop = 0.0;
+              } else {
+                yTop = y; // yTransform uses yTop, and for non-bars and stick, it's the same as
+                // yBottom.
+              }
+              yBottom = y;
+              if (stylerCategory.isStacked() && !series.isOverlapped()) {
+                yTop -= accumulatedStackOffsetNeg[categoryCounter];
+                yBottom -= accumulatedStackOffsetNeg[categoryCounter];
+                accumulatedStackOffsetNeg[categoryCounter] += (yTop - yBottom);
+              }
+            }
+            break;
+          default:
+            break;
+        }
+
+        double yTransform =
+            getBounds().getHeight() - (yTopMargin + (yTop - yMin) / (yMax - yMin) * yTickSpace);
+        double yOffset = getBounds().getY() + yTransform;
+
+        // Record the first series yOffset value, update totalYOffset value
+        // when next is greater then 0
+        if (seriesCounter == 0 || next.doubleValue() > 0) {
+          accumulatedStackOffsetTotalYOffset[categoryCounter] = yOffset;
+        }
+
+        double zeroTransform =
+            getBounds().getHeight() - (yTopMargin + (yBottom - yMin) / (yMax - yMin) * yTickSpace);
+        double zeroOffset = getBounds().getY() + zeroTransform;
+        double xOffset;
+        double barWidth;
+
+        {
+          double barWidthPercentage = stylerCategory.getAvailableSpaceFill();
+          // SteppedBars can not have any space between them
+          if (series.getChartCategorySeriesRenderStyle() == CategorySeriesRenderStyle.SteppedBar)
+            barWidthPercentage = 1;
+
+          if (stylerCategory.isOverlapped() || stylerCategory.isStacked()) {
+
+            barWidth = gridStep * barWidthPercentage;
+            double barMargin = gridStep * (1 - barWidthPercentage) / 2;
+            xOffset = getBounds().getX() + xLeftMargin + gridStep * categoryCounter++ + barMargin;
+          } else {
+
+            barWidth = gridStep / chart.getSeriesMap().size() * barWidthPercentage;
+            double barMargin = gridStep * (1 - barWidthPercentage) / 2;
+            xOffset =
+                getBounds().getX()
+                    + xLeftMargin
+                    + gridStep * categoryCounter++
+                    + seriesCounter * barWidth
+                    + barMargin;
+          }
+        }
+
+        // SteppedBar. Partially drawn in loop, partially after loop.
+        if (series.getChartCategorySeriesRenderStyle() == CategorySeriesRenderStyle.SteppedBar) {
+
+          double yCenter = zeroOffset;
+          double yTip = yOffset;
+          double stepLength = gridStep;
+
+          // yTip should be the value end, yCenter the center (0) end.
+          if (y < 0) {
+
+            yTip = zeroOffset;
+            yCenter = yOffset;
+          }
+
+          // Init in first iteration
+          if (steppedPath == null) {
+            steppedPath = new ArrayList<Point2D.Double>();
+            steppedReturnPath = new ArrayList<Point2D.Double>();
+            steppedPath.add(new Point2D.Double(xOffset, yCenter));
+          } else if (stylerCategory.isStacked()) {
+            // If a section of a stacked graph has changed from positive
+            // to negative or vice-versa, draw what we've stored up so far
+            // and resume with a blank slate.
+            if ((previousY > 0 && y < 0) || (previousY < 0 && y > 0)) {
+              drawStepBar(g, series, steppedPath, steppedReturnPath);
+
+              steppedPath.clear();
+              steppedReturnPath.clear();
+              steppedPath.add(new Point2D.Double(xOffset, yCenter));
+            }
+          }
+
+          if (!yItr.hasNext()) {
+
+            // Shift the far point of the final bar backwards
+            // by the same amount its start was shifted forward.
+            if (!(stylerCategory.isOverlapped() || stylerCategory.isStacked())) {
+
+              double singleBarStep = stepLength / (double) chart.getSeriesMap().size();
+              stepLength -= (seriesCounter * singleBarStep);
+            }
+          }
+
+          // Draw the vertical line to the new y position, and the horizontal flat of the bar.
+          steppedPath.add(new Point2D.Double(xOffset, yTip));
+          steppedPath.add(new Point2D.Double(xOffset + stepLength, yTip));
+
+          // Add the corresponding centerline (or equivalent) to the return path
+          // Could be simplfied and removed for non-stacked graphs
+          steppedReturnPath.add(new Point2D.Double(xOffset, yCenter));
+          steppedReturnPath.add(new Point2D.Double(xOffset + stepLength, yCenter));
+
+          previousY = y;
+        }
+        // paint series
+        else if (series.getChartCategorySeriesRenderStyle() == CategorySeriesRenderStyle.Bar) {
+
+          // paint bar
+          Path2D.Double barPath = new Path2D.Double();
+          barPath.moveTo(xOffset, yOffset);
+          barPath.lineTo(xOffset + barWidth, yOffset);
+          barPath.lineTo(xOffset + barWidth, zeroOffset);
+          barPath.lineTo(xOffset, zeroOffset);
+          barPath.closePath();
+
+          g.setColor(series.getFillColor());
+          g.fill(barPath);
+
+          // TODO maybe we want outlines of the bars?
+          // Legend markers now also draw the outline. It has been disabled for
+          // CategorySeriesRenderStyle.Bar
+          // in Legend_Marker.java. Modify accordingly if you are enabling bar outlines.
+          // if (series.getLineColor() != null) {
+          // path = new Path2D.Double();
+          // int halfLineWidth = (int) (series.getLineStyle().getLineWidth() / 2 + .1);
+          // path.moveTo(xOffset + halfLineWidth, yOffset + halfLineWidth);
+          // path.lineTo(xOffset + halfLineWidth + barWidth - halfLineWidth * 2, yOffset +
+          // halfLineWidth);
+          // path.lineTo(xOffset + halfLineWidth + barWidth - halfLineWidth * 2, zeroOffset -
+          // halfLineWidth);
+          // path.lineTo(xOffset + halfLineWidth, zeroOffset - halfLineWidth);
+          // path.closePath();
+          //
+          // g.setStroke(series.getLineStyle());
+          // g.setColor(series.getLineColor());
+          // g.draw(path);
+          // }
+
+          if (stylerCategory.isLabelsVisible() && next != null) {
+            drawLabels(
+                g,
+                next,
+                xOffset,
+                yOffset,
+                zeroOffset,
+                barWidth,
+                false,
+                false,
+                series.getFillColor());
+          }
+          if (stylerCategory.isLabelsVisible()
+              && stylerCategory.isShowStackSum()
+              && stylerCategory.isStacked()
+              && seriesCounter == (seriesMap.size() - 1)) {
+            Number totalNext =
+                accumulatedStackOffsetPos[categoryCounter - 1]
+                    - accumulatedStackOffsetNeg[categoryCounter - 1];
+            double totalYOffset = accumulatedStackOffsetTotalYOffset[categoryCounter - 1];
+            drawLabels(
+                g,
+                totalNext,
+                xOffset,
+                totalYOffset,
+                zeroOffset,
+                barWidth,
+                true,
+                true,
+                series.getFillColor());
+          }
+        } else if (CategorySeriesRenderStyle.Stick.equals(
+            series.getChartCategorySeriesRenderStyle())) {
+
+          // paint stick
+          if (series.getLineStyle() != SeriesLines.NONE) {
+
+            g.setColor(series.getLineColor());
+            g.setStroke(series.getLineStyle());
+            Shape line =
+                new Line2D.Double(
+                    xOffset + barWidth / 2, zeroOffset, xOffset + barWidth / 2, yOffset);
+            g.draw(line);
+          }
+
+          // paint marker
+          if (series.getMarker() != null) {
+            g.setColor(series.getMarkerColor());
+
+            if (y <= 0) {
+              series
+                  .getMarker()
+                  .paint(g, xOffset + barWidth / 2, zeroOffset, stylerCategory.getMarkerSize());
+            } else {
+              series
+                  .getMarker()
+                  .paint(g, xOffset + barWidth / 2, yOffset, stylerCategory.getMarkerSize());
+            }
+          }
+        } else {
+
+          // paint line
+          if (series.getChartCategorySeriesRenderStyle() == CategorySeriesRenderStyle.Line) {
+
+            if (series.getLineStyle() != SeriesLines.NONE) {
+
+              if (previousX != -Double.MAX_VALUE && previousY != -Double.MAX_VALUE) {
+                g.setColor(series.getLineColor());
+                g.setStroke(series.getLineStyle());
+                Shape line =
+                    new Line2D.Double(previousX, previousY, xOffset + barWidth / 2, yOffset);
+                g.draw(line);
+              }
+            }
+          }
+
+          // paint area
+          if (CategorySeriesRenderStyle.Area.equals(series.getChartCategorySeriesRenderStyle())) {
+
+            if (previousX != -Double.MAX_VALUE && previousY != -Double.MAX_VALUE) {
+
+              g.setColor(series.getFillColor());
+              double yBottomOfArea = getBounds().getY() + getBounds().getHeight() - yTopMargin;
+
+              if (path == null) {
+                path = new Path2D.Double();
+                path.moveTo(previousX, yBottomOfArea);
+                path.lineTo(previousX, previousY);
+              }
+              path.lineTo(xOffset + barWidth / 2, yOffset);
+            }
+            if (xOffset < previousX) {
+              throw new RuntimeException("X-Data must be in ascending order for Area Charts!!!");
+            }
+          }
+
+          previousX = xOffset + barWidth / 2;
+          //          previousX = xOffset ;
+          previousY = yOffset;
+
+          // paint marker
+          if (series.getMarker() != null) {
+            g.setColor(series.getMarkerColor());
+            series.getMarker().paint(g, previousX, previousY, stylerCategory.getMarkerSize());
+          }
+        }
+
+        // paint error bars
+
+        if (errorBars != null) {
+
+          double eb = ebItr.next().doubleValue();
+
+          // set error bar style
+          if (stylerCategory.isErrorBarsColorSeriesColor()) {
+            g.setColor(series.getLineColor());
+          } else {
+            g.setColor(stylerCategory.getErrorBarsColor());
+          }
+          g.setStroke(ERROR_BAR_STROKE);
+
+          // Top value
+          if (stylerCategory.isYAxisLogarithmic()) {
+            eb = Math.log10(eb);
+          }
+          double errorBarLength = ((eb) / (yMax - yMin) * yTickSpace);
+          double topEBOffset;
+          if (y > 0) {
+            topEBOffset = yOffset - errorBarLength;
+          } else {
+            topEBOffset = zeroOffset - errorBarLength;
+          }
+
+          // Bottom value
+          double bottomEBOffset;
+          if (y > 0) {
+            bottomEBOffset = yOffset + errorBarLength;
+          } else {
+            bottomEBOffset = zeroOffset + errorBarLength;
+          }
+
+          // Draw it
+          double errorBarOffset = xOffset + barWidth / 2;
+          Shape line =
+              new Line2D.Double(errorBarOffset, topEBOffset, errorBarOffset, bottomEBOffset);
+          g.draw(line);
+          line =
+              new Line2D.Double(
+                  errorBarOffset - 3, bottomEBOffset, errorBarOffset + 3, bottomEBOffset);
+          g.draw(line);
+          line =
+              new Line2D.Double(errorBarOffset - 3, topEBOffset, errorBarOffset + 3, topEBOffset);
+          g.draw(line);
+        }
+        // add data labels
+        if (chart.getStyler().isToolTipsEnabled()) {
+          Rectangle2D.Double rect =
+              new Rectangle2D.Double(xOffset, yOffset, barWidth, Math.abs(yOffset - zeroOffset));
+          double yPoint;
+          if (y < 0) {
+            yPoint = zeroOffset + 4 + 20 + 5;
+          } else {
+            yPoint = yOffset;
+          }
+
+          toolTips.addData(
+              rect,
+              xOffset,
+              yPoint,
+              barWidth,
+              chart.getXAxisFormat().format(nextCat),
+              chart.getYAxisFormat().format(yOrig));
+        }
+      }
+
+      // close any open path for area charts
+      g.setColor(series.getFillColor());
+      closePath(g, path, previousX, getBounds(), yTopMargin);
+
+      // Final drawing of a steppedBar is done after the main loop,
+      // as it continues on null and we may end up missing the final iteration.
+      if (steppedPath != null && !steppedReturnPath.isEmpty()) {
+        drawStepBar(g, series, steppedPath, steppedReturnPath);
+      }
+
+      seriesCounter++;
+    }
+  }
+
+  private void drawStepBarLine(Graphics2D g, S series, Path2D.Double path) {
+
+    if (series.getLineColor() != null) {
+      g.setColor(series.getLineColor());
+      g.setStroke(series.getLineStyle());
+      g.draw(path);
+    }
+  }
+
+  private void drawStepBarFill(Graphics2D g, S series, Path2D.Double path) {
+
+    if (series.getFillColor() != null) {
+      g.setColor(series.getFillColor());
+      g.fill(path);
+    }
+  }
+
+  private void drawStepBar(
+      Graphics2D g,
+      S series,
+      ArrayList<Point2D.Double> path,
+      ArrayList<Point2D.Double> returnPath) {
+
+    Collections.reverse(returnPath);
+
+    // The last point will be a duplicate of the first.
+    // Pop it before adding all to the main path
+    returnPath.remove(returnPath.size() - 1);
+    path.addAll(returnPath);
+
+    Path2D.Double drawPath = new Path2D.Double();
+
+    // Start draw path from first point, which can then be discarded
+    Point2D.Double startPoint = path.remove(0);
+    drawPath.moveTo(startPoint.getX(), startPoint.getY());
+
+    // Prepare complete fill path
+    for (Point2D.Double currentPoint : path) {
+
+      drawPath.lineTo(currentPoint.getX(), currentPoint.getY());
+    }
+    drawStepBarFill(g, series, drawPath);
+
+    // Remove the bottom portion and draw only the upper outline
+    drawPath.reset();
+    drawPath.moveTo(startPoint.getX(), startPoint.getY());
+    List<Point2D.Double> linePath = path.subList(0, path.size() - returnPath.size() + 1);
+    for (Point2D.Double currentPoint : linePath) {
+
+      drawPath.lineTo(currentPoint.getX(), currentPoint.getY());
+    }
+
+    drawStepBarLine(g, series, drawPath);
+  }
+
+  private void drawLabels(
+      Graphics2D g,
+      Number next,
+      double xOffset,
+      double yOffset,
+      double zeroOffset,
+      double barWidth,
+      boolean showStackSum,
+      boolean isTotalAnnotations,
+      Color seriesColor) {
+
+    String numberAsString = chart.getYAxisFormat().format(next);
+
+    TextLayout textLayout =
+        new TextLayout(
+            numberAsString,
+            stylerCategory.getLabelsFont(),
+            new FontRenderContext(null, true, false));
+
+    AffineTransform rot =
+        AffineTransform.getRotateInstance(
+            -1 * Math.toRadians(stylerCategory.getLabelsRotation()), 0, 0);
+    Shape shape = textLayout.getOutline(rot);
+    Rectangle2D labelRectangle = textLayout.getBounds();
+
+    double labelX;
+    if (stylerCategory.getLabelsRotation() > 0) {
+      double labelXDelta = labelRectangle.getHeight() / 2 + labelRectangle.getWidth() / 2;
+      double rotationOffset = labelXDelta * stylerCategory.getLabelsRotation() / 90;
+      labelX = xOffset + barWidth / 2 - labelRectangle.getWidth() / 2 + rotationOffset - 1;
+    } else {
+      labelX = xOffset + barWidth / 2 - labelRectangle.getWidth() / 2 - 1;
+    }
+    double labelY;
+    if (showStackSum) {
+      labelY = yOffset - 4;
+    } else {
+      if (next.doubleValue() >= 0.0) {
+        labelY =
+            yOffset
+                + (zeroOffset - yOffset) * (1 - stylerCategory.getLabelsPosition())
+                + labelRectangle.getHeight() * stylerCategory.getLabelsPosition();
+      } else {
+        labelY =
+            zeroOffset
+                - (zeroOffset - yOffset) * (1 - stylerCategory.getLabelsPosition())
+                + labelRectangle.getHeight() * (1 - stylerCategory.getLabelsPosition());
+      }
+    }
+    if (stylerCategory.isLabelsFontColorAutomaticEnabled()) {
+      g.setColor(stylerCategory.getLabelsFontColor(seriesColor));
+    } else {
+      g.setColor(stylerCategory.getLabelsFontColor());
+    }
+
+    g.setFont(stylerCategory.getLabelsFont());
+    AffineTransform orig = g.getTransform();
+    AffineTransform at = new AffineTransform();
+    at.translate(labelX, labelY);
+    g.transform(at);
+    g.fill(shape);
+    g.setTransform(orig);
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotContent_Dial.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotContent_Dial.java
new file mode 100644
index 0000000000000000000000000000000000000000..ab7ebc345a20cd1c471c711b44c42b3220ef6397
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotContent_Dial.java
@@ -0,0 +1,318 @@
+package org.knowm.xchart.internal.chartpart;
+
+import java.awt.*;
+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.text.NumberFormat;
+import java.util.Map;
+import org.knowm.xchart.DialSeries;
+import org.knowm.xchart.style.DialStyler;
+
+public class PlotContent_Dial<ST extends DialStyler, S extends DialSeries>
+    extends PlotContent_<ST, S> {
+
+  private final ST styler;
+  private final NumberFormat df = DecimalFormat.getPercentInstance();
+  private double height_r;
+
+  PlotContent_Dial(Chart<ST, S> chart) {
+
+    super(chart);
+    styler = chart.getStyler();
+  }
+
+  @Override
+  public void doPaint(Graphics2D g) {
+
+    Rectangle2D pieBounds = getPieBounds();
+
+    // get total
+    boolean axisTickLabelsVisible = styler.isAxisTickLabelsVisible();
+    double arcAngle = styler.getArcAngle();
+    double donutThickness = styler.getDonutThickness();
+    int axisTitlePadding = styler.getAxisTitlePadding();
+
+    double[] axisTickValues = styler.getAxisTickValues();
+    int markCount = axisTickValues.length;
+    String[] axisTickLabels = styler.getAxisTickLabels();
+
+    double[] fromArr = {styler.getMiddleFrom(), styler.getLowerFrom(), styler.getUpperFrom()};
+    double[] toArr = {styler.getMiddleTo(), styler.getLowerTo(), styler.getUpperTo()};
+    Color[] donutColorArr = {
+      styler.getMiddleColor(), styler.getLowerColor(), styler.getUpperColor()
+    };
+
+    double dountStartAngle = (arcAngle) / 2 + 90;
+    // draw shape
+    for (int i = 0; i < donutColorArr.length; i++) {
+      double from = fromArr[i];
+      double to = toArr[i];
+      if (to <= from || to < 0 || from < 0) {
+        continue;
+      }
+      double totalAngle = (to - from) * arcAngle;
+      double startAngle = dountStartAngle - from * arcAngle - totalAngle;
+      Shape donutSlice =
+          PlotContent_Pie.getDonutSliceShape(pieBounds, donutThickness, startAngle, totalAngle);
+      g.setColor(donutColorArr[i]);
+      g.fill(donutSlice);
+      g.draw(donutSlice);
+    }
+
+    double xDiameter = pieBounds.getWidth() / 2;
+    double yDiameter = pieBounds.getHeight() / 2;
+
+    double xCenter = pieBounds.getX() + xDiameter;
+    double yCenter = pieBounds.getY() + yDiameter;
+
+    if (markCount > 0 && styler.isAxisTicksMarksVisible()) {
+      g.setColor(styler.getAxisTickMarksColor());
+      g.setStroke(styler.getAxisTickMarksStroke());
+
+      for (int i = 0; i < markCount; i++) {
+        double angle = -axisTickValues[i] * arcAngle + (arcAngle) / 2 + 90;
+        double radians = Math.toRadians(angle);
+        double cos = Math.cos(radians);
+        double sin = Math.sin(radians);
+
+        double xOffset = xCenter + cos * xDiameter;
+        double yOffset = yCenter - sin * yDiameter;
+        double xOffset2 = xCenter + cos * xDiameter * (1 - donutThickness);
+        double yOffset2 = yCenter - sin * yDiameter * (1 - donutThickness);
+
+        Line2D.Double line = new Line2D.Double(xOffset2, yOffset2, xOffset, yOffset);
+        g.setColor(styler.getAxisTickMarksColor());
+        g.setStroke(styler.getAxisTickMarksStroke());
+        g.draw(line);
+
+        if (!axisTickLabelsVisible) {
+          continue;
+        }
+        String labels = axisTickLabels[i];
+
+        TextLayout textLayout =
+            new TextLayout(
+                labels, styler.getAxisTitleFont(), new FontRenderContext(null, true, false));
+        Shape shape = textLayout.getOutline(null);
+
+        Rectangle2D labelBounds = shape.getBounds2D();
+        double labelWidth = labelBounds.getWidth();
+        double labelHeight = labelBounds.getHeight();
+
+        // calculate corrections
+        double xc;
+        double yc = 0;
+        if (axisTickValues[i] < 0.49) {
+          xc = 0;
+        } else if (axisTickValues[i] > 0.51) {
+          xc = -labelWidth;
+        } else {
+          xc = -labelWidth / 2;
+          yc = labelHeight / 2;
+        }
+        xOffset2 = xCenter + cos * (xDiameter - axisTitlePadding) * (1 - donutThickness);
+        yOffset2 = yCenter - sin * (yDiameter - axisTitlePadding) * (1 - donutThickness);
+
+        double tx = xOffset2 + xc;
+        double ty = yOffset2 + yc + labelHeight / 2;
+
+        g.setColor(styler.getChartFontColor());
+        g.setFont(styler.getBaseFont());
+        AffineTransform orig = g.getTransform();
+        AffineTransform at = new AffineTransform();
+
+        at.translate(tx, ty);
+
+        g.transform(at);
+        g.fill(shape);
+        g.setTransform(orig);
+      }
+    }
+
+    Map<String, S> map = chart.getSeriesMap();
+    for (S series : map.values()) {
+      if (!series.isEnabled()) {
+        continue;
+      }
+
+      // draw title
+      if (styler.isAxisTitleVisible()) {
+        TextLayout textLayout =
+            new TextLayout(
+                series.getName(),
+                styler.getAxisTitleFont(),
+                new FontRenderContext(null, true, false));
+        Shape shape = textLayout.getOutline(null);
+
+        Rectangle2D labelBounds = shape.getBounds2D();
+        double labelWidth = labelBounds.getWidth();
+        double labelHeight = labelBounds.getHeight();
+
+        // calculate corrections
+        double tx = xCenter - labelWidth / 2;
+        double ty = yCenter - yDiameter / 2 + labelHeight / 2;
+
+        g.setColor(styler.getChartFontColor());
+        g.setFont(styler.getAxisTitleFont());
+        AffineTransform orig = g.getTransform();
+        AffineTransform at = new AffineTransform();
+
+        at.translate(tx, ty);
+
+        g.transform(at);
+        g.fill(shape);
+        g.setTransform(orig);
+      }
+
+      double value = series.getValue();
+      // draw title
+      if (styler.isLabelsVisible()) {
+        String label = series.getLabel();
+        if (label == null) {
+          if (styler.getDecimalPattern() != null) {
+            DecimalFormat df = new DecimalFormat(styler.getDecimalPattern());
+            label = df.format(value);
+          } else {
+            label = df.format(value);
+          }
+        }
+        if (!label.isEmpty()) {
+          TextLayout textLayout =
+              new TextLayout(
+                  label, styler.getLabelsFont(), new FontRenderContext(null, true, false));
+          Shape shape = textLayout.getOutline(null);
+
+          Rectangle2D labelBounds = shape.getBounds2D();
+          double labelnWidth = labelBounds.getWidth();
+          double labelHeight = labelBounds.getHeight();
+
+          double tx = xCenter - labelnWidth / 2;
+          double ty = yCenter + labelHeight / 2;
+          if (styler.getArcAngle() > 180) {
+            ty += height_r * Math.cos(Math.toRadians((360 - styler.getArcAngle()) / 2)) / 2;
+          } else {
+            ty -= yDiameter / 4;
+          }
+
+          g.setColor(styler.getChartFontColor());
+          g.setFont(styler.getAxisTitleFont());
+          AffineTransform orig = g.getTransform();
+          AffineTransform at = new AffineTransform();
+
+          at.translate(tx, ty);
+
+          g.transform(at);
+          g.fill(shape);
+          g.setTransform(orig);
+        }
+      }
+
+      // draw arrow
+      double angle = -value * arcAngle + (arcAngle) / 2 + 90;
+
+      double radians = Math.toRadians(angle);
+      double arrowLengthPercentage = styler.getArrowLengthPercentage();
+      double arrowArcAngle = styler.getArrowArcAngle();
+      double arrowArcPercentage = styler.getArrowArcPercentage();
+      double xOffset = xCenter + Math.cos(radians) * (xDiameter * arrowLengthPercentage);
+      double yOffset = yCenter - Math.sin(radians) * (yDiameter * arrowLengthPercentage);
+
+      Path2D.Double path = new Path2D.Double();
+      if (styler.isToolTipsEnabled()) {
+        String label = series.getLabel();
+        if (label == null) {
+          if (styler.getDecimalPattern() != null) {
+            DecimalFormat df = new DecimalFormat(styler.getDecimalPattern());
+            label = df.format(value);
+          } else {
+            label = df.format(value);
+          }
+        }
+        toolTips.addData(path, xOffset, yOffset + 10, 0, label);
+      }
+      path.moveTo(xCenter, yCenter);
+
+      double[][] angleValues = {
+        {-arrowArcAngle, arrowArcPercentage}, {0, 1}, {arrowArcAngle, arrowArcPercentage}
+      };
+      for (double[] ds : angleValues) {
+        radians = Math.toRadians(angle - ds[0]);
+
+        double diameterPerct = arrowLengthPercentage * ds[1];
+        xOffset = xCenter + Math.cos(radians) * (xDiameter * diameterPerct);
+        yOffset = yCenter - Math.sin(radians) * (yDiameter * diameterPerct);
+        path.lineTo(xOffset, yOffset);
+      }
+
+      path.closePath();
+      g.setColor(styler.getArrowColor());
+      g.fill(path);
+      g.setColor(styler.getArrowColor());
+      g.draw(path);
+    }
+  }
+
+  private Rectangle2D getPieBounds() {
+
+    double pieFillPercentage = styler.getPlotContentSize();
+    double halfBorderPercentage = (1 - pieFillPercentage) / 2.0;
+
+    double boundsWidth = getBounds().getWidth();
+    double boundsHeight = getBounds().getHeight();
+    double pieBounds_x = getBounds().getX();
+    double pieBounds_y = getBounds().getY();
+    double pieBounds_w = 0.0;
+    double pieBounds_h = 0.0;
+
+    double r = boundsHeight * pieFillPercentage / 2;
+    if (styler.isCircular()) {
+      if (styler.getArcAngle() > 180) {
+        double cos = Math.cos(Math.toRadians((360 - styler.getArcAngle()) / 2));
+        r = r + r * (1 - cos) / (1 + cos);
+        if (2 * r > boundsWidth * pieFillPercentage) {
+          r = boundsWidth * pieFillPercentage / 2;
+          pieBounds_x += boundsWidth * halfBorderPercentage;
+          pieBounds_y += (boundsHeight - r - r * cos) / 2;
+        } else {
+          pieBounds_x += boundsWidth / 2 - r;
+          pieBounds_y += boundsHeight * halfBorderPercentage;
+        }
+      } else {
+        r = boundsHeight * pieFillPercentage;
+        double sin = Math.sin(Math.toRadians(styler.getArcAngle() / 2));
+        if (2 * sin * r > boundsWidth * pieFillPercentage) {
+          r = boundsWidth * pieFillPercentage / 2 / sin;
+          pieBounds_x += boundsWidth * halfBorderPercentage - r * (1 - sin);
+          pieBounds_y += (boundsHeight - r) / 2;
+        } else {
+          pieBounds_x += boundsWidth / 2 - r;
+          pieBounds_y += boundsHeight * halfBorderPercentage;
+        }
+      }
+      pieBounds_w = r * 2;
+      pieBounds_h = r * 2;
+    } else {
+      pieBounds_x += boundsWidth * halfBorderPercentage;
+      pieBounds_y += boundsHeight * halfBorderPercentage;
+      pieBounds_w = boundsWidth * pieFillPercentage;
+
+      if (styler.getArcAngle() > 180) {
+
+        double cos = Math.cos(Math.toRadians((360 - styler.getArcAngle()) / 2));
+        r = r + r * (1 - cos) / (1 + cos);
+        pieBounds_h = r * 2;
+      } else {
+        pieBounds_h = boundsHeight * pieFillPercentage * 2;
+      }
+    }
+
+    height_r = r;
+
+    return new Rectangle2D.Double(pieBounds_x, pieBounds_y, pieBounds_w, pieBounds_h);
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotContent_HeatMap.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotContent_HeatMap.java
new file mode 100644
index 0000000000000000000000000000000000000000..ef2673561634f110f94447df53ef721208508e30
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotContent_HeatMap.java
@@ -0,0 +1,237 @@
+package org.knowm.xchart.internal.chartpart;
+
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.font.FontRenderContext;
+import java.awt.font.TextLayout;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Rectangle2D;
+import java.text.DecimalFormat;
+import java.util.List;
+import org.knowm.xchart.HeatMapChart;
+import org.knowm.xchart.HeatMapSeries;
+import org.knowm.xchart.internal.Utils;
+import org.knowm.xchart.style.HeatMapStyler;
+
+public class PlotContent_HeatMap<ST extends HeatMapStyler, S extends HeatMapSeries>
+    extends PlotContent_<ST, S> {
+
+  private final ST heatMapStyler;
+  private final DecimalFormat df = new DecimalFormat("");
+
+  /**
+   * Constructor
+   *
+   * @param chart
+   */
+  PlotContent_HeatMap(Chart<ST, S> chart) {
+
+    super(chart);
+    heatMapStyler = chart.getStyler();
+  }
+
+  @Override
+  protected void doPaint(Graphics2D g) {
+
+    // X-Axis
+    double xTickSpace = heatMapStyler.getPlotContentSize() * getBounds().getWidth();
+    double xLeftMargin = Utils.getTickStartOffset((int) getBounds().getWidth(), xTickSpace);
+
+    // Y-Axis
+    double yTickSpace = heatMapStyler.getPlotContentSize() * getBounds().getHeight();
+    double yTopMargin = Utils.getTickStartOffset((int) getBounds().getHeight(), yTickSpace);
+
+    if (heatMapStyler.getHeatMapValueDecimalPattern() != null) {
+      df.applyPattern(heatMapStyler.getHeatMapValueDecimalPattern());
+    }
+
+    Rectangle2D plotContentBounds = getBounds();
+
+    HeatMapSeries series = ((HeatMapChart) chart).getHeatMapSeries();
+    if (!series.isEnabled()) {
+      return;
+    }
+
+    int x = 0;
+    int y = 0;
+    Number value = 0.0;
+    List<? extends Number[]> list = series.getHeatData();
+    List<?> xData = series.getXData();
+    List<?> yData = series.getYData();
+    double plotContentBoundsWidth = plotContentBounds.getWidth();
+    double plotContentBoundsHeight = plotContentBounds.getHeight();
+    double rectWidth = (plotContentBoundsWidth - 2 * xLeftMargin) / xData.size();
+    double rectHeight = (plotContentBoundsHeight - 2 * yTopMargin) / yData.size();
+    double xOffset = 0.0;
+    double yOffset = 0.0;
+    Rectangle2D rect = null;
+    Color heatMapValueColor = null;
+    for (Number[] numbers : list) {
+      if (numbers == null) {
+        continue;
+      }
+      x = numbers[0].intValue();
+      y = numbers[1].intValue();
+      value = numbers[2].doubleValue();
+      if (x >= xData.size() || y >= yData.size()) {
+        continue;
+      }
+      xOffset = getBounds().getX() + xLeftMargin + rectWidth * x;
+      yOffset = getBounds().getY() + yTopMargin + rectHeight * (yData.size() - 1 - y);
+      rect = new Rectangle2D.Double(xOffset, yOffset, rectWidth, rectHeight);
+      heatMapValueColor = getColor(series, value.doubleValue());
+      g.setColor(heatMapValueColor);
+      g.fill(rect);
+
+      // draw rect border
+      if (heatMapStyler.isDrawBorder()) {
+        g.setColor(heatMapValueColor);
+        g.setStroke(SOLID_STROKE);
+        g.draw(rect);
+      }
+
+      // show heat data value
+      if (heatMapStyler.isShowValue()) {
+        showValue(g, rect, df.format(numbers[2]));
+      }
+
+      if (heatMapStyler.isToolTipsEnabled()) {
+        toolTips.addData(
+            rect,
+            rect.getCenterX(),
+            rect.getCenterY() + heatMapStyler.getToolTipFont().getSize(),
+            0,
+            series.getName()
+                + ": "
+                + chart.getXAxisFormat().format(xData.get(x))
+                + ", "
+                + chart.getYAxisFormat().format(yData.get(y))
+                + ", "
+                + df.format(numbers[2]));
+      }
+    }
+  }
+
+  private Color getColor(HeatMapSeries series, double value) {
+
+    Color color = null;
+    Color[] rangeColors = chart.getStyler().getRangeColors();
+    double min = series.getMin();
+    double max = series.getMax();
+    if (value <= min) {
+      color = rangeColors[0];
+    } else if (value >= max) {
+      color = rangeColors[rangeColors.length - 1];
+    } else {
+      double valueRation = (value - min) / (max - min);
+      if (heatMapStyler.isPiecewise()) {
+        color = getPiecewiseColor(rangeColors, valueRation);
+      } else {
+        color = getGradientColor(rangeColors, valueRation);
+      }
+    }
+    return color;
+  }
+
+  private Color getPiecewiseColor(Color[] rangeColors, double valueRation) {
+
+    Color color = null;
+    int splitNumber = chart.getStyler().getSplitNumber();
+    int splitNumberIndex = (int) (valueRation * splitNumber);
+    for (int i = 0; i < splitNumber; i++) {
+      if (splitNumberIndex == 0) {
+        color = rangeColors[0];
+        break;
+      } else if (splitNumberIndex == splitNumber - 1) {
+        color = rangeColors[rangeColors.length - 1];
+        break;
+      } else {
+        double index = (double) i / splitNumber * rangeColors.length;
+        int beginColorIndex = (int) index;
+        int endColorIndex = 0;
+        if (rangeColors.length != 1) {
+          endColorIndex = beginColorIndex + 1;
+        } else {
+          endColorIndex = beginColorIndex;
+        }
+
+        if (splitNumberIndex == i) {
+          Color beginColor = rangeColors[beginColorIndex];
+          Color endColor = rangeColors[endColorIndex];
+          int red =
+              (int)
+                  (beginColor.getRed()
+                      + (index - (int) index) * (endColor.getRed() - beginColor.getRed()));
+          int green =
+              (int)
+                  (beginColor.getGreen()
+                      + (index - (int) index) * (endColor.getGreen() - beginColor.getGreen()));
+          int blue =
+              (int)
+                  (beginColor.getBlue()
+                      + (index - (int) index) * (endColor.getBlue() - beginColor.getBlue()));
+          color = new Color(red, green, blue);
+          break;
+        }
+      }
+    }
+    return color;
+  }
+
+  private Color getGradientColor(Color[] rangeColors, double valueRation) {
+
+    double index = valueRation * (rangeColors.length - 1);
+    Color color = null;
+    int beginColorIndex = (int) index;
+    int endColorIndex = 0;
+
+    if (rangeColors.length != 1) {
+      endColorIndex = beginColorIndex + 1;
+    } else {
+      endColorIndex = beginColorIndex;
+    }
+
+    Color beginColor = rangeColors[beginColorIndex];
+    Color endColor = rangeColors[endColorIndex];
+
+    if ((int) index < rangeColors.length - 1) {
+      int red =
+          (int)
+              (beginColor.getRed()
+                  + (index - (int) index) * (endColor.getRed() - beginColor.getRed()));
+      int green =
+          (int)
+              (beginColor.getGreen()
+                  + (index - (int) index) * (endColor.getGreen() - beginColor.getGreen()));
+      int blue =
+          (int)
+              (beginColor.getBlue()
+                  + (index - (int) index) * (endColor.getBlue() - beginColor.getBlue()));
+      color = new Color(red, green, blue);
+    } else {
+      color = endColor;
+    }
+
+    return color;
+  }
+
+  private void showValue(Graphics2D g, Rectangle2D rect, String value) {
+
+    double rectCenterX = rect.getCenterX();
+    double rectCenterY = rect.getCenterY();
+
+    TextLayout textLayout =
+        new TextLayout(
+            value, heatMapStyler.getValueFont(), new FontRenderContext(null, true, false));
+    Rectangle2D annotationRectangle = textLayout.getBounds();
+    g.setColor(heatMapStyler.getValueFontColor());
+    AffineTransform orig = g.getTransform();
+    AffineTransform at = new AffineTransform();
+    at.translate(
+        rectCenterX - annotationRectangle.getWidth() / 2,
+        rectCenterY + annotationRectangle.getHeight() / 2);
+    g.transform(at);
+    g.fill(textLayout.getOutline(null));
+    g.setTransform(orig);
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotContent_OHLC.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotContent_OHLC.java
new file mode 100644
index 0000000000000000000000000000000000000000..c92c5d756733acbaf290daf203e6229cb54aaf4c
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotContent_OHLC.java
@@ -0,0 +1,324 @@
+package org.knowm.xchart.internal.chartpart;
+
+import java.awt.Graphics2D;
+import java.awt.geom.Area;
+import java.awt.geom.Line2D;
+import java.awt.geom.Rectangle2D;
+import java.util.Map;
+import org.knowm.xchart.OHLCSeries;
+import org.knowm.xchart.OHLCSeries.OHLCSeriesRenderStyle;
+import org.knowm.xchart.internal.Utils;
+import org.knowm.xchart.style.AxesChartStyler;
+import org.knowm.xchart.style.lines.SeriesLines;
+
+public class PlotContent_OHLC<ST extends AxesChartStyler, S extends OHLCSeries>
+    extends PlotContent_<ST, S> {
+
+  private final ST ohlcStyler;
+
+  /**
+   * Constructor
+   *
+   * @param chart
+   */
+  PlotContent_OHLC(Chart<ST, S> chart) {
+
+    super(chart);
+    ohlcStyler = chart.getStyler();
+  }
+
+  @Override
+  public void doPaint(Graphics2D g) {
+
+    // X-Axis
+    double xTickSpace = ohlcStyler.getPlotContentSize() * getBounds().getWidth();
+    double xLeftMargin = Utils.getTickStartOffset((int) getBounds().getWidth(), xTickSpace);
+
+    // Y-Axis
+    double yTickSpace = ohlcStyler.getPlotContentSize() * getBounds().getHeight();
+    double yTopMargin = Utils.getTickStartOffset((int) getBounds().getHeight(), yTickSpace);
+
+    double xMin = chart.getXAxis().getMin();
+    double xMax = chart.getXAxis().getMax();
+
+    // get ymax and ymin later because it depends on what yaxisgroup it belongs to.
+    double yMin;
+    double yMax;
+
+    Line2D.Double line = new Line2D.Double();
+    Rectangle2D.Double rect = new Rectangle2D.Double();
+
+    // logarithmic
+    if (ohlcStyler.isXAxisLogarithmic()) {
+      xMin = Math.log10(xMin);
+      xMax = Math.log10(xMax);
+    }
+
+    Map<String, S> map = chart.getSeriesMap();
+
+    for (S series : map.values()) {
+
+      if (!series.isEnabled()) {
+        continue;
+      }
+
+      yMin = chart.getYAxis(series.getYAxisGroup()).getMin();
+      yMax = chart.getYAxis(series.getYAxisGroup()).getMax();
+      if (ohlcStyler.isYAxisLogarithmic()) {
+        yMin = Math.log10(yMin);
+        yMax = Math.log10(yMax);
+      }
+
+      // Line Style
+      if (series.getOhlcSeriesRenderStyle() == OHLCSeriesRenderStyle.Line) {
+
+        // data points
+        double[] xData = series.getXData();
+        double[] yData = series.getYData();
+
+        double previousX = -Double.MAX_VALUE;
+        double previousY = -Double.MAX_VALUE;
+
+        for (int i = 0; i < xData.length; i++) {
+
+          double x = xData[i];
+          if (ohlcStyler.isXAxisLogarithmic()) {
+            x = Math.log10(x);
+          }
+          double next = yData[i];
+          if (Double.isNaN(next)) {
+
+            previousX = -Double.MAX_VALUE;
+            previousY = -Double.MAX_VALUE;
+            continue;
+          }
+
+          double yOrig = yData[i];
+
+          double y;
+
+          if (ohlcStyler.isYAxisLogarithmic()) {
+            y = Math.log10(yOrig);
+          } else {
+            y = yOrig;
+          }
+
+          // System.out.println(y);
+
+          double xTransform = xLeftMargin + ((x - xMin) / (xMax - xMin) * xTickSpace);
+          double yTransform =
+              getBounds().getHeight() - (yTopMargin + (y - yMin) / (yMax - yMin) * yTickSpace);
+
+          // a check if all x data are the exact same values
+          if (Math.abs(xMax - xMin) / 5 == 0.0) {
+            xTransform = getBounds().getWidth() / 2.0;
+          }
+
+          // a check if all y data are the exact same values
+          if (Math.abs(yMax - yMin) / 5 == 0.0) {
+            yTransform = getBounds().getHeight() / 2.0;
+          }
+
+          double xOffset = getBounds().getX() + xTransform;
+          double yOffset = getBounds().getY() + yTransform;
+
+          if (previousX != -Double.MAX_VALUE && previousY != -Double.MAX_VALUE) {
+            g.setColor(series.getLineColor());
+            g.setStroke(series.getLineStyle());
+            line.setLine(previousX, previousY, xOffset, yOffset);
+            g.draw(line);
+          }
+
+          previousX = xOffset;
+          previousY = yOffset;
+
+          // paint marker
+          if (series.getMarker() != null) {
+            g.setColor(series.getMarkerColor());
+            series.getMarker().paint(g, xOffset, yOffset, ohlcStyler.getMarkerSize());
+          }
+
+          // add tooltips
+          if (chart.getStyler().isToolTipsEnabled()) {
+            toolTips.addData(
+                xOffset,
+                yOffset,
+                chart.getXAxisFormat().format(x),
+                chart.getYAxisFormat(series.getYAxisDecimalPattern()).format(yOrig));
+          }
+        }
+      } else {
+
+        // data points
+        double[] xData = series.getXData();
+        double[] openData = series.getOpenData();
+        double[] highData = series.getHighData();
+        double[] lowData = series.getLowData();
+        double[] closeData = series.getCloseData();
+
+        double candleHalfWidth =
+            Math.max(3, xTickSpace / xData.length / 2 - ohlcStyler.getAxisTickPadding());
+        float lineWidth = Math.max(2, series.getLineStyle().getLineWidth());
+
+        for (int i = 0; i < xData.length; i++) {
+
+          double x = xData[i];
+          if (ohlcStyler.isXAxisLogarithmic()) {
+            x = Math.log10(x);
+          }
+
+          if (Double.isNaN(closeData[i])) {
+            continue;
+          }
+
+          double openOrig = openData[i];
+          double highOrig = highData[i];
+          double lowOrig = lowData[i];
+          double closeOrig = closeData[i];
+
+          double openY;
+          double highY;
+          double lowY;
+          double closeY;
+
+          // System.out.println(y);
+          if (ohlcStyler.isYAxisLogarithmic()) {
+            openY = Math.log10(openOrig);
+            highY = Math.log10(highOrig);
+            lowY = Math.log10(lowOrig);
+            closeY = Math.log10(closeOrig);
+          } else {
+            openY = openOrig;
+            highY = highOrig;
+            lowY = lowOrig;
+            closeY = closeOrig;
+          }
+
+          double xTransform = xLeftMargin + ((x - xMin) / (xMax - xMin) * xTickSpace);
+          double openTransform =
+              getBounds().getHeight() - (yTopMargin + (openY - yMin) / (yMax - yMin) * yTickSpace);
+          double highTransform =
+              getBounds().getHeight() - (yTopMargin + (highY - yMin) / (yMax - yMin) * yTickSpace);
+          double lowTransform =
+              getBounds().getHeight() - (yTopMargin + (lowY - yMin) / (yMax - yMin) * yTickSpace);
+          double closeTransform =
+              getBounds().getHeight() - (yTopMargin + (closeY - yMin) / (yMax - yMin) * yTickSpace);
+
+          // a check if all x data are the exact same values
+          if (Math.abs(xMax - xMin) / 5 == 0.0) {
+            xTransform = getBounds().getWidth() / 2.0;
+          }
+
+          // a check if all y data are the exact same values
+          if (Math.abs(yMax - yMin) / 5 == 0.0) {
+            openTransform = getBounds().getHeight() / 2.0;
+            highTransform = getBounds().getHeight() / 2.0;
+            lowTransform = getBounds().getHeight() / 2.0;
+            closeTransform = getBounds().getHeight() / 2.0;
+          }
+
+          double xOffset = getBounds().getX() + xTransform;
+          double openOffset = getBounds().getY() + openTransform;
+          double highOffset = getBounds().getY() + highTransform;
+          double lowOffset = getBounds().getY() + lowTransform;
+          double closeOffset = getBounds().getY() + closeTransform;
+
+          Area toolTipArea = null;
+
+          // paint candle
+          if (series.getLineStyle() != SeriesLines.NONE) {
+
+            if (xOffset != -Double.MAX_VALUE
+                && openOffset != -Double.MAX_VALUE
+                && highOffset != -Double.MAX_VALUE
+                && lowOffset != -Double.MAX_VALUE
+                && closeOffset != -Double.MAX_VALUE) {
+
+              if (series.getLineColor() != null) {
+                g.setColor(series.getLineColor());
+              } else {
+
+                if (closeOrig > openOrig) {
+                  g.setColor(series.getUpColor());
+                } else {
+                  g.setColor(series.getDownColor());
+                }
+              }
+              g.setStroke(series.getLineStyle());
+              // high to low line
+              line.setLine(xOffset, highOffset, xOffset, lowOffset);
+              g.draw(line);
+              final double xStart = xOffset - candleHalfWidth;
+              final double xEnd = xOffset + candleHalfWidth;
+              if (chart.getStyler().isToolTipsEnabled()) {
+                rect.setRect(
+                    xOffset - lineWidth / 2, highOffset, lineWidth, lowOffset - highOffset);
+                toolTipArea = new Area(rect);
+              }
+              if (series.getOhlcSeriesRenderStyle() == OHLCSeries.OHLCSeriesRenderStyle.Candle) {
+                // Candle style
+                if (closeOrig > openOrig) {
+                  g.setPaint(series.getUpColor());
+                } else {
+                  g.setPaint(series.getDownColor());
+                }
+                rect.setRect(
+                    xStart,
+                    Math.min(openOffset, closeOffset),
+                    xEnd - xStart,
+                    Math.abs(closeOffset - openOffset));
+                g.fill(rect);
+                // add data labels
+                if (chart.getStyler().isToolTipsEnabled()) {
+                  toolTipArea.add(new Area(rect));
+                }
+
+              } else { // HiLo style
+                // lines only
+                line.setLine(xStart, openOffset, xOffset, openOffset);
+                g.draw(line);
+                line.setLine(xOffset, closeOffset, xEnd, closeOffset);
+                g.draw(line);
+                if (chart.getStyler().isToolTipsEnabled()) {
+                  rect.setRect(xStart, openOffset - lineWidth / 2, xOffset - xStart, lineWidth);
+                  toolTipArea.add(new Area(rect));
+                  rect.setRect(xOffset, closeOffset - lineWidth / 2, xEnd - xOffset, lineWidth);
+                  toolTipArea.add(new Area(rect));
+                }
+              }
+            }
+          }
+
+          // add tooltips
+          if (chart.getStyler().isToolTipsEnabled()) {
+
+            StringBuilder sb = new StringBuilder();
+            if (series.getVolumeData() != null) {
+              sb.append(chart.getXAxisFormat().format(x));
+              sb.append(System.lineSeparator()).append("Volume: " + series.getVolumeData()[i]);
+              sb.append(System.lineSeparator()).append(" ").append(System.lineSeparator());
+            }
+            sb.append(chart.getXAxisFormat().format(x));
+            sb.append(System.lineSeparator()).append(series.getName()).append(":");
+            sb.append(System.lineSeparator())
+                .append("open: ")
+                .append(chart.getYAxisFormat().format(openOrig));
+            sb.append(System.lineSeparator())
+                .append("close: ")
+                .append(chart.getYAxisFormat().format(closeOrig));
+            sb.append(System.lineSeparator())
+                .append("low: ")
+                .append(chart.getYAxisFormat().format(lowOrig));
+            sb.append(System.lineSeparator())
+                .append("high: ")
+                .append(chart.getYAxisFormat().format(highOrig));
+            toolTips.addData(toolTipArea, xOffset, highOffset, 0, sb.toString());
+          }
+        }
+      }
+    }
+  }
+
+  // line chart drawing logic
+  private void paintLine(Graphics2D g, S series) {}
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotContent_Pie.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotContent_Pie.java
new file mode 100644
index 0000000000000000000000000000000000000000..94d6f52ddf4ca5e3fa6de952be22ececbc0fd4cd
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotContent_Pie.java
@@ -0,0 +1,454 @@
+package org.knowm.xchart.internal.chartpart;
+
+import java.awt.*;
+import java.awt.font.FontRenderContext;
+import java.awt.font.TextLayout;
+import java.awt.geom.*;
+import java.awt.geom.Arc2D.Double;
+import java.text.DecimalFormat;
+import java.util.Map;
+import org.knowm.xchart.PieSeries;
+import org.knowm.xchart.PieSeries.PieSeriesRenderStyle;
+import org.knowm.xchart.style.PieStyler;
+import org.knowm.xchart.style.PieStyler.ClockwiseDirectionType;
+import org.knowm.xchart.style.PieStyler.LabelType;
+
+public class PlotContent_Pie<ST extends PieStyler, S extends PieSeries>
+    extends PlotContent_<ST, S> {
+
+  private final ST pieStyler;
+  private final DecimalFormat df = new DecimalFormat("#.0");
+
+  /**
+   * Constructor
+   *
+   * @param chart
+   */
+  PlotContent_Pie(Chart<ST, S> chart) {
+
+    super(chart);
+    pieStyler = chart.getStyler();
+  }
+
+  // TODO get rid of this
+  public static Shape getDonutSliceShape(
+      Rectangle2D pieBounds, double thickness, double start, double extent) {
+
+    thickness = thickness / 2;
+
+    GeneralPath generalPath = new GeneralPath();
+    GeneralPath dummy = new GeneralPath(); // used to find arc endpoints
+
+    double x = pieBounds.getX();
+    double y = pieBounds.getY();
+    double width = pieBounds.getWidth();
+    double height = pieBounds.getHeight();
+    Shape outer = new Arc2D.Double(x, y, width, height, start, extent, Arc2D.OPEN);
+    double wt = width * thickness;
+    double ht = height * thickness;
+    Shape inner =
+        new Arc2D.Double(
+            x + wt, y + ht, width - 2 * wt, height - 2 * ht, start + extent, -extent, Arc2D.OPEN);
+    generalPath.append(outer, false);
+
+    dummy.append(
+        new Arc2D.Double(
+            x + wt, y + ht, width - 2 * wt, height - 2 * ht, start, extent, Arc2D.OPEN),
+        false);
+
+    Point2D point = dummy.getCurrentPoint();
+
+    if (point != null) {
+      generalPath.lineTo(point.getX(), point.getY());
+    }
+    generalPath.append(inner, false);
+
+    dummy.append(new Arc2D.Double(x, y, width, height, start + extent, -extent, Arc2D.OPEN), false);
+
+    point = dummy.getCurrentPoint();
+
+    if (point != null) {
+      generalPath.lineTo(point.getX(), point.getY());
+    }
+
+    return generalPath;
+  }
+
+  @Override
+  public void doPaint(Graphics2D g) {
+
+    // Apply the given pattern to decimalPattern if decimalPattern is not null
+    if (pieStyler.getDecimalPattern() != null) {
+      df.applyPattern(pieStyler.getDecimalPattern());
+    }
+
+    // pie getBounds()
+    double pieFillPercentage = pieStyler.getPlotContentSize();
+
+    double halfBorderPercentage = (1 - pieFillPercentage) / 2.0;
+    double width =
+        pieStyler.isCircular()
+            ? Math.min(getBounds().getWidth(), getBounds().getHeight())
+            : getBounds().getWidth();
+    double height =
+        pieStyler.isCircular()
+            ? Math.min(getBounds().getWidth(), getBounds().getHeight())
+            : getBounds().getHeight();
+
+    Rectangle2D pieBounds =
+        new Rectangle2D.Double(
+            getBounds().getX()
+                + getBounds().getWidth() / 2
+                - width / 2
+                + halfBorderPercentage * width,
+            getBounds().getY()
+                + getBounds().getHeight() / 2
+                - height / 2
+                + halfBorderPercentage * height,
+            width * pieFillPercentage,
+            height * pieFillPercentage);
+
+    //    g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
+    //    g.setColor(Color.black);
+    //    g.draw(pieBounds);
+
+    //    g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
+    //    g.setColor(Color.red);
+    //    g.draw(getBounds());
+
+    // get total
+    double total = 0.0;
+
+    Map<String, S> map = chart.getSeriesMap();
+    for (S series : map.values()) {
+
+      if (!series.isEnabled() || series.getValue() == null) {
+        continue;
+      }
+      total += series.getValue().doubleValue();
+    }
+
+    // draw pie slices
+    double startAngle = pieStyler.getStartAngleInDegrees() + 90;
+    paintSlices(g, pieBounds, total, startAngle);
+    paintLabels(g, pieBounds, total, startAngle);
+    paintSum(g, pieBounds, total);
+  }
+
+  private void paintSlices(Graphics2D g, Rectangle2D pieBounds, double total, double startAngle) {
+
+    double borderAngle = startAngle;
+
+    Map<String, S> map = chart.getSeriesMap();
+    double xCenter = pieBounds.getX() + pieBounds.getWidth() / 2;
+    double yCenter = pieBounds.getY() + pieBounds.getHeight() / 2;
+    for (S series : map.values()) {
+
+      if (!series.isEnabled() || series.getValue() == null) {
+        continue;
+      }
+
+      Number y = series.getValue();
+
+      // draw slice/donut
+      double arcAngle = (y.doubleValue() * 360 / total);
+      g.setColor(series.getFillColor());
+
+      // CLOCKWISE, startAngle minus arcAngle
+      if (ClockwiseDirectionType.CLOCKWISE == pieStyler.getClockwiseDirectionType()) {
+        startAngle -= arcAngle;
+      }
+
+      Shape toolTipShape;
+      // slice
+      if (PieSeriesRenderStyle.Pie == series.getChartPieSeriesRenderStyle()) {
+
+        Double pieShape =
+            new Arc2D.Double(
+                pieBounds.getX(),
+                pieBounds.getY(),
+                pieBounds.getWidth(),
+                pieBounds.getHeight(),
+                startAngle,
+                arcAngle,
+                Arc2D.PIE);
+        g.fill(pieShape);
+        g.setColor(pieStyler.getPlotBackgroundColor());
+        g.draw(pieShape);
+        toolTipShape = pieShape;
+      }
+
+      // donut
+      else {
+
+        Shape donutSlice =
+            getDonutSliceShape(pieBounds, pieStyler.getDonutThickness(), startAngle, arcAngle);
+        g.fill(donutSlice);
+        g.setColor(pieStyler.getPlotBackgroundColor());
+        g.draw(donutSlice);
+        toolTipShape = donutSlice;
+      }
+
+      // TOOLTIPS ////////////////////////////////////////////////////
+      // TOOLTIPS ////////////////////////////////////////////////////
+      // TOOLTIPS ////////////////////////////////////////////////////
+
+      if (pieStyler.isToolTipsEnabled()) {
+        // add data labels
+        // maybe another option to construct this label
+        // TODO use tool tip label type enum and customize this label
+        String toolTipLabel = series.getName() + " (" + df.format(y) + ")";
+
+        double angle = (arcAngle + startAngle) - arcAngle / 2;
+        double xOffset =
+            xCenter
+                + Math.cos(Math.toRadians(angle))
+                    * (pieBounds.getWidth() / 2 * pieStyler.getLabelsDistance());
+        double yOffset =
+            yCenter
+                - Math.sin(Math.toRadians(angle))
+                    * (pieBounds.getHeight() / 2 * pieStyler.getLabelsDistance());
+
+        toolTips.addData(toolTipShape, xOffset, yOffset + 10, 0, toolTipLabel);
+      }
+
+      // TOOLTIPS ////////////////////////////////////////////////////
+      // TOOLTIPS ////////////////////////////////////////////////////
+      // TOOLTIPS ////////////////////////////////////////////////////
+
+      // COUNTER_CLOCKWISE, startAngle plus arcAngle
+      if (ClockwiseDirectionType.COUNTER_CLOCKWISE == pieStyler.getClockwiseDirectionType()) {
+        startAngle += arcAngle;
+      }
+    }
+
+    // draw border between the slices
+    float borderWidth = chart.getStyler().getSliceBorderWidth();
+    if (borderWidth > 0) {
+      Color color = pieStyler.getPlotBackgroundColor();
+      g.setColor(color);
+      for (S series : map.values()) {
+        if (!series.isEnabled() || series.getValue() == null) {
+          continue;
+        }
+        Number y = series.getValue();
+        double arcAngle = y.doubleValue() * 360 / total;
+        borderAngle += arcAngle;
+        double xBorder = (pieBounds.getWidth() / 2.0) * Math.cos(Math.toRadians(borderAngle));
+        double yBorder = (pieBounds.getHeight() / 2.0) * Math.sin(Math.toRadians(borderAngle));
+        xBorder = xBorder + pieBounds.getX() + pieBounds.getWidth() / 2.0;
+        yBorder = pieBounds.getY() + pieBounds.getHeight() / 2.0 - yBorder;
+        Shape line = new Line2D.Double(xCenter, yCenter, xBorder, yBorder);
+        g.setStroke(new BasicStroke(borderWidth, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
+        g.draw(line);
+      }
+    }
+  }
+
+  private void paintLabels(Graphics2D g, Rectangle2D pieBounds, double total, double startAngle) {
+
+    Map<String, S> map = chart.getSeriesMap();
+    for (S series : map.values()) {
+
+      if (!series.isEnabled() || series.getValue() == null) {
+        continue;
+      }
+
+      Number y = series.getValue();
+
+      // draw slice/donut
+      double arcAngle = (y.doubleValue() * 360 / total);
+      // CLOCKWISE, startAngle minus arcAngle
+      if (ClockwiseDirectionType.CLOCKWISE == pieStyler.getClockwiseDirectionType()) {
+        startAngle -= arcAngle;
+      }
+
+      // curValue += y.doubleValue();
+
+      if (pieStyler.isLabelsVisible()) {
+
+        // draw label
+        String label = "";
+        if (pieStyler.getLabelType() == LabelType.Value) {
+
+          if (pieStyler.getDecimalPattern() != null) {
+            label = df.format(y);
+          } else {
+            label = y.toString();
+          }
+        } else if (pieStyler.getLabelType() == LabelType.Name) {
+          label = series.getName();
+        } else if (pieStyler.getLabelType() == LabelType.NameAndPercentage) {
+          double percentage = y.doubleValue() / total * 100;
+          label = series.getName() + " (" + df.format(percentage) + "%)";
+        } else if (pieStyler.getLabelType() == LabelType.Percentage) {
+          double percentage = y.doubleValue() / total * 100;
+          label = df.format(percentage) + "%";
+        } else if (pieStyler.getLabelType() == LabelType.NameAndValue) {
+          if (pieStyler.getDecimalPattern() != null) {
+            label = series.getName() + " (" + df.format(y) + ")";
+          } else {
+            label = series.getName() + " (" + y.toString() + ")";
+          }
+        }
+
+        TextLayout textLayout =
+            new TextLayout(
+                label, pieStyler.getLabelsFont(), new FontRenderContext(null, true, false));
+        Rectangle2D labelRectangle = textLayout.getBounds();
+
+        double xCenter =
+            pieBounds.getX() + pieBounds.getWidth() / 2 - labelRectangle.getWidth() / 2;
+        double yCenter =
+            pieBounds.getY() + pieBounds.getHeight() / 2 + labelRectangle.getHeight() / 2;
+        double angle = (arcAngle + startAngle) - arcAngle / 2;
+        double xOffset =
+            xCenter
+                + Math.cos(Math.toRadians(angle))
+                    * (pieBounds.getWidth() / 2 * pieStyler.getLabelsDistance());
+        double yOffset =
+            yCenter
+                - Math.sin(Math.toRadians(angle))
+                    * (pieBounds.getHeight() / 2 * pieStyler.getLabelsDistance());
+
+        // get annotation width
+        Shape shape = textLayout.getOutline(null);
+        Rectangle2D labelBounds = shape.getBounds2D();
+        double labelWidth = labelBounds.getWidth();
+        // System.out.println("annotationWidth= " + annotationWidth);
+        double labelHeight = labelBounds.getHeight();
+        // System.out.println("annotationHeight= " + annotationHeight);
+
+        // get slice area
+        double xOffset1 =
+            xCenter
+                + Math.cos(Math.toRadians(startAngle))
+                    * (pieBounds.getWidth() / 2 * pieStyler.getLabelsDistance());
+        double yOffset1 =
+            yCenter
+                - Math.sin(Math.toRadians(startAngle))
+                    * (pieBounds.getHeight() / 2 * pieStyler.getLabelsDistance());
+        double xOffset2 =
+            xCenter
+                + Math.cos(Math.toRadians((arcAngle + startAngle)))
+                    * (pieBounds.getWidth() / 2 * pieStyler.getLabelsDistance());
+        double yOffset2 =
+            yCenter
+                - Math.sin(Math.toRadians((arcAngle + startAngle)))
+                    * (pieBounds.getHeight() / 2 * pieStyler.getLabelsDistance());
+        // 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 labelWillFit = false;
+        if (xDiff >= yDiff) { // assume more vertically orientated slice
+          if (labelWidth < xDiff) {
+            labelWillFit = true;
+          }
+        } else if (xDiff <= yDiff) { // assume more horizontally orientated slice
+          if (labelHeight < yDiff) {
+            labelWillFit = true;
+          }
+        }
+
+        // draw label
+        if (pieStyler.isForceAllLabelsVisible() || labelWillFit) {
+
+          if (pieStyler.isLabelsFontColorAutomaticEnabled()) {
+            g.setColor(pieStyler.getLabelsFontColor(series.getFillColor()));
+          } else {
+            g.setColor(pieStyler.getLabelsFontColor());
+          }
+
+          g.setFont(pieStyler.getLabelsFont());
+          AffineTransform orig = g.getTransform();
+          AffineTransform at = new AffineTransform();
+
+          // inside
+          if (pieStyler.getLabelsDistance() <= 1.0) {
+            at.translate(xOffset, yOffset);
+          }
+
+          // outside
+          else {
+
+            // Tick Mark
+            xCenter = pieBounds.getX() + pieBounds.getWidth() / 2;
+            yCenter = pieBounds.getY() + pieBounds.getHeight() / 2;
+            // double endPoint = Math.min((2.0 - (pieStyler.getAnnotationDistance() - 1)), 1.95);
+            double endPoint = (3.0 - pieStyler.getLabelsDistance());
+            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);
+
+            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)) * labelWidth / 2 + 3, yOffset);
+          }
+
+          g.transform(at);
+          g.fill(shape);
+          g.setTransform(orig);
+        }
+      }
+      // else {
+      // System.out.println("Won't fit.");
+      // System.out.println("xDiff= " + xDiff);
+      // System.out.println("yDiff= " + yDiff);
+      // System.out.println("annotationWidth= " + annotationWidth);
+      // System.out.println("annotationHeight= " + annotationHeight);
+      //
+      // }
+      // COUNTER_CLOCKWISE, startAngle plus arcAngle
+      if (ClockwiseDirectionType.COUNTER_CLOCKWISE == pieStyler.getClockwiseDirectionType()) {
+        startAngle += arcAngle;
+      }
+    }
+  }
+
+  private void paintSum(Graphics2D g, Rectangle2D pieBounds, double total) {
+
+    // draw total value if visible
+    if (pieStyler.isSumVisible()) {
+      String label =
+          pieStyler.getSumFormat() == null || pieStyler.getSumFormat().isEmpty()
+              ? df.format(total)
+              : String.format(pieStyler.getSumFormat(), total);
+
+      TextLayout textLayout =
+          new TextLayout(label, pieStyler.getSumFont(), new FontRenderContext(null, true, false));
+      Shape shape = textLayout.getOutline(null);
+      g.setColor(pieStyler.getChartFontColor());
+
+      // compute center
+      Rectangle2D labelRectangle = textLayout.getBounds();
+      double xCenter = pieBounds.getX() + pieBounds.getWidth() / 2 - labelRectangle.getWidth() / 2;
+      double yCenter =
+          pieBounds.getY() + pieBounds.getHeight() / 2 + labelRectangle.getHeight() / 2;
+
+      // set text
+      AffineTransform orig = g.getTransform();
+      AffineTransform at = new AffineTransform();
+
+      at.translate(xCenter, yCenter);
+      g.transform(at);
+      g.fill(shape);
+      g.setTransform(orig);
+    }
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotContent_Radar.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotContent_Radar.java
new file mode 100644
index 0000000000000000000000000000000000000000..88c632f320b0d98bb40eb6d0b34d04256c1fc2cb
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotContent_Radar.java
@@ -0,0 +1,258 @@
+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.Ellipse2D;
+import java.awt.geom.Line2D;
+import java.awt.geom.Path2D;
+import java.awt.geom.Rectangle2D;
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+import java.util.Map;
+import org.knowm.xchart.RadarChart;
+import org.knowm.xchart.RadarSeries;
+import org.knowm.xchart.style.RadarStyler;
+
+public class PlotContent_Radar<ST extends RadarStyler, S extends RadarSeries>
+    extends PlotContent_<ST, S> {
+
+  private final RadarStyler styler;
+  private static final NumberFormat df = DecimalFormat.getPercentInstance();
+
+  /**
+   * Constructor
+   *
+   * @param chart
+   */
+  PlotContent_Radar(Chart<ST, S> chart) {
+
+    super(chart);
+    styler = chart.getStyler();
+  }
+
+  @Override
+  public void doPaint(Graphics2D g) {
+
+    // pre-calculate some often-used values
+
+    double boundsWidth = getBounds().getWidth();
+    double boundsHeight = getBounds().getHeight();
+
+    double radarWidth;
+    double radarHeight;
+    if (styler.isCircular()) {
+      radarWidth =
+          Math.min(boundsWidth, boundsHeight) * styler.getPlotContentSize()
+              - 2 * styler.getRadiiTitlePadding();
+      radarHeight = radarWidth;
+    } else {
+      radarWidth = boundsWidth * styler.getPlotContentSize() - 2 * styler.getRadiiTitlePadding();
+      radarHeight = boundsHeight * styler.getPlotContentSize() - 2 * styler.getRadiiTitlePadding();
+    }
+
+    double radarStartX = getBounds().getX() + boundsWidth / 2.0 - radarWidth / 2.0;
+    double radarStartY = getBounds().getY() + boundsHeight / 2.0 - radarHeight / 2.0;
+    double radarRadiusX = radarWidth / 2.0;
+    double radarRadiusY = radarHeight / 2.0;
+    double radarCenterX = radarStartX + radarWidth / 2;
+    double radarCenterY = radarStartY + radarHeight / 2;
+
+    String[] radiiLabels = ((RadarChart) chart).getRadiiLabels();
+    double radiiAngle = 360.0 / radiiLabels.length;
+    int numRadiiLabels = radiiLabels.length;
+    double[] cosArr = new double[numRadiiLabels];
+    double[] sinArr = new double[numRadiiLabels];
+    double startAngle = styler.getStartAngleInDegrees() + 90;
+    for (int i = 0; i < numRadiiLabels; i++) {
+      double radians = Math.toRadians(startAngle);
+      double cos = Math.cos(radians);
+      double sin = Math.sin(radians);
+      cosArr[i] = cos;
+      sinArr[i] = sin;
+      startAngle += radiiAngle;
+    }
+
+    // paint radii lines and labels
+    startAngle = styler.getStartAngleInDegrees() + 90;
+    for (int i = 0; i < numRadiiLabels; i++) {
+      double cos = cosArr[i];
+      double sin = sinArr[i];
+
+      // draw radii lines
+      if (styler.isRadiiTicksMarksVisible()) {
+
+        double xOffset = radarCenterX + cos * radarRadiusX;
+        double yOffset = radarCenterY - sin * radarRadiusY;
+        Line2D.Double line = new Line2D.Double(radarCenterX, radarCenterY, xOffset, yOffset);
+
+        g.setColor(styler.getRadiiTickMarksColor());
+        g.setStroke(styler.getRadiiTickMarksStroke());
+        g.draw(line);
+      }
+
+      // draw radii labels
+      if (styler.isRadiiTitleVisible()) {
+
+        String radiiLabel = radiiLabels[i];
+        TextLayout textLayout =
+            new TextLayout(
+                radiiLabel, styler.getRadiiTitleFont(), new FontRenderContext(null, true, false));
+        Shape shape = textLayout.getOutline(null);
+        Rectangle2D labelBounds = shape.getBounds2D();
+        double labelWidth = labelBounds.getWidth();
+        double labelHeight = labelBounds.getHeight();
+        double xOffset = radarCenterX + cos * radarRadiusX;
+        double yOffset = radarCenterY - sin * radarRadiusY;
+
+        xOffset =
+            xOffset
+                - Math.sin(Math.toRadians(startAngle - 90))
+                    * (labelWidth / 1.5 + styler.getRadiiTitlePadding())
+                - (labelWidth / 2);
+        yOffset =
+            yOffset
+                - (Math.cos(Math.toRadians(startAngle - 90)) - 1) * labelHeight / 2
+                - (Math.cos(Math.toRadians(startAngle - 90)) * 1.4 * styler.getRadiiTitlePadding());
+
+        g.setColor(styler.getChartFontColor());
+        g.setFont(styler.getBaseFont());
+        AffineTransform orig = g.getTransform();
+        AffineTransform at = new AffineTransform();
+
+        at.translate(xOffset, yOffset);
+
+        g.transform(at);
+        g.fill(shape);
+        g.setTransform(orig);
+      }
+
+      startAngle += radiiAngle;
+    }
+
+    // draw radii tick marks ie. concentric circles/polygons
+    int markCount = styler.getRadiiTickMarksCount();
+    if (markCount > 0 && styler.isRadiiTicksMarksVisible()) {
+      g.setColor(styler.getRadiiTickMarksColor());
+      g.setStroke(styler.getRadiiTickMarksStroke());
+      // draw circular tick mark
+      if (styler.getRadarRenderStyle() == RadarStyler.RadarRenderStyle.Circle) {
+        Ellipse2D.Double markShape = new Ellipse2D.Double(0, 0, 0, 0);
+        double winc = radarRadiusX / markCount;
+        double hinc = radarRadiusY / markCount;
+
+        double newXd = radarRadiusX;
+        double newYd = radarRadiusY;
+        for (int i = 0; i < markCount; i++) {
+          markShape.width = newXd * 2;
+          markShape.height = newYd * 2;
+          markShape.x = radarCenterX - newXd;
+          markShape.y = radarCenterY - newYd;
+
+          if (i == 0) {
+            g.setColor(styler.getRadiiTickMarksColor().darker());
+            //            g.setStroke(styler.getAxisTickMarksStroke());
+          } else {
+            g.setColor(styler.getRadiiTickMarksColor());
+          }
+
+          g.draw(markShape);
+          newXd -= winc;
+          newYd -= hinc;
+        }
+      }
+
+      // draw polygon tick marks
+      else if (styler.getRadarRenderStyle() == RadarStyler.RadarRenderStyle.Polygon) {
+        double winc = radarRadiusX / markCount;
+        double hinc = radarRadiusY / markCount;
+
+        for (int markerInd = 0; markerInd < markCount; markerInd++) {
+          Path2D.Double path = new Path2D.Double();
+          for (int varInd = 0; varInd < numRadiiLabels; varInd++) {
+            double cos = cosArr[varInd];
+            double sin = sinArr[varInd];
+            double xOffset = radarCenterX + cos * (radarRadiusX - markerInd * winc);
+            double yOffset = radarCenterY - sin * (radarRadiusY - markerInd * hinc);
+
+            if (varInd == 0) {
+              path.moveTo(xOffset, yOffset);
+            } else {
+              path.lineTo(xOffset, yOffset);
+            }
+          }
+          path.closePath();
+          if (markerInd == 0) {
+            g.setColor(styler.getRadiiTickMarksColor().darker());
+            //            g.setStroke(styler.getAxisTickMarksStroke());
+          } else {
+            g.setColor(styler.getRadiiTickMarksColor());
+          }
+          g.draw(path);
+        }
+      }
+    }
+
+    // series lines and markers and Tooltips
+    NumberFormat decimalFormat =
+        (styler.getDecimalPattern() == null) ? df : new DecimalFormat(styler.getDecimalPattern());
+    Map<String, S> map = chart.getSeriesMap();
+    for (S series : map.values()) {
+
+      if (!series.isEnabled()) {
+        continue;
+      }
+
+      double[] values = series.getValues();
+      String[] tooltipOverrides = series.getTooltipOverrides();
+
+      g.setColor(series.getFillColor());
+
+      Path2D.Double path = new Path2D.Double();
+      for (int i = 0; i < numRadiiLabels; i++) {
+
+        double cos = cosArr[i];
+        double sin = sinArr[i];
+
+        double value = values[i];
+        double xOffset = radarCenterX + cos * (radarRadiusX * value);
+        double yOffset = radarCenterY - sin * (radarRadiusY * value);
+
+        if (i == 0) {
+          path.moveTo(xOffset, yOffset);
+        } else {
+          path.lineTo(xOffset, yOffset);
+        }
+
+        // paint marker
+        if (series.getMarker() != null) {
+          g.setColor(series.getMarkerColor());
+          series.getMarker().paint(g, xOffset, yOffset, styler.getMarkerSize());
+        }
+
+        // add data labels
+        if (chart.getStyler().isToolTipsEnabled()) {
+          String label = null;
+          if (tooltipOverrides != null) {
+            label = tooltipOverrides[i];
+          }
+          if (label == null) {
+            String ystr = decimalFormat.format(value);
+            label = series.getName() + " (" + radiiLabels[i] + ": " + ystr + ")";
+          }
+          this.toolTips.addData(xOffset, yOffset, label);
+        }
+      }
+      path.closePath();
+      g.setStroke(series.getLineStyle());
+      g.setColor(series.getLineColor());
+      g.draw(path);
+      if (styler.isSeriesFilled()) {
+        g.setColor(series.getFillColor());
+        g.fill(path);
+      }
+    }
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotContent_XY.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotContent_XY.java
new file mode 100644
index 0000000000000000000000000000000000000000..b923d066aa34d2df7e062fbe9beedef15369c0a5
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotContent_XY.java
@@ -0,0 +1,364 @@
+package org.knowm.xchart.internal.chartpart;
+
+import java.awt.Graphics2D;
+import java.awt.geom.Line2D;
+import java.awt.geom.Path2D;
+import java.text.Format;
+import java.util.Map;
+import org.knowm.xchart.XYSeries;
+import org.knowm.xchart.XYSeries.XYSeriesRenderStyle;
+import org.knowm.xchart.internal.Utils;
+import org.knowm.xchart.style.XYStyler;
+import org.knowm.xchart.style.lines.SeriesLines;
+
+public class PlotContent_XY<ST extends XYStyler, S extends XYSeries> extends PlotContent_<ST, S> {
+
+  private final ST xyStyler;
+
+  Cursor cursor;
+
+  /**
+   * Constructor
+   *
+   * @param chart
+   */
+  PlotContent_XY(Chart<ST, S> chart) {
+
+    super(chart);
+    xyStyler = chart.getStyler();
+  }
+
+  @Override
+  public void doPaint(Graphics2D g) {
+
+    // X-Axis
+    double xTickSpace = xyStyler.getPlotContentSize() * getBounds().getWidth();
+    double xLeftMargin = Utils.getTickStartOffset((int) getBounds().getWidth(), xTickSpace);
+
+    // Y-Axis
+    double yTickSpace = xyStyler.getPlotContentSize() * getBounds().getHeight();
+    double yTopMargin = Utils.getTickStartOffset((int) getBounds().getHeight(), yTickSpace);
+
+    double xMin = chart.getXAxis().getMin();
+    double xMax = chart.getXAxis().getMax();
+
+    Line2D.Double line = new Line2D.Double();
+
+    // logarithmic
+    if (xyStyler.isXAxisLogarithmic()) {
+      xMin = Math.log10(xMin);
+      xMax = Math.log10(xMax);
+    }
+
+    Map<String, S> map = chart.getSeriesMap();
+
+    for (S series : map.values()) {
+
+      if (!series.isEnabled()) {
+        continue;
+      }
+      Axis yAxis = chart.getYAxis(series.getYAxisGroup());
+      double yMin = yAxis.getMin();
+      double yMax = yAxis.getMax();
+      if (xyStyler.isYAxisLogarithmic()) {
+        yMin = Math.log10(yMin);
+        yMax = Math.log10(yMax);
+      }
+
+      // data points
+      double[] xData = series.getXData();
+      double[] yData = series.getYData();
+
+      double previousX = -Double.MAX_VALUE;
+      double previousY = -Double.MAX_VALUE;
+
+      // if PolygonArea is used, these coordinates are the starting point for the polygon
+      double polygonStartX = -Double.MAX_VALUE;
+      double polygonStartY = -Double.MAX_VALUE;
+
+      double[] errorBars = series.getExtraValues();
+      Path2D.Double path = null;
+      // smooth curve
+      Path2D.Double smoothPath = null;
+
+      // for area charts
+      double yZeroTransform =
+          getBounds().getHeight() - (yTopMargin + (0 - yMin) / (yMax - yMin) * yTickSpace);
+      double yZeroOffset = yZeroTransform + getBounds().getY();
+
+      for (int i = 0; i < xData.length; i++) {
+
+        double x = xData[i];
+        // System.out.println(x);
+        if (xyStyler.isXAxisLogarithmic()) {
+          x = Math.log10(x);
+        }
+        // System.out.println(x);
+
+        double next = yData[i];
+        if (Double.isNaN(next)) {
+
+          // for area charts
+          g.setColor(series.getFillColor());
+          closePathXY(g, path, previousX, yZeroOffset, polygonStartX, polygonStartY);
+          path = null;
+
+          if (smoothPath != null) {
+            g.setColor(series.getLineColor());
+            g.setStroke(series.getLineStyle());
+            g.draw(smoothPath);
+            smoothPath = null;
+          }
+
+          previousX = -Double.MAX_VALUE;
+          previousY = -Double.MAX_VALUE;
+          continue;
+        }
+
+        double yOrig = yData[i];
+
+        double y;
+
+        // System.out.println(y);
+        if (xyStyler.isYAxisLogarithmic()) {
+          y = Math.log10(yOrig);
+        } else {
+          y = yOrig;
+        }
+        // System.out.println(y);
+
+        double xTransform = xLeftMargin + ((x - xMin) / (xMax - xMin) * xTickSpace);
+        double yTransform =
+            getBounds().getHeight() - (yTopMargin + (y - yMin) / (yMax - yMin) * yTickSpace);
+
+        // a check if all x data are the exact same values
+        if (Math.abs(xMax - xMin) / 5 == 0.0) {
+          xTransform = getBounds().getWidth() / 2.0;
+        }
+
+        // a check if all y data are the exact same values
+        if (Math.abs(yMax - yMin) / 5 == 0.0) {
+          yTransform = getBounds().getHeight() / 2.0;
+        }
+
+        double xOffset = getBounds().getX() + xTransform;
+        double yOffset = getBounds().getY() + yTransform;
+        // System.out.println(xTransform);
+        // System.out.println(xOffset);
+        // System.out.println(yTransform);
+        // System.out.println(yOffset);
+        // System.out.println("---");
+
+        // paint line
+
+        boolean isSeriesLineOrArea =
+            XYSeriesRenderStyle.Line == series.getXYSeriesRenderStyle()
+                || XYSeriesRenderStyle.Area == series.getXYSeriesRenderStyle()
+                || XYSeriesRenderStyle.PolygonArea == series.getXYSeriesRenderStyle();
+        boolean isSeriesStepLineOrStepArea =
+            XYSeriesRenderStyle.Step == series.getXYSeriesRenderStyle()
+                || XYSeriesRenderStyle.StepArea == series.getXYSeriesRenderStyle();
+
+        if (isSeriesLineOrArea || isSeriesStepLineOrStepArea) {
+          if (series.getLineStyle() != SeriesLines.NONE) {
+
+            if (previousX != -Double.MAX_VALUE && previousY != -Double.MAX_VALUE) {
+              g.setColor(series.getLineColor());
+              g.setStroke(series.getLineStyle());
+              if (isSeriesLineOrArea) {
+                if (series.isSmooth()) {
+                  if (smoothPath == null) {
+                    smoothPath = new Path2D.Double();
+                    smoothPath.moveTo(previousX, previousY);
+                  }
+                  smoothPath.curveTo(
+                      (previousX + xOffset) / 2,
+                      previousY,
+                      (previousX + xOffset) / 2,
+                      yOffset,
+                      xOffset,
+                      yOffset);
+                } else {
+                  line.setLine(previousX, previousY, xOffset, yOffset);
+                  g.draw(line);
+                }
+              } else {
+                if (previousX != xOffset) {
+                  line.setLine(previousX, previousY, xOffset, previousY);
+                  g.draw(line);
+                }
+                if (previousY != yOffset) {
+                  line.setLine(xOffset, previousY, xOffset, yOffset);
+                  g.draw(line);
+                }
+              }
+            }
+          }
+        }
+
+        // paint area
+        if (XYSeriesRenderStyle.Area == series.getXYSeriesRenderStyle()
+            || XYSeriesRenderStyle.StepArea == series.getXYSeriesRenderStyle()
+            || XYSeriesRenderStyle.PolygonArea == series.getXYSeriesRenderStyle()) {
+
+          if (previousX != -Double.MAX_VALUE && previousY != -Double.MAX_VALUE) {
+            if (path == null) {
+              path = new Path2D.Double();
+              if (XYSeriesRenderStyle.PolygonArea == series.getXYSeriesRenderStyle()) {
+                path.moveTo(previousX, previousY);
+                polygonStartX = previousX;
+                polygonStartY = previousY;
+              } else {
+                path.moveTo(previousX, yZeroOffset);
+                path.lineTo(previousX, previousY);
+              }
+            }
+            if (XYSeriesRenderStyle.Area == series.getXYSeriesRenderStyle()
+                || XYSeriesRenderStyle.PolygonArea == series.getXYSeriesRenderStyle()) {
+              if (series.isSmooth()) {
+                path.curveTo(
+                    (previousX + xOffset) / 2,
+                    previousY,
+                    (previousX + xOffset) / 2,
+                    yOffset,
+                    xOffset,
+                    yOffset);
+              } else {
+                path.lineTo(xOffset, yOffset);
+              }
+            } else {
+              if (previousX != xOffset) {
+                path.lineTo(xOffset, previousY);
+              }
+              if (previousY != yOffset) {
+                path.lineTo(xOffset, yOffset);
+              }
+            }
+          }
+          if (xOffset < previousX
+              && XYSeriesRenderStyle.PolygonArea != series.getXYSeriesRenderStyle()) {
+            throw new RuntimeException("X-Data must be in ascending order for Area Charts!!!");
+          }
+        }
+
+        previousX = xOffset;
+        previousY = yOffset;
+
+        // paint marker
+        if (series.getMarker() != null) {
+          g.setColor(series.getMarkerColor());
+          series.getMarker().paint(g, xOffset, yOffset, xyStyler.getMarkerSize());
+        }
+
+        // paint error bars
+        if (errorBars != null) {
+
+          double eb = errorBars[i];
+
+          // set error bar style
+          if (xyStyler.isErrorBarsColorSeriesColor()) {
+            g.setColor(series.getLineColor());
+          } else {
+            g.setColor(xyStyler.getErrorBarsColor());
+          }
+          g.setStroke(ERROR_BAR_STROKE);
+
+          // Top value
+          double topValue;
+          if (xyStyler.isYAxisLogarithmic()) {
+            topValue = yOrig + eb;
+            topValue = Math.log10(topValue);
+          } else {
+            topValue = y + eb;
+          }
+          double topEBTransform =
+              getBounds().getHeight()
+                  - (yTopMargin + (topValue - yMin) / (yMax - yMin) * yTickSpace);
+          double topEBOffset = getBounds().getY() + topEBTransform;
+
+          // Bottom value
+          double bottomValue;
+          if (xyStyler.isYAxisLogarithmic()) {
+            bottomValue = yOrig - eb;
+            // System.out.println(bottomValue);
+            bottomValue = Math.log10(bottomValue);
+          } else {
+            bottomValue = y - eb;
+          }
+          double bottomEBTransform =
+              getBounds().getHeight()
+                  - (yTopMargin + (bottomValue - yMin) / (yMax - yMin) * yTickSpace);
+          double bottomEBOffset = getBounds().getY() + bottomEBTransform;
+
+          // Draw it
+          line.setLine(xOffset, topEBOffset, xOffset, bottomEBOffset);
+          g.draw(line);
+          line.setLine(xOffset - 3, bottomEBOffset, xOffset + 3, bottomEBOffset);
+          g.draw(line);
+          line.setLine(xOffset - 3, topEBOffset, xOffset + 3, topEBOffset);
+          g.draw(line);
+        }
+
+        // add tooltips
+        if (chart.getStyler().isToolTipsEnabled()) {
+          toolTips.addData(
+              xOffset,
+              yOffset,
+              chart.getXAxisFormat().format(x),
+              chart.getYAxisFormat(series.getYAxisDecimalPattern()).format(yOrig));
+        }
+
+        if (xyStyler.isCursorEnabled()) {
+          Format xFormat;
+          Format yFormat;
+          if (xyStyler.getCustomCursorXDataFormattingFunction() == null) {
+            xFormat = chart.getXAxisFormat();
+          } else {
+            xFormat = new Formatter_Custom(xyStyler.getCustomCursorXDataFormattingFunction());
+          }
+          if (xyStyler.getCustomCursorYDataFormattingFunction() == null) {
+            yFormat = chart.getYAxisFormat(series.getYAxisDecimalPattern());
+          } else {
+            yFormat = new Formatter_Custom(xyStyler.getCustomCursorYDataFormattingFunction());
+          }
+          cursor.addData(
+              xOffset, yOffset, xFormat.format(x), yFormat.format(yOrig), series.getName());
+        }
+      }
+
+      if (smoothPath != null) {
+        g.setColor(series.getLineColor());
+        g.setStroke(series.getLineStyle());
+        g.draw(smoothPath);
+      }
+      // close any open path for area charts
+      g.setColor(series.getFillColor());
+      closePathXY(g, path, previousX, yZeroOffset, polygonStartX, polygonStartY);
+    }
+    if (chart.getStyler().isCursorEnabled()) {
+      cursor.paint(g);
+    }
+  }
+
+  void closePathXY(
+      Graphics2D g,
+      Path2D.Double path,
+      double previousX,
+      double yZeroOffset,
+      double polygonStartX,
+      double polygonStartY) {
+    if (path != null) {
+      if (polygonStartX != -Double.MAX_VALUE) {
+        path.lineTo(polygonStartX, polygonStartY);
+      } else {
+        path.lineTo(previousX, yZeroOffset);
+      }
+      path.closePath();
+      g.fill(path);
+    }
+  }
+
+  public void setCursor(Cursor cursor) {
+    this.cursor = cursor;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotSurface_.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotSurface_.java
new file mode 100644
index 0000000000000000000000000000000000000000..183721c8951eb39be5e10737f14f6e18c623e83a
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotSurface_.java
@@ -0,0 +1,26 @@
+package org.knowm.xchart.internal.chartpart;
+
+import java.awt.geom.Rectangle2D;
+import org.knowm.xchart.internal.series.Series;
+import org.knowm.xchart.style.Styler;
+
+public abstract class PlotSurface_<ST extends Styler, S extends Series> implements ChartPart {
+
+  final Chart<ST, S> chart;
+
+  /**
+   * Constructor
+   *
+   * @param chart
+   */
+  PlotSurface_(Chart<ST, S> chart) {
+
+    this.chart = chart;
+  }
+
+  @Override
+  public Rectangle2D getBounds() {
+
+    return chart.getPlot().getBounds();
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotSurface_AxesChart.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotSurface_AxesChart.java
new file mode 100644
index 0000000000000000000000000000000000000000..2e3b895c0de657928a698b6b69d1ed345e4be0ed
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotSurface_AxesChart.java
@@ -0,0 +1,176 @@
+package org.knowm.xchart.internal.chartpart;
+
+import java.awt.*;
+import java.awt.geom.Line2D;
+import java.awt.geom.Rectangle2D;
+import java.util.List;
+import org.knowm.xchart.internal.series.Series;
+import org.knowm.xchart.style.AxesChartStyler;
+
+/** Draws the plot background, the plot border and the horizontal and vertical grid lines */
+public class PlotSurface_AxesChart<ST extends AxesChartStyler, S extends Series>
+    extends PlotSurface_<ST, S> {
+
+  private final ST stylerAxesChart;
+
+  /**
+   * Constructor
+   *
+   * @param chart
+   */
+  PlotSurface_AxesChart(Chart<ST, S> chart) {
+
+    super(chart);
+    this.stylerAxesChart = chart.getStyler();
+  }
+
+  @Override
+  public void paint(Graphics2D g) {
+
+    Rectangle2D bounds = getBounds();
+
+    // paint plot background
+    Shape rect =
+        new Rectangle2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight());
+    g.setColor(stylerAxesChart.getPlotBackgroundColor());
+    g.fill(rect);
+    // paint grid lines and/or inner plot ticks
+
+    // horizontal
+
+    if (stylerAxesChart.isPlotGridHorizontalLinesVisible()) {
+
+      List<Double> yAxisTickLocations = chart.getYAxis().getAxisTickCalculator().getTickLocations();
+      for (Double yAxisTickLocation : yAxisTickLocations) {
+        double yOffset = bounds.getY() + bounds.getHeight() - yAxisTickLocation;
+
+        if (yOffset > bounds.getY() && yOffset < bounds.getY() + bounds.getHeight()) {
+
+          // draw lines
+          g.setColor(stylerAxesChart.getPlotGridLinesColor());
+          g.setStroke(stylerAxesChart.getPlotGridLinesStroke());
+          Shape line =
+              stylerAxesChart
+                  .getPlotGridLinesStroke()
+                  .createStrokedShape(
+                      new Line2D.Double(
+                          bounds.getX(), yOffset, bounds.getX() + bounds.getWidth(), yOffset));
+          // g.setStroke(axesChartStyler.getPlotGridLinesStroke());
+          // Shape line = new Line2D.Double(bounds.getX(), yOffset, bounds.getX() +
+          // bounds.getWidth(), yOffset);
+          g.fill(line);
+          // g.drawLine((int) bounds.getX(), (int) yOffset, (int) (bounds.getX() +
+          // bounds.getWidth()), (int) yOffset);
+        }
+      }
+    }
+
+    // horizontal tick marks
+    if (stylerAxesChart.isPlotTicksMarksVisible()) {
+
+      // draw left side
+      List<Double> yAxisTickLocations =
+          chart.getAxisPair().getLeftMainYAxis().getAxisTickCalculator().getTickLocations();
+      for (Double yAxisTickLocation : yAxisTickLocations) {
+        double yOffset = bounds.getY() + bounds.getHeight() - yAxisTickLocation;
+
+        if (yOffset > bounds.getY() && yOffset < bounds.getY() + bounds.getHeight()) {
+
+          // tick marks
+          g.setColor(stylerAxesChart.getAxisTickMarksColor());
+          g.setStroke(stylerAxesChart.getAxisTickMarksStroke());
+          Shape line =
+              new Line2D.Double(
+                  bounds.getX(),
+                  yOffset,
+                  bounds.getX() + stylerAxesChart.getAxisTickMarkLength(),
+                  yOffset);
+          g.draw(line);
+        }
+      }
+
+      // draw right side
+      yAxisTickLocations =
+          chart.getAxisPair().getRightMainYAxis().getAxisTickCalculator().getTickLocations();
+      for (Double yAxisTickLocation : yAxisTickLocations) {
+        double yOffset = bounds.getY() + bounds.getHeight() - yAxisTickLocation;
+
+        if (yOffset > bounds.getY() && yOffset < bounds.getY() + bounds.getHeight()) {
+
+          // tick marks
+          g.setColor(stylerAxesChart.getAxisTickMarksColor());
+          g.setStroke(stylerAxesChart.getAxisTickMarksStroke());
+          Shape line =
+              new Line2D.Double(
+                  bounds.getX() + bounds.getWidth(),
+                  yOffset,
+                  bounds.getX() + bounds.getWidth() - stylerAxesChart.getAxisTickMarkLength(),
+                  yOffset);
+          g.draw(line);
+        }
+      }
+    }
+
+    // vertical
+
+    if (stylerAxesChart.isPlotGridVerticalLinesVisible()
+        || stylerAxesChart.isPlotTicksMarksVisible()) {
+
+      List<Double> xAxisTickLocations = chart.getXAxis().getAxisTickCalculator().getTickLocations();
+      for (Double xAxisTickLocation : xAxisTickLocations) {
+
+        double tickLocation = xAxisTickLocation;
+        double xOffset = bounds.getX() + tickLocation;
+
+        if (xOffset > bounds.getX() && xOffset < bounds.getX() + bounds.getWidth()) {
+
+          // draw lines
+          if (stylerAxesChart.isPlotGridVerticalLinesVisible()) {
+
+            g.setColor(stylerAxesChart.getPlotGridLinesColor());
+            g.setStroke(stylerAxesChart.getPlotGridLinesStroke());
+            // g.setStroke(axesChartStyler.getPlotGridLinesStroke());
+            // System.out.println();
+            Shape line =
+                stylerAxesChart
+                    .getPlotGridLinesStroke()
+                    .createStrokedShape(
+                        new Line2D.Double(
+                            xOffset, bounds.getY(), xOffset, bounds.getY() + bounds.getHeight()));
+            // Shape line = new Line2D.Double(xOffset, bounds.getY(), xOffset, bounds.getY() +
+            // bounds.getHeight());
+            g.fill(line);
+          }
+          // tick marks
+          if (stylerAxesChart.isPlotTicksMarksVisible()) {
+
+            g.setColor(stylerAxesChart.getAxisTickMarksColor());
+            g.setStroke(stylerAxesChart.getAxisTickMarksStroke());
+
+            Shape line =
+                new Line2D.Double(
+                    xOffset,
+                    bounds.getY(),
+                    xOffset,
+                    bounds.getY() + stylerAxesChart.getAxisTickMarkLength());
+            g.draw(line);
+            line =
+                new Line2D.Double(
+                    xOffset,
+                    bounds.getY() + bounds.getHeight(),
+                    xOffset,
+                    bounds.getY() + bounds.getHeight() - stylerAxesChart.getAxisTickMarkLength());
+            g.draw(line);
+          }
+        }
+      }
+    }
+
+    // paint plot border
+    if (stylerAxesChart.isPlotBorderVisible()) {
+      g.setColor(stylerAxesChart.getPlotBorderColor());
+      g.setStroke(SOLID_STROKE);
+      g.draw(rect);
+    }
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotSurface_Pie.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotSurface_Pie.java
new file mode 100644
index 0000000000000000000000000000000000000000..6fc87275612a2334715f06fdd6c59cb39eae8d8f
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotSurface_Pie.java
@@ -0,0 +1,45 @@
+package org.knowm.xchart.internal.chartpart;
+
+import java.awt.*;
+import java.awt.geom.Rectangle2D;
+import org.knowm.xchart.internal.series.Series;
+import org.knowm.xchart.style.Styler;
+
+/** Draws the plot background and the plot border */
+public class PlotSurface_Pie<ST extends Styler, S extends Series> extends PlotSurface_<ST, S> {
+
+  private final ST styler;
+
+  /**
+   * Constructor
+   *
+   * @param chart
+   */
+  PlotSurface_Pie(Chart<ST, S> chart) {
+
+    super(chart);
+    this.styler = chart.getStyler();
+  }
+
+  @Override
+  public void paint(Graphics2D g) {
+
+    Rectangle2D bounds = getBounds();
+
+    //    System.out.println("styler.getPlotBackgroundColor() = " +
+    // styler.getPlotBackgroundColor());
+
+    // paint plot background
+    Shape rect =
+        new Rectangle2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight());
+    g.setColor(styler.getPlotBackgroundColor());
+    g.fill(rect);
+
+    // paint plot border
+    if (styler.isPlotBorderVisible()) {
+      g.setColor(styler.getPlotBorderColor());
+      // g.setStroke(getChartPainter().getstyler().getAxisTickMarksStroke());
+      g.draw(rect);
+    }
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Plot_.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Plot_.java
new file mode 100644
index 0000000000000000000000000000000000000000..239963449f21caff464c95e1411581c895f046b6
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Plot_.java
@@ -0,0 +1,45 @@
+package org.knowm.xchart.internal.chartpart;
+
+import java.awt.*;
+import java.awt.geom.Rectangle2D;
+import org.knowm.xchart.internal.series.Series;
+import org.knowm.xchart.style.Styler;
+
+public class Plot_<ST extends Styler, S extends Series> implements ChartPart {
+
+  final Chart<ST, S> chart;
+  Rectangle2D bounds;
+
+  PlotSurface_<ST, S> plotSurface;
+  PlotContent_<ST, S> plotContent;
+
+  /**
+   * Constructor
+   *
+   * @param chart
+   */
+  Plot_(Chart<ST, S> chart) {
+
+    this.chart = chart;
+  }
+
+  @Override
+  public void paint(Graphics2D g) {
+
+    // g.setColor(Color.red);
+    // g.draw(bounds);
+
+    plotSurface.paint(g);
+    // TODO is this necessary>??
+    if (chart.getSeriesMap().isEmpty()) {
+      return;
+    }
+    plotContent.paint(g);
+  }
+
+  @Override
+  public Rectangle2D getBounds() {
+
+    return bounds;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Plot_AxesChart.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Plot_AxesChart.java
new file mode 100644
index 0000000000000000000000000000000000000000..7813ab7ae87e3a4384abfb91e0f63e3117bdf14e
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Plot_AxesChart.java
@@ -0,0 +1,36 @@
+package org.knowm.xchart.internal.chartpart;
+
+import java.awt.*;
+import java.awt.geom.Rectangle2D;
+import org.knowm.xchart.internal.series.Series;
+import org.knowm.xchart.style.AxesChartStyler;
+
+public class Plot_AxesChart<ST extends AxesChartStyler, S extends Series> extends Plot_<ST, S> {
+
+  /**
+   * Constructor
+   *
+   * @param chart
+   */
+  Plot_AxesChart(Chart<ST, S> chart) {
+
+    super(chart);
+    this.plotSurface = new PlotSurface_AxesChart<ST, S>(chart);
+  }
+
+  @Override
+  public void paint(Graphics2D g) {
+
+    Rectangle2D yAxisBounds = chart.getAxisPair().getLeftYAxisBounds();
+    Rectangle2D xAxisBounds = chart.getXAxis().getBounds();
+
+    // calculate bounds
+    double xOffset = xAxisBounds.getX();
+    double yOffset = yAxisBounds.getY();
+    double width = xAxisBounds.getWidth();
+    double height = yAxisBounds.getHeight();
+    this.bounds = new Rectangle2D.Double(xOffset, yOffset, width, height);
+
+    super.paint(g);
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Plot_Box.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Plot_Box.java
new file mode 100644
index 0000000000000000000000000000000000000000..50670b23ec507fe235af7c80b5f9f6be73ff1739
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Plot_Box.java
@@ -0,0 +1,13 @@
+package org.knowm.xchart.internal.chartpart;
+
+import org.knowm.xchart.BoxSeries;
+import org.knowm.xchart.style.BoxStyler;
+
+public class Plot_Box<ST extends BoxStyler, S extends BoxSeries> extends Plot_AxesChart<ST, S> {
+
+  public Plot_Box(Chart<ST, S> chart) {
+
+    super(chart);
+    this.plotContent = new PlotContent_Box<ST, S>(chart);
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Plot_Bubble.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Plot_Bubble.java
new file mode 100644
index 0000000000000000000000000000000000000000..adbaac293417b8a6c1f6277307c1ec871dc8d845
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Plot_Bubble.java
@@ -0,0 +1,19 @@
+package org.knowm.xchart.internal.chartpart;
+
+import org.knowm.xchart.BubbleSeries;
+import org.knowm.xchart.style.BubbleStyler;
+
+public class Plot_Bubble<ST extends BubbleStyler, S extends BubbleSeries>
+    extends Plot_AxesChart<ST, S> {
+
+  /**
+   * Constructor
+   *
+   * @param chart
+   */
+  public Plot_Bubble(Chart<ST, S> chart) {
+
+    super(chart);
+    this.plotContent = new PlotContent_Bubble<ST, S>(chart);
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Plot_Category.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Plot_Category.java
new file mode 100644
index 0000000000000000000000000000000000000000..338d338a51c51d0bc0d1d6b4847ce7399d897134
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Plot_Category.java
@@ -0,0 +1,19 @@
+package org.knowm.xchart.internal.chartpart;
+
+import org.knowm.xchart.CategorySeries;
+import org.knowm.xchart.style.CategoryStyler;
+
+public class Plot_Category<ST extends CategoryStyler, S extends CategorySeries>
+    extends Plot_AxesChart<ST, S> {
+
+  /**
+   * Constructor
+   *
+   * @param chart
+   */
+  public Plot_Category(Chart<ST, S> chart) {
+
+    super(chart);
+    this.plotContent = new PlotContent_Category_Bar<ST, S>(chart);
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Plot_Circular.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Plot_Circular.java
new file mode 100644
index 0000000000000000000000000000000000000000..26b38996b1d734f0964669eee9221e54f2d3e405
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Plot_Circular.java
@@ -0,0 +1,58 @@
+package org.knowm.xchart.internal.chartpart;
+
+import java.awt.*;
+import java.awt.geom.Rectangle2D;
+import org.knowm.xchart.internal.series.Series;
+import org.knowm.xchart.style.Styler;
+
+public abstract class Plot_Circular<ST extends Styler, S extends Series> extends Plot_<ST, S> {
+
+  /**
+   * Constructor
+   *
+   * @param chart
+   */
+  public Plot_Circular(Chart<ST, S> chart) {
+
+    super(chart);
+    // TODO is this needed??
+    initContentAndSurface(chart);
+  }
+
+  protected abstract void initContentAndSurface(Chart<ST, S> chart);
+
+  @Override
+  public void paint(Graphics2D g) {
+
+    // calculate bounds
+    double xOffset = chart.getStyler().getChartPadding();
+
+    // double yOffset = chart.getChartTitle().getBounds().getHeight() + 2 *
+    // chart.getStyler().getChartPadding();
+    double yOffset =
+        chart.getChartTitle().getBounds().getHeight() + chart.getStyler().getChartPadding();
+
+    double width =
+        chart.getWidth()
+            - (chart.getStyler().getLegendPosition() == Styler.LegendPosition.OutsideE
+                ? chart.getLegend().getBounds().getWidth()
+                : 0)
+            - 2 * chart.getStyler().getChartPadding()
+            - (chart.getStyler().getLegendPosition() == Styler.LegendPosition.OutsideE
+                    && chart.getStyler().isLegendVisible()
+                ? chart.getStyler().getChartPadding()
+                : 0);
+
+    double height =
+        chart.getHeight()
+            - chart.getChartTitle().getBounds().getHeight()
+            - (chart.getStyler().getLegendPosition() == Styler.LegendPosition.OutsideS
+                ? chart.getLegend().getBounds().getHeight()
+                : 0)
+            - 2 * chart.getStyler().getChartPadding();
+
+    this.bounds = new Rectangle2D.Double(xOffset, yOffset, width, height);
+
+    super.paint(g);
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Plot_Dial.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Plot_Dial.java
new file mode 100644
index 0000000000000000000000000000000000000000..e202ffd457152942e50f60e23c5e448a7e3806a3
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Plot_Dial.java
@@ -0,0 +1,24 @@
+package org.knowm.xchart.internal.chartpart;
+
+import org.knowm.xchart.DialSeries;
+import org.knowm.xchart.style.DialStyler;
+
+public class Plot_Dial<ST extends DialStyler, S extends DialSeries> extends Plot_Circular<ST, S> {
+
+  /**
+   * Constructor
+   *
+   * @param chart
+   */
+  public Plot_Dial(Chart<ST, S> chart) {
+
+    super(chart);
+  }
+
+  @Override
+  protected void initContentAndSurface(Chart<ST, S> chart) {
+
+    this.plotContent = new PlotContent_Dial<ST, S>(chart);
+    this.plotSurface = new PlotSurface_Pie<ST, S>(chart);
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Plot_HeatMap.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Plot_HeatMap.java
new file mode 100644
index 0000000000000000000000000000000000000000..bec1099c914f8841d4ce6100e06c8c5dfd07650a
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Plot_HeatMap.java
@@ -0,0 +1,19 @@
+package org.knowm.xchart.internal.chartpart;
+
+import org.knowm.xchart.HeatMapSeries;
+import org.knowm.xchart.style.HeatMapStyler;
+
+public class Plot_HeatMap<ST extends HeatMapStyler, S extends HeatMapSeries>
+    extends Plot_AxesChart<ST, S> {
+
+  /**
+   * Constructor
+   *
+   * @param chart
+   */
+  public Plot_HeatMap(Chart<ST, S> chart) {
+
+    super(chart);
+    this.plotContent = new PlotContent_HeatMap<ST, S>(chart);
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Plot_OHLC.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Plot_OHLC.java
new file mode 100644
index 0000000000000000000000000000000000000000..4ce8109fb86cc3207befee9d95bebcb6bc74f324
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Plot_OHLC.java
@@ -0,0 +1,19 @@
+package org.knowm.xchart.internal.chartpart;
+
+import org.knowm.xchart.OHLCSeries;
+import org.knowm.xchart.style.AxesChartStyler;
+
+public class Plot_OHLC<ST extends AxesChartStyler, S extends OHLCSeries>
+    extends Plot_AxesChart<ST, S> {
+
+  /**
+   * Constructor
+   *
+   * @param chart
+   */
+  public Plot_OHLC(Chart<ST, S> chart) {
+
+    super(chart);
+    this.plotContent = new PlotContent_OHLC<ST, S>(chart);
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Plot_Pie.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Plot_Pie.java
new file mode 100644
index 0000000000000000000000000000000000000000..14466ac903ebfadd0a444e71ef91387dc5249592
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Plot_Pie.java
@@ -0,0 +1,23 @@
+package org.knowm.xchart.internal.chartpart;
+
+import org.knowm.xchart.PieSeries;
+import org.knowm.xchart.style.PieStyler;
+
+public class Plot_Pie<ST extends PieStyler, S extends PieSeries> extends Plot_Circular<ST, S> {
+
+  /**
+   * Constructor
+   *
+   * @param chart
+   */
+  public Plot_Pie(Chart<ST, S> chart) {
+
+    super(chart);
+  }
+
+  protected void initContentAndSurface(Chart<ST, S> chart) {
+
+    this.plotContent = new PlotContent_Pie<ST, S>(chart);
+    this.plotSurface = new PlotSurface_Pie<ST, S>(chart);
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Plot_Radar.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Plot_Radar.java
new file mode 100644
index 0000000000000000000000000000000000000000..867dd2f462efdd6ece200a499979111977c85fc6
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Plot_Radar.java
@@ -0,0 +1,25 @@
+package org.knowm.xchart.internal.chartpart;
+
+import org.knowm.xchart.RadarSeries;
+import org.knowm.xchart.style.RadarStyler;
+
+public class Plot_Radar<ST extends RadarStyler, S extends RadarSeries>
+    extends Plot_Circular<ST, S> {
+
+  /**
+   * Constructor
+   *
+   * @param chart
+   */
+  public Plot_Radar(Chart<ST, S> chart) {
+
+    super(chart);
+  }
+
+  @Override
+  protected void initContentAndSurface(Chart<ST, S> chart) {
+
+    this.plotContent = new PlotContent_Radar<ST, S>(chart);
+    this.plotSurface = new PlotSurface_Pie<ST, S>(chart);
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Plot_XY.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Plot_XY.java
new file mode 100644
index 0000000000000000000000000000000000000000..e762ad55e1add353c37a42e3ecb2ed4828388e51
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Plot_XY.java
@@ -0,0 +1,18 @@
+package org.knowm.xchart.internal.chartpart;
+
+import org.knowm.xchart.XYSeries;
+import org.knowm.xchart.style.XYStyler;
+
+public class Plot_XY<ST extends XYStyler, S extends XYSeries> extends Plot_AxesChart<ST, S> {
+
+  /**
+   * Constructor
+   *
+   * @param chart
+   */
+  public Plot_XY(Chart<ST, S> chart) {
+
+    super(chart);
+    this.plotContent = new PlotContent_XY<ST, S>(chart);
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/RenderableSeries.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/RenderableSeries.java
new file mode 100644
index 0000000000000000000000000000000000000000..580a2143cfc2c0ba76a9e1d1eb9373e6558e6999
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/RenderableSeries.java
@@ -0,0 +1,13 @@
+package org.knowm.xchart.internal.chartpart;
+
+public interface RenderableSeries {
+
+  LegendRenderType getLegendRenderType();
+
+  enum LegendRenderType {
+    Line,
+    Scatter,
+    Box,
+    BoxNoOutline
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/ToolTips.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/ToolTips.java
new file mode 100644
index 0000000000000000000000000000000000000000..9fc4634a7d8c3b814d284ea99b3ff9af3e799646
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/chartpart/ToolTips.java
@@ -0,0 +1,407 @@
+package org.knowm.xchart.internal.chartpart;
+
+import java.awt.Graphics2D;
+import java.awt.Rectangle;
+import java.awt.Shape;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.font.FontRenderContext;
+import java.awt.font.TextLayout;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.Rectangle2D;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import org.knowm.xchart.style.BoxStyler;
+import org.knowm.xchart.style.OHLCStyler;
+import org.knowm.xchart.style.Styler;
+
+// TODO make the background color the same color as the series??
+/**
+ * Tooltips can be put on all data points or configured to popup like a tooltip from a mouse over.
+ */
+public class ToolTips extends MouseAdapter implements ChartPart {
+
+  private static final int MARGIN = 5;
+  private static final int MOUSE_MARGIN = 20;
+
+  private final Chart chart;
+  private final Styler styler;
+
+  // The tool tips and currently shown Tooltip
+  private final List<ToolTip> toolTipList = new ArrayList<>();
+  private ToolTip tooltip = null;
+
+  /**
+   * Constructor
+   *
+   * @param chart
+   */
+  public ToolTips(Chart chart) {
+
+    this.chart = chart;
+    this.styler = chart.getStyler();
+    chart.plot.plotContent.setToolTips(this);
+  }
+
+  ////////////////////////////////////////////
+  // MouseAdapter method /////////////////////
+  ////////////////////////////////////////////
+
+  @Override
+  public void mouseMoved(MouseEvent e) {
+
+    boolean isRepaint = false;
+
+    ToolTip newPoint = getSelectedTooltip(e.getX(), e.getY());
+
+    if (newPoint != null) {
+
+      // if the existing shown data point is already shown, abort
+      if (tooltip != null) {
+        if (tooltip.equals(newPoint)) {
+          isRepaint = false; // Don't need to repaint again as it's already showing
+        }
+      }
+      tooltip = newPoint;
+      isRepaint = true;
+
+    }
+    // remove the popup shape
+    else if (tooltip != null) {
+      tooltip = null;
+      isRepaint = true;
+    }
+
+    if (isRepaint) {
+      //      xChartPanel.invalidate();
+      //      xChartPanel.repaint();
+      e.getComponent().repaint();
+    }
+  }
+
+  private ToolTip getSelectedTooltip(int x, int y) {
+
+    // find the datapoint based on the mouse location
+    ToolTip newPoint = null;
+    for (ToolTip tooltip : toolTipList) {
+      if (tooltip.shape.contains(x, y)) {
+        newPoint = tooltip;
+        break;
+      }
+    }
+    //    System.out.println("newPoint = " + newPoint);
+    return newPoint;
+  }
+
+  ////////////////////////////////////////////
+  // ChartPart methods ///////////////////////
+  ////////////////////////////////////////////
+
+  @Override
+  public Rectangle2D getBounds() {
+    return null;
+  }
+
+  @Override
+  public void paint(Graphics2D g) {
+
+    if (styler.isToolTipsAlwaysVisible()) {
+      for (ToolTip tooltip : toolTipList) {
+        paintToolTip(g, tooltip);
+      }
+    }
+
+    // TODO need this null check??
+    if (tooltip != null) { // dataPoint was created in mouse move, need to render it
+      // TODO See OHLC04. The line series are rendering as multi-line. Can we just define the
+      // tooltip during creation and if it's multiline, paint it
+      // as multiline??
+      if (styler instanceof BoxStyler || styler instanceof OHLCStyler) {
+        paintMultiLineToolTip(g);
+      } else {
+        paintToolTip(g, tooltip);
+      }
+    }
+  }
+
+  ////////////////////////////////////////////////
+  /// PAINTING //////////////////////////////////
+  ///////////////////////////////////////////////
+
+  private void paintToolTip(Graphics2D g, ToolTip tooltip) {
+
+    TextLayout textLayout =
+        new TextLayout(
+            tooltip.label, styler.getToolTipFont(), new FontRenderContext(null, true, false));
+    Rectangle2D annotationRectangle = textLayout.getBounds();
+
+    double w = annotationRectangle.getWidth() + 2 * MARGIN;
+    double h = annotationRectangle.getHeight() + 2 * MARGIN;
+    double halfHeight = h / 2;
+    //    System.out.println("w = " + w);
+    //    System.out.println("h = " + h);
+    //    System.out.println("halfHeight = " + halfHeight);
+
+    // TODO is this needed??
+    if (tooltip == this.tooltip) {
+      // not the box with label, but the shape
+      // highlight shape for popup
+      g.setColor(styler.getToolTipHighlightColor());
+      g.fill(tooltip.shape);
+    }
+
+    //    System.out.println("paintToolTip");
+
+    int leftEdge = (int) chart.plot.plotContent.getBounds().getX();
+    int rightEdge =
+        (int)
+            (chart.plot.plotContent.getBounds().getX()
+                + chart.plot.plotContent.getBounds().getWidth());
+    int topEdge = (int) chart.plot.plotContent.getBounds().getY();
+    int bottomEdge =
+        (int)
+            (chart.plot.plotContent.getBounds().getY()
+                + chart.plot.plotContent.getBounds().getHeight());
+    //    System.out.println("leftEdge = " + leftEdge);
+    //    System.out.println("rightEdge = " + rightEdge);
+    //    System.out.println("topEdge = " + topEdge);
+    //    System.out.println("bottomEdge = " + bottomEdge);
+
+    double x = tooltip.x + tooltip.w / 2 - annotationRectangle.getWidth() / 2 - MARGIN;
+    double y = tooltip.y - 3 * MARGIN - annotationRectangle.getHeight();
+    //    System.out.println("x = " + x);
+    //    System.out.println("y = " + y);
+    //    x = Math.min(x, -w);
+    //    System.out.println("x = " + x);
+
+    // the label in a box
+    x = Math.max(x, leftEdge);
+    x = Math.min(x, rightEdge - w);
+    y = Math.max(y, topEdge);
+    y = Math.min(y, bottomEdge - h);
+    //    System.out.println("x = " + x);
+    //    System.out.println("y = " + y);
+
+    Rectangle2D rectangle = new Rectangle2D.Double(x, y, w, h);
+
+    // fill background
+    g.setColor(styler.getToolTipBackgroundColor());
+    g.fill(rectangle);
+
+    // draw outline
+    g.setStroke(SOLID_STROKE);
+    g.setColor(styler.getToolTipBorderColor());
+    g.draw(rectangle);
+
+    // draw text label
+    Shape shape = textLayout.getOutline(null);
+    g.setColor(styler.getChartFontColor());
+    g.setFont(styler.getToolTipFont());
+    AffineTransform orig = g.getTransform();
+    AffineTransform at = new AffineTransform();
+    at.translate(x + MARGIN - 1, y + MARGIN - 1 + halfHeight);
+    g.transform(at);
+    g.fill(shape);
+    g.setTransform(orig);
+  }
+
+  private void paintMultiLineToolTip(Graphics2D g) {
+
+    String[] texts = tooltip.label.split(System.lineSeparator());
+    List<TextLayout> list = new ArrayList<>();
+    TextLayout textLayout = null;
+    Rectangle2D bounds = null;
+    double backgroundHeight = MARGIN;
+    double backgroundWidth = 0;
+    for (String text : texts) {
+      textLayout =
+          new TextLayout(text, styler.getToolTipFont(), new FontRenderContext(null, true, false));
+      bounds = textLayout.getBounds();
+      bounds.getHeight();
+      if (backgroundWidth < bounds.getWidth()) {
+        backgroundWidth = bounds.getWidth();
+      }
+      backgroundHeight += styler.getToolTipFont().getSize() + MARGIN;
+      list.add(textLayout);
+    }
+
+    //    System.out.println("paintMultiLineToolTip");
+
+    Rectangle clipBounds = g.getClipBounds();
+    double startX = tooltip.x;
+    double startY = tooltip.y;
+    if (tooltip.x + MOUSE_MARGIN + backgroundWidth > clipBounds.getX() + clipBounds.getWidth()) {
+      startX = tooltip.x - backgroundWidth - MOUSE_MARGIN;
+    }
+
+    if (tooltip.y + MOUSE_MARGIN + backgroundHeight > clipBounds.getY() + clipBounds.getHeight()) {
+      startY = tooltip.y - backgroundHeight - MOUSE_MARGIN;
+    }
+
+    g.setColor(styler.getToolTipBackgroundColor());
+    g.fillRect(
+        (int) startX + MOUSE_MARGIN,
+        (int) startY + MOUSE_MARGIN,
+        (int) (backgroundWidth) + 2 * MARGIN,
+        (int) (backgroundHeight));
+
+    AffineTransform orig = g.getTransform();
+    AffineTransform at = new AffineTransform();
+    at.translate(
+        startX + MOUSE_MARGIN + MARGIN,
+        startY + textLayout.getBounds().getHeight() + MOUSE_MARGIN + MARGIN);
+    g.transform(at);
+    // TODO make a fontcolor for tooltips in styler
+    g.setColor(styler.getChartFontColor());
+    g.setFont(styler.getToolTipFont());
+    for (TextLayout t : list) {
+      g.fill(t.getOutline(null));
+      at = new AffineTransform();
+      at.translate(0, styler.getToolTipFont().getSize() + MARGIN);
+      g.transform(at);
+    }
+    g.setTransform(orig);
+  }
+
+  // Adding Tooltips ////////////////////////////
+
+  /**
+   * Adds a data (xValue, yValue) with coordinates (xOffset, yOffset). This point will be
+   * highlighted with a circle centering (xOffset, yOffset)
+   */
+  void addData(double xOffset, double yOffset, String xValue, String yValue) {
+
+    String label = getLabel(xValue, yValue);
+
+    addData(xOffset, yOffset, label);
+  }
+
+  /**
+   * Adds a data with label with coordinates (xOffset, yOffset). This point will be highlighted with
+   * a circle centering (xOffset, yOffset)
+   */
+  void addData(double xOffset, double yOffset, String label) {
+
+    ToolTip toolTip = new ToolTip(xOffset, yOffset, label);
+    toolTipList.add(toolTip);
+  }
+
+  /**
+   * Adds a data (xValue, yValue) with geometry defined with shape. This point will be highlighted
+   * using the shape
+   */
+  void addData(
+      Shape shape, double xOffset, double yOffset, double width, String xValue, String yValue) {
+
+    String label = getLabel(xValue, yValue);
+    addData(shape, xOffset, yOffset, width, label);
+  }
+
+  void addData(Shape shape, double xOffset, double yOffset, double width, String label) {
+
+    ToolTip toolTip = new ToolTip(shape, xOffset, yOffset, width, label);
+    toolTipList.add(toolTip);
+  }
+
+  private String getLabel(String xValue, String yValue) {
+
+    switch (styler.getToolTipType()) {
+      case xAndYLabels:
+        return "(" + xValue + ", " + yValue + ")";
+      case xLabels:
+        return xValue;
+      case yLabels:
+        return yValue;
+      default:
+        break;
+    }
+    return "";
+  }
+
+  public void clearData() {
+    toolTipList.clear();
+  }
+
+  static class ToolTip {
+
+    // width of data point (used for bar charts)
+    // TODO possibly delete this
+    final double w;
+    private final String label;
+    // used for popup detection & popup highlight
+    private final Shape shape;
+    // label center coordinates
+    private final double x;
+    private final double y;
+
+    /**
+     * Constructor
+     *
+     * @param x
+     * @param y
+     * @param label
+     */
+    ToolTip(double x, double y, String label) {
+
+      double halfSize = MARGIN * 1.5;
+      double markerSize = MARGIN * 3;
+
+      this.shape = new Ellipse2D.Double(x - halfSize, y - halfSize, markerSize, markerSize);
+
+      this.x = x;
+      this.y = y;
+      this.w = 0;
+      this.label = label;
+    }
+
+    /**
+     * Constructor
+     *
+     * @param shape
+     * @param x
+     * @param y
+     * @param width
+     * @param label
+     */
+    ToolTip(Shape shape, double x, double y, double width, String label) {
+
+      this.x = x;
+      this.y = y;
+      this.w = width;
+      this.shape = shape;
+      this.label = label;
+    }
+
+    @Override
+    public String toString() {
+      return "DataPoint{"
+          + "w="
+          + w
+          + ", label='"
+          + label
+          + '\''
+          + ", shape="
+          + shape
+          + ", x="
+          + x
+          + ", y="
+          + y
+          + '}';
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) return true;
+      if (o == null || getClass() != o.getClass()) return false;
+      ToolTip tooltip = (ToolTip) o;
+      return label.equals(tooltip.label) && shape.equals(tooltip.shape);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(label, shape);
+    }
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/package-info.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/package-info.java
new file mode 100644
index 0000000000000000000000000000000000000000..1b262af23f94aff918e398eeb89f14d8372466df
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * Classes in this package are internal and are not intended to be accessed directly. Therefore,
+ * they are not included in the JavaDocs.
+ */
+package org.knowm.xchart.internal;
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/series/AxesChartSeries.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/series/AxesChartSeries.java
new file mode 100644
index 0000000000000000000000000000000000000000..9d8bdcff1e95bbc8a591e0730150d908b036f4e2
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/series/AxesChartSeries.java
@@ -0,0 +1,148 @@
+package org.knowm.xchart.internal.series;
+
+import java.awt.*;
+
+/** A Series containing X and Y data to be plotted on a Chart with X and Y Axes. */
+public abstract class AxesChartSeries extends Series {
+
+  final DataType xAxisDataType;
+  final DataType yAxisType;
+
+  /** the minimum value of axis range */
+  protected double xMin;
+
+  /** the maximum value of axis range */
+  protected double xMax;
+
+  /** the minimum value of axis range */
+  protected double yMin;
+
+  /** the maximum value of axis range */
+  protected double yMax;
+
+  /** Line Style */
+  private BasicStroke stroke;
+
+  /** Line Color */
+  private Color lineColor;
+
+  /** Line Width */
+  private float lineWidth = -1.0f;
+
+  /**
+   * Constructor
+   *
+   * @param name
+   * @param xAxisDataType
+   */
+  protected AxesChartSeries(String name, DataType xAxisDataType) {
+
+    super(name);
+    this.xAxisDataType = xAxisDataType;
+    yAxisType = DataType.Number;
+  }
+
+  /**
+   * Constructor
+   *
+   * @param name
+   * @param xAxisDataType
+   * @param yAxisDataType
+   */
+  protected AxesChartSeries(String name, DataType xAxisDataType, DataType yAxisDataType) {
+
+    super(name);
+    this.xAxisDataType = xAxisDataType;
+    this.yAxisType = yAxisDataType;
+  }
+
+  protected abstract void calculateMinMax();
+
+  public double getXMin() {
+
+    return xMin;
+  }
+
+  public double getXMax() {
+
+    return xMax;
+  }
+
+  public double getYMin() {
+
+    return yMin;
+  }
+
+  public double getYMax() {
+
+    return yMax;
+  }
+
+  public BasicStroke getLineStyle() {
+
+    return stroke;
+  }
+
+  /**
+   * Set the line style of the series
+   *
+   * @param basicStroke
+   */
+  public AxesChartSeries setLineStyle(BasicStroke basicStroke) {
+
+    stroke = basicStroke;
+    if (this.lineWidth > 0.0f) {
+      stroke =
+          new BasicStroke(
+              lineWidth,
+              this.stroke.getEndCap(),
+              this.stroke.getLineJoin(),
+              this.stroke.getMiterLimit(),
+              this.stroke.getDashArray(),
+              this.stroke.getDashPhase());
+    }
+    return this;
+  }
+
+  public Color getLineColor() {
+
+    return lineColor;
+  }
+
+  /**
+   * Set the line color of the series
+   *
+   * @param color
+   */
+  public AxesChartSeries setLineColor(java.awt.Color color) {
+
+    this.lineColor = color;
+    return this;
+  }
+
+  public float getLineWidth() {
+
+    return lineWidth;
+  }
+
+  /**
+   * Set the line width of the series
+   *
+   * @param lineWidth
+   */
+  public AxesChartSeries setLineWidth(float lineWidth) {
+
+    this.lineWidth = lineWidth;
+    return this;
+  }
+
+  public DataType getxAxisDataType() {
+
+    return xAxisDataType;
+  }
+
+  public DataType getyAxisDataType() {
+
+    return yAxisType;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/series/AxesChartSeriesCategory.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/series/AxesChartSeriesCategory.java
new file mode 100644
index 0000000000000000000000000000000000000000..8c3176dc5433d0e6121311fec6e3f3370f64795d
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/series/AxesChartSeriesCategory.java
@@ -0,0 +1,182 @@
+package org.knowm.xchart.internal.series;
+
+import java.util.Collection;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * A Series containing X and Y data to be plotted on a Chart with X and Y Axes. xData can be Number
+ * or Date or String, hence a List<?>
+ */
+public abstract class AxesChartSeriesCategory extends MarkerSeries {
+
+  List<?> xData; // can be Number or Date or String
+
+  List<? extends Number> yData;
+
+  List<? extends Number> extraValues;
+
+  /**
+   * Constructor
+   *
+   * @param name
+   * @param xData
+   * @param yData
+   */
+  public AxesChartSeriesCategory(
+      String name,
+      List<?> xData,
+      List<? extends Number> yData,
+      List<? extends Number> extraValues,
+      DataType xAxisDataType) {
+
+    super(name, xAxisDataType);
+
+    this.xData = xData;
+    this.yData = yData;
+    this.extraValues = extraValues;
+
+    calculateMinMax();
+  }
+
+  /**
+   * This is an internal method which shouldn't be called from client code. Use
+   * XYChart.updateXYSeries or CategoryChart.updateXYSeries instead!
+   *
+   * @param newXData
+   * @param newYData
+   * @param newExtraValues
+   */
+  public void replaceData(
+      List<?> newXData, List<? extends Number> newYData, List<? extends Number> newExtraValues) {
+
+    // Sanity check
+    if (newExtraValues != null && newExtraValues.size() != newYData.size()) {
+      throw new IllegalArgumentException("error bars and Y-Axis sizes are not the same!!!");
+    }
+    if (newXData.size() != newYData.size()) {
+      throw new IllegalArgumentException("X and Y-Axis sizes are not the same!!!");
+    }
+
+    xData = newXData;
+    yData = newYData;
+    extraValues = newExtraValues;
+    calculateMinMax();
+  }
+
+  /**
+   * For box plot, replace yData
+   *
+   * @param newYData Updated yData
+   */
+  public void replaceData(List<? extends Number> newYData) {
+
+    yData = newYData;
+    calculateMinMax();
+  }
+
+  @Override
+  protected void calculateMinMax() {
+
+    // xData
+    double[] xMinMax = findMinMax(xData, xAxisDataType);
+    xMin = xMinMax[0];
+    xMax = xMinMax[1];
+    // System.out.println(xMin);
+    // System.out.println(xMax);
+
+    // yData
+    double[] yMinMax;
+    if (extraValues == null) {
+      yMinMax = findMinMax(yData, yAxisType);
+    } else {
+      yMinMax = findMinMaxWithErrorBars(yData, extraValues);
+    }
+    yMin = yMinMax[0];
+    yMax = yMinMax[1];
+    // System.out.println(yMin);
+    // System.out.println(yMax);
+  }
+
+  /**
+   * Finds the min and max of a dataset accounting for error bars
+   *
+   * @param data
+   * @param errorBars
+   * @return
+   */
+  private double[] findMinMaxWithErrorBars(
+      Collection<? extends Number> data, Collection<? extends Number> errorBars) {
+
+    double min = Double.MAX_VALUE;
+    double max = -Double.MAX_VALUE;
+
+    Iterator<? extends Number> itr = data.iterator();
+    Iterator<? extends Number> ebItr = errorBars.iterator();
+    while (itr.hasNext()) {
+      double bigDecimal = itr.next().doubleValue();
+      double eb = ebItr.next().doubleValue();
+      if (bigDecimal - eb < min) {
+        min = bigDecimal - eb;
+      }
+      if (bigDecimal + eb > max) {
+        max = bigDecimal + eb;
+      }
+    }
+    return new double[] {min, max};
+  }
+
+  /**
+   * Finds the min and max of a dataset
+   *
+   * @param data
+   * @return
+   */
+  double[] findMinMax(Collection<?> data, DataType dataType) {
+
+    double min = Double.MAX_VALUE;
+    double max = -Double.MAX_VALUE;
+
+    for (Object dataPoint : data) {
+
+      if (dataPoint == null) {
+        continue;
+      }
+
+      double value = 0.0;
+
+      if (dataType == DataType.Number) {
+        value = ((Number) dataPoint).doubleValue();
+      } else if (dataType == DataType.Date) {
+        Date date = (Date) dataPoint;
+        value = date.getTime();
+      } else if (dataType == DataType.String) {
+        return new double[] {Double.NaN, Double.NaN};
+      }
+      if (value < min) {
+        min = value;
+      }
+      if (value > max) {
+        max = value;
+      }
+    }
+
+    return new double[] {min, max};
+  }
+
+  public Collection<?> getXData() {
+
+    return xData;
+  }
+
+  public Collection<? extends Number> getYData() {
+
+    return yData;
+  }
+
+  public Collection<? extends Number> getExtraValues() {
+
+    return extraValues;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/series/AxesChartSeriesNumericalNoErrorBars.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/series/AxesChartSeriesNumericalNoErrorBars.java
new file mode 100644
index 0000000000000000000000000000000000000000..5941f54201d22db2253b76664c9974ae6bd2d519
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/series/AxesChartSeriesNumericalNoErrorBars.java
@@ -0,0 +1,243 @@
+package org.knowm.xchart.internal.series;
+
+import java.util.Arrays;
+
+/**
+ * A Series containing X and Y data to be plotted on a Chart with X and Y Axes. xData can be Number
+ * or Date(epochtime), hence a double[]
+ */
+// TODO weird name of class since it does contain extravalues for error bars!
+public abstract class AxesChartSeriesNumericalNoErrorBars extends MarkerSeries {
+
+  // permanent data
+  double[] xDataAll;
+  double[] yDataAll;
+  double[] extraValuesAll;
+
+  // temporary data different from permanent data if some is filter out for zooming
+  double[] xData; // can be Number or Date(epochtime)
+  double[] yData;
+  double[] extraValues;
+
+  /**
+   * Constructor
+   *
+   * @param name
+   * @param xData
+   * @param yData
+   * @param xAxisDataType
+   */
+  public AxesChartSeriesNumericalNoErrorBars(
+      String name, double[] xData, double[] yData, double[] extraValues, DataType xAxisDataType) {
+
+    super(name, xAxisDataType);
+
+    this.xDataAll = xData;
+    this.yDataAll = yData;
+    this.extraValuesAll = extraValues;
+
+    this.xData = xData;
+    this.yData = yData;
+    this.extraValues = extraValues;
+
+    calculateMinMax();
+  }
+
+  /**
+   * This is an internal method which shouldn't be called from client code. Use
+   * XYChart.updateXYSeries or CategoryChart.updateXYSeries instead!
+   *
+   * @param newXData
+   * @param newYData
+   * @param newExtraValues
+   */
+  public void replaceData(double[] newXData, double[] newYData, double[] newExtraValues) {
+
+    // Sanity check
+    if (newExtraValues != null && newExtraValues.length != newYData.length) {
+      throw new IllegalArgumentException("error bars and Y-Axis sizes are not the same!!!");
+    }
+    if (newXData.length != newYData.length) {
+      throw new IllegalArgumentException("X and Y-Axis sizes are not the same!!!");
+    }
+
+    this.xDataAll = newXData;
+    this.yDataAll = newYData;
+    this.extraValuesAll = newExtraValues;
+
+    xData = newXData;
+    yData = newYData;
+    extraValues = newExtraValues;
+
+    calculateMinMax();
+  }
+
+  public void filterXByIndex(int startIndex, int endIndex) {
+
+    startIndex = Math.max(0, startIndex);
+    endIndex = Math.min(yDataAll.length, endIndex);
+
+    xData = Arrays.copyOfRange(xDataAll, startIndex, endIndex);
+    yData = Arrays.copyOfRange(yDataAll, startIndex, endIndex);
+    if (extraValuesAll != null) {
+      extraValues = Arrays.copyOfRange(extraValuesAll, startIndex, endIndex);
+    }
+
+    calculateMinMax();
+  }
+
+  public boolean filterXByValue(double minValue, double maxValue) {
+
+    int length = xDataAll.length;
+    boolean[] filterResult = new boolean[length];
+    int remainingDataCount = 0;
+    for (int i = 0; i < length; i++) {
+      double val = xDataAll[i];
+      boolean result = val >= minValue && val <= maxValue;
+      filterResult[i] = result;
+      if (result) {
+        remainingDataCount++;
+      }
+    }
+
+    // System.out.println("Filtering between " + String.format("%.2f %.2f", minValue, maxValue) + "
+    // all: " + length + " rem: " + remainingDataCount);
+    if (remainingDataCount == length) {
+      return false;
+    }
+
+    xData = new double[remainingDataCount];
+    yData = new double[remainingDataCount];
+    boolean extra = extraValuesAll != null;
+
+    if (extra) {
+      extraValues = new double[remainingDataCount];
+    }
+
+    int ind = 0;
+    for (int i = 0; i < length; i++) {
+      if (!filterResult[i]) {
+        continue;
+      }
+      xData[ind] = xDataAll[i];
+      yData[ind] = yDataAll[i];
+      if (extra) {
+        extraValues[ind] = extraValuesAll[i];
+      }
+      ind++;
+    }
+
+    calculateMinMax();
+    return true;
+  }
+
+  public void resetFilter() {
+
+    xData = xDataAll;
+    yData = yDataAll;
+    extraValues = extraValuesAll;
+    calculateMinMax();
+  }
+
+  /**
+   * Finds the min and max of a dataset
+   *
+   * @param data
+   * @return
+   */
+  double[] findMinMax(double[] data) {
+
+    double min = Double.MAX_VALUE;
+    double max = -Double.MAX_VALUE;
+
+    for (double dataPoint : data) {
+
+      if (Double.isNaN(dataPoint)) {
+        continue;
+      } else {
+        if (dataPoint < min) {
+          min = dataPoint;
+        }
+        if (dataPoint > max) {
+          max = dataPoint;
+        }
+      }
+    }
+
+    return new double[] {min, max};
+  }
+
+  @Override
+  protected void calculateMinMax() {
+
+    // xData
+    double[] xMinMax = findMinMax(xData);
+    xMin = xMinMax[0];
+    xMax = xMinMax[1];
+    // System.out.println(xMin);
+    // System.out.println(xMax);
+
+    // yData
+    double[] yMinMax;
+    if (extraValues == null) {
+      yMinMax = findMinMax(yData);
+    } else {
+      yMinMax = findMinMaxWithErrorBars(yData, extraValues);
+    }
+    yMin = yMinMax[0];
+    yMax = yMinMax[1];
+    // System.out.println(yMin);
+    // System.out.println(yMax);
+  }
+
+  /**
+   * Finds the min and max of a dataset accounting for error bars
+   *
+   * @param data
+   * @param errorBars
+   * @return
+   */
+  private double[] findMinMaxWithErrorBars(double[] data, double[] errorBars) {
+
+    double min = Double.MAX_VALUE;
+    double max = -Double.MAX_VALUE;
+
+    for (int i = 0; i < data.length; i++) {
+
+      double d = data[i];
+      double eb = errorBars[i];
+      if (d - eb < min) {
+        min = d - eb;
+      }
+      if (d + eb > max) {
+        max = d + eb;
+      }
+    }
+    return new double[] {min, max};
+  }
+
+  /**
+   * Is xData.length equal to xDataAll.length
+   *
+   * @return true: equal; false: not equal
+   */
+  public boolean isAllXData() {
+
+    return xData.length == xDataAll.length;
+  }
+
+  public double[] getXData() {
+
+    return xData;
+  }
+
+  public double[] getYData() {
+
+    return yData;
+  }
+
+  public double[] getExtraValues() {
+
+    return extraValues;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/series/MarkerSeries.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/series/MarkerSeries.java
new file mode 100644
index 0000000000000000000000000000000000000000..18b11434d5564bd934830bcfe70b6f86c08ff06f
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/series/MarkerSeries.java
@@ -0,0 +1,60 @@
+package org.knowm.xchart.internal.series;
+
+import java.awt.*;
+import org.knowm.xchart.style.markers.Marker;
+
+/**
+ * A Series containing X and Y data to be plotted on a Chart with X and Y Axes, contains series
+ * markers and error bars.
+ */
+public abstract class MarkerSeries extends AxesChartSeries {
+
+  /** Marker */
+  private Marker marker;
+
+  /** Marker Color */
+  private Color markerColor;
+
+  /**
+   * Constructor
+   *
+   * @param name
+   * @param xAxisDataType
+   */
+  protected MarkerSeries(String name, DataType xAxisDataType) {
+
+    super(name, xAxisDataType);
+  }
+
+  public Marker getMarker() {
+
+    return marker;
+  }
+
+  /**
+   * Sets the marker for the series
+   *
+   * @param marker
+   */
+  public MarkerSeries setMarker(Marker marker) {
+
+    this.marker = marker;
+    return this;
+  }
+
+  public Color getMarkerColor() {
+
+    return markerColor;
+  }
+
+  /**
+   * Sets the marker color for the series
+   *
+   * @param color
+   */
+  public MarkerSeries setMarkerColor(java.awt.Color color) {
+
+    this.markerColor = color;
+    return this;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/series/NoMarkersSeries.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/series/NoMarkersSeries.java
new file mode 100644
index 0000000000000000000000000000000000000000..b0122ebc6426b259482d56ee8788b7869fd18a57
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/series/NoMarkersSeries.java
@@ -0,0 +1,45 @@
+package org.knowm.xchart.internal.series;
+
+/**
+ * A Series containing X and Y data to be plotted on a Chart with X and Y Axes, values associated
+ * with each X-Y point, could be used for bubble sizes for example, but no error bars, as the min
+ * and max are calculated differently. No markers.
+ */
+public abstract class NoMarkersSeries extends AxesChartSeriesNumericalNoErrorBars {
+
+  /**
+   * Constructor
+   *
+   * @param name
+   * @param xData
+   * @param yData
+   * @param extraValues
+   */
+  protected NoMarkersSeries(
+      String name, double[] xData, double[] yData, double[] extraValues, Series.DataType axisType) {
+
+    super(name, xData, yData, extraValues, axisType);
+
+    // TODO why do we need this here?
+    this.extraValues = extraValues;
+    calculateMinMax();
+  }
+
+  @Override
+  protected void calculateMinMax() {
+
+    // xData
+    double[] xMinMax = findMinMax(xData);
+    xMin = xMinMax[0];
+    xMax = xMinMax[1];
+    // System.out.println(xMin);
+    // System.out.println(xMax);
+
+    // yData
+    double[] yMinMax = findMinMax(yData);
+    yMin = yMinMax[0];
+    yMax = yMinMax[1];
+    // System.out.println(yMin);
+    // System.out.println(yMax);
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/series/Series.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/series/Series.java
new file mode 100644
index 0000000000000000000000000000000000000000..91c21696c67d2ed4c7e566656f4b3f5794a9195c
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/series/Series.java
@@ -0,0 +1,120 @@
+package org.knowm.xchart.internal.series;
+
+import java.awt.*;
+import org.knowm.xchart.internal.chartpart.RenderableSeries.LegendRenderType;
+
+/** A Series to be plotted on a Chart */
+public abstract class Series {
+
+  private final String name;
+  // TODO rename this displayName??
+  private String label;
+  private Color fillColor;
+  private boolean showInLegend = true;
+  private boolean isEnabled = true;
+
+  // TODO there is not always a y-axis group (pie chart for example) move this to an axis series
+  // tyoe??
+  private int yAxisGroup = 0;
+
+  /** the yAxis decimalPattern */
+  private String yAxisDecimalPattern;
+
+  /**
+   * Constructor
+   *
+   * @param name the name of the series
+   */
+  protected Series(String name) {
+
+    if (name == null || name.length() < 1) {
+      throw new IllegalArgumentException("Series name cannot be null or zero-length!!!");
+    }
+    this.name = name;
+    this.label = name;
+  }
+
+  public abstract LegendRenderType getLegendRenderType();
+
+  public Color getFillColor() {
+
+    return fillColor;
+  }
+
+  public Series setFillColor(Color fillColor) {
+
+    this.fillColor = fillColor;
+    return this;
+  }
+
+  public String getName() {
+
+    return name;
+  }
+
+  public String getLabel() {
+
+    return label;
+  }
+
+  public Series setLabel(String label) {
+
+    this.label = label;
+    return this;
+  }
+
+  public boolean isShowInLegend() {
+
+    return showInLegend;
+  }
+
+  public Series setShowInLegend(boolean showInLegend) {
+
+    this.showInLegend = showInLegend;
+    return this;
+  }
+
+  public boolean isEnabled() {
+
+    return isEnabled;
+  }
+
+  public Series setEnabled(boolean isEnabled) {
+
+    this.isEnabled = isEnabled;
+    return this;
+  }
+
+  public int getYAxisGroup() {
+
+    return yAxisGroup;
+  }
+
+  /**
+   * Set the Y Axis Group the series should belong to
+   *
+   * @param yAxisGroup
+   */
+  public Series setYAxisGroup(int yAxisGroup) {
+
+    this.yAxisGroup = yAxisGroup;
+    return this;
+  }
+
+  public String getYAxisDecimalPattern() {
+
+    return yAxisDecimalPattern;
+  }
+
+  public Series setYAxisDecimalPattern(String yAxisDecimalPattern) {
+
+    this.yAxisDecimalPattern = yAxisDecimalPattern;
+    return this;
+  }
+
+  public enum DataType {
+    Number,
+    Date,
+    String
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/style/SeriesColorMarkerLineStyle.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/style/SeriesColorMarkerLineStyle.java
new file mode 100644
index 0000000000000000000000000000000000000000..da6226488709e9d1edcfaafacfd5ce02726ca743
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/style/SeriesColorMarkerLineStyle.java
@@ -0,0 +1,41 @@
+package org.knowm.xchart.internal.style;
+
+import java.awt.*;
+import org.knowm.xchart.style.markers.Marker;
+
+/** A DTO to hold the Series' Color, Marker, and LineStyle */
+public final class SeriesColorMarkerLineStyle {
+
+  private final Color color;
+  private final Marker marker;
+  private final BasicStroke stroke;
+
+  /**
+   * Constructor
+   *
+   * @param color
+   * @param marker
+   * @param stroke
+   */
+  public SeriesColorMarkerLineStyle(Color color, Marker marker, BasicStroke stroke) {
+
+    this.color = color;
+    this.marker = marker;
+    this.stroke = stroke;
+  }
+
+  public Color getColor() {
+
+    return color;
+  }
+
+  public Marker getMarker() {
+
+    return marker;
+  }
+
+  public BasicStroke getStroke() {
+
+    return stroke;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/internal/style/SeriesColorMarkerLineStyleCycler.java b/XChart/xchart/src/main/java/org/knowm/xchart/internal/style/SeriesColorMarkerLineStyleCycler.java
new file mode 100644
index 0000000000000000000000000000000000000000..16bdeea85bbd729a60a6fec04396b50e8f896aee
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/internal/style/SeriesColorMarkerLineStyleCycler.java
@@ -0,0 +1,65 @@
+package org.knowm.xchart.internal.style;
+
+import java.awt.*;
+import org.knowm.xchart.style.markers.Marker;
+
+/**
+ * Cycles through the different colors, markers, and strokes in a predetermined way
+ *
+ * <p>This is an internal class that should not be used be clients
+ */
+public class SeriesColorMarkerLineStyleCycler {
+
+  /** a List holding the Colors */
+  private final Color[] seriesColorList;
+
+  /** a map holding the SeriesMarkers */
+  private final Marker[] seriesMarkerList;
+
+  /** a map holding the SeriesLineStyles */
+  private final BasicStroke[] seriesLineStyleList;
+
+  /** an internal counter */
+  private int colorCounter = 0;
+
+  private int markerCounter = 0;
+  private int strokeCounter = 0;
+
+  /** Constructor */
+  public SeriesColorMarkerLineStyleCycler(
+      Color[] seriesColorList, Marker[] seriesMarkerList, BasicStroke[] seriesLineStyleList) {
+
+    this.seriesColorList = seriesColorList;
+    this.seriesMarkerList = seriesMarkerList;
+    this.seriesLineStyleList = seriesLineStyleList;
+  }
+
+  /**
+   * Get the next ColorMarkerLineStyle
+   *
+   * @return the next ColorMarkerLineStyle
+   */
+  public SeriesColorMarkerLineStyle getNextSeriesColorMarkerLineStyle() {
+
+    // 1. Color - cycle through colors one by one
+    if (colorCounter >= seriesColorList.length) {
+      colorCounter = 0;
+      strokeCounter++;
+    }
+    Color seriesColor = seriesColorList[colorCounter++];
+
+    // 2. BasicStroke - cycle through strokes one by one but only after a color cycle
+    if (strokeCounter >= seriesLineStyleList.length) {
+      strokeCounter = 0;
+    }
+    BasicStroke seriesLineStyle = seriesLineStyleList[strokeCounter];
+
+    // 3. Marker - cycle through markers one by one
+    if (markerCounter >= seriesMarkerList.length) {
+      markerCounter = 0;
+    }
+    Marker marker = seriesMarkerList[markerCounter++];
+
+    return new SeriesColorMarkerLineStyle(seriesColor, marker, seriesLineStyle);
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/style/AxesChartStyler.java b/XChart/xchart/src/main/java/org/knowm/xchart/style/AxesChartStyler.java
new file mode 100644
index 0000000000000000000000000000000000000000..86940f7c9cd1eac96b4ee69ad66e5e6f80e95793
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/style/AxesChartStyler.java
@@ -0,0 +1,953 @@
+package org.knowm.xchart.style;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Font;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TimeZone;
+import java.util.function.Function;
+
+public abstract class AxesChartStyler extends Styler {
+
+  // Chart Axes ///////////////////////////////
+  private boolean xAxisTitleVisible;
+  private boolean yAxisTitleVisible;
+  private Font axisTitleFont;
+  private boolean xAxisTicksVisible;
+  private boolean yAxisTicksVisible;
+  private Font axisTickLabelsFont;
+  private int axisTickMarkLength;
+  private int axisTickPadding;
+  private Color axisTickMarksColor;
+  private BasicStroke axisTickMarksStroke;
+  private Color axisTickLabelsColor;
+  private boolean isAxisTicksLineVisible;
+  private boolean isAxisTicksMarksVisible;
+  private int plotMargin;
+  private int axisTitlePadding;
+  private int xAxisTickMarkSpacingHint;
+  private int yAxisTickMarkSpacingHint;
+  private boolean isXAxisLogarithmic;
+  private boolean isYAxisLogarithmic;
+  private Double xAxisMin;
+  private Double xAxisMax;
+  private final HashMap<Integer, Double> yAxisMinMap = new HashMap<>();
+  private final HashMap<Integer, Double> yAxisMaxMap = new HashMap<>();
+
+  // By default, all available labels are displayed
+  // TODO what's this for anyway??
+  private int xAxisMaxLabelCount = 0;
+
+  // Chart Plot Area ///////////////////////////////
+  private boolean isPlotGridHorizontalLinesVisible;
+  private boolean isPlotGridVerticalLinesVisible;
+  private boolean isPlotTicksMarksVisible;
+  private Color plotGridLinesColor;
+  private BasicStroke plotGridLinesStroke;
+
+  // Error Bars ///////////////////////////////
+  private Color errorBarsColor;
+  private boolean isErrorBarsColorSeriesColor;
+
+  // Formatting ////////////////////////////////
+  private Locale locale;
+  private TimeZone timezone;
+  private String datePattern;
+  private String xAxisDecimalPattern;
+  private String yAxisDecimalPattern;
+  private Map<Integer, String> yAxisGroupDecimalPatternMap;
+  private boolean xAxisLogarithmicDecadeOnly;
+  private boolean yAxisLogarithmicDecadeOnly;
+  private Function<Double, String> xAxisTickLabelsFormattingFunction;
+  private Function<Double, String> yAxisTickLabelsFormattingFunction;
+
+  // TickLabels and MarksColor colors for xAxis, yAxis, yAxisGroup ////////////////////////////////
+  private Color xAxisTickLabelsColor;
+  private Color yAxisTickLabelsColor;
+  private Color xAxisTickMarksColor;
+  private Color yAxisTickMarksColor;
+  // TODO where's the axis title color map?? Add it here!
+  private final Map<Integer, Color> yAxisGroupTickLabelsColorMap = new HashMap<>();
+  private final Map<Integer, Color> yAxisGroupTickMarksColorMap = new HashMap<>();
+  private TextAlignment xAxisLabelAlignment = TextAlignment.Centre;
+  private TextAlignment xAxisLabelAlignmentVertical = TextAlignment.Centre;
+  private TextAlignment yAxisLabelAlignment = TextAlignment.Left;
+  private int xAxisLabelRotation = 0;
+
+  @Override
+  void setAllStyles() {
+
+    super.setAllStyles();
+
+    // axes
+    this.xAxisTitleVisible = theme.isXAxisTitleVisible();
+    this.yAxisTitleVisible = theme.isYAxisTitleVisible();
+    this.axisTitleFont = theme.getAxisTitleFont();
+    this.xAxisTicksVisible = theme.isXAxisTicksVisible();
+    this.yAxisTicksVisible = theme.isYAxisTicksVisible();
+    this.axisTickLabelsFont = theme.getAxisTickLabelsFont();
+    this.axisTickMarkLength = theme.getAxisTickMarkLength();
+    this.axisTickPadding = theme.getAxisTickPadding();
+    this.axisTickMarksColor = theme.getAxisTickMarksColor();
+    this.axisTickMarksStroke = theme.getAxisTickMarksStroke();
+    this.axisTickLabelsColor = theme.getAxisTickLabelsColor();
+    this.isAxisTicksLineVisible = theme.isAxisTicksLineVisible();
+    this.isAxisTicksMarksVisible = theme.isAxisTicksMarksVisible();
+    this.plotMargin = theme.getPlotMargin();
+    this.axisTitlePadding = theme.getAxisTitlePadding();
+    this.xAxisTickMarkSpacingHint = theme.getXAxisTickMarkSpacingHint();
+    this.yAxisTickMarkSpacingHint = theme.getYAxisTickMarkSpacingHint();
+    this.isXAxisLogarithmic = false;
+    this.isYAxisLogarithmic = false;
+    this.xAxisMin = null;
+    this.xAxisMax = null;
+    this.yAxisMinMap.clear();
+    this.yAxisMaxMap.clear();
+
+    // Chart Plot Area ///////////////////////////////
+    this.isPlotGridVerticalLinesVisible = theme.isPlotGridVerticalLinesVisible();
+    this.isPlotGridHorizontalLinesVisible = theme.isPlotGridHorizontalLinesVisible();
+    this.isPlotTicksMarksVisible = theme.isPlotTicksMarksVisible();
+    this.plotGridLinesColor = theme.getPlotGridLinesColor();
+    this.plotGridLinesStroke = theme.getPlotGridLinesStroke();
+
+    // Error Bars ///////////////////////////////
+    this.errorBarsColor = theme.getErrorBarsColor();
+    this.isErrorBarsColorSeriesColor = theme.isErrorBarsColorSeriesColor();
+
+    // Formatting ////////////////////////////////
+    this.locale = Locale.getDefault();
+    this.timezone = TimeZone.getDefault();
+    this.datePattern = null; // if not null, this override pattern will be used
+    this.xAxisDecimalPattern = null;
+    this.yAxisDecimalPattern = null;
+    this.yAxisGroupDecimalPatternMap = new HashMap<>();
+    this.xAxisLogarithmicDecadeOnly = true;
+    this.yAxisLogarithmicDecadeOnly = true;
+  }
+
+  // Chart Axes ///////////////////////////////
+
+  public boolean isXAxisTitleVisible() {
+
+    return xAxisTitleVisible;
+  }
+
+  /**
+   * Set the x-axis title visibility
+   *
+   * @param xAxisTitleVisible
+   */
+  public AxesChartStyler setXAxisTitleVisible(boolean xAxisTitleVisible) {
+
+    this.xAxisTitleVisible = xAxisTitleVisible;
+    return this;
+  }
+
+  public boolean isYAxisTitleVisible() {
+
+    return yAxisTitleVisible;
+  }
+
+  /**
+   * Set the y-axis title visibility
+   *
+   * @param yAxisTitleVisible
+   */
+  public AxesChartStyler setYAxisTitleVisible(boolean yAxisTitleVisible) {
+
+    this.yAxisTitleVisible = yAxisTitleVisible;
+    return this;
+  }
+
+  /**
+   * Set the x- and y-axis titles visibility
+   *
+   * @param isVisible
+   */
+  public AxesChartStyler setAxisTitlesVisible(boolean isVisible) {
+
+    this.xAxisTitleVisible = isVisible;
+    this.yAxisTitleVisible = isVisible;
+    return this;
+  }
+
+  public Font getAxisTitleFont() {
+
+    return axisTitleFont;
+  }
+
+  /**
+   * Set the x- and y-axis title font
+   *
+   * @param axisTitleFont
+   */
+  public AxesChartStyler setAxisTitleFont(Font axisTitleFont) {
+
+    this.axisTitleFont = axisTitleFont;
+    return this;
+  }
+
+  public boolean isXAxisTicksVisible() {
+
+    return xAxisTicksVisible;
+  }
+
+  /**
+   * Set the x-axis tick marks and labels visibility
+   *
+   * @param xAxisTicksVisible
+   */
+  public AxesChartStyler setXAxisTicksVisible(boolean xAxisTicksVisible) {
+
+    this.xAxisTicksVisible = xAxisTicksVisible;
+    return this;
+  }
+
+  public boolean isYAxisTicksVisible() {
+
+    return yAxisTicksVisible;
+  }
+
+  /**
+   * Set the y-axis tick marks and labels visibility
+   *
+   * @param yAxisTicksVisible
+   */
+  public AxesChartStyler setYAxisTicksVisible(boolean yAxisTicksVisible) {
+
+    this.yAxisTicksVisible = yAxisTicksVisible;
+    return this;
+  }
+
+  /**
+   * Set the x- and y-axis tick marks and labels visibility
+   *
+   * @param isVisible
+   */
+  public AxesChartStyler setAxisTicksVisible(boolean isVisible) {
+
+    this.xAxisTicksVisible = isVisible;
+    this.yAxisTicksVisible = isVisible;
+    return this;
+  }
+
+  public Font getAxisTickLabelsFont() {
+
+    return axisTickLabelsFont;
+  }
+
+  /**
+   * Set the x- and y-axis tick label font
+   *
+   * @param axisTicksFont
+   */
+  public AxesChartStyler setAxisTickLabelsFont(Font axisTicksFont) {
+
+    this.axisTickLabelsFont = axisTicksFont;
+    return this;
+  }
+
+  public int getAxisTickMarkLength() {
+
+    return axisTickMarkLength;
+  }
+
+  /**
+   * Set the axis tick mark length (in pixels)
+   *
+   * @param axisTickMarkLength
+   */
+  public AxesChartStyler setAxisTickMarkLength(int axisTickMarkLength) {
+
+    this.axisTickMarkLength = axisTickMarkLength;
+    return this;
+  }
+
+  public int getAxisTickPadding() {
+
+    return axisTickPadding;
+  }
+
+  /**
+   * sets the padding (in pixels) between the tick labels and the tick marks
+   *
+   * @param axisTickPadding
+   */
+  public AxesChartStyler setAxisTickPadding(int axisTickPadding) {
+
+    this.axisTickPadding = axisTickPadding;
+    return this;
+  }
+
+  public Color getAxisTickMarksColor() {
+
+    return axisTickMarksColor;
+  }
+
+  /**
+   * sets the axis tick mark color
+   *
+   * @param axisTickColor
+   */
+  public AxesChartStyler setAxisTickMarksColor(Color axisTickColor) {
+
+    this.axisTickMarksColor = axisTickColor;
+    return this;
+  }
+
+  public BasicStroke getAxisTickMarksStroke() {
+
+    return axisTickMarksStroke;
+  }
+
+  /**
+   * sets the axis tick marks Stroke
+   *
+   * @param axisTickMarksStroke
+   */
+  public AxesChartStyler setAxisTickMarksStroke(BasicStroke axisTickMarksStroke) {
+
+    this.axisTickMarksStroke = axisTickMarksStroke;
+    return this;
+  }
+
+  public Color getAxisTickLabelsColor() {
+
+    return axisTickLabelsColor;
+  }
+
+  /**
+   * sets the axis tick label color
+   *
+   * @param axisTickLabelsColor
+   */
+  public AxesChartStyler setAxisTickLabelsColor(Color axisTickLabelsColor) {
+
+    this.axisTickLabelsColor = axisTickLabelsColor;
+    return this;
+  }
+
+  public boolean isAxisTicksLineVisible() {
+
+    return isAxisTicksLineVisible;
+  }
+
+  /**
+   * sets the visibility of the line parallel to the plot edges that go along with the tick marks
+   *
+   * @param isAxisTicksLineVisible
+   */
+  public AxesChartStyler setAxisTicksLineVisible(boolean isAxisTicksLineVisible) {
+
+    this.isAxisTicksLineVisible = isAxisTicksLineVisible;
+    return this;
+  }
+
+  public boolean isAxisTicksMarksVisible() {
+
+    return isAxisTicksMarksVisible;
+  }
+
+  /**
+   * sets the visibility of the tick marks
+   *
+   * @param isAxisTicksMarksVisible
+   */
+  public AxesChartStyler setAxisTicksMarksVisible(boolean isAxisTicksMarksVisible) {
+
+    this.isAxisTicksMarksVisible = isAxisTicksMarksVisible;
+    return this;
+  }
+
+  public int getPlotMargin() {
+
+    return plotMargin;
+  }
+
+  /**
+   * sets the margin (in pixels) around the plot area
+   *
+   * @param plotMargin
+   */
+  public AxesChartStyler setPlotMargin(int plotMargin) {
+
+    this.plotMargin = plotMargin;
+    return this;
+  }
+
+  public int getAxisTitlePadding() {
+
+    return axisTitlePadding;
+  }
+
+  /**
+   * sets the padding (in pixels) between the axis title and the tick labels
+   *
+   * @param axisTitlePadding
+   */
+  public AxesChartStyler setAxisTitlePadding(int axisTitlePadding) {
+
+    this.axisTitlePadding = axisTitlePadding;
+    return this;
+  }
+
+  public int getXAxisTickMarkSpacingHint() {
+
+    return xAxisTickMarkSpacingHint;
+  }
+
+  /**
+   * set the spacing (in pixels) between tick marks for the X-Axis
+   *
+   * @param xAxisTickMarkSpacingHint
+   */
+  public AxesChartStyler setXAxisTickMarkSpacingHint(int xAxisTickMarkSpacingHint) {
+
+    this.xAxisTickMarkSpacingHint = xAxisTickMarkSpacingHint;
+    return this;
+  }
+
+  public int getYAxisTickMarkSpacingHint() {
+
+    return yAxisTickMarkSpacingHint;
+  }
+
+  /**
+   * set the spacing (in pixels) between tick marks for the Y-Axis
+   *
+   * @param yAxisTickMarkSpacingHint
+   */
+  public AxesChartStyler setYAxisTickMarkSpacingHint(int yAxisTickMarkSpacingHint) {
+
+    if (yAxisTickMarkSpacingHint < 0) {
+      throw new IllegalArgumentException("yAxisTickMarkSpacingHint cannot be less than 0 !!!");
+    }
+    this.yAxisTickMarkSpacingHint = yAxisTickMarkSpacingHint;
+    return this;
+  }
+
+  public boolean isXAxisLogarithmic() {
+
+    return isXAxisLogarithmic;
+  }
+
+  /**
+   * sets the X-Axis to be rendered with a logarithmic scale or not
+   *
+   * @param isXAxisLogarithmic
+   */
+  public AxesChartStyler setXAxisLogarithmic(boolean isXAxisLogarithmic) {
+
+    this.isXAxisLogarithmic = isXAxisLogarithmic;
+    return this;
+  }
+
+  public boolean isYAxisLogarithmic() {
+
+    return isYAxisLogarithmic;
+  }
+
+  /**
+   * sets the Y-Axis to be rendered with a logarithmic scale or not
+   *
+   * @param isYAxisLogarithmic
+   */
+  public AxesChartStyler setYAxisLogarithmic(boolean isYAxisLogarithmic) {
+
+    this.isYAxisLogarithmic = isYAxisLogarithmic;
+    return this;
+  }
+
+  public Double getXAxisMin() {
+
+    return xAxisMin;
+  }
+
+  public AxesChartStyler setXAxisMin(Double xAxisMin) {
+
+    this.xAxisMin = xAxisMin;
+    return this;
+  }
+
+  public Double getXAxisMax() {
+
+    return xAxisMax;
+  }
+
+  public AxesChartStyler setXAxisMax(Double xAxisMax) {
+
+    this.xAxisMax = xAxisMax;
+    return this;
+  }
+
+  public AxesChartStyler setYAxisMin(Integer yAxisGroup, Double yAxisMin) {
+
+    this.yAxisMinMap.put(yAxisGroup, yAxisMin);
+    return this;
+  }
+
+  public Double getYAxisMin() {
+
+    return yAxisMinMap.get(null);
+  }
+
+  public AxesChartStyler setYAxisMin(Double yAxisMin) {
+
+    this.yAxisMinMap.put(null, yAxisMin);
+    return this;
+  }
+
+  public Double getYAxisMin(Integer yAxisGroup) {
+
+    return yAxisMinMap.get(yAxisGroup);
+  }
+
+  public AxesChartStyler setYAxisMax(Integer yAxisGroup, Double yAxisMax) {
+
+    this.yAxisMaxMap.put(yAxisGroup, yAxisMax);
+    return this;
+  }
+
+  public Double getYAxisMax() {
+
+    return yAxisMaxMap.get(null);
+  }
+
+  public AxesChartStyler setYAxisMax(Double yAxisMax) {
+
+    this.yAxisMaxMap.put(null, yAxisMax);
+    return this;
+  }
+
+  public Double getYAxisMax(Integer yAxisGroup) {
+
+    return yAxisMaxMap.get(yAxisGroup);
+  }
+
+  public int getXAxisMaxLabelCount() {
+
+    return xAxisMaxLabelCount;
+  }
+
+  public AxesChartStyler setXAxisMaxLabelCount(int xAxisMaxLabelCount) {
+
+    this.xAxisMaxLabelCount = xAxisMaxLabelCount;
+    return this;
+  }
+
+  // Chart Plot Area ///////////////////////////////
+
+  public boolean isPlotGridLinesVisible() {
+
+    return isPlotGridHorizontalLinesVisible && isPlotGridVerticalLinesVisible;
+  }
+
+  /**
+   * sets the visibility of the gridlines inside the plot area
+   *
+   * @param isPlotGridLinesVisible
+   */
+  public AxesChartStyler setPlotGridLinesVisible(boolean isPlotGridLinesVisible) {
+
+    this.isPlotGridHorizontalLinesVisible = isPlotGridLinesVisible;
+    this.isPlotGridVerticalLinesVisible = isPlotGridLinesVisible;
+    return this;
+  }
+
+  public boolean isPlotGridHorizontalLinesVisible() {
+
+    return isPlotGridHorizontalLinesVisible;
+  }
+
+  /**
+   * sets the visibility of the horizontal gridlines on the plot area
+   *
+   * @param isPlotGridHorizontalLinesVisible
+   */
+  public AxesChartStyler setPlotGridHorizontalLinesVisible(
+      boolean isPlotGridHorizontalLinesVisible) {
+
+    this.isPlotGridHorizontalLinesVisible = isPlotGridHorizontalLinesVisible;
+    return this;
+  }
+
+  public boolean isPlotGridVerticalLinesVisible() {
+
+    return isPlotGridVerticalLinesVisible;
+  }
+
+  /**
+   * sets the visibility of the vertical gridlines on the plot area
+   *
+   * @param isPlotGridVerticalLinesVisible
+   */
+  public AxesChartStyler setPlotGridVerticalLinesVisible(boolean isPlotGridVerticalLinesVisible) {
+
+    this.isPlotGridVerticalLinesVisible = isPlotGridVerticalLinesVisible;
+    return this;
+  }
+
+  public boolean isPlotTicksMarksVisible() {
+
+    return isPlotTicksMarksVisible;
+  }
+
+  /**
+   * sets the visibility of the ticks marks inside the plot area
+   *
+   * @param isPlotTicksMarksVisible
+   */
+  public AxesChartStyler setPlotTicksMarksVisible(boolean isPlotTicksMarksVisible) {
+
+    this.isPlotTicksMarksVisible = isPlotTicksMarksVisible;
+    return this;
+  }
+
+  public Color getPlotGridLinesColor() {
+
+    return plotGridLinesColor;
+  }
+
+  /**
+   * set the plot area's grid lines color
+   *
+   * @param plotGridLinesColor
+   */
+  public AxesChartStyler setPlotGridLinesColor(Color plotGridLinesColor) {
+
+    this.plotGridLinesColor = plotGridLinesColor;
+    return this;
+  }
+
+  public BasicStroke getPlotGridLinesStroke() {
+
+    return plotGridLinesStroke;
+  }
+
+  /**
+   * set the plot area's grid lines Stroke
+   *
+   * @param plotGridLinesStroke
+   */
+  public AxesChartStyler setPlotGridLinesStroke(BasicStroke plotGridLinesStroke) {
+
+    this.plotGridLinesStroke = plotGridLinesStroke;
+    return this;
+  }
+
+  // Error Bars ///////////////////////////////
+
+  public Color getErrorBarsColor() {
+
+    return errorBarsColor;
+  }
+
+  /**
+   * Sets the color of the error bars
+   *
+   * @param errorBarsColor
+   */
+  public AxesChartStyler setErrorBarsColor(Color errorBarsColor) {
+
+    this.errorBarsColor = errorBarsColor;
+    return this;
+  }
+
+  public boolean isErrorBarsColorSeriesColor() {
+
+    return isErrorBarsColorSeriesColor;
+  }
+
+  /**
+   * Set true if the the error bar color should match the series color
+   *
+   * @return
+   */
+  public AxesChartStyler setErrorBarsColorSeriesColor(boolean isErrorBarsColorSeriesColor) {
+
+    this.isErrorBarsColorSeriesColor = isErrorBarsColorSeriesColor;
+    return this;
+  }
+
+  // Formatting ////////////////////////////////
+
+  public Locale getLocale() {
+
+    return locale;
+  }
+
+  /**
+   * Set the locale to use for rendering the chart
+   *
+   * @param locale - the locale to use when formatting Strings and dates for the axis tick labels
+   */
+  public AxesChartStyler setLocale(Locale locale) {
+
+    this.locale = locale;
+    return this;
+  }
+
+  public TimeZone getTimezone() {
+
+    return timezone;
+  }
+
+  /**
+   * Set the timezone to use for formatting Date axis tick labels
+   *
+   * @param timezone the timezone to use when formatting date data
+   */
+  public AxesChartStyler setTimezone(TimeZone timezone) {
+
+    this.timezone = timezone;
+    return this;
+  }
+
+  public String getDatePattern() {
+
+    return datePattern;
+  }
+
+  /**
+   * Set the String formatter for Data x-axis
+   *
+   * @param datePattern - the pattern describing the date and time format
+   */
+  public AxesChartStyler setDatePattern(String datePattern) {
+
+    this.datePattern = datePattern;
+    return this;
+  }
+
+  public String getXAxisDecimalPattern() {
+
+    return xAxisDecimalPattern;
+  }
+
+  /**
+   * Set the decimal formatting pattern for the X-Axis
+   *
+   * @param xAxisDecimalPattern
+   */
+  public AxesChartStyler setXAxisDecimalPattern(String xAxisDecimalPattern) {
+
+    this.xAxisDecimalPattern = xAxisDecimalPattern;
+    return this;
+  }
+
+  public String getYAxisDecimalPattern() {
+
+    return yAxisDecimalPattern;
+  }
+
+  /**
+   * Set the decimal formatting pattern for the Y-Axis
+   *
+   * @param yAxisDecimalPattern
+   */
+  public AxesChartStyler setYAxisDecimalPattern(String yAxisDecimalPattern) {
+
+    this.yAxisDecimalPattern = yAxisDecimalPattern;
+    return this;
+  }
+
+  public Map<Integer, String> getYAxisGroupDecimalPatternMap() {
+
+    return yAxisGroupDecimalPatternMap;
+  }
+
+  public void putYAxisGroupDecimalPatternMap(int yIndex, String yAxisDecimalPattern) {
+
+    yAxisGroupDecimalPatternMap.put(yIndex, yAxisDecimalPattern);
+  }
+
+  public boolean isXAxisLogarithmicDecadeOnly() {
+    return xAxisLogarithmicDecadeOnly;
+  }
+
+  /**
+   * Set the decade only support for logarithmic Y-Axis
+   *
+   * @param xAxisLogarithmicDecadeOnly
+   */
+  public AxesChartStyler setXAxisLogarithmicDecadeOnly(boolean xAxisLogarithmicDecadeOnly) {
+    this.xAxisLogarithmicDecadeOnly = xAxisLogarithmicDecadeOnly;
+    return this;
+  }
+
+  public boolean isYAxisLogarithmicDecadeOnly() {
+    return yAxisLogarithmicDecadeOnly;
+  }
+
+  /**
+   * Set the decade only support for logarithmic Y-Axis
+   *
+   * @param yAxisLogarithmicDecadeOnly
+   */
+  public AxesChartStyler setYAxisLogarithmicDecadeOnly(boolean yAxisLogarithmicDecadeOnly) {
+    this.yAxisLogarithmicDecadeOnly = yAxisLogarithmicDecadeOnly;
+    return this;
+  }
+
+  public Function<Double, String> getxAxisTickLabelsFormattingFunction() {
+    return xAxisTickLabelsFormattingFunction;
+  }
+
+  public AxesChartStyler setxAxisTickLabelsFormattingFunction(
+      Function<Double, String> xAxisTickLabelsFormattingFunction) {
+    this.xAxisTickLabelsFormattingFunction = xAxisTickLabelsFormattingFunction;
+    return this;
+  }
+
+  public Function<Double, String> getyAxisTickLabelsFormattingFunction() {
+    return yAxisTickLabelsFormattingFunction;
+  }
+
+  public AxesChartStyler setyAxisTickLabelsFormattingFunction(
+      Function<Double, String> yAxisTickLabelsFormattingFunction) {
+    this.yAxisTickLabelsFormattingFunction = yAxisTickLabelsFormattingFunction;
+    return this;
+  }
+
+  // TickLabels and MarksColor colors for xAxis, yAxis, yAxisGroup ////////////////////////////////
+
+  public Color getXAxisTickLabelsColor() {
+
+    if (xAxisTickLabelsColor == null) {
+      return axisTickLabelsColor;
+    }
+    return xAxisTickLabelsColor;
+  }
+
+  public AxesChartStyler setXAxisTickLabelsColor(Color xAxisTickLabelsColor) {
+
+    this.xAxisTickLabelsColor = xAxisTickLabelsColor;
+    return this;
+  }
+
+  public Color getYAxisTickLabelsColor() {
+
+    if (yAxisTickLabelsColor == null) {
+      return axisTickLabelsColor;
+    }
+    return yAxisTickLabelsColor;
+  }
+
+  public AxesChartStyler setYAxisTickLabelsColor(Color yAxisTickLabelsColor) {
+
+    this.yAxisTickLabelsColor = yAxisTickLabelsColor;
+    return this;
+  }
+
+  public Color getXAxisTickMarksColor() {
+
+    if (xAxisTickMarksColor == null) {
+      return axisTickMarksColor;
+    }
+    return xAxisTickMarksColor;
+  }
+
+  public AxesChartStyler setXAxisTickMarksColor(Color xAxisTickMarksColor) {
+
+    this.xAxisTickMarksColor = xAxisTickMarksColor;
+    return this;
+  }
+
+  public Color getYAxisTickMarksColor() {
+
+    if (yAxisTickMarksColor == null) {
+      return axisTickMarksColor;
+    }
+    return yAxisTickMarksColor;
+  }
+
+  public AxesChartStyler setYAxisTickMarksColor(Color yAxisTickMarksColor) {
+
+    this.yAxisTickMarksColor = yAxisTickMarksColor;
+    return this;
+  }
+
+  public Color getYAxisGroupTickLabelsColorMap(int yAxisGroup) {
+
+    Color color = yAxisGroupTickLabelsColorMap.get(yAxisGroup);
+    if (color == null) {
+      color = getYAxisTickLabelsColor();
+    }
+    return color;
+  }
+
+  public AxesChartStyler setYAxisGroupTickLabelsColorMap(
+      int yAxisGroup, Color yAxisTickLabelsColor) {
+
+    yAxisGroupTickLabelsColorMap.put(yAxisGroup, yAxisTickLabelsColor);
+    return this;
+  }
+
+  public Color getYAxisGroupTickMarksColorMap(int yAxisGroup) {
+
+    Color color = yAxisGroupTickMarksColorMap.get(yAxisGroup);
+    if (color == null) {
+      color = getYAxisTickMarksColor();
+    }
+    return color;
+  }
+
+  public AxesChartStyler setYAxisGroupTickMarksColorMap(int yAxisGroup, Color yAxisTickMarksColor) {
+
+    yAxisGroupTickMarksColorMap.put(yAxisGroup, yAxisTickMarksColor);
+    return this;
+  }
+
+  public TextAlignment getXAxisLabelAlignment() {
+
+    return xAxisLabelAlignment;
+  }
+
+  public AxesChartStyler setXAxisLabelAlignment(TextAlignment xAxisLabelAlignment) {
+
+    this.xAxisLabelAlignment = xAxisLabelAlignment;
+    return this;
+  }
+
+  public TextAlignment getXAxisLabelAlignmentVertical() {
+
+    return xAxisLabelAlignmentVertical;
+  }
+
+  public AxesChartStyler setXAxisLabelAlignmentVertical(TextAlignment xAxisLabelAlignmentVertical) {
+
+    this.xAxisLabelAlignmentVertical = xAxisLabelAlignmentVertical;
+    return this;
+  }
+
+  public TextAlignment getYAxisLabelAlignment() {
+
+    return yAxisLabelAlignment;
+  }
+
+  public AxesChartStyler setYAxisLabelAlignment(TextAlignment yAxisLabelAlignment) {
+
+    this.yAxisLabelAlignment = yAxisLabelAlignment;
+    return this;
+  }
+
+  public enum TextAlignment {
+    Left,
+    Centre,
+    Right
+  }
+
+  public int getXAxisLabelRotation() {
+
+    return xAxisLabelRotation;
+  }
+
+  public AxesChartStyler setXAxisLabelRotation(int xAxisLabelRotation) {
+
+    this.xAxisLabelRotation = xAxisLabelRotation;
+    return this;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/style/BoxStyler.java b/XChart/xchart/src/main/java/org/knowm/xchart/style/BoxStyler.java
new file mode 100644
index 0000000000000000000000000000000000000000..628f485faf3730d43e791228b84ad1a4cb05a38d
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/style/BoxStyler.java
@@ -0,0 +1,64 @@
+package org.knowm.xchart.style;
+
+import org.knowm.xchart.style.theme.Theme;
+
+public class BoxStyler extends AxesChartStyler {
+
+  private BoxplotCalCulationMethod boxplotCalCulationMethod;
+
+  public BoxStyler() {
+
+    this.setAllStyles();
+    super.setAllStyles();
+  }
+
+  public void setTheme(Theme theme) {
+
+    this.theme = theme;
+    super.setAllStyles();
+    boxplotCalCulationMethod = BoxplotCalCulationMethod.N_LESS_1_PLUS_1;
+  }
+
+  public BoxplotCalCulationMethod getBoxplotCalCulationMethod() {
+
+    return boxplotCalCulationMethod;
+  }
+
+  public BoxStyler setBoxplotCalCulationMethod(BoxplotCalCulationMethod boxplotCalCulationMethod) {
+
+    this.boxplotCalCulationMethod = boxplotCalCulationMethod;
+    return this;
+  }
+
+  /** Box plot calculation method, method for determining the position of the quartile */
+  public enum BoxplotCalCulationMethod {
+
+    /**
+     * Determine the position of the quartile, where Qi is = i (n + 1) / 4, where i = 1, 2, and 3. n
+     * represents the number of items contained in the sequence. Calculate the corresponding
+     * quartile based on location
+     */
+    N_PLUS_1,
+
+    /**
+     * Determine the position of the quartile, where Qi is = i (n-1) / 4, where i = 1, 2, and 3. n
+     * represents the number of items contained in the sequence. Calculate the corresponding
+     * quartile based on location
+     */
+    N_LESS_1,
+
+    /**
+     * Determine the position of the quartile, where Qi is np = (i * n) / 4, where i = 1, 2, 3 n
+     * represents the number of items contained in the sequence. If np is not an integer, Qi = X [np
+     * + 1] If np is an integer, Qi = (X [np] + X [np + 1]) / 2
+     */
+    NP,
+
+    /**
+     * Determine the position of the quartile, where Qi is = i (n-1) / 4 + 1, where i = 1, 2, 3 n
+     * represents the number of items contained in the sequence. Calculate the corresponding
+     * quartile based on location
+     */
+    N_LESS_1_PLUS_1;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/style/BubbleStyler.java b/XChart/xchart/src/main/java/org/knowm/xchart/style/BubbleStyler.java
new file mode 100644
index 0000000000000000000000000000000000000000..7926485d0f159b774ef3fd732518b05b38a1bd97
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/style/BubbleStyler.java
@@ -0,0 +1,51 @@
+package org.knowm.xchart.style;
+
+import org.knowm.xchart.BubbleSeries.BubbleSeriesRenderStyle;
+import org.knowm.xchart.style.theme.Theme;
+
+public class BubbleStyler extends AxesChartStyler {
+
+  private BubbleSeriesRenderStyle bubbleChartSeriesRenderStyle;
+
+  /** Constructor */
+  public BubbleStyler() {
+
+    setAllStyles();
+  }
+
+  @Override
+  protected void setAllStyles() {
+
+    super.setAllStyles();
+    bubbleChartSeriesRenderStyle = BubbleSeriesRenderStyle.Round; // set default to Round
+  }
+
+  public BubbleSeriesRenderStyle getDefaultSeriesRenderStyle() {
+
+    return bubbleChartSeriesRenderStyle;
+  }
+
+  /**
+   * Sets the default series render style for the chart (Round is the only one for now) You can
+   * override the series render style individually on each Series object.
+   *
+   * @param bubbleChartSeriesRenderStyle
+   */
+  public BubbleStyler setDefaultSeriesRenderStyle(
+      BubbleSeriesRenderStyle bubbleChartSeriesRenderStyle) {
+
+    this.bubbleChartSeriesRenderStyle = bubbleChartSeriesRenderStyle;
+    return this;
+  }
+
+  /**
+   * Set the theme the styler should use
+   *
+   * @param theme
+   */
+  public void setTheme(Theme theme) {
+
+    this.theme = theme;
+    setAllStyles();
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/style/CategoryStyler.java b/XChart/xchart/src/main/java/org/knowm/xchart/style/CategoryStyler.java
new file mode 100644
index 0000000000000000000000000000000000000000..6118b195b5609d4a7a235e59838193b93549a9d3
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/style/CategoryStyler.java
@@ -0,0 +1,267 @@
+package org.knowm.xchart.style;
+
+import java.awt.Color;
+import java.awt.Font;
+import org.knowm.xchart.CategorySeries.CategorySeriesRenderStyle;
+import org.knowm.xchart.style.colors.FontColorDetector;
+import org.knowm.xchart.style.theme.Theme;
+
+public class CategoryStyler extends AxesChartStyler {
+
+  private CategorySeriesRenderStyle chartCategorySeriesRenderStyle;
+
+  private double availableSpaceFill;
+  private boolean isOverlapped;
+  private boolean isStacked;
+
+  // labels //////////////////////
+  private boolean isLabelsVisible = false;
+  private boolean showStackSum = false;
+  private Font labelsFont;
+  private Color labelsFontColor;
+  private int labelsRotation;
+  private double labelsPosition;
+  private boolean isLabelsFontColorAutomaticEnabled;
+  private Color labelsFontColorAutomaticLight;
+  private Color labelsFontColorAutomaticDark;
+
+  /** Constructor */
+  public CategoryStyler() {
+
+    setAllStyles();
+  }
+
+  @Override
+  protected void setAllStyles() {
+
+    super.setAllStyles();
+    this.chartCategorySeriesRenderStyle = CategorySeriesRenderStyle.Bar; // set default to bar
+
+    availableSpaceFill = theme.getAvailableSpaceFill();
+    isOverlapped = theme.isOverlapped();
+    isStacked = false;
+    isLabelsVisible = false;
+    labelsFont = theme.getBaseFont();
+    labelsFontColor = theme.getChartFontColor();
+    labelsRotation = 0;
+    labelsPosition = 0.5;
+    isLabelsFontColorAutomaticEnabled = theme.isLabelsFontColorAutomaticEnabled();
+    labelsFontColorAutomaticLight = theme.getLabelsFontColorAutomaticLight();
+    labelsFontColorAutomaticDark = theme.getLabelsFontColorAutomaticDark();
+  }
+
+  public CategorySeriesRenderStyle getDefaultSeriesRenderStyle() {
+
+    return chartCategorySeriesRenderStyle;
+  }
+
+  /**
+   * Sets the default series render style for the chart (bar, stick, line, scatter, area, etc.) You
+   * can override the series render style individually on each Series object.
+   *
+   * @param chartCategorySeriesRenderStyle
+   */
+  public CategoryStyler setDefaultSeriesRenderStyle(
+      CategorySeriesRenderStyle chartCategorySeriesRenderStyle) {
+
+    this.chartCategorySeriesRenderStyle = chartCategorySeriesRenderStyle;
+    return this;
+  }
+
+  public double getAvailableSpaceFill() {
+
+    return availableSpaceFill;
+  }
+
+  /**
+   * Sets the available space for rendering each category as a percentage. For a bar chart with one
+   * series, it will be the width of the bar as a percentage of the maximum space alloted for the
+   * bar. If there are three series and three bars, the three bars will share the available space.
+   * This affects all category series render types, not only bar charts. Full width is 100%, i.e.
+   * 1.0
+   *
+   * @param availableSpaceFill
+   */
+  public CategoryStyler setAvailableSpaceFill(double availableSpaceFill) {
+
+    this.availableSpaceFill = availableSpaceFill;
+    return this;
+  }
+
+  public boolean isOverlapped() {
+
+    return isOverlapped;
+  }
+
+  /**
+   * set whether or not series renderings (i.e. bars, stick, etc.) are overlapped. Otherwise they
+   * are placed side-by-side.
+   *
+   * @param isOverlapped
+   */
+  public CategoryStyler setOverlapped(boolean isOverlapped) {
+
+    this.isOverlapped = isOverlapped;
+    return this;
+  }
+
+  public boolean isStacked() {
+
+    return isStacked;
+  }
+
+  /**
+   * Set whether or not series renderings (i.e. bars, stick, etc.) are stacked.
+   *
+   * @param isStacked
+   */
+  public CategoryStyler setStacked(boolean isStacked) {
+
+    this.isStacked = isStacked;
+    return this;
+  }
+
+  public boolean isLabelsVisible() {
+
+    return isLabelsVisible;
+  }
+
+  /**
+   * Sets if labels should be added to charts. Each chart type has a different annotation type
+   *
+   * @param labelsVisible
+   */
+  public CategoryStyler setLabelsVisible(boolean labelsVisible) {
+
+    this.isLabelsVisible = labelsVisible;
+    return this;
+  }
+
+  public boolean isShowStackSum() {
+
+    return showStackSum;
+  }
+
+  /**
+   * If the category chart is set to be "stacked", the total value of the stack can be painted above
+   * the stack.
+   *
+   * @param showStackSum
+   * @return
+   */
+  public CategoryStyler setShowStackSum(boolean showStackSum) {
+
+    this.showStackSum = showStackSum;
+    return this;
+  }
+
+  public Font getLabelsFont() {
+
+    return labelsFont;
+  }
+
+  /**
+   * Sets the Font used for chart labels
+   *
+   * @param labelsFont
+   */
+  public CategoryStyler setLabelsFont(Font labelsFont) {
+
+    this.labelsFont = labelsFont;
+    return this;
+  }
+
+  public Color getLabelsFontColor() {
+    return labelsFontColor;
+  }
+
+  public Color getLabelsFontColor(Color backgroundColor) {
+
+    return FontColorDetector.getAutomaticFontColor(
+        backgroundColor, labelsFontColorAutomaticDark, labelsFontColorAutomaticLight);
+  }
+
+  /**
+   * Sets the color of the Font used for chart labels
+   *
+   * @param labelsFontColor
+   */
+  public CategoryStyler setLabelsFontColor(Color labelsFontColor) {
+    this.labelsFontColor = labelsFontColor;
+    return this;
+  }
+
+  public int getLabelsRotation() {
+    return labelsRotation;
+  }
+
+  /**
+   * Sets the rotation (in degrees) for chart labels.
+   *
+   * @param labelsRotation
+   */
+  public CategoryStyler setLabelsRotation(int labelsRotation) {
+    this.labelsRotation = labelsRotation;
+    return this;
+  }
+
+  public double getLabelsPosition() {
+
+    return labelsPosition;
+  }
+
+  /**
+   * A number between 0 and 1 setting the vertical position of the data label. Default is 0.5
+   * placing it in the center.
+   *
+   * @param labelsPosition
+   * @return
+   */
+  public CategoryStyler setLabelsPosition(double labelsPosition) {
+
+    if (labelsPosition < 0 || labelsPosition > 1) {
+      throw new IllegalArgumentException("Annotations position must between 0 and 1!!!");
+    }
+    this.labelsPosition = labelsPosition;
+    return this;
+  }
+
+  public boolean isLabelsFontColorAutomaticEnabled() {
+    return isLabelsFontColorAutomaticEnabled;
+  }
+
+  public CategoryStyler setLabelsFontColorAutomaticEnabled(
+      boolean isLabelsFontColorAutomaticEnabled) {
+    this.isLabelsFontColorAutomaticEnabled = isLabelsFontColorAutomaticEnabled;
+    return this;
+  }
+
+  public Color getLabelsFontColorAutomaticLight() {
+    return labelsFontColorAutomaticLight;
+  }
+
+  public CategoryStyler setLabelsFontColorAutomaticLight(Color labelsFontColorAutomaticLight) {
+    this.labelsFontColorAutomaticLight = labelsFontColorAutomaticLight;
+    return this;
+  }
+
+  public Color getLabelsFontColorAutomaticDark() {
+    return labelsFontColorAutomaticDark;
+  }
+
+  public CategoryStyler setLabelsFontColorAutomaticDark(Color labelsFontColorAutomaticDark) {
+    this.labelsFontColorAutomaticDark = labelsFontColorAutomaticDark;
+    return this;
+  }
+
+  /**
+   * Set the theme the styler should use
+   *
+   * @param theme
+   */
+  public void setTheme(Theme theme) {
+
+    this.theme = theme;
+    setAllStyles();
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/style/DialStyler.java b/XChart/xchart/src/main/java/org/knowm/xchart/style/DialStyler.java
new file mode 100644
index 0000000000000000000000000000000000000000..1d2f0e53be9373f622bfb7ba07395531818270cb
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/style/DialStyler.java
@@ -0,0 +1,409 @@
+package org.knowm.xchart.style;
+
+import static org.knowm.xchart.style.colors.ChartColor.BLUE;
+import static org.knowm.xchart.style.colors.ChartColor.GREEN;
+import static org.knowm.xchart.style.colors.ChartColor.RED;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Font;
+import org.knowm.xchart.style.theme.Theme;
+
+public class DialStyler extends Styler {
+
+  private boolean isCircular;
+
+  // helper tick lines
+  private boolean axisTicksMarksVisible;
+  private Color axisTickMarksColor;
+  private BasicStroke axisTickMarksStroke;
+  private double[] axisTickValues = {0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1};
+  private String[] axisTickLabels = {
+    "0", "10", "20", "30", "40", "50", "60", "70", "80", "90", "100"
+  };
+
+  // variable labels
+  private boolean axisTitleVisible;
+  private Font axisTitleFont;
+  private int axisTitlePadding;
+  private boolean axisTickLabelsVisible = true;
+
+  // donut area
+  private double arcAngle = 270;
+  private double donutThickness = .17;
+
+  private double lowerFrom = 0;
+  private double lowerTo = 0.2;
+  private Color lowerColor = GREEN.getColor();
+
+  private double middleFrom = 0.2;
+  private double middleTo = .8;
+  private Color middleColor = Color.LIGHT_GRAY;
+
+  private double upperFrom = 0.8;
+  private double upperTo = 1;
+  private Color upperColor = RED.getColor();
+
+  //
+  private double arrowLengthPercentage = 0.7;
+  private double arrowArcAngle = 20;
+  private double arrowArcPercentage = 0.15;
+  private Color arrowColor = BLUE.getColor();
+
+  private boolean isLabelsVisible;
+  private Font labelsFont;
+
+  public DialStyler() {
+
+    setAllStyles();
+  }
+
+  @Override
+  void setAllStyles() {
+
+    super.setAllStyles();
+
+    this.isCircular = theme.isCircular();
+
+    this.axisTickMarksColor = theme.getAxisTickMarksColor();
+    this.axisTickMarksStroke = theme.getAxisTickMarksStroke();
+    this.axisTicksMarksVisible = theme.isAxisTicksMarksVisible();
+
+    this.axisTitleVisible = theme.isXAxisTitleVisible() || theme.isYAxisTitleVisible();
+    this.axisTitleFont = theme.getAxisTitleFont();
+    this.axisTitlePadding = theme.getAxisTitlePadding();
+
+    this.isLabelsVisible = true;
+    labelsFont = theme.getBaseFont();
+  }
+
+  /**
+   * Set the theme the styler should use
+   *
+   * @param theme
+   */
+  public DialStyler setTheme(Theme theme) {
+
+    this.theme = theme;
+    setAllStyles();
+    return this;
+  }
+
+  public boolean isCircular() {
+
+    return isCircular;
+  }
+
+  /**
+   * Sets whether or not the radar chart is forced to be circular. Otherwise it's shape is oval,
+   * matching the containing plot.
+   *
+   * @param isCircular
+   */
+  public DialStyler setCircular(boolean isCircular) {
+
+    this.isCircular = isCircular;
+    return this;
+  }
+
+  public boolean isAxisTicksMarksVisible() {
+
+    return axisTicksMarksVisible;
+  }
+
+  public DialStyler setAxisTicksMarksVisible(boolean axisTicksMarksVisible) {
+
+    this.axisTicksMarksVisible = axisTicksMarksVisible;
+    return this;
+  }
+
+  public Color getAxisTickMarksColor() {
+
+    return axisTickMarksColor;
+  }
+
+  public DialStyler setAxisTickMarksColor(Color axisTickMarksColor) {
+
+    this.axisTickMarksColor = axisTickMarksColor;
+    return this;
+  }
+
+  public BasicStroke getAxisTickMarksStroke() {
+
+    return axisTickMarksStroke;
+  }
+
+  public DialStyler setAxisTickMarksStroke(BasicStroke axisTickMarksStroke) {
+
+    this.axisTickMarksStroke = axisTickMarksStroke;
+    return this;
+  }
+
+  public boolean isAxisTitleVisible() {
+
+    return axisTitleVisible;
+  }
+
+  public DialStyler setAxisTitleVisible(boolean axisTitleVisible) {
+
+    this.axisTitleVisible = axisTitleVisible;
+    return this;
+  }
+
+  public Font getAxisTitleFont() {
+
+    return axisTitleFont;
+  }
+
+  public DialStyler setAxisTitleFont(Font axisTitleFont) {
+
+    this.axisTitleFont = axisTitleFont;
+    return this;
+  }
+
+  public int getAxisTitlePadding() {
+
+    return axisTitlePadding;
+  }
+
+  public DialStyler setAxisTitlePadding(int axisTitlePadding) {
+
+    this.axisTitlePadding = axisTitlePadding;
+    return this;
+  }
+
+  public double[] getAxisTickValues() {
+
+    return axisTickValues;
+  }
+
+  public DialStyler setAxisTickValues(double[] axisTickValues) {
+
+    this.axisTickValues = axisTickValues;
+    return this;
+  }
+
+  public String[] getAxisTickLabels() {
+
+    return axisTickLabels;
+  }
+
+  public DialStyler setAxisTickLabels(String[] axisTickLabels) {
+
+    this.axisTickLabels = axisTickLabels;
+    return this;
+  }
+
+  public double getMiddleFrom() {
+
+    return middleFrom;
+  }
+
+  public DialStyler setMiddleFrom(double middleFrom) {
+
+    this.middleFrom = middleFrom;
+    return this;
+  }
+
+  public double getMiddleTo() {
+
+    return middleTo;
+  }
+
+  public DialStyler setMiddleTo(double middleTo) {
+
+    this.middleTo = middleTo;
+    return this;
+  }
+
+  public Color getMiddleColor() {
+
+    return middleColor;
+  }
+
+  public DialStyler setMiddleColor(Color middleColor) {
+
+    this.middleColor = middleColor;
+    return this;
+  }
+
+  public double getLowerFrom() {
+
+    return lowerFrom;
+  }
+
+  public DialStyler setLowerFrom(double lowerFrom) {
+
+    this.lowerFrom = lowerFrom;
+    return this;
+  }
+
+  public double getLowerTo() {
+
+    return lowerTo;
+  }
+
+  public DialStyler setLowerTo(double lowerTo) {
+
+    this.lowerTo = lowerTo;
+    return this;
+  }
+
+  public Color getLowerColor() {
+
+    return lowerColor;
+  }
+
+  public DialStyler setLowerColor(Color lowerColor) {
+
+    this.lowerColor = lowerColor;
+    return this;
+  }
+
+  public double getUpperFrom() {
+
+    return upperFrom;
+  }
+
+  public DialStyler setUpperFrom(double upperFrom) {
+
+    this.upperFrom = upperFrom;
+    return this;
+  }
+
+  public double getUpperTo() {
+
+    return upperTo;
+  }
+
+  public DialStyler setUpperTo(double upperTo) {
+
+    this.upperTo = upperTo;
+    return this;
+  }
+
+  public Color getUpperColor() {
+
+    return upperColor;
+  }
+
+  public DialStyler setUpperColor(Color upperColor) {
+
+    this.upperColor = upperColor;
+    return this;
+  }
+
+  public double getArcAngle() {
+
+    return arcAngle;
+  }
+
+  public DialStyler setArcAngle(double arcAngle) {
+
+    this.arcAngle = arcAngle;
+    return this;
+  }
+
+  public boolean isAxisTickLabelsVisible() {
+
+    return axisTickLabelsVisible;
+  }
+
+  public DialStyler setAxisTickLabelsVisible(boolean axisTickLabelsVisible) {
+
+    this.axisTickLabelsVisible = axisTickLabelsVisible;
+    return this;
+  }
+
+  public double getDonutThickness() {
+
+    return donutThickness;
+  }
+
+  public DialStyler setDonutThickness(double donutThickness) {
+
+    this.donutThickness = donutThickness;
+    return this;
+  }
+
+  public double getArrowLengthPercentage() {
+
+    return arrowLengthPercentage;
+  }
+
+  public DialStyler setArrowLengthPercentage(double arrowLengthPercentage) {
+
+    this.arrowLengthPercentage = arrowLengthPercentage;
+    return this;
+  }
+
+  public double getArrowArcAngle() {
+
+    return arrowArcAngle;
+  }
+
+  public DialStyler setArrowArcAngle(double arrowArcAngle) {
+
+    this.arrowArcAngle = arrowArcAngle;
+    return this;
+  }
+
+  public double getArrowArcPercentage() {
+
+    return arrowArcPercentage;
+  }
+
+  public DialStyler setArrowArcPercentage(double arrowArcPercentage) {
+
+    this.arrowArcPercentage = arrowArcPercentage;
+    return this;
+  }
+
+  public Color getArrowColor() {
+
+    return arrowColor;
+  }
+
+  /**
+   * Set the line color of the series
+   *
+   * @param color
+   */
+  public DialStyler setArrowColor(java.awt.Color color) {
+
+    this.arrowColor = color;
+    return this;
+  }
+
+  public boolean isLabelsVisible() {
+
+    return isLabelsVisible;
+  }
+
+  /**
+   * Sets if labels should be added to charts.
+   *
+   * @param labelsVisible
+   */
+  public DialStyler setLabelVisible(boolean labelsVisible) {
+
+    this.isLabelsVisible = labelsVisible;
+    return this;
+  }
+
+  public Font getLabelsFont() {
+
+    return labelsFont;
+  }
+
+  /**
+   * Sets the Font used for chart labels
+   *
+   * @param labelsFont
+   */
+  public DialStyler setLabelFont(Font labelsFont) {
+
+    this.labelsFont = labelsFont;
+    return this;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/style/HeatMapStyler.java b/XChart/xchart/src/main/java/org/knowm/xchart/style/HeatMapStyler.java
new file mode 100644
index 0000000000000000000000000000000000000000..87b9bcecc311c8a3a49d2865f9ab6cf6bcded68a
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/style/HeatMapStyler.java
@@ -0,0 +1,254 @@
+package org.knowm.xchart.style;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.util.function.Function;
+import org.knowm.xchart.style.colors.ChartColor;
+import org.knowm.xchart.style.theme.Theme;
+
+public class HeatMapStyler extends AxesChartStyler {
+
+  private boolean isPiecewise;
+
+  private boolean isPiecewiseRanged = true;
+
+  private int splitNumber;
+
+  /** default range colors, {'#00FFFF'} */
+  private static final Color[] DEFAULT_RANGE_COLORS = {
+    new Color(255, 255, 255), new Color(0, 255, 255)
+  };
+
+  private Color[] rangeColors;
+
+  private boolean isDrawBorder;
+
+  private boolean showValue;
+
+  private Font valueFont;
+
+  private Color valueFontColor;
+
+  // heatData value min
+  double min;
+
+  // heatData value max
+  double max;
+
+  private int gradientColorColumnWeight;
+
+  private int gradientColorColumnHeight;
+
+  private String heatMapValueDecimalPattern;
+
+  private Function<Double, String> heatMapDecimalValueFormatter;
+
+  /**
+   * Set the theme the styler should use
+   *
+   * @param theme
+   */
+  public void setTheme(Theme theme) {
+
+    this.theme = theme;
+    setAllStyles();
+  }
+
+  @Override
+  public void setAllStyles() {
+
+    super.setAllStyles();
+
+    rangeColors = new Color[3];
+    rangeColors[0] = new Color(255, 165, 0); // #FF5A00
+    rangeColors[1] = new Color(255, 69, 0); // #FF4500
+    rangeColors[2] = new Color(139, 0, 0); // #8B0000
+
+    splitNumber = 5;
+    valueFont = new Font(Font.SANS_SERIF, Font.PLAIN, 16);
+    valueFontColor = ChartColor.BLACK.getColor();
+    min = Double.MIN_VALUE;
+    max = Double.MAX_VALUE;
+    gradientColorColumnWeight = 30;
+    gradientColorColumnHeight = 200;
+  }
+
+  @Override
+  public HeatMapStyler setLegendPosition(LegendPosition legendPosition) {
+
+    if (!LegendPosition.OutsideE.equals(legendPosition)
+        && !LegendPosition.OutsideS.equals(legendPosition)) {
+      throw new IllegalArgumentException(
+          "HeatMapStyler LegendPosition must be OutsideE or OutsideS!!!");
+    }
+    super.setLegendPosition(legendPosition);
+    return this;
+  }
+
+  public boolean isPiecewise() {
+
+    return isPiecewise;
+  }
+
+  public HeatMapStyler setPiecewise(boolean isPiecewise) {
+
+    this.isPiecewise = isPiecewise;
+    return this;
+  }
+
+  public int getSplitNumber() {
+
+    return splitNumber;
+  }
+
+  public HeatMapStyler setSplitNumber(int splitNumber) {
+
+    if (splitNumber > 0) {
+      this.splitNumber = splitNumber;
+    } else {
+      this.splitNumber = 1;
+    }
+    return this;
+  }
+
+  public Color[] getRangeColors() {
+
+    return rangeColors;
+  }
+
+  public HeatMapStyler setRangeColors(Color[] rangeColors) {
+
+    if (rangeColors != null && rangeColors.length > 0) {
+      if (rangeColors.length == 1) {
+        this.rangeColors = new Color[2];
+        this.rangeColors[0] = rangeColors[0];
+        this.rangeColors[1] = rangeColors[0];
+      }
+      this.rangeColors = rangeColors;
+    } else {
+      this.rangeColors = DEFAULT_RANGE_COLORS;
+    }
+    return this;
+  }
+
+  public boolean isDrawBorder() {
+
+    return isDrawBorder;
+  }
+
+  public HeatMapStyler setDrawBorder(boolean isDrawBorder) {
+
+    this.isDrawBorder = isDrawBorder;
+    return this;
+  }
+
+  public boolean isShowValue() {
+
+    return showValue;
+  }
+
+  public HeatMapStyler setShowValue(boolean showValue) {
+
+    this.showValue = showValue;
+    return this;
+  }
+
+  public Font getValueFont() {
+
+    return valueFont;
+  }
+
+  public HeatMapStyler setValueFont(Font valueFont) {
+
+    this.valueFont = valueFont;
+    return this;
+  }
+
+  public Color getValueFontColor() {
+
+    return valueFontColor;
+  }
+
+  public HeatMapStyler setValueFontColor(Color valueFontColor) {
+
+    this.valueFontColor = valueFontColor;
+    return this;
+  }
+
+  public double getMin() {
+
+    return min;
+  }
+
+  public HeatMapStyler setMin(double min) {
+
+    this.min = min;
+    return this;
+  }
+
+  public double getMax() {
+
+    return max;
+  }
+
+  public HeatMapStyler setMax(double max) {
+
+    this.max = max;
+    return this;
+  }
+
+  public int getGradientColorColumnWeight() {
+
+    return gradientColorColumnWeight;
+  }
+
+  public HeatMapStyler setGradientColorColumnWeight(int gradientColorColumnWeight) {
+
+    this.gradientColorColumnWeight = Math.max(gradientColorColumnWeight, 10);
+    return this;
+  }
+
+  public int getGradientColorColumnHeight() {
+
+    return gradientColorColumnHeight;
+  }
+
+  public HeatMapStyler setGradientColorColumnHeight(int gradientColorColumnHeight) {
+
+    this.gradientColorColumnHeight = Math.max(gradientColorColumnHeight, 100);
+    return this;
+  }
+
+  public String getHeatMapValueDecimalPattern() {
+
+    return heatMapValueDecimalPattern;
+  }
+
+  public HeatMapStyler setHeatMapValueDecimalPattern(String heatMapValueDecimalPattern) {
+
+    this.heatMapValueDecimalPattern = heatMapValueDecimalPattern;
+    return this;
+  }
+
+  public Function<Double, String> getHeatMapDecimalValueFormatter() {
+    return heatMapDecimalValueFormatter;
+  }
+
+  public HeatMapStyler setHeatMapDecimalValueFormatter(
+      Function<Double, String> heatMapDecimalValueFormatter) {
+    this.heatMapDecimalValueFormatter = heatMapDecimalValueFormatter;
+    return this;
+  }
+
+  public boolean isPiecewiseRanged() {
+    return isPiecewiseRanged;
+  }
+
+  public HeatMapStyler setPiecewiseRanged(boolean piecewiseRanged) {
+    if (piecewiseRanged) {
+      setPiecewise(true);
+    }
+    isPiecewiseRanged = piecewiseRanged;
+    return this;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/style/OHLCStyler.java b/XChart/xchart/src/main/java/org/knowm/xchart/style/OHLCStyler.java
new file mode 100644
index 0000000000000000000000000000000000000000..b02e55510d39b867ffdf2aa3fbda573dd5d79619
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/style/OHLCStyler.java
@@ -0,0 +1,51 @@
+package org.knowm.xchart.style;
+
+import org.knowm.xchart.OHLCSeries;
+import org.knowm.xchart.OHLCSeries.OHLCSeriesRenderStyle;
+import org.knowm.xchart.style.theme.Theme;
+
+public class OHLCStyler extends AxesChartStyler {
+
+  private OHLCSeriesRenderStyle ohlcSeriesRenderStyle;
+
+  /** Constructor */
+  public OHLCStyler() {
+
+    setAllStyles();
+  }
+
+  @Override
+  protected void setAllStyles() {
+
+    super.setAllStyles();
+    ohlcSeriesRenderStyle = OHLCSeriesRenderStyle.Candle; // set default to candle
+  }
+
+  public OHLCSeries.OHLCSeriesRenderStyle getDefaultSeriesRenderStyle() {
+
+    return ohlcSeriesRenderStyle;
+  }
+
+  /**
+   * Sets the default series render style for the chart (candle, hilo, etc.) You can override the
+   * series render style individually on each Series object.
+   *
+   * @param ohlcSeriesRenderStyle
+   */
+  public OHLCStyler setDefaultSeriesRenderStyle(OHLCSeriesRenderStyle ohlcSeriesRenderStyle) {
+
+    this.ohlcSeriesRenderStyle = ohlcSeriesRenderStyle;
+    return this;
+  }
+
+  /**
+   * Set the theme the styler should use
+   *
+   * @param theme
+   */
+  public void setTheme(Theme theme) {
+
+    this.theme = theme;
+    setAllStyles();
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/style/PieStyler.java b/XChart/xchart/src/main/java/org/knowm/xchart/style/PieStyler.java
new file mode 100644
index 0000000000000000000000000000000000000000..00919166fe391db42df2b2a4b190c2425c6f01ec
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/style/PieStyler.java
@@ -0,0 +1,373 @@
+package org.knowm.xchart.style;
+
+import java.awt.Color;
+import java.awt.Font;
+import org.knowm.xchart.PieSeries.PieSeriesRenderStyle;
+import org.knowm.xchart.style.colors.FontColorDetector;
+import org.knowm.xchart.style.theme.Theme;
+
+public class PieStyler extends Styler {
+
+  private PieSeriesRenderStyle chartPieSeriesRenderStyle;
+  private boolean isCircular;
+  private double startAngleInDegrees;
+
+  private double donutThickness;
+  private boolean isSumVisible;
+  private Font sumFont;
+  private String sumFormat;
+  private ClockwiseDirectionType clockwiseDirectionType = ClockwiseDirectionType.COUNTER_CLOCKWISE;
+  private float sliceBorderWidth = 0;
+
+  // labels //////////////////////
+  private boolean isLabelsVisible;
+  private Font labelsFont;
+  private Color labelsFontColor;
+  private double labelsDistance;
+  private LabelType labelType;
+  private boolean isForceAllLabelsVisible;
+  private boolean isLabelsFontColorAutomaticEnabled;
+  private Color labelsFontColorAutomaticLight;
+  private Color labelsFontColorAutomaticDark;
+
+  public PieStyler() {
+
+    setAllStyles();
+  }
+
+  @Override
+  void setAllStyles() {
+
+    super.setAllStyles();
+
+    this.chartPieSeriesRenderStyle = PieSeriesRenderStyle.Pie;
+    this.isCircular = theme.isCircular();
+    this.startAngleInDegrees = theme.getStartAngleInDegrees();
+
+    this.donutThickness = theme.getDonutThickness();
+
+    this.isSumVisible = theme.isSumVisible();
+    this.sumFont = theme.getSumFont();
+
+    this.isLabelsVisible = true; // default to true
+    this.labelsFont = theme.getBaseFont();
+    this.labelsFontColor = theme.getChartFontColor();
+    this.labelsDistance = theme.getLabelsDistance();
+    this.labelType = theme.getLabelType();
+    this.isForceAllLabelsVisible = theme.setForceAllLabelsVisible();
+    isLabelsFontColorAutomaticEnabled = theme.isLabelsFontColorAutomaticEnabled();
+    labelsFontColorAutomaticLight = theme.getLabelsFontColorAutomaticLight();
+    labelsFontColorAutomaticDark = theme.getLabelsFontColorAutomaticDark();
+  }
+
+  public PieSeriesRenderStyle getDefaultSeriesRenderStyle() {
+
+    return chartPieSeriesRenderStyle;
+  }
+
+  /**
+   * Sets the default series render style for the chart (line, scatter, area, etc.) You can override
+   * the series render style individually on each Series object.
+   *
+   * @param chartPieSeriesRenderStyle
+   */
+  public PieStyler setDefaultSeriesRenderStyle(PieSeriesRenderStyle chartPieSeriesRenderStyle) {
+
+    this.chartPieSeriesRenderStyle = chartPieSeriesRenderStyle;
+    return this;
+  }
+
+  public boolean isCircular() {
+
+    return isCircular;
+  }
+
+  /**
+   * Sets whether or not the pie chart is forced to be circular. Otherwise it's shape is oval,
+   * matching the containing plot.
+   *
+   * @param isCircular
+   */
+  public PieStyler setCircular(boolean isCircular) {
+
+    this.isCircular = isCircular;
+    return this;
+  }
+
+  public double getStartAngleInDegrees() {
+
+    return startAngleInDegrees;
+  }
+
+  /**
+   * Sets the start angle in degrees. Zero degrees is straight up.
+   *
+   * @param startAngleInDegrees
+   */
+  public PieStyler setStartAngleInDegrees(double startAngleInDegrees) {
+
+    this.startAngleInDegrees = startAngleInDegrees;
+    return this;
+  }
+
+  public double getLabelsDistance() {
+
+    return labelsDistance;
+  }
+
+  /**
+   * Sets the distance of the pie chart's annotation where 0 is the center, 1 is at the edge and
+   * greater than 1 is outside of the pie chart.
+   *
+   * @param labelsDistance
+   */
+  public PieStyler setLabelsDistance(double labelsDistance) {
+
+    this.labelsDistance = labelsDistance;
+    return this;
+  }
+
+  public LabelType getLabelType() {
+
+    return labelType;
+  }
+
+  /**
+   * Sets the Pie chart's annotation type
+   *
+   * @param labelType
+   */
+  public PieStyler setLabelType(LabelType labelType) {
+
+    this.labelType = labelType;
+    return this;
+  }
+
+  public boolean isForceAllLabelsVisible() {
+
+    return isForceAllLabelsVisible;
+  }
+
+  /**
+   * By default, only the labels that will "fit", as determined algorithmically, will be drawn.
+   * Otherwise, you can end up with annotations drawn overlapping. If `drawAllAnnotations` is set
+   * true with this method, it will override the algorithmic determination, and always draw all the
+   * annotations, one for each slice. You can also try playing around with the method
+   * `setStartAngleInDegrees` so the the slices are orientated in a more optimal way. You can also
+   * try changing the font size. Also, you can order the slices so that a small slice is followed by
+   * a larger slice, while setting this method with `true`.
+   *
+   * @param forceAllLabelsVisible
+   */
+  public PieStyler setForceAllLabelsVisible(boolean forceAllLabelsVisible) {
+
+    this.isForceAllLabelsVisible = forceAllLabelsVisible;
+    return this;
+  }
+
+  public double getDonutThickness() {
+
+    return donutThickness;
+  }
+
+  /**
+   * Sets the thickness of the donut ring for donut style pie chart series.
+   *
+   * @param donutThickness - Valid range is between 0 and 1.
+   */
+  public PieStyler setDonutThickness(double donutThickness) {
+
+    this.donutThickness = donutThickness;
+    return this;
+  }
+
+  public boolean isSumVisible() {
+
+    return isSumVisible;
+  }
+
+  /**
+   * Set the Format to be applied to the sum, the default is just to display the sum as a number
+   * using the PieStyler DecimalFormat. This allows a separate Formatter @see
+   * java.util.Formatter#format()
+   *
+   * @param sumFormat Format to use for the sum display, the Double sum value will be passed to this
+   *     to generate the overall sum string.
+   * @return PieStyler so that modifiers can be chained.
+   */
+  public PieStyler setSumFormat(String sumFormat) {
+    this.sumFormat = sumFormat;
+    return this;
+  }
+
+  /**
+   * Access the current sumFormat value, a value of "" or null implies use the original sum
+   * formatted using the PieStyler DecimalFormat.
+   *
+   * @return Formatter string to be used when displaying the sum value or <code>null</code>
+   */
+  public String getSumFormat() {
+    return sumFormat;
+  }
+
+  /**
+   * Sets whether or not the sum is visible in the centre of the pie chart.
+   *
+   * @param isSumVisible
+   */
+  public PieStyler setSumVisible(boolean isSumVisible) {
+
+    this.isSumVisible = isSumVisible;
+    return this;
+  }
+
+  public Font getSumFont() {
+
+    return sumFont;
+  }
+
+  /**
+   * Sets the font for the sum.
+   *
+   * @param sumFont font
+   */
+  public PieStyler setSumFont(Font sumFont) {
+
+    this.sumFont = sumFont;
+    return this;
+  }
+
+  /**
+   * Sets the font size for the sum.
+   *
+   * @param sumFontSize
+   */
+  public PieStyler setSumFontSize(float sumFontSize) {
+
+    this.sumFont = this.sumFont.deriveFont(sumFontSize);
+    return this;
+  }
+
+  public boolean isLabelsVisible() {
+
+    return isLabelsVisible;
+  }
+
+  /**
+   * Sets if annotations should be added to charts. Each chart type has a different annotation type
+   *
+   * @param labelsVisible
+   */
+  public PieStyler setLabelsVisible(boolean labelsVisible) {
+
+    this.isLabelsVisible = labelsVisible;
+    return this;
+  }
+
+  public Font getLabelsFont() {
+
+    return labelsFont;
+  }
+
+  /**
+   * Sets the Font used for chart annotations
+   *
+   * @param labelsFont
+   */
+  public PieStyler setLabelsFont(Font labelsFont) {
+
+    this.labelsFont = labelsFont;
+    return this;
+  }
+
+  public Color getLabelsFontColor() {
+    return labelsFontColor;
+  }
+
+  public Color getLabelsFontColor(Color backgroundColor) {
+
+    return FontColorDetector.getAutomaticFontColor(
+        backgroundColor, labelsFontColorAutomaticDark, labelsFontColorAutomaticLight);
+  }
+
+  /**
+   * Sets the color of the Font used for chart annotations
+   *
+   * @param labelsFontColor
+   */
+  public PieStyler setLabelsFontColor(Color labelsFontColor) {
+    this.labelsFontColor = labelsFontColor;
+    return this;
+  }
+
+  public boolean isLabelsFontColorAutomaticEnabled() {
+    return isLabelsFontColorAutomaticEnabled;
+  }
+
+  public PieStyler setLabelsFontColorAutomaticEnabled(boolean isLabelsFontColorAutomaticEnabled) {
+    this.isLabelsFontColorAutomaticEnabled = isLabelsFontColorAutomaticEnabled;
+    return this;
+  }
+
+  public Color getLabelsFontColorAutomaticLight() {
+    return labelsFontColorAutomaticLight;
+  }
+
+  public PieStyler setLabelsFontColorAutomaticLight(Color labelsFontColorAutomaticLight) {
+    this.labelsFontColorAutomaticLight = labelsFontColorAutomaticLight;
+    return this;
+  }
+
+  public Color getLabelsFontColorAutomaticDark() {
+    return labelsFontColorAutomaticDark;
+  }
+
+  public PieStyler setLabelsFontColorAutomaticDark(Color labelsFontColorAutomaticDark) {
+    this.labelsFontColorAutomaticDark = labelsFontColorAutomaticDark;
+    return this;
+  }
+
+  /**
+   * Set the theme the styler should use
+   *
+   * @param theme
+   */
+  public PieStyler setTheme(Theme theme) {
+
+    this.theme = theme;
+    setAllStyles();
+    return this;
+  }
+
+  public ClockwiseDirectionType getClockwiseDirectionType() {
+    return clockwiseDirectionType;
+  }
+
+  public PieStyler setClockwiseDirectionType(ClockwiseDirectionType clockwiseDirectionType) {
+    this.clockwiseDirectionType = clockwiseDirectionType;
+    return this;
+  }
+
+  // used to add border width
+  public PieStyler setSliceBorderWidth(double sliceBorderWidth) {
+    this.sliceBorderWidth = (float) sliceBorderWidth;
+    return this;
+  }
+
+  public float getSliceBorderWidth() {
+    return sliceBorderWidth;
+  }
+
+  public enum LabelType {
+    Value,
+    Percentage,
+    Name,
+    NameAndPercentage,
+    NameAndValue
+  }
+
+  public enum ClockwiseDirectionType {
+    CLOCKWISE,
+    COUNTER_CLOCKWISE
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/style/RadarStyler.java b/XChart/xchart/src/main/java/org/knowm/xchart/style/RadarStyler.java
new file mode 100644
index 0000000000000000000000000000000000000000..e313d9659b66c9c5246f8045be3384c0452cea66
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/style/RadarStyler.java
@@ -0,0 +1,218 @@
+package org.knowm.xchart.style;
+
+import java.awt.*;
+import org.knowm.xchart.style.theme.Theme;
+
+public class RadarStyler extends Styler {
+
+  // radar chart
+  private RadarRenderStyle radarRenderStyle;
+  private boolean isCircular;
+  private double startAngleInDegrees;
+
+  // radii tick marks
+  private boolean radiiTicksMarksVisible;
+  private Color radiiTickMarksColor;
+  private BasicStroke radiiTickMarksStroke;
+  private int radiiTickMarksCount;
+
+  // radii  labels
+  private boolean isRadiiTitleVisible;
+  private Font radiiTitleFont;
+  private int radiiTitlePadding;
+
+  // series style
+  private int markerSize;
+  private boolean isSeriesFilled = true;
+
+  public RadarStyler() {
+
+    setAllStyles();
+  }
+
+  @Override
+  void setAllStyles() {
+
+    super.setAllStyles();
+
+    this.radarRenderStyle = RadarRenderStyle.Polygon;
+    this.isCircular = theme.isCircular();
+    this.startAngleInDegrees = theme.getStartAngleInDegrees();
+
+    this.markerSize = theme.getMarkerSize();
+
+    this.radiiTicksMarksVisible = theme.isAxisTicksMarksVisible();
+    this.radiiTickMarksColor = theme.getPlotGridLinesColor();
+    this.radiiTickMarksStroke = theme.getPlotGridLinesStroke();
+    this.radiiTickMarksCount = 5;
+
+    this.isRadiiTitleVisible = theme.isXAxisTitleVisible() || theme.isYAxisTitleVisible();
+    this.radiiTitleFont = theme.getAxisTitleFont();
+    this.radiiTitlePadding = theme.getAxisTitlePadding();
+  }
+
+  public boolean isCircular() {
+
+    return isCircular;
+  }
+
+  /**
+   * Sets whether or not the radar chart is forced to be circular. Otherwise it's shape is oval,
+   * matching the containing plot.
+   *
+   * @param isCircular
+   */
+  public RadarStyler setCircular(boolean isCircular) {
+
+    this.isCircular = isCircular;
+    return this;
+  }
+
+  public double getStartAngleInDegrees() {
+
+    return startAngleInDegrees;
+  }
+
+  /**
+   * Sets the start angle in degrees. Zero degrees is straight up.
+   *
+   * @param startAngleInDegrees
+   */
+  public RadarStyler setStartAngleInDegrees(double startAngleInDegrees) {
+
+    this.startAngleInDegrees = startAngleInDegrees;
+    return this;
+  }
+
+  /**
+   * Set the theme the styler should use
+   *
+   * @param theme
+   */
+  public RadarStyler setTheme(Theme theme) {
+
+    this.theme = theme;
+    setAllStyles();
+    return this;
+  }
+
+  public int getMarkerSize() {
+
+    return markerSize;
+  }
+
+  /**
+   * Sets the size of the markers (in pixels)
+   *
+   * @param markerSize
+   */
+  public RadarStyler setMarkerSize(int markerSize) {
+
+    this.markerSize = markerSize;
+    return this;
+  }
+
+  public boolean isRadiiTicksMarksVisible() {
+
+    return radiiTicksMarksVisible;
+  }
+
+  public RadarStyler setRadiiTicksMarksVisible(boolean radiiTicksMarksVisible) {
+
+    this.radiiTicksMarksVisible = radiiTicksMarksVisible;
+    return this;
+  }
+
+  public Color getRadiiTickMarksColor() {
+
+    return radiiTickMarksColor;
+  }
+
+  public RadarStyler setRadiiTickMarksColor(Color radiiTickMarksColor) {
+
+    this.radiiTickMarksColor = radiiTickMarksColor;
+    return this;
+  }
+
+  public BasicStroke getRadiiTickMarksStroke() {
+
+    return radiiTickMarksStroke;
+  }
+
+  public RadarStyler setRadiiTickMarksStroke(BasicStroke radiiTickMarksStroke) {
+
+    this.radiiTickMarksStroke = radiiTickMarksStroke;
+    return this;
+  }
+
+  public boolean isRadiiTitleVisible() {
+
+    return isRadiiTitleVisible;
+  }
+
+  public RadarStyler setRadiiTitleVisible(boolean radiiTitleVisible) {
+
+    this.isRadiiTitleVisible = radiiTitleVisible;
+    return this;
+  }
+
+  public Font getRadiiTitleFont() {
+
+    return radiiTitleFont;
+  }
+
+  public RadarStyler setRadiiTitleFont(Font radiiTitleFont) {
+
+    this.radiiTitleFont = radiiTitleFont;
+    return this;
+  }
+
+  public int getRadiiTitlePadding() {
+
+    return radiiTitlePadding;
+  }
+
+  public RadarStyler setRadiiTitlePadding(int radiiTitlePadding) {
+
+    this.radiiTitlePadding = radiiTitlePadding;
+    return this;
+  }
+
+  public int getRadiiTickMarksCount() {
+
+    return radiiTickMarksCount;
+  }
+
+  public RadarStyler setRadiiTickMarksCount(int radiiTickMarksCount) {
+
+    this.radiiTickMarksCount = radiiTickMarksCount;
+    return this;
+  }
+
+  public boolean isSeriesFilled() {
+
+    return isSeriesFilled;
+  }
+
+  public RadarStyler setSeriesFilled(boolean seriesFilled) {
+
+    this.isSeriesFilled = seriesFilled;
+    return this;
+  }
+
+  public RadarRenderStyle getRadarRenderStyle() {
+
+    return radarRenderStyle;
+  }
+
+  public RadarStyler setRadarRenderStyle(RadarRenderStyle radarRenderStyle) {
+
+    this.radarRenderStyle = radarRenderStyle;
+    return this;
+  }
+
+  public enum RadarRenderStyle {
+    Polygon,
+    Circle;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/style/Styler.java b/XChart/xchart/src/main/java/org/knowm/xchart/style/Styler.java
new file mode 100644
index 0000000000000000000000000000000000000000..c30218eb6fa8368243e7d80e6b2532ddf7461680
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/style/Styler.java
@@ -0,0 +1,999 @@
+package org.knowm.xchart.style;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Font;
+import java.util.HashMap;
+import java.util.Map;
+import org.knowm.xchart.style.colors.ChartColor;
+import org.knowm.xchart.style.markers.Marker;
+import org.knowm.xchart.style.theme.GGPlot2Theme;
+import org.knowm.xchart.style.theme.MatlabTheme;
+import org.knowm.xchart.style.theme.Theme;
+import org.knowm.xchart.style.theme.XChartTheme;
+
+/**
+ * The styler is used to manage all things related to styling of the vast number of Chart components
+ */
+public abstract class Styler {
+
+  /** the default Theme */
+  Theme theme = new XChartTheme();
+
+  // Chart Style ///////////////////////////////
+  private Font baseFont;
+  private Color chartBackgroundColor;
+  private Color chartFontColor;
+  private int chartPadding;
+  private Color[] seriesColors;
+  private BasicStroke[] seriesLines;
+  private Marker[] seriesMarkers;
+
+  // Chart Title ///////////////////////////////
+  private Font chartTitleFont;
+  private boolean isChartTitleVisible;
+  private boolean isChartTitleBoxVisible;
+  private Color chartTitleBoxBackgroundColor;
+  private Color chartTitleBoxBorderColor;
+  private int chartTitlePadding;
+
+  // Chart Legend ///////////////////////////////
+  private boolean isLegendVisible;
+  private Color legendBackgroundColor;
+  private Color legendBorderColor;
+  private Font legendFont;
+  private int legendPadding;
+  private int legendSeriesLineLength;
+  private LegendPosition legendPosition;
+  private LegendLayout legendLayout = LegendLayout.Vertical;
+
+  // Chart Plot Area ///////////////////////////////
+  private Color plotBackgroundColor;
+  private Color plotBorderColor;
+  private boolean isPlotBorderVisible;
+  private double plotContentSize;
+
+  // Chart Annotations ///////////////////////////////
+  private Color annotationTextPanelBackgroundColor;
+  private Color annotationTextPanelBorderColor;
+  private Font annotationTextPanelFont;
+  private Color annotationTextPanelFontColor;
+  private int annotationTextPanelPadding;
+
+  private Font annotationTextFont;
+  private Color annotationTextFontColor;
+
+  private BasicStroke annotationLineStroke;
+  private Color annotationLineColor;
+
+  // Chart Button ///////////////////////////////
+
+  private Color chartButtonBackgroundColor;
+  private Color chartButtonBorderColor;
+  private Color chartButtonFontColor;
+  private Font chartButtonFont;
+  private int chartButtonMargin;
+  private ChartButtonPosition chartButtonPosition;
+
+  // Tool Tips ///////////////////////////////
+  private boolean isToolTipsEnabled;
+  private boolean isToolTipsAlwaysVisible;
+  private ToolTipType toolTipType;
+  private Color toolTipBackgroundColor;
+  private Color toolTipBorderColor;
+  private Font toolTipFont;
+  private Color toolTipHighlightColor;
+
+  // Misc. ///////////////////////////////
+  private boolean antiAlias = true;
+  private String decimalPattern;
+  // TODO I don't think this should be in styler directly?
+  private final HashMap<Integer, YAxisPosition> yAxisAlignmentMap = new HashMap<>();
+  private int yAxisLeftWidthHint;
+
+  // TODO move this to boxplot styler
+  // Box plot data ///////////////////////////////
+  private boolean showWithinAreaPoint = false;
+
+  // Axis Title Font Color
+  private Color xAxisTitleColor;
+  private Color yAxisTitleColor;
+  private final Map<Integer, Color> yAxisGroupTitleColorMap = new HashMap<>();
+
+  // Line, Scatter, Area , Radar Charts///////////////////////////////
+  // TODO Move these to the respective stylers where it is needed
+  private int markerSize;
+
+  void setAllStyles() {
+
+    // Chart Style ///////////////////////////////
+    baseFont = theme.getBaseFont();
+    chartBackgroundColor = theme.getChartBackgroundColor();
+    chartFontColor = theme.getChartFontColor();
+    chartPadding = theme.getChartPadding();
+    seriesColors = theme.getSeriesColors();
+    seriesLines = theme.getSeriesLines();
+    seriesMarkers = theme.getSeriesMarkers();
+
+    // Chart Title ///////////////////////////////
+    chartTitleFont = theme.getChartTitleFont();
+    isChartTitleVisible = theme.isChartTitleVisible();
+    isChartTitleBoxVisible = theme.isChartTitleBoxVisible();
+    chartTitleBoxBackgroundColor = theme.getChartTitleBoxBackgroundColor();
+    chartTitleBoxBorderColor = theme.getChartTitleBoxBorderColor();
+    chartTitlePadding = theme.getChartTitlePadding();
+
+    // Legend
+    isLegendVisible = theme.isLegendVisible();
+    legendBackgroundColor = theme.getLegendBackgroundColor();
+    legendBorderColor = theme.getLegendBorderColor();
+    legendFont = theme.getLegendFont();
+    legendPadding = theme.getLegendPadding();
+    legendSeriesLineLength = theme.getLegendSeriesLineLength();
+    legendPosition = theme.getLegendPosition();
+
+    // Chart Plot Area ///////////////////////////////
+    plotBackgroundColor = theme.getPlotBackgroundColor();
+    plotBorderColor = theme.getPlotBorderColor();
+    isPlotBorderVisible = theme.isPlotBorderVisible();
+    plotContentSize = theme.getPlotContentSize();
+
+    // Chart Annotations
+    annotationTextPanelBackgroundColor = theme.getAnnotationTextPanelBackgroundColor();
+    annotationTextPanelBorderColor = theme.getAnnotationTextPanelBorderColor();
+    annotationTextPanelFont = theme.getAnnotationTextPanelFont();
+    annotationTextPanelFontColor = theme.getAnnotationTextPanelFontColor();
+    annotationTextPanelPadding = theme.getAnnotationTextPanelPadding();
+
+    annotationTextFont = theme.getAnnotationTextFont();
+    annotationTextFontColor = theme.getAnnotationTextFontColor();
+
+    annotationLineStroke = theme.getAnnotationLineStroke();
+    annotationLineColor = theme.getAnnotationLineColor();
+
+    // Chart Button
+    // TODO move these to theme
+    chartButtonBackgroundColor = ChartColor.LIGHT_GREY.getColor();
+    chartButtonBorderColor = ChartColor.DARK_GREY.getColor();
+    chartButtonFontColor = getChartFontColor();
+    chartButtonFont = getBaseFont().deriveFont(11f);
+    chartButtonMargin = 6;
+    chartButtonPosition = ChartButtonPosition.InsideN;
+
+    // Tool Tips ///////////////////////////////
+
+    isToolTipsEnabled = theme.isToolTipsEnabled();
+    toolTipType = theme.getToolTipType();
+    toolTipBackgroundColor = theme.getToolTipBackgroundColor();
+    toolTipBorderColor = theme.getToolTipBorderColor();
+    toolTipFont = theme.getToolTipFont();
+    toolTipHighlightColor = theme.getToolTipHighlightColor();
+
+    // Formatting
+    decimalPattern = null;
+
+    // Line, Scatter, Area, Radar Charts ///////////////////////////////
+    this.markerSize = theme.getMarkerSize();
+  }
+
+  public Font getBaseFont() {
+
+    return baseFont;
+  }
+
+  /**
+   * Set the base font
+   *
+   * @param baseFont
+   * @return styler
+   */
+  public Styler setBaseFont(Font baseFont) {
+
+    this.baseFont = baseFont;
+    return this;
+  }
+
+  public Color getChartBackgroundColor() {
+
+    return chartBackgroundColor;
+  }
+
+  /**
+   * Set the chart background color - the part around the edge of the chart
+   *
+   * @param color
+   */
+  public Styler setChartBackgroundColor(Color color) {
+
+    this.chartBackgroundColor = color;
+    return this;
+  }
+
+  public Color getChartFontColor() {
+
+    return chartFontColor;
+  }
+
+  /**
+   * Set the chart font color. includes: Chart title, axes label, legend
+   *
+   * @param color
+   */
+  public Styler setChartFontColor(Color color) {
+
+    this.chartFontColor = color;
+    return this;
+  }
+
+  // Chart Style ///////////////////////////////
+
+  public int getChartPadding() {
+
+    return chartPadding;
+  }
+
+  /**
+   * Set the chart padding
+   *
+   * @param chartPadding
+   */
+  public Styler setChartPadding(int chartPadding) {
+
+    this.chartPadding = chartPadding;
+    return this;
+  }
+
+  public Color[] getSeriesColors() {
+
+    return seriesColors;
+  }
+
+  public Styler setSeriesColors(Color[] seriesColors) {
+
+    this.seriesColors = seriesColors;
+    return this;
+  }
+
+  public BasicStroke[] getSeriesLines() {
+
+    return seriesLines;
+  }
+
+  public Styler setSeriesLines(BasicStroke[] seriesLines) {
+
+    this.seriesLines = seriesLines;
+    return this;
+  }
+
+  public Marker[] getSeriesMarkers() {
+
+    return seriesMarkers;
+  }
+
+  public Styler setSeriesMarkers(Marker[] seriesMarkers) {
+
+    this.seriesMarkers = seriesMarkers;
+    return this;
+  }
+
+  // Chart Title ///////////////////////////////
+
+  public Font getChartTitleFont() {
+
+    return chartTitleFont;
+  }
+
+  /**
+   * Set the chart title font
+   *
+   * @param chartTitleFont
+   * @return
+   */
+  public Styler setChartTitleFont(Font chartTitleFont) {
+
+    this.chartTitleFont = chartTitleFont;
+    return this;
+  }
+
+  public boolean isChartTitleVisible() {
+
+    return isChartTitleVisible;
+  }
+
+  /**
+   * Set the chart title visibility
+   *
+   * @param isChartTitleVisible
+   */
+  public Styler setChartTitleVisible(boolean isChartTitleVisible) {
+
+    this.isChartTitleVisible = isChartTitleVisible;
+    return this;
+  }
+
+  public boolean isChartTitleBoxVisible() {
+
+    return isChartTitleBoxVisible;
+  }
+
+  /**
+   * Set the chart title box visibility
+   *
+   * @param isChartTitleBoxVisible
+   */
+  public Styler setChartTitleBoxVisible(boolean isChartTitleBoxVisible) {
+
+    this.isChartTitleBoxVisible = isChartTitleBoxVisible;
+    return this;
+  }
+
+  public Color getChartTitleBoxBackgroundColor() {
+
+    return chartTitleBoxBackgroundColor;
+  }
+
+  /**
+   * set the chart title box background color
+   *
+   * @param chartTitleBoxBackgroundColor
+   */
+  public Styler setChartTitleBoxBackgroundColor(Color chartTitleBoxBackgroundColor) {
+
+    this.chartTitleBoxBackgroundColor = chartTitleBoxBackgroundColor;
+    return this;
+  }
+
+  public Color getChartTitleBoxBorderColor() {
+
+    return chartTitleBoxBorderColor;
+  }
+
+  /**
+   * set the chart title box border color
+   *
+   * @param chartTitleBoxBorderColor
+   */
+  public Styler setChartTitleBoxBorderColor(Color chartTitleBoxBorderColor) {
+
+    this.chartTitleBoxBorderColor = chartTitleBoxBorderColor;
+    return this;
+  }
+
+  public int getChartTitlePadding() {
+
+    return chartTitlePadding;
+  }
+
+  /**
+   * set the chart title padding; the space between the chart title and the plot area
+   *
+   * @param chartTitlePadding
+   */
+  public Styler setChartTitlePadding(int chartTitlePadding) {
+
+    this.chartTitlePadding = chartTitlePadding;
+    return this;
+  }
+
+  // Chart Legend ///////////////////////////////
+
+  public boolean isLegendVisible() {
+
+    return isLegendVisible;
+  }
+
+  /**
+   * Set the chart legend visibility
+   *
+   * @param isLegendVisible
+   */
+  public Styler setLegendVisible(boolean isLegendVisible) {
+
+    this.isLegendVisible = isLegendVisible;
+    return this;
+  }
+
+  public Color getLegendBackgroundColor() {
+
+    return legendBackgroundColor;
+  }
+
+  /**
+   * Set the chart legend background color
+   *
+   * @param color
+   */
+  public Styler setLegendBackgroundColor(Color color) {
+
+    this.legendBackgroundColor = color;
+    return this;
+  }
+
+  /**
+   * Set the chart legend border color
+   *
+   * @return
+   */
+  public Color getLegendBorderColor() {
+
+    return legendBorderColor;
+  }
+
+  public Styler setLegendBorderColor(Color legendBorderColor) {
+
+    this.legendBorderColor = legendBorderColor;
+    return this;
+  }
+
+  public Font getLegendFont() {
+
+    return legendFont;
+  }
+
+  /**
+   * Set the chart legend font
+   *
+   * @param font
+   */
+  public Styler setLegendFont(Font font) {
+
+    this.legendFont = font;
+    return this;
+  }
+
+  public int getLegendPadding() {
+
+    return legendPadding;
+  }
+
+  /**
+   * Set the chart legend padding
+   *
+   * @param legendPadding
+   */
+  public Styler setLegendPadding(int legendPadding) {
+
+    this.legendPadding = legendPadding;
+    return this;
+  }
+
+  public int getLegendSeriesLineLength() {
+
+    return legendSeriesLineLength;
+  }
+
+  /**
+   * Set the chart legend series line length
+   *
+   * @param legendSeriesLineLength
+   * @return
+   */
+  public Styler setLegendSeriesLineLength(int legendSeriesLineLength) {
+
+    this.legendSeriesLineLength = Math.max(legendSeriesLineLength, 0);
+    return this;
+  }
+
+  public LegendPosition getLegendPosition() {
+
+    return legendPosition;
+  }
+
+  /**
+   * sets the legend position
+   *
+   * @param legendPosition
+   */
+  public Styler setLegendPosition(LegendPosition legendPosition) {
+
+    this.legendPosition = legendPosition;
+    return this;
+  }
+
+  public enum LegendPosition {
+    OutsideE,
+    InsideNW,
+    InsideNE,
+    InsideSE,
+    InsideSW,
+    InsideN,
+    InsideS,
+    OutsideS
+  }
+
+  /**
+   * Set the legend layout
+   *
+   * @return
+   */
+  public LegendLayout getLegendLayout() {
+
+    return legendLayout;
+  }
+
+  public Styler setLegendLayout(LegendLayout legendLayout) {
+
+    this.legendLayout = legendLayout;
+    return this;
+  }
+
+  public enum LegendLayout {
+    Vertical,
+    Horizontal
+  }
+
+  // Chart Plot ///////////////////////////////
+  public Color getPlotBackgroundColor() {
+
+    return plotBackgroundColor;
+  }
+
+  /**
+   * set the plot area's background color
+   *
+   * @param plotBackgroundColor
+   */
+  public Styler setPlotBackgroundColor(Color plotBackgroundColor) {
+
+    this.plotBackgroundColor = plotBackgroundColor;
+    return this;
+  }
+
+  public Color getPlotBorderColor() {
+
+    return plotBorderColor;
+  }
+
+  /**
+   * set the plot area's border color
+   *
+   * @param plotBorderColor
+   */
+  public Styler setPlotBorderColor(Color plotBorderColor) {
+
+    this.plotBorderColor = plotBorderColor;
+    return this;
+  }
+
+  public boolean isPlotBorderVisible() {
+
+    return isPlotBorderVisible;
+  }
+
+  /**
+   * sets the visibility of the border around the plot area
+   *
+   * @param isPlotBorderVisible
+   */
+  public Styler setPlotBorderVisible(boolean isPlotBorderVisible) {
+
+    this.isPlotBorderVisible = isPlotBorderVisible;
+    return this;
+  }
+
+  public double getPlotContentSize() {
+
+    return plotContentSize;
+  }
+
+  /**
+   * Sets the content size of the plot inside the plot area of the chart. To fill the area 100%, use
+   * a value of 1.0.
+   *
+   * @param plotContentSize - Valid range is between 0 and 1.
+   */
+  public Styler setPlotContentSize(double plotContentSize) {
+
+    if (plotContentSize < 0 || plotContentSize > 1) {
+      throw new IllegalArgumentException("Plot content size must be tween 0 and 1!!!");
+    }
+
+    this.plotContentSize = plotContentSize;
+    return this;
+  }
+
+  // Chart Annotations ///////////////////////////////
+
+  public Color getAnnotationTextPanelBackgroundColor() {
+
+    return annotationTextPanelBackgroundColor;
+  }
+
+  public Styler setAnnotationTextPanelBackgroundColor(Color color) {
+
+    this.annotationTextPanelBackgroundColor = color;
+    return this;
+  }
+
+  public Color getAnnotationTextPanelBorderColor() {
+
+    return annotationTextPanelBorderColor;
+  }
+
+  public Styler setAnnotationTextPanelBorderColor(Color borderColor) {
+
+    this.annotationTextPanelBorderColor = borderColor;
+    return this;
+  }
+
+  public Font getAnnotationTextPanelFont() {
+
+    return annotationTextPanelFont;
+  }
+
+  public Styler setAnnotationTextPanelFont(Font font) {
+
+    this.annotationTextPanelFont = font;
+    return this;
+  }
+
+  public Color getAnnotationTextPanelFontColor() {
+    return annotationTextPanelFontColor;
+  }
+
+  public Styler setAnnotationTextPanelFontColor(Color annotationTextPanelFontColor) {
+    this.annotationTextPanelFontColor = annotationTextPanelFontColor;
+    return this;
+  }
+
+  public int getAnnotationTextPanelPadding() {
+
+    return annotationTextPanelPadding;
+  }
+
+  public Styler setAnnotationTextPanelPadding(int annotationTextPanelPadding) {
+
+    this.annotationTextPanelPadding = annotationTextPanelPadding;
+    return this;
+  }
+
+  public Font getAnnotationTextFont() {
+    return annotationTextFont;
+  }
+
+  public Styler setAnnotationTextFont(Font annotationTextFont) {
+    this.annotationTextFont = annotationTextFont;
+    return this;
+  }
+
+  public Color getAnnotationTextFontColor() {
+    return annotationTextFontColor;
+  }
+
+  public Styler setAnnotationTextFontColor(Color annotationTextFontColor) {
+    this.annotationTextFontColor = annotationTextFontColor;
+    return this;
+  }
+
+  public BasicStroke getAnnotationLineStroke() {
+    return annotationLineStroke;
+  }
+
+  public Styler setAnnotationLineStroke(BasicStroke annotationLineStroke) {
+    this.annotationLineStroke = annotationLineStroke;
+    return this;
+  }
+
+  public Color getAnnotationLineColor() {
+    return annotationLineColor;
+  }
+
+  public Styler setAnnotationLineColor(Color annotationLineColor) {
+    this.annotationLineColor = annotationLineColor;
+    return this;
+  }
+
+  // Chart Button ///////////////////////////////
+
+  public Color getChartButtonBackgroundColor() {
+    return chartButtonBackgroundColor;
+  }
+
+  public Styler setChartButtonBackgroundColor(Color chartButtonBackgroundColor) {
+    this.chartButtonBackgroundColor = chartButtonBackgroundColor;
+    return this;
+  }
+
+  public Color getChartButtonBorderColor() {
+    return chartButtonBorderColor;
+  }
+
+  public Styler setChartButtonBorderColor(Color chartButtonBorderColor) {
+    this.chartButtonBorderColor = chartButtonBorderColor;
+    return this;
+  }
+
+  public Color getChartButtonFontColor() {
+    return chartButtonFontColor;
+  }
+
+  public Styler setChartButtonFontColor(Color chartButtonFontColor) {
+    this.chartButtonFontColor = chartButtonFontColor;
+    return this;
+  }
+
+  public Font getChartButtonFont() {
+    return chartButtonFont;
+  }
+
+  public Styler setChartButtonFont(Font chartButtonFont) {
+    this.chartButtonFont = chartButtonFont;
+    return this;
+  }
+
+  public int getChartButtonMargin() {
+    return chartButtonMargin;
+  }
+
+  public Styler setChartButtonMargin(int chartButtonMargin) {
+    this.chartButtonMargin = chartButtonMargin;
+    return this;
+  }
+
+  public ChartButtonPosition getChartButtonPosition() {
+    return chartButtonPosition;
+  }
+
+  public Styler setChartButtonPosition(ChartButtonPosition chartButtonPosition) {
+    this.chartButtonPosition = chartButtonPosition;
+    return this;
+  }
+
+  public enum ChartButtonPosition {
+    InsideNW,
+    InsideNE,
+    InsideSE,
+    InsideSW,
+    InsideN,
+    InsideS
+  }
+
+  // Tool Tips ///////////////////////////////
+
+  public boolean isToolTipsEnabled() {
+
+    return isToolTipsEnabled;
+  }
+
+  public Styler setToolTipsEnabled(boolean toolTipsEnabled) {
+
+    isToolTipsEnabled = toolTipsEnabled;
+    return this;
+  }
+
+  public boolean isToolTipsAlwaysVisible() {
+
+    return isToolTipsAlwaysVisible;
+  }
+
+  public Styler setToolTipsAlwaysVisible(boolean toolTipsAlwaysVisible) {
+
+    isToolTipsAlwaysVisible = toolTipsAlwaysVisible;
+    return this;
+  }
+
+  public ToolTipType getToolTipType() {
+
+    return toolTipType;
+  }
+
+  public Styler setToolTipType(ToolTipType toolTipType) {
+
+    this.toolTipType = toolTipType;
+    return this;
+  }
+
+  public enum ToolTipType {
+    xLabels,
+    yLabels,
+    xAndYLabels
+  }
+
+  public Color getToolTipBackgroundColor() {
+
+    return toolTipBackgroundColor;
+  }
+
+  public Styler setToolTipBackgroundColor(Color toolTipBackgroundColor) {
+
+    this.toolTipBackgroundColor = toolTipBackgroundColor;
+    return this;
+  }
+
+  public Color getToolTipBorderColor() {
+
+    return toolTipBorderColor;
+  }
+
+  public Styler setToolTipBorderColor(Color toolTipBorderColor) {
+
+    this.toolTipBorderColor = toolTipBorderColor;
+    return this;
+  }
+
+  public Font getToolTipFont() {
+
+    return toolTipFont;
+  }
+
+  public Styler setToolTipFont(Font toolTipFont) {
+
+    this.toolTipFont = toolTipFont;
+    return this;
+  }
+
+  public Color getToolTipHighlightColor() {
+
+    return toolTipHighlightColor;
+  }
+
+  public Styler setToolTipHighlightColor(Color toolTipHighlightColor) {
+
+    this.toolTipHighlightColor = toolTipHighlightColor;
+    return this;
+  }
+
+  // Number Formatter ///////////////////////////////
+
+  public String getDecimalPattern() {
+
+    return decimalPattern;
+  }
+
+  /**
+   * Set the decimal formatter for all numbers on the chart
+   *
+   * @param decimalPattern - the pattern describing the decimal format
+   */
+  public Styler setDecimalPattern(String decimalPattern) {
+
+    this.decimalPattern = decimalPattern;
+    return this;
+  }
+
+  // Y-Axis Group Position ///////////////////////////////
+
+  public YAxisPosition getYAxisGroupPosistion(int yAxisGroup) {
+
+    return yAxisAlignmentMap.get(yAxisGroup);
+  }
+
+  /**
+   * Set the Y-Axis group position.
+   *
+   * @param yAxisGroup
+   * @param yAxisPosition
+   */
+  public Styler setYAxisGroupPosition(int yAxisGroup, YAxisPosition yAxisPosition) {
+
+    yAxisAlignmentMap.put(yAxisGroup, yAxisPosition);
+    return this;
+  }
+
+  public enum YAxisPosition {
+    Left,
+    Right
+  }
+
+  public boolean getAntiAlias() {
+
+    return antiAlias;
+  }
+
+  // TODO add javadocs to all setters that are not yet documented.
+  public Styler setAntiAlias(boolean newVal) {
+
+    antiAlias = newVal;
+    return this;
+  }
+
+  public int getYAxisLeftWidthHint() {
+
+    return yAxisLeftWidthHint;
+  }
+
+  /**
+   * Set the width of the Y-Axis tick labels on the left side of the chart. This can help to align
+   * the start of the X-Axis for two or more charts that are arranged in a column of charts.
+   *
+   * @param yAxisLeftWidthHint
+   */
+  public Styler setYAxisLeftWidthHint(int yAxisLeftWidthHint) {
+
+    this.yAxisLeftWidthHint = yAxisLeftWidthHint;
+    return this;
+  }
+
+  public Styler setShowWithinAreaPoint(boolean showWithinAreaPoint) {
+
+    this.showWithinAreaPoint = showWithinAreaPoint;
+    return this;
+  }
+
+  public boolean getShowWithinAreaPoint() {
+
+    return this.showWithinAreaPoint;
+  }
+
+  public Color getXAxisTitleColor() {
+
+    return xAxisTitleColor;
+  }
+
+  public Styler setXAxisTitleColor(Color xAxisTitleColor) {
+
+    this.xAxisTitleColor = xAxisTitleColor;
+    return this;
+  }
+
+  // TODO is this not used internally??
+  public Color getYAxisTitleColor() {
+
+    return yAxisTitleColor;
+  }
+
+  public Styler setYAxisTitleColor(Color yAxisColor) {
+
+    this.yAxisTitleColor = yAxisColor;
+    return this;
+  }
+
+  public Color getYAxisGroupTitleColor(int yAxisGroup) {
+
+    Color color = yAxisGroupTitleColorMap.get(yAxisGroup);
+    if (color == null) {
+      return yAxisTitleColor;
+    }
+    return color;
+  }
+
+  public Styler setYAxisGroupTitleColor(int yAxisGroup, Color yAxisColor) {
+
+    yAxisGroupTitleColorMap.put(yAxisGroup, yAxisColor);
+    return this;
+  }
+
+  // Line, Scatter, Area Charts ///////////////////////////////
+
+  public int getMarkerSize() {
+
+    return markerSize;
+  }
+
+  /**
+   * Sets the size of the markers (in pixels)
+   *
+   * @param markerSize
+   */
+  public Styler setMarkerSize(int markerSize) {
+
+    this.markerSize = markerSize;
+    return this;
+  }
+
+  public enum ChartTheme {
+    XChart,
+    GGPlot2,
+    Matlab;
+
+    public Theme newInstance(ChartTheme chartTheme) {
+
+      switch (chartTheme) {
+        case GGPlot2:
+          return new GGPlot2Theme();
+
+        case Matlab:
+          return new MatlabTheme();
+
+        case XChart:
+        default:
+          return new XChartTheme();
+      }
+    }
+  }
+
+  public Theme getTheme() {
+
+    return theme;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/style/XYStyler.java b/XChart/xchart/src/main/java/org/knowm/xchart/style/XYStyler.java
new file mode 100644
index 0000000000000000000000000000000000000000..c0abadf8b4d3867e4a6c9ff59642b1c0a071a78e
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/style/XYStyler.java
@@ -0,0 +1,227 @@
+package org.knowm.xchart.style;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.util.function.Function;
+import org.knowm.xchart.XYSeries.XYSeriesRenderStyle;
+import org.knowm.xchart.style.colors.ChartColor;
+import org.knowm.xchart.style.theme.Theme;
+
+public class XYStyler extends AxesChartStyler {
+
+  private XYSeriesRenderStyle xySeriesRenderStyle;
+
+  // Zoom ///////////////////////////
+  private boolean isZoomEnabled;
+  private Color zoomSelectionColor;
+  private boolean zoomResetByDoubleClick;
+  private boolean zoomResetByButton;
+
+  // Cursor ////////////////////////////////
+
+  private boolean isCursorEnabled;
+  private Color cursorColor;
+  private float cursorLineWidth;
+  private Font cursorFont;
+  private Color cursorFontColor;
+  private Color cursorBackgroundColor;
+  private Function<Double, String> customCursorXDataFormattingFunction;
+  private Function<Double, String> customCursorYDataFormattingFunction;
+
+  /** Constructor */
+  public XYStyler() {
+
+    setAllStyles();
+  }
+
+  @Override
+  protected void setAllStyles() {
+
+    super.setAllStyles();
+
+    // Zoom ///////////////////////////
+    // TODO set this from the theme
+    xySeriesRenderStyle = XYSeriesRenderStyle.Line; // set default to line
+    isZoomEnabled = false; // set default to false
+    zoomSelectionColor = ChartColor.LIGHT_GREY.getColorTranslucent();
+    zoomResetByDoubleClick = true;
+    zoomResetByButton = true;
+
+    // Cursor ////////////////////////////////
+    this.isCursorEnabled = theme.isCursorEnabled();
+    this.cursorColor = theme.getCursorColor();
+    this.cursorLineWidth = theme.getCursorSize();
+    this.cursorFont = theme.getCursorFont();
+    this.cursorFontColor = theme.getCursorFontColor();
+    this.cursorBackgroundColor = theme.getCursorBackgroundColor();
+  }
+
+  /**
+   * Set the theme the styler should use
+   *
+   * @param theme
+   */
+  public void setTheme(Theme theme) {
+
+    this.theme = theme;
+    setAllStyles();
+  }
+
+  public XYSeriesRenderStyle getDefaultSeriesRenderStyle() {
+
+    return xySeriesRenderStyle;
+  }
+
+  /**
+   * Sets the default series render style for the chart (line, scatter, area, etc.) You can override
+   * the series render style individually on each Series object.
+   *
+   * @param xySeriesRenderStyle
+   */
+  public XYStyler setDefaultSeriesRenderStyle(XYSeriesRenderStyle xySeriesRenderStyle) {
+
+    this.xySeriesRenderStyle = xySeriesRenderStyle;
+    return this;
+  }
+
+  // Zoom ///////////////////////////////
+
+  public boolean isZoomEnabled() {
+    return isZoomEnabled;
+  }
+
+  public XYStyler setZoomEnabled(boolean isZoomEnabled) {
+
+    this.isZoomEnabled = isZoomEnabled;
+    return this;
+  }
+
+  public Color getZoomSelectionColor() {
+
+    return zoomSelectionColor;
+  }
+
+  public XYStyler setZoomSelectionColor(Color zoomSelectionColor) {
+
+    this.zoomSelectionColor = zoomSelectionColor;
+    return this;
+  }
+
+  public boolean isZoomResetByDoubleClick() {
+
+    return zoomResetByDoubleClick;
+  }
+
+  public XYStyler setZoomResetByDoubleClick(boolean zoomResetByDoubleClick) {
+
+    this.zoomResetByDoubleClick = zoomResetByDoubleClick;
+    return this;
+  }
+
+  public boolean isZoomResetByButton() {
+
+    return zoomResetByButton;
+  }
+
+  public XYStyler setZoomResetByButton(boolean zoomResetByButton) {
+
+    this.zoomResetByButton = zoomResetByButton;
+    return this;
+  }
+
+  // Cursor ///////////////////////////////
+
+  public boolean isCursorEnabled() {
+    return isCursorEnabled;
+  }
+
+  public XYStyler setCursorEnabled(boolean isCursorEnabled) {
+
+    this.isCursorEnabled = isCursorEnabled;
+    return this;
+  }
+
+  public Color getCursorColor() {
+    return cursorColor;
+  }
+
+  public XYStyler setCursorColor(Color cursorColor) {
+
+    this.cursorColor = cursorColor;
+    return this;
+  }
+
+  public float getCursorLineWidth() {
+
+    return cursorLineWidth;
+  }
+
+  public XYStyler setCursorLineWidth(float cursorLineWidth) {
+
+    this.cursorLineWidth = cursorLineWidth;
+    return this;
+  }
+
+  public Font getCursorFont() {
+
+    return cursorFont;
+  }
+
+  public XYStyler setCursorFont(Font cursorFont) {
+
+    this.cursorFont = cursorFont;
+    return this;
+  }
+
+  public Color getCursorFontColor() {
+
+    return cursorFontColor;
+  }
+
+  public XYStyler setCursorFontColor(Color cursorFontColor) {
+
+    this.cursorFontColor = cursorFontColor;
+    return this;
+  }
+
+  public Color getCursorBackgroundColor() {
+
+    return cursorBackgroundColor;
+  }
+
+  public XYStyler setCursorBackgroundColor(Color cursorBackgroundColor) {
+
+    this.cursorBackgroundColor = cursorBackgroundColor;
+    return this;
+  }
+
+  public Function<Double, String> getCustomCursorXDataFormattingFunction() {
+    return customCursorXDataFormattingFunction;
+  }
+
+  /**
+   * Set the custom function for formatting the cursor tooltip based on the series X-Axis data
+   *
+   * @param customCursorXDataFormattingFunction
+   */
+  public XYStyler setCustomCursorXDataFormattingFunction(
+      Function<Double, String> customCursorXDataFormattingFunction) {
+    this.customCursorXDataFormattingFunction = customCursorXDataFormattingFunction;
+    return this;
+  }
+
+  public Function<Double, String> getCustomCursorYDataFormattingFunction() {
+    return customCursorYDataFormattingFunction;
+  }
+
+  /**
+   * Set the custom function for formatting the cursor tooltip based on the series Y-Axis data
+   *
+   * @param customCursorYDataFormattingFunction
+   */
+  public XYStyler setCustomCursorYDataFormattingFunction(
+      Function<Double, String> customCursorYDataFormattingFunction) {
+    this.customCursorYDataFormattingFunction = customCursorYDataFormattingFunction;
+    return this;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/style/colors/BaseSeriesColors.java b/XChart/xchart/src/main/java/org/knowm/xchart/style/colors/BaseSeriesColors.java
new file mode 100644
index 0000000000000000000000000000000000000000..d9486e1a1532249c82cfe5251395ea9a7c8ce8a0
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/style/colors/BaseSeriesColors.java
@@ -0,0 +1,38 @@
+package org.knowm.xchart.style.colors;
+
+import java.awt.*;
+
+/**
+ * Colors are taken from <a
+ * href="http://colorbrewer2.org/#type=qualitative&scheme=Set3&n=12">colorbrewer2.org</a>.
+ */
+public class BaseSeriesColors implements SeriesColors {
+
+  private final Color[] seriesColors;
+
+  /** Constructor. */
+  public BaseSeriesColors() {
+
+    seriesColors =
+        new Color[] {
+          new Color(141, 211, 199),
+          new Color(255, 255, 179),
+          new Color(190, 186, 218),
+          new Color(251, 128, 114),
+          new Color(128, 177, 211),
+          new Color(253, 180, 98),
+          new Color(179, 222, 105),
+          new Color(252, 205, 229),
+          new Color(217, 217, 217),
+          new Color(188, 128, 189),
+          new Color(204, 235, 197),
+          new Color(255, 237, 111)
+        };
+  }
+
+  @Override
+  public Color[] getSeriesColors() {
+
+    return seriesColors;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/style/colors/ChartColor.java b/XChart/xchart/src/main/java/org/knowm/xchart/style/colors/ChartColor.java
new file mode 100644
index 0000000000000000000000000000000000000000..dfa7cc179520d20d1a93107aa9735f4674f178a5
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/style/colors/ChartColor.java
@@ -0,0 +1,51 @@
+package org.knowm.xchart.style.colors;
+
+import java.awt.*;
+
+/** Pre-defined Colors used for various Chart Elements */
+public enum ChartColor {
+
+  /** BLACK */
+  BLACK(new Color(0, 0, 0)),
+
+  /** DARK_GREY */
+  DARK_GREY(new Color(130, 130, 130)),
+
+  /** GREY */
+  GREY(new Color(210, 210, 210)),
+
+  /** LIGHT_GREY */
+  LIGHT_GREY(new Color(230, 230, 230)),
+
+  /** WHITE */
+  WHITE(new Color(255, 255, 255)),
+
+  /** RED */
+  RED(new Color(237, 67, 55)),
+
+  /** BLUE */
+  BLUE(new Color(67, 55, 237)),
+
+  /** GREEN */
+  GREEN(new Color(67, 237, 55));
+
+  final Color color;
+
+  /**
+   * Constructor
+   *
+   * @param color
+   */
+  ChartColor(Color color) {
+
+    this.color = color;
+  }
+
+  public Color getColor() {
+    return color;
+  }
+
+  public Color getColorTranslucent() {
+    return new Color(color.getRed(), color.getGreen(), color.getBlue(), 128);
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/style/colors/ColorBlindFriendlySeriesColors.java b/XChart/xchart/src/main/java/org/knowm/xchart/style/colors/ColorBlindFriendlySeriesColors.java
new file mode 100644
index 0000000000000000000000000000000000000000..8ec2f7b8d177422147ca6f1bc67ea16aceddbb9d
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/style/colors/ColorBlindFriendlySeriesColors.java
@@ -0,0 +1,33 @@
+package org.knowm.xchart.style.colors;
+
+import java.awt.*;
+
+public class ColorBlindFriendlySeriesColors implements SeriesColors {
+
+  // The color blind friendly palette
+  private static final Color BLACK = new Color(0, 0, 0, 255);
+  private static final Color ORANGE = new Color(230, 159, 0, 255);
+  private static final Color SKY_BLUE = new Color(86, 180, 233, 255);
+  private static final Color BLUISH_GREEN = new Color(0, 158, 115, 255);
+  private static final Color YELLOW = new Color(240, 228, 66, 255);
+  private static final Color BLUE = new Color(0, 114, 178, 255);
+  private static final Color VERMILLION = new Color(213, 94, 0, 255);
+  private static final Color REDDISH_PURPLE = new Color(204, 121, 167, 255);
+
+  private final Color[] seriesColors;
+
+  /** Constructor */
+  public ColorBlindFriendlySeriesColors() {
+
+    seriesColors =
+        new Color[] {
+          BLACK, ORANGE, SKY_BLUE, BLUISH_GREEN, YELLOW, BLUE, VERMILLION, REDDISH_PURPLE
+        };
+  }
+
+  @Override
+  public Color[] getSeriesColors() {
+
+    return seriesColors;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/style/colors/FontColorDetector.java b/XChart/xchart/src/main/java/org/knowm/xchart/style/colors/FontColorDetector.java
new file mode 100644
index 0000000000000000000000000000000000000000..08a54910384cde49d928a2cdcac5e475feed9cef
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/style/colors/FontColorDetector.java
@@ -0,0 +1,23 @@
+package org.knowm.xchart.style.colors;
+
+import java.awt.Color;
+
+public class FontColorDetector {
+
+  private static final int BRIGHTNESS_THRESHOLD = 130;
+  private static final double RED_FACTOR = .241;
+  private static final double GREEN_FACTOR = .587;
+  private static final double BLUE_FACTOR = .114;
+
+  public static Color getAutomaticFontColor(
+      Color backgroundColor, Color darkForegroundColor, Color lightForegroundColor) {
+    double backgroundColorPerceivedBrightness =
+        Math.sqrt(
+            Math.pow(backgroundColor.getRed(), 2) * RED_FACTOR
+                + Math.pow(backgroundColor.getGreen(), 2) * GREEN_FACTOR
+                + Math.pow(backgroundColor.getBlue(), 2) * BLUE_FACTOR);
+    return backgroundColorPerceivedBrightness < BRIGHTNESS_THRESHOLD
+        ? lightForegroundColor
+        : darkForegroundColor;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/style/colors/GGPlot2SeriesColors.java b/XChart/xchart/src/main/java/org/knowm/xchart/style/colors/GGPlot2SeriesColors.java
new file mode 100644
index 0000000000000000000000000000000000000000..2f9c36b5874f0c08040ce9abca58f2b9a37896e2
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/style/colors/GGPlot2SeriesColors.java
@@ -0,0 +1,26 @@
+package org.knowm.xchart.style.colors;
+
+import java.awt.*;
+
+public class GGPlot2SeriesColors implements SeriesColors {
+
+  public static final Color RED = new Color(248, 118, 109, 255);
+  public static final Color YELLOW_GREEN = new Color(163, 165, 0, 255);
+  public static final Color GREEN = new Color(0, 191, 125, 255);
+  public static final Color BLUE = new Color(0, 176, 246, 255);
+  public static final Color PURPLE = new Color(231, 107, 243, 255);
+
+  private final Color[] seriesColors;
+
+  /** Constructor */
+  public GGPlot2SeriesColors() {
+
+    seriesColors = new Color[] {RED, YELLOW_GREEN, GREEN, BLUE, PURPLE};
+  }
+
+  @Override
+  public Color[] getSeriesColors() {
+
+    return seriesColors;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/style/colors/MatlabSeriesColors.java b/XChart/xchart/src/main/java/org/knowm/xchart/style/colors/MatlabSeriesColors.java
new file mode 100644
index 0000000000000000000000000000000000000000..6d9223fc6836cbb2a27985bf001f8496d1748e99
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/style/colors/MatlabSeriesColors.java
@@ -0,0 +1,28 @@
+package org.knowm.xchart.style.colors;
+
+import java.awt.*;
+
+public class MatlabSeriesColors implements SeriesColors {
+
+  public static final Color BLUE = new Color(0, 0, 255, 255);
+  public static final Color GREEN = new Color(0, 128, 0, 255);
+  public static final Color RED = new Color(255, 0, 0, 255);
+  public static final Color TURQUOISE = new Color(0, 191, 191, 255);
+  public static final Color MAGENTA = new Color(191, 0, 191, 255);
+  public static final Color YELLOW = new Color(191, 191, 0, 255);
+  public static final Color DARK_GREY = new Color(64, 64, 64, 255);
+
+  private final Color[] seriesColors;
+
+  /** Constructor */
+  public MatlabSeriesColors() {
+
+    seriesColors = new Color[] {BLUE, GREEN, RED, TURQUOISE, MAGENTA, YELLOW, DARK_GREY};
+  }
+
+  @Override
+  public Color[] getSeriesColors() {
+
+    return seriesColors;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/style/colors/PrinterFriendlySeriesColors.java b/XChart/xchart/src/main/java/org/knowm/xchart/style/colors/PrinterFriendlySeriesColors.java
new file mode 100644
index 0000000000000000000000000000000000000000..0ae373eb65a7935857b386ba664b1f5e212fb2cc
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/style/colors/PrinterFriendlySeriesColors.java
@@ -0,0 +1,30 @@
+package org.knowm.xchart.style.colors;
+
+import java.awt.*;
+
+public class PrinterFriendlySeriesColors implements SeriesColors {
+
+  // printer-friendly colors from http://colorbrewer2.org/
+  private static final Color RED = new Color(228, 26, 28, 180);
+  private static final Color GREEN = new Color(55, 126, 184, 180);
+  private static final Color BLUE = new Color(77, 175, 74, 180);
+  private static final Color PURPLE = new Color(152, 78, 163, 180);
+  private static final Color ORANGE = new Color(255, 127, 0, 180);
+  // public static Color YELLOW = new Color(255, 255, 51, 180);
+  // public static Color BROWN = new Color(166, 86, 40, 180);
+  // public static Color PINK = new Color(247, 129, 191, 180);
+
+  private final Color[] seriesColors;
+
+  /** Constructor */
+  public PrinterFriendlySeriesColors() {
+
+    seriesColors = new Color[] {RED, GREEN, BLUE, PURPLE, ORANGE};
+  }
+
+  @Override
+  public Color[] getSeriesColors() {
+
+    return seriesColors;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/style/colors/SeriesColors.java b/XChart/xchart/src/main/java/org/knowm/xchart/style/colors/SeriesColors.java
new file mode 100644
index 0000000000000000000000000000000000000000..24c5c3b773ed74934291d2899fc0cd538027a916
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/style/colors/SeriesColors.java
@@ -0,0 +1,8 @@
+package org.knowm.xchart.style.colors;
+
+import java.awt.*;
+
+public interface SeriesColors {
+
+  Color[] getSeriesColors();
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/style/colors/XChartSeriesColors.java b/XChart/xchart/src/main/java/org/knowm/xchart/style/colors/XChartSeriesColors.java
new file mode 100644
index 0000000000000000000000000000000000000000..fc179bbb368e2ca2297f6df8486e040c9012cf2c
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/style/colors/XChartSeriesColors.java
@@ -0,0 +1,37 @@
+package org.knowm.xchart.style.colors;
+
+import java.awt.*;
+
+public class XChartSeriesColors implements SeriesColors {
+
+  // original XChart colors
+  public static final Color BLUE = new Color(0, 55, 255, 180);
+  public static final Color ORANGE = new Color(255, 172, 0, 180);
+  public static final Color PURPLE = new Color(128, 0, 255, 180);
+  public static final Color GREEN = new Color(0, 205, 0, 180);
+  public static final Color RED = new Color(205, 0, 0, 180);
+  public static final Color YELLOW = new Color(255, 215, 0, 180);
+  public static final Color MAGENTA = new Color(255, 0, 255, 180);
+  public static final Color PINK = new Color(255, 166, 201, 180);
+  public static final Color LIGHT_GREY = new Color(207, 207, 207, 180);
+  public static final Color CYAN = new Color(0, 255, 255, 180);
+  public static final Color BROWN = new Color(102, 56, 10, 180);
+  public static final Color BLACK = new Color(0, 0, 0, 180);
+
+  private final Color[] seriesColors;
+
+  /** Constructor */
+  public XChartSeriesColors() {
+
+    seriesColors =
+        new Color[] {
+          BLUE, ORANGE, PURPLE, GREEN, RED, YELLOW, MAGENTA, PINK, LIGHT_GREY, CYAN, BROWN, BLACK
+        };
+  }
+
+  @Override
+  public Color[] getSeriesColors() {
+
+    return seriesColors;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/style/lines/BaseSeriesLines.java b/XChart/xchart/src/main/java/org/knowm/xchart/style/lines/BaseSeriesLines.java
new file mode 100644
index 0000000000000000000000000000000000000000..18266c59bf6662f149336f124a3e8c846e358ac5
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/style/lines/BaseSeriesLines.java
@@ -0,0 +1,21 @@
+package org.knowm.xchart.style.lines;
+
+import java.awt.*;
+
+/** */
+public class BaseSeriesLines implements SeriesLines {
+
+  private final BasicStroke[] seriesLines;
+
+  /** Constructor */
+  public BaseSeriesLines() {
+
+    seriesLines = new BasicStroke[] {SOLID, DOT_DOT, DASH_DASH, DASH_DOT};
+  }
+
+  @Override
+  public BasicStroke[] getSeriesLines() {
+
+    return seriesLines;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/style/lines/GGPlot2SeriesLines.java b/XChart/xchart/src/main/java/org/knowm/xchart/style/lines/GGPlot2SeriesLines.java
new file mode 100644
index 0000000000000000000000000000000000000000..852c7ebeb859bcd437d82593493747f120f1081c
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/style/lines/GGPlot2SeriesLines.java
@@ -0,0 +1,20 @@
+package org.knowm.xchart.style.lines;
+
+import java.awt.*;
+
+public class GGPlot2SeriesLines implements SeriesLines {
+
+  private final BasicStroke[] seriesLines;
+
+  /** Constructor */
+  public GGPlot2SeriesLines() {
+
+    seriesLines = new BasicStroke[] {SOLID, DOT_DOT, DASH_DASH};
+  }
+
+  @Override
+  public BasicStroke[] getSeriesLines() {
+
+    return seriesLines;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/style/lines/MatlabSeriesLines.java b/XChart/xchart/src/main/java/org/knowm/xchart/style/lines/MatlabSeriesLines.java
new file mode 100644
index 0000000000000000000000000000000000000000..a4d70994e860c8e308b82e4f2c175f5b87fd9166
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/style/lines/MatlabSeriesLines.java
@@ -0,0 +1,20 @@
+package org.knowm.xchart.style.lines;
+
+import java.awt.*;
+
+public class MatlabSeriesLines implements SeriesLines {
+
+  private final BasicStroke[] seriesLines;
+
+  /** Constructor */
+  public MatlabSeriesLines() {
+
+    seriesLines = new BasicStroke[] {SOLID, DASH_DASH, DOT_DOT};
+  }
+
+  @Override
+  public BasicStroke[] getSeriesLines() {
+
+    return seriesLines;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/style/lines/NoneStroke.java b/XChart/xchart/src/main/java/org/knowm/xchart/style/lines/NoneStroke.java
new file mode 100644
index 0000000000000000000000000000000000000000..455051971460fd559a3d98cb2d6da89adcb1c2f5
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/style/lines/NoneStroke.java
@@ -0,0 +1,5 @@
+package org.knowm.xchart.style.lines;
+
+import java.awt.*;
+
+class NoneStroke extends BasicStroke {}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/style/lines/SeriesLines.java b/XChart/xchart/src/main/java/org/knowm/xchart/style/lines/SeriesLines.java
new file mode 100644
index 0000000000000000000000000000000000000000..4ba4b7154afb89af62b2ec628409419c87f32a4f
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/style/lines/SeriesLines.java
@@ -0,0 +1,31 @@
+package org.knowm.xchart.style.lines;
+
+import java.awt.*;
+
+/** Pre-defined Line Styles used for Series Lines */
+public interface SeriesLines {
+
+  BasicStroke NONE = new NoneStroke();
+  BasicStroke SOLID = new BasicStroke(2.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER);
+  BasicStroke DASH_DOT =
+      new BasicStroke(
+          2.0f,
+          BasicStroke.CAP_BUTT,
+          BasicStroke.JOIN_MITER,
+          10.0f,
+          new float[] {3.0f, 1.0f},
+          0.0f);
+  BasicStroke DASH_DASH =
+      new BasicStroke(
+          2.0f,
+          BasicStroke.CAP_BUTT,
+          BasicStroke.JOIN_MITER,
+          10.0f,
+          new float[] {3.0f, 3.0f},
+          0.0f);
+  BasicStroke DOT_DOT =
+      new BasicStroke(
+          2.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 10.0f, new float[] {2.0f}, 0.0f);
+
+  BasicStroke[] getSeriesLines();
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/style/lines/XChartSeriesLines.java b/XChart/xchart/src/main/java/org/knowm/xchart/style/lines/XChartSeriesLines.java
new file mode 100644
index 0000000000000000000000000000000000000000..a60a33a6f5d900a32fd70665b6181cd7113eb42a
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/style/lines/XChartSeriesLines.java
@@ -0,0 +1,20 @@
+package org.knowm.xchart.style.lines;
+
+import java.awt.*;
+
+public class XChartSeriesLines implements SeriesLines {
+
+  private final BasicStroke[] seriesLines;
+
+  /** Constructor */
+  public XChartSeriesLines() {
+
+    seriesLines = new BasicStroke[] {SOLID, DASH_DOT, DASH_DASH, DOT_DOT};
+  }
+
+  @Override
+  public BasicStroke[] getSeriesLines() {
+
+    return seriesLines;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/style/markers/BaseSeriesMarkers.java b/XChart/xchart/src/main/java/org/knowm/xchart/style/markers/BaseSeriesMarkers.java
new file mode 100644
index 0000000000000000000000000000000000000000..e4d47e70ee9add9d7517b7de5f71f75e86321bfb
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/style/markers/BaseSeriesMarkers.java
@@ -0,0 +1,19 @@
+package org.knowm.xchart.style.markers;
+
+/** */
+public class BaseSeriesMarkers implements SeriesMarkers {
+
+  private final Marker[] seriesMarkers;
+
+  /** Constructor */
+  public BaseSeriesMarkers() {
+
+    seriesMarkers = new Marker[] {CIRCLE, SQUARE, DIAMOND, TRIANGLE_UP, TRIANGLE_DOWN, CROSS};
+  }
+
+  @Override
+  public Marker[] getSeriesMarkers() {
+
+    return seriesMarkers;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/style/markers/Circle.java b/XChart/xchart/src/main/java/org/knowm/xchart/style/markers/Circle.java
new file mode 100644
index 0000000000000000000000000000000000000000..7aba93782191d060e0594861be7e490d6faa1055
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/style/markers/Circle.java
@@ -0,0 +1,17 @@
+package org.knowm.xchart.style.markers;
+
+import java.awt.*;
+import java.awt.geom.Ellipse2D;
+
+public class Circle extends Marker {
+
+  @Override
+  public void paint(Graphics2D g, double xOffset, double yOffset, int markerSize) {
+
+    g.setStroke(stroke);
+    double halfSize = (double) markerSize / 2;
+    Shape circle =
+        new Ellipse2D.Double(xOffset - halfSize, yOffset - halfSize, markerSize, markerSize);
+    g.fill(circle);
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/style/markers/Cross.java b/XChart/xchart/src/main/java/org/knowm/xchart/style/markers/Cross.java
new file mode 100644
index 0000000000000000000000000000000000000000..ad0419e0d054e03e8747032e2bf00945984ff5cb
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/style/markers/Cross.java
@@ -0,0 +1,23 @@
+package org.knowm.xchart.style.markers;
+
+import java.awt.*;
+import java.awt.geom.Path2D;
+
+public class Cross extends Marker {
+
+  @Override
+  public void paint(Graphics2D g, double xOffset, double yOffset, int markerSize) {
+
+    g.setStroke(stroke);
+
+    // Make a cross
+    double halfSize = (double) markerSize / 2;
+
+    Path2D.Double path = new Path2D.Double();
+    path.moveTo(xOffset - halfSize, yOffset - halfSize);
+    path.lineTo(xOffset + halfSize, yOffset + halfSize);
+    path.moveTo(xOffset - halfSize, yOffset + halfSize);
+    path.lineTo(xOffset + halfSize, yOffset - halfSize);
+    g.draw(path);
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/style/markers/Diamond.java b/XChart/xchart/src/main/java/org/knowm/xchart/style/markers/Diamond.java
new file mode 100644
index 0000000000000000000000000000000000000000..3f6e123721618b448faa778bf14715fa8564d39c
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/style/markers/Diamond.java
@@ -0,0 +1,24 @@
+package org.knowm.xchart.style.markers;
+
+import java.awt.*;
+import java.awt.geom.Path2D;
+
+public class Diamond extends Marker {
+
+  @Override
+  public void paint(Graphics2D g, double xOffset, double yOffset, int markerSize) {
+
+    g.setStroke(stroke);
+
+    // Make a diamond
+    double diamondHalfSize = (double) markerSize / 2 * 1.3;
+
+    Path2D.Double path = new Path2D.Double();
+    path.moveTo(xOffset - diamondHalfSize, yOffset);
+    path.lineTo(xOffset, yOffset - diamondHalfSize);
+    path.lineTo(xOffset + diamondHalfSize, yOffset);
+    path.lineTo(xOffset, yOffset + diamondHalfSize);
+    path.closePath();
+    g.fill(path);
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/style/markers/GGPlot2SeriesMarkers.java b/XChart/xchart/src/main/java/org/knowm/xchart/style/markers/GGPlot2SeriesMarkers.java
new file mode 100644
index 0000000000000000000000000000000000000000..b318853784f04baf88ae797ceb65e180dedb4953
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/style/markers/GGPlot2SeriesMarkers.java
@@ -0,0 +1,18 @@
+package org.knowm.xchart.style.markers;
+
+public class GGPlot2SeriesMarkers implements SeriesMarkers {
+
+  private final Marker[] seriesMarkers;
+
+  /** Constructor */
+  public GGPlot2SeriesMarkers() {
+
+    seriesMarkers = new Marker[] {CIRCLE, DIAMOND};
+  }
+
+  @Override
+  public Marker[] getSeriesMarkers() {
+
+    return seriesMarkers;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/style/markers/Marker.java b/XChart/xchart/src/main/java/org/knowm/xchart/style/markers/Marker.java
new file mode 100644
index 0000000000000000000000000000000000000000..518dd4d41bacf37be3edd2582e9d412fb8cb9660
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/style/markers/Marker.java
@@ -0,0 +1,10 @@
+package org.knowm.xchart.style.markers;
+
+import java.awt.*;
+
+public abstract class Marker {
+
+  final BasicStroke stroke = new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL);
+
+  public abstract void paint(Graphics2D g, double xOffset, double yOffset, int markerSize);
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/style/markers/MatlabSeriesMarkers.java b/XChart/xchart/src/main/java/org/knowm/xchart/style/markers/MatlabSeriesMarkers.java
new file mode 100644
index 0000000000000000000000000000000000000000..aaf34c859452825af282bacc84b6f9f0cd541368
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/style/markers/MatlabSeriesMarkers.java
@@ -0,0 +1,18 @@
+package org.knowm.xchart.style.markers;
+
+public class MatlabSeriesMarkers implements SeriesMarkers {
+
+  private final Marker[] seriesMarkers;
+
+  /** Constructor */
+  public MatlabSeriesMarkers() {
+
+    seriesMarkers = new Marker[] {CIRCLE, SQUARE, DIAMOND};
+  }
+
+  @Override
+  public Marker[] getSeriesMarkers() {
+
+    return seriesMarkers;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/style/markers/None.java b/XChart/xchart/src/main/java/org/knowm/xchart/style/markers/None.java
new file mode 100644
index 0000000000000000000000000000000000000000..15806539dfcaf09110cdd05d59a51ae785cc4777
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/style/markers/None.java
@@ -0,0 +1,13 @@
+package org.knowm.xchart.style.markers;
+
+import java.awt.*;
+
+public class None extends Marker {
+
+  @Override
+  public void paint(Graphics2D g, double xOffset, double yOffset, int markerSize) {
+
+    // do nothing!
+
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/style/markers/Oval.java b/XChart/xchart/src/main/java/org/knowm/xchart/style/markers/Oval.java
new file mode 100644
index 0000000000000000000000000000000000000000..8cdd1c91dea4d7e08204001d66d13014bf873af1
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/style/markers/Oval.java
@@ -0,0 +1,18 @@
+package org.knowm.xchart.style.markers;
+
+import java.awt.*;
+import java.awt.geom.Ellipse2D;
+
+public class Oval extends Marker {
+
+  @Override
+  public void paint(Graphics2D g, double xOffset, double yOffset, int markerSize) {
+    g.setStroke(stroke);
+    markerSize = (int) Math.ceil(markerSize * 1.25);
+    final double halfSize = markerSize / 2.0;
+
+    Shape circle =
+        new Ellipse2D.Double(xOffset - (halfSize / 2), yOffset - halfSize, halfSize, markerSize);
+    g.fill(circle);
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/style/markers/Plus.java b/XChart/xchart/src/main/java/org/knowm/xchart/style/markers/Plus.java
new file mode 100644
index 0000000000000000000000000000000000000000..0bbb8b5c64caa84357fb7b79e3885e71f6d995ad
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/style/markers/Plus.java
@@ -0,0 +1,23 @@
+package org.knowm.xchart.style.markers;
+
+import java.awt.*;
+import java.awt.geom.Path2D;
+
+public class Plus extends Marker {
+
+  @Override
+  public void paint(Graphics2D g, double xOffset, double yOffset, int markerSize) {
+
+    g.setStroke(stroke);
+
+    // Make a cross
+    double halfSize = (double) markerSize / 2;
+
+    Path2D.Double path = new Path2D.Double();
+    path.moveTo(xOffset + halfSize, yOffset);
+    path.lineTo(xOffset - halfSize, yOffset);
+    path.moveTo(xOffset, yOffset + halfSize);
+    path.lineTo(xOffset, yOffset - halfSize);
+    g.draw(path);
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/style/markers/Rectangle.java b/XChart/xchart/src/main/java/org/knowm/xchart/style/markers/Rectangle.java
new file mode 100644
index 0000000000000000000000000000000000000000..903a595ae1ae6047c2801c3c9b880b63dcfc15a1
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/style/markers/Rectangle.java
@@ -0,0 +1,17 @@
+package org.knowm.xchart.style.markers;
+
+import java.awt.*;
+import java.awt.geom.Rectangle2D;
+
+public class Rectangle extends Marker {
+
+  @Override
+  public void paint(Graphics2D g, double xOffset, double yOffset, int markerSize) {
+    g.setStroke(stroke);
+    double halfSize = (double) markerSize / 2;
+
+    Shape square =
+        new Rectangle2D.Double(xOffset - (halfSize / 2), yOffset - halfSize, halfSize, markerSize);
+    g.fill(square);
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/style/markers/SeriesMarkers.java b/XChart/xchart/src/main/java/org/knowm/xchart/style/markers/SeriesMarkers.java
new file mode 100644
index 0000000000000000000000000000000000000000..5d0f1c1ee9a64b20e49eaff2d02bbaa9ab802d78
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/style/markers/SeriesMarkers.java
@@ -0,0 +1,18 @@
+package org.knowm.xchart.style.markers;
+
+public interface SeriesMarkers {
+
+  Marker NONE = new None();
+  Marker CIRCLE = new Circle();
+  Marker DIAMOND = new Diamond();
+  Marker SQUARE = new Square();
+  Marker TRIANGLE_DOWN = new TriangleDown();
+  Marker TRIANGLE_UP = new TriangleUp();
+  Marker CROSS = new Cross();
+  Marker PLUS = new Plus();
+  Marker TRAPEZOID = new Trapezoid();
+  Marker OVAL = new Oval();
+  Marker RECTANGLE = new Rectangle();
+
+  Marker[] getSeriesMarkers();
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/style/markers/Square.java b/XChart/xchart/src/main/java/org/knowm/xchart/style/markers/Square.java
new file mode 100644
index 0000000000000000000000000000000000000000..2cbe804c856c87fd58b3248151cf7fcdae812185
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/style/markers/Square.java
@@ -0,0 +1,17 @@
+package org.knowm.xchart.style.markers;
+
+import java.awt.*;
+import java.awt.geom.Rectangle2D;
+
+public class Square extends Marker {
+
+  @Override
+  public void paint(Graphics2D g, double xOffset, double yOffset, int markerSize) {
+
+    g.setStroke(stroke);
+    double halfSize = (double) markerSize / 2;
+    Shape square =
+        new Rectangle2D.Double(xOffset - halfSize, yOffset - halfSize, markerSize, markerSize);
+    g.fill(square);
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/style/markers/Trapezoid.java b/XChart/xchart/src/main/java/org/knowm/xchart/style/markers/Trapezoid.java
new file mode 100644
index 0000000000000000000000000000000000000000..8f0db8174b87c18b6c43b782e02b1d8d85e9b2c3
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/style/markers/Trapezoid.java
@@ -0,0 +1,22 @@
+package org.knowm.xchart.style.markers;
+
+import java.awt.*;
+
+public class Trapezoid extends Marker {
+  @Override
+  public void paint(Graphics2D g, double xOffset, double yOffset, int markerSize) {
+    g.setStroke(stroke);
+    double halfSize = (double) markerSize / 2;
+    Polygon polygon = new Polygon();
+
+    for (int i = 1; i <= 4; i++) {
+      polygon.addPoint(
+          (int) ((xOffset) + (markerSize * 0.75) * Math.sin(i * 2 * Math.PI / 5)),
+          (int)
+              ((yOffset + (markerSize * 0.25))
+                  + (markerSize * 0.75) * Math.cos(i * 2 * Math.PI / 5)));
+    }
+
+    g.fillPolygon(polygon);
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/style/markers/TriangleDown.java b/XChart/xchart/src/main/java/org/knowm/xchart/style/markers/TriangleDown.java
new file mode 100644
index 0000000000000000000000000000000000000000..cb14e57c3d56f8d34f0bf8ec47d42afd6263f34d
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/style/markers/TriangleDown.java
@@ -0,0 +1,22 @@
+package org.knowm.xchart.style.markers;
+
+import java.awt.*;
+import java.awt.geom.Path2D;
+
+public class TriangleDown extends Marker {
+
+  @Override
+  public void paint(Graphics2D g, double xOffset, double yOffset, int markerSize) {
+
+    g.setStroke(stroke);
+    double halfSize = (double) markerSize / 2;
+
+    // Make a triangle
+    Path2D.Double path = new Path2D.Double();
+    path.moveTo(xOffset - halfSize, 1 + yOffset - halfSize);
+    path.lineTo(xOffset, 1 + yOffset - halfSize + markerSize);
+    path.lineTo(xOffset - halfSize + markerSize, 1 + yOffset - halfSize);
+    path.closePath();
+    g.fill(path);
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/style/markers/TriangleUp.java b/XChart/xchart/src/main/java/org/knowm/xchart/style/markers/TriangleUp.java
new file mode 100644
index 0000000000000000000000000000000000000000..5841fe0725e865ccc17d5f5b7ea91fc4d51086ec
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/style/markers/TriangleUp.java
@@ -0,0 +1,22 @@
+package org.knowm.xchart.style.markers;
+
+import java.awt.*;
+import java.awt.geom.Path2D;
+
+public class TriangleUp extends Marker {
+
+  @Override
+  public void paint(Graphics2D g, double xOffset, double yOffset, int markerSize) {
+
+    g.setStroke(stroke);
+    double halfSize = (double) markerSize / 2;
+
+    // Make a triangle
+    Path2D.Double path = new Path2D.Double();
+    path.moveTo(xOffset - halfSize, yOffset - halfSize + markerSize - 1);
+    path.lineTo(xOffset - halfSize + markerSize, yOffset - halfSize + markerSize - 1);
+    path.lineTo(xOffset, yOffset - halfSize - 1);
+    path.closePath();
+    g.fill(path);
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/style/markers/XChartSeriesMarkers.java b/XChart/xchart/src/main/java/org/knowm/xchart/style/markers/XChartSeriesMarkers.java
new file mode 100644
index 0000000000000000000000000000000000000000..23f9e09af45c62c38c5ab4f48c3a66a14e7c888f
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/style/markers/XChartSeriesMarkers.java
@@ -0,0 +1,19 @@
+package org.knowm.xchart.style.markers;
+
+public class XChartSeriesMarkers implements SeriesMarkers {
+
+  private final Marker[] seriesMarkers;
+
+  /** Constructor */
+  public XChartSeriesMarkers() {
+
+    seriesMarkers =
+        new Marker[] {CIRCLE, DIAMOND, SQUARE, TRIANGLE_DOWN, TRIANGLE_UP, CROSS, PLUS, RECTANGLE};
+  }
+
+  @Override
+  public Marker[] getSeriesMarkers() {
+
+    return seriesMarkers;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/style/theme/AbstractBaseTheme.java b/XChart/xchart/src/main/java/org/knowm/xchart/style/theme/AbstractBaseTheme.java
new file mode 100644
index 0000000000000000000000000000000000000000..42ec681151098996d686503e42c057539027d7e3
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/style/theme/AbstractBaseTheme.java
@@ -0,0 +1,437 @@
+package org.knowm.xchart.style.theme;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Font;
+import org.knowm.xchart.style.PieStyler.LabelType;
+import org.knowm.xchart.style.Styler.LegendPosition;
+import org.knowm.xchart.style.colors.BaseSeriesColors;
+import org.knowm.xchart.style.colors.ChartColor;
+import org.knowm.xchart.style.lines.BaseSeriesLines;
+import org.knowm.xchart.style.markers.BaseSeriesMarkers;
+import org.knowm.xchart.style.markers.Marker;
+
+/** */
+public abstract class AbstractBaseTheme implements Theme {
+
+  // Chart Style ///////////////////////////////
+
+  @Override
+  public Font getBaseFont() {
+
+    return BASE_FONT;
+  }
+
+  @Override
+  public Color getChartBackgroundColor() {
+
+    return ChartColor.WHITE.getColor();
+  }
+
+  @Override
+  public Color getChartFontColor() {
+
+    return ChartColor.BLACK.getColor();
+  }
+
+  @Override
+  public int getChartPadding() {
+
+    return 10;
+  }
+
+  // SeriesMarkers, SeriesLines, SeriesColors ///////////////////////////////
+
+  @Override
+  public Color[] getSeriesColors() {
+
+    return new BaseSeriesColors().getSeriesColors();
+  }
+
+  @Override
+  public Marker[] getSeriesMarkers() {
+
+    return new BaseSeriesMarkers().getSeriesMarkers();
+  }
+
+  @Override
+  public BasicStroke[] getSeriesLines() {
+
+    return new BaseSeriesLines().getSeriesLines();
+  }
+
+  // Chart Title ///////////////////////////////
+
+  /** Base font, bold, size 14. */
+  @Override
+  public Font getChartTitleFont() {
+
+    return getBaseFont().deriveFont(Font.BOLD).deriveFont(14f);
+  }
+
+  @Override
+  public boolean isChartTitleVisible() {
+
+    return true;
+  }
+
+  @Override
+  public boolean isChartTitleBoxVisible() {
+
+    return true;
+  }
+
+  @Override
+  public Color getChartTitleBoxBackgroundColor() {
+
+    return ChartColor.WHITE.getColor();
+  }
+
+  @Override
+  public Color getChartTitleBoxBorderColor() {
+
+    return ChartColor.WHITE.getColor();
+  }
+
+  @Override
+  public int getChartTitlePadding() {
+
+    return 5;
+  }
+
+  // Chart Legend ///////////////////////////////
+
+  @Override
+  public Font getLegendFont() {
+
+    return getBaseFont().deriveFont(11f);
+  }
+
+  @Override
+  public boolean isLegendVisible() {
+
+    return true;
+  }
+
+  @Override
+  public Color getLegendBackgroundColor() {
+
+    return ChartColor.WHITE.getColor();
+  }
+
+  @Override
+  public Color getLegendBorderColor() {
+
+    return ChartColor.DARK_GREY.getColor();
+  }
+
+  @Override
+  public int getLegendPadding() {
+
+    return 10;
+  }
+
+  @Override
+  public int getLegendSeriesLineLength() {
+
+    return 24;
+  }
+
+  @Override
+  public LegendPosition getLegendPosition() {
+
+    return LegendPosition.OutsideE;
+  }
+
+  // Chart Axes ///////////////////////////////
+
+  @Override
+  public boolean isXAxisTitleVisible() {
+
+    return true;
+  }
+
+  @Override
+  public boolean isYAxisTitleVisible() {
+
+    return true;
+  }
+
+  @Override
+  public Font getAxisTitleFont() {
+
+    return getBaseFont().deriveFont(Font.BOLD).deriveFont(12f);
+  }
+
+  @Override
+  public boolean isXAxisTicksVisible() {
+
+    return true;
+  }
+
+  @Override
+  public boolean isYAxisTicksVisible() {
+
+    return true;
+  }
+
+  @Override
+  public Font getAxisTickLabelsFont() {
+
+    return getAxisTitleFont();
+  }
+
+  @Override
+  public int getAxisTickMarkLength() {
+
+    return 3;
+  }
+
+  @Override
+  public int getAxisTickPadding() {
+
+    return 4;
+  }
+
+  @Override
+  public Color getAxisTickMarksColor() {
+
+    return ChartColor.DARK_GREY.getColor();
+  }
+
+  @Override
+  public BasicStroke getAxisTickMarksStroke() {
+
+    return BASE_STROKE;
+  }
+
+  @Override
+  public Color getAxisTickLabelsColor() {
+
+    return ChartColor.BLACK.getColor();
+  }
+
+  @Override
+  public boolean isAxisTicksLineVisible() {
+
+    return true;
+  }
+
+  @Override
+  public boolean isAxisTicksMarksVisible() {
+
+    return true;
+  }
+
+  @Override
+  public int getAxisTitlePadding() {
+
+    return 10;
+  }
+
+  @Override
+  public int getXAxisTickMarkSpacingHint() {
+
+    return 74;
+  }
+
+  @Override
+  public int getYAxisTickMarkSpacingHint() {
+
+    return 44;
+  }
+
+  // Chart Plot Area ///////////////////////////////
+
+  @Override
+  public boolean isPlotGridLinesVisible() {
+
+    return true;
+  }
+
+  @Override
+  public boolean isPlotGridVerticalLinesVisible() {
+
+    return true;
+  }
+
+  @Override
+  public boolean isPlotGridHorizontalLinesVisible() {
+
+    return true;
+  }
+
+  @Override
+  public Color getPlotBackgroundColor() {
+
+    return ChartColor.WHITE.getColor();
+  }
+
+  @Override
+  public Color getPlotBorderColor() {
+
+    return ChartColor.DARK_GREY.getColor();
+  }
+
+  @Override
+  public boolean isPlotBorderVisible() {
+
+    return true;
+  }
+
+  @Override
+  public boolean isPlotTicksMarksVisible() {
+
+    return true;
+  }
+
+  @Override
+  public Color getPlotGridLinesColor() {
+
+    return ChartColor.GREY.getColor();
+  }
+
+  @Override
+  public BasicStroke getPlotGridLinesStroke() {
+
+    return new BasicStroke(
+        1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 10.0f, new float[] {3.0f, 5.0f}, 0.0f);
+  }
+
+  @Override
+  public int getPlotMargin() {
+
+    return 4;
+  }
+
+  // Cursor ///////////////////////////////
+
+  @Override
+  public boolean isCursorEnabled() {
+
+    return false;
+  }
+
+  @Override
+  public Color getCursorColor() {
+
+    return Color.BLACK;
+  }
+
+  @Override
+  public float getCursorSize() {
+
+    return 1;
+  }
+
+  @Override
+  public Font getCursorFont() {
+
+    return new Font(Font.SANS_SERIF, Font.PLAIN, 16);
+  }
+
+  @Override
+  public Color getCursorFontColor() {
+
+    return Color.WHITE;
+  }
+
+  @Override
+  public Color getCursorBackgroundColor() {
+
+    return Color.GRAY;
+  }
+
+  // Category Charts ///////////////////////////////
+
+  @Override
+  public double getAvailableSpaceFill() {
+
+    return 0.9;
+  }
+
+  @Override
+  public boolean isOverlapped() {
+
+    return false;
+  }
+
+  // Pie Charts ///////////////////////////////
+
+  @Override
+  public boolean isCircular() {
+
+    return true;
+  }
+
+  @Override
+  public double getStartAngleInDegrees() {
+
+    return 0;
+  }
+
+  /** Base font, size 15. */
+  @Override
+  public Font getPieFont() {
+
+    return getBaseFont().deriveFont(15f);
+  }
+
+  @Override
+  public double getLabelsDistance() {
+
+    return .67;
+  }
+
+  @Override
+  public LabelType getLabelType() {
+
+    return LabelType.Percentage;
+  }
+
+  @Override
+  public boolean setForceAllLabelsVisible() {
+
+    return false;
+  }
+
+  @Override
+  public double getDonutThickness() {
+
+    return .33;
+  }
+
+  @Override
+  public boolean isSumVisible() {
+
+    return false;
+  }
+
+  @Override
+  public Font getSumFont() {
+
+    return getBaseFont().deriveFont(15f);
+  }
+
+  // Line, Scatter, Area Charts ///////////////////////////////
+
+  @Override
+  public int getMarkerSize() {
+
+    return 8;
+  }
+
+  // Error Bars ///////////////////////////////
+
+  @Override
+  public Color getErrorBarsColor() {
+
+    return ChartColor.BLACK.getColor();
+  }
+
+  @Override
+  public boolean isErrorBarsColorSeriesColor() {
+
+    return false;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/style/theme/GGPlot2Theme.java b/XChart/xchart/src/main/java/org/knowm/xchart/style/theme/GGPlot2Theme.java
new file mode 100644
index 0000000000000000000000000000000000000000..7a12d0b533b2069dd90fd5dc05526b8eda1a4b5b
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/style/theme/GGPlot2Theme.java
@@ -0,0 +1,205 @@
+package org.knowm.xchart.style.theme;
+
+import java.awt.*;
+import org.knowm.xchart.style.PieStyler.LabelType;
+import org.knowm.xchart.style.colors.ChartColor;
+import org.knowm.xchart.style.colors.GGPlot2SeriesColors;
+import org.knowm.xchart.style.lines.GGPlot2SeriesLines;
+import org.knowm.xchart.style.markers.GGPlot2SeriesMarkers;
+import org.knowm.xchart.style.markers.Marker;
+
+public class GGPlot2Theme extends AbstractBaseTheme {
+
+  // Chart Style ///////////////////////////////
+
+  // SeriesMarkers, SeriesLines, SeriesColors ///////////////////////////////
+
+  @Override
+  public Marker[] getSeriesMarkers() {
+
+    return new GGPlot2SeriesMarkers().getSeriesMarkers();
+  }
+
+  @Override
+  public BasicStroke[] getSeriesLines() {
+
+    return new GGPlot2SeriesLines().getSeriesLines();
+  }
+
+  @Override
+  public Color[] getSeriesColors() {
+
+    return new GGPlot2SeriesColors().getSeriesColors();
+  }
+
+  // Chart Title ///////////////////////////////
+
+  @Override
+  public Font getChartTitleFont() {
+
+    return getBaseFont().deriveFont(14f);
+  }
+
+  @Override
+  public Color getChartTitleBoxBackgroundColor() {
+
+    return ChartColor.GREY.getColor();
+  }
+
+  @Override
+  public Color getChartTitleBoxBorderColor() {
+
+    return ChartColor.GREY.getColor();
+  }
+
+  // Chart Legend ///////////////////////////////
+
+  @Override
+  public Font getLegendFont() {
+
+    return getBaseFont().deriveFont(14f);
+  }
+
+  @Override
+  public Color getLegendBorderColor() {
+
+    return ChartColor.WHITE.getColor();
+  }
+
+  // Chart Axes ///////////////////////////////
+
+  @Override
+  public Font getAxisTitleFont() {
+
+    return getBaseFont().deriveFont(14f);
+  }
+
+  @Override
+  public Font getAxisTickLabelsFont() {
+
+    return getBaseFont().deriveFont(Font.BOLD).deriveFont(13f);
+  }
+
+  @Override
+  public int getAxisTickMarkLength() {
+
+    return 8;
+  }
+
+  @Override
+  public int getAxisTickPadding() {
+
+    return 5;
+  }
+
+  @Override
+  public BasicStroke getAxisTickMarksStroke() {
+
+    return new BasicStroke(1.5f);
+  }
+
+  @Override
+  public Color getAxisTickLabelsColor() {
+
+    return ChartColor.DARK_GREY.getColor();
+  }
+
+  @Override
+  public boolean isAxisTicksLineVisible() {
+
+    return false;
+  }
+
+  // Chart Plot Area ///////////////////////////////
+
+  @Override
+  public Color getPlotBackgroundColor() {
+
+    return ChartColor.LIGHT_GREY.getColor();
+  }
+
+  @Override
+  public Color getPlotBorderColor() {
+
+    return ChartColor.WHITE.getColor();
+  }
+
+  @Override
+  public boolean isPlotBorderVisible() {
+
+    return false;
+  }
+
+  @Override
+  public boolean isPlotTicksMarksVisible() {
+
+    return false;
+  }
+
+  @Override
+  public Color getPlotGridLinesColor() {
+
+    return ChartColor.WHITE.getColor();
+  }
+
+  @Override
+  public BasicStroke getPlotGridLinesStroke() {
+
+    return new BasicStroke(1.0f);
+  }
+
+  @Override
+  public int getPlotMargin() {
+
+    return 0;
+  }
+
+  // Tool Tips ///////////////////////////////
+
+  @Override
+  public Color getToolTipBackgroundColor() {
+
+    return ChartColor.WHITE.getColor();
+  }
+
+  @Override
+  public Color getToolTipBorderColor() {
+
+    return ChartColor.DARK_GREY.getColor();
+  }
+
+  @Override
+  public Color getToolTipHighlightColor() {
+
+    return ChartColor.LIGHT_GREY.getColor();
+  }
+
+  // Category Charts ///////////////////////////////
+
+  // Pie Charts ///////////////////////////////
+
+  @Override
+  public LabelType getLabelType() {
+
+    return LabelType.NameAndPercentage;
+  }
+
+  @Override
+  public double getDonutThickness() {
+
+    return .25;
+  }
+
+  // Line, Scatter, Area Charts ///////////////////////////////
+
+  // Error Bars ///////////////////////////////
+
+  @Override
+  public Color getErrorBarsColor() {
+
+    return ChartColor.DARK_GREY.getColor();
+  }
+
+  // Chart Annotations ///////////////////////////////
+
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/style/theme/MatlabTheme.java b/XChart/xchart/src/main/java/org/knowm/xchart/style/theme/MatlabTheme.java
new file mode 100644
index 0000000000000000000000000000000000000000..232f5bd4ef6bfc908a567193c040d30bead1e0c8
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/style/theme/MatlabTheme.java
@@ -0,0 +1,152 @@
+package org.knowm.xchart.style.theme;
+
+import java.awt.*;
+import org.knowm.xchart.style.PieStyler.LabelType;
+import org.knowm.xchart.style.colors.ChartColor;
+import org.knowm.xchart.style.colors.MatlabSeriesColors;
+import org.knowm.xchart.style.lines.MatlabSeriesLines;
+import org.knowm.xchart.style.markers.Marker;
+import org.knowm.xchart.style.markers.MatlabSeriesMarkers;
+
+public class MatlabTheme extends AbstractBaseTheme {
+
+  // Chart Style ///////////////////////////////
+
+  // SeriesMarkers, SeriesLines, SeriesColors ///////////////////////////////
+
+  @Override
+  public Marker[] getSeriesMarkers() {
+
+    return new MatlabSeriesMarkers().getSeriesMarkers();
+  }
+
+  @Override
+  public BasicStroke[] getSeriesLines() {
+
+    return new MatlabSeriesLines().getSeriesLines();
+  }
+
+  @Override
+  public Color[] getSeriesColors() {
+
+    return new MatlabSeriesColors().getSeriesColors();
+  }
+
+  // Chart Title ///////////////////////////////
+
+  @Override
+  public boolean isChartTitleBoxVisible() {
+
+    return false;
+  }
+
+  // Chart Legend ///////////////////////////////
+
+  @Override
+  public Color getLegendBorderColor() {
+
+    return ChartColor.BLACK.getColor();
+  }
+
+  // Chart Axes ///////////////////////////////
+
+  @Override
+  public Font getAxisTitleFont() {
+
+    return getBaseFont().deriveFont(12f);
+  }
+
+  @Override
+  public int getAxisTickMarkLength() {
+
+    return 5;
+  }
+
+  @Override
+  public Color getAxisTickMarksColor() {
+
+    return ChartColor.BLACK.getColor();
+  }
+
+  @Override
+  public BasicStroke getAxisTickMarksStroke() {
+
+    return new BasicStroke(.5f);
+  }
+
+  @Override
+  public boolean isAxisTicksLineVisible() {
+
+    return false;
+  }
+
+  @Override
+  public boolean isAxisTicksMarksVisible() {
+
+    return false;
+  }
+
+  // Chart Plot Area ///////////////////////////////
+
+  @Override
+  public Color getPlotBorderColor() {
+
+    return ChartColor.BLACK.getColor();
+  }
+
+  @Override
+  public Color getPlotGridLinesColor() {
+
+    return ChartColor.BLACK.getColor();
+  }
+
+  @Override
+  public BasicStroke getPlotGridLinesStroke() {
+
+    return new BasicStroke(
+        .5f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND, 10.0f, new float[] {1f, 3.0f}, 0.0f);
+  }
+
+  @Override
+  public int getPlotMargin() {
+
+    return 3;
+  }
+
+  // Tool Tips ///////////////////////////////
+
+  @Override
+  public Color getToolTipBackgroundColor() {
+
+    return new Color(255, 255, 220);
+  }
+
+  @Override
+  public Color getToolTipBorderColor() {
+
+    return ChartColor.BLACK.getColor();
+  }
+
+  @Override
+  public Color getToolTipHighlightColor() {
+
+    return ChartColor.BLACK.getColor();
+  }
+
+  // Category Charts ///////////////////////////////
+
+  // Pie Charts ///////////////////////////////
+
+  @Override
+  public LabelType getLabelType() {
+
+    return LabelType.Name;
+  }
+
+  // Line, Scatter, Area Charts ///////////////////////////////
+
+  // Error Bars ///////////////////////////////
+
+  // Chart Annotations ///////////////////////////////
+
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/style/theme/Theme.java b/XChart/xchart/src/main/java/org/knowm/xchart/style/theme/Theme.java
new file mode 100644
index 0000000000000000000000000000000000000000..faed020d0bc1bc77093b225ad13172144dad3188
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/style/theme/Theme.java
@@ -0,0 +1,284 @@
+package org.knowm.xchart.style.theme;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Font;
+import org.knowm.xchart.style.PieStyler.LabelType;
+import org.knowm.xchart.style.Styler;
+import org.knowm.xchart.style.Styler.LegendPosition;
+import org.knowm.xchart.style.colors.ChartColor;
+import org.knowm.xchart.style.colors.SeriesColors;
+import org.knowm.xchart.style.lines.SeriesLines;
+import org.knowm.xchart.style.markers.SeriesMarkers;
+
+public interface Theme extends SeriesMarkers, SeriesLines, SeriesColors {
+
+  Font BASE_FONT = new Font(Font.SANS_SERIF, Font.PLAIN, 10);
+  BasicStroke BASE_STROKE = new BasicStroke(1.0f);
+
+  // Chart Style ///////////////////////////////
+
+  Font getBaseFont();
+
+  Color getChartBackgroundColor();
+
+  Color getChartFontColor();
+
+  int getChartPadding();
+
+  // Chart Title ///////////////////////////////
+
+  Font getChartTitleFont();
+
+  boolean isChartTitleVisible();
+
+  boolean isChartTitleBoxVisible();
+
+  Color getChartTitleBoxBackgroundColor();
+
+  Color getChartTitleBoxBorderColor();
+
+  int getChartTitlePadding();
+
+  // Chart Legend ///////////////////////////////
+
+  Font getLegendFont();
+
+  boolean isLegendVisible();
+
+  Color getLegendBackgroundColor();
+
+  Color getLegendBorderColor();
+
+  int getLegendPadding();
+
+  int getLegendSeriesLineLength();
+
+  LegendPosition getLegendPosition();
+
+  // Chart Plot Area ///////////////////////////////
+
+  boolean isPlotGridLinesVisible();
+
+  boolean isPlotGridVerticalLinesVisible();
+
+  boolean isPlotGridHorizontalLinesVisible();
+
+  Color getPlotBackgroundColor();
+
+  Color getPlotBorderColor();
+
+  boolean isPlotBorderVisible();
+
+  Color getPlotGridLinesColor();
+
+  BasicStroke getPlotGridLinesStroke();
+
+  boolean isPlotTicksMarksVisible();
+
+  default double getPlotContentSize() {
+    return .92;
+  }
+
+  int getPlotMargin();
+
+  // Chart Annotations ///////////////////////////////
+
+  default Font getAnnotationTextPanelFont() {
+    return new Font(Font.MONOSPACED, Font.PLAIN, 10);
+  }
+
+  default Color getAnnotationTextPanelFontColor() {
+    return ChartColor.BLACK.getColor();
+  }
+
+  default Color getAnnotationTextPanelBackgroundColor() {
+    return ChartColor.WHITE.getColor();
+  }
+
+  default Color getAnnotationTextPanelBorderColor() {
+    return ChartColor.DARK_GREY.getColor();
+  }
+
+  default int getAnnotationTextPanelPadding() {
+    return 10;
+  }
+
+  default Font getAnnotationTextFont() {
+    return new Font(Font.MONOSPACED, Font.PLAIN, 10);
+  }
+
+  default Color getAnnotationTextFontColor() {
+    return ChartColor.BLACK.getColor();
+  }
+
+  default BasicStroke getAnnotationLineStroke() {
+    return BASE_STROKE;
+  }
+
+  default Color getAnnotationLineColor() {
+    return ChartColor.DARK_GREY.getColor();
+  }
+
+  // Chart Button ///////////////////////////////
+
+  default Color getChartButtonBackgroundColor() {
+    return ChartColor.BLUE.getColor().brighter();
+  }
+
+  default Color getChartButtonBorderColor() {
+    return ChartColor.BLUE.getColor().darker();
+  }
+
+  default Color getChartButtonHoverColor() {
+    return ChartColor.BLUE.getColor();
+  }
+
+  default Color getChartButtonFontColor() {
+    return getChartFontColor();
+  }
+
+  default Font getChartButtonFont() {
+    return getLegendFont();
+  }
+
+  default int getChartButtonMargin() {
+    return 6;
+  }
+
+  // ToolTips ///////////////////////////////
+
+  default boolean isToolTipsEnabled() {
+
+    return false;
+  }
+
+  default Styler.ToolTipType getToolTipType() {
+
+    return Styler.ToolTipType.xAndYLabels;
+  }
+
+  default Font getToolTipFont() {
+
+    return BASE_FONT;
+  }
+
+  default Color getToolTipBackgroundColor() {
+
+    return ChartColor.WHITE.getColor();
+  }
+
+  default Color getToolTipBorderColor() {
+
+    return ChartColor.DARK_GREY.getColor();
+  }
+
+  default Color getToolTipHighlightColor() {
+
+    return ChartColor.LIGHT_GREY.getColor();
+  }
+
+  // Chart Axes ///////////////////////////////
+
+  boolean isXAxisTitleVisible();
+
+  boolean isYAxisTitleVisible();
+
+  Font getAxisTitleFont();
+
+  boolean isXAxisTicksVisible();
+
+  boolean isYAxisTicksVisible();
+
+  Font getAxisTickLabelsFont();
+
+  int getAxisTickMarkLength();
+
+  int getAxisTickPadding();
+
+  Color getAxisTickMarksColor();
+
+  BasicStroke getAxisTickMarksStroke();
+
+  Color getAxisTickLabelsColor();
+
+  boolean isAxisTicksLineVisible();
+
+  boolean isAxisTicksMarksVisible();
+
+  int getAxisTitlePadding();
+
+  int getXAxisTickMarkSpacingHint();
+
+  int getYAxisTickMarkSpacingHint();
+
+  // Cursor ///////////////////////////////
+
+  boolean isCursorEnabled();
+
+  Color getCursorColor();
+
+  float getCursorSize();
+
+  Font getCursorFont();
+
+  Color getCursorFontColor();
+
+  Color getCursorBackgroundColor();
+
+  // Zoom /////////////////////////////////////
+
+  default boolean isZoomEnabled() {
+    return false;
+  }
+
+  // Bar Charts ///////////////////////////////
+
+  double getAvailableSpaceFill();
+
+  boolean isOverlapped();
+
+  // Pie Charts ///////////////////////////////
+
+  boolean isCircular();
+
+  double getStartAngleInDegrees();
+
+  Font getPieFont();
+
+  double getLabelsDistance();
+
+  LabelType getLabelType();
+
+  boolean setForceAllLabelsVisible();
+
+  double getDonutThickness();
+
+  boolean isSumVisible();
+
+  Font getSumFont();
+
+  // Line, Scatter, Area Charts ///////////////////////////////
+
+  int getMarkerSize();
+
+  // Error Bars ///////////////////////////////
+
+  Color getErrorBarsColor();
+
+  boolean isErrorBarsColorSeriesColor();
+
+  // Labels (pie charts, bar charts)
+
+  default Color getLabelsFontColorAutomaticDark() {
+    return Color.BLACK;
+  }
+
+  default Color getLabelsFontColorAutomaticLight() {
+    return Color.WHITE;
+  }
+
+  default boolean isLabelsFontColorAutomaticEnabled() {
+    return true;
+  }
+}
diff --git a/XChart/xchart/src/main/java/org/knowm/xchart/style/theme/XChartTheme.java b/XChart/xchart/src/main/java/org/knowm/xchart/style/theme/XChartTheme.java
new file mode 100644
index 0000000000000000000000000000000000000000..4f757cd5131297817b00eefd1cffa32ab9558726
--- /dev/null
+++ b/XChart/xchart/src/main/java/org/knowm/xchart/style/theme/XChartTheme.java
@@ -0,0 +1,85 @@
+package org.knowm.xchart.style.theme;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import org.knowm.xchart.style.colors.ChartColor;
+import org.knowm.xchart.style.colors.XChartSeriesColors;
+import org.knowm.xchart.style.lines.XChartSeriesLines;
+import org.knowm.xchart.style.markers.Marker;
+import org.knowm.xchart.style.markers.XChartSeriesMarkers;
+
+public class XChartTheme extends AbstractBaseTheme {
+
+  // Chart Style ///////////////////////////////
+
+  @Override
+  public Color getChartBackgroundColor() {
+
+    return ChartColor.GREY.getColor();
+  }
+
+  // SeriesMarkers, SeriesLines, SeriesColors ///////////////////////////////
+
+  @Override
+  public Color[] getSeriesColors() {
+
+    return new XChartSeriesColors().getSeriesColors();
+  }
+
+  @Override
+  public Marker[] getSeriesMarkers() {
+
+    return new XChartSeriesMarkers().getSeriesMarkers();
+  }
+
+  @Override
+  public BasicStroke[] getSeriesLines() {
+
+    return new XChartSeriesLines().getSeriesLines();
+  }
+
+  // Chart Title ///////////////////////////////
+
+  @Override
+  public boolean isChartTitleBoxVisible() {
+
+    return false;
+  }
+
+  @Override
+  public Color getChartTitleBoxBackgroundColor() {
+
+    return ChartColor.GREY.getColor();
+  }
+
+  @Override
+  public Color getChartTitleBoxBorderColor() {
+
+    return ChartColor.GREY.getColor();
+  }
+
+  // Chart Legend ///////////////////////////////
+
+  // Chart Axes ///////////////////////////////
+
+  // Chart Plot Area ///////////////////////////////
+
+  @Override
+  public boolean isPlotTicksMarksVisible() {
+
+    return false;
+  }
+
+  // Tool Tips ///////////////////////////////
+
+  // Category Charts ///////////////////////////////
+
+  // Pie Charts ///////////////////////////////
+
+  // Line, Scatter, Area Charts ///////////////////////////////
+
+  // Error Bars ///////////////////////////////
+
+  // Chart Annotations ///////////////////////////////
+
+}
diff --git a/XChart/xchart/src/test/java/org/knowm/xchart/AnnotationLineTest.java b/XChart/xchart/src/test/java/org/knowm/xchart/AnnotationLineTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..988aa60d99e2e767e5e4edbf476b71e2b5d211ec
--- /dev/null
+++ b/XChart/xchart/src/test/java/org/knowm/xchart/AnnotationLineTest.java
@@ -0,0 +1,30 @@
+package org.knowm.xchart;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import org.junit.jupiter.api.Test;
+
+public class AnnotationLineTest {
+
+  @Test
+  public void annotationLineShouldNotSpanOverWholeWidth() throws IOException {
+    // given
+    double[] xData = new double[] {0.0, 1.0, 2.0};
+    double[] yData = new double[] {2.0, 1.0, 0.0};
+    XYChart chart = QuickChart.getChart("Sample Chart", "X", "Y", "y(x)", xData, yData);
+
+    // when
+    AnnotationLine annotation = new AnnotationLine(0.5d, false, false);
+    chart.addAnnotation(annotation);
+    OutputStream output = new ByteArrayOutputStream();
+    BitmapEncoder.saveBitmap(chart, output, BitmapEncoder.BitmapFormat.PNG);
+
+    // test
+    assertTrue(annotation.getBounds().getX() > 50);
+    assertTrue(annotation.getBounds().getX() > 50);
+    assertTrue(annotation.getBounds().getWidth() < 500);
+  }
+}
diff --git a/XChart/xchart/src/test/java/org/knowm/xchart/BitmapEncoderTest.java b/XChart/xchart/src/test/java/org/knowm/xchart/BitmapEncoderTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..68d74f2167046cabca6bad1e96175d51caa1c119
--- /dev/null
+++ b/XChart/xchart/src/test/java/org/knowm/xchart/BitmapEncoderTest.java
@@ -0,0 +1,26 @@
+package org.knowm.xchart;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+
+public class BitmapEncoderTest {
+
+    @Test
+    public void testAddFileExtension() {
+        String fileName1 = "image";
+        String fileName2 = "image.png";
+        String fileName3 = "image.PNG";
+
+        for (String s : Arrays.asList(fileName1, fileName2, fileName3)) {
+            assertEquals("image.png", BitmapEncoder.addFileExtension(s, BitmapEncoder.BitmapFormat.PNG));
+        }
+        assertEquals("z.bmp", BitmapEncoder.addFileExtension("z", BitmapEncoder.BitmapFormat.BMP));
+        assertEquals("asdf.bmp", BitmapEncoder.addFileExtension("asdf", BitmapEncoder.BitmapFormat.BMP));
+        assertEquals(".bmp", BitmapEncoder.addFileExtension(".bmp", BitmapEncoder.BitmapFormat.BMP));
+        assertEquals(".bmp", BitmapEncoder.addFileExtension(".BmP", BitmapEncoder.BitmapFormat.BMP));
+    }
+}
diff --git a/XChart/xchart/src/test/java/org/knowm/xchart/CategoryChartTest.java b/XChart/xchart/src/test/java/org/knowm/xchart/CategoryChartTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..2b82be79b4f833456f0cac29d50d45e7e7f045ce
--- /dev/null
+++ b/XChart/xchart/src/test/java/org/knowm/xchart/CategoryChartTest.java
@@ -0,0 +1,252 @@
+package org.knowm.xchart;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.knowm.xchart.style.Styler.ChartTheme.GGPlot2;
+import static org.knowm.xchart.style.Styler.ChartTheme.XChart;
+
+import java.util.*;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.knowm.xchart.custom.CustomGraphic;
+import org.knowm.xchart.custom.CustomTheme;
+import org.knowm.xchart.internal.series.Series;
+import org.knowm.xchart.style.Styler;
+
+public class CategoryChartTest {
+
+  private CategoryChart chart;
+
+  @BeforeEach
+  void setUp() {
+    chart = new CategoryChart(800, 600, GGPlot2);
+  }
+
+  @Test
+  void constructor() {
+    CategoryChartBuilder builder =
+        new CategoryChartBuilder()
+            .width(800)
+            .height(600)
+            .theme(Styler.ChartTheme.GGPlot2)
+            .title("CategoryChart")
+            .xAxisTitle("x-axis")
+            .yAxisTitle("y-axis");
+
+    assertAll(
+        () -> assertDoesNotThrow(() -> new CategoryChart(800, 600)),
+        () -> assertDoesNotThrow(() -> new CategoryChart(800, 600, new CustomTheme())),
+        () -> assertDoesNotThrow(() -> new CategoryChart(800, 600, XChart)),
+        () -> assertDoesNotThrow(() -> new CategoryChart(builder)));
+  }
+
+  @Test
+  void alreadyContainsSeriesName() {
+    assertThatThrownBy(
+            () -> {
+              chart.addSeries(
+                  "a",
+                  Arrays.asList(1, 2, 3, 4, 5),
+                  Arrays.asList(10, 2, 30, 40, 50),
+                  Arrays.asList(1, 3, 2, 1, 2));
+
+              chart.addSeries(
+                  "a", Arrays.asList("A", "B", "C", "D", "E"), Arrays.asList(10, 25, 30., 4, 5));
+            })
+        .isInstanceOf(IllegalArgumentException.class)
+        .hasMessageMatching(
+            "Series name >a< has already been used. Use unique names for each series!!!");
+  }
+
+  @Test
+  void yDataIsNull() {
+    assertThatThrownBy(
+            () -> {
+              chart.addSeries(
+                  "a", Arrays.asList("A", "B", "C", "D", "E"), null, Arrays.asList(1, 2, 3, 4, 5));
+            })
+        .isInstanceOf(IllegalArgumentException.class)
+        .hasMessageMatching("Y-Axis data cannot be null!!!");
+  }
+
+  @Test
+  void yDataIsEmpty() {
+    assertThatThrownBy(
+            () -> {
+              chart.addSeries(
+                  "a",
+                  Arrays.asList("A", "B", "C", "D", "E"),
+                  Arrays.asList(),
+                  Arrays.asList(1, 2, 3, 4, 5));
+            })
+        .isInstanceOf(IllegalArgumentException.class)
+        .hasMessageMatching("Y-Axis data cannot be empty!!!");
+  }
+
+  @Test
+  void xDataIsNull() {
+    assertThatThrownBy(
+            () -> {
+              chart.addSeries(
+                  "a",
+                  Arrays.asList(),
+                  Arrays.asList(1, 2, 3, 4, 5),
+                  Arrays.asList(10, 20, 30, 40, 50));
+            })
+        .isInstanceOf(IllegalArgumentException.class)
+        .hasMessageMatching("X-Axis data cannot be empty!!!");
+  }
+
+  @Test
+  void errorBarSizeIsNotEqualToYDataSize() {
+    assertThatThrownBy(
+            () -> {
+              chart.addSeries(
+                  "a",
+                  Arrays.asList("A", "B", "C", "D", "E"),
+                  Arrays.asList(1, 2),
+                  Arrays.asList(1, 3, 2, 1, 2));
+            })
+        .isInstanceOf(IllegalArgumentException.class)
+        .hasMessageMatching("Error bars and Y-Axis sizes are not the same!!!");
+  }
+
+  @Test
+  void xDataSizeIsNotEqualToYDataSize() {
+    assertThatThrownBy(
+            () -> {
+              chart.addSeries(
+                  "a",
+                  Arrays.asList("A", "B", "C", "D"),
+                  Arrays.asList(1, 2, 3, 4, 5),
+                  Arrays.asList(1, 3, 2, 1, 2));
+            })
+        .isInstanceOf(IllegalArgumentException.class)
+        .hasMessageMatching("X and Y-Axis sizes are not the same!!!");
+  }
+
+  @Test
+  void checkAddSeries() {
+    CategorySeries series =
+        chart.addSeries(
+            "fruit",
+            Arrays.asList("Blue", "Red", "Green", "Yellow", "Orange"),
+            Arrays.asList(-40, 40.8, 20, 60, 60),
+            Arrays.asList(3, 3, 4, 3, 5));
+
+    assertAll(
+        () -> assertThat(series.getName()).isEqualTo("fruit"),
+        () -> assertThat(series.getxAxisDataType()).isEqualTo(Series.DataType.String),
+        () ->
+            assertThat(series.getXData())
+                .isEqualTo(Arrays.asList("Blue", "Red", "Green", "Yellow", "Orange")),
+        () -> assertThat(series.getYData()).isEqualTo(Arrays.asList(-40, 40.8, 20, 60, 60)),
+        () -> assertThat(series.getExtraValues()).isEqualTo(Arrays.asList(3, 3, 4, 3, 5)),
+        () -> assertThat(series.getYData()).hasSize(5));
+  }
+
+  @Test
+  void updateNonExistentSeries() {
+    chart.addSeries(
+        "fruit",
+        Arrays.asList("Blue", "Red", "Green", "Yellow", "Orange"),
+        Arrays.asList(-40, 30, 20, 60, 60));
+    chart.addSeries(
+        "food",
+        Arrays.asList("Blue", "Red", "Green", "Yellow", "Orange"),
+        Arrays.asList(50, 10, -20, 40, 60));
+
+    assertThatThrownBy(
+            () -> {
+              chart.updateCategorySeries("a", Arrays.asList(1, 2, 3, 4, 5), null, null);
+            })
+        .isInstanceOf(IllegalArgumentException.class)
+        .hasMessageMatching("Series name >a< not found!!!");
+  }
+
+  @Test
+  void updateXData() {
+    CategorySeries fruit =
+        chart.addSeries(
+            "fruit",
+            Arrays.asList("Blue", "Red", "Green", "Yellow", "Orange"),
+            Arrays.asList(-40, 30, 20, 60, 60));
+
+    chart.updateCategorySeries(
+        "fruit", Arrays.asList("a", "b", "c", "d", "e"), Arrays.asList(-40, 30, 20, 60, 60), null);
+
+    assertThat(fruit.getXData()).isEqualTo(Arrays.asList("a", "b", "c", "d", "e"));
+  }
+
+  @Test
+  void updateYData() {
+    CategorySeries fruit =
+        chart.addSeries(
+            "fruit",
+            Arrays.asList("Blue", "Red", "Green", "Yellow", "Orange"),
+            Arrays.asList(-40, 30, 20, 60, 60));
+
+    chart.updateCategorySeries(
+        "fruit",
+        Arrays.asList("Blue", "Red", "Green", "Yellow", "Orange"),
+        Arrays.asList(50, 10, -20, 40, 60),
+        null);
+
+    assertThat(fruit.getYData()).isEqualTo(Arrays.asList(50, 10, -20, 40, 60));
+  }
+
+  @Test
+  void updateErrorBar() {
+    CategorySeries fruit =
+        chart.addSeries(
+            "fruit",
+            Arrays.asList("Blue", "Red", "Green", "Yellow", "Orange"),
+            Arrays.asList(-40, 30, 20, 60, 60),
+            Arrays.asList(5, 5, 10, 5, 5));
+
+    chart.updateCategorySeries(
+        "fruit",
+        Arrays.asList("Blue", "Red", "Green", "Yellow", "Orange"),
+        Arrays.asList(-40, 30, 20, 60, 60),
+        Arrays.asList(3, 1, 2, 1, 2));
+
+    assertThat(fruit.getExtraValues()).isEqualTo(Arrays.asList(3, 1, 2, 1, 2));
+  }
+
+  @Test
+  void automaticallyCreatedWhenXDataIsNull() {
+    CategorySeries fruit =
+        chart.addSeries(
+            "fruit",
+            Arrays.asList("Blue", "Red", "Green", "Yellow", "Orange"),
+            Arrays.asList(-40, 30, 20, 60, 60));
+
+    chart.updateCategorySeries("fruit", null, Arrays.asList(-40, 30, 20, 60, 60), null);
+
+    assertThat(fruit.getXData()).isEqualTo(Arrays.asList(1, 2, 3, 4, 5));
+  }
+
+  @Test
+  void paint() {
+    chart.addSeries(
+        "fruit",
+        Arrays.asList("Blue", "Red", "Green", "Yellow", "Orange"),
+        Arrays.asList(-40, 30, 20, 60, 60));
+    chart.addSeries(
+        "food",
+        Arrays.asList("Blue", "Red", "Green", "Yellow", "Orange"),
+        Arrays.asList(-40, 30, 20, 60, 60));
+
+    chart.paint(new CustomGraphic(), 20, 20);
+
+    for (CategorySeries series : chart.getSeriesMap().values()) {
+      CategorySeries.CategorySeriesRenderStyle seriesType =
+          series.getChartCategorySeriesRenderStyle();
+      if (seriesType != null) {
+        assertThat(series.getChartCategorySeriesRenderStyle())
+            .isEqualTo(chart.getStyler().getDefaultSeriesRenderStyle());
+      }
+    }
+  }
+}
diff --git a/XChart/xchart/src/test/java/org/knowm/xchart/HistogramTest.java b/XChart/xchart/src/test/java/org/knowm/xchart/HistogramTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..b214754d9bded31226deb2735fa38610849929c6
--- /dev/null
+++ b/XChart/xchart/src/test/java/org/knowm/xchart/HistogramTest.java
@@ -0,0 +1,34 @@
+package org.knowm.xchart;
+
+import static org.assertj.core.api.Assertions.*;
+
+import java.util.Arrays;
+import org.junit.jupiter.api.Test;
+
+public class HistogramTest {
+
+  @Test
+  public void test1() {
+
+    Histogram histogram = new Histogram(Arrays.asList(1, 2, 3, 4, 5, 6), 2, 0, 4);
+
+    assertThat(histogram.getMax()).isEqualTo(4.0);
+    assertThat(histogram.getMin()).isEqualTo(0.0);
+    assertThat(histogram.getNumBins()).isEqualTo(2);
+    assertThat(histogram.getyAxisData().get(0) + histogram.getyAxisData().get(1)).isEqualTo(4);
+
+    // Chart chart = new ChartBuilder().chartType(ChartType.Bar).width(800).height(600).build();
+    // chart.addSeries("histogram 1", histogram.getxAxisData(), histogram.getyAxisData());
+    // new SwingWrapper(chart).displayChart();
+  }
+
+  @Test
+  public void testNegetiveValues() {
+
+    Histogram histogram = new Histogram(Arrays.asList(-1, -2, -3, -4, -5, -6), 3);
+
+    assertThat(histogram.getMax()).isEqualTo(-1);
+    assertThat(histogram.getMin()).isEqualTo(-6);
+    assertThat(histogram.getNumBins()).isEqualTo(3);
+  }
+}
diff --git a/XChart/xchart/src/test/java/org/knowm/xchart/VectorGraphicsEncoderTest.java b/XChart/xchart/src/test/java/org/knowm/xchart/VectorGraphicsEncoderTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..3db8c032f39facc17f23d00f5a6d139845d6c131
--- /dev/null
+++ b/XChart/xchart/src/test/java/org/knowm/xchart/VectorGraphicsEncoderTest.java
@@ -0,0 +1,42 @@
+package org.knowm.xchart;
+
+import static org.assertj.core.api.Assertions.*;
+
+import org.junit.jupiter.api.Test;
+import org.knowm.xchart.VectorGraphicsEncoder.VectorGraphicsFormat;
+
+public class VectorGraphicsEncoderTest {
+
+  @Test
+  public void testAddFileExtension() {
+
+    for (VectorGraphicsFormat format : VectorGraphicsFormat.values()) {
+
+      // test -> test.svg
+      assertThat(VectorGraphicsEncoder.addFileExtension("test", format))
+          .isEqualTo(String.format("test.%s", format.toString().toLowerCase()));
+
+      // test.svg -> test.svg
+      assertThat(
+              VectorGraphicsEncoder.addFileExtension(
+                  String.format("test.%s", format.toString().toLowerCase()), format))
+          .isEqualTo(String.format("test.%s", format.toString().toLowerCase()));
+
+      // test.svgsvg -> test.svgsvg.svg
+      assertThat(
+              VectorGraphicsEncoder.addFileExtension(
+                  String.format("test.%1$s%1$s", format.toString().toLowerCase()), format))
+          .isEqualTo(String.format("test.%1$s%1$s.%1$s", format.toString().toLowerCase()));
+
+      // test.svg.svg -> test.svg.svg
+      assertThat(
+              VectorGraphicsEncoder.addFileExtension(
+                  String.format("test.%1$s.%1$s", format.toString().toLowerCase()), format))
+          .isEqualTo(String.format("test.%1$s.%1$s", format.toString().toLowerCase()));
+
+      // test.txt -> test.txt.svg
+      assertThat(VectorGraphicsEncoder.addFileExtension("test.txt", format))
+          .isEqualTo(String.format("test.txt.%1$s", format.toString().toLowerCase()));
+    }
+  }
+}
diff --git a/XChart/xchart/src/test/java/org/knowm/xchart/XYChartTest.java b/XChart/xchart/src/test/java/org/knowm/xchart/XYChartTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..8245a1057ed2b7be7bd20a4fa4c2fa7b053e3520
--- /dev/null
+++ b/XChart/xchart/src/test/java/org/knowm/xchart/XYChartTest.java
@@ -0,0 +1,29 @@
+package org.knowm.xchart;
+
+import java.io.ByteArrayOutputStream;
+import java.security.DigestOutputStream;
+import java.security.MessageDigest;
+import org.junit.jupiter.api.Disabled;
+
+public class XYChartTest {
+  private static final String digestType = "md5";
+
+  // https://github.com/knowm/XChart/issues/799
+  @Disabled // because the issue is not fixed yet
+  public void issue799() throws Exception {
+    // given
+    double[] xData = new double[] {0.0, 1.0, 2.0};
+    double[] yData = new double[] {2.0, 1.0, 0.0};
+    XYChart chart = QuickChart.getChart("Sample Chart", "X", "Y", "y(x)", xData, yData);
+    chart.getStyler().setyAxisTickLabelsFormattingFunction(yValue -> "1");
+
+    // when
+    DigestOutputStream output =
+        new DigestOutputStream(new ByteArrayOutputStream(), MessageDigest.getInstance(digestType));
+    BitmapEncoder.saveBitmap(chart, output, BitmapEncoder.BitmapFormat.PNG);
+    output.close();
+
+    // test
+    // finishes
+  }
+}
diff --git a/XChart/xchart/src/test/java/org/knowm/xchart/custom/CustomGraphic.java b/XChart/xchart/src/test/java/org/knowm/xchart/custom/CustomGraphic.java
new file mode 100644
index 0000000000000000000000000000000000000000..bbc5864cc345eb90c059460c3a5a18c7ea51a30b
--- /dev/null
+++ b/XChart/xchart/src/test/java/org/knowm/xchart/custom/CustomGraphic.java
@@ -0,0 +1,293 @@
+package org.knowm.xchart.custom;
+
+import java.awt.*;
+import java.awt.font.FontRenderContext;
+import java.awt.font.GlyphVector;
+import java.awt.geom.AffineTransform;
+import java.awt.image.BufferedImage;
+import java.awt.image.BufferedImageOp;
+import java.awt.image.ImageObserver;
+import java.awt.image.RenderedImage;
+import java.awt.image.renderable.RenderableImage;
+import java.text.AttributedCharacterIterator;
+import java.util.Map;
+
+public class CustomGraphic extends Graphics2D {
+  @Override
+  public void draw(Shape s) {}
+
+  @Override
+  public boolean drawImage(Image img, AffineTransform xform, ImageObserver obs) {
+    return false;
+  }
+
+  @Override
+  public void drawImage(BufferedImage img, BufferedImageOp op, int x, int y) {}
+
+  @Override
+  public void drawRenderedImage(RenderedImage img, AffineTransform xform) {}
+
+  @Override
+  public void drawRenderableImage(RenderableImage img, AffineTransform xform) {}
+
+  @Override
+  public void drawString(String str, int x, int y) {}
+
+  @Override
+  public void drawString(String str, float x, float y) {}
+
+  @Override
+  public void drawString(AttributedCharacterIterator iterator, int x, int y) {}
+
+  @Override
+  public boolean drawImage(Image img, int x, int y, ImageObserver observer) {
+    return false;
+  }
+
+  @Override
+  public boolean drawImage(Image img, int x, int y, int width, int height, ImageObserver observer) {
+    return false;
+  }
+
+  @Override
+  public boolean drawImage(Image img, int x, int y, Color bgcolor, ImageObserver observer) {
+    return false;
+  }
+
+  @Override
+  public boolean drawImage(
+      Image img, int x, int y, int width, int height, Color bgcolor, ImageObserver observer) {
+    return false;
+  }
+
+  @Override
+  public boolean drawImage(
+      Image img,
+      int dx1,
+      int dy1,
+      int dx2,
+      int dy2,
+      int sx1,
+      int sy1,
+      int sx2,
+      int sy2,
+      ImageObserver observer) {
+    return false;
+  }
+
+  @Override
+  public boolean drawImage(
+      Image img,
+      int dx1,
+      int dy1,
+      int dx2,
+      int dy2,
+      int sx1,
+      int sy1,
+      int sx2,
+      int sy2,
+      Color bgcolor,
+      ImageObserver observer) {
+    return false;
+  }
+
+  @Override
+  public void dispose() {}
+
+  @Override
+  public void drawString(AttributedCharacterIterator iterator, float x, float y) {}
+
+  @Override
+  public void drawGlyphVector(GlyphVector g, float x, float y) {}
+
+  @Override
+  public void fill(Shape s) {}
+
+  @Override
+  public boolean hit(Rectangle rect, Shape s, boolean onStroke) {
+    return false;
+  }
+
+  @Override
+  public GraphicsConfiguration getDeviceConfiguration() {
+    return null;
+  }
+
+  @Override
+  public void setComposite(Composite comp) {}
+
+  @Override
+  public void setPaint(Paint paint) {}
+
+  @Override
+  public void setStroke(Stroke s) {}
+
+  @Override
+  public void setRenderingHint(RenderingHints.Key hintKey, Object hintValue) {}
+
+  @Override
+  public Object getRenderingHint(RenderingHints.Key hintKey) {
+    return null;
+  }
+
+  @Override
+  public void setRenderingHints(Map<?, ?> hints) {}
+
+  @Override
+  public void addRenderingHints(Map<?, ?> hints) {}
+
+  @Override
+  public RenderingHints getRenderingHints() {
+    return null;
+  }
+
+  @Override
+  public Graphics create() {
+    return null;
+  }
+
+  @Override
+  public void translate(int x, int y) {}
+
+  @Override
+  public Color getColor() {
+    return null;
+  }
+
+  @Override
+  public void setColor(Color c) {}
+
+  @Override
+  public void setPaintMode() {}
+
+  @Override
+  public void setXORMode(Color c1) {}
+
+  @Override
+  public Font getFont() {
+    return null;
+  }
+
+  @Override
+  public void setFont(Font font) {}
+
+  @Override
+  public FontMetrics getFontMetrics(Font f) {
+    return null;
+  }
+
+  @Override
+  public Rectangle getClipBounds() {
+    return null;
+  }
+
+  @Override
+  public void clipRect(int x, int y, int width, int height) {}
+
+  @Override
+  public void setClip(int x, int y, int width, int height) {}
+
+  @Override
+  public Shape getClip() {
+    return null;
+  }
+
+  @Override
+  public void setClip(Shape clip) {}
+
+  @Override
+  public void copyArea(int x, int y, int width, int height, int dx, int dy) {}
+
+  @Override
+  public void drawLine(int x1, int y1, int x2, int y2) {}
+
+  @Override
+  public void fillRect(int x, int y, int width, int height) {}
+
+  @Override
+  public void clearRect(int x, int y, int width, int height) {}
+
+  @Override
+  public void drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) {}
+
+  @Override
+  public void fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) {}
+
+  @Override
+  public void drawOval(int x, int y, int width, int height) {}
+
+  @Override
+  public void fillOval(int x, int y, int width, int height) {}
+
+  @Override
+  public void drawArc(int x, int y, int width, int height, int startAngle, int arcAngle) {}
+
+  @Override
+  public void fillArc(int x, int y, int width, int height, int startAngle, int arcAngle) {}
+
+  @Override
+  public void drawPolyline(int[] xPoints, int[] yPoints, int nPoints) {}
+
+  @Override
+  public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints) {}
+
+  @Override
+  public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints) {}
+
+  @Override
+  public void translate(double tx, double ty) {}
+
+  @Override
+  public void rotate(double theta) {}
+
+  @Override
+  public void rotate(double theta, double x, double y) {}
+
+  @Override
+  public void scale(double sx, double sy) {}
+
+  @Override
+  public void shear(double shx, double shy) {}
+
+  @Override
+  public void transform(AffineTransform Tx) {}
+
+  @Override
+  public void setTransform(AffineTransform Tx) {}
+
+  @Override
+  public AffineTransform getTransform() {
+    return null;
+  }
+
+  @Override
+  public Paint getPaint() {
+    return null;
+  }
+
+  @Override
+  public Composite getComposite() {
+    return null;
+  }
+
+  @Override
+  public void setBackground(Color color) {}
+
+  @Override
+  public Color getBackground() {
+    return null;
+  }
+
+  @Override
+  public Stroke getStroke() {
+    return null;
+  }
+
+  @Override
+  public void clip(Shape s) {}
+
+  @Override
+  public FontRenderContext getFontRenderContext() {
+    return null;
+  }
+}
diff --git a/XChart/xchart/src/test/java/org/knowm/xchart/custom/CustomSeriesColors.java b/XChart/xchart/src/test/java/org/knowm/xchart/custom/CustomSeriesColors.java
new file mode 100644
index 0000000000000000000000000000000000000000..e266e6d9f02bddb4e072e89cff01ae5f374d6873
--- /dev/null
+++ b/XChart/xchart/src/test/java/org/knowm/xchart/custom/CustomSeriesColors.java
@@ -0,0 +1,21 @@
+package org.knowm.xchart.custom;
+
+import java.awt.Color;
+import org.knowm.xchart.style.colors.SeriesColors;
+
+public class CustomSeriesColors implements SeriesColors {
+  public static final Color GREEN = new Color(0, 205, 0, 180);
+  public static final Color RED = new Color(205, 0, 0, 180);
+  public static final Color BLACK = new Color(0, 0, 0, 180);
+
+  private final Color[] seriesColors;
+
+  public CustomSeriesColors() {
+    seriesColors = new Color[] {GREEN, RED, BLACK};
+  }
+
+  @Override
+  public Color[] getSeriesColors() {
+    return seriesColors;
+  }
+}
diff --git a/XChart/xchart/src/test/java/org/knowm/xchart/custom/CustomTheme.java b/XChart/xchart/src/test/java/org/knowm/xchart/custom/CustomTheme.java
new file mode 100644
index 0000000000000000000000000000000000000000..07fd91ecce85065ff1e1785a3628633604ff8136
--- /dev/null
+++ b/XChart/xchart/src/test/java/org/knowm/xchart/custom/CustomTheme.java
@@ -0,0 +1,89 @@
+package org.knowm.xchart.custom;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Font;
+import org.knowm.xchart.style.colors.ChartColor;
+import org.knowm.xchart.style.lines.XChartSeriesLines;
+import org.knowm.xchart.style.markers.Marker;
+import org.knowm.xchart.style.markers.XChartSeriesMarkers;
+import org.knowm.xchart.style.theme.AbstractBaseTheme;
+
+public class CustomTheme extends AbstractBaseTheme {
+  @Override
+  public Font getBaseFont() {
+    return new Font(Font.SERIF, Font.PLAIN, 10);
+  }
+
+  @Override
+  public Color getChartBackgroundColor() {
+    return ChartColor.DARK_GREY.getColor();
+  }
+
+  @Override
+  public Color getChartFontColor() {
+    return ChartColor.DARK_GREY.getColor();
+  }
+
+  @Override
+  public int getChartPadding() {
+    return 12;
+  }
+
+  @Override
+  public Color[] getSeriesColors() {
+    return new CustomSeriesColors().getSeriesColors();
+  }
+
+  @Override
+  public Marker[] getSeriesMarkers() {
+    return new XChartSeriesMarkers().getSeriesMarkers();
+  }
+
+  @Override
+  public BasicStroke[] getSeriesLines() {
+    return new XChartSeriesLines().getSeriesLines();
+  }
+
+  @Override
+  public Font getChartTitleFont() {
+    return getBaseFont().deriveFont(Font.BOLD).deriveFont(18f);
+  }
+
+  @Override
+  public boolean isChartTitleBoxVisible() {
+    return false;
+  }
+
+  @Override
+  public Color getChartTitleBoxBackgroundColor() {
+    return ChartColor.GREY.getColor();
+  }
+
+  @Override
+  public Color getChartTitleBoxBorderColor() {
+    return ChartColor.GREY.getColor();
+  }
+
+  @Override
+  public BasicStroke getAxisTickMarksStroke() {
+    return new BasicStroke(
+        1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 10.0f, new float[] {3.0f, 0.0f}, 0.0f);
+  }
+
+  @Override
+  public boolean isPlotTicksMarksVisible() {
+    return false;
+  }
+
+  @Override
+  public BasicStroke getPlotGridLinesStroke() {
+    return new BasicStroke(
+        0.25f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 10.0f, new float[] {3.0f, 3.0f}, 0.0f);
+  }
+
+  @Override
+  public int getMarkerSize() {
+    return 16;
+  }
+}
diff --git a/XChart/xchart/src/test/java/org/knowm/xchart/internal/UtilsTest.java b/XChart/xchart/src/test/java/org/knowm/xchart/internal/UtilsTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..e174ab3a1067b8e27845ce7a383629808c32c4b9
--- /dev/null
+++ b/XChart/xchart/src/test/java/org/knowm/xchart/internal/UtilsTest.java
@@ -0,0 +1,16 @@
+package org.knowm.xchart.internal;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+public class UtilsTest {
+
+  @Test
+  void addFileExtension() {
+    assertEquals(Utils.addFileExtension("yourchart.png", ".png"), "yourchart.png");
+    assertEquals(Utils.addFileExtension("yourchart.png", ".pn"), "yourchart.png.pn");
+    assertEquals(Utils.addFileExtension("a", ".png"), "a.png");
+    assertEquals(Utils.addFileExtension("a.PNG", ".png"), "a.PNG");
+  }
+}
diff --git a/XChart/xchart/src/test/java/org/knowm/xchart/internal/chartpart/AxisTickCalculatorCategoryTest.java b/XChart/xchart/src/test/java/org/knowm/xchart/internal/chartpart/AxisTickCalculatorCategoryTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..ab0ac6e9ef03194163c844d18c7c04f068e28fe1
--- /dev/null
+++ b/XChart/xchart/src/test/java/org/knowm/xchart/internal/chartpart/AxisTickCalculatorCategoryTest.java
@@ -0,0 +1,62 @@
+package org.knowm.xchart.internal.chartpart;
+
+import static org.assertj.core.api.Assertions.*;
+
+import java.util.Arrays;
+import java.util.List;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.knowm.xchart.internal.series.Series;
+import org.knowm.xchart.style.CategoryStyler;
+
+public class AxisTickCalculatorCategoryTest {
+
+  @Test
+  public void shouldHonorMaxAxisLabelCount() {
+    // given
+    List<String> categories = Arrays.asList("one", "two", "three", "four", "five", "six");
+    CategoryStyler styler = new CategoryStyler();
+    styler.setXAxisMaxLabelCount(3);
+
+    // when
+    AxisTickCalculator_Category calculator =
+        new AxisTickCalculator_Category(
+            Axis.Direction.X, 900, categories, Series.DataType.String, styler);
+
+    // test
+    assertThat(calculator.tickLabels.size()).isEqualTo(3);
+  }
+
+  @Test
+  public void shouldFailIfMaxAxisLabelCountIsOne() {
+    // given
+    List<String> categories = Arrays.asList("one", "two", "three", "four", "five", "six");
+    CategoryStyler styler = new CategoryStyler();
+    styler.setXAxisMaxLabelCount(1);
+
+    // when & test
+    Assertions.assertThrows(
+        IllegalArgumentException.class,
+        () -> {
+          new AxisTickCalculator_Category(
+              Axis.Direction.X, 900, categories, Series.DataType.String, styler);
+        });
+  }
+
+  @Test
+  public void shouldAllowAllLabelsIfThereisEnoughSpace() {
+    // given
+    List<String> categories = Arrays.asList("one", "two", "three", "four", "five", "six");
+    CategoryStyler styler = new CategoryStyler();
+
+    // when
+    AxisTickCalculator_Category calculator =
+        new AxisTickCalculator_Category(
+            Axis.Direction.X, 900, categories, Series.DataType.String, styler);
+
+    // test
+    assertThat(calculator.tickLabels.size()).isEqualTo(6);
+    assertThat(calculator.tickLocations)
+        .isEqualTo(Arrays.asList(105.0, 243.0, 381.0, 519.0, 657.0, 795.0));
+  }
+}
diff --git a/XChart/xchart/src/test/java/org/knowm/xchart/internal/chartpart/AxisTickCalculatorDateTest.java b/XChart/xchart/src/test/java/org/knowm/xchart/internal/chartpart/AxisTickCalculatorDateTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..124b7b0f7123eaa5b472ab1b0580d8892d8a4b67
--- /dev/null
+++ b/XChart/xchart/src/test/java/org/knowm/xchart/internal/chartpart/AxisTickCalculatorDateTest.java
@@ -0,0 +1,38 @@
+package org.knowm.xchart.internal.chartpart;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.awt.*;
+import java.util.Arrays;
+import java.util.Locale;
+import java.util.TimeZone;
+import org.junit.jupiter.api.Test;
+import org.knowm.xchart.style.AxesChartStyler;
+
+public class AxisTickCalculatorDateTest {
+
+  @Test
+  public void shouldHonorMaxAxisLabelCount() {
+    // given
+    AxesChartStyler styler = new AxesChartStyler() {};
+    styler.setTimezone(TimeZone.getTimeZone("UTC"));
+    styler.setDatePattern("yyyy-MM-dd");
+    styler.setLocale(Locale.UK);
+    styler.setPlotContentSize(.900d);
+    styler.setAxisTickLabelsFont(new Font(Font.SERIF, Font.PLAIN, 11));
+
+    long june1 = 1685577600000L;
+    long june2 = 1685664000000L;
+
+    // when
+    AxisTickCalculator_Date calculator =
+        new AxisTickCalculator_Date(Axis.Direction.X, 900, june1, june2, styler);
+
+    // test
+    assertThat(calculator.tickLabels)
+        .isEqualTo(
+            // NOTE: I don't fully understand why would it take dates before June 1 and after June
+            // 2, but hey, this is how it works currently.
+            Arrays.asList("2023-05-31", "2023-06-01", "2023-06-02", "2023-06-03", "2023-06-04"));
+  }
+}
diff --git a/XChart/xchart/src/test/java/org/knowm/xchart/internal/chartpart/RegressionIssue536Test.java b/XChart/xchart/src/test/java/org/knowm/xchart/internal/chartpart/RegressionIssue536Test.java
new file mode 100644
index 0000000000000000000000000000000000000000..85030a64063d23d6179df704ecb615b29a1f1b43
--- /dev/null
+++ b/XChart/xchart/src/test/java/org/knowm/xchart/internal/chartpart/RegressionIssue536Test.java
@@ -0,0 +1,87 @@
+package org.knowm.xchart.internal.chartpart;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.TimeZone;
+import org.junit.jupiter.api.Test;
+import org.knowm.xchart.BitmapEncoder;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYChartBuilder;
+import org.knowm.xchart.XYSeries;
+import org.knowm.xchart.style.Styler;
+import org.knowm.xchart.style.markers.SeriesMarkers;
+
+/** Regression test for <a href="https://github.com/knowm/XChart/issues/536">issue 536</a>. */
+public class RegressionIssue536Test {
+
+  @Test
+  public void issue536RegressionTest() throws Exception {
+
+    String series = "ABC";
+
+    List<Date> x = new ArrayList<>(); // List of dates
+    List<Double> y = new ArrayList<>();
+
+    XYChart chart =
+        new XYChartBuilder()
+            .width(800)
+            .height(720)
+            .theme(Styler.ChartTheme.XChart)
+            .title("XChart")
+            .xAxisTitle("Date")
+            .yAxisTitle("%Diff ")
+            .build();
+    //    chart.getStyler().setPlotBackgroundColor(java.awt.Color.BLACK);
+    chart.getStyler().setPlotMargin(0);
+    chart.getStyler().setLegendPosition(Styler.LegendPosition.OutsideE);
+    chart.getStyler().setXAxisLabelRotation(90);
+    chart.getStyler().setYAxisGroupPosition(1, Styler.YAxisPosition.Right);
+
+    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+    sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
+    chart.getStyler().setTimezone(TimeZone.getTimeZone("UTC"));
+
+    x.add(sdf.parse("2020-10-19")); // dates on X
+    //    System.out.println("x.get(0).getTime() = " + x.get(0).getTime());
+    //    System.out.println("sdf.parse(\"2020-10-19\") = " +
+    // sdf.parse("2020-10-19").toGMTString());
+    x.add(sdf.parse("2020-10-20"));
+    x.add(sdf.parse("2020-10-21"));
+    x.add(sdf.parse("2020-10-22"));
+    x.add(sdf.parse("2020-10-23"));
+    x.add(sdf.parse("2020-10-24"));
+    x.add(sdf.parse("2020-10-25"));
+    x.add(sdf.parse("2020-10-26"));
+    x.add(sdf.parse("2020-10-27"));
+    x.add(sdf.parse("2020-10-28"));
+
+    y.add(2.1);
+    y.add(4.5);
+    y.add(3.2);
+    y.add(5.6);
+    y.add(2.5);
+    y.add(3.8);
+    y.add(5.1);
+    y.add(7.4);
+    y.add(4.8);
+    y.add(2.7);
+
+    XYSeries xyseries = chart.addSeries(series, x, y);
+    xyseries.setMarker(SeriesMarkers.NONE);
+    xyseries.setYAxisGroup(1);
+    byte[] bytes = BitmapEncoder.getBitmapBytes(chart, BitmapEncoder.BitmapFormat.PNG);
+
+    List<String> tickLabels = chart.axisPair.getXAxis().getAxisTickCalculator().getTickLabels();
+
+    assertThat(tickLabels.size()).isEqualTo(13);
+    assertThat(tickLabels.get(0)).isEqualTo("10-18");
+    boolean areAllLabelsUnique =
+        ((AxisTickCalculator_Date) chart.axisPair.getXAxis().getAxisTickCalculator())
+            .areAllTickLabelsUnique(tickLabels);
+    assertThat(areAllLabelsUnique).isEqualTo(true);
+  }
+}
diff --git a/XChart/xchart/src/test/java/org/knowm/xchart/regressiontests/RegressionTestIssue536.java b/XChart/xchart/src/test/java/org/knowm/xchart/regressiontests/RegressionTestIssue536.java
new file mode 100644
index 0000000000000000000000000000000000000000..01ffad8e156fcc8c5e6052715965c7e3b8374f68
--- /dev/null
+++ b/XChart/xchart/src/test/java/org/knowm/xchart/regressiontests/RegressionTestIssue536.java
@@ -0,0 +1,30 @@
+package org.knowm.xchart.regressiontests;
+
+import static java.lang.Double.NaN;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.jupiter.api.Test;
+import org.knowm.xchart.BitmapEncoder;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYSeries;
+import org.knowm.xchart.style.markers.SeriesMarkers;
+
+/** Regression test for <a href="https://github.com/knowm/XChart/issues/536">issue 536</a>. */
+public class RegressionTestIssue536 {
+
+  @Test
+  public void issue546RegressionTest() throws Exception {
+
+    XYChart chart = new XYChart(800, 600);
+
+    List<? extends Number> times = Arrays.asList(1L, 2L, 3L);
+    List<? extends Number> values =
+        times.stream().mapToDouble(x -> NaN).boxed().collect(Collectors.toList());
+    XYSeries series = chart.addSeries("Series", times, values);
+    series.setMarker(SeriesMarkers.NONE);
+
+    byte[] bytes = BitmapEncoder.getBitmapBytes(chart, BitmapEncoder.BitmapFormat.PNG);
+  }
+}
diff --git a/XChart/xchart/src/test/java/org/knowm/xchart/regressiontests/SimplestExampleTest.java b/XChart/xchart/src/test/java/org/knowm/xchart/regressiontests/SimplestExampleTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..882e54073b21148a6f1c79b2a9d239f96e66abfb
--- /dev/null
+++ b/XChart/xchart/src/test/java/org/knowm/xchart/regressiontests/SimplestExampleTest.java
@@ -0,0 +1,60 @@
+package org.knowm.xchart.regressiontests;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+
+import java.awt.Font;
+import java.io.ByteArrayOutputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.DigestOutputStream;
+import java.security.MessageDigest;
+import org.junit.jupiter.api.Disabled;
+import org.knowm.xchart.BitmapEncoder;
+import org.knowm.xchart.QuickChart;
+import org.knowm.xchart.XYChart;
+
+// Disabled because I don't have example chart pngs from all OSes
+@Disabled
+public class SimplestExampleTest {
+
+  @Disabled
+  public void testSimplestExampleStaysTheSame() throws Exception {
+    // given
+    double[] xData = new double[] {0.0, 1.0, 2.0};
+    double[] yData = new double[] {2.0, 1.0, 0.0};
+
+    // when
+    XYChart chart = QuickChart.getChart("Sample Chart", "X", "Y", "y(x)", xData, yData);
+    chart.getStyler().setChartTitleFont(arial());
+    chart.getStyler().setAxisTickLabelsFont(arial());
+    chart.getStyler().setLegendFont(arial());
+    chart.getStyler().setAxisTitleFont(arial());
+    DigestOutputStream output =
+        new DigestOutputStream(new ByteArrayOutputStream(), MessageDigest.getInstance(digestType));
+    BitmapEncoder.saveBitmap(chart, output, BitmapEncoder.BitmapFormat.PNG);
+    output.close();
+
+    // test
+    assertImagesEquals("simplestExample.png", output);
+  }
+
+  static final String digestType = "md5";
+
+  private Font arial() {
+    return new Font("Arial", Font.PLAIN, 14);
+  }
+
+  public void assertImagesEquals(String expectedFileName, DigestOutputStream actual)
+      throws Exception {
+    String path =
+        "/expectedChartRenderings/"
+            + System.getProperty("os.name").replaceAll(" ", "")
+            + "/"
+            + expectedFileName;
+    byte[] expectedBytes = Files.readAllBytes(Paths.get(getClass().getResource(path).toURI()));
+    byte[] expectedDigest = MessageDigest.getInstance(digestType).digest(expectedBytes);
+    byte[] actualDigest = actual.getMessageDigest().digest();
+
+    assertArrayEquals(expectedDigest, actualDigest);
+  }
+}