LineChartView 交互與細部設定

春麗 S.T.E.M.
13 min readOct 17, 2023

--

目錄

⦿ 交互
⦿ 雙擊放大、拖動
⦿ 僅 X 軸向放大
⦿ 慣性與摩擦係數
⦿ 加入第二項或更多數據
⦿ 創造更真實的數據
⦿ Data Entry
⦿ Data Set
⦿ 調整細節
⦿ 圓角邊框
⦿ 不顯示座標軸數據
⦿ 不顯示框線
⦿ 不顯示格線
⦿ 格線變虛線
⦿ UX 呈現

交互
User Interaction,交互的意思是使用者做了某項操作,頁面上無論是元件或是 UI 呈現都會有相應的變化,這在現代裝置上是相當重要的概念,比方說你輸入密碼後,按下登入,畫面卻卡在那邊,使用者可能會以為 APP 卡住或當機,接著滑掉 APP,重新進到登入頁面,但很可能會遇上相同狀況,所以設計流程上,登入驗證需花上一小段時間,比方體感超過兩秒,我們會希望有一個進度條在跑動,讓使用者知道後台可能有一些動作正在進行,即是說 UX 是 APP 中不可或缺的一環。

雙擊放大、拖動

接著,我們來看看 LineChartView 中的交互設置:

lineChartView.doubleTapToZoomEnabled = true
lineChartView.dragEnabled = true

在 DGCharts 裡有兩個基本的交互設置,點兩下放大以及拖動,兩者預設 true,當數據產生後,如果這些數據可以一張圖呈現,我們就會選擇一張圖呈現,前一篇文章產生的 DataEntries,x 有 20 個,而 y 在 50 內,在講 DataEntries 時,有說過 xMaxxMinyMaxyMinleftAxisMaxleftAxisMinrightAxisMaxrightAxisMin 這些重要屬性。

這些屬性是幫助 DGCharts 定下呈現的範圍,使用 DGChart 這個套件都會用到,先看到如下:

y 在 50 內,當你雙擊放大後,X、Y 就會有可拖動的範圍,不過你再雙擊又會再放大,似無止盡地放大下去,變成無意義的操作。

而如果數據確定在某個範圍內,比方說 PM2.5 固定在 500 內,x 是時間(1 min)、y 是 PM2.5 的值(μg/m3),一個小時的數據產生,呈現在畫面上將是擠在一起的,但 Y的範圍是一覽無遺,此時你會希望放大的是 X,所以我們可以鎖定 Y 不放大。

僅 X 軸向放大

        lineChartView.doubleTapToZoomEnabled = true
lineChartView.scaleYEnabled = false
lineChartView.dragEnabled = true

加入了 lineChartView.scaleYEnabled = false,結果如下:

就可讓每分鐘的數據看得更清楚。

慣性與摩擦係數

接著,加入下面的程式碼:

lineChartView.dragDecelerationEnabled = true
lineChartView.dragDecelerationFrictionCoef = 1.0

調整拖動時的慣性效果及摩擦係數,數字越小慣性越不明顯,結果如下:

左圖,在摩擦係數設置為 1 慣性相當明顯,沒有停下來的跡象,右圖,設置為 0.1 時,很快就停下來了。

繼續閱讀|回目錄

加入第二項或更多數據

現在,我們加入第二項數據,同樣是一小時內,所以 x 有 60 個,y 要呈現 PM10 的值,先將它設置在 350 內,結果如下:

一個為 350 內,PM 10 呈現的數值;一個為 500 內,PM 2.5 呈現的數值擺在一起就是這樣,也可以清楚辨識哪條線是 PM 10,哪條線是 PM 2.5,不過,這並不是那麼有趣貼近現實,接下來,我們試著再加入其他數據,呈現比較有趣的畫面吧!

創造更真實的數據

首先,我們希望有一個 Model,這個 Model 是用來呈現空氣數值的基底,它來源可能是政府網站(打 API),也可能是空污偵測器(透過 BLE 或 WiFi 連接),如下:

struct AirData {
let pm10: Int
let pm25: Int
let pm1: Int
let voc: Int
let timestamp: Date
}

PM 10 指的是某個粒徑以下的微粒數量(濃度),這即是說 PM 10 其實包括影響 PM 2.5 及 PM 1 的微粒。

