マウスカーソル情報

kickzone/SHChart · GitHub

だんだんそれっぽくなってきた。今日はここまで。残りはスクロール・拡大縮小と、DBからの読み込み。
本日苦労したところ:マウスカーソルの位置を取得するとき、MouseEvent.clientX が得られる位置は、ウインドウ内の位置だ。canvasはウインドウ端から若干(8ピクセルくらい)右にずれて配置されているので、clientXの位置をそのまま使用して描画すると、マウスカーソルの位置と8ピクセルくらいずれてしまう。なので、canvasの置かれている位置を考慮しないといけない。

//これではだめ
CanvasMouseMove = (e: MouseEvent) => {
this.mi.drop();
for (var i in this.graphs) {
this.graphs[i].paintMouseInfo(e.clientX, this.mi);
}
}
//これならOK
CanvasMouseMove = (e: MouseEvent) => {
this.mi.drop();
var xCanvas = e.clientX - this.canvas.offsetLeft;
for (var i in this.graphs) {
this.graphs[i].paintMouseInfo(xCanvas, this.mi);
}
}

はじめはMouseEvent.xを使用して開発していたが、FireFoxでは使えないらしく全く動作しなかった。clientXを使用するのが無難。
それから、破線を引く機能がcanvasには存在しないので、自作する必要があった。easeljsのmoveToとlineToを交互に呼び出すことで実現可能だった。

public createDashX(x: number) {
var g: createjs.Graphics = new createjs.Graphics();
g.setStrokeStyle(1).beginStroke("#000000");
//破線の数(*2)を計算
var dashes: number = (this.ymax - this.ymin) / this.DASH_LEN;
//半分ずつ線を引く
for (var y: number = this.ymin, q: number = 0; q < dashes; y += this.DASH_LEN, q++) {
if (q % 2 == 0) g.moveTo(x, y);
else g.lineTo(x, y);
}
this.dashX = new createjs.Shape(g);
this.stage.addChild(this.dashX);
}

XY軸

kickzone/SHChart · GitHub

まだ見栄えが良くないけれど一応XY軸を作成することができた。来週はマウスに対応した情報を表示する処理を作業する。作業中、日曜日なのに仕事の依頼が入ってきたので、時間が少なくしか取れなかった。
見返してみると月が1か月ずれている、、あ、そういえば、javascriptのDate.getMonth()って1か月ずれてるんだった!Date.prototype.getMonth() – JavaScript | MDN 1月は0、2月は1なのね。修正はまた来週。
一番面倒だったのは、XY軸の目盛の間隔の自動決定方法。Y軸に関するコードを参考に載せておきます。

    /**
    Y軸目盛の目安本数
    */
private YAXIS_BASE_NUM: number = 4;
/**
    目盛に使用する数値一覧を算出
    */
private DecideScale(min: number, max: number): Array<number> {
//数値の幅を目安本数で割って、大体の間隔を算出
var range: number = max - min;
var intervalBase: number = range / this.YAXIS_BASE_NUM;
//キリのいい数に揃える 22→20、158→200など
var keta: number = Math.floor(Math.log(intervalBase) / Math.log(10) + 1);
var interval: number = Math.round(intervalBase / Math.pow(10, keta - 1)) * Math.pow(10, keta - 1);
//intervalで割り切れる数を返す
var minBase = Math.ceil(min / interval);
var maxBase = Math.floor(max / interval);
var ret: Array<number> = [];
for (var i: number = minBase; i <= maxBase; i++) {
ret.push(i * interval);
}
return ret;
}

繰り返しになるけどTypeScriptでは原理的に実行時エラーが発生しない。これはすごい。少し型を意識してプログラミングするだけで、デバッグの手間が1/10程度まで減った。いやーー楽だ。


n日移動平均

意外と簡単だった。

/**
n日移動平均を求める
*/
public static CalcSMA(hiashiArr: Array<Hiashi>, days: number): Array<DateVal> {
var retArr: Array<DateVal> = [];
var currentSum: number = 0;
for (var i in hiashiArr) {
var hiashi: Hiashi = hiashiArr[i];
var dv: DateVal = new DateVal;
dv.date = new Date(hiashi.ymd);
currentSum += Number(hiashi.close);
if (i >= days) {
var nPrevHiashi = hiashiArr[i - days];
currentSum -= Number(nPrevHiashi.close);
dv.val = currentSum / days;
} else {
dv.val = currentSum / (i + 1);
}
retArr[i] = dv;
}
return retArr;
}


