MENU

JavaScriptCanvas画像処理

【JavaScript】 Canvasの原点を左下にしてグラフを描画

更新日:2021/08/31

 

JavaScriptのCanvasAPIを使用すると、ブラウザに図形を描画できます。

 

しかし左下を原点としてグラフを描画しようとすると、少し手間取ることがあります。

 

どうすればいいのでしょうか。

 

 

Canvasについては次の記事を参考にしてください。
【JavaScript】 Canvasの使い方まとめ

Canvasの座標系とグラフ

 

Canvasは左上を原点として、右側に向かってx値が増加します。
問題はy値です。
こちらは、下側に向かって増加します。

 

canvasの座標系

 

一方、一般的なグラフは上方向に向かってy値が増加します。

 

一般的なグラフの座標系

 

外部から受け取ったデータでグラフを描画するケースが考えられますが、素直に描画してしまうと次のように反転することになります。

 

素直に描画するとグラフが反転する

 

そのため、位置を補正しながら描画する必要があります。

 

描画位置の補正の概念

 

左下を原点として、上方向に向かってy値を増加させるのは、それほど難しくありません。

 

次のようなケースを考えてみます。

 

原点を( 10 , 100 )に移動

 

  1. 原点を( 10 , 100 )に移動
  2. 上方向に向かってY値を増加

 

x値は、描画時に10を加算するだけです。
つまり、次のような式になります。

 

x' = x + 10

 

y値は、描画時に100を減算して絶対値を求めます。
つまり、次のような式になります。

 

y' = Math.abs( y - 100 )

 

例えばy値が10のときは、計算結果が90です。
y値が20のときは、計算結果80です。
合ってます。

 

問題はy値にマイナスを指定したときです。
y値が-10のときは、計算結果が110です。
y値が-20のときは、計算結果が120です。

 

これもあってますね。

 

これをもとに、直線を描画するコードを作成してみます。

 


const canvas =document.createElement("canvas");
const {width,height} = canvas;
const context = canvas.getContext("2d"); 

const originX = 10;
const originY = 100;

const transPoint = ( x , y ) => [ x + originX , Math.abs( y - originY ) ];
context.fillStyle = "black";
context.beginPath();
context.moveTo( ...transPoint(originX * -1 , 0) );
context.lineTo( ...transPoint(width - originX  , 0 ));
context.stroke();

 

moveToとlineToの引数で使っている「...」は、スプレッド構文です。
transPointから受け取った要素を二つ持つ配列を、2つの引数に展開しています。

 

スプレッド構文については次の記事を読んでみてください。
【JavaScript】 コード中の「...」は意味があった スプレッド/レスト構文

もっとも簡単な補正方法

 

上で紹介した方法は、座標指定のたびに関数を呼び出す必要があり、かなりの手間となります。

 

実は次の二つのメソッドを実行するだけで、以降はグラフ座標での指定が可能になります。

 

context.translate( 原点X , 原点Y );
context.scale(1, -1);

 

原点X と原点Yは、グラフの原点としたいキャンバス座標です。

 

この二つのメソッドは、座標変換方法を登録します。
その後座標が指定されると、登録された情報をもとに、座標が変換されます。

 

translateは指定した値を、座標値に加算します。
つまり( 0 , 0 )は、( 原点X , 原点Y )に変換されます。

 

scaleは拡大率を指定するメソッドですが、-1を指定するとy軸を中心として反転してくれます。
1は、何もしないという意味です。

 

つまり( 100 , 100 )は、( 100 + 原点X , 100 + 原点Y )に移動した後、原点Yを起点とした水平軸を中心に反転します。

 

これで意図した位置に、グラフを描画できます。

グラフ作成例

 

簡単な折れ線グラフと棒グラフを描画してみます。

 

下のコードを実行すると、次のようなグラフが描画されます。

 

グラフ作成例

 

 

 

HTML

 


<canvas id="canvas" width="300" height="300"></canvas>

 

JavaScript

 


