Странное поведение свечного графика JFreechart при перетаскивании

Это дополнительный вопрос из этого вопроса.

Происходит следующее:

Когда я запускаю график и перетаскиваю его, происходит что-то странное: через определенный интервал, кажется, каждые 7 периодов свечи становятся все меньше и меньше, пока не превратятся в полосу. Затем, когда я тяну дальше, они снова становятся толще, пока снова не станут нормального размера. Кажется, это происходит каждые 7 периодов.

Пример этого явления показан на следующих 3 изображениях:

введите здесь описание изображениявведите здесь описание изображения введите здесь описание изображения

Следующий код покажет именно то, что я имею в виду. Просто скомпилируйте и запустите его. Затем нажмите и удерживайте CTRL, а затем щелкните и удерживайте мышь на графике. Теперь попробуйте перетащить график вправо или влево. После определенного «расстояния перетаскивания» вы заметите ошибку.

Мой вопрос: как предотвратить/обойти это?

Код:

    import org.jfree.chart.*;
    import org.jfree.chart.axis.*;
    import org.jfree.chart.plot.XYPlot;
    import org.jfree.chart.renderer.xy.CandlestickRenderer;
    import org.jfree.data.xy.*;

    import javax.swing.*;
    import java.awt.*;
    import java.io.*;
    import java.net.URL;
    import java.text.*;
    import java.util.*;
    import java.util.List;

    public class CandlestickDemo2 extends JFrame {
        public CandlestickDemo2(String stockSymbol) {
            super("CandlestickDemo");
            this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

            DateAxis    domainAxis       = new DateAxis("Date");
            NumberAxis  rangeAxis        = new NumberAxis("Price");
            CandlestickRenderer renderer = new CandlestickRenderer();
            XYDataset   dataset          = getDataSet(stockSymbol);

            XYPlot mainPlot = new XYPlot(dataset, domainAxis, rangeAxis, renderer);

            //Do some setting up, see the API Doc
            renderer.setSeriesPaint(0, Color.BLACK);
            renderer.setDrawVolume(false);
            rangeAxis.setAutoRangeIncludesZero(false);
            domainAxis.setTimeline( SegmentedTimeline.newMondayThroughFridayTimeline() );

            //Now create the chart and chart panel
            JFreeChart chart = new JFreeChart(stockSymbol, null, mainPlot, false);
            ChartPanel chartPanel = new ChartPanel(chart, false);
            chartPanel.setPreferredSize(new Dimension(600, 300));

            mainPlot.setDomainPannable(true);
            mainPlot.setRangePannable(true);

            this.add(chartPanel);
            this.pack();
        }
        protected AbstractXYDataset getDataSet(String stockSymbol) {
            //This is the dataset we are going to create
            DefaultOHLCDataset result = null;
            //This is the data needed for the dataset
            OHLCDataItem[] data;

            //This is where we go get the data, replace with your own data source
            data = getData(stockSymbol);

            //Create a dataset, an Open, High, Low, Close dataset
            result = new DefaultOHLCDataset(stockSymbol, data);

            return result;
        }
        //This method uses yahoo finance to get the OHLC data
        protected OHLCDataItem[] getData(String stockSymbol) {
            List<OHLCDataItem> dataItems = new ArrayList<OHLCDataItem>();
            try {
                String strUrl= "http://ichart.yahoo.com/table.csv?s=GOOG&a=2&b=1&c=2013&d=4&e=24&f=2013&g=d&ignore=.csv";
                URL url = new URL(strUrl);
                BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream()));
                DateFormat df = new SimpleDateFormat("y-M-d");

                String inputLine;
                in.readLine();
                while ((inputLine = in.readLine()) != null) {
                    StringTokenizer st = new StringTokenizer(inputLine, ",");

                    Date date       = df.parse( st.nextToken() );
                    double open     = Double.parseDouble( st.nextToken() );
                    double high     = Double.parseDouble( st.nextToken() );
                    double low      = Double.parseDouble( st.nextToken() );
                    double close    = Double.parseDouble( st.nextToken() );
                    double volume   = Double.parseDouble( st.nextToken() );
                    double adjClose = Double.parseDouble( st.nextToken() );

                    OHLCDataItem item = new OHLCDataItem(date, open, high, low, close, volume);
                    dataItems.add(item);
                }
                in.close();
            }
            catch (Exception e) {
                e.printStackTrace();
            }
            //Data from Yahoo is from newest to oldest. Reverse so it is oldest to newest
            Collections.reverse(dataItems);

            //Convert the list into an array
            OHLCDataItem[] data = dataItems.toArray(new OHLCDataItem[dataItems.size()]);

            return data;
        }

        public static void main(String[] args) {
            EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {
                    new CandlestickDemo2("GOOG").setVisible(true);
                }
            });
        }
    }

