Code Monkey home page Code Monkey logo

jfreechart-builder's Introduction

jfreechart-builder

A builder pattern module for working with the jfreechart library.

It's a companion to ChartFactory.java for using a declarative approach to creating complex charts with fewer lines of code.

Features

  • XY time series charts using CombinedDomainXYPlot for data alignment on sub-plots.
  • Stockmarket OHLC candlestick and volume bar charts.
  • A time gap removal solution (see JfreeChart Issue #197).
  • A combined axis sub-plot crosshair synchronization solution.
  • Easy time axis tick format control including a minimalist date format solution.
  • Easy markers and annotations (arrows, boxes, images, lines, polygons, text, titles).
  • Easy color, line style, and gridline control.
  • A Demo app for testing and prototyping.

Note: all charts use a CombinedDomainXYPlot even if there's one plot in order to ease the maintenance of this framework.

In the future, more facilities may be added to leverage more of what jfreechart provides.

Samples

A simple annotated plot

ChartBuilder.get()
  .title("Simple Time Series With Annotations")
  .timeData(timeArray)
  .xyPlot(XYTimeSeriesPlotBuilder.get()
    .series(XYTimeSeriesBuilder.get().name("Amplitude").data(array1).color(Color.BLUE).style(SOLID_LINE))
    .annotation(XYArrowBuilder.get().x(arrowX).y(arrowY).angle(180.0).color(Color.RED).text(arrowTxt))
    .annotation(XYArrowBuilder.get().x(arrowX).y(arrowY).angle(0.0).color(Color.RED))
    .annotation(XYTextBuilder.get().x(arrowX).y(arrowY).color(DARK_GREEN)
        .text("This value!").textPaddingLeft(5).textAlign(TextAnchor.BASELINE_LEFT).angle(90.0)))
  .build();

A multi-plot minute time series chart

Multiple series plots

ChartBuilder.get()

  .title("Multi Plot Minute Time Series")
  .timeData(timeArray)

  .xyPlot(XYTimeSeriesPlotBuilder.get().yAxisName("Values")
    .backgroundColor(Color.DARK_GRAY).axisColor(Color.RED).axisFontColor(Color.BLUE)
    .series(XYTimeSeriesBuilder.get().data(array1).color(Color.YELLOW).style(SOLID_LINE))
    .series(XYTimeSeriesBuilder.get().data(array2).color(Color.RED).style(SOLID_LINE))
    .series(XYTimeSeriesBuilder.get().data(array3).color(Color.GREEN).style(SOLID_LINE))
    .series(XYTimeSeriesBuilder.get().data(array4).color(Color.MAGENTA).style(SOLID_LINE)))

  .xyPlot(XYTimeSeriesPlotBuilder.get().yAxisName("Amplitudes").noGridLines()
    .series(XYTimeSeriesBuilder.get().data(array2).color(Color.BLACK).style(SOLID_LINE))
    .series(XYTimeSeriesBuilder.get().data(array3).color(Color.LIGHT_GRAY).style(SOLID_LINE)))

  .xyPlot(XYTimeSeriesPlotBuilder.get().yAxisName("Series 1")
    .backgroundColor(DARK_GREEN).axisColor(Color.RED).axisFontColor(Color.BLUE)
    .series(XYTimeSeriesBuilder.get().data(array1).color(Color.GREEN).style(SOLID_LINE)))

  .xyPlot(XYTimeSeriesPlotBuilder.get().yAxisName("Series 2")
    .backgroundColor(DARK_RED).axisColor(Color.RED).axisFontColor(Color.BLUE)
    .series(XYTimeSeriesBuilder.get().data(array2).color(Color.RED).style(SOLID_LINE)))

  .xyPlot(XYTimeSeriesPlotBuilder.get().yAxisName("Series 3")
    .backgroundColor(DARK_BLUE).axisColor(Color.RED).axisFontColor(Color.BLUE)
    .series(XYTimeSeriesBuilder.get().data(array3).color(Color.CYAN).style(SOLID_LINE)))

  .build();

A multi-plot minute time series chart

Stock market plots

ChartBuilder.get()

  .title("Stock Chart Time Series With Weekend Gaps, Lines, and Annotations")
  .timeData(timeArray)

  .xyPlot(OhlcPlotBuilder.get().yAxisName("Price").plotWeight(3)
    .series(OhlcSeriesBuilder.get().ohlcv(dohlcv).upColor(Color.WHITE).downColor(Color.RED))
    .series(XYTimeSeriesBuilder.get().name("MA(20)").data(sma20).color(Color.MAGENTA).style(SOLID_LINE))
    .series(XYTimeSeriesBuilder.get().name("MA(50)").data(sma50).color(Color.BLUE).style(SOLID_LINE))
    .series(XYTimeSeriesBuilder.get().name("MA(200)").data(sma200).color(Color.RED).style(SOLID_LINE))
    .annotation(XYArrowBuilder.get().x(stockEventDate).y(stockEventPrice).angle(270.0).color(DARK_GREEN)
      .textAlign(TextAnchor.BOTTOM_CENTER).text(String.format("%.2f", stockEventPrice)))
    .marker(MarkerBuilder.get().horizontal().at(resistanceLevel).color(Color.LIGHT_GRAY).style(SOLID_LINE)))

  .xyPlot(VolumeXYPlotBuilder.get().yAxisName("Volume").yTickFormat(volNumFormat)
    .series(VolumeXYTimeSeriesBuilder.get().ohlcv(dohlcv).upColor(Color.DARK_GRAY).downColor(Color.RED))
    .series(XYTimeSeriesBuilder.get().name("MA(90)").data(volSma90).color(Color.BLUE).style(SOLID_LINE))
    .annotation(XYArrowBuilder.get().x(stockEventDate).y(stockEventVolume).angle(270.0).color(DARK_GREEN)
      .textAlign(TextAnchor.BOTTOM_CENTER).text(String.format("%.0f", stockEventVolume)))
    .marker(MarkerBuilder.get().horizontal().at(volumeLine).color(DARK_GREEN).style(SOLID_LINE)))

  .xyPlot(XYTimeSeriesPlotBuilder.get().yAxisName("Stoch").yAxisRange(0.0, 100.0).yAxisTickSize(50.0)
    .series(XYTimeSeriesBuilder.get().name("K(" + K + ")").data(stoch.getPctK()).color(Color.RED).style(SOLID_LINE))
    .series(XYTimeSeriesBuilder.get().name("D(" + D + ")").data(stoch.getPctD()).color(Color.BLUE).style(SOLID_LINE))
    .marker(MarkerBuilder.get().horizontal().at(80.0).color(Color.BLACK).style(SOLID_LINE))
    .marker(MarkerBuilder.get().horizontal().at(50.0).color(Color.BLUE).style(SOLID_LINE))
    .marker(MarkerBuilder.get().horizontal().at(20.0).color(Color.BLACK).style(SOLID_LINE)))

  .build();

A stock chart time series chart with weekend gaps

Time Gap Removal

Implements a solution for removing visible time gaps where no data exists (like on weekends). Accomplishes this with a family of adapter classes mapping NumberAxis values as indices in time value arrays.

Configured using showTimeGaps(boolean):

ChartBuilder.get()

  .title("Stock Chart Time Series No Gaps for Weekends")
  .showTimeGaps(false)

  ...

A stock chart time series chart with weekend gaps not rendered

Note: the x-axis month label in the gapless time chart currently doesn't always correspond to the first day (or trading day) of the month.

Sub-plot Crosshair Synchronization

ChartCombinedAxisClickDispatcher was created to dispatch JfreeChart ChartMouseEvents to combined axis sub-plots. This leverages JFreeChart's built-in updating of crosshairs and emulates the perpendicular ChartPanel trace lines like a snapshot of them. The demo app has example code but essentially you add a chart mouse listener to the panel:

ChartPanel panel = new ChartPanel();

...

ChartMouseListener clickDispatcher = new ChartCombinedAxisClickDispatcher(panel);

panel.addChartMouseListener(clickDispatcher);

Now a click on any combined axis sub-plot will make all sub-plots get a handleClick() call.

To remove this, pass the same listener object to the remove method:

panel.removeChartMouseListener(clickDispatcher);

ChartCombinedAxisClickDispatcher is extendable for other customizations.

Time Axis Tick Label Formats

You can supply DateFormat instances to render time axis tick labels by calling dateFormat(DateFormat format). You can even implement your own sub-class.

You can also set the vertical label flag to draw them vertically.

ChartBuilder.get()

  .dateFormat( /* supply your DateFormat instance here */ )

  .verticalTickLabels(true)

  ...

Convenience Minimal Tick Format

An optional MinimalDateFormat class is implemented to format dates with month letter(s) on first instance of a new month then only month days until a new month is reached.

Minimal axis date format

Demo App

See the demo-app solution for an interactive demo. Used for development and testing.

To launch it see Testing section further down.

Incorporating into your project

The module is not published to Maven Central so you must build the solution locally and install it in local Maven repositories.

Prerequisites

  • JDK 8 or greater [1] [2] installed.
  • Apache Maven installed.
  • Internet connection for Maven dependency downloads or you add those to your local Maven repo yourself.

Installing source code

git clone <this repo's URL>

Versioning

The major and minor numbers are the same as the jfreechart major and minor to denote compatibility.

The incremental ("patch") number is the monolithic version number of jfreechart-builder.

Branching model

The latest and greatest but unreleased contributions are on the develop branch. These commits give you a preview of what's to come.

Each time develop is merged into main, a version tag is added onto that merge commit.

Each commit to main represents the next released version.

Folder Structure

framework/ contains the builder library code and produces the consumable jfreechart-builder JAR file.

demo-app/ contains the demo app code and produces the launchable jfreechart-builder-demo JAR file.

Build and install the jars into your local Maven repo

Set the desired branch

cd path/to/cloned/repo

git checkout <desired branch or tag>

Build and install everything:

mvn install

Or build and install modules independently:

cd framework
mvn install

cd ../demo-app
mvn install

Note: to build the jars without installing use mvn package instead of mvn install . They'll be in the framework/target/ and demo-app/target/ folders.

Add the jfreechart-builder JAR to a client project

Add this dependency to your project's .pom file:

<dependency>
  <groupId>com.jfcbuilder</groupId>
  <artifactId>jfreechart-builder</artifactId>
  <version>1.5.8</version>
<dependency>

Testing

Run the demo-app from your IDE or launch it from the command line:

java -jar jfreechart-builder-demo.jar

Test Coverage Warning

Testing of jfreechart-builder is limited to manually running the jfreechart-builder-demo application locally on Windows or Linux. It's rarely (if ever) run on both operating systems for the same code changes being merged. As of this writing the author(s)/maintainer(s) have not tested it on MacOS.

There is a reliance on the cross-platform natured promise of Java.

There are currently no runnable or automated unit tests, integration tests, regression tests, and the like.

Testing is done by visual inspection of the jfreechart-builder-demo app user interface.

You should thoroughly test the use of jfreechart-builder in your project and environment to satisfy yourself it does what you need and expect.

If you feel a capability is missing or there's a bug feel free to create an issue or start a discussion thread. Contributions are also welcome (see Contributing below)!

Javadoc

The latest release Javadoc is hosted here.

Generating Javadoc locally

mvn javadoc:javadoc -Dsource=8

Use a browser to open framework/target/site/apidocs/index.html

Alternatively, run the generation script by specifying what version tag to associate with the Javadoc:

./scripts/generate-javadoc.sh v1.5.8

That output will be in target/site/apidocs/javadoc

Thread-safety and garbage collection

No thread-safety measures are deliberately taken. If you require thread-safety then provide deep copies of objects, don't share builders, don't share charts, or you could add synchronization to your business logic.

Generally, primitive data arrays are copied into jfreechart objects. jfreechart-builder will maintain references to other objects passed-in like strings, colors, and drawing strokes. When the builders and charts they produce go out of scope, the objects you provided (and other objects that may be referencing them) should be garbage collected as applicable.

License

jfreechart-builder is not affiliated with the jfreechart project but for compatibility it and the jfreechart-builder-demo app are provided under the terms of the same LGPL 2.1 license.

You should be aware of the contents of the jfreechart-builder JAR file built from this project.

It should contain the compiled .class files only of jfreechart-builder and should not incorporate any from jfreechart, however you must verify its contents to know what the build tools are actually producing.

If you need clarification on the LGPL vs. Java, please see the FSF's tech note about it.

Contributing

Contributions are welcome and will be accepted as the maintainers' time permits.

  • Please use indentations of two spaces (no tabs)
  • Wrap lines at a width of 100 characters.
  • Write good Javadoc at least for interfaces, class descriptions, and public methods.

jfreechart-builder's People

Contributors

matoos32 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

jfreechart-builder's Issues

Add configuration of time-axis tick labels

Discussed in #53

Originally posted by jcobb88 November 8, 2022
The 'ChartBuilder' should offer a method to customize the tick labels, e.g. ChartBuilder#setTickLabelFormatting(SimpleDateFormat format). The format could be stored as a field and called upon the #build() method (e.g. ChartBuilder, line 279).
In this context a #setVerticalTickLabels would also be useful to allow more tick labels on the chart (e.g. call on NumberAxis in line 229 of the ChartBuilder).

Volume plots from VolumeXYPlotBuilder have OHLC style tooltip

Builder plots created using VolumeXYPlotBuilder and VolumeXYTimeSeriesBuilder have mouse-over tooltips on volume bars with Open, High, Low, Close (OHLC) attributes. Should be just a single volume value.

To reproduce see the demo project or use a snippet similar to:

ChartBuilder.get()
    .xyPlot(VolumeXYPlotBuilder.get()
      .yAxisName("Volume").yTickFormat(volNumFormat).gridLines()
      .series(VolumeXYTimeSeriesBuilder.get().ohlcv(dohlcv).upColor(Color.DARK_GRAY).downColor(Color.RED)))
    .build();

Found in v1.5.5 but likely in older versions.

Multi-plot crosshair synchronization

Add crosshair synchronization for multi-plot charts so clicking on one plot will move the crosshair to the same shared coordinate (eg: time axis) on all the other plots.

This will prevent the need for developers to develop their own custom solution each time they want this.

Candlesticks and XY lines have pixel offset from one another

From FIXME in OhlcPlotBuilder.java:

FIXME: Candlestick renderer offsets center of candle from x-axis start pixel. When rendering general xy lines, those line points are drawn at the x-axis start pixel. This causes indicator lines to be misaligned by a few pixels from the OHLC bars and may provide false signals to those looking at charts.

Found in v1.5.6
https://github.com/matoos32/jfreechart-builder/blob/v1.5.6/src/main/java/com/jfcbuilder/builders/OhlcPlotBuilder.java#L279

Since initial implementation:
56640dc#diff-5f7449896404872cdff11efafb7c20a7d63447f210b85aa749e71a76afb70631R238

Needs confirmation if still is or really is an issue.

Java 8 gapless time series chart OutOfMemoryError with XYShapeBuilder

Issue

I get an OutOfMemoryError with XYShapeAnnotation and Arrays.copyOf() when running the demo app's gapless-time annotations case with Java 8 on Ubuntu 20.04.5 LTS.

private static JFreeChart stockChartDailyNoGapsWithAnnotations() {

This happens if it's compiled with JDK 8 or 11 targetting 8 AND launching it with Java 8. If I launch it with Java 11 the issue doesn't occur. If I comment out the demo app's use of XYShapeBuilder it ceases to occur.

Java 8 Version

$ java -version
openjdk version "1.8.0_352"
OpenJDK Runtime Environment (build 1.8.0_352-8u352-ga-1~20.04-b08)
OpenJDK 64-Bit Server VM (build 25.352-b08, mixed mode)

Steps to Reproduce

  1. sudo apt-get install openjdk-8-jdk
  2. sudo apt-get install openjdk-11-jdk
  3. sudo update-alternatives --config java (select JDK 8)
  4. java -version (confirm Java 8)
  5. cd path/to/jfreechart-builder/repo
  6. mvn clean install
  7. cd path/to/.m2/repository/com/jfcbuilder/jfreechart-builder-demo/VERSION
  8. java -jar jfreechart-builder-demo-VERSION.jar
  9. From the Demonstrations drop-down pick the Gapless | Annotations and Markers one.
  10. Observe the app freezes then shows an eventual out of memory exception.
  11. Use the alternatives to switch to Java 11
  12. Launch the JAR again and pick the same annotations demo.
  13. Observe the app doesn't freeze and doesn't throw the exception.

I didn't test this on Windows.

Java 8 Stack trace

$ java -jar jfreechart-builder-demo-1.5.6.jar 
Exception in thread "AWT-EventQueue-1" java.lang.OutOfMemoryError: Java heap space
	at java.util.Arrays.copyOf(Arrays.java:3356)
	at sun.java2d.pisces.Helpers.widenArray(Helpers.java:160)
	at sun.java2d.pisces.Renderer.addLine(Renderer.java:295)
	at sun.java2d.pisces.Renderer.lineTo(Renderer.java:382)
	at sun.java2d.pisces.Stroker$PolyStack.pop(Stroker.java:1202)
	at sun.java2d.pisces.Stroker.emitReverse(Stroker.java:423)
	at sun.java2d.pisces.Stroker.finish(Stroker.java:446)
	at sun.java2d.pisces.Stroker.moveTo(Stroker.java:356)
	at sun.java2d.pisces.Dasher.goTo(Dasher.java:155)
	at sun.java2d.pisces.Dasher.somethingTo(Dasher.java:244)
	at sun.java2d.pisces.Dasher.curveTo(Dasher.java:540)
	at sun.java2d.pipe.RenderingEngine.feedConsumer(RenderingEngine.java:373)
	at sun.java2d.pisces.PiscesRenderingEngine.pathTo(PiscesRenderingEngine.java:484)
	at sun.java2d.pisces.PiscesRenderingEngine.strokeTo(PiscesRenderingEngine.java:363)
	at sun.java2d.pisces.PiscesRenderingEngine.strokeTo(PiscesRenderingEngine.java:163)
	at sun.java2d.pisces.PiscesRenderingEngine.getAATileGenerator(PiscesRenderingEngine.java:562)
	at sun.java2d.pipe.AAShapePipe.renderPath(AAShapePipe.java:147)
	at sun.java2d.pipe.AAShapePipe.draw(AAShapePipe.java:78)
	at sun.java2d.pipe.PixelToParallelogramConverter.draw(PixelToParallelogramConverter.java:148)
	at sun.java2d.SunGraphics2D.draw(SunGraphics2D.java:2502)
	at org.jfree.chart.annotations.XYShapeAnnotation.draw(XYShapeAnnotation.java:194)
	at org.jfree.chart.plot.XYPlot.drawAnnotations(XYPlot.java:3673)
	at org.jfree.chart.plot.XYPlot.draw(XYPlot.java:3048)
	at org.jfree.chart.plot.CombinedDomainXYPlot.draw(CombinedDomainXYPlot.java:447)
	at org.jfree.chart.JFreeChart.draw(JFreeChart.java:1160)
	at org.jfree.chart.ChartPanel.paintComponent(ChartPanel.java:1452)
	at javax.swing.JComponent.paint(JComponent.java:1056)
	at javax.swing.JComponent.paintChildren(JComponent.java:889)
	at javax.swing.JComponent.paint(JComponent.java:1065)
	at javax.swing.JComponent.paintChildren(JComponent.java:889)
	at javax.swing.JComponent.paint(JComponent.java:1065)
	at javax.swing.JLayeredPane.paint(JLayeredPane.java:586)

Migrate demo app code into this repo

Migrate the jfreechart-builder-demo ("test") app code into this repo to reduce the development and release burden.

Without this, commits and releases in two separate repos must be managed.
Will also add visibility of the demo/test app.

Using a parent POM there can be multiple modules with each module built and installed individually or as a batch.

After this is delivered the jfreechart-builder-demo repo should be deprecated.

Add grid customization

Discussed in #55

Originally posted by jcobb88 November 8, 2022
Currently the grid can be very/too strong, especially when you change the background color. A customization method in ChartBuilder would be helpful.

Add ability to set desired margins

While investigating issue #32 using the demo app, I first added the ability to set the time-axis lower and upper margin values in ChartBuilder. While this worked fine for time-gapped charts that use DateAxis, this quickly became non-trivial for gapless charts that rely on NumberAxis. Part of the issue was the gapless chart NumberAxis not adding tick labels in the margin area of the axis like DateAxis does. The other was if the margin value was set near or above 0.01 (1% of the axis range) in gapless charts, then the logic in NumberAxis#selectHorizontalAutoTickUnit(Graphics2D, Rectangle2D, RectangleEdge) would "guess" a number of ticks that was way too large. The result was too many closely packed tick labels with labels and plotted data overlapping. It seemed to have to do with the TickUnit size being calculated too small (~2.0) as opposed to a value of 5.0. When I hard-coded the gapless chart tick unit to 5.0 it created a nice amount of ticks with margin values above 0.01.

This ticket is to find a way to allow setting top/right/bottom/left margin values with charts rendering OK.

Prepare release 1.5.7

When ready:

  • Bump the version number
  • Repo documentation updates (the hosted Javadoc to be updated post-release).
  • Anything else?
  • Merge develop into main and apply tag

XYShapeAnnotation solution for Gapless Charts

Follow-on to enhancement #49 (comment) to implement a solution for NumberAxis ("gapless") chart XYShapeAnnotation support. Effictively, how to map disparate implementations' time-based x-coordinate to numeric indices.

Could be for specific Shape implementations on a case-by-case basis.

On hold until there's actual demand for it.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.