Happy New Year 2018!

Happy New Year! May this be a happy and fruitful year.

新年最初のエントリーは JavaFX で IBM Cloud (Bluemix) の Watson Personality Insights を利用した人格診断プログラムです。

昨年の 11 月 1 日から IBM Cloud (Bluemix)ライト・アカウントが気楽に使えるようになったので少し遊んでみました。

もう、リリースされてから随分経ちますので参考となる資料がネット上にあるのでググればなんとかそれなりのものは動かせるようです。

と言うことで定番の Twitter のつぶやきを解析して人格診断をおこなうプログラムを組んでみました。

本家サイトではこんなデモがおかれています。

1

自分のアカウントで試そうとしたけどエラーで駄目だったのでサンプルの方ので分析してみました。

2

結果が上のスクリーンショットのようになります。

Watson Personality Insights で得られる結果は下段の 「ビッグファイブ – 個性」、「欲求」、「価値」のデータです。

分析データのスコアに関する説明は以下のようされています。

スコアはすべて百分位数であり、膨大な集団の中での位置を表しています。

たとえば、外向性が90%という結果は、その人が90%外向的であることではなく、100人中その人より外向性の低い人が90人(高い人が10人)ということを意味しています。

データは JSON 形式で返されてきます。

これらの分析データの信頼性はどれ位のものかは想像もつかないのですがなかなか面白そうです。

これらの技術を利用した面白いデモも用意されています。

Science and Star Wars の「自分のライトセーバーの色を見つけよう」です。

3

ヨーダと言えば STAR WARS では生ける伝説と称されるジェダイ・マスターで絶対的存在となっています。

うれしいですね! (^_^)

これはもっと詳しく人格診断してみたくなります。

JavaFX で Watson Personality Insights を利用した人格診断プログラムを作るに当たり、分析データをレーダーチャートを使って表示させることにしました。

ところが JavaFX 標準で用意されているチャートにレーダーチャートは見当たりません。((((;゜Д゜)))))))

しかたないので JFreeChart-FX に期待して見に行ったらレーダーチャートは無かった。(見落としていたらごめんなさい)

これで一気に面倒くささハードルが上がった。

とりあえずレーダーチャートは Canvas にお絵かき対応することにしました。

できあがったプログラムを動かして人格診断を行った結果がこちらです。

4

あれれ? なんか・・・ 酷くないですか?

ヨーダと 71 パーセントも性格が一致しているわりには・・・

