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.
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.
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();
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();
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();
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)
...
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.
ChartCombinedAxisClickDispatcher
was created to dispatch JfreeChart ChartMouseEvent
s 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.
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)
...
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.
See the demo-app solution for an interactive demo. Used for development and testing.
To launch it see Testing section further down.
The module is not published to Maven Central so you must build the solution locally and install it in local Maven repositories.
git clone <this repo's URL>
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.
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.
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.
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 this dependency to your project's .pom
file:
<dependency>
<groupId>com.jfcbuilder</groupId>
<artifactId>jfreechart-builder</artifactId>
<version>1.5.8</version>
<dependency>
Run the demo-app from your IDE or launch it from the command line:
java -jar jfreechart-builder-demo.jar
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)!
The latest release Javadoc is hosted here.
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
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.
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.
Contributions are welcome and will be accepted as the maintainers' time permits.