緑:25日平均、青:75日平均
今日はここまで。


日足グラフが書けた + VS2013とGitHubの連携

kickzone/SHChart · GitHub
Visual Studio 2013 GitHubの使い方 : Exceedone Technical Knowledgeを参考に、VS2013とGitHubを連携することができた。これでソース管理も楽々だ。
小田急電鉄2014年後半の日足グラフ

次は折れ線グラフと移動平均線を試しに書いてみて、それができたら縦軸・横軸を追加し、さらにマウス連携の画面表示とスクロール・倍率操作を実装して、見栄えをよくした後最後にインターフェースを整えたら一通り終了だ。ここまで来たら、あとはすぐ実装できるだろう。


PHP, jQuery + ajax 連携練習

最寄駅の時刻表を見て、いちいちNaviTimeやらを使って新宿駅に到着する時間を調べるのが面倒。
そんなニーズに応えるため、この時刻表(最寄駅は変えてあります)のページに細工をして、マウスカーソルを合わせると新宿駅到着時刻が出てくるようにした。
クライアント側ではページロード後に、時刻表から発時刻をサーバーにajaxで送って、新宿駅到着時間を返してもらう。サーバー側では、発駅発時刻を入力すると新宿駅までの経路を判定して文字列を返すPHPスクリプトを書く(実はこっちの方が面倒)。TrainNaviで作ったデータベースをそのまんま使用した。
さて、すべての時間を一気にajaxで問い合わせると、リクエストが200はあるから貧弱な自宅PCが悲鳴を上げそうだ。しかし、asyncをやめて順次問い合わせにすると、ページが表示されるまでとても時間がかかってしまう。というわけで、asyncにしつつ、1つ処理が終わったら次の処理を実行するようなチェーンの仕組みが必要だ。
概念図

処理開始→ajax処理   →ajax処理 →ajax処理・・・
↓       ↑ ↓      ↑
処理終了→↑処理終了→↑
↓           ↓
コールバック コールバック

出来上がったコード(開始駅を変えてあります)

//ajax処理をするメイン関数
function ktmain(start, end, time, service, cbObj, nextObj){
$.ajax({
async: true,
type: "POST",
url: "ktajax.php",
data: { start : start, end : end, time : time, service : service }
}).done(function( msg ) {
//コールバック(DOMの要素にmsgをセット)
cbObj.callback(msg);
//次なるktmainをコール あらかじめnextObjに引数などを保存してある
if(nextObj) nextObj.next();
});
}
//コールバック用オブジェクト
var KTCallBackObject = (function (){
function KTCallBackObject(dom) {
this.dom = dom;
};
KTCallBackObject.prototype.callback = function(msg){
//title属性をmsgに変更
this.dom.title = msg;
}
return KTCallBackObject;
})();
//メソッド実行用オブジェクト
var KTNextObject = (function (){
function KTNextObject(start, end, time, service, cbObj) {
this.start = start;
this.end = end;
this.time = time;
this.service = service;
this.cbObj = cbObj;
this.nextObj = null;
};
KTNextObject.prototype.next = function(){
//次の関数をコール
ktmain(this.start, this.end, this.time, this.service, this.cbObj, this.nextObj);
}
return KTNextObject;
})();
//メソッドチェーンを作成する
function MakeMethodChain(base, start, end, service){
var nextObjFirst, nextObjBefore;
for(var i=1; i<=21; i++){
var snum = ("0" + i).slice(-2);
var selector = "." + base + snum;
var root = $(selector);
if(root.size()){
//時刻
var hour = root.find(".diaHour").html();
var items = root.find("a");
for(var j=0; j<items.size(); j++){
//分
var minute = items[j].innerHTML.replace(/\s| /g,"").substr(0, 2);
var time = hour + ":" + minute;
//コールバック用オブジェクト
var cbObj = new KTCallBackObject(items[j]);
//次のメソッド実行用オブジェクト
var nextObj = new KTNextObject(start, end, time, service, cbObj);
//関連付けてチェーン作成
if(nextObjBefore) nextObjBefore.nextObj = nextObj;
if(!nextObjFirst) nextObjFirst = nextObj;
nextObjBefore = nextObj;
}
}
}
//先頭のメソッド実行用オブジェクトを返す
return nextObjFirst;
}
function DOMManupilation(){
var start = $("#station-name").find("a").html();
var end = (start == "足柄") ? "新宿" : "足柄";
//メソッドチェーンを作って、順次実行
//平日
var mcWeekday = MakeMethodChain("t1-time", start, end, 1);
//休日
var mcHoliday = MakeMethodChain("t3-time", start, end, 2);
//fire
//平日・休日 2列並列順次実行
mcWeekday.next();
mcHoliday.next();
}
$(document).ready(DOMManupilation);

