SVGを学習してようやく目的のツールを作ることができました。
株価チャートは、「キャンドルチャート」として、色々なグラフツールでライブラリとして公開されているのですが、どれも機能が足りていなかったり、表示を変更したいが根本的に対応していなかったりして、かなりのカスタマイズをしなければいけないものがほとんどで、その工数を考えたら自作した方が早いという結論だったわけです。
株価チャートに求めたかった事
レスポンシブデザインに対応したい
どうしても昨今ではスマートフォンというニーズを外せないため、ブラウザの表示サイズに対応できるチャートツールが必要。
表示を日本語にしたい
ほとんどのチャートツールは海外製の為、いたるところが英語表記されているので、それらを修正していくのはなかなかの手間。
表示スピードをサックサクにしたい
ライブラリは裏で色々な処理をしているらしく、表示がモタつくものも少なくありませんでした。
必要最低限の機能にするだけで、サクサクの挙動が可能になりました。
ソースコード
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<title>[svg] candle chart</title>
<link rel="stylesheet" href="svg.css">
<script src="candleChart.js"></script>
</head>
<body>
<h1>[3909]</h1>
<div id="svg"></div>
<script>
new $$candle({
id : "#svg",
data : "3909.csv",
height : 400
});
</script>
</body>
</html>
#svg svg{
width:640;
height:640;
margin:0 auto;
display:block;
}
#chart_base{
user-select: none;
}
.button-plus:hover,
.button-minus:hover,
.button-next:hover,
.button-prev:hover{
opacity:0.5;
cursor:pointer;
}
.value-data,
.volume-data{
cursor:pointer;
}
.scroll-bar:hover{
opacity:0.5;
cursor:move;
}
.info-frame-shadow{
filter:url(#info-shadow);
}
#scale_future,
#scale_past{
cursor:pointer;
}
;$$ajax = (function(){
var $$ajax = function(options){
if(!options){return}
var ajax = new $$ajax;
var httpoj = $$ajax.prototype.createHttpRequest();
if(!httpoj){return;}
// open メソッド;
var option = ajax.setOption(options);
// 実行
httpoj.open( option.method , option.url , option.async );
// type
httpoj.setRequestHeader('Content-Type', option.type);
// onload-check
httpoj.onreadystatechange = function(){
//readyState値は4で受信完了;
if (this.readyState==4){
//コールバック
option.onSuccess(this.responseText);
}
};
// responseType
if(typeof option.responseType !== "undefined"
&& option.responseType){
httpoj.responseType = option.responseType;
}
//query整形
var data = ajax.setQuery(option);
//send メソッド
if(data.length){
httpoj.send(data.join("&"));
}
else{
httpoj.send();
}
};
$$ajax.prototype.dataOption = {
url:"",
query:{}, // same-key Nothing
querys:[], // same-key OK
data:{}, // ETC-data event受渡用
async:"true", // [trye:非同期 false:同期]
method:"POST", // [POST / GET]
responseType:"",
type:"application/x-www-form-urlencoded", // [text/javascript]...
onSuccess:function(res){},
onError:function(res){}
};
$$ajax.prototype.option = {};
$$ajax.prototype.createHttpRequest = function(){
//Win ie用
if(window.ActiveXObject){
//MSXML2以降用;
try{return new ActiveXObject("Msxml2.XMLHTTP")}
catch(e){
//旧MSXML用;
try{return new ActiveXObject("Microsoft.XMLHTTP")}
catch(e2){return null}
}
}
//Win ie以外のXMLHttpRequestオブジェクト実装ブラウザ用;
else if(window.XMLHttpRequest){return new XMLHttpRequest()}
else{return null}
};
$$ajax.prototype.setOption = function(options){
var option = {};
for(var i in this.dataOption){
if(typeof options[i] != "undefined"){
option[i] = options[i];
}
else{
option[i] = this.dataOption[i];
}
}
return option;
};
$$ajax.prototype.setQuery = function(option){
var data = [];
if(typeof option.query != "undefined"){
for(var i in option.query){
data.push(i+"="+encodeURIComponent(option.query[i]));
}
}
if(typeof option.querys != "undefined"){
for(var i=0;i<option.querys.length;i++){
if(typeof option.querys[i] == "Array"){
data.push(option.querys[i][0]+"="+encodeURIComponent(option.querys[i][1]));
}
else{
var sp = option.querys[i].split("=");
data.push(sp[0]+"="+encodeURIComponent(sp[1]));
}
}
}
return data;
};
return $$ajax;
})();
;$$svg = (function(){
var $$ = function(selector , option , shapes){
var target = (selector) ? document.querySelector(selector) : document.body;
if(!target){return;}
var svg = target.querySelector("svg");
if(!svg){
svg = $$.prototype.make.svg(option);
}
target.appendChild(svg);
for(var i=0; i<shapes.length; i++){
$$.prototype.make.shapes(svg , shapes[i]);
}
};
$$.prototype.add = function(target , shapes){
if(!target){return;}
if(typeof target === "string"){
target = document.querySelector(selector);
}
if(!target){return;}
for(var i=0; i<shapes.length; i++){
$$.prototype.make.shapes(target , shapes[i]);
}
};
$$.prototype.make = {
namespace : "http://www.w3.org/2000/svg",
xlink : "http://www.w3.org/1999/xlink",
svg : function(option){
if(!option){return;}
var svg = document.createElementNS(this.namespace , "svg");
for(var i in option){
svg.setAttribute(i , option[i]);
}
return svg;
},
shapes : function(target , option){
if(!option){return;}
var elm = document.createElementNS(this.namespace , option[0]);
target.appendChild(elm);
// attribute
for(var i in option[1]){
if(i === "transform" && typeof option[1][i] === "object"){
option[1][i] = this.transform(option[1][i]);
}
elm.setAttribute(i , option[1][i]);
}
// string or childNodes
if(typeof option[2] === "string" || typeof option[2] === "number"){
// elm.textContent = option[2];
elm.innerHTML = option[2];
}
else if(option[2] && typeof option[2] === "object" && option[2].length){
for(var i=0; i<option[2].length; i++){
this.shapes(elm , option[2][i]);
}
}
// event
if(option[3] && typeof option[3] === "object" && option[3].length){
for(var i=0; i<option[3].length; i++){
$$event(elm , option[3][i][0] , option[3][i][1]);
}
}
return elm;
},
transform:function(option){
var transform = [];
if(option.translate){
var tx = (option.translate.x) ? option.translate.x : 0;
var ty = (option.translate.y) ? option.translate.y : 0;
transform.push("translate("+ tx +" "+ ty +")");
}
if(option.rotate){
transform.push("rotate("+ option.rotate +")");
}
if(option.scale){
transform.push("scale("+ option.scale +")");
}
if(transform.length){
return transform.join(" ");
}
else{
return "";
}
}
};
var $$event = function(target, mode, func){
if (typeof target.addEventListener !== "undefined"){
target.addEventListener(mode, func, false);
}
else if(typeof target.attachEvent !== "undefined"){
target.attachEvent('on' + mode, function(){func.call(target , window.event)});
}
};
return $$;
})();
;$$candle = (function(){
// 株価データ格納用
var $$data = {};
var $$split = {};
// 各種オプションデータ
var $$options = {
options : {}, // 初期設定値
base:{
id : "chart_base",
width : null, // 全体の幅
responsive : true,
height : null, // 全体の縦
chartWidth : null, // チャート部分の幅
chartHeight : null, // チェート部分の縦
margin_top : 40,
margin_bottom : 40,
margin_left : 70,
margin_right : 30,
mainLineColor : "#ccc", // ラインカラー
bgColor : "white"
},
fromTo :{
margin : 240
},
title:{
fontSize : 14,
color : "black"
},
menu:{
value_id : "menuValue",
volume_id : "menuVolume",
lineColor : "rgba(180,180,180,0.5)",
fontSize : 10, // menuフォントサイズ
fontWeight : "bold",
minPrice : null, // value-menu のmin値
maxPrice : null, // value-menu のmax値
minVolume : null, // volume-menu のmin値
maxVolume : null, // volume-menu のmax値
unitPrice : null, // menuの1unit単位の数値
unitPixel : null, // menuの表示ピクセル数
unitBefore : "",
unitAfter : ""
},
dateMenu:{
},
candle:{
plusFill : "lightblue",
plusStroke : "black",
minusFill : "white",
minusStroke : "black"
},
value:{
id : "value",
start : null,
max_view : 60, //最大表示日数
view_ratio : 1.2,
margin_candle : 1, // 1つの左右マージン
minNum : null, // 最小 株価
maxNum : null, // 最大 株価
heightSplit : 5 // menu-scaleの分割数
},
volume:{
id : "volume",
height : 60, // volumeの縦サイズ
margin_bottom : 20,
minNum : null, // 最小 出来高
maxNum : null, // 最大 出来高
fillColor : "black", // 棒グラフの塗り色
strokeColor : "none",
margin_lr : 1, // 1つの左右マージン
heightSplit : 2 // menu-scaleの分割数
},
scroll:{
moveFlg : false,
sizePastFlg : false,
sizeFutureFlg : false,
id : "scroll",
height : 40,
margin : 0,
bgColor : "white",
stroke : "#ccc",
barSize : null,
barPos : null,
barColor : "rgba(0,0,0,0.3)",
barStroke : "rgb(0,0,0,0.3)",
chartBG : "lightblue",
chartLine : "#0073A2"
}
};
// 起動処理
var $$ = function(options){
$$options.options = options;
// global-valueを設定
if(typeof options.width === "undefined" || typeof options.height === "undefined"){
console.log("No value for 'width' or 'height'");
return;
}
$$options.base.responsive = (options.width) ? false : true;
$$options.base.width = (options.width) ? options.width : window.document.body.offsetWidth;
$$options.base.height = options.height;
$$options.base.chartWidth = $$getValueWidth();
$$options.base.chartHeight = $$getValueHeight();
// drow-graph-base(枠線などを設置)
$$draw_chartBase();
// // コントロールボタンの設置
// $$draw_button();
// 日付のfrom-to表示欄を表示
$$draw_from_to.make();
// load-data
var datas = new $$ajax({
url : options.data,
method : "GET",
onSuccess : $$loadData
});
};
// csvデータ読み込み後処理
// data-format [ Data , Open , High , Low , Close , Volume ]
var $$loadData = function(res){
if(!res){
console.log("No data.");
return;
}
var lines = res.split("\n");
console.log(lines.length +" datas.");
var minPrice = null;
var maxPrice = null;
var minVolume = null;
var maxVolume = null;
// pick split
lines.sort();
for(var i=0; i<lines.length; i++){
if(!lines[i]){continue;}
if(!lines[i]){continue;}
var sp = lines[i].split(",");
if(sp[1] !== "split" || !sp[2]){continue;}
$$split[sp[0]] = Number(sp[2]);
}
// data-adjust
var split = 1;
for(var i=lines.length-1; i>=0; i--){
if(!lines[i]){continue;}
var sp = lines[i].split(",");
if(sp[1] === "split"){continue;}
var dates = $$dateDisassembly(sp[0]);
var newData = {
date : {y:dates[0] , m:dates[1] , d:dates[2]},
open : Number(sp[1] / split),
high : Number(sp[2] / split),
low : Number(sp[3] / split),
close : Number(sp[4] / split),
volume : Number(sp[5]),
};
$$data[$$dateFormat_number(dates)] = newData;
if(minPrice === null || minPrice > newData.low ){minPrice = newData.low;}
if(maxPrice === null || maxPrice < newData.high ){maxPrice = newData.high;}
if(minVolume === null || minVolume > newData.volume){minVolume = newData.volume;}
if(maxVolume === null || maxVolume < newData.volume){maxVolume = newData.volume;}
//split
if(typeof $$split[sp[0]] !== "undefined"){
split = split * $$split[sp[0]];
}
}
$$options.value.minNum = minPrice;
$$options.value.maxNum = maxPrice;
$$options.volume.minNum = minVolume;
$$options.volume.maxNum = maxVolume;
$$options.value.start = Object.keys($$data).length - $$options.value.max_view;
// first - draw
$$redraw_chart();
$$event(window , "resize" , $$resize);
};
// 画面(ウィンドウ)リサイズ時のサイズ変更処理
var $$resize = function(){
// console.log("resize");
if(!$$options.base.responsive){return;}
$$options.base.width = window.document.body.offsetWidth;
$$options.base.chartWidth = $$getValueWidth();
// 座標合わせ
// base
var svg = document.querySelector($$options.options.id +" svg");
svg.setAttribute("width" , $$options.base.width);
// from-to
var from_to = document.getElementById("from_to");
from_to.transform.baseVal[0].matrix.e = $$options.base.width - $$options.fromTo.margin;
// scroll
var scroll = document.getElementById("scroll");
scroll.parentNode.removeChild(scroll);
$$redraw_chart();
};
// メニュー部分(value)の表示金額を算出
var $$getScaleUnitNum = function(){
return ($$options.menu.maxPrice - $$options.menu.minPrice) / $$options.value.heightSplit;
};
// メニュー部分(value)の表示間隔ピクセル数を算出
var $$getScaleUnitPixel = function(){
return $$options.base.chartHeight / $$options.value.heightSplit;
};
// メニュー部分(volume)の表示間隔ピクセル数を算出
var $$getScaleUnitVolume = function(){
return $$options.volume.height / $$options.volume.heightSplit;
};
// yyyymmddフォーマットの日付データを[y,m,d]の配列に分解
var $$dateDisassembly = function(date){
// format判定
if(date.match(/[0-9]{8}/)){
var dates = [];
dates[0] = Number(date.substr(0,4));
dates[1] = Number(date.substr(4,2));
dates[2] = Number(date.substr(6,2));
return dates;
}
};
// 配列に分解された日付データをyyyymmdd文字列で返す
// date-array(3) -> yyyymmdd
var $$dateFormat_number = function(dates){
var y = String(dates[0]);
var m = (Array(2).join("0")+dates[1]).slice(-2);
var d = (Array(2).join("0")+dates[2]).slice(-2);
return y + m + d;
};
// キャンドルチャートの基礎svg構築(初期表示設定)
// view-chart-base
var $$draw_chartBase = function(){
$$options.svg = new $$svg($$options.options.id , {
width : $$options.base.width,
height : $$options.base.height,
id : $$options.base.id,
version : "1.1"
},
[
["text" , {
class : "chart-title",
x : $$options.base.margin_left,
y : $$options.title.fontSize + 10,
fill : $$options.title.color,
"font-weight" : "bold"
} , $$options.options.title]
]
);
};
// 各種ボタンの表示
// draw - button
var $$draw_button = function(){
$$svg($$options.options.id,{},[
["g" , {class:"button-plus" , transform:{translate:{x:$$options.base.width - 35 , y:10} , scale:0.8}} , [
["circle" , {
cx : 15,
cy : 15,
r : 15,
fill : "#ccc",
stroke : "#888",
"stroke-width" : 2
}],
["line" , {
x1 : 15,
y1 : 7,
x2 : 15,
y2 : 23,
fill : "none",
stroke : "#888",
"stroke-width" : 4,
"stroke-linecap" : "round"
}],
["line" , {
x1 : 7,
y1 : 15,
x2 : 23,
y2 : 15,
fill : "none",
stroke : "#888",
"stroke-width" : 4,
"stroke-linecap" : "round"
}]
] , [
["click" , $$chartZoom.plus]
]],
["g" , {class:"button-minus" , transform:{translate:{x:$$options.base.width - 80 , y:10} , scale:0.8}} , [
["circle" , {
cx : 15,
cy : 15,
r : 15,
fill : "#ccc",
stroke : "#888",
"stroke-width" : 2
}],
["line" , {
x1 : 7,
y1 : 15,
x2 : 23,
y2 : 15,
fill : "none",
stroke : "#888",
"stroke-width" : 4,
"stroke-linecap" : "round"
}]
] , [
["click" , $$chartZoom.minus]
]]
]);
};
// 期間オブジェクト(from - to)の表示処理
var $$draw_from_to = {
// 初期表示設定
make:function(){
$$svg($$options.options.id,{},[
["g" , {id : "from_to", class:"view-date" , transform:{translate:{x:$$options.base.width - $$options.fromTo.margin , y:10}} , "font-size":12 , fill : "black"} , [
["g" , {} , [
["rect" , {
x : 0,
y : 0,
width : 100,
height : 24,
rx : 4,
ry : 4,
fill : "white",
stroke : "#aaa",
"stroke-width" : 1
}],
["text" , {
id : "date_from",
x : 0 + 50,
y : 16,
dx : 0,
dy : 0,
"text-anchor" : "middle"
} , ""]
] , [["click" , $$draw_from_to.calendar]]],
["g" , {} , [
["rect" , {
x : 110,
y : 0,
width : 100,
height : 24,
rx : 4,
ry : 4,
fill : "white",
stroke : "#aaa",
"stroke-width" : 1
}],
["text" , {
id : "date_to",
x : 110 + 50,
y : 16,
dx : 0,
dy : 0,
"text-anchor" : "middle"
} , ""]
] , [["click" , $$draw_from_to.calendar]]]
]]
]);
},
// チャート表示を元にテキスト部分の更新
text:function(){
var elm1 = document.getElementById("date_from");
var elm2 = document.getElementById("date_to");
var date1 = $$draw_from_to.getCount2Date($$options.value.start);
var date2 = $$draw_from_to.getCount2Date($$options.value.start + $$options.value.max_view -1);
elm1.textContent = date1;
elm2.textContent = date2;
},
// 表示しているデータ配列番号から、日付を算出する
getCount2Date:function(cnt){
var count = 0;
var date = null;
for(var i in $$data){
if(count === cnt){
date = i;
break;
}
count++;
}
return $$draw_from_to.dateFormat(date);
},
// 日付からデータの何番目かのカウント数を返す
getDate2Count:function(date){
var count = 0;
for(var i in $$data){
if(i === date){
return count;
break;
}
count++;
}
return null;
},
// 日付の表示フォーマット
dateFormat:function(date){
var dates = $$dateDisassembly(date);
var val = dates[0] +"/"+ (Array(2).join("0")+dates[1]).slice(-2) +"/"+ (Array(2).join("0")+dates[2]).slice(-2);
return val;
},
//カレンダー表示
calendar:function(){
console.log("calendar");
}
};
// チャート全体の期間拡大・縮小処理
var $$chartZoom = {
// 合わせ位置判定
minus:function(e){
// front合わせ
if($$options.value.start <= 0){
var res = $$chartZoom.minus_front(e);
}
// back合わせ
else if($$options.value.start + $$options.value.max_view >= Object.keys($$data).length){
var res = $$chartZoom.minus_back(e);
}
// 中央合わせ
else{
var res = $$chartZoom.minus_middle(e);
}
if(!res){return;}
$$options.value.start = res.start;
$$options.value.max_view = res.max_view;
$$redraw_chart();
$$draw_scroll();
$$scrollFuturePos();
},
plus:function(e){
// front合わせ
if($$options.value.start <= 0){
var res = $$chartZoom.plus_front(e);
}
// back合わせ
else if($$options.value.start + $$options.value.max_view >= Object.keys($$data).length){
var res = $$chartZoom.plus_back(e);
}
// 中央合わせ
else{
var res = $$chartZoom.plus_middle(e);
}
if(!res){return;}
$$options.value.start = res.start;
$$options.value.max_view = res.max_view;
$$redraw_chart();
$$draw_scroll();
$$scrollFuturePos();
},
// 先頭合わせ
minus_front:function(e){
var max_view = Math.floor($$options.value.max_view * $$options.value.view_ratio);
var max = Object.keys($$data).length;
var start = ($$options.value.start + max_view > max) ? max - max_view : $$options.value.start;
if(max_view > 250){
return null;
}
else{
return {start : start , max_view : max_view};
}
},
plus_front:function(e){
var max_view = Math.floor($$options.value.max_view / $$options.value.view_ratio);
if(max_view < 10){
return null;
}
else{
return {start : $$options.value.start , max_view : max_view};
}
},
// 中央合わせ
minus_middle:function(e){
var endpoint = $$options.value.start + $$options.value.max_view;
var cache = Math.floor($$options.value.max_view * $$options.value.view_ratio);
var start = $$options.value.start + Math.floor(($$options.value.max_view - cache) / 2);
var max_view = cache;
var max = Object.keys($$data).length;
start = (start + max_view > max) ? max - max_view : start;
start = (start < 0) ? 0 : start;
if(max_view > 250){
return null;
}
else{
return {start : start , max_view : max_view};
}
},
plus_middle:function(e){
var endpoint = $$options.value.start + $$options.value.max_view;
var cache = Math.floor($$options.value.max_view / $$options.value.view_ratio);
var start = $$options.value.start + Math.floor(($$options.value.max_view - cache) / 2);
var max_view = cache;
if(max_view < 10){
return null;
}
else{
return {start : start , max_view : max_view};
}
},
// 後方合わせ
minus_back:function(e){
var endpoint = $$options.value.start + $$options.value.max_view;
var cache = Math.floor($$options.value.max_view * $$options.value.view_ratio);
var start = endpoint - cache;
var max_view = cache;
start = (start < 0) ? 0 : start;
if(max_view > 250){
return null;
}
else{
return {start : start , max_view : max_view};
}
},
plus_back:function(e){
var endpoint = $$options.value.start + $$options.value.max_view;
var cache = Math.floor($$options.value.max_view / $$options.value.view_ratio);
var start = endpoint - cache;
var max_view = cache;
if(max_view < 10){
return null;
}
else{
return {start : start , max_view : max_view};
}
}
};
var $$chartMove = {
next : function(){
$$options.value.start += Math.floor($$options.value.max_view / 2);
var max = Object.keys($$data).length - $$options.value.max_view;
$$options.value.start = ($$options.value.start > max) ? max : $$options.value.start;
$$redraw_chart();
},
prev : function(){
$$options.value.start -= Math.floor($$options.value.max_view / 2);
$$options.value.start = ($$options.value.start <= 0) ? 0 : $$options.value.start;
$$redraw_chart();
}
};
var $$redraw_chart = function(){
$$popupDateInfo.rm();
$$getMaxData();
$$drawValue();
$$drawVolume();
$$draw_scale();
$$draw_scroll();
$$draw_from_to.text();
// $$draw_split.draw();
};
// Menu-scale
var $$draw_scale = function(){
// value
var menuValue = document.getElementById($$options.menu.value_id);
if(!menuValue){
var svg = document.querySelector($$options.options.id +" svg");
menuValue = $$svg.prototype.make.shapes(svg , ["g" , {
id:$$options.menu.value_id,
transform:{translate:{x:0 , y:$$options.base.margin_top}}
}]);
}
else{
menuValue.textContent = null;
}
for(var i=0; i<=$$options.value.heightSplit; i++){
var texts = [];
var x1 = $$options.base.margin_left;
var x2 = $$options.base.width - $$options.base.margin_right
var y = $$options.base.chartHeight - ( $$options.menu.unitPixel * i );
var num = $$getMenuText(i);
texts.push(["text" , {
x : x1 - 12,
y : y + ($$options.menu.fontSize/2),
"font-size" : $$options.menu.fontSize,
"text-anchor" : "end",
"font-weight" : $$options.menu.fontWeight
} , $$numberFormat(num)]);
texts.push(["line" , {
class:"menuLine",
x1 : x1-6,
y1 : y,
x2 : x2,
y2 : y,
fill : "none",
stroke : $$options.menu.lineColor,
"stroke-width" : 1
}]);
$$svg.prototype.add(menuValue , texts);
}
// volume
var menuVolume = document.getElementById($$options.menu.volume_id);
if(!menuVolume){
var svg = document.querySelector($$options.options.id +" svg");
menuVolume = $$svg.prototype.make.shapes(svg , ["g" , {
id:$$options.menu.volume_id,
transform:{translate:{
x : 0,
y : $$options.base.height - $$options.volume.height - $$options.volume.margin_bottom - $$options.scroll.height
}
}}]);
}
else{
menuVolume.textContent = null;
}
for(var i=0; i<=$$options.volume.heightSplit; i++){
var texts = [];
var x1 = $$options.base.margin_left;
var x2 = $$options.base.width - $$options.base.margin_right
var y = $$options.volume.height - ( $$options.menu.unitVolume * i );
var num = $$getMenuText(i);
texts.push(["text" , {
x : x1 - 12,
y : y + ($$options.menu.fontSize/2),
"font-size" : $$options.menu.fontSize,
"text-anchor" : "end",
"font-weight" : $$options.menu.fontWeight
} , $$numberFormat(num)]);
texts.push(["line" , {
class:"menuLine",
x1 : x1-6,
y1 : y,
x2 : x2,
y2 : y,
fill : "none",
stroke : $$options.menu.lineColor,
"stroke-width" : 1
}]);
$$svg.prototype.add(menuVolume , texts);
}
};
var $$getMenuText = function(num){
var num = $$options.menu.minPrice + ($$options.menu.unitPrice * num);
return $$options.menu.unitBefore + num + $$options.menu.unitAfter;
// return $$options.menu.unitPrice * num;
}
// draw - value
var $$drawValue = function(){
var chart_w = $$getValueWidth();
var dayWidth = (chart_w / $$options.value.max_view); // 1日分の幅サイズ
var candleBase = document.getElementById($$options.value.id);
if(!candleBase){
var svg = document.querySelector($$options.options.id +" svg");
candleBase = $$svg.prototype.make.shapes(svg , ["g" , {
id:$$options.value.id,
transform:{translate:{
x : $$options.base.margin_left,
y : $$options.base.margin_top
}}
}]);
}
else{
candleBase.textContent = null;
}
var count = 0;
$$yearFlg = {};
for(var i in $$data){
// 開始位置
if(count < $$options.value.start){
count++;
continue;
}
// 終了位置
if(count >= $$options.value.start + $$options.value.max_view){
break;
}
var x = dayWidth * (count - $$options.value.start);
var candle = $$makeValue(
candleBase,
(count - $$options.value.start),
i,
$$data[i].open,
$$data[i].high,
$$data[i].low,
$$data[i].close,
x,
dayWidth
);
count++;
}
};
// スクロールパーツの表示
var $$draw_scroll = function(){
$$options.scroll.barSize = $$getScrollSize();
$$options.scroll.barPos = $$getScrollPos();
var scrollElm = document.getElementById($$options.scroll.id);
if(!scrollElm){
$$new_scroll();
}
else{
$$redraw_scroll();
}
};
// スクロールパーツの新規表示処理
var $$new_scroll = function(){
var svg = document.querySelector($$options.options.id +" svg");
var x = $$options.base.margin_left;
var y = $$options.base.height - $$options.scroll.height;
var h = $$options.scroll.height - ($$options.scroll.margin * 2);
var scroll = [
["g" , {id:$$options.scroll.id , transform : {translate:{x:x , y:y}}} , [
["rect" , {x:0,y:0,
width : $$options.base.chartWidth,
height : $$options.scroll.height,
fill : $$options.scroll.bgColor,
stroke : $$options.scroll.stroke,
"stroke-width" : 1
}],
["polyline" , {points : $$setScrollGraph() , fill:$$options.scroll.chartBG , stroke:$$options.scroll.chartLine , "stroke-width":1}],
["g" , {id:"scrollBar" , class:"scroll-bar" , transform:{translate:{x:$$options.scroll.barPos , y:$$options.scroll.margin}}} , [
["rect" , {
id : "scrollBar_child",
x : 0,
y : 0,
width : $$options.scroll.barSize,
height : h,
fill : $$options.scroll.barColor,
stroke : $$options.scroll.barStroke,
"stroke-width" : 1
} , "" , [["mousedown" , $$scrollEventMouseDown] , ["touchstart" , $$scrollEventMouseDown]]]
]],
["g" , {id:"scale_past",transform:{translate:{x:$$options.scroll.barPos,y:h/4}}},[
["circle" , {cx:0,cy:h/4,r:h/4,fill:"#eee",stroke:"#888","stroke-width":1}],
["line" , {x1:-2,y1:4 ,x2:-2,y2:h/2-4,fill:"none",stroke:"#aaa","stroke-width":2}],
["line" , {x1: 2,y1:4 ,x2: 2,y2:h/2-4,fill:"none",stroke:"#aaa","stroke-width":2}]
],[["mousedown" , $$scrollPastClick] , ["touchstart" , $$scrollPastClick]]],
["g" , {id:"scale_future" , transform:{translate:{x:$$options.scroll.barPos + $$options.scroll.barSize,y:h/4}}},[
["circle" , {cx:0,cy:h/4,r:h/4,fill:"#eee",stroke:"#888","stroke-width":1}],
["line" , {x1:-2,y1:4 ,x2:-2,y2:h/2-4,fill:"none",stroke:"#aaa","stroke-width":2}],
["line" , {x1: 2,y1:4 ,x2: 2,y2:h/2-4,fill:"none",stroke:"#aaa","stroke-width":2}]
],[["mousedown" , $$scrollFutureClick] , ["touchstart" , $$scrollFutureClick]]]
]]
];
$$svg.prototype.add(svg , scroll);
$$event(window , "mouseup" , $$scrollEventMouseUp);
$$event(window , "touchend" , $$scrollEventMouseUp);
$$event(window , "mousemove" , $$scrollEventMouseMove);
$$event(window , "touchmove" , $$scrollEventMouseMove);
};
// スクロールパーツの再表示処理
var $$redraw_scroll = function(){
var elm1 = document.getElementById("scrollBar");
var elm2 = document.getElementById("scrollBar_child");
if(!elm1 || !elm2){return;}
elm1.transform.baseVal[0].matrix.e = $$options.scroll.barPos;
elm2.setAttribute("width" , $$options.scroll.barSize);
};
// スクロールサイズ変更(過去分)
var $$scrollPastClick = function(e){
var pageX = (typeof e.touches !== "undefined") ? e.touches[0].pageX : e.pageX;
var elm = document.getElementById("scale_past");
$$options.scroll.sizePastFlg = {
pageX : pageX,
// pageY : e.pageY,
posX : Number(elm.transform.baseVal[0].matrix.e)
};
};
// スクロールサイズ変更(未来分)
var $$scrollFutureClick = function(e){
var pageX = (typeof e.touches !== "undefined") ? e.touches[0].pageX : e.pageX;
var elm = document.getElementById("scale_future");
$$options.scroll.sizeFutureFlg = {
pageX : pageX,
// pageY : e.pageY,
posX : Number(elm.transform.baseVal[0].matrix.e)
};
};
// scroll部分に表示するclose値グラフ
var $$setScrollGraph = function(){
// y
var height = $$options.scroll.height;
var max_price = $$options.value.maxNum;
var ratio_y = height / max_price;
// x
var width = $$options.base.chartWidth;
var max_count = Object.keys($$data).length;
var ratio_x = width / max_count;
var points = [];
points.push("0" + "," + height);
var count = 0;
for(var i in $$data){
var x = String(ratio_x * count);
var y = String(height - (ratio_y * $$data[i].close));
points.push(x + "," + y);
count++;
}
points.push(width + ","+height);
return points.join(" ");
};
// scroll部分の横幅ピクセル数の取得
var $$getScrollArea = function(){
// return $$options.base.chartWidth - ($$options.scroll.margin * 2);
return $$options.base.chartWidth;
};
// スクロールバーのサイズを算出
var $$getScrollSize = function(){
var ratio = $$options.value.max_view / Object.keys($$data).length;
$$options.scroll.barSize = $$getScrollArea() * ratio;
return $$options.scroll.barSize;
};
// スクロールバーのポジションを算出
var $$getScrollPos = function(){
var max_size = $$getScrollArea() - $$options.scroll.barSize;
var max_count = Object.keys($$data).length - $$options.value.max_view;
var ratio = $$options.value.start / max_count;
var pos = max_size * ratio;
// var elm = document.getElementById("scrollBar");
if(pos < 0){
return 0;
}
else{
return pos;
}
};
// スクロールバーのx値を取得(右側のみ座標コントロールが必要なため)
var $$scrollFuturePos = function(){
var scale_future = document.getElementById("scale_future");
scale_future.transform.baseVal[0].matrix.e = $$options.scroll.barSize;
};
// スクロールイベントmousedown処理(座標情報の格納)
var $$scrollEventMouseDown = function(e){
var pageX = (typeof e.touches !== "undefined") ? e.touches[0].pageX : e.pageX;
var elm = document.getElementById("scrollBar");
$$options.scroll.moveFlg = {
pageX : pageX,
posX : Number(elm.transform.baseVal[0].matrix.e)
};
};
// スクロールイベントのmouseup処理(フラグ情報の解放)
var $$scrollEventMouseUp = function(e){
$$options.scroll.moveFlg = false;
$$options.scroll.sizePastFlg = false;
$$options.scroll.sizeFutureFlg = false;
};
// スクロールイベントのmousemove処理(スクロール移動、拡縮処理)
var $$scrollEventMouseMove = function(e){
var pageX = (typeof e.touches !== "undefined") ? e.touches[0].pageX : e.pageX;
// date-move
if($$options.scroll.moveFlg !== false){
$$scroll.drag(pageX);
}
// scroll-size-past
if($$options.scroll.sizePastFlg !== false){
$$scroll.scalePast(pageX);
}
// scroll-size-future
if($$options.scroll.sizeFutureFlg !== false){
$$scroll.scaleFuture(pageX);
}
};
// スクロール処理
var $$scroll = {
// barドラッグ処理
drag : function(pageX){
var flg = $$options.scroll.moveFlg;
var x = (pageX - flg.pageX) + flg.posX;
if(x < $$options.scroll.margin){
x = $$options.scroll.margin;
}
else if(x > $$options.base.chartWidth - $$options.scroll.barSize){
x = $$options.base.chartWidth - $$options.scroll.barSize;
}
var elm = document.getElementById("scrollBar");
elm.transform.baseVal[0].matrix.e = x;
// scale-left
var scale_past = document.getElementById("scale_past");
scale_past.transform.baseVal[0].matrix.e = x;
var scale_future = document.getElementById("scale_future");
scale_future.transform.baseVal[0].matrix.e = x + $$options.scroll.barSize;
// chart - redraw
$$options.value.start = $$getBarPos2Start();
$$redraw_chart();
},
// 過去分拡縮処理
scalePast:function(pageX){
var flg = $$options.scroll.sizePastFlg;
var x = (pageX - flg.pageX) + flg.posX;
var scrollBar = document.getElementById("scrollBar");
var elm = document.getElementById("scale_past");
// scroll - redraw
var endpoint = $$options.value.start + $$options.value.max_view;
var start = $$getScrollPos2Start(x);
var size = $$options.value.start - start + $$options.value.max_view;
if(start < 0){return;}
if(size < 10 || size > 240){return;}
// リサイズ
elm.transform.baseVal[0].matrix.e = x;
$$options.value.start = start;
$$options.value.max_view = size;
// chart - redraw
$$redraw_chart();
},
scaleFuture:function(pageX){
var flg = $$options.scroll.sizeFutureFlg;
var x = (pageX - flg.pageX) + flg.posX;
var elm = document.getElementById("scale_future");
// scroll - redraw
var endBefore = $$options.value.start + $$options.value.max_view;
var endAfter = $$getScrollPos2Start(x);
var size = endAfter - $$options.value.start;
if(endAfter > Object.keys($$data).length){return;}
if(size < 10 || size > 240){return;}
elm.transform.baseVal[0].matrix.e = x;
$$options.value.max_view = size;
// chart - redraw
$$redraw_chart();
}
};
// draw - volume
// draw-price
var $$drawVolume = function(){
var chart_w = $$getValueWidth();
var dayWidth = (chart_w / $$options.value.max_view); // 1日分の幅サイズ
var volumeBase = document.getElementById($$options.volume.id);
if(!volumeBase){
var svg = document.querySelector($$options.options.id +" svg");
volumeBase = $$svg.prototype.make.shapes(svg , ["g" , {
id : $$options.volume.id,
transform:{translate:{
x : $$options.base.margin_left,
y : $$options.base.height - $$options.volume.height - $$options.volume.margin_bottom - $$options.scroll.height
}}
}]);
}
else{
volumeBase.textContent = null;
}
var count = 0;
for(var i in $$data){
// 開始位置
if(count < $$options.value.start){
count++;
continue;
}
// 終了位置
if(count >= $$options.value.start + $$options.value.max_view){
break;
}
var x = dayWidth * (count - $$options.value.start);
var volumeBar = $$makeVolumeBar(
volumeBase,
count - $$options.value.start,
i,
$$data[i].volume,
x,
dayWidth
);
count++;
}
};
// scroll-barの座標を元にローソクチャートを更新する。(開始日時を決める)
var $$getBarPos2Start = function(){
var elm = document.getElementById("scrollBar");
var x = Number(elm.transform.baseVal[0].matrix.e);
var max_size = $$options.base.chartWidth - $$options.scroll.barSize;
var max_count = Object.keys($$data).length - $$options.value.max_view;
var ratio = max_size / max_count;
var res = Math.floor(x / ratio);
return res;
};
// scrollの任意座標からstartカウント(ローロクチャート開始位置)を算出 x->start
var $$getScrollPos2Start = function(x){
var max_size = $$options.base.chartWidth - $$options.scroll.barSize;
var max_count = Object.keys($$data).length - $$options.value.max_view;
var ratio = max_size / max_count;
var res = Math.floor(x / ratio);
return res;
};
/*
open-closeのサイズを求める式
chart-height / MAth.round(最大値 / Math.abs(始値 - 終値))
*/
var $$yearFlg;
var $$makeValue = function(candleBase , count , date,open,high,low,close , x , w){
// 始値-終値
var top = (open >= close) ? open : close;
// 座標取得
var h = $$getChartSize(open , close);
var yp = $$getChartPosition(top);
var yh = $$getChartPosition(high);
var yl = $$getChartPosition(low);
// 下部メニュー表示感覚(サイズが狭い場合は、一定間隔で間引く)
var underMenuCount = ( w >= $$options.menu.fontSize*4) ? 1 : Math.floor($$options.menu.fontSize * 6 / w);
var y = $$chartUnderDateFormat1(date);
if(count % underMenuCount === 0 && !$$yearFlg[y]){
var year = y;
$$yearFlg[y] = true;
}
var txt = (count % underMenuCount === 0) ? ["g" , {transform:{translate:{x:x,y:$$options.base.chartHeight }}} , [
["line" , {
x1 : (w/2),
y1 : 0,
x2 : (w/2),
y2 : -$$options.base.chartHeight,
fill : "none",
stroke : $$options.menu.lineColor,
"stroke-width" : 1
}],
["g" , {transform:{translate:{x:0,y:0}} , "font-size" : 10} , [
["text" , {
x : 10,
y : 16,
fill : "black",
"text-anchor" : "middle"
} , year],
["text" , {
x : 10,
y : 30,
fill : "black",
"text-anchor" : "middle"
} , $$chartUnderDateFormat2(date)]
]]
]] : null;
var w2 = (w<0.1)? 0.1 : w;
var w3 = (w - ($$options.value.margin_candle*2) < 0.1) ? 0.1 : w - ($$options.value.margin_candle*2);
$$svg.prototype.add(candleBase , [
["g" , {
class:"value-data",
"data-date":date,
transform:{translate:{x:x,y:0}}
} , [
["rect" , {
class:"single-value",
"data-date":date,
x:0,
y:0,
width:w2,
height:$$options.base.chartHeight,
fill:"white",
stroke:"none"
} , "", []],
["line" , {
x1 : Math.round(w/2),
y1 : yh,
x2 : Math.round(w/2),
y2 : yl,
stroke : "black",
"stroke-width" : 1
}],
["rect" , {
x : $$options.value.margin_candle,
y : yp,
width : w3,
height : h,
fill : (open < close) ? $$options.candle.plusFill : $$options.candle.minusFill,
stroke : (open < close) ? $$options.candle.plusStroke : $$options.candle.minusStroke
}]
] , [
["mouseover",$$eventSingledataMouseover],
["mouseout",$$eventSingledataMouseout]
]],
txt
]);
};
var $$eventSingledataMouseover = function (e){
var target = e.currentTarget;
var date = target.getAttribute("data-date");
var elm1 = document.querySelector('.value-data .single-value[data-date="'+date+'"]');
if(elm1){
elm1.setAttribute("fill","#eee");
}
var elm2 = document.querySelector('.volume-data .single-volume[data-date="'+date+'"]');
if(elm2){
elm2.setAttribute("fill","#eee");
}
$$popupDateInfo.view(date);
};
var $$eventSingledataMouseout = function (e){
var target = e.currentTarget;
var date = target.getAttribute("data-date");
var elm1 = document.querySelector('.value-data .single-value[data-date="'+date+'"]');
if(elm1){
elm1.setAttribute("fill","white");
}
var elm2 = document.querySelector('.volume-data .single-volume[data-date="'+date+'"]');
if(elm2){
elm2.setAttribute("fill","white");
}
};
var $$makeVolumeBar = function(volumeBase , count , date,volume , x , w){
var h = $$getVolumeSize(volume);
// console.log(h);
var y = $$options.volume.height - h;
// 罫線表示
var underMenuCount = ( w >= $$options.menu.fontSize*4) ? 1 : Math.floor($$options.menu.fontSize * 6 / w);
var line = (count % underMenuCount === 0) ? ["g" , {transform:{translate:{x:x,y:$$options.volume.height }}} , [
["line" , {
x1 : w / 2,
y1 : 0,
x2 : w / 2,
y2 : -$$options.volume.height,
fill : "none",
stroke : $$options.menu.lineColor,
"stroke-width" : 1
}]
]] : null;
var w1 = (w - ($$options.volume.margin_lr * 2) < 1) ? 1 : w - ($$options.volume.margin_lr * 2);
$$svg.prototype.add(volumeBase , [
["g" , {
class : "volume-data",
"data-date" : date,
transform : {translate:{x : x , y : 0}}
} , [
["rect" , {
class : "single-volume",
"data-date" : date,
x : 0,
y : 0,
width : w,
height : $$options.volume.height,
fill : "white",
stroke : "none"
}],
["rect" , {
x : $$options.volume.margin_lr,
y : y,
width : w1,
height : h,
fill : $$options.volume.fillColor,
stroke : $$options.volume.strokeColor
}]
] , [
["mouseover",$$eventSingledataMouseover],
["mouseout",$$eventSingledataMouseout]
]],
line
]);
};
var $$chartUnderDateFormat = function(date){
var dates = $$dateDisassembly(date);
var val = dates[0]+"年>"+dates[1]+"月"+dates[2]+"日";
return val;
};
var $$chartUnderDateFormat1 = function(date){
var dates = $$dateDisassembly(date);
var val = dates[0];
return val;
};
var $$chartUnderDateFormat2 = function(date){
var dates = $$dateDisassembly(date);
var val = dates[1]+"/"+dates[2];
return val;
};
var $$popupDateInfo = {
rm:function(){
// 初期に既存ウィンドウは削除する。
var infoElm = document.getElementById("date_info");
if(infoElm){
infoElm.parentNode.removeChild(infoElm);
}
},
view:function(date){
// rm
$$popupDateInfo.rm();
// view
var svg = document.querySelector($$options.options.id +" svg");
var x = $$popupDateInfo.getPos(date);
var y = $$options.base.margin_top + 20;
var w = 140;
var data = $$popupDateInfo.getData(date);
var splitData = (typeof $$split[date] === "undefined") ? null : $$split[date];
var splitTitle = (typeof $$split[date] === "undefined") ? null : "分割数 :";
var h = (typeof $$split[date] === "undefined") ? 126 : 150;
var dt = new Date(Date.parse(data.date_format));
var week = ["日","月","火","水","木","金","土"];
$$svg.prototype.add(svg , [
["g" , {
id : "date_info",
"data-date" : date,
transform : {translate:{x : x , y : y}},
"font-size" : 12
} , [
["filter" , {id : "info-shadow"} , [
["feGaussianBlur" , {in:"SourceAlpha" , stdDeviation:4}]
]],
["rect" , {
class : "info-frame-shadow",
filter : "#info-shadow",
x : 4 , y : 4 , width : w , height : h,
fill : "rgba(0,0,0,0.5)",
stroke : "none",
"stroke-width" : 1
}],
["rect" , {
class : "info-frame",
x : 0 , y : 0 , width : w , height : h,
fill : "white",
stroke : "black",
"stroke-width" : 1
}],
["g" , {fill : "black"} , [
["text" , {x : 10 , y : 14 , dx : 4 , dy : 4} , data.date_format +" ("+ week[dt.getDay()] +")"],
["text" , {x : w -8 , y : 114 , dx : 4 , dy : 4 , "text-anchor":"end"} , "(円)"],
["g" , {"font-weight":"bold" , "text-anchor":"end"},[
["text" , {x : 48 , y : 36 , dx : 0 , dy : 4} , "始値 :"],
["text" , {x : 48 , y : 50 , dx : 0 , dy : 4} , "終値 :"],
["text" , {x : 48 , y : 66 , dx : 0 , dy : 4} , "高値 :"],
["text" , {x : 48 , y : 82 , dx : 0 , dy : 4} , "安値 :"],
["text" , {x : 48 , y : 98 , dx : 0 , dy : 4} , "出来高 :"],
["text" , {x : 48 , y : 136 , dx : 0 , dy : 4} , splitTitle]
]],
["g" , {"text-anchor":"end"} , [
["text" , {x : w -10 , y : 36 , dx : 0 , dy : 4} , $$numberFormat(data.open)],
["text" , {x : w -10 , y : 50 , dx : 0 , dy : 4} , $$numberFormat(data.close)],
["text" , {x : w -10 , y : 66 , dx : 0 , dy : 4} , $$numberFormat(data.high)],
["text" , {x : w -10 , y : 82 , dx : 0 , dy : 4} , $$numberFormat(data.low)],
["text" , {x : w -10 , y : 98 , dx : 0 , dy : 4} , $$numberFormat(data.volume)],
["text" , {x : w -10 , y : 136 , dx : 0 , dy : 4} , splitData]
]]
]]
]]
]);
},
// カーソルが画面の右側か左側にあるかを判定
getPos:function(date){
var from = $$options.value.start;
var to = $$options.value.start + $$options.value.max_view -1;
var dateCount = $$draw_from_to.getDate2Count(date) - $$options.value.start;
// left
if((to - from) / 2 < dateCount){
return $$options.base.margin_left + 20;
}
// right
else{
return $$options.base.width - 120 - $$options.base.margin_right - 20;
}
},
getData:function(date){
for(var i in $$data){
if(i === date){
$$data[i].date_key = i;
$$data[i].date_format = $$data[i].date.y +"/"+ (Array(2).join("0")+$$data[i].date.m).slice(-2) +"/"+ (Array(2).join("0")+$$data[i].date.d).slice(-2);
return $$data[i];
}
}
return null;
}
};
/*
open-closeのサイズを求める式
chart-height / MAth.round(最大値 / Math.abs(始値 - 終値))
*/
var $$getChartSize = function(open , close){
var diff = Math.abs(open - close); // 開始、終値の値段
var unit = ($$options.menu.maxPrice - $$options.menu.minPrice) / diff; // 開始、終値の幅
res = Math.round($$options.base.chartHeight) / unit;
return (res < 2) ? 2 : res; // 実際の高さ
};
var $$getVolumeSize = function(volume){
var unit = ($$options.menu.maxVolume - $$options.menu.minVolume) / volume; // 開始、終値の幅
res = Math.round($$options.volume.height - $$options.volume.margin_bottom) / unit;
return (res < 2) ? 2 : res; // 実際の高さ
};
/*
値を元にchartの座標を求める
chartHeight - (num / maxPrice) * chartHeight
*/
var $$getChartPosition = function(num){
var num1 = (num - $$options.menu.minPrice) / ($$options.menu.maxPrice - $$options.menu.minPrice);
var num2 = Math.round($$options.base.chartHeight * num1);
var res = $$options.base.chartHeight - num2;
return res;
};
var $$getValueWidth = function(){
return $$options.base.width - $$options.base.margin_left - $$options.base.margin_right;
};
var $$getValueHeight = function(){
return $$options.base.height - $$options.base.margin_top - $$options.base.margin_bottom - $$options.volume.height - $$options.volume.margin_bottom - $$options.scroll.height;
};
// データ一覧から株価と出来高の最高地を取得
var $$getMaxData = function(){
var start = (!$$options.value.start) ? 0 : $$options.value.start;
var minPrice = null;
var maxPrice = null;
var minVolume = null;
var maxVolume = null;
var count = 0;
for(var i in $$data){
if(count < start){count++;continue;} // 開始位置
if(count >= start + $$options.value.max_view){break;} // 表示数
if(minPrice === null || minPrice > $$data[i].low ){minPrice = $$data[i].low;}
if(maxPrice === null || maxPrice < $$data[i].high ){maxPrice = $$data[i].high;}
if(minVolume === null || minVolume > $$data[i].volume){minVolume = $$data[i].volume;}
if(maxVolume === null || maxVolume < $$data[i].volume){maxVolume = $$data[i].volume;}
count++;
}
$$options.menu.minPrice = $$getMinRound(minPrice);
$$options.menu.maxPrice = $$getMaxRound(maxPrice);
$$options.menu.minVolume = $$getMinRound(minVolume);
$$options.menu.maxVolume = $$getMaxRound(maxVolume);
$$options.menu.unitPrice = $$getScaleUnitNum();
$$options.menu.unitPixel = $$getScaleUnitPixel();
$$options.menu.unitVolume = $$getScaleUnitVolume();
};
// チャート用に数値をキリのいい値に繰り上げる(最上位桁を1つ繰り上げる)
var $$getMaxRound = function(num){
var sp = String(num).split(".");
var str = String(Number(sp[0])); // 数値を文字列に変換
if(!str.length){
return null;
}
else if(str.length === 1){
return 10;
}
else{
var keta = str.length - 2;
var maru = 10 ** (str.length - 2); // 基準小数点の桁数合わせ様桁数
var num1 = num / ((maru) ? maru : 1); // 上位桁の数値取得
var num2 = Math.round(num1 / 10);// 上位桁に1を加える
if(num2 < (num1/10)){num2 += 0.5;}
var num3 = String(num2 * ((maru) ? 100 : 10)) + Array(keta).join("0")// 桁数を合わせる
return Number(num3);
}
};
// チャート用に数値の開始位置を最小を基準にキリのいい数値を洗濯する
var $$getMinRound = function(num){
var str = String(num);
if(!str.length){
return null;
}
else if(str.length === 1){
return 0;
}
else{
var keta = str.length - 2;
var maru = 10 ** (str.length - 2); // 基準小数点の桁数合わせ様桁数
var num1 = num / ((maru) ? maru : 1); // 上位桁の数値取得
var num2 = Math.floor(num1 / 10);// 上位桁に1を加える
var num3 = String(num2 * ((maru) ? 100 : 10)) + Array(keta).join("0")// 桁数を合わせる
return Number(num3);
}
};
// 数値の3桁ごとに","(カンマ)を追加する
var $$numberFormat = function(num){
num = String(num);
var tmpStr = "";
while (num != (tmpStr = num.replace(/^([+-]?\d+)(\d\d\d)/,"$1,$2"))){num = tmpStr;}
return num;
};
var $$event = function(target, mode, func){
if (typeof target.addEventListener !== "undefined"){
target.addEventListener(mode, func, false);
}
else if(typeof target.attachEvent !== "undefined"){
target.attachEvent('on' + mode, function(){func.call(target , window.event)});
}
};
return $$;
})();
使い方
index.htmlのheadタグでcandlechart.jsを読み込んで、scriptタグで、グラフを表示したいタグをselectorで指定してあげるだけで、表示されますが、その前に事前に株価データをcsv形式で持っておかなければいけません。
csvデータは、下記サイトで企業データを一括でダウンロードできるので、それを使ってもらえるといいでしょう。
https://kabuoji3.com/stock/
ただ、一つだけ注意点があって、日付データの箇所がダウンロードしたものだろ"yyyy-mm-dd"となっていますが、"-"をとった"yyyymmdd"としないとツールではエラーになってしまう仕様にしました。
テキストエディタで置換処理すればすぐに対応できると思います。
そして、下記のようにscriptで実行してあげればチャート表示されます。
<script>
new $$candle({
id : "#svg",
data : "3909.csv",
height : 400
});
</script>
指定する箇所をシンプルにしたので、説明もほとんどいらないかもしれませんが、一応説明すると・・・
id : チャートを表示するエレメントのselectorを記載。(今回はid="svg"の中に表示)
data : csvデータのurlを記載。(phpなどで動的にする事も可能です)
height : 表示したい際の縦サイズを記載。(最低値は400ぐらいにしてください)
簡単解説
とにかくこだわったのは、チャートの拡大やスクロールなどをボタンをたくさん設置するのを避けたくてシンプルインターフェイスを目指しました。
結果、チャート下部にするロールバーとして、全体チャートの表示と、表示エリアのバーを設置することで、かなり分かりやすく使いやすくなりました。
また、株価分割にも対応したかったので、分割日を下記フォーマットでcsvに混ぜ込む事で、それ以前の株価が分割後の表示になるようにしています。
20160329,split,2,
20160727,split,2,
ちなみに、分割日は、info表示の一番下に分割数を表示するようにしました。
グラフ表示にも、この情報を入れたいところですね。
今後つけたい機能
表示期間の日付を表示していますが、これをカレンダーで任意入力できるようなモードが必要ですね。
以前に書いたカレンダーjsを使って行う事も可能です。
[Javascript] カレンダーのソースコードを記載
あとは、分割数のチャート表示を視覚化したいところですね。
参考
freeで使える株価チャートツールです。
http://techanjs.org/
0 件のコメント:
コメントを投稿