Quantcast
Channel: CSSタグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 8925

Chart.js でY軸を表示しながらグラフを横スクロールする【デモページあり】

$
0
0

下記画像を見て「ぴん」とくる方むけの記事です。

image.png

デモページ

下記 JSFiddle ページにデモがあります。
https://jsfiddle.net/oktopus1959/w4gan9tk/23/

適当にソースを修正して「Run」を実行すると、その修正を反映した表示がされます。いろんな値をいじって、表示がどのように変化するかを見るのも面白いですよ。

基本アイデア

参考」にも書きましたが、

  • Y軸のイメージを別の canvas にコピーして元のイメージと重なるように配置する

です。

ソースコード

Chart.js, JavaScript, CSS にある程度慣れている方なら、コメントを読めばやっていることが理解できるかと思います1

ソースコード(クリックして開いてください)
<head><style>/* 配置の基準となるラッパーブロック; とりあえず背景色もここで設定している */.scrollableChartWrapper{position:relative;background-color:ghostwhite;}/* スクロール可能グラフを囲む div */.scrollableChartWrapper>div{position:relative;overflow-x:scroll;}/* Y軸イメージコピー用 canvas */.scrollableChartWrapper>canvas{position:absolute;/* これにより、上の div と重なる位置に canvas が配置される */left:0;top:0;background-color:ghostwhite;/* ここをコメントアウトすると、背景色が透明になるので、Y軸を透かしてグラフ部分が見えるようになる */}</style></head><body><!-- 以下 HTML 部分 --><!-- スクロール可能グラフ領域のラッパーブロック --><divclass="scrollableChartWrapper"><!-- スクロールされる canvas を持つ div:
       - style.width を省略すると div の幅がページ幅に合わせられる;
       - style.width を指定すると div の幅(≒スクロールバーの長さ)が固定される --><div><!-- グラフ描画用 canvas:
         - style.height は必須;
         - style.width は全データを表示するのに必要なグラフ幅であり、JS によって設定する;
         - width,height は Chart により設定される --><canvasid="chart"style="height: 250px"></canvas></div><!-- Y軸イメージコピー用 canvas: {style.,}{height,width} は JS によって設定する --><canvasid="yAxis"width="0"></canvas></div><!-- 以下 JavaScript 部分 --><script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.js"></script><script>// Y軸コピー用 canvasvarcvsYAxis=document.getElementById('yAxis');varctxYAxis=cvsYAxis.getContext('2d');// テスト用データの用意vardata=[];varlabels=[];varcolors=[];for(i=0;i<15;i+=1){[12,19,3,5,2,3].forEach(x=>{data.push(x);});['Red','Blue','Yellow','Green','Purple','Orange'].forEach(x=>{labels.push(x);});['rgb(255, 99, 132)','rgb(54, 162, 235)','rgb(255, 206, 86)','rgb(75, 192, 192)','rgb(153, 102, 255)','rgb(255, 159, 64)'].forEach(x=>{colors.push(x);});}// X軸の1データ当たりの幅varxAxisStepSize=30;// グラフ全体の幅を計算varchartWidth=data.length*xAxisStepSize;// グラフ描画用 canvasvarcvsChart=document.getElementById('chart');varctxChart=cvsChart.getContext('2d');// グラフ描画用canvasのstyle.width(すなわち全データを描画するのに必要なサイズ)に上記の幅を設定cvsChart.style.width=chartWidth+"px";// canvas.width(height)はグラフ描画時に Chart により変更される(らしい)ので、下記は不要//cvsChart.width = chartWidth;//cvsChart.height = chartHeight;console.log("Before chart canvas width="+cvsChart.style.width);console.log("Before chart canvas height="+cvsChart.style.height);// 二重実行防止用フラグvarcopyYAxisCalled=false;// Y軸イメージのコピー関数functioncopyYAxisImage(chart){//console.log("copyYAxisCalled="+copyYAxisCalled);if(copyYAxisCalled)return;copyYAxisCalled=true;// グラフ描画後は、canvas.width(height):canvas.style.width(height) 比は、下記 scale の値になっているvarscale=window.devicePixelRatio;// Y軸のスケール情報varyAxScale=chart.scales['y-axis-0'];// Y軸部分としてグラフからコピーすべき幅 (TODO: 良く分かっていない)varyAxisStyleWidth0=yAxScale.width-10;// canvas におけるコピー幅(yAxisStyleWidth0を直接使うと微妙にずれるので、整数値に切り上げる)varcopyWidth=Math.ceil(yAxisStyleWidth0*scale);// Y軸canvas の幅(右側に少し空白部を残す)varyAxisCvsWidth=copyWidth+4;// 実際の描画幅(styleに設定する)varyAxisStyleWidth=yAxisCvsWidth/scale;// Y軸部分としてグラフからコピーすべき高さ (TODO: 良く分かっていない) ⇒これを実際の描画高とする(styleに設定)varyAxisStyleHeight=yAxScale.height+yAxScale.top+10;// canvas におけるコピー高varcopyHeight=yAxisStyleHeight*scale;// Y軸canvas の高さvaryAxisCvsHeight=copyHeight;console.log("After chart canvas width="+cvsChart.width);console.log("After chart canvas height="+cvsChart.height);console.log("scale="+scale);console.log("copyWidth="+copyWidth);console.log("copyHeight="+copyHeight);console.log("yAxisCvsWidth="+yAxisCvsWidth);console.log("yAxisCvsHeight="+yAxisCvsHeight);console.log("yAxisStyleWidth0="+yAxisStyleWidth0);console.log("yAxisStyleWidth="+yAxisStyleWidth);console.log("yAxisStyleHeight="+yAxisStyleHeight);// 下記はやってもやらなくても結果が変わらないっぽい//ctxYAxis.scale(scale, scale);// Y軸canvas の幅と高さを設定cvsYAxis.width=yAxisCvsWidth;cvsYAxis.height=yAxisCvsHeight;// Y軸canvas.style(実際に描画される大きさ)の幅と高さを設定cvsYAxis.style.width=yAxisStyleWidth+"px";cvsYAxis.style.height=yAxisStyleHeight+"px";// グラフcanvasからY軸部分のイメージをコピーするctxYAxis.drawImage(cvsChart,0,0,copyWidth,copyHeight,0,0,copyWidth,copyHeight);// 軸ラベルのフォント色を透明に変更して、以降、再表示されても見えないようにするchart.options.scales.yAxes[0].ticks.fontColor='rgba(0,0,0,0)';chart.update();// 最初に描画されたグラフのY軸ラベル部分をクリアするctxChart.clearRect(0,0,yAxisStyleWidth,yAxisStyleHeight);}// グラフ描画varmyChart=newChart(ctxChart,{type:'bar',data:{labels:labels,datasets:[{label:'# of Votes',data:data,backgroundColor:colors,borderColor:colors,}]},options:{responsive:false,// true(デフォルト)にすると画面の幅に合わせてしまうscales:{yAxes:[{ticks:{beginAtZero:true}}]}},plugins:[{// 描画完了後に copyYAxisImage() を呼び出す// see https://www.chartjs.org/docs/latest/developers/plugins.html//     https://stackoverflow.com/questions/55416218/what-is-the-order-in-which-the-hooks-plugins-of-chart-js-are-executedafterRender:copyYAxisImage}]});</script></body>