これで、処理終了ごとにktmainがコールされることになり、しかもページの表示を妨げない。実行すると、4時台、5時台、・・・と処理が進んでいって、終電のtitle属性の表示が変わったのは30秒ほど後。やっぱりasyncなしじゃきついね。

サーバー側(開始駅を変えてあります)やっつけ

<?php
require_once 'funcs.php';
$start = $_POST["start"];
$end = $_POST["end"];
$service = $_POST["service"];
$time = $_POST["time"];
if(strlen($time) == 4) $time = "0" . $time;
if($start == "足柄"){
AshigaraShinjuku($service, $time);
}
function AshigaraShinjuku($service, $time){
$mysqli = OpenDb();
//足柄発の電車
$query = "SELECT * FROM tnroute WHERE linename = '小田急小田原線' AND startstation = '足柄' AND endstation='螢田' AND starttime='$time' AND service=$service";
$result = ExecQuery($mysqli, $query);
if($result->num_rows == 0) return;
$row = $result->fetch_assoc();
$trainname = $row['trainname'];
$trainkind = TrainKind($mysqli, $service, $trainname);
$train = Train($mysqli, $service, $trainname);
$dest = $train[count($train)-1]['endstation'];
echo "$trainkind $dest"."\n";
echo "螢田 $time"."\n\n";
if($dest == "新松田" || $dest == "相模大野"){
//終点で乗り換える
$endtime = TimeTrim($train[count($train)-1]['endtime']);
echo "$dest $endtime"."\n\n";
$trainname = SearchNorikae($mysqli, $endtime, $dest, $service, true);
$trainkind = TrainKind($mysqli, $service, $trainname);
$train = Train($mysqli, $service, $trainname);
$current = $dest;
$dest = $train[count($train)-1]['endstation'];
$loc = StartStation($train, $current);
$starttime = TimeTrim($train[$loc]['starttime']);
echo "$trainkind $dest"."\n";
echo "$current $starttime"."\n\n";
if(strpos($trainkind, "はこね") !== FALSE)
{
$endtime = TimeTrim($train[count($train)-1]['endtime']);
echo "$dest $endtime"."\n\n";
//ロマンスカーを使った場合は、もう一度検索
echo "(ロマンスカーを使わない場合)\n\n";
$trainname = SearchNorikae($mysqli, $starttime, $current, $service, true);
$trainkind = TrainKind($mysqli, $service, $trainname);
$train = Train($mysqli, $service, $trainname);
$dest = $train[count($train)-1]['endstation'];
$loc = StartStation($train, $current);
$starttime = TimeTrim($train[$loc]['starttime']);
echo "$trainkind $dest"."\n";
echo "$current $starttime"."\n\n";
}
}
else if($dest == "町田"){
//相模大野で乗り換える
$loc = StartStation($train, "小田急相模原");
if($loc == -1) $loc = StartStation($train, "海老名");
$endtime = TimeTrim($train[$loc]['endtime']);
$new_trainname = SearchNorikae($mysqli, $endtime, "相模大野", $service, true);
if($new_trainname != $trainname)
{
echo "相模大野 $endtime"."\n\n";
$trainname = $new_trainname;
$trainkind = TrainKind($mysqli, $service, $trainname);
$train = Train($mysqli, $service, $trainname);
$current = "相模大野";
$dest = $train[count($train)-1]['endstation'];
$loc = StartStation($train, $current);
$starttime = TimeTrim($train[$loc]['starttime']);
echo "$trainkind $dest"."\n";
echo "$current $starttime"."\n\n";
}
else
{
//乗り換えないでよかった
//$endtime = TimeTrim($train[count($train)-1]['endtime']);
//echo "$dest $endtime"."着\n\n";
}
}
else if(ExistStation($train, "新松田") && $dest != "成城学園前" && $time != "00:12"){
//新松田で乗り換える
$loc = StartStation($train, "新松田");
$endtime = TimeTrim($train[$loc]['starttime']);
echo "新松田 $endtime"."\n\n";
$trainname = SearchNorikae($mysqli, $endtime, "新松田", $service, true);
$trainkind = TrainKind($mysqli, $service, $trainname);
$train = Train($mysqli, $service, $trainname);
$current = "新松田";
$dest = $train[count($train)-1]['endstation'];
$loc = StartStation($train, $current);
$starttime = TimeTrim($train[$loc]['starttime']);
echo "$trainkind $dest"."\n";
echo "$current $starttime"."\n\n";
}
$endtime = TimeTrim($train[count($train)-1]['endtime']);
echo "$dest $endtime"."\n\n";
if($dest == "経堂" || $dest == "成城学園前" || ($dest == "町田" && $time == "00:12")){
echo "新宿に着けません";
}
else if($dest != "新宿"){
//新宿につかなかったのでもう一本
$trainname = SearchNorikae($mysqli, $endtime, $dest, $service, true);
$trainkind = TrainKind($mysqli, $service, $trainname);
$train = Train($mysqli, $service, $trainname);
$current = $dest;
$dest = $train[count($train)-1]['endstation'];
$loc = StartStation($train, $current);
$starttime = TimeTrim($train[$loc]['starttime']);
echo "$trainkind $dest"."\n";
echo "$current $starttime"."\n\n";
$endtime = TimeTrim($train[count($train)-1]['endtime']);
echo "$dest $endtime"."\n\n";
}
}
function TrainKind($mysqli, $service, $trainname){
$query = "SELECT * FROM tntrain WHERE linename = '小田急小田原線' AND trainname = '$trainname' AND service=$service";
$result = ExecQuery($mysqli, $query);
$row = $result->fetch_assoc();
return $row['trainkind'];
}
function Train($mysqli, $service, $trainname){
$query = "SELECT * FROM tnroute WHERE linename = '小田急小田原線' AND trainname = '$trainname' AND service=$service";
$result = ExecQuery($mysqli, $query);
$train = array();
while($row = $result->fetch_assoc()){
$train[] = $row;
}
return $train;
}
function ExistStation($train, $station){
for($i=0; $i<count($train); $i++){
if($train[$i]['endstation'] == $station) return $i;
}
return false;
}
function StartStation($train, $station){
for($i=0; $i<count($train); $i++){
if($train[$i]['startstation'] == $station) return $i;
}
return -1;
}
function TimeTrim($time){
return substr($time, 0, 5);
}
function SearchNorikae($mysqli, $time, $station, $service, $up){
$query = "SELECT * FROM tnroute WHERE linename = '小田急小田原線' AND startstation = '$station' AND starttime>'$time' AND service=$service";
if($up){
if($station == "新松田"){
$query .= " AND (endstation = '渋沢' OR endstation = '本厚木')";
}else if($station == "相模大野"){
$query .= " AND endstation = '町田'";
}else if($station == "町田"){
$query .= " AND endstation = '新百合ヶ丘'";
}
}
$query .= " ORDER BY starttime ASC";
$result = ExecQuery($mysqli, $query);
$row = $result->fetch_assoc();
return $row['trainname'];
}
?>

