シェイプ(形状)

概要

Curl® 言語の Shapes パッケージは、ダイアグラム、チャート、グラフ、その他の 2D グラフィックを容易に作成できるように設計されています。このパッケージでは、オブジェクトをコンテナ内に配置するレイアウト方法を採用しており、これはグラフやダイアグラムの作成において、GUI ツールキットのグラフィカル レイアウトを使用するよりも優れています。
Graphic オブジェクトをグラフィカル コンテナに配置すると、モニタやプリンタなどの表示デバイスに実際に表示される外観は、Curl レイアウト システムによって制御されます。このシステムは、オブジェクトに対して指定されたユーザー設定サイズや使用可能な表示領域を考慮して、指定されたすべてのコンテンツを最大限コンテナに収めます。Curl レイアウト システムによるオブジェクトの配置方法の詳細については、「エラスティックとページ レイアウト」を参照してください。
この方法は、レイアウトを予期しない表示条件に合わせる必要がある場合 (たとえば、GUI のコンポーネントをブラウザ ウィンドウに収めるなど) は、非常に役立ちます。ただし、オブジェクトのサイズや配置を精密に制御する場合 (たとえば、データ ポイントを折れ線グラフ上に配置するなど) は、この方法では効果はあまり得られません。
Shapes パッケージを使用すると、2D レンダリングの複雑な処理や面倒なプログラミングを行わずに、グラフィックを精密に制御できます。シェイプは GUI ツールキットのイベント処理もサポートします。つまり、アニメーションを使用してマウス イベントに応答するダイアグラムも作成できます。
次の表に、Graphic オブジェクトと Shape オブジェクトの主な相違点を一覧表示します。
Graphic オブジェクトShape
オブジェクトのサイズ設定を指定します。オブジェクトは、伸縮させてレイアウトに収めることができます。
オブジェクトのサイズを正確に指定します。オブジェクトは、レイアウトに収まらない場合はクリップされます。
レイアウト ネゴシエーションを行ってオブジェクトを配置します。
変換を適用してオブジェクトを配置します。
Graphic.horiginGraphic.vorigin についてオブジェクトの外側起点を移動します。
オブジェクトの寸法について、オブジェクトの起点を移動することはできません。
回転およびスケーリングはできません。
回転およびスケーリングをサポートします。
Curl 言語には Charts パッケージも用意されています。これは Shapes パッケージに組み込まれており、RecordSet 内のデータのチャートおよびグラフを作成するための特別な機能を提供します。

シェイプの使用

Shapes パッケージは、シェイプコンテナ内でシェイプを編成します。すべてのシェイプの基本クラスは Shape で、Shape オブジェクトのコンテナの基本クラスは ShapeContainerBase です。ShapeShapeContainerBase を継承します。つまり、Shape は他の Shape を格納することもできます。
このパッケージには、四角形や楕円形など数多くの基本シェイプが用意されています。また、シェイプ間にさまざまな特別な線を描画するための矢印シェイプも備わっています。PathShape クラスと RegionShape クラスを使用すると、カスタム グラフィック Shape を作成できます。Shape のサブクラスを作成して、動作や外観をカスタマイズすることもできます。
Shape を作成するときは、その寸法を指定します。寸法を GRect として指定する場合もあります。また、線の両端を指定したり、複雑なポリゴンを含む Region を指定したりする場合もあります。どのような形式でシェイプの寸法を記述する場合でも、寸法はシェイプの起点から相対的に解釈されます。
たとえば、GRectRectangleShape の寸法、および独自の起点からのシェイプのオフセットを記述するとします。次に、Shape の変換を変更することにより、Shape をその起点も含め目的の位置に移動できます。transformation を変更するには、引数を Shape のコンストラクタに指定するか、Shape クラスにより提供されているいずれかのメソッドを使用します。

グラフィック階層への Shape の追加

シェイプを表示可能にするためには、Graphic オブジェクト内に配置する必要があります。具体的には、ShapeBoxCanvas の両方とも Shape のグラフィック コンテナに適しています。この 2 つのオブジェクトは ShapeRoot を継承します。ShapeRootShapeContainerBase を継承し、グラフィック階層内のシェイプオブジェクトにアンカーを設定できるグラフィック オブジェクトの抽象基本クラスです。CanvasShapeRoot は、それ自体はシェイプオブジェクトではなく、Shape を継承しないことに注意してください。
Canvas には、非常に簡単なレイアウトがあります。Canvas に対しサイズを指定する必要があります。このサイズはコンテンツのサイズに応じて変わりません。また、コンテンツのサイズも変わりません。コンテンツの配置場所とサイズは、常に指定された場所とサイズです。
ShapeBox は、コンテンツに必要なサイズを使用して GUI レイアウト用の独自のサイズ設定を決定し、レイアウト時に ShapeBox に割り当てられるサイズをコンテンツに通知します。グラフの場合、Chart はこの通知を受けて、割り当てられたサイズを使用します。これにより GUI に似たレイアウトが生成され、他のシェイプは無視されます。
ShapeBox は明示的に作成できますが、多くの場合その必要はありません。グラフィック階層にシェイプを挿入しようとすると、シェイプは自動的に ShapeBox にラップされます。

