這幾天學習下《演算法》的排序章節,具體見對排序的總結,想著做點東西,能將各種排序演算法的排序過程使用Rxjs通過視覺化的方式展示出來,正好練系一下Rxjs的使用
本文不會太多介紹Rxjs的基本概念,重點介紹如何用響應式程式設計的思想來實現功能
效果圖
需求
頁面中包括一個隨機生成300個數字的按鈕和、一個選擇不同排序演算法的下拉選單和一個echart渲染的容器元素
點選按鈕會隨機生成300個隨機數,同時頁面渲染出300個數的柱狀圖,然後選擇一種排序演算法後,頁面開始展示排序過程,在排序過程中如果我們切換成另一種排序演算法,會停止當前演算法的視覺化展示,轉而開始新的排序演算法的視覺化展示
思路
要展示出排序演算法在排序過程中陣列中資料的變化,我們要定期儲存一下排序過程中當前陣列的快照
,然後通過echart展示當前陣列的資料,重複這個過程直到排序完成,我們也就有了表示排序過程的一個動畫展示
具體實現
在Rxjs中,一切皆是流,要實現這個功能,重要的是確定好資料流,以及資料流在未來一段時間內的變化過程
根據頁面,可以清晰的確定幾個資料流
按鈕點選操作生成的資料流
const createNumber$ = Rx.Observable.fromEvent(query('.numberCreator'), 'click')複製程式碼
切換下拉選單生成的資料流
const select$ = Rx.Observable.fromEvent(query('.sortTypes'), 'change')複製程式碼
點選按鈕生成隨機陣列並渲染echart圖表很顯然就用到map和do這兩個operator
createNumber$
.map(e => {
return numberCreator()
})
.do(nums => {
const option = getOption(nums)
echartInstance.setOption(option)
})複製程式碼
切換下拉選單時我們要得到當前選擇的排序演算法的一個標識
let currentType
select$
.map(e => e.target)
.map(x => x.options[x.selectedIndex].value)
.map(type => {
return {
type,
timer:1
}
})
.do(x => {
currentType = x.type
})複製程式碼
下面是重點
只點選按鈕或者只切換下拉頁面都不應該展示排序過程,只有當兩個事件流都觸發了,並且之後某一個再次觸發的時候才會渲染排序過程的動畫,所以我們需要combineLatest
操作符,將兩個資料流合併成一個
const combine$=Rx.Observable.combineLatest(
createNumber$,
select$
)複製程式碼
現在在combine$資料流中我們就有個隨機陣列和排序型別
[Array[300],'1']
然後就應該排序演算法進行工作了,這裡思考一下
[] 怎樣來生成我們排序演算法排序過程中資料的快照?
[] 生成的資料快照什麼時候讓echart來渲染?
對於第一點,我們需要將排序演算法封裝成一個自定義的operator
,在排序過程中不斷next() 資料快照,
到這裡我們的資料流就變成能在未來一段時間內不斷生成新Value的一個資料流
Rx.Observable.prototype.sort = function () {
const input = this
return Rx.Observable.create((observer) => {
input.subscribe((arr) => {
const nums = clone(arr[0])
const select = arr[1]
const sortMethod = sortTypes[select.type]
sortMethod(nums, function (arr) {
observer.next({
nums: JSON.parse(JSON.stringify(arr)),
select
})
}, error => {
observer.error(error)
})
}, )
})
}
combine$.sort()複製程式碼
對於第二點,因為排序演算法是非常快的,如果我們subscibe sort()操作符產生的新值就開始渲染echart,頁面上是看不出動畫效果的,所以,我們需要延遲echart渲染圖表的過程
,我們需要將sort()觸發的值轉變成一個非同步的新事件流並打平到原資料流中
combine$
.sort()
.flatMap(obj => {
return Rx.Observable.of(obj).delay(100 * obj.select.timer++)
})複製程式碼
注意obj.select.timer++
,對於sort()前後觸發的兩個值,為了展示出echart渲染的動畫,我們要給它們渲染的時間依次遞增
到這一步,我們的單次功能就能正常進行了,但如果在一個排序動畫過程還沒有結束,我們又點選了一個新的排序型別
,則新舊兩次的還在序列
中沒進行的渲染都會依次進行,干擾echart渲染的效果,所以在切換到新的型別時,我們要過濾序列中的值。
combine$
.sort()
.flatMap(obj => {
return Rx.Observable.of(obj).delay(100 * obj.select.timer++)
})
.filter(x => {
return x.select.type == currentType
})
.do(x => {
const option = getOption(x.nums)
echartInstance.setOption(option)
})
.subscribe(() => { }, null, () => {
console.log('complete')
})複製程式碼
整個資料流序列
-createNumber$---------------------------------------------------------------------------------
---------------select$-------------------------------------------------------------------------
combineLatest()
---------------------------combine$------------------------------------------------------------
sort()
---------------------------v1 v2 v3 v4 .......v11 v22 v33----------
flatMap()
---------------------------delay1 delay2 delay3 delay4 ....delay11 delay22 delay33--------
filter(currentType==type)
---------------------------delay1 delay2 delay11 delay22 delay33--------------------------複製程式碼