質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
Chart.js

Chart.jsは、多様なグラフを組み込めるJavaScriptのライブラリ。折れ線グラフや棒グラフ、円グラフ、レーダーチャートなどのグラフの種類が用意されています。HTML5のCanvasを用いて描画され、マークアップも分かりやすく、簡単に編集することが可能です。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

HTML

HTMLとは、ウェブ上の文書を記述・作成するためのマークアップ言語のことです。文章の中に記述することで、文書の論理構造などを設定することができます。ハイパーリンクを設定できるハイパーテキストであり、画像・リスト・表などのデータファイルをリンクする情報に結びつけて情報を整理します。現在あるネットワーク上のほとんどのウェブページはHTMLで作成されています。

CSS

CSSはXMLやHTMLで表現した色・レイアウト・フォントなどの要素を指示する仕様の1つです。

Q&A

解決済

1回答

1957閲覧

[Chart.js][JavaScript] 再生バーとカーソルの連動:再生バー動かしたらグラフ項目上のカーソルを移動させたい

退会済みユーザー

退会済みユーザー

総合スコア0

Chart.js

Chart.jsは、多様なグラフを組み込めるJavaScriptのライブラリ。折れ線グラフや棒グラフ、円グラフ、レーダーチャートなどのグラフの種類が用意されています。HTML5のCanvasを用いて描画され、マークアップも分かりやすく、簡単に編集することが可能です。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

HTML

HTMLとは、ウェブ上の文書を記述・作成するためのマークアップ言語のことです。文章の中に記述することで、文書の論理構造などを設定することができます。ハイパーリンクを設定できるハイパーテキストであり、画像・リスト・表などのデータファイルをリンクする情報に結びつけて情報を整理します。現在あるネットワーク上のほとんどのウェブページはHTMLで作成されています。

CSS

CSSはXMLやHTMLで表現した色・レイアウト・フォントなどの要素を指示する仕様の1つです。

0グッド

1クリップ

投稿2021/10/11 08:59

編集2021/10/12 08:51

前提

  • [Chart.js][JavaScript]再生数グラフ付きプレーヤー開発・再生位置カーソルを追加したい

https://teratail.com/questions/360634

  • [Chart.js][JavaScript] CSSで描画した円カーソルをChart.jsのグラフ項目のトップに表示させたい

https://teratail.com/questions/360753

  • [Chart.js][JavaScript] グラフ部分をクリックして対応の再生位置に遷移させたい、再生バー動かしたらカーソルも移動させたい

https://teratail.com/questions/361574

実現したいこと

  • プレーヤーシークバーの移動(再生ボタン押下、シークバードラッグなど)に応じてグラフ頂点のカーソルを連動させたい

カーソルというのは画像の赤点を指しています。グラフ項目の頂点をマウスオーバーやクリックに応じて移動するイメージです。
イメージ説明

プレーヤーの現在再生時間を取得し、グラフ項目の一番近い部分にカーソルを描画するかと思いますが、画像を例とし、以下の流れを想定しています。

  1. プレーヤーの現在再生時間を取得(17秒)
  2. 現在再生時間÷全体再生時間=再生割合を算出(17秒÷30秒=56%)
  3. グラフ項目数×再生割合=移動すべき項目を算出(今回は四捨五入で30個×56%=17)
  4. 対象のグラフ項目トップにカーソル描画(17番目のグラフ項目)

また前回の質問でも回答者様より以下の助言いただきました。

・Barチャートの各要素の高さを取得する方法

・リスト特定のindexの要素のBarの頂点位置にカーソルを描画/更新する方法 または
・特定の要素のツールチップ位置にカーソルを固定表示する方法
が必要になりそうです。

このうち現在再生時間(currentTime)、全体再生時間(duration)、グラフの順番(index)は取得できました。対象グラフ項目の高さもそれらしい(element.height)のを取得できましたが、それ以降の工程、つまり対象グラフ項目のトップにカーソルを描画するロジックが自分の力だけではなかなか思いつきません。皆様のご協力をお待ちしております。

追記:
2021.10.12 videoタグ仕様を見るとseekingイベントというのがあるので、ここでseekingイベントハンドラを記載し、そこでdrawCursor関数と同様の処理を書けばいいのではと考えていますがいかがでしょうか。

ソースコード

JavaScript