例: ShapeBox に自動的にラップされる RectangleShape
{import * from CURL.GUI.SHAPES}

{value 
    {RectangleShape
        {GRect 3.5cm, 3.5cm, 2.5cm, 2.5cm},
        color = "wheat"
    }
}

ShapeBox はレイアウト境界の概念を使用して対話的にコンテンツを扱います。レイアウト境界は Shape.get-own-bounds から返され、通常の境界とは異なります。つまり絶対的な制約ではありません。Shape.get-own-bounds は、Shape によって描画されるすべての領域を含む四角形を返す必要があります。多くの場合、この四角形は Shape の指定領域よりも多少大きくなります。一方で、レイアウト境界は Shape の指定寸法を反映することを目的としており、すべての描画領域を格納する必要はありません。ShapeBox は境界の代わりにレイアウト境界を使用してサイズを決定します。境界を使用すると Shape の境界周辺に余分な領域が取られるためです。
ShapeBox は、Shape.constrain-own-layout-bounds を呼び出す Shape.constrain-shape-layout-bounds を使用して、使用可能な領域をコンテンツに通知します。これを有効に実装する標準の Shape クラスは Chart だけです。Chart は、そのサイズが事前に直接指定されていない限り、制約に合わせて自身のサイズを変更します。つまりグラフィックと同じようにして、GUI ツールキットレイアウトのコンテキスト内で伸縮できます。ユーザーは、コンテナに合わせて伸縮の寸法を指定するだけです。Shape サブクラスも伸縮可能にできます。このサブクラスは、Shape.get-own-layout-boundsShape.constrain-own-layout-bounds だけ実装する必要があります。

GRect の処理

GRect オブジェクトについて理解することは、シェイプを処理する場合非常に重要です。RectangleShapeEllipseShape などの一部のシェイプは、コンストラクタの引数として GRect を使用します。すべてのシェイプは GRectget-own-bounds から返します。GRect は、起点からの範囲を指定する 4 つの距離で定義されます。
GRect のこれらのプロパティがオブジェクトの描画方法をどのように定義するかについて理解する必要があります。次のように指定された GRect について考えてみましょう。
{GRect 3.5cm, 3.5cm, 2.5cm, 2.5cm}
起点はその中心です。次の図は、これらの距離と起点とがどのように関連しているかを示します。
Figure: GRect のプロパティ
GRect コンストラクタに指定されているすべての値が正であるのに対し、結果として得られるオブジェクトの 2D 領域の一部のポイントが X 座標または Y 座標に対して負の値になることに注意してください。次の例は、この GRect に基づいて Canvas に配置される RectangleShape を示します。

例: それ自体の中心を起点とする GRect
{import * from CURL.GUI.SHAPES}

{value 
    {Canvas width = 8cm, height = 6cm,  
        {RectangleShape
            {GRect 3.5cm, 3.5cm, 2.5cm, 2.5cm},
            color = "wheat",
||--        translation = {Distance2d 3.5cm, 2.5cm},
            border-color = "black",
            border-width = 1px
        }
    }
}

Canvas の起点は、それ自体の左上隅です。GRect の起点は、それ自体の中心です。この結果、RectangleShape の右下の象限だけが表示されます。次の行のコメントを解除して、この例を実行します。
translation = {Distance2d 3.5cm, 2.5cm}
translation により四角形の起点が Canvas の起点から右下に移動されたため、四角形が完全に表示されるようになります。
この RectangleShape に基づいた GRect の場合を考えてみましょう。
{GRect 0cm, 7cm, 0cm, 5cm}
次の例で示すように、GRect 全体が右下、それ自体の起点より下に描画されるため、四角形は完全に表示されます。

例: 左上隅を起点とする GRect
{import * from CURL.GUI.SHAPES}

{value 
    {Canvas width = 8cm, height = 6cm,  
        {RectangleShape
            {GRect 0cm, 7cm, 0cm, 5cm},
            color = "wheat",
            border-color = "black",
            border-width = 1px
        }
    }
}

