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 @@ +## [](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); +``` + + + +## 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(); +``` + + + +## 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); + } +}); + +``` + + + +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 + + + +`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 + + + +`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 + + + +`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 + + + +`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 + + + +`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 + + + +`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 + + + +`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 + + + +`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 + + + +`HeatMapChart` take Date, Number or String data types for the X-Axis, Y-Axis. + +## Real-time Java Charts using XChart + + + +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! + + + + + +### 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. + + + +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`: + + + +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: + + + +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); +``` + + + +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); +``` + + + +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(); + + + +## 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 + + + +- 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 +``` + + + +## Bugs + +Please report any bugs or submit feature requests to [XChart's Github issue tracker](https://github.com/knowm/XChart/issues). + +## Continuous Integration + +* [](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<?> + * + * @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<? extends Chart> + * @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<? extends Chart> + * @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<? extends Chart> + * @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); + } +}