まぁ、人工知能が人の発言(ツイート)を分析して人格診断するとこんなもんですねと悔しいから言っておきます。(ヲヒ

せっかく年末の忙しい時期にゴニョゴニョしてプログラム組んだのにこれではあんまりだから現在の日本の内閣総理大臣である安倍晋三氏の人格診断をしてみた。

5

おおっ! ビッグファイブ – 個性 のスコアが凄い!

やはり職業柄か誠実性、外向性のポイント高いですね。

個々のスコアもそうかも知れないと思わせる結果です。(個人の感想です。)

断っておきますが私は政治にあまり詳しくなくこの結果を利用して政治批判や誹謗中傷を行うことを目的としていません。

テレビ等で見聞きする情報をもとにした感想です。

それではもう一人診断してみましょう。

私の好きな歌手の宇多田ヒカルさんです。

6

ビッグファイブ – 個性の知的好奇心、協調性、外向性のポイントが高いですね。

やはり生まれついての芸術家なのでしょう。

さて、この結果から私には個性が足りないことがバレてしまいました。

2018 年は個性をもう少しなんとかする年にしなければ。。。(^_^;

そう言うことでこのプログラムのコードを載せておきます。

ただし、かなり酷いことを平気でしています。

パスワード等はプログラムにハードコーディングしていますのでそこは適宜書き換えてくださいませ。

あと、例外処理もほとんど対処していません。

IBM Cloud (Bluemix)ライト・アカウントでお気楽に Watson Personality Insights を試してみたかったからです。

JavaFX はそれなりに手抜きプログラムにも対応してくれます。(ヲヒ 本当は駄目ですよ(^_^;

package jp.yucchi.personalitydiagnosis;

import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.stage.Stage;
import javafx.util.Duration;

/**
 *
 * @author Yucchi
 */
public class PersonalityDiagnosis extends Application {

    @Override
    public void start(Stage stage) throws Exception {

        FXMLLoader loader = new FXMLLoader(getClass().getResource("FXMLPersonalityDiagnosis.fxml"));
        loader.load();
        Parent root = loader.getRoot();
        FXMLPersonalityDiagnosisController controller = loader.getController();
        controller.setStage(stage);
        Scene scene = new Scene(root);
        scene.getStylesheets().add(this.getClass().getResource("PersonalityDiagnosisCSS.css").toExternalForm());
        stage.setScene(scene);
        Image myIcon = new Image(this.getClass().getResource("resources/sakura_icon.png").toExternalForm());
        stage.getIcons().add(myIcon);
        stage.setTitle("Personality Diagnosis for JavaFX");
        stage.setResizable(false);
        // 起動アニメーション
        DoubleProperty openOpacityProperty = new SimpleDoubleProperty(0.0);
        stage.opacityProperty().bind(openOpacityProperty);
        Timeline openTimeline = new Timeline(
                new KeyFrame(
                        new Duration(100),
                        new KeyValue(openOpacityProperty, 0.0)
                ), new KeyFrame(
                        new Duration(2_500),
                        new KeyValue(openOpacityProperty, 1.0)
                ));
        openTimeline.setCycleCount(1);
        openTimeline.play();
        stage.centerOnScreen();
        stage.show();

    }

    public static void main(String[] args) {
        launch(args);
    }

}

package jp.yucchi.personalitydiagnosis;

import com.ibm.watson.developer_cloud.personality_insights.v3.model.Profile;
import com.ibm.watson.developer_cloud.personality_insights.v3.model.Trait;
import java.io.StringReader;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.concurrent.Task;
import static javafx.concurrent.Worker.State.RUNNING;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.control.TextField;
import javafx.scene.layout.AnchorPane;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.FontSmoothingType;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
import javafx.util.Duration;
import javax.json.Json;
import javax.json.JsonObject;
import javax.json.JsonReader;
import twitter4j.Status;

/**
 *
 * @author Yucchi
 */
public class FXMLPersonalityDiagnosisController implements Initializable {

    @FXML
    private ResourceBundle resources;

    @FXML
    private URL location;

    @FXML
    private AnchorPane anchorPane;

    @FXML
    private Canvas personalityChart;

    @FXML
    private Canvas personalityCanvas;

    @FXML
    private Canvas needsChart;

    @FXML
    private Canvas needsCanvas;

    @FXML
    private Canvas valuesChart;

    @FXML
    private Canvas valuesCanvas;

    @FXML
    private Label big5Label;

    @FXML
    private Label needsLabel;

    @FXML
    private Label valueLabel;

    @FXML
    private Label idLabel;

    @FXML
    private TextField idTextField;

    @FXML
    private Button diagnosisButton;

    @FXML
    private Button cancelButton;

    @FXML
    private ProgressIndicator progressIndicator;

    @FXML
    private Button clearButton;

    @FXML
    private Label messageLabel;

    // PersonalityChart 頂点の座標
    // 知的好奇心
    private double opennessX;
    private double opennessY;
    // 誠実性
    private double conscientiousnessX;
    private double conscientiousnessY;
    // 外向性
    private double extraversionX;
    private double extraversionY;
    // 感情起伏
    private double emotionalRangeX;
    private double emotionalRangeY;
    // 協調性
    private double agreeablenessX;
    private double agreeablenessY;

    // NeedsChart 頂点の座標
    // 実用主義
    private double practicalityX;
    private double practicalityY;
    // 好奇心
    private double curiosityX;
    private double curiosityY;
    // 自己表現
    private double selfExpressionX;
    private double selfExpressionY;
    // 理想
    private double idealX;
    private double idealY;
    // 自由主義
    private double libertyX;
    private double libertyY;
    // 安定性
    private double stabilityX;
    private double stabilityY;
    // 仕組
    private double structureX;
    private double structureY;
    // 興奮
    private double excitementX;
    private double excitementY;
    // 挑戦
    private double challengeX;
    private double challengeY;
    // 社会性
    private double loveX;
    private double loveY;
    // 調和
    private double harmonyX;
    private double harmonyY;
    // 親密
    private double closenessX;
    private double closenessY;

    // ValueChart 頂点の座標
    // 快楽主義
    private double hedonismX;
    private double hedonismY;
    // 自己増進
    private double selfEnhancementX;
    private double selfEnhancementY;
    // 変化許容性
    private double opennessToChangeX;
    private double opennessToChangeY;
    // 現状維持
    private double conservationX;
    private double conservationY;
    // 自己超越
    private double selfTranscendenceX;
    private double selfTranscendenceY;

    private final ExecutorService service = Executors.newSingleThreadExecutor();
    private Task<Profile> task;
    private TweetCollection tweetCollection;
    private PersonalityAnalysis personalityAnalysis;
    private GraphicsContext personalityGc;
    private GraphicsContext needsGc;
    private GraphicsContext valuesGc;
    private double width;
    private double jpCenterHeight;
    private double offset;

    @FXML
    void cancel(ActionEvent event) {
        task.cancel();
    }

    @FXML
    void diagnosis(ActionEvent event) {
        clearChart();
        getprofile();
    }

    @FXML
    void clear(ActionEvent event) {
        clearChart();
        idTextField.clear();
        messageLabel.textProperty().unbind();
        messageLabel.setText("Please enter the Twitter ID and press the diagnosis button.");
    }

    private void getprofile() {

        progressIndicator.setOpacity(1.0);

        task = new Task<>() {

            @Override
            protected Profile call() throws Exception {

                List<Status> tweets;
                Profile profile;

                updateMessage("Getting a tweets.");
                tweets = tweetCollection.getTweets(idTextField, task);

                if (isCancelled()) {
                    return null;
                }

                updateMessage("Completed getting tweets.  Personality analysis started.  Please wait for a while.");
                profile = personalityAnalysis.toAnalyze(tweets);

                if (profile == null) {
                    failed();
                }

                return profile;

            }

            @Override
            protected void succeeded() {

                super.succeeded();

                Profile profile = getValue();
                List<Trait> personality = profile.getPersonality();
                List<Trait> needs = profile.getNeeds();
                List<Trait> values = profile.getValues();

                JsonObject jsonObject;
                try (JsonReader jsonReader = Json.createReader(new StringReader(profile.toString()))) {
                    jsonObject = jsonReader.readObject();
                }

                updateMessage("Personality analysis is completed with " + jsonObject.getInt("word_count") + " words of Tweet.");

                Map<String, Double> personalityMap = new HashMap<>(personality.size());
                Map<String, Double> valuesMap = new HashMap<>(values.size());
                Map<String, Double> needsMap = new HashMap<>(needs.size());

                for (int i = 0; personality.size() > i; i++) {
                    String key = jsonObject.getJsonArray("personality").getJsonObject(i).getJsonString("name").getString();
                    double value = jsonObject.getJsonArray("personality").getJsonObject(i).getJsonNumber("percentile").doubleValue();
                    personalityMap.put(key, value);
                }

                for (int i = 0; values.size() > i; i++) {
                    String key = jsonObject.getJsonArray("values").getJsonObject(i).getJsonString("name").getString();
                    double value = jsonObject.getJsonArray("values").getJsonObject(i).getJsonNumber("percentile").doubleValue();
                    valuesMap.put(key, value);
                }

                for (int i = 0; needs.size() > i; i++) {
                    String key = jsonObject.getJsonArray("needs").getJsonObject(i).getJsonString("name").getString();
                    double value = jsonObject.getJsonArray("needs").getJsonObject(i).getJsonNumber("percentile").doubleValue();
                    needsMap.put(key, value);
                }

                drawPersonalityResult(personalityMap);
                drawNeedsResult(needsMap);
                drawValuesResult(valuesMap);

            }

            @Override
            protected void cancelled() {
                super.cancelled();
                updateMessage("Cancelled!");
                clearChart();
            }

            @Override
            protected void failed() {
                super.failed();
                updateMessage("Failed!  An unexpected error occurred for some reason.");
            }

            private void drawPersonalityResult(Map<String, Double> personalityMap) {

                // PersonalityResult
                double openness = personalityMap.getOrDefault("知的好奇心", 0.0);
                double conscientiousness = personalityMap.getOrDefault("誠実性", 0.0);
                double extraversion = personalityMap.getOrDefault("外向性", 0.0);
                double emotionalRange = personalityMap.getOrDefault("感情起伏", 0.0);
                double agreeableness = personalityMap.getOrDefault("協調性", 0.0);

                // チャート表示用
                String opennessResult = String.format("%.2f", openness * 100).concat("%");
                String conscientiousnessResult = String.format("%.2f", conscientiousness * 100).concat("%");
                String extraversionResult = String.format("%.2f", extraversion * 100).concat("%");
                String emotionalRangeResult = String.format("%.2f", emotionalRange * 100).concat("%");
                String agreeablenessResult = String.format("%.2f", agreeableness * 100).concat("%");

                // 原点
                final double originX = personalityCanvas.getWidth() / 2;
                final double originY = personalityCanvas.getHeight() / 2;
                // ライン長さ
                final double lineLength = 150.0;
                // 項目数
                final int items = personalityMap.size();
                // 知的好奇心 スコアポジション
                final double opennessScoreX = getPositionX(originX, lineLength, openness, items, 0);
                final double opennessScoreY = getPositionY(originY, lineLength, openness, items, 0);
                // 誠実性 スコアポジション
                final double conscientiousnessScoreX = getPositionX(originX, lineLength, conscientiousness, items, 1);
                final double conscientiousnessScoreY = getPositionY(originY, lineLength, conscientiousness, items, 1);
                // 外向性 スコアポジション
                final double extraversionScoreX = getPositionX(originX, lineLength, extraversion, items, 2);
                final double extraversionScoreY = getPositionY(originY, lineLength, extraversion, items, 2);
                // 感情起伏 スコアポジション
                final double emotionalRangeScoreX = getPositionX(originX, lineLength, emotionalRange, items, 3);
                final double emotionalRangeScoreY = getPositionY(originY, lineLength, emotionalRange, items, 3);
                // 協調性 スコアポジション
                final double agreeablenessScoreX = getPositionX(originX, lineLength, agreeableness, items, 4);
                final double agreeablenessScoreY = getPositionY(originY, lineLength, agreeableness, items, 4);

                // スコア表示
                personalityGc.setStroke(Color.rgb(255, 0, 255));
                personalityGc.setLineWidth(2);
                personalityGc.beginPath();
                personalityGc.moveTo(opennessScoreX, opennessScoreY);
                personalityGc.lineTo(conscientiousnessScoreX, conscientiousnessScoreY);
                personalityGc.lineTo(extraversionScoreX, extraversionScoreY);
                personalityGc.lineTo(emotionalRangeScoreX, emotionalRangeScoreY);
                personalityGc.lineTo(agreeablenessScoreX, agreeablenessScoreY);
                personalityGc.closePath();
                personalityGc.stroke();
                personalityGc.setFill(Color.rgb(255, 0, 255, 0.3));
                personalityGc.fill();

                // 項目 結果表示
                personalityGc.setFontSmoothingType(FontSmoothingType.LCD);
                personalityGc.setFont(Font.font(18));
                personalityGc.setFill(Color.RED);
                personalityGc.fillText(opennessResult, opennessX - width * 5 / 2, opennessY + jpCenterHeight - offset - offset * 1.1);
                personalityGc.fillText(conscientiousnessResult, conscientiousnessX + width / 2, conscientiousnessY + jpCenterHeight - offset * 1.1);
                personalityGc.fillText(extraversionResult, extraversionX - width, extraversionY + jpCenterHeight + offset + offset * 1.1);
                personalityGc.fillText(emotionalRangeResult, emotionalRangeX - width * 9 / 4, emotionalRangeY + jpCenterHeight + offset + offset * 1.1);
                personalityGc.fillText(agreeablenessResult, agreeablenessX - width * 7 / 2, agreeablenessY + jpCenterHeight - offset * 1.1);

            }

            private void drawNeedsResult(Map<String, Double> needsMap) {

                // NeedsResult
                double practicality = needsMap.getOrDefault("実用主義", 0.0);
                double curiosity = needsMap.getOrDefault("好奇心", 0.0);
                double selfExpression = needsMap.getOrDefault("自己表現", 0.0);
                double ideal = needsMap.getOrDefault("理想", 0.0);
                double liberty = needsMap.getOrDefault("自由主義", 0.0);
                double stability = needsMap.getOrDefault("安定性", 0.0);
                double structure = needsMap.getOrDefault("仕組", 0.0);
                double excitement = needsMap.getOrDefault("興奮", 0.0);
                double challenge = needsMap.getOrDefault("挑戦", 0.0);
                double love = needsMap.getOrDefault("社会性", 0.0);
                double harmony = needsMap.getOrDefault("調和", 0.0);
                double closeness = needsMap.getOrDefault("親密", 0.0);

                // チャート表示用
                String practicalityResult = String.format("%.2f", practicality * 100).concat("%");
                String curiosityResult = String.format("%.2f", curiosity * 100).concat("%");
                String selfExpressionResult = String.format("%.2f", selfExpression * 100).concat("%");
                String idealResult = String.format("%.2f", ideal * 100).concat("%");
                String libertyResult = String.format("%.2f", liberty * 100).concat("%");
                String stabilityResult = String.format("%.2f", stability * 100).concat("%");
                String structureResult = String.format("%.2f", structure * 100).concat("%");
                String excitementResult = String.format("%.2f", excitement * 100).concat("%");
                String challengeResult = String.format("%.2f", challenge * 100).concat("%");
                String loveResult = String.format("%.2f", love * 100).concat("%");
                String harmonyResult = String.format("%.2f", harmony * 100).concat("%");
                String closenessResult = String.format("%.2f", closeness * 100).concat("%");

                // 原点
                final double originX = needsCanvas.getWidth() / 2;
                final double originY = needsCanvas.getHeight() / 2;
                // ライン長さ
                final double lineLength = 150.0;
                // 項目数
                final int items = needsMap.size();
                // 実用主義 スコアポジション
                final double practicalityScoreX = getPositionX(originX, lineLength, practicality, items, 0);
                final double practicalityScoreY = getPositionY(originY, lineLength, practicality, items, 0);
                // 好奇心 スコアポジション
                final double curiosityScoreX = getPositionX(originX, lineLength, curiosity, items, 1);
                final double curiosityScoreY = getPositionY(originY, lineLength, curiosity, items, 1);
                // 自己表現 スコアポジション
                final double selfExpressionScoreX = getPositionX(originX, lineLength, selfExpression, items, 2);
                final double selfExpressionScoreY = getPositionY(originY, lineLength, selfExpression, items, 2);
                // 理想 スコアポジション
                final double idealScoreX = getPositionX(originX, lineLength, ideal, items, 3);
                final double idealScoreY = getPositionY(originY, lineLength, ideal, items, 3);
                // 自由主義 スコアポジション
                final double libertyScoreX = getPositionX(originX, lineLength, liberty, items, 4);
                final double libertyScoreY = getPositionY(originY, lineLength, liberty, items, 4);
                // 安定性 スコアポジション
                final double stabilityScoreX = getPositionX(originX, lineLength, stability, items, 5);
                final double stabilityScoreY = getPositionY(originY, lineLength, stability, items, 5);
                // 仕組 スコアポジション
                final double structureScoreX = getPositionX(originX, lineLength, structure, items, 6);
                final double structureScoreY = getPositionY(originY, lineLength, structure, items, 6);
                // 興奮 スコアポジション
                final double excitementScoreX = getPositionX(originX, lineLength, excitement, items, 7);
                final double excitementScoreY = getPositionY(originY, lineLength, excitement, items, 7);
                // 挑戦 スコアポジション
                final double challengeScoreX = getPositionX(originX, lineLength, challenge, items, 8);
                final double challengeScoreY = getPositionY(originY, lineLength, challenge, items, 8);
                // 社会性 スコアポジション
                final double loveScoreX = getPositionX(originX, lineLength, love, items, 9);
                final double loveScoreY = getPositionY(originY, lineLength, love, items, 9);
                // 調和 スコアポジション
                final double harmonyScoreX = getPositionX(originX, lineLength, harmony, items, 10);
                final double harmonyScoreY = getPositionY(originY, lineLength, harmony, items, 10);
                // 親密 スコアポジション
                final double closenessScoreX = getPositionX(originX, lineLength, closeness, items, 11);
                final double closenessScoreY = getPositionY(originY, lineLength, closeness, items, 11);

                // スコア表示
                needsGc.setStroke(Color.rgb(255, 0, 255));
                needsGc.setLineWidth(2);
                needsGc.beginPath();
                needsGc.moveTo(practicalityScoreX, practicalityScoreY);
                needsGc.lineTo(curiosityScoreX, curiosityScoreY);
                needsGc.lineTo(selfExpressionScoreX, selfExpressionScoreY);
                needsGc.lineTo(idealScoreX, idealScoreY);
                needsGc.lineTo(libertyScoreX, libertyScoreY);
                needsGc.lineTo(stabilityScoreX, stabilityScoreY);
                needsGc.lineTo(structureScoreX, structureScoreY);
                needsGc.lineTo(excitementScoreX, excitementScoreY);
                needsGc.lineTo(challengeScoreX, challengeScoreY);
                needsGc.lineTo(loveScoreX, loveScoreY);
                needsGc.lineTo(harmonyScoreX, harmonyScoreY);
                needsGc.lineTo(closenessScoreX, closenessScoreY);
                needsGc.closePath();
                needsGc.stroke();
                needsGc.setFill(Color.rgb(255, 0, 255, 0.3));
                needsGc.fill();

                // 項目 結果表示
                needsGc.setFontSmoothingType(FontSmoothingType.LCD);
                needsGc.setFont(Font.font(18));
                needsGc.setFill(Color.RED);
                needsGc.fillText(practicalityResult, practicalityX - width * 4 / 2, practicalityY + jpCenterHeight - offset - offset * 1.1);
                needsGc.fillText(curiosityResult, curiosityX + width / 2, curiosityY + jpCenterHeight - offset / 2 - offset * 1.1);
                needsGc.fillText(selfExpressionResult, selfExpressionX + width / 2, selfExpressionY - jpCenterHeight + offset / 2 - offset * 1.1);
                needsGc.fillText(idealResult, idealX + width / 2, idealY + jpCenterHeight - offset * 1.1);
                needsGc.fillText(libertyResult, libertyX + width / 2, libertyY + jpCenterHeight + offset / 2 + offset * 1.1);;
                needsGc.fillText(stabilityResult, stabilityX + width / 2, stabilityY - jpCenterHeight + offset * 1.5 + offset * 1.1);
                needsGc.fillText(structureResult, structureX - width * 2 / 2, structureY + jpCenterHeight + offset + offset * 1.1);
                needsGc.fillText(excitementResult, excitementX - width * 5 / 2, excitementY - jpCenterHeight + offset * 1.5 + offset * 1.1);
                needsGc.fillText(challengeResult, challengeX - width * 5 / 2, challengeY + jpCenterHeight + offset / 2 + offset * 1.1);
                needsGc.fillText(loveResult, loveX - width * 7 / 2, loveY + jpCenterHeight - offset * 1.1);
                needsGc.fillText(harmonyResult, harmonyX - width * 5 / 2, harmonyY - jpCenterHeight + offset / 2 - offset * 1.1);
                needsGc.fillText(closenessResult, closenessX - width * 5 / 2, closenessY + jpCenterHeight - offset / 2 - offset * 1.1);

            }

            private void drawValuesResult(Map<String, Double> valuesMap) {

                double hedonism = valuesMap.getOrDefault("快楽主義", 0.0);
                double selfEnhancement = valuesMap.getOrDefault("自己増進", 0.0);
                double opennessToChange = valuesMap.getOrDefault("変化許容性", 0.0);
                double conservation = valuesMap.getOrDefault("現状維持", 0.0);
                double selfTranscendence = valuesMap.getOrDefault("自己超越", 0.0);

                String hedonismResult = String.format("%.2f", hedonism * 100).concat("%");
                String selfEnhancementResult = String.format("%.2f", selfEnhancement * 100).concat("%");
                String opennessToChangeResult = String.format("%.2f", opennessToChange * 100).concat("%");
                String conservationResult = String.format("%.2f", conservation * 100).concat("%");
                String selfTranscendenceResult = String.format("%.2f", selfTranscendence * 100).concat("%");

                // 原点
                final double originX = valuesCanvas.getWidth() / 2;
                final double originY = valuesCanvas.getHeight() / 2;
                // ライン長さ
                final double lineLength = 150.0;
                // 項目数
                final int items = valuesMap.size();
                // 快楽主義 スコアポジション
                final double hedonismScoreX = getPositionX(originX, lineLength, hedonism, items, 0);
                final double hedonismScoreY = getPositionY(originY, lineLength, hedonism, items, 0);
                // 自己増進 スコアポジション
                final double selfEnhancementScoreX = getPositionX(originX, lineLength, selfEnhancement, items, 1);
                final double selfEnhancementScoreY = getPositionY(originY, lineLength, selfEnhancement, items, 1);
                // 変化許容性 スコアポジション
                final double opennessToChangeScoreX = getPositionX(originX, lineLength, opennessToChange, items, 2);
                final double opennessToChangeScoreY = getPositionY(originY, lineLength, opennessToChange, items, 2);
                // 現状維持 スコアポジション
                final double conservationScoreX = getPositionX(originX, lineLength, conservation, items, 3);
                final double conservationScoreY = getPositionY(originY, lineLength, conservation, items, 3);
                // 自己超越 スコアポジション
                final double selfTranscendenceScoreX = getPositionX(originX, lineLength, selfTranscendence, items, 4);
                final double selfTranscendenceScoreY = getPositionY(originY, lineLength, selfTranscendence, items, 4);

                // スコア表示
                valuesGc.setStroke(Color.rgb(255, 0, 255));
                valuesGc.setLineWidth(2);
                valuesGc.beginPath();
                valuesGc.moveTo(hedonismScoreX, hedonismScoreY);
                valuesGc.lineTo(selfEnhancementScoreX, selfEnhancementScoreY);
                valuesGc.lineTo(opennessToChangeScoreX, opennessToChangeScoreY);
                valuesGc.lineTo(conservationScoreX, conservationScoreY);
                valuesGc.lineTo(selfTranscendenceScoreX, selfTranscendenceScoreY);
                valuesGc.closePath();
                valuesGc.stroke();
                valuesGc.setFill(Color.rgb(255, 0, 255, 0.3));
                valuesGc.fill();

                // 項目 結果表示
                valuesGc.setFontSmoothingType(FontSmoothingType.LCD);
                valuesGc.setFont(Font.font(18));
                valuesGc.setFill(Color.RED);
                valuesGc.fillText(hedonismResult, hedonismX - width * 4 / 2, hedonismY + jpCenterHeight - offset - offset * 1.1);
                valuesGc.fillText(selfEnhancementResult, selfEnhancementX + width / 2, selfEnhancementY + jpCenterHeight - offset * 1.1);
                valuesGc.fillText(opennessToChangeResult, opennessToChangeX - width * 2, opennessToChangeY + jpCenterHeight + offset + offset * 1.1);
                valuesGc.fillText(conservationResult, conservationX - width * 9 / 4, conservationY + jpCenterHeight + offset + offset * 1.1);
                valuesGc.fillText(selfTranscendenceResult, selfTranscendenceX - width * 9 / 2, selfTranscendenceY + jpCenterHeight - offset * 1.1);

            }

        };

        service.submit(task);

        idTextField.editableProperty().bind(task.stateProperty().isNotEqualTo(RUNNING));
        diagnosisButton.disableProperty().bind(Bindings.or(
                task.stateProperty().isEqualTo(RUNNING),
                idTextField.textProperty().isEmpty()));
        cancelButton.disableProperty().bind(task.stateProperty().isNotEqualTo(RUNNING));
        clearButton.disableProperty().bind(task.stateProperty().isEqualTo(RUNNING));
        progressIndicator.visibleProperty().bind(task.stateProperty().isEqualTo(RUNNING));
        messageLabel.textProperty().bind(task.messageProperty());

    }

    @Override
    public void initialize(URL url, ResourceBundle rb) {
        tweetCollection = new TweetCollection();
        personalityAnalysis = new PersonalityAnalysis();
        personalityGc = personalityCanvas.getGraphicsContext2D();
        needsGc = needsCanvas.getGraphicsContext2D();
        valuesGc = valuesCanvas.getGraphicsContext2D();
        progressIndicator.setProgress(-1.0);
        diagnosisButton.disableProperty().bind(idTextField.textProperty().isEmpty());

        // Canvas のテキスト座標設定に使う。生肉、焼肉、苦肉の策としてこうなった。(>_<。) 今後の課題とする。
        Text text = new Text("夢");
        text.setFont(Font.font(18));
        width = text.getBoundsInLocal().getWidth();
        jpCenterHeight = (2 * text.getBaselineOffset() - text.getBoundsInLocal().getHeight()) / 2;
        offset = text.getFont().getSize();

        drawPersonalityChart();
        drawNeedsChart();
        drawValueChart();
    }

    void setStage(Stage stage) {
        stage.setOnCloseRequest((WindowEvent we) -> {
            // クロージングアニメーション
            DoubleProperty closeOpacityProperty = new SimpleDoubleProperty(1.0);
            stage.opacityProperty().bind(closeOpacityProperty);
            Timeline closeTimeline = new Timeline(
                    new KeyFrame(
                            new Duration(100),
                            new KeyValue(closeOpacityProperty, 1.0)
                    ), new KeyFrame(
                            new Duration(2_500),
                            new KeyValue(closeOpacityProperty, 0.0)
                    ));
            EventHandler<ActionEvent> eh = ae -> {
                service.shutdownNow();
                Platform.exit();
                System.exit(0);
            };

            closeTimeline.setOnFinished(eh);
            closeTimeline.setCycleCount(1);
            closeTimeline.play();
            we.consume();
        });
    }

    private void drawPersonalityChart() {
        // 原点
        final double originX = personalityChart.getWidth() / 2;
        final double originY = personalityChart.getHeight() / 2;
        // ライン長さ
        final double lineLength = 150.0;
        // 項目数
        final int items = 5;
        // 知的好奇心
        opennessX = getPositionX(originX, lineLength, 1.0, items, 0);
        opennessY = getPositionY(originY, lineLength, 1.0, items, 0);
        // 誠実性
        conscientiousnessX = getPositionX(originX, lineLength, 1.0, items, 1);
        conscientiousnessY = getPositionY(originY, lineLength, 1.0, items, 1);
        // 外向性
        extraversionX = getPositionX(originX, lineLength, 1.0, items, 2);
        extraversionY = getPositionY(originY, lineLength, 1.0, items, 2);
        // 感情起伏
        emotionalRangeX = getPositionX(originX, lineLength, 1.0, items, 3);
        emotionalRangeY = getPositionY(originY, lineLength, 1.0, items, 3);
        // 協調性
        agreeablenessX = getPositionX(originX, lineLength, 1.0, items, 4);
        agreeablenessY = getPositionY(originY, lineLength, 1.0, items, 4);

        GraphicsContext gc = personalityChart.getGraphicsContext2D();

        gc.setStroke(Color.BLUE);
        gc.setLineWidth(1);
        gc.strokeLine(originX, originY, opennessX, opennessY);
        gc.strokeLine(originX, originY, conscientiousnessX, conscientiousnessY);
        gc.strokeLine(originX, originY, extraversionX, extraversionY);
        gc.strokeLine(originX, originY, emotionalRangeX, emotionalRangeY);
        gc.strokeLine(originX, originY, agreeablenessX, agreeablenessY);
        for (int i = 0; 6 > i; i++) {
            gc.beginPath();
            gc.moveTo(getPositionX(originX, lineLength, i * 0.2, items, 0), getPositionY(originY, lineLength, i * 0.2, items, 0));
            gc.lineTo(getPositionX(originX, lineLength, i * 0.2, items, 1), getPositionY(originY, lineLength, i * 0.2, items, 1));
            gc.lineTo(getPositionX(originX, lineLength, i * 0.2, items, 2), getPositionY(originY, lineLength, i * 0.2, items, 2));
            gc.lineTo(getPositionX(originX, lineLength, i * 0.2, items, 3), getPositionY(originY, lineLength, i * 0.2, items, 3));
            gc.lineTo(getPositionX(originX, lineLength, i * 0.2, items, 4), getPositionY(originY, lineLength, i * 0.2, items, 4));
            gc.closePath();
            gc.stroke();
        }

        gc.setFontSmoothingType(FontSmoothingType.LCD);
        gc.setFont(Font.font(18));
        gc.setFill(Color.RED);
        gc.fillText("知的好奇心", opennessX - width * 5 / 2, opennessY + jpCenterHeight - offset);
        gc.fillText("誠実性", conscientiousnessX + width / 2, conscientiousnessY + jpCenterHeight);
        gc.fillText("外向性", extraversionX - width, extraversionY + jpCenterHeight + offset);
        gc.fillText("感情起伏", emotionalRangeX - width * 9 / 4, emotionalRangeY + jpCenterHeight + offset);
        gc.fillText("協調性", agreeablenessX - width * 7 / 2, agreeablenessY + jpCenterHeight);

    }

    private void drawNeedsChart() {
        // 原点
        final double originX = needsChart.getWidth() / 2;
        final double originY = needsChart.getHeight() / 2;
        // ライン長さ
        final double lineLength = 150.0;
        // 項目数
        final int items = 12;
        // 実用主義
        practicalityX = getPositionX(originX, lineLength, 1.0, items, 0);
        practicalityY = getPositionY(originY, lineLength, 1.0, items, 0);
        // 好奇心
        curiosityX = getPositionX(originX, lineLength, 1.0, items, 1);
        curiosityY = getPositionY(originY, lineLength, 1.0, items, 1);
        // 自己表現
        selfExpressionX = getPositionX(originX, lineLength, 1.0, items, 2);
        selfExpressionY = getPositionY(originY, lineLength, 1.0, items, 2);
        // 理想
        idealX = getPositionX(originX, lineLength, 1.0, items, 3);
        idealY = getPositionY(originY, lineLength, 1.0, items, 3);
        // 自由主義
        libertyX = getPositionX(originX, lineLength, 1.0, items, 4);
        libertyY = getPositionY(originY, lineLength, 1.0, items, 4);
        // 安定性
        stabilityX = getPositionX(originX, lineLength, 1.0, items, 5);
        stabilityY = getPositionY(originY, lineLength, 1.0, items, 5);
        // 仕組
        structureX = getPositionX(originX, lineLength, 1.0, items, 6);
        structureY = getPositionY(originY, lineLength, 1.0, items, 6);
        // 興奮
        excitementX = getPositionX(originX, lineLength, 1.0, items, 7);
        excitementY = getPositionY(originY, lineLength, 1.0, items, 7);
        // 挑戦
        challengeX = getPositionX(originX, lineLength, 1.0, items, 8);
        challengeY = getPositionY(originY, lineLength, 1.0, items, 8);
        // 社会性
        loveX = getPositionX(originX, lineLength, 1.0, items, 9);
        loveY = getPositionY(originY, lineLength, 1.0, items, 9);
        // 調和
        harmonyX = getPositionX(originX, lineLength, 1.0, items, 10);
        harmonyY = getPositionY(originY, lineLength, 1.0, items, 10);
        // 親密
        closenessX = getPositionX(originX, lineLength, 1.0, items, 11);
        closenessY = getPositionY(originY, lineLength, 1.0, items, 11);

        GraphicsContext gc = needsChart.getGraphicsContext2D();

        gc.setStroke(Color.BLUE);
        gc.setLineWidth(1);
        gc.strokeLine(originX, originY, practicalityX, practicalityY);
        gc.strokeLine(originX, originY, curiosityX, curiosityY);
        gc.strokeLine(originX, originY, selfExpressionX, selfExpressionY);
        gc.strokeLine(originX, originY, idealX, idealY);
        gc.strokeLine(originX, originY, libertyX, libertyY);
        gc.strokeLine(originX, originY, stabilityX, stabilityY);
        gc.strokeLine(originX, originY, structureX, structureY);
        gc.strokeLine(originX, originY, excitementX, excitementY);
        gc.strokeLine(originX, originY, challengeX, challengeY);
        gc.strokeLine(originX, originY, loveX, loveY);
        gc.strokeLine(originX, originY, harmonyX, harmonyY);
        gc.strokeLine(originX, originY, closenessX, closenessY);
        for (int i = 0; 6 > i; i++) {
            gc.beginPath();
            gc.moveTo(getPositionX(originX, lineLength, i * 0.2, items, 0), getPositionY(originY, lineLength, i * 0.2, items, 0));
            gc.lineTo(getPositionX(originX, lineLength, i * 0.2, items, 1), getPositionY(originY, lineLength, i * 0.2, items, 1));
            gc.lineTo(getPositionX(originX, lineLength, i * 0.2, items, 2), getPositionY(originY, lineLength, i * 0.2, items, 2));
            gc.lineTo(getPositionX(originX, lineLength, i * 0.2, items, 3), getPositionY(originY, lineLength, i * 0.2, items, 3));
            gc.lineTo(getPositionX(originX, lineLength, i * 0.2, items, 4), getPositionY(originY, lineLength, i * 0.2, items, 4));
            gc.lineTo(getPositionX(originX, lineLength, i * 0.2, items, 5), getPositionY(originY, lineLength, i * 0.2, items, 5));
            gc.lineTo(getPositionX(originX, lineLength, i * 0.2, items, 6), getPositionY(originY, lineLength, i * 0.2, items, 6));
            gc.lineTo(getPositionX(originX, lineLength, i * 0.2, items, 7), getPositionY(originY, lineLength, i * 0.2, items, 7));
            gc.lineTo(getPositionX(originX, lineLength, i * 0.2, items, 8), getPositionY(originY, lineLength, i * 0.2, items, 8));
            gc.lineTo(getPositionX(originX, lineLength, i * 0.2, items, 9), getPositionY(originY, lineLength, i * 0.2, items, 9));
            gc.lineTo(getPositionX(originX, lineLength, i * 0.2, items, 10), getPositionY(originY, lineLength, i * 0.2, items, 10));
            gc.lineTo(getPositionX(originX, lineLength, i * 0.2, items, 11), getPositionY(originY, lineLength, i * 0.2, items, 11));
            gc.closePath();
            gc.stroke();
        }

        gc.setFontSmoothingType(FontSmoothingType.LCD);
        gc.setFont(Font.font(18));
        gc.setFill(Color.RED);
        gc.fillText("実用主義", practicalityX - width * 4 / 2, practicalityY + jpCenterHeight - offset);
        gc.fillText("好奇心", curiosityX + width / 2, curiosityY + jpCenterHeight - offset / 2);
        gc.fillText("自己表現", selfExpressionX + width / 2, selfExpressionY - jpCenterHeight + offset / 2);
        gc.fillText("理想", idealX + width / 2, idealY + jpCenterHeight);
        gc.fillText("自由主義", libertyX + width / 2, libertyY + jpCenterHeight + offset / 2);
        gc.fillText("安定性", stabilityX + width / 2, stabilityY - jpCenterHeight + offset * 1.5);
        gc.fillText("仕組", structureX - width * 2 / 2, structureY + jpCenterHeight + offset);
        gc.fillText("興奮", excitementX - width * 5 / 2, excitementY - jpCenterHeight + offset * 1.5);
        gc.fillText("挑戦", challengeX - width * 5 / 2, challengeY + jpCenterHeight + offset / 2);
        gc.fillText("社会性", loveX - width * 7 / 2, loveY + jpCenterHeight);
        gc.fillText("調和", harmonyX - width * 5 / 2, harmonyY - jpCenterHeight + offset / 2);
        gc.fillText("親密", closenessX - width * 5 / 2, closenessY + jpCenterHeight - offset / 2);

    }

    private void drawValueChart() {
        // 原点
        final double originX = valuesChart.getWidth() / 2;
        final double originY = valuesChart.getHeight() / 2;
        // ライン長さ
        final double lineLength = 150.0;
        // 項目数
        final int items = 5;

        // 快楽主義
        hedonismX = getPositionX(originX, lineLength, 1.0, items, 0);
        hedonismY = getPositionY(originY, lineLength, 1.0, items, 0);
        // 自己増進
        selfEnhancementX = getPositionX(originX, lineLength, 1.0, items, 1);
        selfEnhancementY = getPositionY(originY, lineLength, 1.0, items, 1);
        // 変化許容性
        opennessToChangeX = getPositionX(originX, lineLength, 1.0, items, 2);
        opennessToChangeY = getPositionY(originY, lineLength, 1.0, items, 2);
        // 現状維持
        conservationX = getPositionX(originX, lineLength, 1.0, items, 3);
        conservationY = getPositionY(originY, lineLength, 1.0, items, 3);
        // 自己超越
        selfTranscendenceX = getPositionX(originX, lineLength, 1.0, items, 4);
        selfTranscendenceY = getPositionY(originY, lineLength, 1.0, items, 4);

        GraphicsContext gc = valuesChart.getGraphicsContext2D();

        gc.setStroke(Color.BLUE);
        gc.setLineWidth(1);
        gc.strokeLine(originX, originY, hedonismX, hedonismY);
        gc.strokeLine(originX, originY, selfEnhancementX, selfEnhancementY);
        gc.strokeLine(originX, originY, opennessToChangeX, opennessToChangeY);
        gc.strokeLine(originX, originY, conservationX, conservationY);
        gc.strokeLine(originX, originY, selfTranscendenceX, selfTranscendenceY);
        for (int i = 0; 6 > i; i++) {
            gc.beginPath();
            gc.moveTo(getPositionX(originX, lineLength, i * 0.2, items, 0), getPositionY(originY, lineLength, i * 0.2, items, 0));
            gc.lineTo(getPositionX(originX, lineLength, i * 0.2, items, 1), getPositionY(originY, lineLength, i * 0.2, items, 1));
            gc.lineTo(getPositionX(originX, lineLength, i * 0.2, items, 2), getPositionY(originY, lineLength, i * 0.2, items, 2));
            gc.lineTo(getPositionX(originX, lineLength, i * 0.2, items, 3), getPositionY(originY, lineLength, i * 0.2, items, 3));
            gc.lineTo(getPositionX(originX, lineLength, i * 0.2, items, 4), getPositionY(originY, lineLength, i * 0.2, items, 4));
            gc.closePath();
            gc.stroke();
        }

        gc.setFontSmoothingType(FontSmoothingType.LCD);
        gc.setFont(Font.font(18));
        gc.setFill(Color.RED);
        gc.fillText("快楽主義", hedonismX - width * 4 / 2, hedonismY + jpCenterHeight - offset);
        gc.fillText("自己増進", selfEnhancementX + width / 2, selfEnhancementY + jpCenterHeight);
        gc.fillText("変化許容性", opennessToChangeX - width * 2, opennessToChangeY + jpCenterHeight + offset);
        gc.fillText("現状維持", conservationX - width * 9 / 4, conservationY + jpCenterHeight + offset);
        gc.fillText("自己超越", selfTranscendenceX - width * 9 / 2, selfTranscendenceY + jpCenterHeight);
    }

    private double getPositionX(double originX, double lineLength, double ratio, int items, int vertex) {
        return originX + lineLength * ratio * Math.cos(Math.toRadians(90 - (360 / items) * vertex));
    }

    private double getPositionY(double originY, double lineLength, double ratio, int items, int vertex) {
        return originY - lineLength * ratio * Math.sin(Math.toRadians(90 - (360 / items) * vertex));
    }

    private void clearChart() {
        personalityGc.clearRect(0, 0, personalityCanvas.getWidth(), personalityCanvas.getHeight());
        needsGc.clearRect(0, 0, needsCanvas.getWidth(), needsCanvas.getHeight());
        valuesGc.clearRect(0, 0, valuesCanvas.getWidth(), valuesCanvas.getHeight());
    }

}

package jp.yucchi.personalitydiagnosis;

import com.ibm.watson.developer_cloud.personality_insights.v3.model.Profile;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.concurrent.Task;
import javafx.scene.control.TextField;
import twitter4j.Paging;
import twitter4j.ResponseList;
import twitter4j.Status;
import twitter4j.Twitter;
import twitter4j.TwitterException;
import twitter4j.TwitterFactory;
import twitter4j.conf.ConfigurationBuilder;

/**
 *
 * @author Yucchi
 */
public class TweetCollection {

    Task<Profile> task;
    TextField idTextField;

    TweetCollection() {
    }

    public TweetCollection(Task<Profile> task, TextField idTextField) {
        this.task = task;
        this.idTextField = idTextField;
    }

    public List<Status> getTweets(TextField idTextField, Task<Profile> task) {

        Twitter twitter;

        ConfigurationBuilder cb = new ConfigurationBuilder();
        cb.setDebugEnabled(true)
                .setOAuthConsumerKey("Set Your OAuth ConsumerKey")
                .setOAuthConsumerSecret("Set Your OAuth Consumer Secret")
                .setOAuthAccessToken("Set Your OAuth Access Token")
                .setOAuthAccessTokenSecret("Set YourOAuth Access Token Secret");

        TwitterFactory tf = new TwitterFactory(cb.build());
        twitter = tf.getInstance();
        long userId = 0;
        List<Status> tweetList = new ArrayList<>();

        try {
            userId = twitter.showUser(idTextField.getText()).getId();
        } catch (TwitterException ex) {
            Logger.getLogger(FXMLPersonalityDiagnosisController.class.getName()).log(Level.SEVERE, null, ex);
        }

        long cursor = -1;
        int tweetQuantity = 200;
        int totalTweets = 1_000;
        Paging page = new Paging(1, tweetQuantity);
        ResponseList<Status> tweets = null;

        do {
            if (task.isCancelled()) {
                return null;
            }
            try {
                tweets = twitter.getUserTimeline(userId, page);
            } catch (TwitterException ex) {
                Logger.getLogger(FXMLPersonalityDiagnosisController.class.getName()).log(Level.SEVERE, null, ex);
            }
            if (tweets == null || tweets.isEmpty()) {
                break;
            }
            for (int i = 0; i < tweets.size(); i++) {
                if (task.isCancelled()) {
                    return null;
                }
                Status status = tweets.get(i);
                // オーバーラップ防止
                cursor = status.getId() - 1;
                // リツイートを除外
                if (status.isRetweet()) {
                    continue;
                }
                // 日本語だけ選択
                if (!"ja".contains(status.getLang())) {
                    continue;
                }
                tweetList.add(status);
            }
            if (tweetList.size() >= totalTweets) {
                return tweetList;
            }
            page.maxId(cursor); // 次はここで設定される max_id から
        } while (true);

        return tweetList;

    }

}

package jp.yucchi.personalitydiagnosis;

import com.ibm.watson.developer_cloud.personality_insights.v3.PersonalityInsights;
import com.ibm.watson.developer_cloud.personality_insights.v3.model.ContentItem;
import com.ibm.watson.developer_cloud.personality_insights.v3.model.Profile;
import com.ibm.watson.developer_cloud.personality_insights.v3.model.ProfileOptions;
import com.ibm.watson.developer_cloud.service.exception.BadRequestException;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import twitter4j.Status;

/**
 *
 * @author Yucchi
 */
public class PersonalityAnalysis {

    List<Status> tweets;

    PersonalityAnalysis() {
    }

    public PersonalityAnalysis(List<Status> tweets) {
        this.tweets = tweets;
    }

    Profile toAnalyze(List<Status> tweets) {

        Profile result = null;
        String text = null;

        for (int i = 0; tweets.size() > i; i++) {
            text += tweets.get(i).getText();
        }

        PersonalityInsights service = new PersonalityInsights("2016-10-19");
        service.setUsernameAndPassword("username", "password");
        ProfileOptions options = new ProfileOptions.Builder()
                .contentLanguage(ContentItem.Language.JA)
                .acceptLanguage(ProfileOptions.AcceptLanguage.JA)
                .text(text).build();

        try {
            result = service.profile(options).execute();
        } catch (BadRequestException e) {
            Logger.getLogger(PersonalityAnalysis.class.getName()).log(Level.SEVERE, null, e);
        }

        return result;

    }

}

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.Insets?>
<?import javafx.scene.canvas.Canvas?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ProgressIndicator?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.text.Font?>

<AnchorPane id="anchorPane" fx:id="anchorPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="730.0" prefWidth="1532.0" xmlns="http://javafx.com/javafx/9.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="jp.yucchi.personalitydiagnosis.FXMLPersonalityDiagnosisController">
    <children>
        <Canvas id="personalityChart" fx:id="personalityChart" height="500.0" layoutX="49.0" layoutY="653.0" width="500.0" AnchorPane.bottomAnchor="70.0" AnchorPane.leftAnchor="6.0" />
        <Canvas id="personalityCanvas" fx:id="personalityCanvas" height="500.0" layoutX="105.0" layoutY="335.0" width="500.0" AnchorPane.bottomAnchor="70.0" AnchorPane.leftAnchor="6.0" />
        <Canvas id="needsChart" fx:id="needsChart" height="500.0" layoutX="643.0" layoutY="635.0" width="500.0" AnchorPane.bottomAnchor="70.0" AnchorPane.leftAnchor="516.0" />
        <Canvas id="needsCanvas" fx:id="needsCanvas" height="500.0" layoutX="674.0" layoutY="335.0" width="500.0" AnchorPane.bottomAnchor="70.0" AnchorPane.leftAnchor="516.0" />
        <Canvas id="valuesChart" fx:id="valuesChart" height="500.0" layoutX="1228.0" layoutY="630.0" width="500.0" AnchorPane.bottomAnchor="70.0" AnchorPane.rightAnchor="6.0" />
        <Canvas id="valuesCanvas" fx:id="valuesCanvas" height="500.0" layoutX="1192.0" layoutY="335.0" width="500.0" AnchorPane.bottomAnchor="70.0" AnchorPane.rightAnchor="6.0" />
        <Label id="big5PersonalityLabel" fx:id="big5Label" alignment="CENTER" layoutX="164.0" layoutY="385.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="50.0" prefWidth="500.0" text="Big 5 Personality" AnchorPane.bottomAnchor="576.0" AnchorPane.leftAnchor="6.0">
            <font>
                <Font size="36.0" />
            </font>
        </Label>
        <Label id="needsLabel" fx:id="needsLabel" alignment="CENTER" layoutX="638.0" layoutY="359.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="50.0" prefWidth="500.0" text="Needs" AnchorPane.bottomAnchor="576.0" AnchorPane.leftAnchor="516.0">
            <font>
                <Font size="36.0" />
            </font>
        </Label>
        <Label id="valueLabel" fx:id="valueLabel" alignment="CENTER" layoutX="1183.0" layoutY="357.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="50.0" prefWidth="500.0" text="Value" AnchorPane.bottomAnchor="576.0" AnchorPane.rightAnchor="6.0">
            <font>
                <Font size="36.0" />
            </font>
        </Label>
        <Label id="id" fx:id="idLabel" alignment="CENTER" layoutX="73.0" layoutY="50.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="70.0" prefWidth="230.0" text="Twitter ID " AnchorPane.leftAnchor="90.0" AnchorPane.topAnchor="10.0">
            <font>
                <Font name="System Bold" size="38.0" />
            </font>
        </Label>
        <Button id="diagnosis" fx:id="diagnosisButton" alignment="CENTER" layoutX="802.0" layoutY="14.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" onAction="#diagnosis" prefHeight="70.0" prefWidth="300.0" text="DIAGNOSIS" AnchorPane.leftAnchor="802.0" AnchorPane.topAnchor="10.0">
            <font>
                <Font name="System Bold" size="38.0" />
            </font>
        </Button>
        <TextField id="idTextField" fx:id="idTextField" focusTraversable="false" layoutX="326.0" layoutY="10.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="70.0" prefWidth="435.0" text="\@Yucchi_jp" AnchorPane.leftAnchor="326.0" AnchorPane.topAnchor="10.0">
            <font>
                <Font name="System Bold" size="38.0" />
            </font>
        </TextField>
        <Button id="cancel" fx:id="cancelButton" alignment="CENTER" disable="true" layoutX="1078.0" layoutY="14.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" onAction="#cancel" prefHeight="70.0" prefWidth="300.0" text="CANCEL" AnchorPane.rightAnchor="90.0" AnchorPane.topAnchor="10.0">
            <font>
                <Font name="System Bold" size="38.0" />
            </font>
            <opaqueInsets>
                <Insets />
            </opaqueInsets>
        </Button>
        <ProgressIndicator id="progressIndicator" fx:id="progressIndicator" layoutX="613.0" layoutY="315.0" opacity="0.0" progress="0.0" AnchorPane.bottomAnchor="75.0" AnchorPane.leftAnchor="300.0" AnchorPane.rightAnchor="300.0" AnchorPane.topAnchor="165.0" />
        <Button id="clearButton" fx:id="clearButton" alignment="CENTER" layoutX="1142.0" layoutY="664.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" onAction="#clear" prefHeight="50.0" prefWidth="300.0" text="CLEAR" AnchorPane.bottomAnchor="10.0" AnchorPane.rightAnchor="90.0">
            <font>
                <Font name="System Bold" size="26.0" />
            </font>
        </Button>
        <Label id="messageLabel" fx:id="messageLabel" layoutX="90.0" layoutY="686.0" minHeight="50.0" prefHeight="50.0" prefWidth="1026.0" text="Please enter the Twitter ID and press the diagnosis button." AnchorPane.bottomAnchor="10.0" AnchorPane.leftAnchor="90.0">
            <font>
                <Font name="System Bold" size="26.0" />
            </font>
        </Label>
    </children>
</AnchorPane>

/* 
    Author     : Yucchi
*/

/* AnchorPane */
#anchorPane {
    -fx-background-color: black;
}

/* Button */
.button {
    -fx-padding: 5 22 5 22;   
    -fx-border-color: hotpink;
    -fx-border-width: 2;
    -fx-background-radius: 0;
    -fx-background-color: #1d1d1d;
    -fx-font-family: "Segoe UI", Helvetica, Arial, sans-serif;
    -fx-font-size: 30pt;
    -fx-text-fill: hotpink;
}

.button:hover {
    -fx-background-color: #3a3a3a;
}

.button:pressed, .button:default:hover:pressed
{
    -fx-background-color:#ccccff;
    -fx-text-fill:#ff00ff;
}

.button:focused
{
    -fx-border-color: hotpink, hotpink;
    -fx-border-width: 1, 1;
    -fx-border-style: solid, segments(1, 1);
    -fx-border-radius: 0, 0;
    -fx-border-insets: 1 1 1 1, 0;
}

.button:disabled, .button:default:disabled
{
    -fx-opacity: 0.4;
    -fx-background-color: #1d1d1d;
    -fx-text-fill:lightpink;
}

.button:default
{
    -fx-background-color: #008287;
    -fx-text-fill: #ffffff;
}

.button:default:hover{
    -fx-background-color: #219297;
}

#clearButton{
    -fx-padding: 5 22 5 22;   
    -fx-border-color: hotpink;
    -fx-border-width: 2;
    -fx-background-radius: 0;
    -fx-background-color: #1d1d1d;
    -fx-font-family: "Segoe UI", Helvetica, Arial, sans-serif;
    -fx-font-size: 20pt;
    -fx-text-fill: hotpink;
}

#clearButton:hover {
    -fx-background-color: #3a3a3a;
}

#clearButton:pressed, .button:default:hover:pressed
{
    -fx-background-color: #ccccff;
    -fx-text-fill: #ff00ff;
}

#clearButton:focused
{
    -fx-border-color: hotpink, hotpink;
    -fx-border-width: 1, 1;
    -fx-border-style: solid, segments(1, 1);
    -fx-border-radius: 0, 0;
    -fx-border-insets: 1 1 1 1, 0;
}

#clearButton:disabled, .button:default:disabled
{
    -fx-opacity: 0.4;
    -fx-background-color: #1d1d1d;
    -fx-text-fill:lightpink;
}

#clearButton:default
{
    -fx-background-color: #008287;
    -fx-text-fill: #ffffff;
}

#clearButton:default:hover{
    -fx-background-color: #219297;
}