1<html> 2 <head > 3 <meta http-equiv="Content-Style-Type" content="text/html"> 4 <title>Chart.js Sample</title> 5 <style type="text/css"> 6 #wrapper { 7 position: relative; 8 padding-bottom: 60%; 9 width: 100%; 10 } 11 12 #video { 13 position: absolute; 14 width: 100%; 15 } 16 17 #chart { 18 z-index: 3; 19 position: absolute; 20 padding-top: 60%; 21 width: 100%; 22 height: 85%; 23 bottom: 100px; 24 } 25 26 #cursor { 27 z-index: 4; 28 position: absolute; 29 width: 20px; 30 height: 20px; 31 border-radius: 50%; 32 background: red; 33 } 34 </style> 35 </head> 36 <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.5.1/chart.min.js"></script> 37 <body> 38 <div id="wrapper"> 39 <video id="video" src="file_example_MP4_1280_10MG.mp4" type="video/mp4" controls></video> 40 <div id="chart"> 41 <canvas id="myChart"></canvas> 42 <script> 43 var data_labels = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 44 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 45 21, 22, 23, 24, 25, 26, 27, 28, 29, 30 ] 46 var data_sets = [ 35, 25, 33, 15, 18, 6, 20, 39, 12, 8, 47 6, 15, 35, 30, 23, 10, 8, 5, 11, 30, 48 35, 39, 40, 41, 32, 20, 12, 9, 3, 1 ] 49 var canvas = document.getElementById( 'myChart' ); 50 var context = canvas.getContext('2d'); 51 var graph = { 52 type: 'bar', 53 data: { 54 labels: data_labels, 55 datasets: [{ 56 label: 'Views', 57 data: data_sets, 58 backgroundColor: [ 59 'rgba(255, 255, 0, 0.2)' 60 ], 61 barPercentage: 1, 62 categoryPercentage: 1, 63 }] 64 }, 65 options: { 66 title: { 67 display: false, 68 }, 69 responsive: true, 70 legend: { 71 display: false, 72 }, 73 scales: { 74 x: { 75 display: false, 76 }, 77 y: { 78 display: false, 79 } 80 }, 81 plugins: { 82 legend: { 83 display: false, 84 }, 85 tooltip: { 86 enabled: false, 87 external: drawCursor, 88 } 89 }, 90 events: ['click'], 91 onClick: (e) => { 92 // クリックした要素の取得 93 var ele = myGraph.getElementsAtEventForMode( 94 e, 'index', { intersect: false }, false); 95 //デバッグ用・何番目の項目かを表示、高さを表示 96 console.log(ele); 97 console.log("Index: " + ele[0].index); 98 console.log("Height: " + ele[0].element.height); 99 100 var eleIndex = ele[0].index; 101 var eleLength = data_sets.length; 102 // 再生位置更新 103 var v = document.getElementById("video"); 104 var duration = v.duration; 105 v.currentTime = duration * eleIndex / eleLength; 106 } 107 } 108 }; 109 var myGraph = new Chart( canvas, graph ); 110 111 function drawCursor(context) { 112 console.log(context); 113 //ツールチップ要素 114 var tooltipEl = document.getElementById('chartjs-tooltip'); 115 116 //div要素を生成 117 if (!tooltipEl) { 118 tooltipEl = document.createElement('div'); 119 tooltipEl.id = 'chartjs-tooltip'; 120 121 var canvas_ = document.createElement("canvas"); 122 canvas_.height = 30; 123 canvas_.width = 30; 124 tooltipEl.appendChild(canvas_); 125 document.body.appendChild(tooltipEl); 126 127 context_ = canvas_.getContext('2d'); 128 context_.beginPath(); 129 context_.arc( 130 10, 131 10, 132 10, 133 0 * Math.PI / 180, 134 360 * Math.PI / 180 135 ); 136 context_.fillStyle = "red"; 137 context_.fill(); 138 canvas_.id = "cursor"; 139 140 } 141 142 //ツールチップがない場合は非表示 143 var tooltipModel = context.tooltip; 144 if (tooltipModel.opacity === 0) { 145 tooltipEl.style.opacity = 0; 146 return; 147 } 148 // グラフ内部にカーソルがなくてもツールチップがX軸方向に動くようにするためには、以下2行の設定が必要。 149 tooltipModel.options.intersect = false; 150 tooltipModel.options.mode = 'x'; 151 152 var position = context.chart.canvas.getBoundingClientRect(); 153 var bodyFont = Chart.helpers.toFont(tooltipModel.options.bodyFont); 154 155 tooltipEl.style.opacity = 1; 156 tooltipEl.style.position = 'absolute'; 157 tooltipEl.style.left = position.left + window.pageXOffset + tooltipModel.caretX -10+ 'px'; 158 tooltipEl.style.top = position.top + window.pageYOffset + tooltipModel.caretY -10+ 'px'; 159 tooltipEl.style.font = bodyFont.string; 160 tooltipEl.style.padding = tooltipModel.padding + 'px ' + tooltipModel.padding + 'px'; 161 tooltipEl.style.pointerEvents = 'none'; 162 163 //再生箇所をDiv:textに表示したい 164 var video_ = document.querySelector('video'); 165 video_.addEventListener("timeupdate", (e) => { 166 var currentTime_ = video_.currentTime; 167 //デバッグ:現在の再生位置を取得 168 console.log("CurrentTime: " + currentTime_); 169 console.log(e); 170 }); 171 video_.addEventListener("seeking", (e) => { 172 var currentTime_ = video_.currentTime; 173 } 174 </script> 175 </div> 176 </div> 177 </body> 178</html>

補足情報

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

色々調べた結果、描画はpluginを使ってafterDrawでフックできるようです。

