2015-08-08

記者の眼 - Windows 10が話題の今、Linuxをお薦めする7つの理由:ITpro

Linux はきっと今後、Windows と匹敵するほどのポピュラーな OS になるに違いないと、Linux を使い始めた当時は思っていました。

「当時」とは昔の話になってしまいますが Windows 95 が発売された 1995 年前後です。ちょうど Tcl/Tk を盛んに使い始めていた時期で、会社のワークステーションであった SunOS/Solaris と(同じではないものの)同じような環境を、Linux で極めて廉価に自宅の非力な PC 上で構築できたことがかなりの衝撃だったのです。もともと Windows 上で Visual C++ などのいろいろなコンパイラ環境を買い求めることに金銭的に疲れ、もっと手軽にプログラミングを楽しめる環境が欲しかったことが Linux を使い始めた動機でした。当時はデュアルブートで Windows と Linux の両方を使っていたので、結局 Visual Studio 6.0 までは買い続けましたが、その後は Linux を中心としたプログラミング環境へ完全に移行しました。今では、自宅で新旧合わせて三台の PC が稼働していますが全て Linux です。

一方、世の中は、20 年前に自分が思ったこととは異なり、デスクトップやノート PC 市場で Linux は普及していません1.。それどころか、コンシューマ向け PC そのものの市場が縮小しているようです2.。家電量販店の、いわゆる PC コーナーは勢いが無いばかりか、店内の売場面積も縮小されているようにさえ見えます。

そんな中で、久しぶりに Linux を薦める記事を読みました。

この記事を読んで、多くの人や企業が PC に求めることはなんだろうと考えさせられました。

自宅で使うのであれば、きっと、家電量販店あるいはネットショップから手ごろな PC を選んで、自宅に設置してすぐ使えるものであればそれで十分なのだと思います。それが Windows PC なのです。企業にしても、デスクトップ用途には、サポートあるいは苦情先がはっきりしている、納入業者が薦める Windows PC を購入しているのが大半でしょう。Windows サーバの Active Directory 下で企業のネットワークを構築してしまえば、そこそこ動いてしまうだろうし、サポートも契約次第でバッチリです。自分の経験では、昔々は電子メールを含むインターネット関係のサーバーは UNIX 系 OS でカバーしていましたので、そういう管理をする UNIX のシステム管理者がいましたが、今では、Windows の知識だけで管理できてしまいます。Microsoft 社がここまで Windows を社会に普及させたことは偉業なのでしょう。

だから、特に個人で使用する場合、OS を気にする人以外は、一番簡単な方法を選べばよいのでしょう。PC が古くなって速度が気になれば新しい PC に買い替えるべきでしょう。Linux だって、PC の性能が上がればそれだけ力を発揮します。古い PC でも Linux だったら十分使えるのかもしれませんが、果たして使う人はそれで満足するのでしょうか。

Windows PC が非力で、少しでも効率的に動かしたいと考えていた時代は、Linux はソリューションのひとつになりえました。しかし今や 4G, 8G のメモリ搭載は当たり前になり、必要があればもっと搭載することも可能な時代になりました。会社のチームでリモート接続で利用できる解析用の Windows PC に搭載されているメモリが 128GB だと最近知りびっくりしました。これだけ搭載されていれば、そうそうリソース不足には悩まされることはないでしょう。CPU も強力ですので、Windows とか Linux とかもはや関係なさそうです。これはまだ極端な例なのかもしれませんが、OS に何を使うべきか、なんて議論は、もう時代遅れになってしまったのかもしれません。

そう、使いたければ、好きな OS を選べばよいのです。サーバ分野で Linux は活躍しているから、Linux ファンとしては良しとしないと…3.

参考サイト

  1. Operating system market share
  2. タブレット端末、今年度にノートPCと出荷台数が逆転へ | RBB TODAY(2015年6月22日)
  3. Usage share of operating systems - Wikipedia, the free encyclopedia

2015-08-02

【備忘録】Tomcat のインストールと起動

Apache Tomcat は、Java Servlet や JavaServer Pages (JSP) を実行するための Web コンテナです。

Fedora 22 において、Apache HTTP Server の方は、パッケージ (httpd-2.4.16-1.fc22.x86_64) をインストールしてサービスを起動すれば、とにかく Web サーバーが起動しましたが、tomcat (tomcat-7.0.59-4.fc22.noarch) の方はそうはいきませんでした。調べてみたら下記にぴったりのサイトがありましたので試してみました。

