/* Reversound is used to get the music sheet of a piece from a music file. Copyright (C) 2014 Gabriel AUGENDRE Copyright (C) 2014 Gabriel DIENY Copyright (C) 2014 Arthur GAUCHER Copyright (C) 2014 Gabriel LEPETIT-AIMON This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package gui.graphs; /* * Copyright (c) 2008, 2013 Oracle and/or its affiliates. * All rights reserved. Use is subject to license terms. * * This file is available and licensed under the following license: * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the distribution. * - Neither the name of Oracle Corporation nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ import javafx.collections.ObservableList; import javafx.scene.Node; import javafx.scene.chart.Axis; import javafx.scene.chart.CategoryAxis; import javafx.scene.chart.XYChart; import javafx.scene.shape.Path; import java.util.ArrayList; import java.util.Iterator; import java.util.List; /** * Un graphe XY customisé pour afficher des barres de longueur varriable et de * coordonnées variables. * * Adapté de CandleStickChart.java dans la démo Ensemble8 de javafx. * Originellement définit comme : "A CandleChart is a style of candle-chart used primarily * to describe price movements of a security, derivative, * or currency over time. * The Data Y value is used for the opening price and then the close, high and * low values are stored in the Data's extra value property using a * CandleExtraValues object." * * Modifié pour dessiner des barres horizontales plutôt que verticales et pour * accepter un String comme donnée en Y */ public class TraceChart extends XYChart { // -------------- CONSTRUCTORS ---------------------------------------------- /** * Construct a new CandleStickChart with the given axis. */ public TraceChart(Axis xAxis, Axis yAxis) { super(xAxis, yAxis); setAnimated(false); xAxis.setAnimated(false); yAxis.setAnimated(false); } /** * Construct a new TraceChart with the given axis and data. */ public TraceChart(Axis xAxis, Axis yAxis, ObservableList> data) { this(xAxis, yAxis); setData(data); } // -------------- METHODS ------------------------------------------------------------------------------------------ /** Called to update and layout the content for the plot */ @Override protected void layoutPlotChildren() { // we have nothing to layout if no data is present if (getData() == null) { return; } // update candle positions for (int seriesIndex = 0; seriesIndex < getData().size(); seriesIndex++) { XYChart.Series series = getData().get(seriesIndex); Iterator> iter = getDisplayedDataIterator(series); Path seriesPath = null; if (series.getNode() instanceof Path) { seriesPath = (Path) series.getNode(); seriesPath.getElements().clear(); } while (iter.hasNext()) { XYChart.Data item = iter.next(); double x = getXAxis().getDisplayPosition(getCurrentDisplayedXValue(item)); double y = getYAxis().getDisplayPosition(getCurrentDisplayedYValue(item)); Node itemNode = item.getNode(); BarExtraValues extra = (BarExtraValues) item.getExtraValue(); if (itemNode instanceof Bar && extra != null) { Bar bar = (Bar) itemNode; double close = getXAxis().getDisplayPosition((extra.getClose())); // calculate candle width double barHeight = -1; if (getYAxis() instanceof CategoryAxis) { CategoryAxis yAxis = (CategoryAxis) getYAxis(); barHeight = yAxis.getHeight()/yAxis.getCategories().size(); // no gap between ticks*/ } // update candle bar.update(close - x, barHeight, extra.getColor()); // position the candle bar.setLayoutX(x); bar.setLayoutY(y); } } } } @Override protected void dataItemChanged(XYChart.Data item) { } @Override protected void dataItemAdded(XYChart.Series series, int itemIndex, XYChart.Data item) { Node bar = createBar(item); getPlotChildren().add(bar); // always draw average line on top if (series.getNode() != null) { series.getNode().toFront(); } } @Override protected void dataItemRemoved(XYChart.Data item, XYChart.Series series) { final Node bar = item.getNode(); getPlotChildren().remove(bar); } @Override protected void seriesAdded(XYChart.Series series, int seriesIndex) { // handle any data already in series for (int j = 0; j < series.getData().size(); j++) { XYChart.Data item = series.getData().get(j); Node bar = createBar(item); getPlotChildren().add(bar); } // create series path Path seriesPath = new Path(); //useless :) //seriesPath.getStyleClass().setAll("candlestick-average-line", "series" + seriesIndex); series.setNode(seriesPath); getPlotChildren().add(seriesPath); } @Override protected void seriesRemoved(XYChart.Series series) { // remove all candle nodes for (XYChart.Data d : series.getData()) { final Node bar = d.getNode(); getPlotChildren().remove(bar); } } /** * Create a new Bar node to represent a single data item */ private Node createBar(final XYChart.Data item) { Node bar = item.getNode(); BarExtraValues extra = (BarExtraValues) item.getExtraValue(); // check if bar has already been created if (bar instanceof Bar) { ((Bar) bar).setSeriesAndDataStyleClasses(extra.getColor()); } else { bar = new Bar(extra.getColor()); item.setNode(bar); } return bar; } /** * This is called when the range has been invalidated and we need to update it. If the axis are auto * ranging then we compile a list of all data that the given axis has to plot and call invalidateRange() on the * axis passing it that data. */ @Override protected void updateAxisRange() { // For candle stick chart we need to override this method as we need to let the axis know that they need to be able // to cover the whole area occupied by the high to low range not just its center data value final Axis xAxis = getXAxis(); final Axis yAxis = getYAxis(); List xData = null; List yData = null; if (xAxis.isAutoRanging()) { xData = new ArrayList(); } if (yAxis.isAutoRanging()) { yData = new ArrayList(); } if (xData != null || yData != null) { for (XYChart.Series series : getData()) { for (XYChart.Data data : series.getData()) { if (yData != null) { yData.add(data.getYValue()); } if (xData != null) { BarExtraValues extras = (BarExtraValues) data.getExtraValue(); if (extras != null) { //yData.addNote(extras.getHigh()); //yData.addNote(extras.getLow()); } else { xData.add(data.getXValue()); } } } } if (xData != null) { xAxis.invalidateRange(xData); } if (yData != null) { yAxis.invalidateRange(yData); } } } }