ローソク図試作品

仕事が多すぎて日曜日までずれ込んだ。まだ動かしていないけど、今日はここまで。

/// <reference path="scripts/typings/easeljs/easeljs.d.ts" />
module SHChart {
export class CandleStick implements GraphElement {
//ローソク本体
private body: createjs.Shape;
//上ヒゲ
private uShadow: createjs.Shape;
//下ヒゲ
private lShadow: createjs.Shape;
//陽線か?
private isYang: boolean;
constructor(public date: Date, public open: number, public high: number, public low: number, public close: number) {
this.isYang = open < close;
}
public getMax(): number {
return this.high;
}
public getMin(): number {
return this.low;
}
public paint(stage: createjs.Stage, min: number, max: number, x: number, width: number, xmin: number, xmax: number, ymin: number, ymax: number): void  {
//座標決定
//ToDo:0.8でいいのか?
var bodyWidth = width * 0.8;
var bodyXmin = x - width / 2;
var bodyXmax = x + width / 2;
var bodyYmin = ymin + (ymax - ymin) * ((max - (this.isYang ? this.close : this.open)) / (max - min));
var bodyYmax = ymin + (ymax - ymin) * ((max - (this.isYang ? this.open : this.close)) / (max - min));
var uShadowYmin = ymin + (ymax - ymin) * ((max - this.high) / (max - min));
var lShadowYmax = ymin + (ymax - ymin) * ((max - this.low) / (max - min));
//本体描画
var g: createjs.Graphics = new createjs.Graphics();
//ToDo:色の変更
g.beginStroke("#000000");
if (!this.isYang) g.beginFill("#000000");
g.drawEllipse(bodyXmin, bodyYmin, bodyXmax - bodyXmin, bodyYmax - bodyYmin);
this.body = new createjs.Shape(g);
stage.addChild(this.body);
//ヒゲの描画
if (uShadowYmin < bodyYmin) {
g = new createjs.Graphics();
//ToDo:色の変更
g.beginStroke("#000000")
.moveTo(x, bodyYmin)
.lineTo(x, uShadowYmin)
.closePath();
this.uShadow = new createjs.Shape(g);
stage.addChild(this.uShadow);
}
if (lShadowYmax > bodyYmax) {
g = new createjs.Graphics();
//ToDo:色の変更
g.beginStroke("#000000")
.moveTo(x, bodyYmax)
.lineTo(x, lShadowYmax)
.closePath();
this.lShadow = new createjs.Shape(g);
stage.addChild(this.lShadow);
}
}
public drop(stage: createjs.Stage) {
if(this.body) stage.removeChild(this.body);
if(this.uShadow) stage.removeChild(this.uShadow);
if(this.lShadow) stage.removeChild(this.lShadow);
}
}
}