最後に、次の例について考えてみましょう。GRect 全外が左下、その起点より上に描画されるため、RectangleShape は全く表示されません。translation 指定するコード行により、この四角形が Canvas に移動されます。この行のコメントを解除して、この例を実行します。四角形全体が表示されるようになります。

例: 右下隅を起点とする GRect
{import * from CURL.GUI.SHAPES}

{value 
    {Canvas width = 8cm, height = 6cm,  
        {RectangleShape
            {GRect 7cm, 0cm, 5cm, 0cm},
            color = "wheat",
||--        translation = {Distance2d 7cm, 5cm},
            border-color = "black",
            border-width = 1px
        }
    }
}

簡単なダイアグラムの作成

次の例は、シェイプを使用して作成された簡単なダイアグラムを示します。ここでは、2 つの四角形が矢印で結合されています。コンストラクタで指定されている GRect は、各四角形の寸法を設定します。コンストラクタの translation 引数は、各四角形を Canvas 内に配置します。
矢印の始点と終点のどちらも、任意の矢印スタイルを使用できます。この場合は、両方とも ArrowStyle.solid で、結果は両方向の矢印になります。

例: シェイプを使用した簡単なダイアグラム
{import * from CURL.GUI.SHAPES}
{value
    let rect-left:RectangleShape = 
        {RectangleShape
            {GRect 0cm, 1cm, 0cm, 1cm},
            color = FillPattern.wheat,
            translation = {Distance2d .5cm, .5cm}
        }
    let rect-right:RectangleShape = 
        {RectangleShape
            {GRect 0cm, 1cm, 0cm, 1cm},
            color = FillPattern.wheat,
            translation = {Distance2d 3.5cm, .5cm}
        }
    let arr:ArrowShape = 
        {ArrowShape
            {Distance2d 1.5cm, 1cm},
            {Distance2d 3.5cm, 1cm},
            arrow-body-width = 1px,
            arrow-head-width = 11px,
            arrow-tail-width = 11px,
            arrow-head-style = ArrowStyle.solid,
            arrow-tail-style = ArrowStyle.solid
        }
    {Canvas
        height = 2cm,
        rect-left,
        arr,
        rect-right
    }
}
次の例は上記の例に似ていますが、シェイプの機能が一部追加されています。コンストラクタに引数を指定して楕円形を配置する代わりに、この例では Shape.apply-translation メソッドを使用します。
Canvas クラスは AntialiasMixin を継承するため、オブジェクトを画面上の Canvas にレンダリングするときに Curl ランタイムがアンチエイリアスを使用できることにも注意してください。Canvas ではなく ShapeBox に格納される Shape の場合は、グラフィック階層の場合と同様 AntialiasedFrame を使用してください。アンチエイリアスはパフォーマンスを低下させるため、AntialiasMixin.factor プロパティを使用して、Canvas でアンチエイリアスを使用するかどうか、およびオーバーサンプリングの程度を制御する必要があります。この例ではアンチエイリアスを使用して、曲線のシェイプが画面上で滑らかに表示されるようにします。
この例では矢印の両端で ArrowStyle.none を使用しているため、矢印は線のように見えます。

例: 楕円形を使用した簡単なダイアグラム
{import * from CURL.GUI.SHAPES}

{let ellipse-left:EllipseShape = 
    {EllipseShape
        {GRect 0cm, 1cm, 0cm, 1cm},
        color = FillPattern.wheat,
        border-color = FillPattern.maroon,
        border-width = 1px
    }
}
{let ellipse-right:EllipseShape = 
    {EllipseShape
        {GRect 0cm, 1cm, 0cm, 2cm},
        color = FillPattern.wheat,
        border-color = FillPattern.maroon,
        border-width = 1px
    }
}
{let arr:ArrowShape = 
    {ArrowShape
        {Distance2d 1.5cm, 1cm},
        {Distance2d 3.5cm, 3.5cm},
        arrow-body-width = 1px,
        arrow-head-width = 10px,
        arrow-head-style = ArrowStyle.none,
        arrow-tail-style = ArrowStyle.none,
        color = FillPattern.brown
    }
}
{ellipse-left.apply-translation .5cm, .5cm}
{ellipse-right.apply-translation 3.5cm, 2.5cm}
{Canvas
    factor = AntialiasFactor.high,
    border-width = 1px,
    ellipse-left,
    arr,
    ellipse-right
}

ShapeGroup でのシェイプの分類