"use strict";
window.addEventListener( "DOMContentLoaded" , ()=> {

    const line = (context,sx,sy,ex,ey,width,color) => {
        context.save();
        context.beginPath();
        context.lineWidth = width;
        context.strokeStyle = color;
        context.moveTo( sx,sy );
        context.lineTo( ex,ey );
        context.stroke();
        context.restore();
    };
    const circle = (context,cx,cy,radius,lineColor,fillColor) => {
        context.beginPath();
        context.save();
        context.strokeStyle = lineColor;
        context.fillStyle = fillColor;
        context.arc( cx,cy, radius, 0, 2 * Math.PI ,false);
        context.fill();
        context.stroke();
        context.restore();
    };
    const rect = (context,sx,sy,ex,ey,color) => {

        context.save();
        context.fillStyle = color;
        context.fillRect(
            Math.min( sx , ex ) , Math.min( sy , ey ),
            Math.abs( sx - ex ) , Math.abs( sy - ey ));
        context.restore();
    };

    const initialCanvas = id => {

        const originX = 10; // 原点位置(Canvas左下
        const originY = 10; // からの距離)

        const canvas =document.getElementById(id);
        const {width,height} = canvas;

        const context = canvas.getContext("2d");

        context.translate( originX, height - originY );
        context.scale(1, -1);
             // x,y軸を描画
        line( context , originX * -1 , 0 , width - originX  , 0 , 1 , "black" );
        line( context , 0 , originY * -1 , 0 , height - originY  ,  1 , "black" );

        return context;
    };

    const drawGraph = (  context , graphData ) =>{
        const graphStep = 40;
        const rectWidth = 20 / 2;

        // 棒グラフを描画
        graphData.forEach( (posY,index) => {
            const posX = ( index + 1 ) * graphStep;

            rect( context , posX - rectWidth , posY , posX + rectWidth , 0 , "pink" );
        })

        // 折れ線グラフを描画
        let start = [  graphStep  , graphData[ 0 ] ];

        for( let i = 1 ; i < graphData.length ; i ++ ){
            const end = [ ( i + 1 ) * graphStep , graphData[ i ] ];
            line( context , ...start , ...end , 3 , "blue" );
            circle( context,...start,5,"blue","white");
            if( i === graphData.length -1 )
                circle( context,...end,5,"blue","white");
            start = end;
        }

    };

    const context = initialCanvas( "canvas" );

    drawGraph( context , [ 120 , 170 , 130 , 125 , 190 , 155 ] );

});

グラフ座標でのキャンバスのクリア

 

キャンバスの描画内容をクリアするとき、clearRectメソッドを使用します。

 

 

clearRectの形式

 

コンテキスト.clearRect( 左上x座標, 左上y座標 , クリアする幅 , クリアする高さ );

 

 

通常なら、左上を( 0, 0 )として、キャンバスの幅と高さを与えるだけです。

 

しかしこのメソッドも、変換後の座標が適用されます。

 

そのためキャンバス全体をクリアするには、変換後の左上座標を計算する必要があります。

 

次のようなコードで、座標系を変更したときのケースを考えてみます。

 

context.translate( x , y );
context.scale( 1, -1 );

 

このとき、次のようなコードでキャンバス全体をクリアできます。

 

const [ width , height ] = キャンバス;
const left = -1 * x;
const top = y;
const clearWidth = width;
const clearHeight = -1 * height;

 

コンテキスト.clearRect( left, top , clearWidth , clearHeight );

 

もし、複雑な変換をおこなっている場合は、次のように座標変換をリセットしてから、クリアするのが手っ取り早いです。

 

コンテキスト.setTransform( 1,0,0,1,0,0 );
コンテキスト.clearRect( 左上x座標, 左上y座標 , クリアする幅 , クリアする高さ );

 

この後、もう一度、座標変換のコードを実行してください。

 

けーちゃんおススメJavaScript入門書

  • スラスラ読める JavaScript ふりがなプログラミング
  • プログラム未経験者がJavaScript始めるならコレ!
    コードを掲載して自分で理解しろという投げっぱなしな入門書とは異なり、コードに一つ一つどんなことをやっているかをふりがなという形式で解説しています。
    それでいてJavaScriptの基礎と応用を学べる良書です。
  • これからWebをはじめる人のHTML&CSS、JavaScriptのきほんのきほん
  • JavaScriptの機能を実践で活かすにはHTMLやCSSの知識が不可欠です。
    しかしそれらの知識があることが前提として書かれている書籍が多い中、この本は総合的な知識を身に着けることができます。
    HTMLやCSSの知識も不安な方には、ぴったりの一冊です
  •  

    入門書の役割は、自分のやりたいことをネットで調べることができるようになるための、基礎的な知識の獲得です。
    まずはこれらの本でしっかりと基礎知識を身につけましょう。
    そしてもっと高度なことや専門的なことはネットで調べ、情報が足りないと感じたら書籍を購入してください。


    記事の内容について

     

    こんにちはけーちゃんです。
    説明するのって難しいですね。


    「なんか言ってることおかしくない?」
    たぶん、こんなご意見あると思います。

    裏付けを取りながら記事を作成していますが、僕の勘違いだったり、そもそも情報源の内容が間違えていたりで、正確でないことが多いと思います。

    そんなときは、ご意見もらえたら嬉しいです。

    ご意見はこちら。
    https://note.affi-sapo-sv.com/info.php

    【お願い】

    お願い

    ■このページのURL


    ■このページのタイトル


    ■リンクタグ


    ※リンクして頂いた方でご希望者には貴サイトの紹介記事を作成してリンクを設置します。
    サイト上部の問い合わせよりご連絡ください。
    ただしサイトのジャンルによっては、お断りさせていただくことがあります。