クラス図としては
Chart(canvas1枚に対応) ← ChartHandler(UI担当)

1
n

Graph(グラフ1本に対応)-固定要素、xy軸情報、マウスカーソルに対応する情報など

1
n

GraphElement(グラフの値1日分に対応) 派生:CandleStick, Bar, Line
という感じか?まだまだ、完成しそうにない。来週中には、UIは度外視してグラフくらいは描けるようになるかもしれない。
時間がかかっても自作にこだわる理由。他人が作ったグラフや指標、シグナルを使うのってなんか悔しいじゃない。自分に都合がいいように改良できなくてもどかしいし、他人と同じことをやってるんじゃだめでしょ?


Visual Studio 2013 + TypeScript .jsファイル作成

Visual Studio 2013でTypeScriptを使用して、ファイル分割して作成したアプリケーションを1ファイルにまとめるには、プロジェクトをwebアプリケーションとして作成した上で、
プロジェクト
→「”プロジェクト名”のプロパティ」を選択
→「TypeScript ビルド」セクションの「出力」の「JavaScript 出力をファイルに結合する」を選択し、出力したいファイル名(??????.js)を入力
→コンパイルすれば、プロジェクトファイルと同じフォルダに、jsファイルが作成されます。あとはこのファイルをお好きなhtmlファイルから呼び出してやればOK!
簡単な設定なのに、意外とここにたどり着くまで時間がかかった。。


TypeScript 1.4 with VS2013

easeljs.d.ts を導入して、やったぜタイプセーフwebプログラミング始まる!と思っていたら、導入直後にコンパイルエラーが同時多発で48か所も発生した。

export class AlphaMapFilter extends Filter {
constructor(alphaMap: HTMLImageElement | HTMLCanvasElement);
// properties
alphaMap: HTMLImageElement | HTMLCanvasElement;
// methods
clone(): AlphaMapFilter;
}

