カテゴリー
【Svelte Framework入門】Svelteを使ってJavascript/CSSからSVG曲線チャートを描く
※ 当ページには【広告/PR】を含む場合があります。
2022/01/24
2022/08/19
基本のテクニック 〜 SVG要素のPath属性を使った曲線の書き方
Qコマンド
(...先行してMやQコマンドで始点が必要)
Q [中間制御点のx座標] [中間制御点のy座標]
[終点のx座標] [終点のy座標]
<svg width="300" height="300" viewBox='-10 -10 310 310'>
<path d="
M 10 80
Q 225 250 250 100"
stroke="black" fill="transparent"/>
</svg>
(10, 80)
(250, 100)
(225, 250)
<svg width="300" height="300" viewBox='-10 -10 310 310'>
<path d="
M 10 80
Q 45 60 80 80
Q 140 110 200 200
Q 225 250 250 100"
stroke="black" fill="transparent"/>
</svg>
SVG曲線をSvelteで描くやり方
Svelteアプリの開発環境を準備する
App.svelte
App.svelte
Sassコード側からSvelteへ(やや強引に)データを受け渡すやり方
_data.scss
$ tree
.
├── node_modules
├── package.json
├── public
│ ├── favicon.png
│ └── index.html
├── rollup.config.js
├── src
│ ├── App.svelte
│ ├── _data.scss #👈ファイルを追加
│ ├── global.d.ts
│ └── main.ts
├── svelte.config.js
├── tsconfig.json
└── yarn.lock
@use 'data'; //👈_data.scss
//...
#dataholder {
display: none;
&::before { content: "#{data.$sim_xydata}"; }
}
Svelteコードの実装
<script lang="ts">
import { onMount } from 'svelte';
let dataholderSpan: any;
let dataArr: any[] = [];
let path: string;
const prevData: number[] = [0, 0, 0, 0];
const bendCoeff = 0.4; // 0.2 ~ 0.6
onMount(() => {
//👇隠しデータセットを仕込んだ擬似要素を取り出し
const pseudo = window.getComputedStyle(dataholderSpan, '::before');
//👇隠し要素からcontentの文字列(2次配列)を読み込み、xy座標の2次配列に復元
dataArr = pseudo.content.replace(/^\"\[/,'').replace(/\]\"$/,'').match(/(\[.*?\])/g).map((xy: string) => {
const xymatch = xy.match(/\[(.*),(.*)\]/);
return [ parseFloat(xymatch[1]), parseFloat(xymatch[2]) ];
});
//👇SVGのPath要素で2次のベシェ曲線を描くコマンドを構築(☆)
path = `M${dataArr.map(p => {
const interpolitedX = (p[0] + prevData[0]) / 2;
const interpolitedY = (p[1] + prevData[1]) / 2 + bendCoeff * prevData[3];
prevData[2] = interpolitedX;
prevData[3] = p[1] - prevData[1];
prevData[0] = p[0];
prevData[1] = p[1];
return `${interpolitedX},${interpolitedY} ${p[0]},${p[1]}`;
}).join('Q')}`
});
</script>
<svg class="curve" width="100" height="10" viewBox='0 0 100 10'>
<path d="{path}" stroke="black" fill="transparent"/>
{#each dataArr as xyset}
<circle class="dataPoint" cx="{xyset[0]}" cy="{xyset[1]}" r="0.3" />
{/each}
</svg>
<span id="dataholder" bind:this="{dataholderSpan}"></span>
<style lang="scss">
@use 'data';
svg {
display: block;
width: 900px;
height: 250px;
.dataPoint { fill:none; stroke:#eea300; stroke-width:0.2; }
&.curve {
path { stroke-width: 0.15; }
}
}
#dataholder {
display: none;
&::before { content: "#{data.$sim_xydata}"; }
}
</style>
☆
//...中略
//👇一つ前に使った終点xy座標と制御点の情報を保持
const prevData: number[] = [0, 0, 0, 0];
//👇各始点-終点からなる線分の中点からの制御点の乖離率(0.2 ~ 0.6程度)
const bendCoeff = 0.4;
//...中略
path = `M${dataArr.map(p => {
//👇制御点のx座標 => 線分の中点のx座標
const interpolitedX = (p[0] + prevData[0]) / 2;
//👇制御点のy座標 => 線分の中点のy座標から前回のy座標の差分をモーメンタム量として
// 適当な乖離率(ここでは0.4)を掛けた値で補正
const interpolitedY = (p[1] + prevData[1]) / 2 + bendCoeff * prevData[3];
//👇古い情報を更新
prevData[3] = p[1] - prevData[1];
prevData[0] = p[0];
prevData[1] = p[1];
prevData[2] = interpolitedX;
return `${interpolitedX},${interpolitedY} ${p[0]},${p[1]}`;
}).join('Q')}`
まとめ
記事を書いた人
ナンデモ系エンジニア
主にAngularでフロントエンド開発することが多いです。 開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。 コツコツとプログラミングするのが好きな人間です。
カテゴリー