誰喜歡圖表?每個人都喜歡,對吧?創建圖表的方法有很多,包括許多庫。例如D3.js、Chart.js、amCharts、Highcharts 和Chartist,這只是眾多選項中的一小部分。
但是我們並不一定需要圖表庫來創建圖表。 Mobx-state-tree (MST) 是Redux 的一種直觀替代方案,用於管理React 中的狀態。我們可以使用簡單的SVG 元素構建交互式自定義圖表,並使用MST 來管理和操作圖表的數據。如果您過去曾嘗試使用D3.js 等工具構建圖表,我認為您會發現這種方法更直觀。即使您是經驗豐富的D3.js 開發人員,我仍然認為您會對MST 作為可視化數據架構的強大功能感興趣。
以下是MST 用於驅動圖表的示例:
此示例使用D3 的比例函數,但圖表本身只是使用JSX 中的SVG 元素渲染的。我不知道任何圖表庫具有閃爍倉鼠點的選項,因此這是一個很好的例子,說明為什麼構建自己的圖表很棒——而且它並不像您想像的那麼難!
我已經使用D3 構建圖表超過10 年了,雖然我喜歡它的強大功能,但我總是發現我的代碼最終會變得笨拙且難以維護,尤其是在處理複雜的可視化時。 MST 通過提供一種優雅的方式將數據處理與渲染分離,徹底改變了這一切。我希望這篇文章能夠鼓勵您嘗試一下。
熟悉MST 模型
首先,讓我們快速概述一下MST 模型的外觀。這不是關於MST 的所有內容的深入教程。我只想展示基礎知識,因為實際上,您在90% 的時間裡只需要這些基礎知識。
下面是一個Sandbox,其中包含使用MST 構建的簡單待辦事項列表的代碼。快速瀏覽一下,然後我將解釋每個部分的作用。
首先,對象的形狀是用模型屬性的類型化定義來定義的。簡單來說,這意味著待辦事項模型的實例必須有一個標題,該標題必須是字符串,並且默認情況下“done”屬性為false。
<code>.model("Todo", { title: types.string, done: false //这相当于types.boolean,默认为false })</code>
接下來,我們有視圖和操作函數。視圖函數是根據模型中的數據訪問計算值的方法,而不會更改模型保存的數據。您可以將它們視為只讀函數。
<code>.views(self => ({ outstandingTodoCount() { return self.todos.length - self.todos.filter(t => t.done).length; } }))</code>
另一方面,操作函數允許我們安全地更新數據。這始終以非可變的方式在後台完成。
<code>.actions(self => ({ addTodo(title) { self.todos.push({ id: Math.random(), title }); } }));</code>
最後,我們創建一個新的存儲實例:
<code>const todoStore = TodoStore.create({ todos: [ { title: "foo", done: false } ] });</code>
為了展示存儲器的實際作用,我添加了一些控制台日誌來顯示在觸發Todo 的第一個實例的toggle 函數之前和之後outStandingTodoCount() 的輸出。
<code>console.log(todoStore.outstandingTodoCount()); // 输出:1 todoStore.todos[0].toggle(); console.log(todoStore.outstandingTodoCount()); // 输出:0</code>
如您所見,MST 為我們提供了一種數據結構,使我們可以輕鬆地訪問和操作數據。更重要的是,它的結構非常直觀,代碼一目了然——沒有reducer!
讓我們創建一個React 圖表組件
好的,現在我們已經了解了MST 的外觀,讓我們使用它來創建一個存儲器,用於管理圖表的數據。不過,我們將從圖表JSX 開始,因為一旦知道需要哪些數據,構建存儲器就容易得多。
讓我們看看渲染圖表的JSX。
首先要注意的是,我們正在使用styled-components 來組織我們的CSS。如果您不熟悉它,Cliff Hall 有一篇很棒的文章展示了它在React 應用程序中的使用。
首先,我們正在渲染將更改圖表坐標軸的下拉菜單。這是一個相當簡單的HTML 下拉菜單,包裝在一個styled 組件中。需要注意的是,這是一個受控輸入,其狀態使用我們模型中的selectedAxes 值設置(稍後我們將介紹這一點)。
<code>model.setSelectedAxes(parseInt(e.target.value, 10)) } defaultValue={model.selectedAxes} ></code>
接下來是圖表本身。我已經將坐標軸和點拆分為它們自己的組件,這些組件位於單獨的文件中。通過保持每個文件簡潔,這確實有助於保持代碼的可維護性。此外,這意味著如果我們想使用折線圖而不是點,我們可以重用坐標軸。這在處理具有多種圖表類型的大型項目時非常有效。它還使單獨測試組件變得很容易,無論是在編程上還是在活動樣式指南中手動測試。
<code>{model.ready ? (</code> <code>{model.ready ? (</code> <axes xlabel="{xAxisLabels[model.selectedAxes]}" xticks="{model.getXAxis()}" ylabel="{yAxisLabels[model.selectedAxes]}" yticks="{model.getYAxis()}"></axes><points points="{model.getPoints()}"></points> ) : ( <loading></loading> )}
嘗試在上面的Sandbox 中註釋掉axes 和points 組件以查看它們如何獨立工作。
最後,我們將組件包裝在一個observer 函數中。這意味著模型中的任何更改都將觸發重新渲染。
<code>export default observer(HeartrateChart);</code>
讓我們看看Axes 組件:
如您所見,我們有一個XAxis 和一個YAxis。每個都有一個標籤和一組刻度標記。我們稍後將介紹如何創建標記,但在這裡您應該注意,每個坐標軸都由一組刻度組成,這些刻度是通過遍歷一組對像生成的,這些對象具有標籤以及x 或y 值(取決於我們正在渲染哪個坐標軸)。
嘗試更改元素的一些屬性值,看看會發生什麼……或者會中斷什麼!例如,將YAxis 中的線元素更改為以下內容:
<code><line x1="{30}" x2="95%" y1="{0}" y2="{y}"></line></code>
學習如何使用SVG 構建可視化效果的最佳方法就是簡單地進行實驗並破壞事物。 ?
好的,那是圖表的一半。現在讓我們看看Points 組件。
圖表上的每個點都由兩部分組成:一個SVG 圖像和一個圓形元素。圖像是動物圖標,圓圈提供將鼠標懸停在圖標上時可見的脈衝動畫。
嘗試註釋掉圖像元素,然後註釋掉圓形元素,看看會發生什麼。
這次模型必須提供一個點對像數組,該數組為我們提供四個屬性:用於在圖表上定位點的x 和y 值、點的標籤(動物的名稱)和脈衝,這是每個動物圖標的脈衝動畫的持續時間。希望這一切看起來都直觀且合乎邏輯。
同樣,嘗試修改屬性值以查看哪些更改和中斷。您可以嘗試將圖像的y 屬性設置為0。相信我,這比閱讀SVG 圖像元素的W3C 規範要容易得多!
希望這能讓您了解我們在React 中如何渲染圖表。現在,只需要創建一個具有適當操作的模型來生成我們在JSX 中循環所需的數據即可。
創建我們的存儲器
這是存儲器的完整代碼:
我將代碼分解成前面提到的三個部分:
- 定義模型的屬性
- 定義操作
- 定義視圖
定義模型的屬性
我們在這裡定義的所有內容都可以從外部作為模型實例的屬性訪問,並且——如果使用可觀察的包裝組件——對這些屬性的任何更改都將觸發重新渲染。
<code>.model('ChartModel', { animals: types.array(AnimalModel), paddingAndMargins: types.frozen({ paddingX: 30, paddingRight: 0, marginX: 30, marginY: 30, marginTop: 30, chartHeight: 500 }), ready: false, // 表示types.boolean,默认为false selectedAxes: 0 // 表示types.number,默认为0 })</code>
每種動物都有四個數據點:名稱(Creature)、壽命(Longevity__Years_)、重量(Mass__grams_) 和靜息心率(Resting_Heart_Rate__BPM_)。
<code>const AnimalModel = types.model('AnimalModel', { Creature: types.string, Longevity__Years_: types.number, Mass__grams_: types.number, Resting_Heart_Rate__BPM_: types.number });</code>
定義操作
我們只有兩個操作。第一個(setSelectedAxes) 在更改下拉菜單時調用,它更新selectedAxes 屬性,該屬性反過來決定使用哪些數據來渲染坐標軸。
<code>setSelectedAxes(val) { self.selectedAxes = val; },</code>
setUpScales 操作需要更多解釋。此函數在圖表組件掛載後立即在useEffect hook 函數中調用,或在窗口大小調整後調用。它接受一個對象,其中包含包含該元素的DOM 的寬度。這使我們可以為每個坐標軸設置比例函數以填充完整的可用寬度。我將很快解釋比例函數。
為了設置比例函數,我們需要計算每種數據類型的最大值,因此我們首先遍歷動物來計算這些最大值和最小值。我們可以將零用作我們想要從零開始的任何比例的最小值。
<code>// ... self.animals.forEach( ({ Creature, Longevity__Years_, Mass__grams_, Resting_Heart_Rate__BPM_, ...rest }) => { maxHeartrate = Math.max( maxHeartrate, parseInt(Resting_Heart_Rate__BPM_, 10) ); maxLongevity = Math.max( maxLongevity, parseInt(Longevity__Years_, 10) ); maxWeight = Math.max(maxWeight, parseInt(Mass__grams_, 10)); minWeight = minWeight === 0 ? parseInt(Mass__grams_, 10) : Math.min(minWeight, parseInt(Mass__grams_, 10)); } ); // ...</code>
現在設置比例函數!在這裡,我們將使用D3.js 的scaleLinear 和scaleLog 函數。在設置這些函數時,我們指定域,它是函數可以預期的最小和最大輸入,以及範圍,它是最大和最小輸出。
例如,當我使用maxHeartrate 值調用self.heartScaleY 時,輸出將等於marginTop。這是有道理的,因為這將位於圖表的頂部。對於壽命屬性,我們需要有兩個比例函數,因為此數據將顯示在x 軸或y 軸上,具體取決於選擇的哪個下拉選項。
<code>self.heartScaleY = scaleLinear() .domain([maxHeartrate, minHeartrate]) .range([marginTop, chartHeight - marginY - marginTop]); self.longevityScaleX = scaleLinear() .domain([minLongevity, maxLongevity]) .range([paddingX marginY, width - marginX - paddingX - paddingRight]); self.longevityScaleY = scaleLinear() .domain([maxLongevity, minLongevity]) .range([marginTop, chartHeight - marginY - marginTop]); self.weightScaleX = scaleLog() .base(2) .domain([minWeight, maxWeight]) .range([paddingX marginY, width - marginX - paddingX - paddingRight]);</code>
最後,我們將self.ready 設置為true,因為圖表已準備好渲染。
定義視圖
我們有兩組視圖函數。第一組輸出渲染坐標軸刻度所需的數據(我說我們會到那裡!)第二組輸出渲染點所需的數據。我們將首先查看刻度函數。
只有兩個從React 應用程序調用的刻度函數:getXAxis 和getYAxis。這些函數只是根據self.selectedAxes 的值返回其他視圖函數的輸出。
<code>getXAxis() { switch (self.selectedAxes) { case 0: return self.longevityXAxis; break; case 1: case 2: return self.weightXAxis; break; } }, getYAxis() { switch (self.selectedAxes) { case 0: case 1: return self.heartYAxis; break; case 2: return self.longevityYAxis; break; } },</code>
如果我們查看Axis 函數本身,我們可以看到它們使用比例函數的ticks 方法。這將返回適合坐標軸的一組數字。然後,我們遍歷這些值以返回坐標軸組件所需的數據。
<code>heartYAxis() { return self.heartScaleY.ticks(10).map(val => ({ label: val, y: self.heartScaleY(val) })); } // ...</code>
嘗試將ticks 函數的參數值更改為5,看看它如何影響圖表:self.heartScaleY.ticks(5)。
現在我們有了返回Points 組件所需數據的視圖函數。
如果我們查看longevityHeartratePoints(它返回“壽命與心率”圖的點數據),我們可以看到我們正在遍歷動物數組並使用適當的比例函數來獲取點的x 和y 位置。對於脈衝屬性,我們使用一些數學方法將心率的每分鐘跳動次數轉換為表示單個心跳以毫秒為單位的持續時間的數值。
<code>longevityHeartratePoints() { return self.animals.map( ({ Creature, Longevity__Years_, Resting_Heart_Rate__BPM_ }) => ({ y: self.heartScaleY(Resting_Heart_Rate__BPM_), x: self.longevityScaleX(Longevity__Years_), pulse: Math.round(1000 / (Resting_Heart_Rate__BPM_ / 60)), label: Creature }) ); },</code>
在store.js 文件的末尾,我們需要創建一個Store 模型,然後使用動物對象的原始數據實例化它。通常的做法是將所有模型附加到父Store 模型,然後可以根據需要通過頂層提供程序訪問這些模型。
<code>const Store = types.model('Store', { chartModel: ChartModel }); const store = Store.create({ chartModel: { animals: data } }); export default store;</code>
就是這樣!這是我們的演示:
這絕不是在JSX 中組織數據以構建圖表的唯一方法,但我發現它非常有效。我已經在實際環境中使用這種結構和堆棧為大型企業客戶構建了一個自定義圖表庫,並且對MST 在此目的中的出色表現感到震驚。我希望您也能有同樣的體驗!
以上是製作圖表?嘗試使用MOBX狀態樹為數據供電的詳細內容。更多資訊請關注PHP中文網其他相關文章!

喬納森·桑普森(Jonathan Sampson)的有趣研究,他觀看網絡請求瀏覽器,這是您第一次在新的安裝中啟動它,並且


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

Dreamweaver Mac版
視覺化網頁開發工具

PhpStorm Mac 版本
最新(2018.2.1 )專業的PHP整合開發工具

SublimeText3 英文版
推薦:為Win版本,支援程式碼提示!

DVWA
Damn Vulnerable Web App (DVWA) 是一個PHP/MySQL的Web應用程序,非常容易受到攻擊。它的主要目標是成為安全專業人員在合法環境中測試自己的技能和工具的輔助工具,幫助Web開發人員更好地理解保護網路應用程式的過程,並幫助教師/學生在課堂環境中教授/學習Web應用程式安全性。 DVWA的目標是透過簡單直接的介面練習一些最常見的Web漏洞,難度各不相同。請注意,該軟體中

mPDF
mPDF是一個PHP庫,可以從UTF-8編碼的HTML產生PDF檔案。原作者Ian Back編寫mPDF以從他的網站上「即時」輸出PDF文件,並處理不同的語言。與原始腳本如HTML2FPDF相比,它的速度較慢,並且在使用Unicode字體時產生的檔案較大,但支援CSS樣式等,並進行了大量增強。支援幾乎所有語言,包括RTL(阿拉伯語和希伯來語)和CJK(中日韓)。支援嵌套的區塊級元素(如P、DIV),