.label{
    -fx-text-fill: hotpink;
}

/* TextField */
#idTextField{
    -fx-text-fill: hotpink;
    -fx-border-color: hotpink;
    -fx-background-color:TRANSPARENT;
}

.progress-indicator {
    -fx-base: rgba(165.000005, 214.000002, 167.000005, 1.0);
    -fx-progress-color: #ff00ff;
}

見ての通り GUI は AnchorPane に直接部品を貼り付けリサイズ出来なくするということをしています。(HBox,VBox 使いましょう(^_^;

テキストフィールドに入力された Twitter ID のツイートを約 1000 件取得してそれを Watson Personality Insights で分析してその結果をレーダーチャートに表示させています。

Twitter のツイートを取得するためには Twitter API を利用しなくてはならないので Twitter Developer アプリケーション登録が必要となります。

IBM Cloud (Bluemix) の Watson Personality Insights を利用するには IBM Cloud (Bluemix)ライト・アカウントの登録が必要となります。

既に登録されている、もしくは有料プランをご利用の方は必要ありません。

参考

ツイート取得、分析データ取得の処理は時間がかかるので非同期処理で対応しています。

JavaFX に非同期処理を簡単に扱うことを可能とする javafx.concurrent パッケージがあります。

参考

Twitter のツイートを取得処理は TweetCollection クラスでおこなっています。

取得したツイートの分析処理は PersonalityAnalysis クラスでおこなっています。

これら二つの処理を非同期処理してそれによって得られた分析データを Canvas にお絵かき対応したレーダーチャート上に表示させています。

ここで Canvas はベースとなるレーダーチャートと、分析データの表示用の二つを利用しています。

分析データをベースのレーダーチャートに重ねて表示させています。(フォトショップで使うレイヤーのように)

ここで Canvas に文字列を表示させるのに位置決めが難しく苦労しました。

X 座標の位置は日本語で等幅のものを使っているので一文字の幅データを元に計算することによって対応しました。

Y 座標はなかなか難しく結局これといった結論が得られずフォントのサイズをそのまま利用したり、日本語のセンターの位置をゴニョゴニョして取得しています。

これではあんまりなので FontMetrics を取得してちゃんと対応しようとしたのですがそれでもあまり綺麗にプログラマチックに決めることができなかった。

しかたないのでウザウザの力技コード対応としています。

ちなみに JavaFX 9 での FontMetrics 取得は JavaFX 8 と同じように出来なくなってしまっているので注意が必要です。

参考

このように手抜きだけどちょっと遊んでみようかなっていうデスクトッププログラムが JavaFX だと簡単にできてしまします。

取得解析するものを二つにして重ね合わせて表示して相性診断プログラムとしても楽しいかもしれません。

Java には JavaFX が標準でついているので Java プログラマー、Java に興味のあるかたはお気楽に楽しんでみてはいかがでしょうか。

もちろん、JavaFX も Java なのでクロスプラットフォームです。

Write once, run anywhere

Windows でも Linux でも一度書けばどちらでも動いちゃいます。

Write once,test anywhere とは言わないで (>_<。)

 

Hatena タグ: ,,