而光學偵測器的原理為控制雷射光波大小,當光打到微粒上造成兩種不同的散射機制(米氏散射瑞利散射,通常不分析拉曼散射),再去估算單位體積內約有多少微粒,以此來判斷空氣汙染程度。

而 VOC 的估量更是各家標準皆不一,揮發性氣體相當多,烷類、芳烴類⋯⋯等,各類氣體濃度影響人體程度也不一,似乎沒有一個泛用標準說 VOC 超過多少將對人體造成危害,比方說對人類有害的甲醛可能要超過某個量才會對人體造成危害,但這個量卻在另個相對影響不大的氣體卻是相當少量,且屋內甲醛的分佈也不一(除了新家買傢俱),所以我們很難去訂定一個泛用標準說 VOC 多少是不好的。

接著,我們還需要時間戳,若在 viewDidLoad 中產生這個空氣數據,我們可以寫一個 Function 再去 Call 它,如下:

func generateAirData() {
let currentDate = Date()
for hour in 0..<24 {
let pm10 = Int.random(in: 3...10)
let pm25 = Int.random(in: 1...10)
let pm1 = Int.random(in: 5...10)
let voc = Int.random(in: 1...5)

let timestamp = Calendar.current.date(byAdding: .hour,
value: -hour,
to: currentDate)!

airData.append(AirData(pm10: pm10, pm25: pm25, pm1: pm1,
voc: voc, timestamp: timestamp))
}
}

產生 24 小時的每小時各種空汙平均數據,共有 24 筆,每筆都有一個時間戳,此外,在 IoT 領域中,空汙偵測器希望直接連動清淨機,讓這個運作的系統在偵測到空汙超過某個數值時,自動開啟清淨機,所以我們還需要清淨機的開關或風速檔次的數據,如下:

var purifierData: [PurifierData] = []
func generatePufifierData() {
let currentDate = Date()
for minute in stride(from: 0, to: 24, by: 1) {
// 隨機生成開或關的狀態
let power = Bool.random() ? "on" : "off"

// 從當前時間減去分鐘數來創建時間戳
let timestamp = Calendar.current.date(byAdding: .minute, value: -minute, to: currentDate)!

// 添加到陣列
purifierData.append(PurifierData(power: power, timestamp: timestamp))
}
}

Data Entry

接著,我們還需要把這些 Data 轉成 Chart 的 Data Entry,如下:

var dataEntries = [ChartDataEntry]()
for (index, airDatum) in airData.enumerated() {
let entry = ChartDataEntry.init(x: Double(index + 1), y: Double(airDatum.pm25))
dataEntries.append(entry)
}

var dataEntriesTwo = [ChartDataEntry]()
for (index, airDatum) in airData.enumerated() {
let entry = ChartDataEntry.init(x: Double(index + 1), y: Double(airDatum.pm10))
dataEntriesTwo.append(entry)
}

var dataEntriesThree = [ChartDataEntry]()
for (index, apDatum) in purifierData.enumerated() {
var y = 0
if apDatum.power == "on" {
y = 18
} else if apDatum.power == "off" {
y = 16
}
let entry = ChartDataEntry.init(x: Double(index + 1), y: Double(y))
dataEntriesThree.append(entry)
}

空汙數值希望用一般折線表示,但是清淨機開關希望用階梯狀來呈現,但想要呈現階梯狀仍須有數值,所以將 on、off 的狀態,轉換成一般空汙不會觸及的較高數值,off 在 16,on 在 18。

Data Set

所以在設定 Data Set 的時候,如下:

        let dataSet = LineChartDataSet(entries: dataEntries, label: "")
let dataSetTwo = LineChartDataSet(entries: dataEntriesTwo, label: "")
let dataSetThree = LineChartDataSet(entries: dataEntriesThree, label: "")
dataSetThree.mode = .stepped

label 設定為空,就不會在下方顯示折線的數據是什麼,如 PM 2.5,這一點是比較麻煩,如果你直接沒設定,下方會出現 DataSet,如下:

Chart View Data

最後再設定 Chart Data,如下:

        let chartData = LineChartData(dataSets: [dataSet, dataSetTwo, dataSetThree])