# dnf install tomcat-webapps tomcat-admin-webapps
Last metadata expiration check performed 1:05:53 ago on Sun Aug  2 13:28:37 2015.
Dependencies resolved.
================================================================================
 Package                   アーキテクチャ
                                       バージョン             リポジトリー
                                                                           容量
================================================================================
インストールしています:
 tomcat-admin-webapps      noarch      1:7.0.59-4.fc22        fedora       44 k
 tomcat-webapps            noarch      1:7.0.59-4.fc22        fedora      359 k

トランザクションの要約
================================================================================
インストール  2 Packages

総ダウンロード容量: 403 k
インストール済み容量: 1.2 M
これでいいですか? [y/N]y
パッケージをダウンロードしています:
(1/2): tomcat-admin-webapps-7.0.59-4.fc22.noarc 361 kB/s |  44 kB     00:00    
(2/2): tomcat-webapps-7.0.59-4.fc22.noarch.rpm  1.8 MB/s | 359 kB     00:00    
--------------------------------------------------------------------------------
合計                                            148 kB/s | 403 kB     00:02     
Running transaction check
Transaction check succeeded.
Running transaction test
Transaction test succeeded.
Running transaction
  インストールしています: tomcat-admin-webapps-1:7.0.59-4.fc22.noarch       1/2 
  インストールしています: tomcat-webapps-1:7.0.59-4.fc22.noarch             2/2 
  Verifying             : tomcat-webapps-1:7.0.59-4.fc22.noarch             1/2 
  Verifying             : tomcat-admin-webapps-1:7.0.59-4.fc22.noarch       2/2 

インストール:
  tomcat-admin-webapps.noarch 1:7.0.59-4.fc22                                   
  tomcat-webapps.noarch 1:7.0.59-4.fc22                                         

完了しました!
# systemctl enable tomcat.service
Created symlink from /etc/systemd/system/multi-user.target.wants/tomcat.service to /usr/lib/systemd/system/tomcat.service.
# systemctl start tomcat.service
# ls -l /usr/share/tomcat/
合計 4
drwxr-xr-x. 2 root root   4096  4月 25 09:55 bin
lrwxrwxrwx. 1 root tomcat   11  3月 20 18:29 conf -> /etc/tomcat
lrwxrwxrwx. 1 root tomcat   22  3月 20 18:29 lib -> /usr/share/java/tomcat
lrwxrwxrwx. 1 root tomcat   15  3月 20 18:29 logs -> /var/log/tomcat
lrwxrwxrwx. 1 root tomcat   22  3月 20 18:29 temp -> /var/cache/tomcat/temp
lrwxrwxrwx. 1 root tomcat   23  3月 20 18:29 webapps -> /var/lib/tomcat/webapps
lrwxrwxrwx. 1 root tomcat   22  3月 20 18:29 work -> /var/cache/tomcat/work
# lsof -i :8080
COMMAND  PID   USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
java    7086 tomcat   48u  IPv6 206231      0t0  TCP *:webcache (LISTEN)
#

念の為、ブラウザで localhost:8080 を見てみると確かにサービスが起動していることがわかります。


2015-07-20

JavaFX: チャートのカスタマイズ (2)

JavaFX のチャートを使いこなせるようになりたいと、いくつかチャートのサンプルを紹介してきましたが、前回は、LineChart クラスを継承した MyChart1 を紹介しました。今回はさらに話を進めて、XYChart から継承したサンプルを紹介します。

と言っても自分自身のオリジナルサンプルではなく、Oracle の JavaFX のサンプル(参考サイト [1])にある CandleStickeChart クラス(右図)です。

なお Candlestick Chart は、日本語ではローソク足チャートと呼ばれていて、株価などの相場の値動きを時系列に沿った図表を表す時の手法の一つです。

動作環境

動作環境は次の通りです。

  • OS: Fedora 22 x86_64
  • jdk1.8.0_51-1.8.0_51-fcs.x86_64 (Oracle)
  • IDE: NetBeans IDE 8.0.2

配布ライセンス

