diff --git a/.classpath b/.classpath
index c511809..159690e 100644
--- a/.classpath
+++ b/.classpath
@@ -1,20 +1,38 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs
new file mode 100644
index 0000000..04cfa2c
--- /dev/null
+++ b/.settings/org.eclipse.core.resources.prefs
@@ -0,0 +1,6 @@
+eclipse.preferences.version=1
+encoding//src/main/java=UTF-8
+encoding//src/main/resources=UTF-8
+encoding//src/test/java=UTF-8
+encoding//src/test/resources=UTF-8
+encoding/=UTF-8
diff --git a/pom.xml b/pom.xml
index 24f41a3..e219c90 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,6 +1,127 @@
-
- 4.0.0
- com.github.kilianB
- MultiTypeChart
- 0.0.1-SNAPSHOT
+
+ 4.0.0
+ com.github.kilianB
+ MultiTypeChart
+ 1.0.0
+
+
+ maven
+ MultiTypeChart
+ UTF-8
+
+
+
+
+ MIT
+ https://opensource.org/licenses/MIT
+ repo
+
+
+
+
+
+ Kilian Brachtendorf
+ Kilian.Brachtendorf@t-online.de
+
+ developer
+
+ Europe/Berlin
+
+
+
+
+
+
+ jcenter
+ https://jcenter.bintray.com/
+
+
+
+
+
+ com.github.kilianB
+ UtilityCode
+ 1.5.1
+
+
+
+
+
+
+
+ maven-compiler-plugin
+ 3.7.0
+
+ 10
+ 10
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 2.21.0
+
+
+ org.junit.platform
+ junit-platform-surefire-provider
+ 1.2.0-M1
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ 5.2.0-M1
+
+
+
+
+ maven-source-plugin
+
+
+ **/kilianB/demo/**
+
+
+
+
+ attach-sources
+
+ jar
+
+
+
+
+
+ maven-javadoc-plugin
+
+
+ **/kilianB/demo/**
+
+
+
+
+ attach-javadocs
+
+ jar
+
+
+
+
+
+
+
+
+
+
+ bintray-kilianb-maven
+ kilianb-maven
+ https://api.bintray.com/maven/kilianb/${bintrayRepository}//${bintrayPackage}/
+
+
+
\ No newline at end of file
diff --git a/src/main/java/com/github/kilianB/MultiTypeChart.java b/src/main/java/com/github/kilianB/MultiTypeChart.java
index d62336a..e00fc5e 100644
--- a/src/main/java/com/github/kilianB/MultiTypeChart.java
+++ b/src/main/java/com/github/kilianB/MultiTypeChart.java
@@ -14,7 +14,7 @@
import java.util.logging.Logger;
import com.github.kilianB.Legend.LegendItem;
-import com.github.kilianB.utility.ColorUtil;
+import com.github.kilianB.graphics.ColorUtil;
import javafx.animation.FadeTransition;
import javafx.animation.KeyFrame;
@@ -31,17 +31,22 @@
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
+import javafx.geometry.Orientation;
import javafx.geometry.Side;
import javafx.scene.AccessibleRole;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.chart.Axis;
+import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.control.Label;
import javafx.scene.effect.ColorAdjust;
import javafx.scene.input.MouseEvent;
import javafx.scene.chart.LineChart.SortingPolicy;
+import javafx.scene.chart.XYChart.Data;
+import javafx.scene.chart.XYChart.Series;
import javafx.scene.chart.NumberAxis;
+import javafx.scene.chart.ValueAxis;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.CornerRadii;
@@ -59,12 +64,12 @@
// the classloader
public class MultiTypeChart extends XYChart {
- private static final Logger LOG = Logger.getLogger(MultiTypeChart.class.getName());
-
public enum SeriesType {
- SCATTER, LINE, AREA, STACKED_AREA, STACKED_BAR
+ SCATTER, LINE, AREA, BAR, STACKED_AREA, STACKED_BAR
}
+ private static final Logger LOG = Logger.getLogger(MultiTypeChart.class.getName());
+
// Global chart properties
private static final String COLOR_CSS_CLASS = "default-color";
private static final String LINE_CHART_LINE_CSS_CLASS = "chart-series-line";
@@ -75,6 +80,10 @@ public enum SeriesType {
private static final String AREA_CHART_SYMBOL_CSS_CLASS = "chart-area-symbol";
private static final String AREA_CHART_SERIES_LINE_CSS_CLASS = "chart-series-area-line";
private static final String AREA_CHART_FILL_CSS_CLASS = "chart-series-area-fill";
+
+ //Bar
+ private static String NEGATIVE_STYLE = "negative";
+
// Markers
ReentrantLock markerLock = new ReentrantLock();
private HashMap, Path> valueMarkers = new HashMap<>();
@@ -85,7 +94,6 @@ public enum SeriesType {
protected HashMap, TypedSeries> typedSeries = new HashMap<>();
protected HashMap, Boolean> seriesVisibility = new HashMap<>();
-
/** A package private hashmap in the chart object */
protected Map, Integer> seriesColorMap = new HashMap<>();
protected BitSet availableColors = new BitSet(8);
@@ -94,122 +102,24 @@ public enum SeriesType {
/** Remember the current animation state of area charts for layout */
private Map, DoubleProperty> seriesYMultiplierMap = new HashMap<>();
+ // Bar chart
+
+ // For every series save data inside here
+ private Map, Map>> seriesCategoryMap = new HashMap<>();
+
protected Legend legend = new Legend();
private Group plotArea;
// Implement multi axis support
private final int AXIS_PADDING = 5;
- private HashMap> additionalXAxis = new HashMap<>();
- private HashMap> additionalYAxis = new HashMap<>();
-
- /**
- * Legend items
- */
- private HashMap,LegendItem> legendMap;
-
+ private HashMap> additionalXAxis = new HashMap<>();
+ private HashMap> additionalYAxis = new HashMap<>();
+
/**
- *
- * @param index
- * @param xAxis true xAxis false yAxis
+ * Legend items
*/
- @SuppressWarnings("unchecked")
- protected void addAdditionalAxis(int index, boolean isX, Side side) {
-
- // todo only number?
- Axis> axis;
-
- if(isX) {
- //XAxis
- axis = new NumberAxis();
- additionalXAxis.put(index,(Axis) axis);
- }else {
- //YAxis
- axis = new NumberAxis();
- additionalYAxis.put(index,(Axis) axis);
- }
-
- axis.setSide(side);
-
- // package private. But as far as I can see this is not necessary when the side
- // is set?
- // axis.setEffectiveOrientation(Orientation.VERTICAL);
-
- axis.autoRangingProperty().addListener((ov, t, t1) -> {
- updateAxisRange();
- });
-
- getChartChildren().add(axis);
-
- //Make space for the axis. We are using the padding since we don't have enough access to other components
-
-
- //Construct the binding
- ReadOnlyDoubleProperty[] axisWidthAndHeightProperty = new ReadOnlyDoubleProperty[additionalYAxis.size()+additionalXAxis.size()];
-
- int i = 0;
- for(var entry : additionalYAxis.entrySet()) {
- axisWidthAndHeightProperty[i++] = entry.getValue().widthProperty();
- }
-
- for(var entry : additionalXAxis.entrySet()) {
- axisWidthAndHeightProperty[i++] = entry.getValue().heightProperty();
- }
-
-
- ObjectBinding paddingBinding = new ObjectBinding<>() {
-
- {super.bind(axisWidthAndHeightProperty);}
-
- @Override
- protected Insets computeValue() {
-
- double top = 0, right = 0, bottom = 0, left = 0;
-
- for(var entry : additionalYAxis.entrySet()) {
-
- double width = entry.getValue().getWidth();
-
- switch(entry.getValue().getSide()) {
- case LEFT:
- left += (width+AXIS_PADDING);
- break;
- case RIGHT:
- right += (width+AXIS_PADDING);
- break;
- default:
- throw new IllegalStateException("Y Axis may only be positioned right or left");
- }
- }
-
- for(var entry : additionalXAxis.entrySet()) {
- double height = entry.getValue().getHeight();
-
- System.out.println("X Axis: " + height + " " + entry.getValue() + " " + entry.getValue().getWidth()
- + entry.getValue().getSide());
-
- switch(entry.getValue().getSide()) {
- case TOP:
- top += (height+AXIS_PADDING);
- break;
- case BOTTOM:
- bottom += (height+AXIS_PADDING);
- break;
- default:
- throw new IllegalStateException("X Axis may only be positioned top or bottom");
- }
- }
- System.out.println(top + " " + right + " " + bottom + " " + left);
- return new Insets(top,right,bottom,left);
-
- }
-
- };
-
- this.paddingProperty().bind(paddingBinding);
-
- this.requestChartLayout();
- }
+ private HashMap, LegendItem> legendMap;
public MultiTypeChart(@NamedArg("xAxis") Axis xAxis, @NamedArg("yAxis") Axis yAxis) {
super(xAxis, yAxis);
@@ -234,9 +144,7 @@ public MultiTypeChart(@NamedArg("xAxis") Axis xAxis, @NamedArg("yAxis") Axis<
}
-
-
- public boolean addSeries(TypedSeries series) throws IllegalArgumentException{
+ public boolean addSeries(TypedSeries series) throws IllegalArgumentException {
if (!typedSeries.containsKey(series.getSeries())) {
// JavaFX uses weak listeners? No need to dispose afterwards
@@ -290,18 +198,34 @@ public boolean addSeries(TypedSeries series) throws IllegalArgumentExcepti
typedSeries.put(series.getSeries(), series);
seriesVisibility.put(series.getSeries(), Boolean.TRUE);
- //Check if we need to add an additional axis
+ // Axis validation.
int yAxisIndex = series.getYAxisIndex();
int xAxisIndex = series.getXAxisIndex();
-
- if(yAxisIndex != 0 && !additionalYAxis.containsKey(yAxisIndex)) {
- addAdditionalAxis(yAxisIndex,false,series.getyAxisSide());
+
+ if (yAxisIndex != 0 && !additionalYAxis.containsKey(yAxisIndex)) {
+ addAdditionalAxis(yAxisIndex, false, series.getyAxisSide());
}
-
- if(xAxisIndex != 0 && !additionalXAxis.containsKey(xAxisIndex)) {
- addAdditionalAxis(xAxisIndex,true,series.getxAxisSide());
+
+ if (xAxisIndex != 0 && !additionalXAxis.containsKey(xAxisIndex)) {
+ addAdditionalAxis(xAxisIndex, true, series.getxAxisSide());
}
-
+
+ // Check if we need to add an additional axis
+ if (series.getSeriesType().equals(SeriesType.BAR)) {
+
+ Axis yAxis = additionalYAxis.get(yAxisIndex);
+ Axis xAxis = additionalXAxis.get(xAxisIndex);
+
+ if (!((xAxis instanceof ValueAxis && yAxis instanceof CategoryAxis)
+ || (yAxis instanceof ValueAxis && xAxis instanceof CategoryAxis))) {
+ throw new IllegalArgumentException(
+ "Axis type incorrect, one of X,Y should be CategoryAxis and the other NumberAxis");
+
+ }
+ // One of them has to be a categorial axis.
+
+ }
+
getData().add(series.getSeries());
return true;
@@ -310,303 +234,566 @@ public boolean addSeries(TypedSeries series) throws IllegalArgumentExcepti
}
}
- @Override
- protected void dataItemAdded(Series series, int itemIndex, Data item) {
- switch (typedSeries.get(series).getSeriesType()) {
- case AREA:
- addAreaItem(series, itemIndex, item);
- break;
- case LINE:
- addLineItem(series, itemIndex, item);
- break;
- case SCATTER:
- addScatterItem(series, itemIndex, item);
- break;
- case STACKED_AREA:
- break;
- case STACKED_BAR:
- break;
- default:
- break;
+ /**
+ * Add a value marker to the chart
+ *
+ * @param markerToAdd the marker to add
+ */
+ public void addValueMarker(ValueMarker> markerToAdd) {
+ markerLock.lock();
+ Path p = new Path();
+ valueMarkers.put(markerToAdd, p);
+
+ ChangeListener cl = (obs, oldValue, newValue) -> {
+ if (newValue) {
+ getChartChildren().add(((ValueMarker>) obs).getLabel());
+ } else {
+ getChartChildren().remove(((ValueMarker>) obs).getLabel());
+ }
+ requestChartLayout();
+ };
+
+ valueMarkerLabelMap.put(markerToAdd, cl);
+ markerToAdd.enableLabelProperty().addListener(cl);
+
+ // Add it to the chart
+ getChartChildren().add(p);
+
+ if (markerToAdd.enableLabelProperty().get()) {
+ getChartChildren().add(markerToAdd.getLabel());
}
+
+ markerLock.unlock();
}
- @Override
- protected void dataItemRemoved(Data item, Series series) {
- // TODO Auto-generated method stub
+ /**
+ * The current displayed data value plotted on the X axis. This may be the same
+ * as xValue or different. It is used by XYChart to animate the xValue from the
+ * old value to the new value. This is what you should plot in any custom
+ * XYChart implementations. Some XYChart chart implementations such as LineChart
+ * also use this to animate when data is added or removed.
+ *
+ * @param item The XYChart.Data item from which the current X axis data value is
+ * obtained
+ * @return The current displayed X data value
+ */
+ public X getCoordinate(Data item) {
+ return this.getCurrentDisplayedXValue(item);
+ }
+ /**
+ * The current displayed data value plotted on the Y axis. This may be the same
+ * as yValue or different. It is used by XYChart to animate the yValue from the
+ * old value to the new value. This is what you should plot in any custom
+ * XYChart implementations. Some XYChart chart implementations such as LineChart
+ * also use this to animate when data is added or removed.
+ *
+ * @param item The XYChart.Data item from which the current Y axis data value is
+ * obtained
+ * @return The current displayed Y data value
+ */
+ public Y getYCoordinate(Data item) {
+ return this.getCurrentDisplayedYValue(item);
}
- // Never overwritten. some other requestChartLayout probably catches it
- @Override
- protected void dataItemChanged(Data item) {
+ /**
+ * @param series the typed series
+ * @return true if the series is displayed false if it is hidden
+ */
+ public boolean isSeriesVisible(TypedSeries series) {
+ return seriesVisibility.get(series.getSeries());
}
- @Override
- protected void seriesAdded(Series series, int seriesIndex) {
+ /**
+ * Remove a value marker from the chart
+ *
+ * @param markerToRemove the marker to remove
+ */
+ public void removeValueMarker(ValueMarker> markerToRemove) {
+ markerLock.lock();
+ Path p = valueMarkers.remove(markerToRemove);
+ if (p != null) {
+ getChartChildren().remove(p);
+ }
- int freeIndex = availableColors.nextClearBit(0) % availableColors.size();
- availableColors.set(freeIndex);
+ // We also have to remove the listener or else we leake memory
+ ChangeListener l = valueMarkerLabelMap.remove(markerToRemove);
+ if (l != null) {
+ markerToRemove.enableLabelProperty().removeListener(l);
+ }
+ markerLock.unlock();
+ }
- seriesColorMap.put(series, freeIndex);
+ public void setSeriesVisibility(TypedSeries series, boolean b) {
+ toggleSeriesVisability(series.getSeries(), b);
- // Chart specific setup
- TypedSeries ser = typedSeries.get(series);
+ LegendItem lItem = legendMap.get(series.getSeries());
- switch (ser.getSeriesType()) {
- case AREA:
- // create new paths for series
- Path seriesLine = new Path();
- Path fillPath = new Path();
- seriesLine.setStrokeLineJoin(StrokeLineJoin.BEVEL);
- Group areaGroup = new Group(fillPath, seriesLine);
- series.setNode(areaGroup);
- // create series Y multiplier
- DoubleProperty seriesYAnimMultiplier = new SimpleDoubleProperty(this, "seriesYMultiplier");
- seriesYMultiplierMap.put(series, seriesYAnimMultiplier);
- seriesYAnimMultiplier.setValue(1d);
- getPlotChildren().add(areaGroup);
- break;
- case LINE:
- // Line and area charts require a path node
- seriesLine = new Path();
- seriesLine.setStrokeLineJoin(StrokeLineJoin.BEVEL);
- series.setNode(seriesLine);
- // create series Y multiplier
- seriesYAnimMultiplier = new SimpleDoubleProperty(this, "seriesYMultiplier");
- seriesYMultiplierMap.put(series, seriesYAnimMultiplier);
- seriesYAnimMultiplier.setValue(1d);
- getPlotChildren().add(seriesLine);
- break;
- case SCATTER:
- // No need to set up
- break;
- case STACKED_AREA:
- case STACKED_BAR:
- throw new UnsupportedOperationException("Not implemented yet");
- default:
- break;
+ // Get the legends and update it accordingly
+ Node symbol = lItem.getSymbol();
+ ObservableList cssClass = symbol.getStyleClass();
+ // TODO duplicated code.
+ if (cssClass.contains("hide-series")) {
+ symbol.setEffect(null);
+ symbol.getStyleClass().remove("hide-series");
+ toggleSeriesVisability(series.getSeries(), true);
+ } else {
+ symbol.getStyleClass().add("hide-series");
+ ColorAdjust colorAdjust = new ColorAdjust();
+ colorAdjust.setSaturation(-0.5);
+ symbol.setEffect(colorAdjust);
+ toggleSeriesVisability(series.getSeries(), false);
+ }
+ }
+ private void createSymbols(Series series, int itemIndex, Data item, String symbolCSSIdentifier) {
+ Node symbol = item.getNode();
+ // TODO should we also check here is e.g. scatter chart ticks should be drawn?
+ if (symbol == null) {
+ symbol = new StackPane();
+ symbol.setAccessibleRole(AccessibleRole.TEXT);
+ symbol.setAccessibleRoleDescription("Point");
+ symbol.focusTraversableProperty().bind(Platform.accessibilityActiveProperty());
+ item.setNode(symbol);
}
- // Finally add the individual data points
- for (int j = 0; j < series.getData().size(); j++) {
- dataItemAdded(series, j, series.getData().get(j));
+ if (symbol != null) {
+ symbol.getStyleClass().addAll(symbolCSSIdentifier, "series" + getData().indexOf(series), "data" + itemIndex,
+ COLOR_CSS_CLASS + seriesColorMap.get(series));
}
}
- @Override
- protected void seriesRemoved(Series series) {
+ // Layout
- SeriesType removedSeriesType = typedSeries.remove(series).getSeriesType();
+ /**
+ * Add an additional axis to the chart. This method is automatically called
+ * whenever a series is added to the chart with not already present axis index.
+ *
+ * @param index the index this chart can be accessed in the future. Reusing
+ * already existing indices will overwrite the axis currently
+ * present.
+ * @param isX if true is xaxis, if false yaxis
+ * @param side specify the position of the axis. Valid values for XAxis are Top
+ * and Bottom. And Left and Right for YAxis.
+ */
+ @SuppressWarnings("unchecked")
+ protected void addAdditionalAxis(int index, boolean isX, Side side) {
- if (shouldAnimate()) {
+ // todo only number?
+ Axis> axis;
- int animationDuration = 0;
- switch (removedSeriesType) {
- case LINE:
- animationDuration = 900;
- break;
- case SCATTER:
- animationDuration = 400;
- break;
- default:
- animationDuration = 500;
- }
+ if (isX) {
+ // XAxis
+ axis = new NumberAxis();
+ additionalXAxis.put(index, (Axis) axis);
+ } else {
+ // YAxis
+ axis = new NumberAxis();
+ additionalYAxis.put(index, (Axis) axis);
+ }
- // Can't we use this for all chart types? Why do we need an additional one for
- // catter?
+ axis.setSide(side);
- // What happened to the exceptions?
- // TODO do we want to use reflection or simply copy and paste the method?
- try {
- Method method = XYChart.class.getMethod("createSeriesRemoveTimeLine", Series.class, Integer.class);
- method.setAccessible(true);
- KeyFrame[] keyframes = (KeyFrame[]) method.invoke(this, series, animationDuration);
- method.setAccessible(false);
- Timeline tl = new Timeline(keyframes);
- tl.play();
- } catch (Exception e) {
- e.printStackTrace();
- }
- } else {
+ // package private. But as far as I can see this is not necessary when the side
+ // is set?
+ // axis.setEffectiveOrientation(Orientation.VERTICAL);
- // Not all types of charts use series nodes. Check before removing
- if (series.getNode() != null) {
- getPlotChildren().remove(series.getNode());
- }
+ axis.autoRangingProperty().addListener((ov, t, t1) -> {
+ updateAxisRange();
+ });
- // Everything else is the same
- for (final Data d : series.getData()) {
- final Node symbol = d.getNode();
- getPlotChildren().remove(symbol);
- }
- removeSeriesFromDisplay(series);
+ getChartChildren().add(axis);
+
+ // Make space for the axis. We are using the padding since we don't have enough
+ // access to other components
+
+ // Construct the binding
+ ReadOnlyDoubleProperty[] axisWidthAndHeightProperty = new ReadOnlyDoubleProperty[additionalYAxis.size()
+ + additionalXAxis.size()];
+
+ int i = 0;
+ for (var entry : additionalYAxis.entrySet()) {
+ axisWidthAndHeightProperty[i++] = entry.getValue().widthProperty();
}
- // if (shouldAnimate()) {
- // ParallelTransition pt = new ParallelTransition();
- // pt.setOnFinished(event -> {
- // removeSeriesFromDisplay(series);
- // });
- // for (final Data d : series.getData()) {
- // final Node symbol = d.getNode();
- // // fade out old symbol
- // FadeTransition ft = new FadeTransition(Duration.millis(500), symbol);
- // ft.setToValue(0);
- // ft.setOnFinished(actionEvent -> {
- // getPlotChildren().remove(symbol);
- // symbol.setOpacity(1.0);
- // });
- // pt.getChildren().add(ft);
- // }
- // pt.play();
- // }
+ for (var entry : additionalXAxis.entrySet()) {
+ axisWidthAndHeightProperty[i++] = entry.getValue().heightProperty();
+ }
- }
+ ObjectBinding paddingBinding = new ObjectBinding<>() {
- @Override
- protected void seriesChanged(ListChangeListener.Change extends Series> c) {
+ {
+ super.bind(axisWidthAndHeightProperty);
+ }
- for (int i = 0; i < getData().size(); i++) {
- final Series s = getData().get(i);
+ @Override
+ protected Insets computeValue() {
- TypedSeries type = typedSeries.get(s);
+ double top = 0, right = 0, bottom = 0, left = 0;
- String seriesColor = COLOR_CSS_CLASS + seriesColorMap.get(s);
+ for (var entry : additionalYAxis.entrySet()) {
- switch (type.getSeriesType()) {
- case AREA:
- Path seriesLine = (Path) ((Group) s.getNode()).getChildren().get(1);
- Path fillPath = (Path) ((Group) s.getNode()).getChildren().get(0);
- seriesLine.getStyleClass().setAll(AREA_CHART_SERIES_LINE_CSS_CLASS, "series" + i, seriesColor);
- fillPath.getStyleClass().setAll(AREA_CHART_FILL_CSS_CLASS, "series" + i, seriesColor);
- for (int j = 0; j < s.getData().size(); j++) {
- final Data item = s.getData().get(j);
- final Node node = item.getNode();
- if (node != null)
- node.getStyleClass().setAll(AREA_CHART_SYMBOL_CSS_CLASS, "series" + i, "data" + j, seriesColor);
- }
- break;
- case LINE:
- Node seriesNode = s.getNode();
- if (seriesNode != null)
- seriesNode.getStyleClass().setAll(LINE_CHART_LINE_CSS_CLASS, "series" + i, seriesColor);
- for (int j = 0; j < s.getData().size(); j++) {
- final Node symbol = s.getData().get(j).getNode();
- if (symbol != null)
- symbol.getStyleClass().setAll(LINE_CHART_SYMBOL_CSS_CLASS, "series" + i, "data" + j,
- seriesColor);
+ double width = entry.getValue().getWidth();
+
+ switch (entry.getValue().getSide()) {
+ case LEFT:
+ left += (width + AXIS_PADDING);
+ break;
+ case RIGHT:
+ right += (width + AXIS_PADDING);
+ break;
+ default:
+ throw new IllegalStateException("Y Axis may only be positioned right or left");
+ }
}
- break;
- case SCATTER:
- for (int j = 0; j < s.getData().size(); j++) {
- final Node symbol = s.getData().get(j).getNode();
- if (symbol != null)
- symbol.getStyleClass().setAll(SCATTER_CHART_SYMBOL_CSS_CLASS, "series" + i, "data" + j,
- seriesColor);
+ for (var entry : additionalXAxis.entrySet()) {
+ double height = entry.getValue().getHeight();
+
+ System.out.println("X Axis: " + height + " " + entry.getValue() + " " + entry.getValue().getWidth()
+ + entry.getValue().getSide());
+
+ switch (entry.getValue().getSide()) {
+ case TOP:
+ top += (height + AXIS_PADDING);
+ break;
+ case BOTTOM:
+ bottom += (height + AXIS_PADDING);
+ break;
+ default:
+ throw new IllegalStateException("X Axis may only be positioned top or bottom");
+ }
}
- break;
- case STACKED_AREA:
- case STACKED_BAR:
- default:
- throw new UnsupportedOperationException("Not implemented yet");
- }
+ System.out.println(top + " " + right + " " + bottom + " " + left);
+ return new Insets(top, right, bottom, left);
- }
- }
+ }
- // Layout
+ };
- @Override
- protected void layoutPlotChildren() {
+ this.paddingProperty().bind(paddingBinding);
- List constructedPath = new ArrayList<>(getDataSizeMultiType());
+ this.requestChartLayout();
+ }
- series: for (int seriesIndex = 0; seriesIndex < getDataSizeMultiType(); seriesIndex++) {
- Series series = getData().get(seriesIndex);
+ // Add data item
+ protected void addAreaItem(Series series, int itemIndex, Data item) {
+ // TODO add animation code
+ createSymbols(series, itemIndex, item, AREA_CHART_SYMBOL_CSS_CLASS);
+ getPlotChildren().add(item.getNode());
+ }
- //If it's not visible don't layout
-
- var typed = typedSeries.get(series);
-
- SeriesType type = typed.getSeriesType();
- int xAxisIndex = typed.getXAxisIndex();
- int yAxisIndex = typed.getYAxisIndex();
-
- final Axis xAxis;
- final Axis yAxis;
-
- if(xAxisIndex == 0) {
- xAxis = getXAxis();
- }else {
- xAxis = additionalXAxis.get(xAxisIndex);
- }
-
- if(yAxisIndex == 0) {
- yAxis = getYAxis();
- }else {
- yAxis = additionalYAxis.get(yAxisIndex);
- }
+ protected void addLineItem(Series series, int itemIndex, Data item) {
- // Not really object oriented but hey. Work with what we got here ...
- switch (type) {
- case AREA:
- layoutAreaChart(series, constructedPath,xAxis,yAxis);
- break;
- case LINE:
- layoutLineChart(series, constructedPath,xAxis,yAxis);
- break;
- case SCATTER:
- layoutScatterSeries(series,xAxis,yAxis);
- break;
- default:
- LOG.warning("No layout method found for: " + type.name() + ". Skip series: " + series);
- continue series;
- }
+ // Create the node
+ if (typedSeries.get(series).showSymbolsProperty().get()) {
+ createSymbols(series, itemIndex, item, LINE_CHART_SYMBOL_CSS_CLASS);
}
+ Node symbol = item.getNode();
- // Take care of markers
-
- final Axis xAxis = getXAxis();
- final Axis yAxis = getYAxis();
- //double maxY = xAxis.getWidth();
- //double maxX = yAxis.getHeight();
+ if (symbol != null) {
+ getPlotChildren().add(symbol);
+ }
- // 10 px padding
- double yMin = yAxis.getBoundsInLocal().getMaxY() + 5;
- double yMax = yMin - plotArea.getBoundsInParent().getHeight();
+ // TODO add animation code
+ }
- markerLock.lock();
- Iterator, Path>> iter = valueMarkers.entrySet().iterator();
+ protected void addScatterItem(Series series, int itemIndex, Data item) {
- while (iter.hasNext()) {
+ createSymbols(series, itemIndex, item, SCATTER_CHART_SYMBOL_CSS_CLASS);
+ Node symbol = item.getNode();
- var entry = iter.next();
- ValueMarker> marker = entry.getKey();
- Path p = entry.getValue();
- p.getElements().clear();
- try {
- p.setStroke(marker.getColor());
+ // add and fade in new symbol if animated
+ if (shouldAnimate()) {
+ symbol.setOpacity(0);
+ getPlotChildren().add(symbol);
+ FadeTransition ft = new FadeTransition(Duration.millis(500), symbol);
+ ft.setToValue(1);
+ ft.play();
+ } else {
+ getPlotChildren().add(symbol);
+ }
+ }
- Label label = null;
- if (marker.enableLabelProperty().get()) {
- label = (Label) marker.getLabel();
- label.setTextFill(ColorUtil.getContrastColor(marker.getColor()));
- label.setBackground(
- new Background(new BackgroundFill(marker.getColor(), new CornerRadii(3), null)));
- // relocate label
+ protected void addBarItem(Series series, int itemIndex, Data item) {
- }
+ // Add bar nodes
- if (marker.isVertical()) {
+ }
- double x = xAxis.getDisplayPosition((X) marker.getValue()) + xAxis.getLayoutX();
- // Check if it is currently displayed
- if (!Double.valueOf(x).isNaN() && (x != xAxis.getZeroPosition() || !isVerticalZeroLineVisible())
+ @Override
+ protected void dataItemAdded(Series series, int itemIndex, Data item) {
+ switch (typedSeries.get(series).getSeriesType()) {
+ case AREA:
+ addAreaItem(series, itemIndex, item);
+ break;
+ case LINE:
+ addLineItem(series, itemIndex, item);
+ break;
+ case SCATTER:
+ addScatterItem(series, itemIndex, item);
+ break;
+ case BAR:
+ addBarItem(series, itemIndex, item);
+ case STACKED_AREA:
+ break;
+ case STACKED_BAR:
+ break;
+ default:
+ break;
+ }
+ }
+
+ // Never overwritten. some other requestChartLayout probably catches it
+ @Override
+ protected void dataItemChanged(Data item) {
+ }
+
+ @Override
+ protected void dataItemRemoved(Data item, Series series) {
+ }
+
+ // utility
+ protected int getDataSizeMultiType() {
+ final ObservableList> data = getData();
+ return (data != null) ? data.size() : 0;
+ }
+
+ // Go with the more specific implementation for line and area charts.
+ // TODO we could auto range to not stick to the 0 line
+
+ protected void layoutAreaChart(Series series, List constructedPath, Axis xAxis, Axis yAxis) {
+ final ObservableList children = ((Group) series.getNode()).getChildren();
+
+ boolean visible = seriesVisibility.get(series);
+
+ Path fillPath = (Path) children.get(0);
+ Path linePath = (Path) children.get(1);
+
+ var cssPath = fillPath.getStyleClass();
+ var cssLine = linePath.getStyleClass();
+
+ if (!visible) {
+ if (!cssPath.contains("hide-series-symbol")) {
+ cssPath.add("hide-series-symbol");
+ cssLine.add("hide-series-symbol");
+
+ // Also add the hide artifact to the symbols
+ for (Iterator> it = getDisplayedDataIterator(series); it.hasNext();) {
+ Node symbol = it.next().getNode();
+ symbol.getStyleClass().add("hide-series-symbol");
+ }
+ }
+ } else {
+ if (cssPath.contains("hide-series-symbol")) {
+ cssPath.remove("hide-series-symbol");
+ cssLine.add("hide-series-symbol");
+ // Also add the hide artifact to the symbols
+ for (Iterator> it = getDisplayedDataIterator(series); it.hasNext();) {
+ Node symbol = it.next().getNode();
+ symbol.getStyleClass().remove("hide-series-symbol");
+ }
+ }
+ makePaths(series, constructedPath, fillPath, linePath, SortingPolicy.X_AXIS, xAxis, yAxis);
+ }
+ }
+
+ protected void layoutLineChart(Series series, List constructedPath, Axis xAxis, Axis yAxis) {
+ final Node seriesNode = series.getNode();
+ if (seriesNode instanceof Path) {
+
+ boolean visible = seriesVisibility.get(series);
+ var cssPath = seriesNode.getStyleClass();
+
+ if (!visible) {
+ if (!cssPath.contains("hide-series-symbol")) {
+ cssPath.add("hide-series-symbol");
+
+ // Also add the hide artifact to the symbols
+ for (Iterator> it = getDisplayedDataIterator(series); it.hasNext();) {
+ Node symbol = it.next().getNode();
+ symbol.getStyleClass().add("hide-series-symbol");
+ }
+ }
+ } else {
+ if (cssPath.contains("hide-series-symbol")) {
+ cssPath.remove("hide-series-symbol");
+ // Also add the hide artifact to the symbols
+ for (Iterator> it = getDisplayedDataIterator(series); it.hasNext();) {
+ Node symbol = it.next().getNode();
+ symbol.getStyleClass().remove("hide-series-symbol");
+ }
+ }
+ makePaths(series, constructedPath, null, (Path) seriesNode, typedSeries.get(series).getSortingPolicy(),
+ xAxis, yAxis);
+ }
+
+ }
+ }
+
+ protected void layoutBarChart(Series series, Axis xAxis, Axis yAxis) {
+
+
+ // Define which axis is which axis
+
+
+
+ //TODO
+ Orientation orientation = Orientation.VERTICAL;
+ CategoryAxis categoryAxis;
+ ValueAxis valueAxis;
+
+ double catSpace = categoryAxis.getCategorySpacing();
+ // calculate bar spacing
+
+ final double availableBarSpace = catSpace - (getCategoryGap() + getBarGap());
+ double barWidth = (availableBarSpace / series.getData().size()) - getBarGap();
+ final double barOffset = -((catSpace - getCategoryGap()) / 2);
+ final double zeroPos = (valueAxis.getLowerBound() > 0) ? valueAxis.getDisplayPosition(valueAxis.getLowerBound())
+ : valueAxis.getZeroPosition();
+ // RT-24813 : if the data in a series gets too large, barWidth can get negative.
+ if (barWidth <= 0)
+ barWidth = 1;
+ // update bar positions and sizes
+ int catIndex = 0;
+ for (String category : categoryAxis.getCategories()) {
+ int index = 0;
+// for (Iterator> sit = getDisplayedSeriesIterator(); sit.hasNext(); ) {
+//
+// }
+ final Data item = getDataItem(series, category);
+ if (item != null) {
+ final Node bar = item.getNode();
+ final double categoryPos;
+ final double valPos;
+ if (orientation == Orientation.VERTICAL) {
+
+ //TODO
+ categoryPos = xAxis.getDisplayPosition(item.getXValue());
+ valPos = yAxis.getDisplayPosition(item.getYValue());
+// categoryPos = getXAxis().getDisplayPosition(item.getCurrentX());
+// valPos = getYAxis().getDisplayPosition(item.getCurrentY());
+ } else {
+ categoryPos = yAxis.getDisplayPosition(item.getYValue());
+ valPos = xAxis.getDisplayPosition(item.getXValue());
+
+// categoryPos = getYAxis().getDisplayPosition(item.getCurrentY());
+// valPos = getXAxis().getDisplayPosition(item.getCurrentX());
+ }
+ if (Double.isNaN(categoryPos) || Double.isNaN(valPos)) {
+ continue;
+ }
+ final double bottom = Math.min(valPos, zeroPos);
+ final double top = Math.max(valPos, zeroPos);
+
+ //TODO
+ //bottomPos = bottom;
+
+ if (orientation == Orientation.VERTICAL) {
+ bar.resizeRelocate(categoryPos + barOffset + (barWidth + getBarGap()) * index, bottom, barWidth,
+ top - bottom);
+ } else {
+ // noinspection SuspiciousNameCombination
+ bar.resizeRelocate(bottom, categoryPos + barOffset + (barWidth + getBarGap()) * index, top - bottom,
+ barWidth);
+ }
+
+ index++;
+ }
+ catIndex++;
+ }
+ }
+
+ @Override
+ protected void layoutPlotChildren() {
+
+ List constructedPath = new ArrayList<>(getDataSizeMultiType());
+
+ series: for (int seriesIndex = 0; seriesIndex < getDataSizeMultiType(); seriesIndex++) {
+ Series series = getData().get(seriesIndex);
+
+ // If it's not visible don't layout
+
+ var typed = typedSeries.get(series);
+
+ SeriesType type = typed.getSeriesType();
+ int xAxisIndex = typed.getXAxisIndex();
+ int yAxisIndex = typed.getYAxisIndex();
+
+ final Axis xAxis;
+ final Axis yAxis;
+
+ if (xAxisIndex == 0) {
+ xAxis = getXAxis();
+ } else {
+ xAxis = additionalXAxis.get(xAxisIndex);
+ }
+
+ if (yAxisIndex == 0) {
+ yAxis = getYAxis();
+ } else {
+ yAxis = additionalYAxis.get(yAxisIndex);
+ }
+
+ // Not really object oriented but hey. Work with what we got here ...
+ switch (type) {
+ case AREA:
+ layoutAreaChart(series, constructedPath, xAxis, yAxis);
+ break;
+ case LINE:
+ layoutLineChart(series, constructedPath, xAxis, yAxis);
+ break;
+ case SCATTER:
+ layoutScatterSeries(series, xAxis, yAxis);
+ break;
+ case BAR:
+ layoutBarChart(series, xAxis, yAxis);
+ default:
+ LOG.warning("No layout method found for: " + type.name() + ". Skip series: " + series);
+ continue series;
+ }
+ }
+
+ // Take care of markers
+
+ final Axis xAxis = getXAxis();
+ final Axis yAxis = getYAxis();
+ // double maxY = xAxis.getWidth();
+ // double maxX = yAxis.getHeight();
+
+ // 10 px padding
+ double yMin = yAxis.getBoundsInLocal().getMaxY() + 5;
+ double yMax = yMin - plotArea.getBoundsInParent().getHeight();
+
+ markerLock.lock();
+ Iterator, Path>> iter = valueMarkers.entrySet().iterator();
+
+ while (iter.hasNext()) {
+
+ var entry = iter.next();
+ ValueMarker> marker = entry.getKey();
+ Path p = entry.getValue();
+ p.getElements().clear();
+ try {
+ p.setStroke(marker.getColor());
+
+ Label label = null;
+ if (marker.enableLabelProperty().get()) {
+ label = (Label) marker.getLabel();
+ label.setTextFill(ColorUtil.getContrastColor(marker.getColor()));
+ label.setBackground(
+ new Background(new BackgroundFill(marker.getColor(), new CornerRadii(3), null)));
+ // relocate label
+
+ }
+
+ if (marker.isVertical()) {
+
+ double x = xAxis.getDisplayPosition((X) marker.getValue()) + xAxis.getLayoutX();
+ // Check if it is currently displayed
+ if (!Double.valueOf(x).isNaN() && (x != xAxis.getZeroPosition() || !isVerticalZeroLineVisible())
&& x > 0 && x <= xAxis.getWidth()) {
- // 0.5 offest to counter anti aliasing
+ // 0.5 offset to counter anti aliasing
p.getElements().addAll(new MoveTo(x + 0.5, yMin), new LineTo(x + 0.5, yMax));
if (label != null) {
@@ -646,48 +833,51 @@ protected void layoutPlotChildren() {
}
markerLock.unlock();
- // layout additional axis should be done in requestAxisLayout but it's once again final ....
-
+ // layout additional axis should be done in requestAxisLayout but it's once
+ // again final ....
+
double offsetRight = 0;
- double offsetLeft = - (yAxis.getWidth()*2 + AXIS_PADDING);
+ double offsetLeft = -(yAxis.getWidth() * 2 + AXIS_PADDING);
double offsetTop = 0;
double offsetBottom = xAxis.getHeight() + AXIS_PADDING;
-
- for(var axisEntry : additionalYAxis.entrySet()) {
- //Shall we create an array after each new axis addition / deletion for quicker traversal?
- //Hashmaps are more expensive for sure...
+
+ for (var axisEntry : additionalYAxis.entrySet()) {
+ // Shall we create an array after each new axis addition / deletion for quicker
+ // traversal?
+ // Hashmaps are more expensive for sure...
Axis additional = axisEntry.getValue();
- //Original Y Axis is already layed out. We still need to compute the width as label sizes might
- //differ depending on values
+ // Original Y Axis is already layed out. We still need to compute the width as
+ // label sizes might
+ // differ depending on values
double prefWidth = snapSizeY(additional.prefWidth(getYAxis().getHeight()));
-
- if(additional.getSide().equals(Side.RIGHT)) {
- additional.resizeRelocate(plotArea.getBoundsInParent().getMaxX()+offsetRight, getYAxis().getLayoutY(),
+
+ if (additional.getSide().equals(Side.RIGHT)) {
+ additional.resizeRelocate(plotArea.getBoundsInParent().getMaxX() + offsetRight, getYAxis().getLayoutY(),
prefWidth, getYAxis().getHeight());
offsetRight += prefWidth + AXIS_PADDING;
- }else if(additional.getSide().equals(Side.LEFT)) {
- additional.resizeRelocate(plotArea.getBoundsInParent().getMinX()+offsetLeft, getYAxis().getLayoutY(),
+ } else if (additional.getSide().equals(Side.LEFT)) {
+ additional.resizeRelocate(plotArea.getBoundsInParent().getMinX() + offsetLeft, getYAxis().getLayoutY(),
prefWidth, getYAxis().getHeight());
offsetLeft -= (prefWidth + AXIS_PADDING);
}
}
-
- for(var axisEntry : additionalXAxis.entrySet()) {
- //Shall we create an array after each new axis addition / deletion for quicker traversal?
- //Hashmaps are more expensive for sure...
+
+ for (var axisEntry : additionalXAxis.entrySet()) {
+ // Shall we create an array after each new axis addition / deletion for quicker
+ // traversal?
+ // Hashmaps are more expensive for sure...
Axis additional = axisEntry.getValue();
- //Original Y Axis is already layed out. We still need to compute the width as label sizes might
- //differ depending on values
+ // Original Y Axis is already layed out. We still need to compute the width as
+ // label sizes might
+ // differ depending on values
double prefHeight = snapSizeY(additional.prefHeight(getXAxis().getWidth()));
- if(additional.getSide().equals(Side.TOP)) {
+ if (additional.getSide().equals(Side.TOP)) {
additional.resizeRelocate(plotArea.getBoundsInParent().getMinX(),
- plotArea.getBoundsInParent().getMinY()+offsetTop-prefHeight,
- xAxis.getWidth(),
- prefHeight);
-
+ plotArea.getBoundsInParent().getMinY() + offsetTop - prefHeight, xAxis.getWidth(), prefHeight);
+
offsetTop -= (prefHeight + AXIS_PADDING);
- }else if(additional.getSide().equals(Side.BOTTOM)) {
+ } else if (additional.getSide().equals(Side.BOTTOM)) {
// additional.resizeRelocate(
// plotArea.getBoundsInParent().getMinX(),
// xAxis.getLayoutBounds().getMaxY() + offsetBottom,
@@ -695,88 +885,16 @@ protected void layoutPlotChildren() {
// prefHeight);
// offsetLeft -= (prefHeight + AXIS_PADDING);
}
-
+
}
//
}
- protected void layoutLineChart(Series series, List constructedPath,Axis xAxis, Axis yAxis) {
- final Node seriesNode = series.getNode();
- if (seriesNode instanceof Path) {
-
- boolean visible = seriesVisibility.get(series);
- var cssPath = seriesNode.getStyleClass();
-
- if(!visible) {
- if(!cssPath.contains("hide-series-symbol")) {
- cssPath.add("hide-series-symbol");
-
- //Also add the hide artifact to the symbols
- for (Iterator> it = getDisplayedDataIterator(series); it.hasNext();) {
- Node symbol = it.next().getNode();
- symbol.getStyleClass().add("hide-series-symbol");
- }
- }
- }else {
- if(cssPath.contains("hide-series-symbol")) {
- cssPath.remove("hide-series-symbol");
- //Also add the hide artifact to the symbols
- for (Iterator> it = getDisplayedDataIterator(series); it.hasNext();) {
- Node symbol = it.next().getNode();
- symbol.getStyleClass().remove("hide-series-symbol");
- }
- }
- makePaths(series, constructedPath, null, (Path) seriesNode, typedSeries.get(series).getSortingPolicy(),xAxis,yAxis);
- }
-
-
-
-
- }
- }
+ protected void layoutScatterSeries(Series series, Axis xAxis, Axis yAxis) {
- protected void layoutAreaChart(Series series, List constructedPath,Axis xAxis, Axis yAxis) {
- final ObservableList children = ((Group) series.getNode()).getChildren();
-
boolean visible = seriesVisibility.get(series);
-
- Path fillPath = (Path) children.get(0);
- Path linePath = (Path) children.get(1);
-
- var cssPath = fillPath.getStyleClass();
- var cssLine = linePath.getStyleClass();
-
- if(!visible) {
- if(!cssPath.contains("hide-series-symbol")) {
- cssPath.add("hide-series-symbol");
- cssLine.add("hide-series-symbol");
-
- //Also add the hide artifact to the symbols
- for (Iterator> it = getDisplayedDataIterator(series); it.hasNext();) {
- Node symbol = it.next().getNode();
- symbol.getStyleClass().add("hide-series-symbol");
- }
- }
- }else {
- if(cssPath.contains("hide-series-symbol")) {
- cssPath.remove("hide-series-symbol");
- cssLine.add("hide-series-symbol");
- //Also add the hide artifact to the symbols
- for (Iterator> it = getDisplayedDataIterator(series); it.hasNext();) {
- Node symbol = it.next().getNode();
- symbol.getStyleClass().remove("hide-series-symbol");
- }
- }
- makePaths(series, constructedPath, fillPath, linePath, SortingPolicy.X_AXIS,xAxis,yAxis);
- }
- }
- protected void layoutScatterSeries(Series series,Axis xAxis, Axis yAxis) {
-
- boolean visible = seriesVisibility.get(series);
-
-
for (Iterator> it = getDisplayedDataIterator(series); it.hasNext();) {
Data item = it.next();
// We don't have access to the item's current value circumvent using chart
@@ -787,14 +905,14 @@ protected void layoutScatterSeries(Series series,Axis xAxis, Axis yA
return;
}
Node symbol = item.getNode();
-
+
var css = symbol.getStyleClass();
- if(!visible) {
- if(!css.contains("hide-series-symbol")) {
+ if (!visible) {
+ if (!css.contains("hide-series-symbol")) {
css.add("hide-series-symbol");
}
- }else {
- if(css.contains("hide-series-symbol")) {
+ } else {
+ if (css.contains("hide-series-symbol")) {
css.remove("hide-series-symbol");
}
@@ -807,125 +925,379 @@ protected void layoutScatterSeries(Series series,Axis xAxis, Axis yA
}
}
- // Add data item
- protected void addAreaItem(Series series, int itemIndex, Data item) {
- // TODO add animation code
- createSymbols(series, itemIndex, item, AREA_CHART_SYMBOL_CSS_CLASS);
- getPlotChildren().add(item.getNode());
- }
+ protected void makePaths(Series series, List constructedPath, Path fillPath, Path linePath,
+ SortingPolicy sortAxis, Axis xAxis, Axis yAxis) {
- protected void addScatterItem(Series series, int itemIndex, Data item) {
+ double yAnimMultiplier = seriesYMultiplierMap.get(series).get();
+ final double hlw = linePath.getStrokeWidth() / 2.0;
+ final boolean sortX = (sortAxis == SortingPolicy.X_AXIS);
+ final boolean sortY = (sortAxis == SortingPolicy.Y_AXIS);
+ final double dataXMin = sortX ? -hlw : Double.NEGATIVE_INFINITY;
+ final double dataXMax = sortX ? xAxis.getWidth() + hlw : Double.POSITIVE_INFINITY;
+ final double dataYMin = sortY ? -hlw : Double.NEGATIVE_INFINITY;
+ final double dataYMax = sortY ? yAxis.getHeight() + hlw : Double.POSITIVE_INFINITY;
+ LineTo prevDataPoint = null;
+ LineTo nextDataPoint = null;
+ constructedPath.clear();
+ for (Iterator> it = getDisplayedDataIterator(series); it.hasNext();) {
+ Data item = it.next();
+ double x = xAxis.getDisplayPosition(getCurrentDisplayedXValue(item));
+ double y = yAxis.getDisplayPosition(
+ yAxis.toRealValue(yAxis.toNumericValue(getCurrentDisplayedYValue(item)) * yAnimMultiplier));
+ boolean skip = (Double.isNaN(x) || Double.isNaN(y));
+ Node symbol = item.getNode();
+ if (symbol != null) {
+ final double w = symbol.prefWidth(-1);
+ final double h = symbol.prefHeight(-1);
+ if (skip) {
+ symbol.resizeRelocate(-w * 2, -h * 2, w, h);
+ } else {
+ symbol.resizeRelocate(x - (w / 2), y - (h / 2), w, h);
+ }
+ }
+ if (skip)
+ continue;
+ if (x < dataXMin || y < dataYMin) {
+ if (prevDataPoint == null) {
+ prevDataPoint = new LineTo(x, y);
+ } else if ((sortX && prevDataPoint.getX() <= x) || (sortY && prevDataPoint.getY() <= y)) {
+ prevDataPoint.setX(x);
+ prevDataPoint.setY(y);
+ }
+ } else if (x <= dataXMax && y <= dataYMax) {
+ constructedPath.add(new LineTo(x, y));
+ } else {
+ if (nextDataPoint == null) {
+ nextDataPoint = new LineTo(x, y);
+ } else if ((sortX && x <= nextDataPoint.getX()) || (sortY && y <= nextDataPoint.getY())) {
+ nextDataPoint.setX(x);
+ nextDataPoint.setY(y);
+ }
+ }
+ }
- createSymbols(series, itemIndex, item, SCATTER_CHART_SYMBOL_CSS_CLASS);
- Node symbol = item.getNode();
+ if (!constructedPath.isEmpty() || prevDataPoint != null || nextDataPoint != null) {
+ if (sortX) {
+ Collections.sort(constructedPath, (e1, e2) -> Double.compare(e1.getX(), e2.getX()));
+ } else if (sortY) {
+ Collections.sort(constructedPath, (e1, e2) -> Double.compare(e1.getY(), e2.getY()));
+ } else {
+ // assert prevDataPoint == null && nextDataPoint == null
+ }
+ if (prevDataPoint != null) {
+ constructedPath.add(0, prevDataPoint);
+ }
+ if (nextDataPoint != null) {
+ constructedPath.add(nextDataPoint);
+ }
- // add and fade in new symbol if animated
- if (shouldAnimate()) {
- symbol.setOpacity(0);
- getPlotChildren().add(symbol);
- FadeTransition ft = new FadeTransition(Duration.millis(500), symbol);
- ft.setToValue(1);
- ft.play();
- } else {
- getPlotChildren().add(symbol);
+ // assert !constructedPath.isEmpty()
+ LineTo first = constructedPath.get(0);
+ LineTo last = constructedPath.get(constructedPath.size() - 1);
+
+ final double displayYPos = first.getY();
+
+ ObservableList lineElements = linePath.getElements();
+ lineElements.clear();
+ lineElements.add(new MoveTo(first.getX(), displayYPos));
+ lineElements.addAll(constructedPath);
+
+ if (fillPath != null) {
+ ObservableList fillElements = fillPath.getElements();
+ fillElements.clear();
+ double yOrigin = yAxis.getDisplayPosition(yAxis.toRealValue(0.0));
+
+ fillElements.add(new MoveTo(first.getX(), yOrigin));
+ fillElements.addAll(constructedPath);
+ fillElements.add(new LineTo(last.getX(), yOrigin));
+ fillElements.add(new ClosePath());
+ }
}
}
- protected void addLineItem(Series series, int itemIndex, Data item) {
+ @Override
+ protected void seriesAdded(Series series, int seriesIndex) {
+
+ int freeIndex = availableColors.nextClearBit(0) % availableColors.size();
+ availableColors.set(freeIndex);
+
+ seriesColorMap.put(series, freeIndex);
+
+ // Chart specific setup
+ TypedSeries ser = typedSeries.get(series);
+
+ switch (ser.getSeriesType()) {
+ case AREA:
+ // create new paths for series
+ Path seriesLine = new Path();
+ Path fillPath = new Path();
+ seriesLine.setStrokeLineJoin(StrokeLineJoin.BEVEL);
+ Group areaGroup = new Group(fillPath, seriesLine);
+ series.setNode(areaGroup);
+ // create series Y multiplier
+ DoubleProperty seriesYAnimMultiplier = new SimpleDoubleProperty(this, "seriesYMultiplier");
+ seriesYMultiplierMap.put(series, seriesYAnimMultiplier);
+ seriesYAnimMultiplier.setValue(1d);
+ getPlotChildren().add(areaGroup);
+ break;
+ case LINE:
+ // Line and area charts require a path node
+ seriesLine = new Path();
+ seriesLine.setStrokeLineJoin(StrokeLineJoin.BEVEL);
+ series.setNode(seriesLine);
+ // create series Y multiplier
+ seriesYAnimMultiplier = new SimpleDoubleProperty(this, "seriesYMultiplier");
+ seriesYMultiplierMap.put(series, seriesYAnimMultiplier);
+ seriesYAnimMultiplier.setValue(1d);
+ getPlotChildren().add(seriesLine);
+ break;
+
+ case BAR:
+ //TODO
+ Orientation orientation = Orientation.VERTICAL;
+
+ Map> categoryMap = new HashMap>();
+ for (int j = 0; j < series.getData().size(); j++) {
+ Data item = series.getData().get(j);
+ Node bar = createBar(series, seriesIndex, item, j);
+ String category;
+ if (orientation == Orientation.VERTICAL) {
+ category = (String) item.getXValue();
+ } else {
+ category = (String) item.getYValue();
+ }
+ categoryMap.put(category, item);
+
+ // RT-21164 check if bar value is negative to add NEGATIVE_STYLE style class
+ double barVal = (orientation == Orientation.VERTICAL) ? ((Number) item.getYValue()).doubleValue()
+ : ((Number) item.getXValue()).doubleValue();
+ if (barVal < 0) {
+ bar.getStyleClass().add(NEGATIVE_STYLE);
+ }
+ getPlotChildren().add(bar);
+
+ }
+ if (categoryMap.size() > 0)
+ seriesCategoryMap.put(series, categoryMap);
+ break;
+
+ case SCATTER:
+ // No need to set up
+ break;
+ case STACKED_AREA:
+ case STACKED_BAR:
+ throw new UnsupportedOperationException("Not implemented yet");
+ default:
+ break;
- // Create the node
- if (typedSeries.get(series).showSymbolsProperty().get()) {
- createSymbols(series, itemIndex, item, LINE_CHART_SYMBOL_CSS_CLASS);
}
- Node symbol = item.getNode();
- if (symbol != null) {
- getPlotChildren().add(symbol);
+ // Finally add the individual data points
+ for (int j = 0; j < series.getData().size(); j++) {
+ dataItemAdded(series, j, series.getData().get(j));
}
+ }
- // TODO add animation code
+ @Override
+ protected void seriesChanged(ListChangeListener.Change extends Series> c) {
+
+ for (int i = 0; i < getData().size(); i++) {
+ final Series s = getData().get(i);
+
+ TypedSeries type = typedSeries.get(s);
+
+ String seriesColor = COLOR_CSS_CLASS + seriesColorMap.get(s);
+
+ switch (type.getSeriesType()) {
+ case AREA:
+ Path seriesLine = (Path) ((Group) s.getNode()).getChildren().get(1);
+ Path fillPath = (Path) ((Group) s.getNode()).getChildren().get(0);
+ seriesLine.getStyleClass().setAll(AREA_CHART_SERIES_LINE_CSS_CLASS, "series" + i, seriesColor);
+ fillPath.getStyleClass().setAll(AREA_CHART_FILL_CSS_CLASS, "series" + i, seriesColor);
+ for (int j = 0; j < s.getData().size(); j++) {
+ final Data item = s.getData().get(j);
+ final Node node = item.getNode();
+ if (node != null)
+ node.getStyleClass().setAll(AREA_CHART_SYMBOL_CSS_CLASS, "series" + i, "data" + j, seriesColor);
+ }
+ break;
+ case LINE:
+ Node seriesNode = s.getNode();
+ if (seriesNode != null)
+ seriesNode.getStyleClass().setAll(LINE_CHART_LINE_CSS_CLASS, "series" + i, seriesColor);
+ for (int j = 0; j < s.getData().size(); j++) {
+ final Node symbol = s.getData().get(j).getNode();
+ if (symbol != null)
+ symbol.getStyleClass().setAll(LINE_CHART_SYMBOL_CSS_CLASS, "series" + i, "data" + j,
+ seriesColor);
+ }
+ break;
+
+ case SCATTER:
+ for (int j = 0; j < s.getData().size(); j++) {
+ final Node symbol = s.getData().get(j).getNode();
+ if (symbol != null)
+ symbol.getStyleClass().setAll(SCATTER_CHART_SYMBOL_CSS_CLASS, "series" + i, "data" + j,
+ seriesColor);
+ }
+ break;
+ case STACKED_AREA:
+ case STACKED_BAR:
+ default:
+ throw new UnsupportedOperationException("Not implemented yet");
+ }
+
+ }
}
- private void createSymbols(Series series, int itemIndex, Data item, String symbolCSSIdentifier) {
- Node symbol = item.getNode();
- // TODO should we also check here is e.g. scatter chart ticks should be drawn?
- if (symbol == null) {
- symbol = new StackPane();
- symbol.setAccessibleRole(AccessibleRole.TEXT);
- symbol.setAccessibleRoleDescription("Point");
- symbol.focusTraversableProperty().bind(Platform.accessibilityActiveProperty());
- item.setNode(symbol);
+ // public double
+
+ @Override
+ protected void seriesRemoved(Series series) {
+
+ SeriesType removedSeriesType = typedSeries.remove(series).getSeriesType();
+
+ if (shouldAnimate()) {
+
+ int animationDuration = 0;
+ switch (removedSeriesType) {
+ case LINE:
+ animationDuration = 900;
+ break;
+ case SCATTER:
+ animationDuration = 400;
+ break;
+ default:
+ animationDuration = 500;
+ }
+
+ // Can't we use this for all chart types? Why do we need an additional one for
+ // catter?
+
+ // What happened to the exceptions?
+ // TODO do we want to use reflection or simply copy and paste the method?
+ try {
+ Method method = XYChart.class.getMethod("createSeriesRemoveTimeLine", Series.class, Integer.class);
+ method.setAccessible(true);
+ KeyFrame[] keyframes = (KeyFrame[]) method.invoke(this, series, animationDuration);
+ method.setAccessible(false);
+ Timeline tl = new Timeline(keyframes);
+ tl.play();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ } else {
+
+ // Not all types of charts use series nodes. Check before removing
+ if (series.getNode() != null) {
+ getPlotChildren().remove(series.getNode());
+ }
+
+ // Everything else is the same
+ for (final Data d : series.getData()) {
+ final Node symbol = d.getNode();
+ getPlotChildren().remove(symbol);
+ }
+ removeSeriesFromDisplay(series);
}
- if (symbol != null) {
- symbol.getStyleClass().addAll(symbolCSSIdentifier, "series" + getData().indexOf(series), "data" + itemIndex,
- COLOR_CSS_CLASS + seriesColorMap.get(series));
- }
+ // if (shouldAnimate()) {
+ // ParallelTransition pt = new ParallelTransition();
+ // pt.setOnFinished(event -> {
+ // removeSeriesFromDisplay(series);
+ // });
+ // for (final Data d : series.getData()) {
+ // final Node symbol = d.getNode();
+ // // fade out old symbol
+ // FadeTransition ft = new FadeTransition(Duration.millis(500), symbol);
+ // ft.setToValue(0);
+ // ft.setOnFinished(actionEvent -> {
+ // getPlotChildren().remove(symbol);
+ // symbol.setOpacity(1.0);
+ // });
+ // pt.getChildren().add(ft);
+ // }
+ // pt.play();
+ // }
+
}
- // Go with the more specific implementation for line and area charts.
- // TODO we could auto range to not stick to the 0 line
+ protected void toggleSeriesVisability(Series series, boolean b) {
+ seriesVisibility.put(series, b);
+ this.requestChartLayout();
+ }
+
+ /*
+ * Value markers
+ *
+ * 3 ways we can implement value markers. 1. Either we create a separate series
+ * 2. Draw a line on top using stackpanes 3. draw on the plot area emulating
+ * grid line approach
+ *
+ * 1. Is a bit more tricky if we want seperate colors and custom labels
+ * therefore le
+ */
@Override
protected void updateAxisRange() {
- //default axis
+ // default axis
final Axis xa = getXAxis();
final Axis ya = getYAxis();
-
- //Feetch axis related data
- HashMap> xData = new HashMap<>();
- HashMap> yData = new HashMap<>();
-
+
+ // Feetch axis related data
+ HashMap> xData = new HashMap<>();
+ HashMap> yData = new HashMap<>();
+
/*
* Check if we have at least one axis which requires automatic layouting
*/
boolean oneAutoRanging = false;
-
- //Default axis
+
+ // Default axis
if (xa.isAutoRanging()) {
xData.put(0, new ArrayList());
oneAutoRanging = true;
}
-
+
if (ya.isAutoRanging()) {
yData.put(0, new ArrayList());
oneAutoRanging = true;
}
-
+
for (var entry : additionalYAxis.entrySet()) {
-
- if(entry.getValue().isAutoRanging()) {
+
+ if (entry.getValue().isAutoRanging()) {
yData.put(entry.getKey(), new ArrayList());
oneAutoRanging = true;
}
}
-
- for (var entry : additionalXAxis.entrySet()) {
- if(entry.getValue().isAutoRanging()) {
+
+ for (var entry : additionalXAxis.entrySet()) {
+ if (entry.getValue().isAutoRanging()) {
xData.put(entry.getKey(), new ArrayList());
oneAutoRanging = true;
}
}
if (oneAutoRanging) {
-
- //Collect data for each axis
+
+ // Collect data for each axis
for (Series series : getData()) {
-
- //If the series is not visible don't include it in the axis range
- if(!seriesVisibility.get(series)) {
+
+ // If the series is not visible don't include it in the axis range
+ if (!seriesVisibility.get(series)) {
continue;
}
-
- //To which axis does it belong to?
+
+ // To which axis does it belong to?
var typed = typedSeries.get(series);
int yAxisIndex = typed.getYAxisIndex();
int xAxisIndex = typed.getXAxisIndex();
-
+
var xDataAxis = xData.get(xAxisIndex);
var yDataAxis = yData.get(yAxisIndex);
-
- if(xDataAxis == null && yDataAxis == null) {
+
+ if (xDataAxis == null && yDataAxis == null) {
continue;
}
for (Data data : series.getData()) {
@@ -935,178 +1307,46 @@ protected void updateAxisRange() {
yDataAxis.add(data.getYValue());
}
}
-
- //Push new data to each axis
- for(var entry : xData.entrySet()) {
-
+
+ // Push new data to each axis
+ for (var entry : xData.entrySet()) {
+
int axisIndex = entry.getKey();
List xDataPoints = entry.getValue();
Axis axis;
-
- if(axisIndex == 0) {
+
+ if (axisIndex == 0) {
axis = xa;
- }else {
+ } else {
axis = additionalXAxis.get(axisIndex);
}
-
- if (xDataPoints != null && !xDataPoints.isEmpty() && !(xDataPoints.size() == 1 && axis.toNumericValue(xDataPoints.get(0)) == 0)) {
+
+ if (xDataPoints != null && !xDataPoints.isEmpty()
+ && !(xDataPoints.size() == 1 && axis.toNumericValue(xDataPoints.get(0)) == 0)) {
axis.invalidateRange(xDataPoints);
}
}
-
- for(var entry : yData.entrySet()) {
-
+
+ for (var entry : yData.entrySet()) {
+
int axisIndex = entry.getKey();
List yDataPoints = entry.getValue();
Axis axis;
-
- if(axisIndex == 0) {
+
+ if (axisIndex == 0) {
axis = ya;
- }else {
+ } else {
axis = additionalYAxis.get(axisIndex);
}
-
- if (yDataPoints != null && !yDataPoints.isEmpty() && !(yDataPoints.size() == 1 && axis.toNumericValue(yDataPoints.get(0)) == 0)) {
- axis.invalidateRange(yDataPoints);
- }
- }
- }
- }
-
- // utility
- protected int getDataSizeMultiType() {
- final ObservableList> data = getData();
- return (data != null) ? data.size() : 0;
- }
-
- protected void makePaths(Series series, List constructedPath, Path fillPath, Path linePath,
- SortingPolicy sortAxis, Axis xAxis, Axis yAxis) {
- double yAnimMultiplier = seriesYMultiplierMap.get(series).get();
- final double hlw = linePath.getStrokeWidth() / 2.0;
- final boolean sortX = (sortAxis == SortingPolicy.X_AXIS);
- final boolean sortY = (sortAxis == SortingPolicy.Y_AXIS);
- final double dataXMin = sortX ? -hlw : Double.NEGATIVE_INFINITY;
- final double dataXMax = sortX ? xAxis.getWidth() + hlw : Double.POSITIVE_INFINITY;
- final double dataYMin = sortY ? -hlw : Double.NEGATIVE_INFINITY;
- final double dataYMax = sortY ? yAxis.getHeight() + hlw : Double.POSITIVE_INFINITY;
- LineTo prevDataPoint = null;
- LineTo nextDataPoint = null;
- constructedPath.clear();
- for (Iterator> it = getDisplayedDataIterator(series); it.hasNext();) {
- Data item = it.next();
- double x = xAxis.getDisplayPosition(getCurrentDisplayedXValue(item));
- double y = yAxis.getDisplayPosition(
- yAxis.toRealValue(yAxis.toNumericValue(getCurrentDisplayedYValue(item)) * yAnimMultiplier));
- boolean skip = (Double.isNaN(x) || Double.isNaN(y));
- Node symbol = item.getNode();
- if (symbol != null) {
- final double w = symbol.prefWidth(-1);
- final double h = symbol.prefHeight(-1);
- if (skip) {
- symbol.resizeRelocate(-w * 2, -h * 2, w, h);
- } else {
- symbol.resizeRelocate(x - (w / 2), y - (h / 2), w, h);
- }
- }
- if (skip)
- continue;
- if (x < dataXMin || y < dataYMin) {
- if (prevDataPoint == null) {
- prevDataPoint = new LineTo(x, y);
- } else if ((sortX && prevDataPoint.getX() <= x) || (sortY && prevDataPoint.getY() <= y)) {
- prevDataPoint.setX(x);
- prevDataPoint.setY(y);
- }
- } else if (x <= dataXMax && y <= dataYMax) {
- constructedPath.add(new LineTo(x, y));
- } else {
- if (nextDataPoint == null) {
- nextDataPoint = new LineTo(x, y);
- } else if ((sortX && x <= nextDataPoint.getX()) || (sortY && y <= nextDataPoint.getY())) {
- nextDataPoint.setX(x);
- nextDataPoint.setY(y);
+ if (yDataPoints != null && !yDataPoints.isEmpty()
+ && !(yDataPoints.size() == 1 && axis.toNumericValue(yDataPoints.get(0)) == 0)) {
+ axis.invalidateRange(yDataPoints);
}
}
}
-
- if (!constructedPath.isEmpty() || prevDataPoint != null || nextDataPoint != null) {
- if (sortX) {
- Collections.sort(constructedPath, (e1, e2) -> Double.compare(e1.getX(), e2.getX()));
- } else if (sortY) {
- Collections.sort(constructedPath, (e1, e2) -> Double.compare(e1.getY(), e2.getY()));
- } else {
- // assert prevDataPoint == null && nextDataPoint == null
- }
- if (prevDataPoint != null) {
- constructedPath.add(0, prevDataPoint);
- }
- if (nextDataPoint != null) {
- constructedPath.add(nextDataPoint);
- }
-
- // assert !constructedPath.isEmpty()
- LineTo first = constructedPath.get(0);
- LineTo last = constructedPath.get(constructedPath.size() - 1);
-
- final double displayYPos = first.getY();
-
- ObservableList lineElements = linePath.getElements();
- lineElements.clear();
- lineElements.add(new MoveTo(first.getX(), displayYPos));
- lineElements.addAll(constructedPath);
-
- if (fillPath != null) {
- ObservableList fillElements = fillPath.getElements();
- fillElements.clear();
- double yOrigin = yAxis.getDisplayPosition(yAxis.toRealValue(0.0));
-
- fillElements.add(new MoveTo(first.getX(), yOrigin));
- fillElements.addAll(constructedPath);
- fillElements.add(new LineTo(last.getX(), yOrigin));
- fillElements.add(new ClosePath());
- }
- }
}
-
- public void setSeriesVisibility(TypedSeries series, boolean b) {
- toggleSeriesVisability(series.getSeries(),b);
-
- LegendItem lItem = legendMap.get(series.getSeries());
-
- //Get the legends and update it accordingly
- Node symbol = lItem.getSymbol();
- ObservableList cssClass = symbol.getStyleClass();
- //TODO duplicated code.
- if(cssClass.contains("hide-series")) {
- symbol.setEffect(null);
- symbol.getStyleClass().remove("hide-series");
- toggleSeriesVisability(series.getSeries(),true);
- }else {
- symbol.getStyleClass().add("hide-series");
- ColorAdjust colorAdjust = new ColorAdjust();
- colorAdjust.setSaturation(-0.5);
- symbol.setEffect(colorAdjust);
- toggleSeriesVisability(series.getSeries(),false);
- }
-
- }
-
- protected void toggleSeriesVisability(Series series, boolean b) {
- seriesVisibility.put(series,b);
- this.requestChartLayout();
- }
-
- /**
- *
- * @param series
- * @return true if the series is displayed false if it is hidden
- */
- public boolean isSeriesVisible(TypedSeries series) {
- return seriesVisibility.get(series.getSeries());
- }
-
@Override
protected void updateLegend() {
legendMap = new LinkedHashMap<>();
@@ -1124,23 +1364,23 @@ protected void updateLegend() {
"chart-symbol"); // This css class contains background-color of the chart. Just go ahead and use
// it
LegendItem lItem = new LegendItem(name, symbol);
-
+
lItem.addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler() {
@Override
public void handle(MouseEvent event) {
-
+
ObservableList cssClass = symbol.getStyleClass();
-
- if(cssClass.contains("hide-series")) {
+
+ if (cssClass.contains("hide-series")) {
symbol.setEffect(null);
symbol.getStyleClass().remove("hide-series");
- toggleSeriesVisability(series,true);
- }else {
+ toggleSeriesVisability(series, true);
+ } else {
symbol.getStyleClass().add("hide-series");
ColorAdjust colorAdjust = new ColorAdjust();
colorAdjust.setSaturation(-0.5);
symbol.setEffect(colorAdjust);
- toggleSeriesVisability(series,false);
+ toggleSeriesVisability(series, false);
}
}
});
@@ -1164,83 +1404,33 @@ public void handle(MouseEvent event) {
*
*/
- // public double
-
- /**
- * Get the JavaFX X coordinate of the item
- *
- * @param item
- * @return
- */
- public X getCoordinate(Data item) {
- return this.getCurrentDisplayedXValue(item);
+ // Bar chart specific
+ // TODO
+ private double getCategoryGap() {
+ return 10;
}
- /**
- * Get the JavaFX Y coordinate of the item
- *
- * @param item
- * @return
- */
- public Y getYCoordinate(Data item) {
- return this.getCurrentDisplayedYValue(item);
+ private double getBarGap() {
+ return 4;
}
- /*
- * Value markers
- *
- * 3 ways we can implement value markers. 1. Either we create a separate series
- * 2. Draw a line on top using stackpanes 3. draw on the plot area emulating
- * grid line approach
- *
- * 1. Is a bit more tricky if we want seperate colors and custom labels
- * therefore le
- */
-
- public void addValueMarker(ValueMarker> markerToAdd) {
- markerLock.lock();
- Path p = new Path();
- valueMarkers.put(markerToAdd, p);
-
- ChangeListener cl = (obs, oldValue, newValue) -> {
- if (newValue) {
- getChartChildren().add(((ValueMarker>) obs).getLabel());
- } else {
- getChartChildren().remove(((ValueMarker>) obs).getLabel());
- }
- requestChartLayout();
- };
-
- valueMarkerLabelMap.put(markerToAdd, cl);
- markerToAdd.enableLabelProperty().addListener(cl);
-
- // Add it to the chart
- getChartChildren().add(p);
-
- if (markerToAdd.enableLabelProperty().get()) {
- getChartChildren().add(markerToAdd.getLabel());
- }
-
- markerLock.unlock();
+ private Data getDataItem(Series series, String category) {
+ Map> catmap = seriesCategoryMap.get(series);
+ return (catmap != null) ? catmap.get(category) : null;
}
- public void removeValueMarker(ValueMarker> markerToRemove) {
- markerLock.lock();
- Path p = valueMarkers.remove(markerToRemove);
- if (p != null) {
- getChartChildren().remove(p);
- }
-
- // We also have to remove the listener or else we leake memory
- ChangeListener l = valueMarkerLabelMap.remove(markerToRemove);
- if (l != null) {
- markerToRemove.enableLabelProperty().removeListener(l);
+ private Node createBar(Series series, int seriesIndex, final Data item, int itemIndex) {
+ Node bar = item.getNode();
+ if (bar == null) {
+ bar = new StackPane();
+ bar.setAccessibleRole(AccessibleRole.TEXT);
+ bar.setAccessibleRoleDescription("Bar");
+ bar.focusTraversableProperty().bind(Platform.accessibilityActiveProperty());
+ item.setNode(bar);
}
-
- markerLock.unlock();
+ bar.getStyleClass().setAll("chart-bar", "series" + seriesIndex, "data" + itemIndex,
+ "default-color" + seriesColorMap.get(series));
+ return bar;
}
-
-
-
}
diff --git a/src/main/java/com/github/kilianB/TypedSeries.java b/src/main/java/com/github/kilianB/TypedSeries.java
index 18a12d9..9d97ecf 100644
--- a/src/main/java/com/github/kilianB/TypedSeries.java
+++ b/src/main/java/com/github/kilianB/TypedSeries.java
@@ -121,15 +121,16 @@ public static Builder builder(Series series) {
public interface ITypeStage{
- IAxisStage line();
+ ILineAxisStage line();
+ IBarAxisStage bar();
IAxisStage scatter();
IAxisStage area();
+
}
public interface ILineAxisStage{
//Only line chart options
- ILineAxisStage withXAxisSortingPolicy(SortingPolicy sortingPolicy);
-
+ IAxisStage withXAxisSortingPolicy(SortingPolicy sortingPolicy);
IAxisStage withXAxisIndex(int xAxisIndex);
IAxisStage withXAxisSide(Side side);
IAxisStage withYAxisIndex(int yAxisIndex);
@@ -137,6 +138,12 @@ public interface ILineAxisStage{
TypedSeries build();
}
+ public interface IBarAxisStage{
+ //Which axis is the categorical axis?
+ IBarAxisStage withDomainAxisIndex(int index);
+ IBarAxisStage withRangeAxisIndex(int index );
+ }
+
public interface IAxisStage{
IAxisStage withXAxisIndex(int xAxisIndex);
@@ -147,7 +154,7 @@ public interface IAxisStage{
TypedSeries build();
}
- public static final class Builder implements ITypeStage,ILineAxisStage,IAxisStage{
+ public static final class Builder implements ITypeStage,ILineAxisStage,IAxisStage,IBarAxisStage{
private Series series;
private ReadOnlyObjectWrapper seriesType;
@@ -168,7 +175,7 @@ private Builder(Series series) {
}
@Override
- public IAxisStage line() {
+ public ILineAxisStage line() {
seriesType = new ReadOnlyObjectWrapper(SeriesType.LINE);
return this;
}
@@ -186,7 +193,13 @@ public IAxisStage area() {
}
@Override
- public ILineAxisStage withXAxisSortingPolicy(SortingPolicy sortingPolicy) {
+ public IBarAxisStage bar() {
+ seriesType = new ReadOnlyObjectWrapper(SeriesType.BAR);
+ return this;
+ }
+
+ @Override
+ public IAxisStage withXAxisSortingPolicy(SortingPolicy sortingPolicy) {
xAxisSortingPolicy = sortingPolicy;
return this;
}
@@ -196,6 +209,18 @@ public IAxisStage withXAxisIndex(int xAxisIndex) {
this.xAxisIndex = xAxisIndex;
return this;
}
+
+ @Override
+ public IBarAxisStage withDomainAxisIndex(int index) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public IBarAxisStage withRangeAxisIndex(int index) {
+ // TODO Auto-generated method stub
+ return null;
+ }
@Override
public IAxisStage withXAxisSide(Side side) {
@@ -234,9 +259,8 @@ public TypedSeries build() {
return new TypedSeries(series,seriesType,yAxisIndex,xAxisIndex,xAxisSortingPolicy,
xAxisSide,yAxisSide);
}
-
-
+
}
}
\ No newline at end of file
diff --git a/src/main/java/com/github/kilianB/ValueMarker.java b/src/main/java/com/github/kilianB/ValueMarker.java
index ba8241e..b3fd480 100644
--- a/src/main/java/com/github/kilianB/ValueMarker.java
+++ b/src/main/java/com/github/kilianB/ValueMarker.java
@@ -6,6 +6,12 @@
import javafx.scene.control.Label;
import javafx.scene.paint.Color;
+/**
+ * A value marker represents a horizontal or vertical line overlaid over the chart.
+ * @author Kilian
+ *
+ * @param type of the axis
+ */
public class ValueMarker{
// public enum Direction{
diff --git a/src/main/java/com/github/kilianB/renderer/AreaRenderer.java b/src/main/java/com/github/kilianB/renderer/AreaRenderer.java
new file mode 100644
index 0000000..6461628
--- /dev/null
+++ b/src/main/java/com/github/kilianB/renderer/AreaRenderer.java
@@ -0,0 +1,57 @@
+package com.github.kilianB.renderer;
+
+import java.util.Iterator;
+
+import javafx.collections.ObservableList;
+import javafx.scene.Group;
+import javafx.scene.Node;
+import javafx.scene.chart.Axis;
+import javafx.scene.chart.LineChart.SortingPolicy;
+import javafx.scene.chart.XYChart.Data;
+import javafx.scene.chart.XYChart.Series;
+import javafx.scene.shape.Path;
+
+/**
+ * @author Kilian
+ *
+ */
+public class AreaRenderer implements Renderer {
+
+ @Override
+ public void layout(Series series, Axis xAxis, Axis yAxis, boolean visible) {
+ final ObservableList children = ((Group) series.getNode()).getChildren();
+
+ Path fillPath = (Path) children.get(0);
+ Path linePath = (Path) children.get(1);
+
+ var cssPath = fillPath.getStyleClass();
+ var cssLine = linePath.getStyleClass();
+
+ if (!visible) {
+ if (!cssPath.contains("hide-series-symbol")) {
+ cssPath.add("hide-series-symbol");
+ cssLine.add("hide-series-symbol");
+
+ // Also add the hide artifact to the symbols
+ for (Iterator> it = getDisplayedDataIterator(series); it.hasNext();) {
+ Node symbol = it.next().getNode();
+ symbol.getStyleClass().add("hide-series-symbol");
+ }
+ }
+ } else {
+ if (cssPath.contains("hide-series-symbol")) {
+ cssPath.remove("hide-series-symbol");
+ cssLine.add("hide-series-symbol");
+ // Also add the hide artifact to the symbols
+ for (Iterator> it = getDisplayedDataIterator(series); it.hasNext();) {
+ Node symbol = it.next().getNode();
+ symbol.getStyleClass().remove("hide-series-symbol");
+ }
+ }
+ makePaths(series, constructedPath, fillPath, linePath, SortingPolicy.X_AXIS, xAxis, yAxis);
+ }
+ }
+
+
+
+}
diff --git a/src/main/java/com/github/kilianB/renderer/LineRenderer.java b/src/main/java/com/github/kilianB/renderer/LineRenderer.java
new file mode 100644
index 0000000..f6ff6b9
--- /dev/null
+++ b/src/main/java/com/github/kilianB/renderer/LineRenderer.java
@@ -0,0 +1,18 @@
+package com.github.kilianB.renderer;
+
+import javafx.scene.chart.Axis;
+import javafx.scene.chart.XYChart.Series;
+
+/**
+ * @author Kilian
+ *
+ */
+public class LineRenderer implements Renderer{
+
+ @Override
+ public void layout(Series series, Axis xAxis, Axis yAxis, boolean visible) {
+ // TODO Auto-generated method stub
+
+ }
+
+}
diff --git a/src/main/java/com/github/kilianB/renderer/Renderer.java b/src/main/java/com/github/kilianB/renderer/Renderer.java
new file mode 100644
index 0000000..4333274
--- /dev/null
+++ b/src/main/java/com/github/kilianB/renderer/Renderer.java
@@ -0,0 +1,14 @@
+package com.github.kilianB.renderer;
+
+import javafx.scene.chart.Axis;
+import javafx.scene.chart.XYChart.Series;
+
+/**
+ * @author Kilian
+ *
+ */
+public interface Renderer {
+
+ void layout(Series