読解のためのヒント

  • グラフ描画用 canvas と、そのY軸イメージをコピーして表示する canvas を用意する
  • 2つの canvas の配置を決定できるように、外側に位置の基準となる div を配置 (これが .scrollableChartWrapper)

  • 水平スクロールバーを表示するには、グラフ用 canvas を div で囲んで、その div に style="overflow-x: scroll"を付加する (CSS の .scrollableChartWrapper > divで設定してあるので、 HTML上では何も書く必要なし)
  • 上記 div と同じレベルで Y軸イメージコピー用 canvas を配置し、position: absoluteを指定して、グラフ用 canvas に重ねて表示する(CSS の .scrollableChartWrapper > canvasで設定してあるので、 HTML上では何も書く必要なし)

  • scrollable div の幅(≒スクロールバーの長さ)をページ幅に合わせる場合は、各 <div>タグに付加している position: relativeが必須(div で style.width を設定する場合は absolute でもOK)
  • グラフ用 canvas の style.widthはJSで計算して設定している(が、HTML部分で設定してもよい;この幅が 外側の scrollable div の幅を超えるとスクロールされるようになる)
  • Chart の responsive属性は falseにしておく(true (default) の場合、ページ幅に応じてグラフ用 canvas の style.width が調節されてしまう)

  • グラフの描画完了のタイミングで、Y軸イメージをコピーする plugin 関数 copyYAxisImage()を呼び出す
  • 軸ラベルのフォントを透明色に変更して、以後の再描画によって軸ラベルが再表示されても目には見えないようにする
  • copyYAxisImage()の末尾でコピー元のY軸イメージをクリアして、最初に表示されていた軸ラベルを消去する
  • デバイスやブラウザ、ズーム率によって window.devicePixelRatioの値が異なり、これがグラフ canvas の width と height に影響するので、Y軸コピー用 canvas を用意する際は、それを考慮する必要がある

  • とにかく canvas と Chart と responsive が絡むと本当に混乱する

参考

下記を参考にさせていただきました。

Y軸のイメージをコピーする、というアイデアは後者のページに負っています(というか、かなりソースを流用させてもらってます)。

ただ、後者の実装では <div>が三重になっているのですが、その理由がよく分からず2。筆者の実装では <div>を二重で済ませてあります。その他、 plugin の呼び出しも初めの1回だけにするなど、いくつかの改良も施してあります。

余談

ここまで書いておいて今さらなんですが、このようにY軸を固定したりするなら、SVG系のチャートライブラリを使うほうがよさそうです。ちなみにNHKの新型コロナ特設サイトでは Highcharts3を使っているようです。このサイトをブラウザの開発者画面で見ると、Y軸ラベル、Y軸、グラフエリアに要素が分かれています。

SVG系チャートライブラリについては、「Chart ツールは Canvas より SVG のほうがよさそう」というページがまとまっているかと思います。


  1. コピーすべきY軸イメージの幅や高さの計算など、流用コードの一部で理解できていない部分があるので、どなたかお分かりになる方がいればコメントいただければ幸いです。(「TODO」を付けているところ) 

  2. いったんグラフを構築してから、追加でデータを push して、その増えた分だけ div の幅を広げるような操作をしているので、そのために必要なのかもしれないのですが、よく理解できていません。 

  3. 商用で使うには有料版を購入する必要があるっぽいですね。 


Viewing all articles
Browse latest Browse all 8925

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>