なお、今回紹介するサンプルは、少し手を加えてはあるものの、Oracle が配布しているサンプルほぼそのままです。この記事の全てのサンプルの配布は以下のライセンスに従っています。

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.

CandleStickChart クラス

リスト:CandleStickChart.java
package candlestickchart;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javafx.animation.FadeTransition;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.scene.Node;
import javafx.scene.chart.Axis;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.util.Duration;

/**
 * A candlestick chart is a style of bar-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
 * CandleStickExtraValues object.
 */
public class CandleStickChart extends XYChart<Number, Number> {

    // -------------- CONSTRUCTORS ----------------------------------------------
    /**
     * Construct a new CandleStickChart with the given axis.
     *
     * @param xAxis The x axis to use
     * @param yAxis The y axis to use
     */
    public CandleStickChart(Axis<Number> xAxis, Axis<Number> yAxis) {
        super(xAxis, yAxis);
        getStylesheets().add(getClass().getResource("CandleStickChart.css").toExternalForm());
        setAnimated(false);
        xAxis.setAnimated(false);
        yAxis.setAnimated(false);
    }

    /**
     * Construct a new CandleStickChart with the given axis and data.
     *
     * @param xAxis The x axis to use
     * @param yAxis The y axis to use
     * @param data The data to use, this is the actual list used so any changes
     * to it will be reflected in the chart
     */
    public CandleStickChart(Axis<Number> xAxis, Axis<Number> yAxis, ObservableList<XYChart.Series<Number, Number>> 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<Number, Number> series = getData().get(seriesIndex);
            Iterator<XYChart.Data<Number, Number>> iter = getDisplayedDataIterator(series);
            Path seriesPath = null;
            if (series.getNode() instanceof Path) {
                seriesPath = (Path) series.getNode();
                seriesPath.getElements().clear();
            }
            while (iter.hasNext()) {
                XYChart.Data<Number, Number> item = iter.next();
                double x = getXAxis().getDisplayPosition(getCurrentDisplayedXValue(item));
                double y = getYAxis().getDisplayPosition(getCurrentDisplayedYValue(item));
                Node itemNode = item.getNode();
                CandleStickExtraValues extra = (CandleStickExtraValues) item.getExtraValue();
                if (itemNode instanceof Candle && extra != null) {
                    Candle candle = (Candle) itemNode;

                    double close = getYAxis().getDisplayPosition(extra.getClose());
                    double high = getYAxis().getDisplayPosition(extra.getHigh());
                    double low = getYAxis().getDisplayPosition(extra.getLow());

                    // calculate candle width
                    double candleWidth = -1;
                    if (getXAxis() instanceof NumberAxis) {
                        NumberAxis xa = (NumberAxis) getXAxis();
                        candleWidth = xa.getDisplayPosition(xa.getTickUnit()) * 0.90; // use 90% width between ticks
                    }

                    // update candle
                    candle.update(close - y, high - y, low - y, candleWidth);
                    candle.updateTooltip(item.getYValue().doubleValue(), extra.getClose(), extra.getHigh(), extra.getLow());

                    // position the candle
                    candle.setLayoutX(x);
                    candle.setLayoutY(y);
                }
                if (seriesPath != null) {
                    if (seriesPath.getElements().isEmpty()) {
                        seriesPath.getElements().add(new MoveTo(x, getYAxis().getDisplayPosition(extra.getAverage())));
                    } else {
                        seriesPath.getElements().add(new LineTo(x, getYAxis().getDisplayPosition(extra.getAverage())));
                    }
                }
            }
        }
    }

    @Override
    protected void dataItemChanged(XYChart.Data<Number, Number> item) {
    }

    @Override
    protected void dataItemAdded(XYChart.Series<Number, Number> series, int itemIndex, XYChart.Data<Number, Number> item) {
        Node candle = createCandle(getData().indexOf(series), item, itemIndex);
        if (shouldAnimate()) {
            candle.setOpacity(0);
            getPlotChildren().add(candle);

            // fade in new candle
            FadeTransition ft = new FadeTransition(Duration.millis(500), candle);
            ft.setToValue(1);
            ft.play();
        } else {
            getPlotChildren().add(candle);
        }

        // always draw average line on top
        if (series.getNode() != null) {
            series.getNode().toFront();
        }
    }

    @Override
    protected void dataItemRemoved(XYChart.Data<Number, Number> item, XYChart.Series<Number, Number> series) {
        final Node candle = item.getNode();

        if (shouldAnimate()) {
            // fade out old candle
            FadeTransition ft = new FadeTransition(Duration.millis(500), candle);
            ft.setToValue(0);
            ft.setOnFinished((ActionEvent actionEvent) -> {
                getPlotChildren().remove(candle);
            });
            ft.play();
        } else {
            getPlotChildren().remove(candle);
        }
    }

    @Override
    protected void seriesAdded(XYChart.Series<Number, Number> 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 candle = createCandle(seriesIndex, item, j);
            if (shouldAnimate()) {
                candle.setOpacity(0);
                getPlotChildren().add(candle);

                // fade in new candle
                FadeTransition ft = new FadeTransition(Duration.millis(500), candle);
                ft.setToValue(1);
                ft.play();
            } else {
                getPlotChildren().add(candle);
            }
        }
        // create series path
        Path seriesPath = new Path();
        seriesPath.getStyleClass().setAll("candlestick-average-line", "series" + seriesIndex);
        series.setNode(seriesPath);
        getPlotChildren().add(seriesPath);
    }

    @Override
    protected void seriesRemoved(XYChart.Series<Number, Number> series) {
        // remove all candle nodes
        series.getData().stream().map((d) -> d.getNode()).forEach((candle) -> {
            if (shouldAnimate()) {

                // fade out old candle
                FadeTransition ft = new FadeTransition(Duration.millis(500), candle);
                ft.setToValue(0);
                ft.setOnFinished((ActionEvent actionEvent) -> {
                    getPlotChildren().remove(candle);
                });
                ft.play();
            } else {
                getPlotChildren().remove(candle);
            }
        });
    }

    /**
     * Create a new Candle node to represent a single data item
     *
     * @param seriesIndex The index of the series the data item is in
     * @param item The data item to create node for
     * @param itemIndex The index of the data item in the series
     * @return New candle node to represent the give data item
     */
    private Node createCandle(int seriesIndex, final XYChart.Data item, int itemIndex) {
        Node candle = item.getNode();
        // check if candle has already been created
        if (candle instanceof Candle) {
            ((Candle) candle).setSeriesAndDataStyleClasses("series" + seriesIndex, "data" + itemIndex);
        } else {
            candle = new Candle("series" + seriesIndex, "data" + itemIndex);
            item.setNode(candle);
        }
        return candle;
    }

    /**
     * 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<Number> xa = getXAxis();
        final Axis<Number> ya = getYAxis();
        List<Number> xData = null;
        List<Number> yData = null;
        if (xa.isAutoRanging()) {
            xData = new ArrayList<>();
        }
        if (ya.isAutoRanging()) {
            yData = new ArrayList<>();
        }
        if (xData != null || yData != null) {
            for (XYChart.Series<Number, Number> series : getData()) {
                for (XYChart.Data<Number, Number> data : series.getData()) {
                    if (xData != null) {
                        xData.add(data.getXValue());
                    }
                    if (yData != null) {
                        CandleStickExtraValues extras = (CandleStickExtraValues) data.getExtraValue();
                        if (extras != null) {
                            yData.add(extras.getHigh());
                            yData.add(extras.getLow());
                        } else {
                            yData.add(data.getYValue());
                        }
                    }
                }
            }
            if (xData != null) {
                xa.invalidateRange(xData);
            }
            if (yData != null) {
                ya.invalidateRange(yData);
            }
        }
    }
}
リスト:Candle.java
package candlestickchart;

import javafx.scene.Group;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.Region;
import javafx.scene.shape.Line;

/** Candle node used for drawing a candle */
public class Candle extends Group {
    private final Line highLowLine = new Line();
    private final Region bar = new Region();
    private String seriesStyleClass;
    private String dataStyleClass;
    private boolean openAboveClose = true;
    private final Tooltip tooltip = new Tooltip();