Обновлять

Об этой ошибке теперь сообщается на странице sourceforge JFreeChart.

Эту ошибку можно отследить здесь.


person Jean-Paul    schedule 24.08.2013    source источник
comment
@assylias: Значит, единственный недостаток - неравномерно распределенная ось? Можете ли вы показать мне, как вы изменили класс?   -  person Jean-Paul    schedule 27.08.2013
comment
Я думал, что несколько решил это, но когда я попробовал ваш пример, я получил похожее поведение... Забудьте, что я сказал. Извиняюсь.   -  person assylias    schedule 27.08.2013
comment
@assylias: Не имеет значения. Это просто очень раздражающая проблема, которую, я полагаю, не так просто решить.   -  person Jean-Paul    schedule 27.08.2013
comment
Я потратил на это довольно много времени назад, и это была головная боль - каждый раз, когда я что-то чинил, это ломало что-то еще. Моей последней мыслью (которую я никогда не пробовал) было использовать Text Axis вместо DateAxis и вручную заполнить легенду шкалы.   -  person assylias    schedule 27.08.2013
comment
@assylias: Это может сработать. К сожалению, моя программа использует dataset.getXValue(0, k), и, поскольку я использую TimeSeries, очень важно, чтобы XValue было точным (в миллисекундах). Следовательно, заполнение легенды шкалы строками (также известными как Strings, имитирующими Dates) вызовет серьезную проблему.   -  person Jean-Paul    schedule 27.08.2013


Ответы (1)


Я могу воспроизвести описанный эффект. Как и ранее, эффект виден только с SegmentedTimeline; это не очевидно с DefaultTimeline. Кажется, это совпадает с перетаскиванием «скрытых» выходных на временной шкале с понедельника по пятницу, но я не вижу очевидной ошибки.

Одним из обходных путей может быть предоставление пользователю возможности выбрать TimeLine с помощью соседнего элемента управления, как предлагается в этом примере. Поскольку DefaultTimeline равно private, вам необходимо сохранить результат из getTimeline() перед вызовом setTimeline() в обработчике элемента управления.

Приложение: Вот вариант программы, которая использует JCheckBox для переключения Timeline. Установите флажок, чтобы включить SegmentedTimeline; панорамируйте по горизонтали, чтобы увидеть эффект (щелчок, удерживая клавишу Control, в Windows; щелкните, удерживая клавишу option, в Mac).

Временная шкала по умолчанию

Сегментированная временная шкала

import org.jfree.chart.*;
import org.jfree.chart.axis.*;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.CandlestickRenderer;
import org.jfree.data.xy.*;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.io.*;
import java.net.URL;
import java.text.*;
import java.util.*;
import java.util.List;

/**
 * @see https://stackoverflow.com/a/18421887/230513
 * @see http://www.jfree.org/forum/viewtopic.php?f=10&t=24521
 */
public class CandlestickDemo2 extends JFrame {

    public CandlestickDemo2(String stockSymbol) {
        super("CandlestickDemo2");
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        final DateAxis domainAxis = new DateAxis("Date");
        NumberAxis rangeAxis = new NumberAxis("Price");
        CandlestickRenderer renderer = new CandlestickRenderer();
        XYDataset dataset = getDataSet(stockSymbol);
        XYPlot mainPlot = new XYPlot(dataset, domainAxis, rangeAxis, renderer);
        //Do some setting up, see the API Doc
        renderer.setSeriesPaint(0, Color.BLACK);
        renderer.setDrawVolume(false);
        rangeAxis.setAutoRangeIncludesZero(false);
        //Now create the chart and chart panel
        JFreeChart chart = new JFreeChart(stockSymbol, null, mainPlot, false);
        ChartPanel chartPanel = new ChartPanel(chart, false);
        chartPanel.setPreferredSize(new Dimension(600, 300));
        mainPlot.setDomainPannable(true);
        mainPlot.setRangePannable(true);
        this.add(chartPanel);
        // Add tiemline toggle
        final Timeline oldTimeline = domainAxis.getTimeline();
        final Timeline newTimeline = SegmentedTimeline.newMondayThroughFridayTimeline();
        this.add(new JCheckBox(new AbstractAction("Segmented Timeline") {
            @Override
            public void actionPerformed(ActionEvent e) {
                JCheckBox jcb = (JCheckBox) e.getSource();
                if (jcb.isSelected()) {
                    domainAxis.setTimeline(newTimeline);
                } else {
                    domainAxis.setTimeline(oldTimeline);
                }
            }
        }), BorderLayout.SOUTH);
        this.pack();
        this.setLocationRelativeTo(null);
    }