これより、描画部分は下記のような感じで。

js

1const customimg = new Image(); 2customimg.src = 'カーソル画像のurl'; 3 4let seekpos = 0; // 再生位置を格納するグローバル変数 5 6const QnPlugin = { 7 id: 'qnplugin', 8 afterDraw: (chart) => { 9 if (customimg.complete) { 10 const ctx = chart.ctx; 11 // グラフ要素取得 12 const meta = chart.getDatasetMeta(0) 13 const {top, left, width, height} = chart.chartArea; 14 // Out of index回避 15 const posindex = Math.min( 16 Math.floor(meta.data.length * seekpos), 17 meta.data.length - 1 18 ); 19 // 描画位置設定 20 const x = left + meta.data[posindex].x - customimg.width / 2; 21 const y = top + meta.data[posindex].y - customimg.height / 2; 22 ctx.drawImage(customimg, x, y); 23 } else { 24 // 画像が読み込み完了していないときは読み込まれるまで待つ 25 customimg.onload = () => chart.draw(); 26 } 27 } 28} 29Chart.register(QnPlugin);

※上記はimageを使用しているがcanvasにdrawpathももちろん可能。それくらいはさすがに自分で調べてくださいね

 


動画再生時の処理
timeupdate イベントの発火の度に、
再生地点をグローバル変数seekposに上書き更新して
強制的にグラフを描画させます。

※グラフ再描画が動画再生中に短い間隔で行われることになるため、マシンスペックによっては重くなります。
丁寧にやるなら「再生位置と現状のカーソル位置から、グラフ上のカーソル位置を更新する必要があるか否かを判定し、更新する必要があるときだけdrawを発火させる」ような処理を行った方がよいでしょう。

var video_ = document.getElementById("video"); video_.addEventListener("timeupdate", (e) => { var currentTime_ = video_.currentTime; seekpos = video_.currentTime / video_.duration; myGraph.draw() });

 

** One more thing... **
videoコントロールのseek位置の精度が甘い為、クリック時のカーソルが1つ前にずれる場合が有ります。
なのでクリック時には1秒ほど加えてあげると、良い具合になるかと。

diff

1 events: ['click'], 2 onClick: (e) => { 3 // クリックした要素の取得 4 var ele = myGraph.getElementsAtEventForMode(e, 'index', { 5 intersect: false }, false); 6 //デバッグ用・何番目の項目かを表示、高さを表示 7 console.log(ele); 8 console.log("Index: " + ele[0].index); 9 console.log("Height: " + ele[0].element.height); 10 11 var eleIndex = ele[0].index; 12 var eleLength = data_sets.length; 13 // 再生位置更新 14 var v = document.getElementById("video"); 15 var duration = v.duration; 16- v.currentTime = duration * eleIndex / eleLength; 17+ v.currentTime = duration * eleIndex / eleLength + 1; 18 }

以上

投稿2021/10/13 13:06

編集2021/10/13 13:09
退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2021/10/15 10:10

回答いただきありがとうございました。 今回はimageを使いましたが、canvasにdrawpath、またはカーソルを動かす方法も、時間が取れた時に改めてしっかりと考えたいと思います。 可能であればこちらからも何かお礼させていただきたいです、これまでのご回答大変感謝いたします、ありがとうございます!
退会済みユーザー

退会済みユーザー

2022/03/07 08:33

上記回答に追加で質問させていただいてもよろしいでしょうか。 こちらのコードを少々変更し、グラフ幅がカーソルより細い場合はカーソルを縮小表示させる処理を追加しました。 しかし上記コードに変えると、縮小されたカーソルの表示がグラフ項目のトップからずれてしまいます。 コードを書いてみたのですが、何が原因なのかアドバイスをいただけないでしょうか。よろしくお願いいたします。
退会済みユーザー

退会済みユーザー

2022/03/07 08:34

// グラフ要素取得 var meta = chart.getDatasetMeta(0); var data = meta.data; var cursor_shift = 0; var { top, left, width, height } = chart.chartArea; var posindex = Math.min( Math.floor(meta.data.length * percent_view), meta.data.length - 1 ); //canvas.drawImage(cursor_image, left, top); //left = 0; //top = 0; var cursorWidth = meta.data[posindex].width * 1.5; if (cursorWidth < 30) { cursor_shift = cursorWidth; // 描画位置設定 var x = left + meta.data[posindex].x - (cursor_image.width) / 2/* + (cursorWidth / 2)*/; var y = top + meta.data[posindex].y - (cursor_image.height / 2)/* + (cursorWidth / 2)*/; canvas.drawImage(cursor_image, x, y); /*canvas.drawImage( cursor_image, x, y, cursorWidth + 5, cursorWidth);*/ } else { var x = left + meta.data[posindex].x - (cursor_image.width) / 2; var y = top + meta.data[posindex].y - (cursor_image.height / 2) ; console.log("x: " + x); console.log("y: " + y); canvas.drawImage(cursor_image, x, y); };
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問