    Candle(String seriesStyleClass, String dataStyleClass) {
        setAutoSizeChildren(false);
        getChildren().addAll(highLowLine, bar);
        this.seriesStyleClass = seriesStyleClass;
        this.dataStyleClass = dataStyleClass;
        updateStyleClasses();
        tooltip.setGraphic(new TooltipContent());
        Tooltip.install(bar, tooltip);
    }

    public void setSeriesAndDataStyleClasses(String seriesStyleClass, String dataStyleClass) {
        this.seriesStyleClass = seriesStyleClass;
        this.dataStyleClass = dataStyleClass;
        updateStyleClasses();
    }

    public void update(double closeOffset, double highOffset, double lowOffset, double candleWidth) {
        openAboveClose = closeOffset > 0;
        updateStyleClasses();
        highLowLine.setStartY(highOffset);
        highLowLine.setEndY(lowOffset);
        if (candleWidth == -1) {
            candleWidth = bar.prefWidth(-1);
        }
        if (openAboveClose) {
            bar.resizeRelocate(-candleWidth / 2, 0, candleWidth, closeOffset);
        } else {
            bar.resizeRelocate(-candleWidth / 2, closeOffset, candleWidth, closeOffset * -1);
        }
    }

    public void updateTooltip(double open, double close, double high, double low) {
        TooltipContent tooltipContent = (TooltipContent) tooltip.getGraphic();
        tooltipContent.update(open, close, high, low);
    }

