【JointJS】描画される要素の重ね合わせ

この記事ではJointJSで描画される要素の重ね合わせについて説明します。

## なぜ重ね合わせを意識する？

SVG重ね合わせでは、基本的に**上にあるものが下にあるものを隠す**動作になります。
表示で考えたら当たり前ですね。この挙動は表示だけでなくイベント伝搬についても同様です。2つの重なっている要素にクリックのイベントが設定されていた場合、上に表示されている要素のイベントのみが動作します。
下の要素のみにクリックイベントが設定されていた場合でも上の要素が優先されますので、クリックイベントを動作できなくなる、といったことも発生します。（設定による回避は可能）

## SVGの重ね合わせの法則

JointJSの話に移る前に、まずSVGの重ね合わせのルールについておさらいします。SVGの重ね合わせのルールはシンプルです。後に記述したものが上になります。
HTMLの場合、CSSの`z-index`で表示順序をコントロールできますが、SVG自体の仕様では`z-index`を使用できなくなっています。また、それに相当するような設定も存在しません。

## JointJSでの重ね合わせのコントロール

ここからは、JointJSで描画するSVGの重ね合わせコントロールについて説明します。

### 1. Graphへの追加順

JointJSでは原則Graphへ追加した順に要素が描画されます。そのため、上に表示したい要素を後に追加することで重ね合わせをコントロールできます。

#### コード例

```js
const rectangle1 = new joint.shapes.standard.Rectangle({
  size: { width: 150, height: 40 },
  position: { x: 10, y: 30 },
});
rectangle1.attr('label/text', '1個めの四角形');
rectangle1.attr('body/fill', 'yellow');

const rectangle2 = new joint.shapes.standard.Rectangle({
  size: { width: 150, height: 40 },
  position: { x: 100, y: 30 },  
});
rectangle2.attr('label/text', '2個めの四角形');
rectangle2.attr('body/fill', 'lightblue');

graph.addCell(rectangle1);
graph.addCell(rectangle2);
```

#### 実行結果

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129526/487010ff-90af-5064-7c2a-81175cc07c93.png)

#### 補足

影響するのは「生成した順」ではなく「Graphに追加した順」です。上のコードの`graph.addCell`の2行を逆転させると、逆の結果を得られます。

```js
// （四角形生成部分は省略）

graph.addCell(rectangle1);
graph.addCell(rectangle2);
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129526/5f34e57b-67fa-c54f-b9ec-28524772f166.png)

### 2. `z`の値を指定する

先ほどSVGでは`z-index`を使用できない、と説明しましたが、JointJSでは`z`という設定値を使って重ね合わせ順序設定ができるようになっています。設定値はCSSの`z-index`と同様に数値を指定し、数値が大きいものが上に表示されるようになっています。
※同じ`z`の値を持つ要素同士では、要素の追加順で重ね合わせが決まります。

先程のコードにzの設定のみを追加したコードと実行結果を以下に記載します。`z`の値を大きくすることで先に追加した要素でも上に表示されるようになったことがわかります。

#### コード例

```js
const rectangle1 = new joint.shapes.standard.Rectangle({
  size: { width: 150, height: 40 },
  position: { x: 10, y: 30 },
  z: 2,  // 追加
});
rectangle1.attr('label/text', '1個めの四角形\nz=2');
rectangle1.attr('body/fill', 'yellow');

const rectangle2 = new joint.shapes.standard.Rectangle({
  size: { width: 150, height: 40 },
  position: { x: 100, y: 30 },
  z: 1, // 追加
});
rectangle2.attr('label/text', '2個めの四角形\nz=1');
rectangle2.attr('body/fill', 'lightblue');

graph.addCell(rectangle1);
graph.addCell(rectangle2);
```

#### 実行結果

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129526/5e7aa086-8981-5946-c6bb-8fd03bfbd735.png)

#### なぜ`z`はGraphの追加順を無視できるのか？

JointJSがGraphへ要素を追加する際に`z`の値に基づいてソートしています。その結果、最終的に表示されるSVGではzの重ね合わせ順序に基づいた順番で出力されるようになっています。

なお、要素を追加するメソッド（`addCell()`など）にはオプション`sort`でソートの有無指定できます。`sort:false`で無効化した場合は前述のソートの動作が無効化されるため、`z`の指定は無視されてしまうことにご注意ください。

```zの指定が効かない例.js
// （四角形生成部分は省略）

graph.addCell(rectangle1, {sort: false});
graph.addCell(rectangle2, {sort: false});
```

## まとめ

今回はSVGの重ね合わせ仕様と、JointJSでの重ね合わせ順序指定方法について紹介しました。特にカスタム要素を使用する場合はGraphへの追加タイミングをコントロールできない状況が出てくるため、`z`をうまく使用してコントロールしましょう。

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

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