こういう | で結ばれたのがコンパイルエラーになる。で、このパイプで結ばれたプロパティはなんじゃらほい、見慣れた記号だしどっちの型でもいいってことじゃないかな、と思ってしばらく調べると、TypeScript1.4でサポートされたUnion typesという機能らしい。やはり、どっちの型でもよいという意味だった。VS2013はインストール時TypeScript1.0までしかサポートされていない。しかしつい最近、2015/01/17にアップデート用モジュールがリリースされていた!
TypeScript 1.4 for Visual Studio 2013 extension
インストールしたらコンパイルエラーが絶滅した。やった!
1.0からの大きな変更点は
・protectedのサポート
・tuple typesのサポート:var tuple: [number, string, boolean] = [1, “str”, true]; //こういう配列の型定義
・union typesのサポート:前述
・type aliasのサポート:declare type PropertyKey = string | number | Symbol; //PropertyKeyは3つのいずれかの型をとる
このくらいか。日々バージョンアップが繰り返されており、今後も目が離せない。


TypeScript Handbook

Handbook – Welcome to TypeScript
を読んだ!!言語仕様は大まかに把握できたので、もう少しで実装に入れる。クラスベースでwebアプリケーションが作成できるなんて、夢のようだ。特に、プロジェクトが大きくなればなるほど、恩恵を受けられることだろう。d.tsファイルを揃えれば、有名どころのjsライブラリでも型セーフなプログラミングが可能、というところもすごい。
実装には北米Yahoo!Financeのチャートがとても参考になりそうだ。ほぼ、このグラフと同じ機能でいいのではないか。見た目が


TypeScriptすごい

Visual Studio Community 2013をインストールした。これが無料!?信じられない。ほぼ会社で使っている環境と変わらない。
TypeScriptはマイクロソフトが作っただけあって、Visual Studioとの連携は完璧だ。IntelliSenseも使えるし、デバッグもブレークポイントの設定もVS上でできる(ただしブラウザはIEに限る)。しかもTypeScriptのままで。もうこれは、C++やC#と全く同じ感覚で使えるね。仕事でやってることと一緒だ。
さらに驚くべきは、TypeScriptで作ったコードは、コンパイルして完璧なJavaScriptに自動変換されるということだ。

class Greeter {
element: HTMLElement;
span: HTMLElement;
timerToken: number;
constructor(element: HTMLElement) {
this.element = element;
this.element.innerHTML += "The time is: ";
this.span = document.createElement('span');
this.element.appendChild(this.span);
this.span.innerText = new Date().toUTCString();
}
start() {
this.timerToken = setInterval(() => this.span.innerHTML = new Date().toUTCString(), 500);
}
stop() {
clearTimeout(this.timerToken);
}
}
window.onload = () => {
var el = document.getElementById('content');
var greeter = new Greeter(el);
greeter.start();
};

たとえばプロジェクトを新規作成すると標準で付属しているこのコード。0.5秒ごとに時刻を更新する時計プログラムだ。クラス定義や型定義、アロー矢印など独自の定義が使用されている。これをブラウザで実行したときのソースは

var Greeter = (function () {
function Greeter(element) {
this.element = element;
this.element.innerHTML += "The time is: ";
this.span = document.createElement('span');
this.element.appendChild(this.span);
this.span.innerText = new Date().toUTCString();
}
Greeter.prototype.start = function () {
var _this = this;
this.timerToken = setInterval(function () {
return _this.span.innerHTML = new Date().toUTCString();
}, 500);
};
Greeter.prototype.stop = function () {
clearTimeout(this.timerToken);
};
return Greeter;
})();
window.onload = function () {
var el = document.getElementById('content');
var greeter = new Greeter(el);
greeter.start();
};
//# sourceMappingURL=app.js.map

こうなる。すげー。クラスはちゃんとクロージャ内に収まってるし、start()内の this 識別子も _this 変数に代入することでjavascriptでも正しく動作するように解決されている。っていうか、生の javascript って、書き方めんどくさいよねぇ。。
詳しい記事は以下にもあります。
TypeScriptとは? Visual Studioを使って開発してみよう – Build Insider