    private void updateStyleClasses() {
        getStyleClass().setAll("candlestick-candle", seriesStyleClass, dataStyleClass);
        highLowLine.getStyleClass().setAll("candlestick-line", seriesStyleClass, dataStyleClass,
                openAboveClose ? "open-above-close" : "close-above-open");
        bar.getStyleClass().setAll("candlestick-bar", seriesStyleClass, dataStyleClass,
                openAboveClose ? "open-above-close" : "close-above-open");
    }
}
リスト:CandleStickeExtractValues.java
package candlestickchart;

/** Data extra values for storing close, high and low. */
public class CandleStickExtraValues  {
    private final double close;
    private final double high;
    private final double low;
    private final double average;

    public CandleStickExtraValues(double close, double high, double low, double average) {
        this.close = close;
        this.high = high;
        this.low = low;
        this.average = average;
    }

    public double getClose() {
        return close;
    }

    public double getHigh() {
        return high;
    }

    public double getLow() {
        return low;
    }

    public double getAverage() {
        return average;
    }

    @Override
    public String toString() {
        return "CandleStickExtraValues{" + "close=" + close + ", high=" + high + ", low=" + low + ", average=" + average + '}';
    }
}

ツールチップに表示する内容は、日本語に換えてあります。

リスト:TooltipContent.java
package candlestickchart;

import javafx.scene.control.Label;
import javafx.scene.layout.GridPane;

/**
 * The content for Candle tool tips
 */
public class TooltipContent  extends GridPane {
    private final Label openValue = new Label();
    private final Label closeValue = new Label();
    private final Label highValue = new Label();
    private final Label lowValue = new Label();

    TooltipContent() {
        Label open = new Label("始値:");
        Label close = new Label("終値:");
        Label high = new Label("高値:");
        Label low = new Label("安値:");
        open.getStyleClass().add("candlestick-tooltip-label");
        close.getStyleClass().add("candlestick-tooltip-label");
        high.getStyleClass().add("candlestick-tooltip-label");
        low.getStyleClass().add("candlestick-tooltip-label");
        setConstraints(open, 0, 0);
        setConstraints(openValue, 1, 0);
        setConstraints(close, 0, 1);
        setConstraints(closeValue, 1, 1);
        setConstraints(high, 0, 2);
        setConstraints(highValue, 1, 2);
        setConstraints(low, 0, 3);
        setConstraints(lowValue, 1, 3);
        getChildren().addAll(open, openValue, close, closeValue, high, highValue, low, lowValue);
    }

    public void update(double open, double close, double high, double low) {
        openValue.setText(Double.toString(open));
        closeValue.setText(Double.toString(close));
        highValue.setText(Double.toString(high));
        lowValue.setText(Double.toString(low));
    }
}

CandleStickChart の CSS では、チャートの基本的なフォントサイズなどの修飾も追加してあります。