lineChartView.data = chartData

如此一來,生成 Chart View 的時候就會照著你的數據呈現你想要的圖形。

繼續閱讀|回目錄

調整細節

圓角邊框

如果要呈現這樣一個圖形,我們會把 Chart View 放到另一個 UIView 裡面,再分別去設定 Chart View 與 SuperView 的 Margin 距離多少,這樣若要幫 Chart View 加上圓角邊框也是相當方便,如下:

self.view.addSubview(charView)
self.charView.addSubview(lineChartView)
charView.layer.masksToBounds = true
charView.layer.cornerRadius = 20

不顯示座標軸數據

        // 不顯示座標軸數據
lineChartView.leftAxis.drawLabelsEnabled = false
lineChartView.rightAxis.drawLabelsEnabled = false
lineChartView.xAxis.drawLabelsEnabled = false

取消左邊 Y 軸上及右邊 Y 軸上的數據,還有上方 X 軸上的數據。

不顯示框線

        // 不顯示框線
lineChartView.leftAxis.drawAxisLineEnabled = false
lineChartView.rightAxis.drawAxisLineEnabled = false
lineChartView.xAxis.drawAxisLineEnabled = false

不顯示 X 格線

        // 不顯示 X 格線
lineChartView.leftAxis.drawGridLinesEnabled = false
lineChartView.rightAxis.drawGridLinesEnabled = false

格線變虛線

        // 不顯示 Y 格線
// lineChartView.xAxis.drawGridLinesEnabled = false
// 將 Y 格線變成虛線
lineChartView.xAxis.gridLineDashLengths = [5.0, 5.0]

外觀細部加上前篇文章,基礎已完成,不過,在畫圖時還有許多呈現技巧,比方說當 X 軸線透過雙擊放大,我們希望一鍵回到今日一般呈現數據;或者是當下觸碰點位的數值希望能夠記憶下來,加上在呈現多種數據圖形時,要能有相同的縮放,例如我們希望比對同一時間的 PM 2.5、PM 10 的差別。

但這些牽涉到大量實作,後面有機會再來分享。

繼續閱讀|回目錄

UX 呈現

任何牽涉到 UI 的部份,自然也跟 UX 脫離不了關係,最後來講講 UX 的部份,我們先看到下方 UI:

其實左圖的 UI 通常不是用來呈現我們一般希望看到的數值圖,因為它只能夠看出趨勢,沒錯!趨勢也有其存在的必要性,我們可以這樣設計,一個房間裡面裝了許多設備為了降低空氣污染而努力,比方說有多台空汙偵測器,多台清淨機

那麼,在點入這間房之前,我希望看到一個大方向的這間房間空汙變化,所以可能是一次呈現多種空氣數值,如 PM 2.5PM 10 的變化,以及清淨機的風速或何時、何時,這樣的趨勢,並不需要清楚的數值呈現,所以拿掉一些不必要的東西,讓畫面更加簡潔。

可以看到 Chart 的邊框消失,以及 Y 軸簡單分段,並去掉 X 軸的格線,還有各種數值呈現出來的折線圖色彩樣式的變化。

但為什麼需要這個趨勢呢?趨勢自然是為了讓我們理解大方向,最後點進這個趨勢圖,我們可以看到呈現的細節,上方右圖是點進去後的濃度折線圖,我們可以看到 X 軸是每分鐘呈現,Y 軸恰恰是各種空汙指標的真實數值

試著想想看,如果這個機器是每三秒偵測一次數據,我們卻不一定要三秒一次就呈現,圖表呈現時,其實只要抓每分鐘的平均數值就很不錯了,當然,若想要看到即時數據,就得再另外做出讀取空汙數值的畫面。這時可能是透過藍芽連接,也可能透過 WiFi(內網)連接,也可能需要 Call API 去拿到,很多時候是藍芽方便,但在其他情境也會用到 WiFi伺服器請求

好了!這次就分享到這,感謝您的閱讀。

繼續閱讀|回目錄

附上 GitHub:

--

--

春麗 S.T.E.M.
春麗 S.T.E.M.

Written by 春麗 S.T.E.M.

Do not go gentle into that good night, Old age should burn and rave at close of day; Rage, rage, against the dying of the light.

No responses yet