EP33 - ex5. SVG

更新 發佈閱讀 23 分鐘
哇!開始用SVG畫圖了~越來越實用了~
設計一些實用的可縮放向量圖形~
應該可以做一些簡單的動畫吧!

這次有四個檔案,App.vue、AxisLabel.vue、PolyGraph.vue、util.js

App.vue

<!--
An SVG graph
-->

<script setup>
import PolyGraph from './PolyGraph.vue'
import { ref, reactive } from 'vue'

const newLabel = ref('')
const stats = reactive([
{ label: 'A', value: 100 },
{ label: 'B', value: 100 },
{ label: 'C', value: 100 },
{ label: 'D', value: 100 },
{ label: 'E', value: 100 },
{ label: 'F', value: 100 }
])

function add(e) {
e.preventDefault()
if (!newLabel.value) return
stats.push({
label: newLabel.value,
value: 100
})
newLabel.value = ''
}

function remove(stat) {
if (stats.length > 3) {
stats.splice(stats.indexOf(stat), 1)
} else {
alert("Can't delete more!")
}
}
</script>

<template>
<svg width="200" height="200">
<PolyGraph :stats="stats"></PolyGraph>
</svg>

<!-- controls -->
<div v-for="stat in stats">
<label>{{stat.label}}</label>
<input type="range" v-model="stat.value" min="0" max="100">
<span>{{stat.value}}</span>
<button @click="remove(stat)" class="remove">X</button>
</div>

<form id="add">
<input name="newlabel" v-model="newLabel">
<button @click="add">Add a Stat</button>
</form>

<pre id="raw">{{ stats }}</pre>
</template>

<style>
polygon {
fill: #42b983;
opacity: 0.75;
}

circle {
fill: transparent;
stroke: #999;
}

text {
font-size: 10px;
fill: #666;
}

label {
display: inline-block;
margin-left: 10px;
width: 20px;
}

#raw {
position: absolute;
top: 0;
left: 300px;
}
</style>

<script setup>

import PolyGraph from './PolyGraph.vue'
import { ref, reactive } from 'vue'
  • PolyGraph 是我們將用於繪製多邊形圖的子組件。
  • refreactive 是 Vue 3 的響應性 API,用來創建響應性數據。
const newLabel = ref('')
const stats = reactive([
{ label: 'A', value: 100 },
{ label: 'B', value: 100 },
{ label: 'C', value: 100 },
{ label: 'D', value: 100 },
{ label: 'E', value: 100 },
{ label: 'F', value: 100 }
])
  • newLabel 是一個響應性引用,用來儲存新添加的標籤。
  • stats 是一個響應性物件,用來儲存每個標籤及其對應的值。
function add(e) {
e.preventDefault()
if (!newLabel.value) return
stats.push({
label: newLabel.value,
value: 100
})
newLabel.value = ''
}
  • add 函數用來添加新的標籤到 stats 中。
  • e.preventDefault() 用來防止表單的默認提交行為。
  • 如果 newLabel 不是空的,就將其添加到 stats 中,並重置 newLabel
function remove(stat) {
if (stats.length > 3) {
stats.splice(stats.indexOf(stat), 1)
} else {
alert("Can't delete more!")
}
}
  • remove 函數用來從 stats 中移除標籤。
  • stats 長度超過 3 時,允許移除;否則,顯示警告信息。

<template>

<svg width="200" height="200">
<PolyGraph :stats="stats"></PolyGraph>
</svg>
  • 使用 svg 元素來顯示多邊形圖。
  • PolyGraph 組件接收 stats 作為道具,繪製圖表。
  <div v-for="stat in stats">
<label>{{stat.label}}</label>
<input type="range" v-model="stat.value" min="0" max="100">
<span>{{stat.value}}</span>
<button @click="remove(stat)" class="remove">X</button>
</div>
  • 使用 v-for 遍歷 stats,顯示每個標籤及其對應的控制項。
  • input 元素用來調整標籤的值。
  • button 用來移除標籤。
  <form id="add">
<input name="newlabel" v-model="newLabel">
<button @click="add">Add a Stat</button>
</form>
  • 表單用來添加新標籤。
  • input 用來輸入新標籤的名稱,綁定到 newLabel
  • button 用來觸發 add 函數,添加新標籤。
  <pre id="raw">{{ stats }}</pre>
  • 使用 <pre> 元素顯示 stats 的原始數據,以便於調試。

<style>

<style scoped>
polygon {
fill: #42b983;
opacity: 0.75;
}

