SVG学習 9日目「株価チャート」

2018年8月20日

Javascript SVG テクノロジー プログラミング 特集

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/

人気の投稿

このブログを検索

ごあいさつ

このWebサイトは、独自思考で我が道を行くユゲタの少し尖った思考のTechブログです。 毎日興味がどんどん切り替わるので、テーマはマルチになっています。 もしかしたらアイデアに困っている人の助けになるかもしれません。

ブログ アーカイブ