まえがき
d3でグラフを作成しているとき、指定した範囲のグラフ部分の色を変更したい場面があった。実際のイメージはこんな感じ。
グラフのうち、値が100以上の部分は赤、それ以下は青で描画させる。
ところが、d3自体にはこれを簡単にやってくれる機能がないらしい。そこで、clip-pathというSVGのプロパティを使ってこれを実現する。
clip-pathとは
まず、clip-pathについて理解しておく必要がある。clip-pathを使用すれば、他の場所で定義された一部分を取り除くことができる。これをクリッピングと呼ぶ。
<svg><clipPathid="clip-left-above"><rectx="0"y="0"width="130"height="50"/> //切り取る部分の設定
</clipPath><circlecx="100"cy="100"r="100"clip-path="url(#clip-left-above)"/> //元の図形
</svg>
まず、clipPathタグに固有のidを指定したのち、そのタグ内で、切り取る部分(実際に描画させたい部分)を指定している。そして、元の図形(切り取りして描画させたい図形)のタグにclip-path属性を"url(#先程指定したclipPathのid)"の形で付与する。
すると、5行目で書かれている、中心が(cx, cy) = (100, 100)で半径100の円のうち、clipPath内で指定したrectと重なる部分のみが実際に画面に描画される。
コードは以下。
<svgwidth="1000"height="1000"><clipPathid="clip-somewhere"><rectx="130"y="0"width="125"height="200"/><rectx="350"y="20"width="255"height="180"/><rectx="220"y="220"width="120"height="110"/><rectx="400"y="200"width="170"height="155"/><rectx="130"y="350"width="190"height="110"/><rectx="430"y="440"width="220"height="50"/><rectx="145"y="515"width="450"height="40"/><rectx="222"y="570"width="160"height="100"/><rectx="490"y="560"width="230"height="230"/></clipPath><imagexlinkHref={"画像のURL"}width="800"height="800"clip-path="url(#clip-somewhere)"/></svg>
何と、clip-pathを使えばこんなことまでできてしまう。画像が全部みたい?であれば、上記のコードを使って、ぜひ自分でやってみると良い。見たい部分をclipPathのタグ内で好きな形で範囲指定できる。
d3グラフで指定の範囲の色を変える
おふざけはここまでにして、本題に戻る。
今回はこのclip-pathを使って、100を超える範囲についてのみ色が赤くなる棒グラフを作成する。
作成の手順としては、まず切り取られる対象(棒グラフ)を作成する際、ほぼ全く同じ値を持ち、同じ動きをするグラフを2つ作る。そしてそのそれぞれに異なるclip-path属性を指定して、違う色を指定する。
d3.select(graphSelector)
.selectAll(".bar-below")
.data(Data)
.enter()
.append("rect")
.attr("clip-path", "url(#clip-below)") //clip-path属性の指定
.attr("class", "bar-below")
.attr("x", d => Number(scale.x(d.x.toString())) + 5)
.attr("y", height)
.attr("width", width / Data.length - barPadding)
.attr("y", d => scale.y(d.y))
.attr("height", d => height - scale.y(d.y))
.style("fill", "blue"); //clip-belowで切り取られた範囲は青
d3.select(graphSelector)
.selectAll(".bar")
.data(Data)
.enter()
.append("rect")
.attr("x", d => Number(scale.x(d.x.toString())) + 5)
.attr("y", height)
.attr("width", width / Data.length - barPadding)
.attr("clip-path", "url(#clip-above)") //clip-path属性の指定
.attr("class", "bar-above")
.attr("y", d => scale.y(d.y))
.attr("height", d => height - scale.y(d.y))
.style("fill", "red"); //clip-aboveで切り取られた範囲は赤
そして次に、切り取る範囲の指定を行う。ここでclipPathタグをappendしてそのタグにid="clip-above"を指定する。ここで指定された範囲は赤色でfillされる。
ここでは、y="0"、すなわち上端から、横幅はグラフコンポーネントの幅、縦幅はグラフコンポーネントの縦幅の上端から10/13まで、すなわち130~100までの範囲を切り取っている。
d3.select(graphSelector)
.data(Data)
.append("clipPath")
.attr("id", "clip-above")
.append("rect")
.attr("x", d => Number(scale.x(d.x.toString())) + 5)
.attr("y", 0)
.attr("width", width)
//130から100までの高さを指定
.attr("height", (height * 3) / 13)
同様にして、100以下の範囲も切り取る。切り取られた範囲と、グラフが重なる部分は青く描画される。
d3.select(graphSelector)
.data(Data)
.append("clipPath")
.attr("id", "clip-below")
.append("rect")
.attr("x", d => Number(scale.x(d.x.toString())) + 5)
//100%の高さを起点に指定
.attr("y", (height * 3) / 13)
.attr("width", width)
.attr("height", 500)
ちなみにブラウザのHTMLはこんな感じ。作成の際の参考にしてほしい。
<gclass="graphContainer"><clipPathid="clip-above"><rectx="5"y="0"width="500"height="69.23076923076923"></rect></clipPath><clipPathid="clip-below"><rectx="5"y="69.23076923076923"width="500"height="500"></rect></clipPath><rectclip-path="url(#clip-below)"class="bar-below"x="5"y="184.6153846153846"width="45"height="115.38461538461539"style="fill: blue;"></rect><rectclip-path="url(#clip-below)"class="bar-below"x="55"y="46.15384615384615"width="45"height="253.84615384615384"style="fill: blue;"></rect><rectclip-path="url(#clip-below)"class="bar-below"x="105"y="230.76923076923075"width="45"height="69.23076923076925"style="fill: blue;"></rect><rectclip-path="url(#clip-below)"class="bar-below"x="155"y="161.53846153846152"width="45"height="138.46153846153848"style="fill: blue;"></rect><rectclip-path="url(#clip-below)"class="bar-below"x="205"y="23.076923076923062"width="45"height="276.92307692307696"style="fill: blue;"></rect><rectclip-path="url(#clip-below)"class="bar-below"x="255"y="53.07692307692309"width="45"height="246.9230769230769"style="fill: blue;"></rect><rectclip-path="url(#clip-below)"class="bar-below"x="305"y="76.15384615384615"width="45"height="223.84615384615387"style="fill: blue;"></rect><rectclip-path="url(#clip-below)"class="bar-below"x="355"y="235.3846153846154"width="45"height="64.61538461538461"style="fill: blue;"></rect><rectclip-path="url(#clip-below)"class="bar-below"x="405"y="140.76923076923077"width="45"height="159.23076923076923"style="fill: blue;"></rect><rectclip-path="url(#clip-below)"class="bar-below"x="455"y="11.538461538461531"width="45"height="288.46153846153845"style="fill: blue;"></rect><rectx="5"y="184.6153846153846"width="45"clip-path="url(#clip-above)"class="bar-above"height="115.38461538461539"style="fill: red;"></rect><rectx="55"y="46.15384615384615"width="45"clip-path="url(#clip-above)"class="bar-above"height="253.84615384615384"style="fill: red;"></rect><rectx="105"y="230.76923076923075"width="45"clip-path="url(#clip-above)"class="bar-above"height="69.23076923076925"style="fill: red;"></rect><rectx="155"y="161.53846153846152"width="45"clip-path="url(#clip-above)"class="bar-above"height="138.46153846153848"style="fill: red;"></rect><rectx="205"y="23.076923076923062"width="45"clip-path="url(#clip-above)"class="bar-above"height="276.92307692307696"style="fill: red;"></rect><rectx="255"y="53.07692307692309"width="45"clip-path="url(#clip-above)"class="bar-above"height="246.9230769230769"style="fill: red;"></rect><rectx="305"y="76.15384615384615"width="45"clip-path="url(#clip-above)"class="bar-above"height="223.84615384615387"style="fill: red;"></rect><rectx="355"y="235.3846153846154"width="45"clip-path="url(#clip-above)"class="bar-above"height="64.61538461538461"style="fill: red;"></rect><rectx="405"y="140.76923076923077"width="45"clip-path="url(#clip-above)"class="bar-above"height="159.23076923076923"style="fill: red;"></rect><rectx="455"y="11.538461538461531"width="45"clip-path="url(#clip-above)"class="bar-above"height="288.46153846153845"style="fill: red;"></rect></g>
終わりに
clip-pathを使えば結構自由にグラフをアレンジできる。色を変える以外にも、使える用途がたくさんありそう。