リスト:CandleStickChart.css
/* ====== CANDLE STICK CHART =========================================================== */
.candlestick-tooltip-label {
    -fx-font-size: 9pt;
    -fx-font-weight: bold;
    -fx-text-fill: white;
    -fx-padding: 2 5 2 0;
}
.candlestick-average-line {
    -fx-stroke: #106ece;
    -fx-stroke-width: 2px;
}
.candlestick-candle {
    -fx-effect: dropshadow( two-pass-box , rgba(0,0,0,0.4) , 10, 0.0 , 2 , 4 );
}
.candlestick-line {
    -fx-stroke: gray;
    -fx-stroke-width: 2px;
}
.candlestick-bar {
    -fx-padding: 5;
    -candlestick-bar-fill: red;
    -fx-background-color: linear-gradient(derive(-candlestick-bar-fill,-30%), derive(-candlestick-bar-fill,-40%)),
        linear-gradient(derive(-candlestick-bar-fill,100%), derive(-candlestick-bar-fill, 10%)),
        linear-gradient(derive(-candlestick-bar-fill,30%), derive(-candlestick-bar-fill,-10%));
    -fx-background-insets: 0,1,2;
}
.candlestick-bar.close-above-open {
    -candlestick-bar-fill: greenyellow;
}
.candlestick-bar.open-above-close {
    -candlestick-bar-fill: lightpink;
}

.chart {
    -fx-padding: 10px;
    -fx-background-color: white;
}
 
.chart-content {
    -fx-padding: 10px;    
}
 
.chart-title {
    -fx-font-size: 18pt;    
}
 
 
.axis-label {
    -fx-font-size: 14pt;
}
 
.axis {
    -fx-tick-label-font: 12pt system;
}

実行例

CandleStickChart クラスを利用したサンプルを下記に紹介します。東証など日本の株式の履歴は、無料では長い期間を参照できなかったので、Yahoo Finance へアクセスして US の某社の株価データを使用しました。

リスト:CandleStickChartSample.java
package candlestickchart.sample;

import candlestickchart.CandleStickChart;
import candlestickchart.CandleStickExtraValues;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.stage.Stage;

public class CandleStickChartSample extends Application {

