Vue.jsとSVGのz-index

# はじめに

svgではcssのようにz-indexを指定できません。単純に後ろに記述された要素が前面に来ます。
d3.jsや素のjavascriptで重なり順を操作しようとすると結構面倒なことになりますが、
vueでsvgを操作する場合は意外と簡明です。

# デモ

[デモ](https://jsfiddle.net/u5qzdetz/3/)

サンプルとしてレイヤーを操作するデモを書いてみました。



# 説明

デモ中で、要素を最背面に移動するコードは次のようになっています。

```js
this.graphItems.splice(this.graphItems.indexOf(targetItem),1);
this.graphItems.unshift(targetItem);
```

たったの2行です。シンプル。

なぜこれで順序がかわるのかと言うと、テンプレートに次のように記述されているので、単純に配列`graphItems`の順序が重なり順になるんですね。

```html
<line-groups v-for="graphItem in graphItems" ... >
```

レイヤーを一つ上に移動するのも再前面にいくのも同じように配列操作のみで可能です。

# おわりに

実際にグラフを描くときは、軸-グラフ-ラベルにコンポーネントを分けて、階層順に記述し、ルートで生成した共通のデータをpropsで渡すのがコードが簡単になりそう。

```html
 <base :graphdata="graphdata" /> <!--土台-->
 <axis :graphdata="graphdata" /> <!--軸-->
 <charts :graphdata="graphdata" /><!--グラフ-->
 <labels :graphdata="graphdata" /> <!--ラベル-->
 <tool-tips :graphdata="graphdata" />
```


# コード全文

```js
Vue.component('line-groups',{
	template:`
  	<g>
      <line v-for="line in lines"
            :x1="line[0].x" :y1="line[0].y" 
  			:x2="line[1].x" :y2="line[1].y" 
            :style="{stroke:color,'stroke-width':10}" />
    </g>
  `,
  props:{
  	color:String,
    lines:Array
  }
});


new Vue({
  el: '#app',
  template:`
  <div>
    <svg  height="400" width="400">
       <line-groups v-for="graphItem in graphItems"
         :color="graphItem.color"
         :lines="graphItem.lines"
         :key="graphItem.color" ></line-groups>
    </svg>
  
     <div>
       <span>対象:</span>
       <select v-model="target">
         <option v-for="color in colors" :value="color">{{color}}</option>
       </select>
       <button @click="toTop">最前面</button>
       <button @click="toUp">上へ</button>
       <button @click="toLow">下へ</button>
       <button @click="toBottom">再背面</button>
     </div>
  </div>
  `,
  data(){
  	return {
    	colors:["red","blue","green","yellow"],
    	graphItems:[],
        target:"red"
    }
  },
  computed:{
  	targetItem(){
    	return this.graphItems.find((graphItem)=>graphItem.color === this.target);
    }
  },
  mounted(){
  	this.reset();
  },
  methods:{
    toTop(){
    	 const targetItem = this.targetItem;
         this.graphItems.splice(this.graphItems.indexOf(targetItem),1);
         this.graphItems.push(targetItem);
    },
    toUp(){
    	 const targetItem = this.targetItem;
         const targetIndex = this.graphItems.indexOf(targetItem);
         const uppperItem = this.graphItems[targetIndex+1];
         console.log(uppperItem);
         if (!uppperItem){
       		return;
         }
         this.graphItems.splice(targetIndex,2,uppperItem,targetItem);
    },
    toLow(){
       const targetItem = this.targetItem;
       const targetIndex = this.graphItems.indexOf(targetItem);
       const lowerItem = this.graphItems[targetIndex-1];
       if (!lowerItem){
       		return;
       }
       this.graphItems.splice(targetIndex-1,2,targetItem,lowerItem);
    },
    toBottom(){
       const targetItem = this.targetItem;
       this.graphItems.splice(this.graphItems.indexOf(targetItem),1);
       this.graphItems.unshift(targetItem);
    },
    reset(){
    	this.graphItems = this.colors.map((color)=>{
        const lines = Array.from(new Array(4), ()=>{
        	if (Math.random() < 0.5){
            return [{x:Math.random()*400,y:0},{x:Math.random()*400,y:400}];
          } else {
            return [{x:0,y:Math.random()*400},{x:400,y:Math.random()*400,}];
          }
        });
        return {color,lines};
      });
    }
  }
})
```