circle {
fill: transparent;
stroke: #999;
}

text {
font-size: 10px;
fill: #666;
}

label {
display: inline-block;
margin-left: 10px;
width: 20px;
}

#raw {
position: absolute;
top: 0;
left: 300px;
}
</style>
  • polygon 樣式用來設定多邊形的顏色和透明度。
  • circle 樣式用來設定圓形的填充和邊框。
  • text 樣式用來設定文本的字體大小和顏色。
  • label 樣式用來設定標籤的顯示方式。
  • #raw 樣式用來設定顯示原始數據的位置和樣式。

AxisLabel.vue

<script setup>
import { computed } from 'vue'
import { valueToPoint } from './util.js'

const props = defineProps({
stat: Object,
index: Number,
total: Number
})

const point = computed(() =>
valueToPoint(+props.stat.value + 10, props.index, props.total)
)
</script>

<template>
<text :x="point.x" :y="point.y">{{stat.label}}</text>
</template>

<script setup>

import { computed } from 'vue'
import { valueToPoint } from './util.js'
  • 從 Vue 中引入 computed 函數,用來創建計算屬性。
  • util.js 文件中引入 valueToPoint 函數,用來將值轉換為座標點。
const props = defineProps({
stat: Object,
index: Number,
total: Number
})

使用 defineProps 定義組件的 props,包括:

  • stat:一個物件,表示統計數據。
  • index:一個數字,表示當前統計數據在列表中的索引。
  • total:一個數字,表示統計數據的總數。
const point = computed(() =>
valueToPoint(+props.stat.value + 10, props.index, props.total)
)
  • 使用 computed 函數創建計算屬性 point
  • valueToPoint 函數接收三個參數:
    • +props.stat.value + 10:將統計數據的值轉換為數字並加上 10。
    • props.index:當前統計數據的索引。
    • props.total:統計數據的總數。
  • point 是一個響應性物件,包含計算出來的 xy 座標。

<template>

<template>
<text :x="point.x" :y="point.y">{{props.stat.label}}</text>
</template>
  • 使用 <text> 元素在 SVG 中顯示文本。
  • :x="point.x":綁定 x 屬性到計算屬性 pointx 值。
  • :y="point.y":綁定 y 屬性到計算屬性 pointy 值。
  • {{props.stat.label}}:顯示 props.stat 中的標籤文本。

PolyGraph.vue

<script setup>
import AxisLabel from './AxisLabel.vue'
import { computed } from 'vue'
import { valueToPoint } from './util.js'

const props = defineProps({
stats: Array
})

const points = computed(() => {
const total = props.stats.length
return props.stats
.map((stat, i) => {
const { x, y } = valueToPoint(stat.value, i, total)
return `${x},${y}`
})
.join(' ')
})
</script>

<template>
<g>
<polygon :points="points"></polygon>
<circle cx="100" cy="100" r="80"></circle>
<axis-label
v-for="(stat, index) in stats"
:stat="stat"
:index="index"
:total="stats.length"
>
</axis-label>
</g>
</template>

<style>
polygon {
fill: #42b983;
opacity: 0.75;
}

circle {
fill: transparent;
stroke: #999;
}
</style>

<script setup>

import AxisLabel from './AxisLabel.vue'
import { computed } from 'vue'
import { valueToPoint } from './util.js'
  • 從當前目錄中的 AxisLabel.vue 文件中引入 AxisLabel 組件。
  • 從 Vue 中引入 computed 函數,用來創建計算屬性。
  • 從當前目錄中的 util.js 文件中引入 valueToPoint 函數,用來將值轉換為座標點。
const props = defineProps({
stats: Array
})

使用 defineProps 定義組件的 props,包括:

  • stats:一個陣列,表示統計數據。
const points = computed(() => {
const total = props.stats.length
return props.stats
.map((stat, i) => {
const { x, y } = valueToPoint(stat.value, i, total)
return `${x},${y}`
})
.join(' ')
})
  • 使用 computed 函數創建計算屬性 points
  • total 變數計算 stats 陣列的長度,表示統計數據的總數。
  • props.stats.map 遍歷 stats 陣列,對每個 stat 和其索引 i 進行以下操作:
    • 使用 valueToPoint 函數計算出 stat.value 對應的 xy 座標。
    • 返回格式為 ${x},${y} 的字串。
  • join(' ') 方法將所有座標字串用空格連接起來,形成 SVG polygon 元素需要的 points 屬性值。

<template>

