【JointJS】カスタム要素を作成して再利用可能なパーツを作成する

この記事では、JointJSで柔軟な図を作成するために重要なカスタム要素について紹介します。

## カスタム要素の定義方法

カスタム要素は、JointJSで用意されている図形を拡張して作成します。図形の拡張は、各図形クラスの`define()`メソッドを使用します。

## `define()`メソッドの使い方

[公式ドキュメント](https://resources.jointjs.com/docs/jointjs/v3.7/joint.html#dia.Cell.define)によると`define()`メソッドの定義は`define(type [, defaultAttributes, prototypeProperties, staticProperties])`となっています。
オプションとなっている引数も含めて1つずつ内容を見ていきます。

### 第1引数 `type`

JointJS内で識別するための文字列を指定します。メソッド引数の中で唯一、必須の引数です。
指定した文字列はJointJS内のnamespaceとして`joint.shapes`配下に追加されます。例えば`example.MyRectangle`を指定した場合、`joint.shapes.example.MyRectangle`の名前でカスタムした図形を使用できるようになります。

#### コード例

```js
joint.shapes.standard.Rectangle.define('example.MyRectangle');

const rect = new joint.shapes.example.MyRectangle({
  position : { x: 10, y: 10 },
  size : { width: 100, height: 40},
  attrs: {
    body: {
      stroke: "#999",
      fill: "#999",      
    }
  }
});

rect.addTo(graph);
```

第1引数のみの指定だと拡張元の図形と全く同じものを定義することになりますので、実際は第2引数以降で指定する内容でカスタマイズしていくことになります。

### 第2引数 `defaultAttributes`

カスタムの要素にデフォルトで指定するプロパティを指定します。固定で設定する属性値を設定するのに便利です。例えば「テキスト入りの長方形」を作る場合、変更が少ない背景色や図形の大きさなどの設定を`defaultAttributes`にしておくことで、再利用しやすい要素を作れます。

#### コード例

```js
/*
 * カスタム要素の定義
 * 変更の少ないサイズ・色の情報はデフォルト値で指定
 */
joint.shapes.standard.Rectangle.define('example.MyRectangle', {
  size : { width: 100, height: 40 },
  attrs: {
    body: {
      stroke: "#999",
      fill: "#999",      
    }, 
    label: {
      stroke: "#EEE",
      fill: "#EEE",
    }
  }
});

/* 
 * カスタム要素の生成
 * デフォルト値を省いてCellを追加できる
 */
const rect1 = new joint.shapes.example.MyRectangle({
  position : { x: 10, y: 10 },
  attrs: {
    label: {
      text: 'グレーの箱１'
    }    
  }
});

const rect2 = new joint.shapes.example.MyRectangle({
  position : { x: 150, y: 10 },
  attrs: {
    label: {
      text: 'グレーの箱2'
    }    
  }
});

graph.addCells(rect1, rect2);
```

#### 実行結果

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129526/1068e407-4cdf-e53c-8bf9-86661870fe15.png)

### 第3引数 `prototypeProperties`

要素に紐づくオブジェクトのプロパティを指定します。`defaultAttributes`では実際に描画されるタグの属性を指定していたのに対して、こちらは紐づいているオブジェクトのプロパティを指定する点が異なります。

要素のマークアップ情報を指定するのに使用することが多いです。
マークアップ情報は、特に後述する`dia.Element`（空要素）の拡張をするときに特に重要になります。

### 第4引数 `staticProperties`

カスタム要素のクラスのstaticプロパティ・メソッドを定義できます。

カスタム要素のコンストラクタも定義できるので、可変な要素を引数に取るコンストラクタを定義しておくと再利用性の高いパーツを作成できます。

### コード例

```js
joint.shapes.standard.Rectangle.define('example.MyRectangle', {
    // （中略）
},
{
    // （中略）
},
{
  // テキストと座標を指定できるコンストラクタ
  create: function(text, x, y) {
    const rect = new joint.shapes.example.MyRectangle();
    rect.attr('label/text', text);
    rect.position(x, y);
    
    return rect;
  }
});

// コンストラクタでカスタム要素を生成
const rect1 = joint.shapes.example.MyRectangle.create('四角形１', 10, 10);
const rect2 = joint.shapes.example.MyRectangle.create('四角形2', 150, 10);

graph.addCells(rect1, rect2);
```

実行結果は[こちら](#実行結果)と変わりませんが、前回と比べると再利用時のコード記述量が抑えられています。

## 空要素 `dia.Element`

`dia.Element`は全てのJointJS要素の基礎モデルです。抽象クラスのようなイメージです。そのままだと使うことができませんが、`dia.Element`から拡張させるとカスタム要素を定義することで0からパーツを作成できます。

## どんなときにカスタム要素を使うべきか？

何度も同じ形状の図形を描画する必要がある場合に、カスタム要素でパーツの情報を定義しておくことで同じ設定やロジックを何回も書く必要がなくなるため、再利用性の高いコードを書けます。

また、`staticProperties`でコンストラクタを定義しておくことで、ある程度のロジックをパーツ側に持たせることもできます。設計にもよりますが、適切な責務分離を行うのにも一役買うことができるでしょう。

## まとめ

今回はカスタム要素紹介しました。カスタム要素を定義することで、複雑な図形でも共通部分をパーツ情報としてまとめられ、記述量を抑えることができます。カスタム要素はJointJSの図形描画の自由度を広げる重要な要素ですので、使い方を抑えておきましょう。

※この記事は [JointJS Advent Calendar 2023](https://qiita.com/advent-calendar/2023/jointjs) の記事です。他の記事を読む場合はカレンダーのページを参照してください。

https://qiita.com/advent-calendar/2023/jointjs