    // DAY, OPEN, CLOSE, HIGH, LOW, AVERAGE
    private static final double[][] data = new double[][]{
        {1, 17.9, 17.9, 17.69, 17.77, 17.795},
        {2, 18.43, 18.48, 17.64, 17.87, 18.06},
        {3, 18.9, 18.99, 18.52, 18.57, 18.755},
        {4, 19.31, 19.42, 18.86, 18.89, 19.14},
        {5, 18.9, 19.63, 18.87, 19.31, 19.25},
        {6, 18.83, 18.99, 18.72, 18.77, 18.855},
        {7, 18.74, 18.91, 18.51, 18.52, 18.71},
        {8, 19, 19.04, 18.52, 18.55, 18.78},
        {9, 19.23, 19.3, 18.73, 19.17, 19.015},
        {10, 19.38, 19.72, 19.26, 19.27, 19.49},
        {11, 19.55, 19.64, 19.45, 19.61, 19.545},
        {12, 19.34, 19.66, 19.34, 19.51, 19.5},
        {13, 19.28, 19.41, 19.06, 19.22, 19.235},
        {14, 19.59, 19.59, 19.19, 19.21, 19.39},
        {15, 19.96, 20.16, 19.63, 19.78, 19.895},
        {16, 20.22, 20.28, 20.01, 20.06, 20.145},
        {17, 20.24, 20.46, 20.08, 20.1, 20.27},
        {18, 19.84, 20.13, 19.83, 19.96, 19.98},
        {19, 19.79, 19.95, 19.7, 19.86, 19.825},
        {20, 19.83, 19.93, 19.68, 19.72, 19.805},
        {21, 19.62, 19.86, 19.58, 19.84, 19.72},
        {22, 19.79, 19.9, 19.52, 19.54, 19.71},
        {23, 19.64, 19.85, 19.57, 19.82, 19.71},
        {24, 19.45, 19.79, 19.34, 19.71, 19.565},
        {25, 19.74, 19.79, 19.59, 19.62, 19.69},
        {26, 20.04, 20.11, 19.74, 19.78, 19.925},
        {27, 20, 20.14, 19.96, 20.04, 20.05},
        {28, 19.82, 20.01, 19.71, 19.91, 19.86},
        {29, 20.14, 20.24, 19.81, 19.87, 20.025},
        {30, 20.04, 20.23, 19.93, 20.15, 20.08},
        {31, 20.24, 20.31, 19.86, 20.06, 20.085},
        {32, 20.54, 20.59, 20.33, 20.36, 20.46},
        {33, 20.27, 20.59, 20.09, 20.38, 20.34},
        {34, 20.03, 20.34, 20.03, 20.31, 20.185},
        {35, 20.43, 20.43, 20.06, 20.13, 20.245},
        {36, 20.34, 20.6, 20.29, 20.36, 20.445},
        {37, 19.96, 20.5, 19.96, 20.34, 20.23},
        {38, 20.11, 20.24, 19.88, 19.99, 20.06},
        {39, 20.18, 20.28, 20.07, 20.14, 20.175},
        {40, 19.83, 20.26, 19.82, 20.15, 20.04},
        {41, 19.95, 20.07, 19.87, 19.91, 19.97},
        {42, 20.2, 20.33, 19.93, 19.99, 20.13},
        {43, 20.26, 20.34, 20.02, 20.19, 20.18},
        {44, 20.57, 20.59, 20.12, 20.2, 20.355},
        {45, 20.03, 20.1, 19.79, 19.86, 19.945},
        {46, 19.95, 20.04, 19.78, 19.93, 19.91},
        {47, 19.91, 19.99, 19.78, 19.89, 19.885},
        {48, 19.8, 19.96, 19.73, 19.92, 19.845},
        {49, 19.78, 19.85, 19.65, 19.69, 19.75},
        {50, 19.21, 19.67, 19.2, 19.56, 19.435},
        {51, 19.45, 19.56, 19.17, 19.45, 19.365},
        {52, 19.77, 19.84, 19.32, 19.36, 19.58},
        {53, 19.98, 20.08, 19.86, 19.88, 19.97},
        {54, 19.93, 20.11, 19.82, 19.98, 19.965},
        {55, 19.92, 19.92, 19.65, 19.79, 19.785},
        {56, 19.95, 20.01, 19.63, 20.01, 19.82},
        {57, 20.13, 20.49, 19.99, 20.04, 20.24},
        {58, 20.53, 20.55, 19.64, 19.97, 20.095},
        {59, 22.27, 22.35, 21.75, 21.8, 22.05},
        {60, 22.14, 22.24, 21.95, 22.17, 22.095},
        {61, 22.26, 22.42, 22.01, 22.31, 22.215},
        {62, 22.27, 22.67, 22.06, 22.29, 22.365},
        {63, 21.71, 21.93, 21.68, 21.76, 21.805},
        {64, 21.7, 21.76, 21.37, 21.49, 21.565},
        {65, 22.2, 22.22, 21.71, 21.88, 21.965},
        {66, 22.06, 22.71, 22.05, 22.55, 22.38}
    };

    private CandleStickChart chart;
    private NumberAxis xAxis;
    private NumberAxis yAxis;

    public Parent createContent() {
        xAxis = new NumberAxis(0, 67, 1);
        xAxis.setMinorTickCount(0);
        yAxis = new NumberAxis(17, 23, 1);
        chart = new CandleStickChart(xAxis, yAxis);
        chart.setTitle("ローソク足チャートのサンプル");

        // setup chart
        xAxis.setLabel("日付(順序)");
        yAxis.setLabel("株価 (USD)");

        // add starting data
        XYChart.Series<Number, Number> series = new XYChart.Series<>();
        for (double[] day : data) {
            series.getData().add(new XYChart.Data<>(day[0], day[5], new CandleStickExtraValues(day[1], day[2], day[3], day[4])));
        }

        ObservableList<XYChart.Series<Number, Number>> stockData = chart.getData();
        if (stockData == null) {
            stockData = FXCollections.observableArrayList(series);
            chart.setData(stockData);
        } else {
            chart.getData().add(series);
        }
        return chart;
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        Scene scene = new Scene(createContent(), 600, 400);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    /**
     * Java main for when running without JavaFX launcher
     *
     * @param args command line arguments
     */
    public static void main(String[] args) {
        launch(args);
    }
}

ローソク足チャートは、XYChart を継承して、カスタマイズしたチャートを作成したい時に大いに参考となるサンプルですが、このサンプルをちょっと変更するだけでも使い道がありそうです。例えば、エラーバーをトレンドチャートに追加するときや、ボックスプロットのトレンドを作成するときなどです。

 

参考サイト

  1. JavaFX Samples