<template>
<g>
<polygon :points="points"></polygon>
<circle cx="100" cy="100" r="80"></circle>
<axis-label
v-for="(stat, index) in stats"
:stat="stat"
:index="index"
:total="stats.length"
>
</axis-label>
</g>
</template>


SVG 元素群組 <g>

  • 使用 <g> 標籤將 SVG 元素分組,這是一個容器元素,用來組織其他 SVG 元素。

多邊形 <polygon>

  • 使用 <polygon> 元素繪製多邊形。
  • :points="points" 綁定 points 計算屬性作為多邊形的頂點座標。

圓形 <circle>

  • 使用 <circle> 元素繪製一個圓。
  • cx="100"cy="100" 設置圓心的 x 和 y 座標。
  • r="80" 設置圓的半徑。

自定義標籤組件 <axis-label>

  • 使用 <axis-label> 元素來顯示每個統計數據的標籤。
  • 使用 v-for 指令遍歷 stats 陣列,為每個 stat 創建一個 axis-label 元素。
  • :stat="stat" 綁定當前的 stat
  • :index="index" 綁定當前的索引。
  • :total="stats.length" 綁定統計數據的總數。
<style>
polygon {
fill: #42b983;
opacity: 0.75;
}

circle {
fill: transparent;
stroke: #999;
}
</style>
style部分,我從App.vue搬到子組件裡頭,因為我App.vue使用style scoped的關係唷!為什麼會使用呢?就是因為全域的style又會影響到其他祖先組件的內容了

util.js

export function valueToPoint(value, index, total) {
const x = 0
const y = -value * 0.8
const angle = ((Math.PI * 2) / total) * index
const cos = Math.cos(angle)
const sin = Math.sin(angle)
const tx = x * cos - y * sin + 100
const ty = x * sin + y * cos + 100
return {
x: tx,
y: ty
}
}

這個函數 valueToPoint 用來將某個值轉換成在圓形坐標系中的點,常用於繪製雷達圖或其他基於極坐標的圖形。下面是逐行解釋:

  • export function valueToPoint:定義並導出名為 valueToPoint 的函數。
  • value, index, total:這個函數接受三個參數:
    • value:要轉換的值。
    • index:當前點在多邊形中的索引。
    • total:多邊形的頂點總數。
  • const x = 0:定義一個 x 變量並初始化為 0。這表示在極坐標系中,所有點的初始 x 坐標都在 y 軸上。
  • const y = -value * 0.8:定義一個 y 變量並將其初始化為 -value * 0.8。這裡的 value 是要轉換的值,乘以 -0.8 是為了縮放並翻轉 y 坐標,使得值越大,點越遠離圓心。
  • const angle = ((Math.PI * 2) / total) * index:計算當前點的角度。
    • Math.PI * 2:表示完整的圓周(360度)。
    • / total:將圓周分成等份,每份的角度。
    • * index:將角度乘以當前點的索引,確定當前點在圓周上的位置。
  • const cos = Math.cos(angle):計算該角度的餘弦值,用於旋轉變換。
  • const sin = Math.sin(angle):計算該角度的正弦值,用於旋轉變換。
  • const tx = x * cos - y * sin + 100:計算變換後的 x 坐標,並將其平移 100 個單位,使得點在畫布中心(100, 100)周圍旋轉。
    • x * cos:x 坐標旋轉後的新 x 值。
    • - y * sin:y 坐標旋轉後的新 x 值。
    • + 100:將 x 坐標平移 100 個單位,使其位於畫布中心。
  • const ty = x * sin + y * cos + 100:計算變換後的 y 坐標,並將其平移 100 個單位。
    • x * sin:x 坐標旋轉後的新 y 值。
    • + y * cos:y 坐標旋轉後的新 y 值。
    • + 100:將 y 坐標平移 100 個單位,使其位於畫布中心。
  • return { x: tx, y: ty }:返回一個包含變換後 x 和 y 坐標的物件,表示在圓形坐標系中的點。