    private AbstractXYDataset getDataSet(String stockSymbol) {
        //This is the dataset we are going to create
        DefaultOHLCDataset result;
        //This is the data needed for the dataset
        OHLCDataItem[] data;
        //This is where we go get the data, replace with your own data source
        data = getData(stockSymbol);
        //Create a dataset, an Open, High, Low, Close dataset
        result = new DefaultOHLCDataset(stockSymbol, data);
        return result;
    }
    //This method uses yahoo finance to get the OHLC data

    protected OHLCDataItem[] getData(String stockSymbol) {
        List<OHLCDataItem> dataItems = new ArrayList<OHLCDataItem>();
        try {
            String strUrl = "http://ichart.yahoo.com/table.csv?s=" + stockSymbol
                + "&a=4&b=1&c=2013&d=6&e=1&f=2013&g=d&ignore=.csv";
            URL url = new URL(strUrl);
            BufferedReader in = new BufferedReader(
                new InputStreamReader(url.openStream()));
            DateFormat df = new SimpleDateFormat("y-M-d");
            String inputLine;
            in.readLine();
            while ((inputLine = in.readLine()) != null) {
                StringTokenizer st = new StringTokenizer(inputLine, ",");
                Date date = df.parse(st.nextToken());
                double open = Double.parseDouble(st.nextToken());
                double high = Double.parseDouble(st.nextToken());
                double low = Double.parseDouble(st.nextToken());
                double close = Double.parseDouble(st.nextToken());
                double volume = Double.parseDouble(st.nextToken());
                double adjClose = Double.parseDouble(st.nextToken());
                OHLCDataItem item = new OHLCDataItem(date, open, high, low, close, volume);
                dataItems.add(item);
            }
            in.close();
        } catch (Exception e) {
            e.printStackTrace(System.err);
        }
        //Data from Yahoo is from newest to oldest. Reverse so it is oldest to newest
        Collections.reverse(dataItems);
        //Convert the list into an array
        OHLCDataItem[] data = dataItems.toArray(new OHLCDataItem[dataItems.size()]);
        return data;
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                new CandlestickDemo2("AAPL").setVisible(true);
            }
        });
    }
}
person trashgod    schedule 24.08.2013
comment
Но поскольку у меня нет данных для выходных, не изменится ли при переходе на DefaultTimeline единицы измерения на x axis так, что появятся пробелы? Возможно, для дополнительной информации: я только что нашел аналогичный вопрос на jfree. - person Jean-Paul; 24.08.2013
comment
Да; отсутствие исправления, это компромисс. Я не фанат SegmentedTimeline, но предпочтение в любом случае было бы неплохо. Я замечаю, что newFifteenMinuteTimeline() панорамирует нормально. - person trashgod; 25.08.2013
comment
Хорошо, спасибо. Как вы думаете, существует ли исправление или это невозможно по конструкции? (Если возможно, я подожду 2 дня, чтобы начать щедрость) - person Jean-Paul; 25.08.2013
comment
Я не уверен, является ли это ошибкой или просто аномалией родительской реализации pan(). Смело оставляйте вопрос открытым. Если у вас есть иллюстрированный sscce, вы можете опубликовать его на форуме или в системе отслеживания ошибок проекта. Используйте ссылку share выше, чтобы получить значок диктора. - person trashgod; 25.08.2013
comment
Хорошо. Спасибо за ваше время и рекомендации. Я постараюсь привлечь к этому больше внимания. - person Jean-Paul; 25.08.2013
comment
@trashgod Вы пытались использовать ось Text вместо DateAxis и вручную создать серию? Это должно сработать, но я никогда не пытался быть честным. - person assylias; 27.08.2013
comment
@assylias: я вижу ту же проблему с синтетическими данными. Я не понимаю ось текста. - person trashgod; 28.08.2013
comment
Отображение дат в виде строк и индексация активных дней от 0 до n (вместо использования миллисекунд). - person assylias; 28.08.2013