ShapeGroup はビジュアル表現を持たないシェイプで、他のシェイプのコンテナとしてのみ機能します。すべてのシェイプは他のシェイプのコンテナとすることができますが、それ自体は何も描画しないコンテナでシェイプをグループ化したい場合は ShapeGroup が便利です。ShapeGroup に非ローカル オプションを設定すると、グループ内のすべての子のシェイプの値を容易に設定できるようになります。
この例では ShapeGroup を使用して、棒グラフの棒をグループ化します。また、Shapes パッケージで階層的な変換を使用することも示されています。階層的な変換とは、シェイプの描画時にシェイプに適用される変換が、それ自体とその親すべての変換の合成であることを意味します。この場合、各四角形に設定される変換は水平行に四角形を配置します。親の ShapeGroup に適用される変換は、四角形の行を Canvas 内の適切な位置に移動します。
ShapeGroup translationy 座標を 7cm から 6cm へ変更してから、変更した例を実行してみます。すべての棒の位置が 1 cm 変更され、それぞれの棒と棒の相対的な位置関係は維持されます。

例: ShapeGroup を使用した棒グラフ
{import * from CURL.GUI.SHAPES}
{Canvas
    width = 9cm,
    height = 8cm,
    {ShapeGroup
        translation = {Distance2d 0cm, 7cm},
        {RectangleShape
            {GRect 0cm, 1cm, 1cm, 0cm},
            color = "#006968",
            translation = {Distance2d (1.1cm * 1), 0cm}
        },
        {RectangleShape
            {GRect 0cm, 1cm, 5cm, 0cm},
            color = "#006968",
            translation = {Distance2d (1.1cm * 3), 0cm}
        },
        {RectangleShape
            {GRect 0cm, 1cm, 3cm, 0cm},
            color = "#006968",
            translation = {Distance2d (1.1cm * 5), 0cm}
        },
        {RectangleShape
            {GRect 0cm, 1cm, 2cm, 0cm},
            color = "#2462a2",
            translation = {Distance2d (1.1cm * 2), 0cm}
        },
        {RectangleShape
            {GRect 0cm, 1cm, 4cm, 0cm},
            color = "#2462a2",
            translation = {Distance2d (1.1cm * 4), 0cm}
        },
        {RectangleShape
            {GRect 0cm, 1cm, 5cm, 0cm},
            color = "#2462a2",
            translation = {Distance2d (1.1cm * 6), 0cm}
        }
    }
}

グラフィック オブジェクトとシェイプの使用

GraphicShape を使用すると、Graphic オブジェクトを Shape 階層に埋め込むことができます。グラフィック オブジェクトには、コンテナ、コントロール、および GUI ツールキットのグラフィックが含まれます。グラフィック オブジェクトでは、シェイプでサポートされる回転およびスケーリングの変換をサポートしないため、グラフィックを含むシェイプに対し適用される回転やスケーリングは無視されることに注意してください。この方法は「ビルボード」と呼ばれる場合があります。GraphicShape は明示的に作成できますが、Shape に追加されるグラフィックは暗黙的に GraphicShape にキャストされるため、特に明示的に構築する必要はありません。
既定では、ImageShape も回転ではなくビルボードを使用します。「ImageShape の処理」を参照してください。
次の例は、TextFlowBoxEllipseShape に埋め込みます。TextFlowBox は楕円形の子であるため、楕円形に適用される変換はテキストにも適用されます。ただし、この楕円形に適用される回転はテキストには適用されません。
シェイプ内のグラフィックも、非ローカル オプションの値を親から取得します。この例で、次の行をコメントにします。
color = FillPattern.black
TextFlowBox 内のテキストは、テキストを非表示にする EllipseShape の色セットを使用します。
この結果、GUI ツールキットまたは Shapes パッケージだけを使用した場合にはできない方法でテキストが表示されます。TextFlowBox には、TextShape ではできないリッチ テキストを組み込む方法が用意され、楕円形の回転も、GUI ツールキットでは提供できないビジュアルな対象を追加します。

例: シェイプへのグラフィックの追加
{import * from CURL.GUI.SHAPES}
{let txt:TextFlowBox = 
    {TextFlowBox
        width = 2.7cm,
        horigin = "center",
        vorigin = "center",
        {paragraph
            paragraph-justify = "center", 
            This text is contained in a 
            {monospace TextFlowBox}, which is a 
            {monospace Graphic} object.
        },
        color = FillPattern.black
    }
}
{let ell:EllipseShape = 
    {EllipseShape
        {GRect 2cm, 2cm, 2.5cm, 2.5cm},
        color = "#ddffff",
        font-size = 11pt
    }
}
{value 
    {ell.add txt}
    {ell.apply-translation 2.5cm, 2.5cm}
    {ell.apply-rotation 45deg}
    {Canvas
        width = 5cm, height = 5cm,
        ell
    }
}