vocus|新世代的創作平台
這題的CSS,我有改過一些~有興趣可以再看一下github
讓我想到這個範例可以改一改拿來當作獵人的念能力分佈圖
2024連載再開!希望能活著看到結局~看到庫拉皮卡下船www
留言
avatar-img
卡關的人生
4會員
73內容數
分享生活趣事~
卡關的人生的其他內容
2024/11/10
Vue 提供了多種動畫技術來提升應用程式的互動性,包括基於 CSS 類別的動畫、基於狀態的動畫,以及使用監視器來動畫化數值。基於類別的動畫可通過動態添加 CSS 類別來觸發,像是觸發按鈕搖動效果。基於狀態的動畫則是透過樣式綁定,根據互動動態調整元素的外觀,例如根據滑鼠位置改變背景顏色。
Thumbnail
2024/11/10
Vue 提供了多種動畫技術來提升應用程式的互動性,包括基於 CSS 類別的動畫、基於狀態的動畫,以及使用監視器來動畫化數值。基於類別的動畫可通過動態添加 CSS 類別來觸發,像是觸發按鈕搖動效果。基於狀態的動畫則是透過樣式綁定,根據互動動態調整元素的外觀,例如根據滑鼠位置改變背景顏色。
Thumbnail
2024/11/09
Web Components 是一組網頁原生 API,允許開發者創建可重複使用的自訂元素。Vue 與 Web Components 是互補的技術,Vue 支援整合和創建自訂元素。
Thumbnail
2024/11/09
Web Components 是一組網頁原生 API,允許開發者創建可重複使用的自訂元素。Vue 與 Web Components 是互補的技術,Vue 支援整合和創建自訂元素。
Thumbnail
2024/11/08
Vue 建議使用模板構建應用程式,但在需要 JavaScript 的全程式化功能時,渲染函數可派上用場。渲染函數通過 h() 函數創建 vnode,h 是 hyperscript 的簡寫,能生成 HTML 的 JavaScript。
Thumbnail
2024/11/08
Vue 建議使用模板構建應用程式,但在需要 JavaScript 的全程式化功能時,渲染函數可派上用場。渲染函數通過 h() 函數創建 vnode,h 是 hyperscript 的簡寫,能生成 HTML 的 JavaScript。
Thumbnail
看更多
你可能也想看
Thumbnail
本文分析導演巴里・柯斯基(Barrie Kosky)如何運用極簡的舞臺配置,將布萊希特(Bertolt Brecht)的「疏離效果」轉化為視覺奇觀與黑色幽默,探討《三便士歌劇》在當代劇場中的新詮釋,並藉由舞臺、燈光、服裝、音樂等多方面,分析該作如何在保留批判核心的同時,觸及觀眾的觀看位置與人性幽微。
Thumbnail
本文分析導演巴里・柯斯基(Barrie Kosky)如何運用極簡的舞臺配置,將布萊希特(Bertolt Brecht)的「疏離效果」轉化為視覺奇觀與黑色幽默,探討《三便士歌劇》在當代劇場中的新詮釋,並藉由舞臺、燈光、服裝、音樂等多方面,分析該作如何在保留批判核心的同時,觸及觀眾的觀看位置與人性幽微。
Thumbnail
5 月將於臺北表演藝術中心映演的「2026 北藝嚴選」《海妲・蓋柏樂》,由臺灣劇團「晃晃跨幅町」製作,本文將以從舞台符號、聲音與表演調度切入,討論海妲・蓋柏樂在父權社會結構下的困境,並結合榮格心理學與馮.法蘭茲對「阿尼姆斯」與「永恆少年」原型的分析,理解女人何以走向精神性的操控、毀滅與死亡。
Thumbnail
5 月將於臺北表演藝術中心映演的「2026 北藝嚴選」《海妲・蓋柏樂》,由臺灣劇團「晃晃跨幅町」製作,本文將以從舞台符號、聲音與表演調度切入,討論海妲・蓋柏樂在父權社會結構下的困境,並結合榮格心理學與馮.法蘭茲對「阿尼姆斯」與「永恆少年」原型的分析,理解女人何以走向精神性的操控、毀滅與死亡。
Thumbnail
切換頁面卡卡有很多種原因,這裡舉的例子只針對元件太大的情境。 除了想辦法拆分外,還有一個方法就是利用 Vue 的 Async Component。
Thumbnail
切換頁面卡卡有很多種原因,這裡舉的例子只針對元件太大的情境。 除了想辦法拆分外,還有一個方法就是利用 Vue 的 Async Component。
Thumbnail
這是一場修復文化與重建精神的儀式,觀眾不需要完全看懂《遊林驚夢:巧遇Hagay》,但你能感受心與土地團聚的渴望,也不急著在此處釐清或定義什麼,但你的在場感受,就是一條線索,關於如何找著自己的路徑、自己的聲音。
Thumbnail
這是一場修復文化與重建精神的儀式,觀眾不需要完全看懂《遊林驚夢:巧遇Hagay》,但你能感受心與土地團聚的渴望,也不急著在此處釐清或定義什麼,但你的在場感受,就是一條線索,關於如何找著自己的路徑、自己的聲音。
Thumbnail
浮動(float)是早期用來創建佈局的技術。元素可以向左或向右浮動,旁邊的元素會環繞浮動元素。浮動元素通常用於圖文混排或簡單的兩欄佈局。
Thumbnail
浮動(float)是早期用來創建佈局的技術。元素可以向左或向右浮動,旁邊的元素會環繞浮動元素。浮動元素通常用於圖文混排或簡單的兩欄佈局。
Thumbnail
各位使用 Vue.js 開發的小夥伴們,你們都怎麼實作父子層組件資料的雙向綁定呢?如果你還在寫 prop + emit 的話,不妨進來看看吧。
Thumbnail
各位使用 Vue.js 開發的小夥伴們,你們都怎麼實作父子層組件資料的雙向綁定呢?如果你還在寫 prop + emit 的話,不妨進來看看吧。
Thumbnail
背景:從冷門配角到市場主線,算力與電力被重新定價   小P從2008進入股市,每一個時期的投資亮點都不同,記得2009蘋果手機剛上市,當時蘋果只要在媒體上提到哪一間供應鏈,隔天股價就有驚人的表現,當時光學鏡頭非常熱門,因為手機第一次搭上鏡頭可以拍照,也造就傳統相機廠的殞落,如今手機已經全面普及,題
Thumbnail
背景:從冷門配角到市場主線,算力與電力被重新定價   小P從2008進入股市,每一個時期的投資亮點都不同,記得2009蘋果手機剛上市,當時蘋果只要在媒體上提到哪一間供應鏈,隔天股價就有驚人的表現,當時光學鏡頭非常熱門,因為手機第一次搭上鏡頭可以拍照,也造就傳統相機廠的殞落,如今手機已經全面普及,題
Thumbnail
先前提到 Quasar 的 Dialog Plugin 很好用,再讓我補充一個用法。
Thumbnail
先前提到 Quasar 的 Dialog Plugin 很好用,再讓我補充一個用法。
Thumbnail
※ 視圖模板 視圖模板(View Templates) 是在 MVC 架構中負責展示數據的 HTML 文件,包含模板語法,用於在渲染時插入實際數據。它們的主要目的是分離數據與展示邏輯,讓代碼更加模塊化和易於維護。 視圖模板設計和使用的核心理念,就是「重複的事情不要重複做、效益最大化、有效利用資源
Thumbnail
※ 視圖模板 視圖模板(View Templates) 是在 MVC 架構中負責展示數據的 HTML 文件,包含模板語法,用於在渲染時插入實際數據。它們的主要目的是分離數據與展示邏輯,讓代碼更加模塊化和易於維護。 視圖模板設計和使用的核心理念,就是「重複的事情不要重複做、效益最大化、有效利用資源
Thumbnail
一般在使用 TypeScript 的時候,大家都有遇過定義列舉資料的情境吧。 不過不管是 enum 和 literal 的方式其實都有些小缺點,以下推薦一個個人認為體驗更好的方式。
Thumbnail
一般在使用 TypeScript 的時候,大家都有遇過定義列舉資料的情境吧。 不過不管是 enum 和 literal 的方式其實都有些小缺點,以下推薦一個個人認為體驗更好的方式。
Thumbnail
如何在 Vite 專案中安裝和設置 TypeScript 及路徑別名的步驟,包括安裝必要的依賴、配置 vite.config.js、tsconfig.json 的設置,及如何創建類型聲明文件來正確識別 .vue 文件。
Thumbnail
如何在 Vite 專案中安裝和設置 TypeScript 及路徑別名的步驟,包括安裝必要的依賴、配置 vite.config.js、tsconfig.json 的設置,及如何創建類型聲明文件來正確識別 .vue 文件。
Thumbnail
因為最近想嘗試編碼風格,於是就選了一套比較"不嚴格"的輔助工具來摸索。 編輯器 VS CODE 框架 VUE3 打包工具 VITE 編碼風格 Standard 環境 version { "nodejs":"v18.18.0", "npm":"9.8.1" }
Thumbnail
因為最近想嘗試編碼風格,於是就選了一套比較"不嚴格"的輔助工具來摸索。 編輯器 VS CODE 框架 VUE3 打包工具 VITE 編碼風格 Standard 環境 version { "nodejs":"v18.18.0", "npm":"9.8.1" }
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News