0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

学習補助Webアプリ「Base」④(後編):Chart.jsを使わずに成績グラフを実装する - 棒グラフ【高校生の開発メモ006】

0
Posted at

紹介する作品は実際に公開しています。覗いて頂けると嬉しいです|ω`)zZ

「Base」紹介HP → https://ss719917.stars.ne.jp/HP/index.html

「Base」試用版 → https://ss719917.stars.ne.jp/

探究活動で開発したWebアプリ「Base」を題材に,設計や開発時の工夫を全5回で紹介します。

実装の意図

はじめに

棒グラフを表示する方法は,

  • Chart.js
  • Charts.css
  • SVG
  • Canvas

など様々あります。

今回はそれらを使わず,HTMLのdivだけで実装しました。

理由は,

  1. 点数と平均点を横に並べたい
  2. データが存在しない場合は×を表示したい
  3. 後から自由にレイアウトを変更したい

という要件があったためです。

JavaScriptでグラフを生成する

私が実際に実装した値を基準としておりますので,適宜調整してご利用下さい。

①まず表示領域を取得し,一度中身を空にします。

script.js
let graph=document.getElementById("graph");
graph.innerHTML="";

②補助線を追加します。

for(let i=1;i<10;i++){
    let line=MakeDiv();
    line.classList.add("line","line-B");
    graph.append(line);
}

③教科ごとに色を設定

前回紹介した関数を使って,チェックされた教科へ色を設定しています。

RecSetColor(subject.length,element,index);

色はCSS変数として保持されるため、

棒グラフ側では,

background:var(--main);

だけで利用できます。

④divとしての棒グラフ生成

点数データが存在する場合は,

let bar=MakeDiv();
bar.classList.add("graph-element");

height=score*0.4

として棒を生成しています。

座標は

SetStyle(/*要素,上位置,左位置,幅,高さ*/);

という共通関数を用意し,簡潔に指定しています。

⑤データが無い場合の×描画

前回ご紹介した通りのものを,教科数に合わせた横幅で描画しています。

let width=(75.1/(学期数×テスト数))-3;

実際の画像とコード全文

Screenshot 2026-07-29 10.42.53.png

script.js
function RecHome(){//成績管理画面の表示
  window.scrollTo(0,0);
  let a=ClearMain();
  SetHead()
  let b=MakeDiv("graph");
  let d=MakeTable("list");
  let e=MakeDiv("input")
  a.append(b,d,e);
  let f=[];
  let g=0;
  for(let h=0; h<GroupName[UserInformation1].length; h++){
    let d=0;
    for(let j=0; j<GroupTeacher[UserInformation1][h].length; j++){
      if(UserGroup[g]==1){
        d++
      }
      g++
    }
    if(0<d){
      f.push(GroupName[UserInformation1][h]);
    }
  }
  RecList(f);
  RecGraph(f);
  let k=document.getElementById("margin");
  k=SetStyle(k,110+8*f.length);
  Display[UserCode]=[2,0];
}
function RecSetColor(a,b,c){//総数・要素・番号を引数にとり、色を付けて返す
  let d=255;
  let e=80;
  let f=115;
  for(let g=0; g<(140*3/a)*c; g++){
    if(g<140){
      d--
      e++
    }
    else if(g<280){
      e--
      f++
    }
    else{
      d++
      f--
    }
  }
  b.style.setProperty('--main',`rgb(${d},${e},${f})`);
}
function RecGraph(a){//グラフを表示
  let b=document.getElementById("graph");
  b.innerHTML="";
  b.classList.add('graph-A','card');
  b=SetStyle(b,28,8,82.5,46.5);
  for(let c=1; c<10; c++){
    let d=MakeDiv();
    d.classList.add('line','line-B');
    d=SetStyle(d,4*c,1.2,80.5);
    b.append(d);
  }
  let e=[...document.querySelectorAll('input[name="score"]:checked')];
  e.forEach(d=>{
    RecSetColor(a.length,d,Number(d.value));
  });
  let a1=(75.1/(RStyleSemester.length*RStyleTest.length))-3;
  if(a1>0){
    if(e.length<a1){
      let f=e.map(a=>Number(a.value));
      let g=(a1/f.length)-1;
      let h=0;
      for(let i=0; i<RStyleSemester.length; i++){
        for(let j=0; j<RStyleTest.length; j++){
          let k=MakeSpan("",RStyleSemester[i]+RStyleTest[j]);
          k=SetStyle(k,41,(a1*15/31)+(a1+3)*h);
          k.classList.add('graph-A-text-1');
          b.append(k);
          for(let l=0; l<f.length; l++){
            if(f[l]==0){//総合部分を生成
              let m=MakeDiv();
              let n=MakeDiv();
              let o=0;
              let p=0;
              for(let q=0; q<a.length; q++){
                if(RListScore[UserCode][i][j][q]){
                  o+=RListScore[UserCode][i][j][q];
                }
                if(RListScoreAverage[UserInformation1][i][j][GroupName[UserInformation1].indexOf(a[q])][UserInformation2]){
                  p+=RListScoreAverage[UserInformation1][i][j][GroupName[UserInformation1].indexOf(a[q])][UserInformation2];
                }
              }
              if(o>0){
                m.classList.add('graph-element');
                m=SetStyle(m,40-o*0.4/a.length,6.2+(a1+3)*h+l*(g+1),g/2,o*0.4/a.length);
                RecSetColor(a.length,m,f[l]);
              }
              else{
                m.classList.add('graph-text');
                m.style.setProperty('--size',`${g/3}vw`);
                m=SetStyle(m,40-g/3,6.2+(a1+3)*h+l*(g+1)+g/12);
              }
              if(p>0){
                n.classList.add('graph-element');
                n=SetStyle(n,40-p*0.4/a.length,6.2+(a1+3)*h+l*(g+1)+g/2,g/2,p*0.4/a.length);
                RecSetColor(a.length,n,f[l]);
                n.style.opacity=0.7;
              }
              else{
                n.classList.add('graph-text');
                n.style.setProperty('--size',`${g/3}vw`);
                n=SetStyle(n,40-g/3,6.2+(a1+3)*h+l*(g+1)+g/12+g/2);
              }
              b.append(m,n);
            }
            else{//その他選択した教科部分を生成
              if(RListScore[UserCode][i][j][f[l]-1]&&typeof RListScore[UserCode][i][j][f[l]-1]==="number"){
                let r=MakeDiv();
                r.classList.add('graph-element')
                r=SetStyle(r,40-RListScore[UserCode][i][j][f[l]-1]*0.4,6.2+(a1+3)*h+l*(g+1),g/2,RListScore[UserCode][i][j][f[l]-1]*0.4);
                RecSetColor(a.length,r,f[l]);
                b.append(r);
              }
              else{
                let s=MakeDiv("");
                s.classList.add('graph-text');
                s.style.setProperty('--size',`${g/3}vw`);
                s=SetStyle(s,40-g/3,6.2+(a1+3)*h+l*(g+1)+g/12);
                b.append(s);
              }
              if(RListScoreAverage[UserInformation1][i][j][GroupName[UserInformation1].indexOf(a[f[l]-1])][UserInformation2]&&typeof RListScoreAverage[UserInformation1][i][j][GroupName[UserInformation1].indexOf(a[f[l]-1])][UserInformation2]==="number"){
                let t=MakeDiv();
                t.classList.add('graph-element')
                t=SetStyle(t,40-RListScoreAverage[UserInformation1][i][j][GroupName[UserInformation1].indexOf(a[f[l]-1])][UserInformation2]*0.4,6.2+(a1+3)*h+l*(g+1)+g/2,g/2,RListScoreAverage[UserInformation1][i][j][GroupName[UserInformation1].indexOf(a[f[l]-1])][UserInformation2]*0.4);
                RecSetColor(a.length,t,f[l]);
                t.style.opacity=0.7;
                b.append(t);
              }
              else{
                let u=MakeDiv("");
                u.classList.add('graph-text');
                u.style.setProperty('--size',`${g/3}vw`);
                u=SetStyle(u,40-g/3,6.2+(a1+3)*h+l*(g+1)+g/12+g/2);
                b.append(u);
              }
            }
          }
          h++
        }
      }
      let v=MakeDiv();
      v.classList.add('line','line-A');
      v=SetStyle(v,40,0.7,81.5);
      b.append(v);
    }
    else{
      let w=MakeSpan("","教科数が多く表示できません");
      w.classList.add("text-small2");
      w=SetStyle(w,1,1.5);
      b.append(w);
    }
  }
  else{
    let w=MakeSpan("","テストが多く表示できません");
    w.classList.add("text-small2");
    w=SetStyle(w,1,1.5);
    b.append(w);
  }
}
function RecList(a){//点数をtable化して表示
  let b=document.getElementById("list");
  b.innerHTML="";
  b.classList.add('table');
  b=SetStyle(b,80,8,82.9);
  let c=MakeTr();
  let d=MakeTd();
  d.classList.add('table-2');
  d=SetStyle(d,0,0,0,28,4);
  d.colSpan=2;
  c.append(d);
  let ae=0;
  for(let e=0; e<RStyleSemester.length; e++){//回ごとのテスト名を生成
    for(let f=0; f<RStyleTest.length; f++){
      let ab=MakeTd(RStyleSemester[e]+RStyleTest[f]);
      ab.classList.add('table-2');
      ab=SetStyle(ab,0,28+13.75*ae,13.75,3.9);
      c.append(ab);
      ae++
    }
  }
  b.append(c);
  for(let g=0; g<a.length+1; g++){//checkboxなどの要素を生成
    let h=MakeTr();
    let i=MakeTr();
    let j=MakeTd();
    j=SetStyle(j,4+8*g,0,8,8);
    j.classList.add('table-2');
    j.rowSpan=2;
    let k=MakeInput("checkbox","","",g,"score");
    k.addEventListener("change",()=>RecGraph(a));
    k.classList.add('input','input-checkbox');
    k=SetStyle(k,-0.25,-0.5);
    if(g==0){
      k.checked=true;
    }
    j.append(k)
    let l;
    h.append(j);
    if(g==0){//総合部分を生成
      l=MakeTd("総合");
      l=SetStyle(l,4,8,20,8);
      l.classList.add('table-2');
      h.append(l);
      let ac=0;
      for(let m=0; m<RStyleSemester.length; m++){
        for(let n=0; n<RStyleTest.length; n++){
          let o=0;
          let p=0;
          for(let q=0; q<a.length; q++){
            if(RListScore[UserCode][m][n][q]){
              o+=RListScore[UserCode][m][n][q];
            }
            if(RListScoreAverage[UserInformation1][m][n][GroupName[UserInformation1].indexOf(a[q])][UserInformation2]){
              p+=RListScoreAverage[UserInformation1][m][n][GroupName[UserInformation1].indexOf(a[q])][UserInformation2];
            }
          }
          if(o>0){
            let r=MakeTd(o);
            r=SetStyle(r,4,28+13.75*ac,13.75,4);
            r.classList.add('table-2');
            h.append(r);
          }
          else{
            let s=MakeTd("×");
            s=SetStyle(s,4,28+13.75*ac,13.75,4);
            s.classList.add('table-2');
            h.append(s);
          }
          if(p>0){
            let t=MakeTd(p);
            t=SetStyle(t,8,28+13.75*ac,13.75,4);
            t.classList.add('table-2');
            i.append(t);
          }
          else{
            let u=MakeTd("×");
            u=SetStyle(u,8,28+13.75*ac,13.75,4);
            u.classList.add('table-2');
            i.append(u);
          }
          ac++
        }
      }
    }
    else{//その他選択した教科部分を生成
      l=MakeTd(a[g-1]);
      l=SetStyle(l,4+8*g,8,20,8);
      l.classList.add('table-2');
      h.append(l);
      let ad=0;
      for(let v=0; v<RStyleSemester.length; v++){
        for(let w=0; w<RStyleTest.length; w++){
          if(RListScore[UserCode][v][w][g-1]){
            let x=MakeTd(RListScore[UserCode][v][w][g-1]);
            x=SetStyle(x,4+8*g,28+ad*13.75,13.75,4);
            x.classList.add('table-2');
            h.append(x);
          }
          else{
            let y=MakeTd("×");
            y=SetStyle(y,4+8*g,28+ad*13.75,13.75,4);
            y.classList.add('table-2');
            h.append(y);
          }
          if(RListScoreAverage[UserInformation1][v][w][GroupName[UserInformation1].indexOf(a[g-1])][UserInformation2]){
            let z=MakeTd(RListScoreAverage[UserInformation1][v][w][GroupName[UserInformation1].indexOf(a[g-1])][UserInformation2]);
            z=SetStyle(z,8+8*g,28+ad*13.75,13.75,4);
            z.classList.add('table-2');
            i.append(z);
          }
          else{
            let aa=MakeTd("×");
            aa=SetStyle(aa,8+8*g,28+ad*13.75,13.75,4);
            aa.classList.add('table-2');
            i.append(aa);
          }
          ad++
        }
      }
    }
    l.rowSpan=2;
    b.append(h,i);
  }
  let f=document.getElementById("input");
  let g=MakeButton(()=>RecInput(a),"編集");
  g.classList.add('button','button-E');
  g=SetStyle(g,96+a.length*8,8,28);
  f.append(g);
  let h=document.getElementById("margin");
  h=SetStyle(h,106+a.length*8);
}

おわりに

ライブラリを利用すれば短時間で実装できる機能もありますが,要件に合わせて自分で実装したことで

  • 自由なレイアウト
  • 教科数への柔軟な対応
  • データが存在しない場合の表現

など,必要な機能を無理なく実現することができました。同じ機能でも,環境や要件が変われば実装方法も変わります。この記事が,何かを自作するときのアイデアや選択肢の一つになれば嬉しいです。

次回は,Baseシリーズ最終回としてアプリ名やUIなど,プログラム以外の場所でのこだわりを紹介します。

「Base」紹介HP(再掲) → https://ss719917.stars.ne.jp/HP/index.html
「Base」試用版(再掲) → https://ss719917.stars.ne.jp/


前回 → ④(中編):Chart.jsを使わずに成績グラフを実装する - ×の描画

今回 → ④(後編):Chart.jsを使わずに成績グラフを実装する - divで棒グラフを作る

次回 → ⑤:命名の由来とUIデザインについて
投稿次第,掲載します。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?