-
Notifications
You must be signed in to change notification settings - Fork 0
/
search.xml
1103 lines (1101 loc) · 219 KB
/
search.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>JAVA的转型与动态绑定</title>
<url>/2022/08/03/64294984be89/</url>
<content><![CDATA[<h3 id="上转型与下转型"><a href="#上转型与下转型" class="headerlink" title="上转型与下转型"></a>上转型与下转型</h3><p>想要理解java中的转型只需要记住一句话:</p>
<p><font color = "red" size="5">父类引用指向子类对象</font><br>这句话是什么意思呢?<br><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">Father</span> <span class="variable">f1</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Son</span>(); <span class="comment">// 这就叫 upcasting (向上转型)</span></span><br><span class="line"><span class="comment">// 现在 f1 引用指向一个Son对象</span></span><br><span class="line"></span><br><span class="line"><span class="type">Son</span> <span class="variable">s1</span> <span class="operator">=</span> (Son)f1; <span class="comment">// 这就叫 downcasting (向下转型)</span></span><br><span class="line"><span class="comment">// 现在f1 还是指向 Son对象</span></span><br></pre></td></tr></table></figure><br>但是以下代码就会出错:<br><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">Father</span> <span class="variable">f1</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Father</span>();</span><br><span class="line"><span class="type">Son</span> <span class="variable">s1</span> <span class="operator">=</span> (Son)f1;<span class="comment">//ERROR!!</span></span><br></pre></td></tr></table></figure><br>因为f1是父类的引用,指向了父类对象,父类对象不能够指向子类引用。</p>
<h3 id="上转型的特点"><a href="#上转型的特点" class="headerlink" title="上转型的特点"></a>上转型的特点</h3><ol>
<li>上转型对象不能操作子类新增加的成员变量,<strong>不能使用子类新增的方法</strong>。即为较子类B失去一些属性和功能,这些属性和功能是新增的。<ol>
<li>上转型对象可以操作子类继承或隐藏的成员变量,也<strong>可以使用子类继承的或重写的方法</strong>。即为上转型对象<strong>可以操纵父类原有的属性和功能,无论这些方法是否被重写</strong>。</li>
<li>上转型对象调用方法时,就是调用<strong>子类继承和重写过</strong>的方法。而不会是新增的方法,也不是父类原有的方法。</li>
<li>可以将对象的上转型对象再强制转换到一个子类对象<strong>(下转型)</strong>,强制转换过的对象具有子类所有属性和功能。</li>
</ol>
</li>
</ol>
<h3 id="上转型的优点"><a href="#上转型的优点" class="headerlink" title="上转型的优点"></a>上转型的优点</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">dosleep</span><span class="params">(Human h)</span> {</span><br><span class="line"> <span class="comment">//这里的Human h是传入父类,如果传入子类可以将其上转型为父类</span></span><br><span class="line"> h.sleep();</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>这里以父类为参数,调用有时用子类作为参数,就是利用了向上转型。这样使代码变得简洁。不然的话,如果 dosleep 以子类对象为参数,则有多少个子类就需要写多少个函数。这也体现了 JAVA 的抽象编程思想。</p>
<h3 id="动态绑定"><a href="#动态绑定" class="headerlink" title="动态绑定"></a>动态绑定</h3><p><strong>概念</strong><br>方法可以在沿着继承链的多个类中实现,子类可以重写父类的方法。JVM决定运行时调用哪个方法。这就是动态绑定。<br><strong>原理</strong><br>假设,对象o是类C1的实例,其中C1是C2的子类,C2是C3的子类,那么o也是C2,C3的实例。如果对象o调用一个方法p,JVM会依次在类C1,C2,C3中查找方法p的实现<strong>(首先看参数类型,找到最合适的参数类型。再在参数类型符合的类方法中按照C1,C2,C3的顺序来查找p)</strong>,直到找到为止。</p>
<p>这也是java中多态的一种体现形式<br><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">class 人{</span><br><span class="line"> 吃饭();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">class 程序员 extends 人{</span><br><span class="line"> 吃饭();</span><br><span class="line"> 编码();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">另外有个函数:</span><br><span class="line"></span><br><span class="line">void doSomething(人){</span><br><span class="line"> 一个人.编码();//这里就会编译出错,因为编译器只知道参数类型是人,而人的通用行为里只有吃饭,没有编码</span><br><span class="line"> 一个人.吃饭();//这里没有编译错,因为编译器知道人有吃饭的行为。具体的行为是程序员吃饭还是工人吃饭,就看传的是什么人了,靠运行时函数的晚绑定实现。</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p>
]]></content>
<categories>
<category>thought</category>
</categories>
<tags>
<tag>java</tag>
</tags>
</entry>
<entry>
<title>Pull Request完整过程【记一次给antvis/G6的PR】</title>
<url>/2023/06/08/aea9cc721cd4/</url>
<content><![CDATA[<h1 id="Pull-Request完整过程【记一次给antvis-G6的PR】"><a href="#Pull-Request完整过程【记一次给antvis-G6的PR】" class="headerlink" title="Pull Request完整过程【记一次给antvis/G6的PR】"></a>Pull Request完整过程【记一次给antvis/G6的PR】</h1><h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><p>G6正在进行<code>v4</code>到<code>v5</code>的版本升级,发了几个Issue Hunt,因为很喜欢G6,所以想尝试帮助完成一个矩形Item的迁移。在编写测试demo的过程中,发现了G6的一个严重bug。本文记录了我从发现bug,排查bug到给G6提PR,与仓库管理员沟通,最终PR被成功merge的过程。这是我给G6的第二个PR,给想要参与开源,为自己喜欢的项目贡献绵薄之力的朋友提供一套完整的贡献流程参考。附上两次Pull Request的链接</p>
<ul>
<li>Doc fix :<a href="https://github.com/antvis/G6/pull/4554">Fix issue#4552, another 404 not found and typo errors #4554</a></li>
<li>Bug fix :<a href="https://github.com/antvis/G6/pull/4608">Fix: “Node not found” error from ‘getNode()’#4608</a></li>
</ul>
<h3 id="Bug-重现"><a href="#Bug-重现" class="headerlink" title="Bug 重现"></a>Bug 重现</h3><p>这是我在编写测试demo时,发现G6中存在的一个bug。报错信息显示:<code>Node not found for id: 1</code>。</p>
<p><img src="https://raw.githubusercontent.com/zqqcee/img_repo/main/img/202306082202885.png" alt="image"></p>
<p>根据字面意思,某个方法收到了<code>id</code>为1的节点,但是在我传入的数据中并不存在这个节点。</p>
<h3 id="问题排查"><a href="#问题排查" class="headerlink" title="问题排查"></a>问题排查</h3><p>我在<code>Graph.getNode()</code>这个方法的前后调试了很久,更奇怪的是,直接调用<code>Graph.getNode(1)</code>居然是能够返回节点的。由于G6的代码中写的是:</p>
<figure class="highlight typescript"><table><tr><td class="code"><pre><span class="line"><span class="title class_">Object</span>.<span class="title function_">keys</span>(data).<span class="title function_">forEach</span>(<span class="function">(<span class="params">id</span>)=></span>{</span><br><span class="line"> <span class="comment">//...</span></span><br><span class="line"> <span class="keyword">const</span> innerModel = graphCore.<span class="title function_">getNode</span>(id);</span><br><span class="line"> <span class="comment">//...</span></span><br><span class="line">})</span><br><span class="line"></span><br></pre></td></tr></table></figure>
<p>我在getNode之前在控制台<code>console.log</code>了<code>id</code>, 控制台输出<code>1</code>。这就很奇怪了,我一度怀疑是<code>getNode</code>这个方法出现了问题,但是<code>getNode</code>是一个核心方法,应该不可能出错。</p>
<p>如果直接调用<code>Graph.getNode(1)</code> 能够返回节点,那么说明id就不是1,于是我输出了<code>id==1</code>,果不其然控制台输出<code>false</code>。进一步使用<code>typeof</code>查看<code>id</code>的类型,才发现id不知道怎么已经变成了<code>string</code>。原来使用<code>Object.keys()</code>生成的数组,无论<code>key</code>的类型是什么,统一生成为<code>string</code>数组。这个<code>bug</code>很严重啊,如果用户在数据中定义的<code>id</code>是<code>number</code>类型,那么将无法获取到这个<code>Node</code>。</p>
<p>到这里,问题就定位完毕了。</p>
<h3 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h3><p>其实要解决这个问题很简单,有以下三种方法:</p>
<ul>
<li>在用户的id为<code>number</code>类型时,使用<code>Number(id)</code>进行一次转换,而在用户<code>id</code>为<code>string</code>类型时,不做任何处理</li>
<li>修改<code>getNode()</code>,使他能够识别用户传入数据中<code>id</code>的类型</li>
<li>在文档中强制限制用户输入<code>string</code>类型,并且使用类型检查将用户输入的data限制为string</li>
</ul>
<p>三种方法首先排除第二种,因为<code>getNode()</code>是一个核心方法,是从<code>antv</code>的核心代码仓中<code>import</code>过来的一个方法,找不到修改的入口</p>
<p>我这里选择了第一种方法,并在PR中提示了,如果不做修改的话需要在文档中明确标注<code>id</code>必须为<code>string</code>类型</p>
<p>在解决这个问题的时候,我还考虑到了一种情况,如果用户比较调皮,<strong>输入的<code>id</code>中又有<code>string</code>类型,又有<code>number</code>类型</strong>应该怎么解决呢?这里我采用了添加<code>try-catch</code>代码块来进行解决。</p>
<h4 id="old-version"><a href="#old-version" class="headerlink" title="old version"></a>old version</h4><figure class="highlight typescript"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 'id' variable is always string in here, but one in user data is number, possibly. </span></span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">keys</span>(data).<span class="title function_">forEach</span>(<span class="function">(<span class="params">id</span>)=></span>{</span><br><span class="line"> <span class="keyword">const</span> innerModel = graphCore.<span class="title function_">getNode</span>(id);</span><br><span class="line"> <span class="keyword">const</span> relatedEdgeInnerModels = graphCore.<span class="title function_">getRelatedEdges</span>(id);</span><br><span class="line">})</span><br><span class="line"></span><br></pre></td></tr></table></figure>
<h4 id="new-version"><a href="#new-version" class="headerlink" title="new version"></a>new version</h4><figure class="highlight typescript"><table><tr><td class="code"><pre><span class="line"><span class="title class_">Object</span>.<span class="title function_">keys</span>(data).<span class="title function_">forEach</span>(<span class="function">(<span class="params">id</span>)=></span>{</span><br><span class="line"> <span class="keyword">let</span> innerModel</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> innerModel = graphCore.<span class="title function_">getNode</span>(id);</span><br><span class="line"> } <span class="keyword">catch</span> (e) {</span><br><span class="line"> innerModel = graphCore.<span class="title function_">getNode</span>(<span class="title class_">Number</span>(id))</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">let</span> relatedEdgeInnerModels;</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> relatedEdgeInnerModels = graphCore.<span class="title function_">getRelatedEdges</span>(id);</span><br><span class="line"> } <span class="keyword">catch</span> (error) {</span><br><span class="line"> relatedEdgeInnerModels = graphCore.<span class="title function_">getRelatedEdges</span>(<span class="title class_">Number</span>(id));</span><br><span class="line"> }</span><br><span class="line">})</span><br></pre></td></tr></table></figure>
<p>并且我在我的PR底下,做了如下说明,来询问这么做是否合理:</p>
<blockquote>
<p>An error is throwed from this function ‘getNode()’; msg: “Node not found for id: 1”<br>After testing, I found that ‘id’ variable is always string after <code>Object.keys(update).forEach((id)=>{...})</code>, but one in user data is number, possibly.<br>I tried adding a <code>try-catch</code> block to fix this bug, but <strong>it doesn’t seem very reasonable to do so</strong>. I guess you can restrict the user to set the <code>id</code> to a string type in the document, or force the <code>id</code> to a string type in the <code>transformer data</code> layer to avoid this error.</p>
<hr>
<p><code>getNode()</code>方法抛出了一个异常; 报错信息为:“找不到 id为1的节点“<br>经过测试,我发现<code>id</code>变量在<code>Object.keys(update).forEach((id)=>{...})</code>之后总是字符串类型,但是这个<code>id</code>变量在用户数据中很有可能是数字类型。<br>我尝试添加一个 <code>try-catch</code> 块来修复这个错误,但<strong>这样做似乎不太合理</strong>。 我想你们可以在文档中说明:限制 <code>id</code> 为字符串类型,或者在 <code>transformer data</code> 层强制将 <code>id</code> 设置为字符串类型来避免这个错误。</p>
</blockquote>
<h4 id="收到回复"><a href="#收到回复" class="headerlink" title="收到回复"></a>收到回复</h4><p>很快,我收到了仓库管理员<strong>十吾</strong>的回复,她回复了一个👍,我好开心,我问她这是可以接受的吗,如果是的话,需不需要重新创建一个PR来进行提交(因为我一开始提交的PR有其他修改,但是另外的修改无法被merge)。</p>
<p><img src="https://raw.githubusercontent.com/zqqcee/img_repo/main/img/202306082202026.png" alt="image-20230608213348021"></p>
<h3 id="PR提交完整过程"><a href="#PR提交完整过程" class="headerlink" title="PR提交完整过程"></a>PR提交完整过程</h3><p>这一部分记录完整的PR提交过程,其中包含了我遇到的问题,一并做陈述并给出解决方案。因为这是我第二次给开源仓库做贡献,所以一些看起来很简单的细节我也记录在这里,帮后面的同学少踩一些坑。</p>
<h4 id="fork仓库-amp-clone代码仓"><a href="#fork仓库-amp-clone代码仓" class="headerlink" title="fork仓库 & clone代码仓"></a>fork仓库 & clone代码仓</h4><p>直接fork,选仅fork默认分支即可。fork仓库后,在自己的github主页就能看到一个一摸一样的代码仓了。这一步注意,是要clone自己fork后的代码仓,比如我需要clone的地址是:<code>https://github.com/zqqcee/G6.git</code>,这里<code>zqqcee</code>是自己的用户名,不要clone错了。</p>
<h4 id="添加upstream"><a href="#添加upstream" class="headerlink" title="添加upstream"></a>添加upstream</h4><p>这一步的目的是将<code>antvis</code>的源仓库添加为上游仓库,不然我们无法同步它们的更新。运行:</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">git remote add upstream "https://github.com/antvis/G6.git"</span><br></pre></td></tr></table></figure>
<p>运行完毕后,输入<code>git remote -v</code> ,能够看到</p>
<blockquote>
<p>origin:xxxx<br>origin:xxxx<br>upstream:xxxx<br>upstream:xxxx</p>
</blockquote>
<h4 id="fetch-新分支"><a href="#fetch-新分支" class="headerlink" title="fetch 新分支"></a>fetch 新分支</h4><p>由于我是给<strong>v5分支</strong>提的PR,因此我需要先<strong>fetch v5分支</strong>。运行:</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">git fetch upstream/v5</span><br></pre></td></tr></table></figure>
<p>输入<code>git branch -a</code>就可以看到有一个红色的分支<code>upstream/v5</code>,这说明已经fetch成功了</p>
<p>下一步,我们就需要把这个分支的内容在本地创建,并进行修改。</p>
<h4 id="创建新分支"><a href="#创建新分支" class="headerlink" title="创建新分支"></a>创建新分支</h4><p>这一步在我执行的时候有一个很大的坑:我在master分支上直接运行:</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">git checkout -b v5</span><br><span class="line">git rebase xxx</span><br></pre></td></tr></table></figure>
<p>结果出了<strong>一堆冲突</strong>,后来才知道是<strong>我的v5分支是从master分支上创建的,而不是从远程拉过来的</strong>。</p>
<p>应该输入:</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">git checkout -b origin/v5 upstream/v5</span><br></pre></td></tr></table></figure>
<p>这一步的意思是从<code>upstream/v5</code>分支创建一个<code>origin/v5</code>分支。</p>
<p>到这里还没结束,因为这个<code>origin/v5</code>分支是我们从upstream中拉取出来的,我的习惯是要在这个分支上再新建一个分支做开发,分支名也有一些含义,于是接着运行</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">git checkout -b v5-fix#NodeNotFound</span><br></pre></td></tr></table></figure>
<h4 id="完成修改(注意commit规范)"><a href="#完成修改(注意commit规范)" class="headerlink" title="完成修改(注意commit规范)"></a>完成修改(注意commit规范)</h4><p>写完代码后注意自己的commit规范,每一个commit都要让别人能看懂,不要全部修改完再做提交。这里我把每一个修改的含义都分得比较清楚,如下:</p>
<ul>
<li>bug 重现 commit</li>
<li>bug 修复 commit</li>
</ul>
<h4 id="push到个人仓库"><a href="#push到个人仓库" class="headerlink" title="push到个人仓库"></a>push到个人仓库</h4><p>在这一步我遇到了大麻烦,由于G6发布了<code>issue hunt</code>,因此这里我push到个人仓库时,由于我的<strong><code>personal token</code>没有包含workflow,</strong>因此push不成功。报错:<strong>“refusing to allow a Personal Access Token to create or update workflow <code>.github/workflows/build.yml</code> without <code>workflow</code> scope”</strong></p>
<p>这一步正确的解决方案是重新创建一个Token并push,但是我为了省事,直接把workflow删掉了。结果PR就没有被合,并且收到了这个comment:</p>
<p><img src="https://raw.githubusercontent.com/zqqcee/img_repo/main/img/202306082202094.png" alt="image-20230608215157196"></p>
<p>对于这个问题,解决方案我也记录在此处:</p>
<ul>
<li><p>首先,创建一个token,勾选workflow,这一步在网上有很多教程,跟着做就好了,这里不做过多赘述</p>
</li>
<li><p>接着,重新设置<code>origin</code>。运行</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">git remote remove origin </span><br><span class="line">git remote add origin https://{token}@github.com/zqqcee/G6.git</span><br></pre></td></tr></table></figure>
<p>将<code>{token}</code>替换为刚刚创建的带workflow的token</p>
</li>
<li><p>最后,重新push就能成功了</p>
</li>
</ul>
<h4 id="创建Pull-Request"><a href="#创建Pull-Request" class="headerlink" title="创建Pull Request"></a>创建Pull Request</h4><p>到fork的仓库中,push成功后,仓库中会显示有一个新的分支。然后点击<code>Pull Request</code></p>
<p>创建一个新的<code>Pull Request</code></p>
<p>这一步没什么好说的,重点是要选对你要修改的分支</p>
<p><img src="https://raw.githubusercontent.com/zqqcee/img_repo/main/img/202306082202467.png" alt="image-20230608215845380"></p>
<h4 id="填写PR信息"><a href="#填写PR信息" class="headerlink" title="填写PR信息"></a>填写PR信息</h4><p>PR信息非常关键,必须非常清楚地说明你为什么要创建这个PR ,以及这个PR修复了什么问题。这里直接贴上我的PR 说明,供参考。</p>
<p><img src="https://raw.githubusercontent.com/zqqcee/img_repo/main/img/202306082203170.png" alt="iShot_2023-06-08_21.59.51"></p>
<hr>
<p><strong>以上就是全部的解决过程了,很开心能为G6做了贡献,希望有机会能加入AntV团队,也希望自己能为更多仓库创建更优秀的PR~!</strong></p>
]]></content>
<categories>
<category>thought</category>
</categories>
<tags>
<tag>git</tag>
</tags>
</entry>
<entry>
<title>【D3.js 学习记录(实战)】Force 力导图数据可视化</title>
<url>/2022/08/03/aab110ab011c/</url>
<content><![CDATA[<h2 id="Force-Simulation-力导图实战-D3-js"><a href="#Force-Simulation-力导图实战-D3-js" class="headerlink" title="Force Simulation 力导图实战 @ D3.js"></a>Force Simulation 力导图实战 @ D3.js</h2><h3 id="数据结构"><a href="#数据结构" class="headerlink" title="数据结构"></a>数据结构</h3><p>做力导图使用到的数据为json数据,一般采用json数据来表达图结构。本次实验选用的json数据数据结构如下(图片中为经过了力模拟后的):</p>
<ul>
<li>links至少要由<code>target</code>, <code>source</code>组成<ul>
<li>这连个属性标记了起始node和终止node,其值通<img src="https://raw.githubusercontent.com/zqqcee/img_repo/main/img/202307060008078.png" alt="在这里插入图片描述"><br>常为<strong>node的id。</strong></li>
</ul>
</li>
<li>node由<code>group</code>和<code>id</code>构成<ul>
<li>group表示node属于哪一类(方便着色)</li>
<li>id是node的唯一标识符</li>
</ul>
</li>
</ul>
<h3 id="思路分析"><a href="#思路分析" class="headerlink" title="思路分析"></a>思路分析</h3><ul>
<li>画布初始化,全局变量定义</li>
<li>数据读取</li>
<li>力模拟</li>
<li>数据绑定 datajoin<ul>
<li>结点node(<circle>和<text>)</li>
<li>连边link</li>
</ul>
</li>
<li><code>simulation.on("tick",tick)</code>,tick函数编写</li>
<li>drag 拖曳交互设计</li>
</ul>
<h3 id="画布初始化,全局变量定义"><a href="#画布初始化,全局变量定义" class="headerlink" title="画布初始化,全局变量定义"></a>画布初始化,全局变量定义</h3><p>这步比较基础,直接贴上代码</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> svg = d3.<span class="title function_">select</span>(<span class="string">'svg'</span>)</span><br><span class="line"><span class="keyword">const</span> height = +(svg.<span class="title function_">attr</span>(<span class="string">'height'</span>))</span><br><span class="line"><span class="keyword">const</span> width = +(svg.<span class="title function_">attr</span>(<span class="string">'width'</span>))</span><br><span class="line"><span class="keyword">const</span> margin = {</span><br><span class="line"> <span class="attr">top</span>:<span class="number">150</span>,</span><br><span class="line"> <span class="attr">left</span>:<span class="number">50</span>,</span><br><span class="line"> <span class="attr">right</span>:<span class="number">50</span>,</span><br><span class="line"> <span class="attr">bottom</span>:<span class="number">0</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> innerHeight = height-margin.<span class="property">top</span>-margin.<span class="property">bottom</span>;</span><br><span class="line"><span class="keyword">const</span> innerWidth = width - margin.<span class="property">left</span> - margin.<span class="property">right</span>;</span><br><span class="line"><span class="keyword">var</span> color = d3.<span class="title function_">scaleOrdinal</span>(d3.<span class="property">schemeCategory10</span>);</span><br><span class="line"><span class="keyword">let</span> link;</span><br><span class="line"><span class="keyword">let</span> nodes;</span><br><span class="line"><span class="keyword">let</span> simulation;</span><br><span class="line"><span class="keyword">const</span> render_init = <span class="keyword">function</span>(<span class="params"></span>){</span><br><span class="line"> svg.<span class="title function_">append</span>(<span class="string">"text"</span>)</span><br><span class="line"> .<span class="title function_">attr</span>(<span class="string">'class'</span>, <span class="string">'title'</span>)</span><br><span class="line"> .<span class="title function_">attr</span>(<span class="string">'font-size'</span>,<span class="string">'2em'</span>)</span><br><span class="line"> .<span class="title function_">attr</span>(<span class="string">'x'</span>,margin.<span class="property">left</span>)</span><br><span class="line"> .<span class="title function_">attr</span>(<span class="string">'y'</span>, margin.<span class="property">top</span>/<span class="number">2</span>)</span><br><span class="line"> .<span class="title function_">attr</span>(<span class="string">'transform'</span>,<span class="string">`translate(0,-40)`</span> )</span><br><span class="line"> .<span class="title function_">attr</span>(<span class="string">'font-weight'</span>, <span class="string">'bold'</span>)</span><br><span class="line"> .<span class="title function_">attr</span>(<span class="string">'fill'</span>,<span class="string">'blue'</span> )</span><br><span class="line"> .<span class="title function_">html</span>(<span class="string">"Force Simulation"</span>)</span><br><span class="line"></span><br><span class="line"> svg.<span class="title function_">append</span>(<span class="string">"g"</span>)</span><br><span class="line"> .<span class="title function_">attr</span>(<span class="string">'id'</span>, <span class="string">'maingroup'</span>)</span><br><span class="line"> .<span class="title function_">attr</span>(<span class="string">'x'</span>, margin.<span class="property">left</span>)</span><br><span class="line"> .<span class="title function_">attr</span>(<span class="string">'y'</span>, margin.<span class="property">top</span>)</span><br><span class="line"> .<span class="title function_">attr</span>(<span class="string">'transform'</span>, <span class="string">`translate(<span class="subst">${margin.left}</span>,<span class="subst">${margin.top}</span>)`</span>)</span><br><span class="line"> .<span class="title function_">attr</span>(<span class="string">'width'</span>,innerWidth )</span><br><span class="line"> .<span class="title function_">attr</span>(<span class="string">'height'</span>,innerHeight )</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="数据读取"><a href="#数据读取" class="headerlink" title="数据读取"></a>数据读取</h3><p>通常读取进来的数据要进行预处理,但是图数据有些不同。<strong>通常我们拿到的数据都是不符合d3力导图数据结构要求的</strong>。一般用python等语言写的<strong>脚本文件进行数据预处理</strong>整理成符合要求的数据结构。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">d3.<span class="title function_">json</span>(<span class="string">'./data/miserables.json'</span>).<span class="title function_">then</span>(<span class="function"><span class="params">data</span> =></span> {</span><br><span class="line"> <span class="title function_">render_init</span>();<span class="comment">//画布初始化</span></span><br><span class="line"> <span class="comment">//force simulation 力模拟</span></span><br><span class="line"> <span class="comment">//data join 数据绑定</span></span><br><span class="line"> <span class="comment">//drag 交互事件</span></span><br><span class="line">})</span><br></pre></td></tr></table></figure>
<h3 id="力模拟"><a href="#力模拟" class="headerlink" title="力模拟"></a>力模拟</h3><p>有两点需要注意的地方:</p>
<ul>
<li>forceLink这里要处理一下,绑定<code>node.id</code>。不然会按照node的索引来进行,这样设置tick的时候会非常不方便</li>
<li>仅进行力模拟结点的位置不会进行实时更新,必须要有<code>simulation.on(tick)</code>才会把力模拟的结果反映到图元上 </li>
</ul>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">simulation = d3.<span class="title function_">forceSimulation</span>()</span><br><span class="line"> .<span class="title function_">nodes</span>(data.<span class="property">nodes</span>)</span><br><span class="line"> .<span class="title function_">force</span>(<span class="string">"link"</span>,d3.<span class="title function_">forceLink</span>(data.<span class="property">links</span>).<span class="title function_">id</span>(<span class="function"><span class="params">d</span> =></span> d.<span class="property">id</span>))</span><br><span class="line"> .<span class="title function_">force</span>(<span class="string">"manyBody"</span>,d3.<span class="title function_">forceManyBody</span>())</span><br><span class="line"> .<span class="title function_">force</span>(<span class="string">"center"</span>,d3.<span class="title function_">forceCenter</span>(innerWidth/<span class="number">2</span>,innerHeight/<span class="number">2</span>)) </span><br><span class="line"> .<span class="title function_">on</span>(<span class="string">"tick"</span>,tick)</span><br></pre></td></tr></table></figure>
<h3 id="数据绑定-datajoin"><a href="#数据绑定-datajoin" class="headerlink" title="数据绑定 datajoin"></a>数据绑定 datajoin</h3><p>这里我们需要绑定的三个元素为: <strong>结点,结点名称,链接</strong></p>
<h4 id="结点与结点名称"><a href="#结点与结点名称" class="headerlink" title="结点与结点名称"></a>结点与结点名称</h4><p>一个传统的方法是<code>circle</code>和<code>text</code>分开绑定,但是这里采用一个更加高效的办法:就是创建一个<g>, 每个g标签代表一个结点,其中包含<code>circle</code>和<code>text</code>两个图元</p>
<ul>
<li>创建结点group <g></li>
</ul>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">nodes = group.<span class="title function_">append</span>(<span class="string">'g'</span>).<span class="title function_">attr</span>(<span class="string">"class"</span>, <span class="string">"nodegroup"</span>)</span><br><span class="line"><span class="comment">//先创建一个group,其中包含所有结点(这步可有可无)</span></span><br><span class="line"> .<span class="title function_">selectAll</span>(<span class="string">'.node'</span>)</span><br><span class="line"> .<span class="title function_">data</span>(data.<span class="property">nodes</span>)</span><br><span class="line"> .<span class="title function_">join</span>(<span class="string">'g'</span>)</span><br><span class="line"> .<span class="title function_">attr</span>(<span class="string">'class'</span>,<span class="string">'node'</span> )</span><br></pre></td></tr></table></figure>
<ul>
<li><p>结点绑定(circle)</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> circle = nodes.<span class="title function_">append</span>(<span class="string">'circle'</span>)</span><br><span class="line">.<span class="title function_">attr</span>(<span class="string">'r'</span>, <span class="number">5</span>)</span><br><span class="line">.<span class="title function_">attr</span>(<span class="string">'fill'</span>, <span class="function"><span class="params">d</span> =></span> <span class="title function_">color</span>(d.<span class="property">group</span>))</span><br></pre></td></tr></table></figure>
</li>
<li><p>结点名称绑定</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> label = nodes.<span class="title function_">append</span>(<span class="string">'text'</span>)</span><br><span class="line">.<span class="title function_">attr</span>(<span class="string">'x'</span>, <span class="number">6</span>)</span><br><span class="line">.<span class="title function_">attr</span>(<span class="string">'y'</span>, <span class="number">3</span> )</span><br><span class="line">.<span class="title function_">html</span>(<span class="function"><span class="params">d</span> =></span> d.<span class="property">id</span> )</span><br><span class="line">.<span class="title function_">attr</span>(<span class="string">'font-size'</span>, <span class="string">'12px'</span>)</span><br></pre></td></tr></table></figure>
</li>
</ul>
<h4 id="链接"><a href="#链接" class="headerlink" title="链接"></a>链接</h4><p>这里有个坑,line标签是不能设置<code>fill</code>的,应该通过设置<code>stroke</code>属性来设置line的颜色。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">//注意设置stroke</span></span><br><span class="line">link = group.<span class="title function_">append</span>(<span class="string">'g'</span>).<span class="title function_">attr</span>(<span class="string">'class'</span>,<span class="string">'linkgroup'</span>).<span class="title function_">selectAll</span>(<span class="string">'line'</span>)</span><br><span class="line"> .<span class="title function_">data</span>(data.<span class="property">links</span>)</span><br><span class="line"> .<span class="title function_">enter</span>().<span class="title function_">append</span>(<span class="string">'line'</span>)</span><br><span class="line"> .<span class="title function_">attr</span>(<span class="string">'stroke-width'</span>, <span class="function"><span class="params">d</span> =></span> <span class="title class_">Math</span>.<span class="title function_">sqrt</span>(d.<span class="property">value</span>))</span><br><span class="line"> .<span class="title function_">attr</span>(<span class="string">'stroke'</span>, <span class="string">'green'</span>)</span><br><span class="line"> .<span class="title function_">attr</span>(<span class="string">'stroke-opacity'</span>,<span class="number">0.6</span> )</span><br></pre></td></tr></table></figure>
<h3 id="tick函数编写"><a href="#tick函数编写" class="headerlink" title="tick函数编写"></a>tick函数编写</h3><p>在tick函数中,我们要更新<strong>node的位置</strong>和<strong>link的起点和终点</strong></p>
<p><strong>注意</strong>:node指包含了circle和text的那个group,这里只需要更新那个group的位置,<circle>和<text>就会一起更新。这就是上面提出为什么要把<circle>和<text>放在一个<g>中。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> tick = <span class="keyword">function</span>(<span class="params"></span>){</span><br><span class="line"> link</span><br><span class="line"> .<span class="title function_">attr</span>(<span class="string">'x1'</span>, <span class="function"><span class="params">d</span> =></span> d.<span class="property">source</span>.<span class="property">x</span>)</span><br><span class="line"> .<span class="title function_">attr</span>(<span class="string">'y1'</span>, <span class="function"><span class="params">d</span> =></span> d.<span class="property">source</span>.<span class="property">y</span>)</span><br><span class="line"> .<span class="title function_">attr</span>(<span class="string">'x2'</span>, <span class="function"><span class="params">d</span> =></span> d.<span class="property">target</span>.<span class="property">x</span>)</span><br><span class="line"> .<span class="title function_">attr</span>(<span class="string">'y2'</span>, <span class="function"><span class="params">d</span> =></span> d.<span class="property">target</span>.<span class="property">y</span>)</span><br><span class="line"></span><br><span class="line"> nodes</span><br><span class="line"> .<span class="title function_">attr</span>(<span class="string">'transform'</span>, <span class="function"><span class="params">d</span> =></span> <span class="string">`translate(<span class="subst">${d.x}</span>,<span class="subst">${d.y}</span>)`</span>)</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="drag-拖曳交互设计"><a href="#drag-拖曳交互设计" class="headerlink" title="drag 拖曳交互设计"></a>drag 拖曳交互设计</h3><p>现在给图加一些交互效果:让结点能够被鼠标选中并拖拽</p>
<h4 id="d3-drag-函数"><a href="#d3-drag-函数" class="headerlink" title="d3.drag()函数"></a><code>d3.drag()</code>函数</h4><p>drag函数有三个需要配置的,分别是拖曳开始,拖曳过程和拖曳结束</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> dragFunc = d3.<span class="title function_">drag</span>()</span><br><span class="line">.<span class="title function_">on</span>(<span class="string">'start'</span>,dragstarted)</span><br><span class="line">.<span class="title function_">on</span>(<span class="string">'drag'</span>,dragged)</span><br><span class="line">.<span class="title function_">on</span>(<span class="string">'end'</span>,dragended)</span><br></pre></td></tr></table></figure>
<h4 id="dragstarted-拖曳开始"><a href="#dragstarted-拖曳开始" class="headerlink" title="dragstarted 拖曳开始"></a>dragstarted 拖曳开始</h4><ul>
<li><code>alphaTarget</code>:衰减系数,对节点位置移动过程的模拟,数值越高移动越快,数值范围[0,1]</li>
<li><code>restart()</code>: <strong>重新启动仿真的内部定时器并且返回仿真</strong>。与 <code>simulation*.alphaTarget</code>或 <code>simulation*.alpha</code>结合使用,这个方法可以在交互期间再次激活仿真,比如拖拽节点或者在使用 <code>simulation.stop</code>临时暂停仿真后使用。</li>
</ul>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">dragstarted</span>(<span class="params">d</span>) {</span><br><span class="line"> <span class="keyword">if</span> (!d3.<span class="property">event</span>.<span class="property">active</span>) simulation.<span class="title function_">alphaTarget</span>(<span class="number">0.3</span>).<span class="title function_">restart</span>();</span><br><span class="line"> d.<span class="property">fx</span> = d.<span class="property">x</span>;</span><br><span class="line"> d.<span class="property">fy</span> = d.<span class="property">y</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="dragged-拖曳过程"><a href="#dragged-拖曳过程" class="headerlink" title="dragged 拖曳过程"></a>dragged 拖曳过程</h4><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"></span><br><span class="line">```js</span><br><span class="line">const dragged = function(d){</span><br><span class="line"> d.fx = d3.event.x;</span><br><span class="line"> d.fy = d3.event.y;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="dragended-拖曳结束"><a href="#dragended-拖曳结束" class="headerlink" title="dragended 拖曳结束"></a>dragended 拖曳结束</h4><p>最后将fx和fy设置成null,表示拖曳结束后让结点回到力模拟的位置,而不是停留在拖曳的位置</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> dragended = <span class="keyword">function</span>(<span class="params">d</span>){</span><br><span class="line"> <span class="keyword">if</span> (!d3.<span class="property">event</span>.<span class="property">active</span>) {</span><br><span class="line"> simulation.<span class="title function_">alphaTarget</span>(<span class="number">0</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//让它回到原来的位置</span></span><br><span class="line"> d.<span class="property">fx</span> = <span class="literal">null</span>;</span><br><span class="line"> d.<span class="property">fy</span> = <span class="literal">null</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="调用方式"><a href="#调用方式" class="headerlink" title="调用方式"></a>调用方式</h4><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="title function_">dragFunc</span>(nodes)<span class="comment">//param:拖曳对象</span></span><br></pre></td></tr></table></figure>
<h2 id="完整代码"><a href="#完整代码" class="headerlink" title="完整代码"></a>完整代码</h2><figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="meta"><!DOCTYPE <span class="keyword">html</span>></span></span><br><span class="line"><span class="tag"><<span class="name">html</span>></span></span><br><span class="line"><span class="tag"><<span class="name">head</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">title</span>></span>force<span class="tag"></<span class="name">title</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">script</span> <span class="attr">src</span>=<span class="string">"./js/d3.min.js"</span>></span><span class="tag"></<span class="name">script</span>></span></span><br><span class="line"></span><br><span class="line"><span class="tag"></<span class="name">head</span>></span></span><br><span class="line"><span class="tag"><<span class="name">body</span>></span></span><br><span class="line"></span><br><span class="line"><span class="tag"><<span class="name">svg</span> <span class="attr">width</span>=<span class="string">"1200"</span> <span class="attr">height</span> = <span class="string">"650"</span>></span><span class="tag"></<span class="name">svg</span>></span></span><br><span class="line"><span class="tag"><<span class="name">script</span> ></span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript"> <span class="keyword">const</span> svg = d3.<span class="title function_">select</span>(<span class="string">'svg'</span>)</span></span><br><span class="line"><span class="language-javascript"> <span class="keyword">const</span> height = +(svg.<span class="title function_">attr</span>(<span class="string">'height'</span>))</span></span><br><span class="line"><span class="language-javascript"> <span class="keyword">const</span> width = +(svg.<span class="title function_">attr</span>(<span class="string">'width'</span>))</span></span><br><span class="line"><span class="language-javascript"> <span class="keyword">const</span> margin = {</span></span><br><span class="line"><span class="language-javascript"> <span class="attr">top</span>:<span class="number">150</span>,</span></span><br><span class="line"><span class="language-javascript"> <span class="attr">left</span>:<span class="number">50</span>,</span></span><br><span class="line"><span class="language-javascript"> <span class="attr">right</span>:<span class="number">50</span>,</span></span><br><span class="line"><span class="language-javascript"> <span class="attr">bottom</span>:<span class="number">0</span></span></span><br><span class="line"><span class="language-javascript"> }</span></span><br><span class="line"><span class="language-javascript"> <span class="keyword">const</span> innerHeight = height-margin.<span class="property">top</span>-margin.<span class="property">bottom</span>;</span></span><br><span class="line"><span class="language-javascript"> <span class="keyword">const</span> innerWidth = width - margin.<span class="property">left</span> - margin.<span class="property">right</span>;</span></span><br><span class="line"><span class="language-javascript"> <span class="keyword">var</span> color = d3.<span class="title function_">scaleOrdinal</span>(d3.<span class="property">schemeCategory10</span>);</span></span><br><span class="line"><span class="language-javascript"> <span class="keyword">let</span> link;</span></span><br><span class="line"><span class="language-javascript"> <span class="keyword">let</span> nodes;</span></span><br><span class="line"><span class="language-javascript"> <span class="keyword">let</span> simulation;</span></span><br><span class="line"><span class="language-javascript"> <span class="keyword">const</span> render_init = <span class="keyword">function</span>(<span class="params"></span>){</span></span><br><span class="line"><span class="language-javascript"> svg.<span class="title function_">append</span>(<span class="string">"text"</span>)</span></span><br><span class="line"><span class="language-javascript"> .<span class="title function_">attr</span>(<span class="string">'class'</span>, <span class="string">'title'</span>)</span></span><br><span class="line"><span class="language-javascript"> .<span class="title function_">attr</span>(<span class="string">'font-size'</span>,<span class="string">'2em'</span>)</span></span><br><span class="line"><span class="language-javascript"> .<span class="title function_">attr</span>(<span class="string">'x'</span>,margin.<span class="property">left</span>)</span></span><br><span class="line"><span class="language-javascript"> .<span class="title function_">attr</span>(<span class="string">'y'</span>, margin.<span class="property">top</span>/<span class="number">2</span>)</span></span><br><span class="line"><span class="language-javascript"> .<span class="title function_">attr</span>(<span class="string">'transform'</span>,<span class="string">`translate(0,-40)`</span> )</span></span><br><span class="line"><span class="language-javascript"> .<span class="title function_">attr</span>(<span class="string">'font-weight'</span>, <span class="string">'bold'</span>)</span></span><br><span class="line"><span class="language-javascript"> .<span class="title function_">attr</span>(<span class="string">'fill'</span>,<span class="string">'blue'</span> )</span></span><br><span class="line"><span class="language-javascript"> .<span class="title function_">html</span>(<span class="string">"Force Simulation"</span>)</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript"> svg.<span class="title function_">append</span>(<span class="string">"g"</span>)</span></span><br><span class="line"><span class="language-javascript"> .<span class="title function_">attr</span>(<span class="string">'id'</span>, <span class="string">'maingroup'</span>)</span></span><br><span class="line"><span class="language-javascript"> .<span class="title function_">attr</span>(<span class="string">'x'</span>, margin.<span class="property">left</span>)</span></span><br><span class="line"><span class="language-javascript"> .<span class="title function_">attr</span>(<span class="string">'y'</span>, margin.<span class="property">top</span>)</span></span><br><span class="line"><span class="language-javascript"> .<span class="title function_">attr</span>(<span class="string">'transform'</span>, <span class="string">`translate(<span class="subst">${margin.left}</span>,<span class="subst">${margin.top}</span>)`</span>)</span></span><br><span class="line"><span class="language-javascript"> .<span class="title function_">attr</span>(<span class="string">'width'</span>,innerWidth )</span></span><br><span class="line"><span class="language-javascript"> .<span class="title function_">attr</span>(<span class="string">'height'</span>,innerHeight )</span></span><br><span class="line"><span class="language-javascript"> }</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript"> <span class="keyword">const</span> tick = <span class="keyword">function</span>(<span class="params"></span>){</span></span><br><span class="line"><span class="language-javascript"> link</span></span><br><span class="line"><span class="language-javascript"> .<span class="title function_">attr</span>(<span class="string">'x1'</span>, <span class="function"><span class="params">d</span> =></span> d.<span class="property">source</span>.<span class="property">x</span>)</span></span><br><span class="line"><span class="language-javascript"> .<span class="title function_">attr</span>(<span class="string">'y1'</span>, <span class="function"><span class="params">d</span> =></span> d.<span class="property">source</span>.<span class="property">y</span>)</span></span><br><span class="line"><span class="language-javascript"> .<span class="title function_">attr</span>(<span class="string">'x2'</span>, <span class="function"><span class="params">d</span> =></span> d.<span class="property">target</span>.<span class="property">x</span>)</span></span><br><span class="line"><span class="language-javascript"> .<span class="title function_">attr</span>(<span class="string">'y2'</span>, <span class="function"><span class="params">d</span> =></span> d.<span class="property">target</span>.<span class="property">y</span>)</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript"> nodes</span></span><br><span class="line"><span class="language-javascript"> .<span class="title function_">attr</span>(<span class="string">'transform'</span>, <span class="function"><span class="params">d</span> =></span> <span class="string">`translate(<span class="subst">${d.x}</span>,<span class="subst">${d.y}</span>)`</span>)</span></span><br><span class="line"><span class="language-javascript"> }</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript"> <span class="keyword">const</span> dragstarted = <span class="keyword">function</span>(<span class="params">d</span>){</span></span><br><span class="line"><span class="language-javascript"> <span class="keyword">if</span>(!d3.<span class="property">event</span>.<span class="property">active</span>){</span></span><br><span class="line"><span class="language-javascript"> <span class="comment">//// 设置衰减系数,对节点位置移动过程的模拟,数值越高移动越快,数值范围[0,1]</span></span></span><br><span class="line"><span class="language-javascript"> simulation.<span class="title function_">alphaTarget</span>(<span class="number">0.3</span>)</span></span><br><span class="line"><span class="language-javascript"> .<span class="title function_">restart</span>();</span></span><br><span class="line"><span class="language-javascript"> d.<span class="property">fx</span> = d.<span class="property">x</span>;</span></span><br><span class="line"><span class="language-javascript"> d.<span class="property">fy</span> = d.<span class="property">y</span>;</span></span><br><span class="line"><span class="language-javascript"> }</span></span><br><span class="line"><span class="language-javascript"> }</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript"> <span class="keyword">const</span> dragged = <span class="keyword">function</span>(<span class="params">d</span>){</span></span><br><span class="line"><span class="language-javascript"> d.<span class="property">fx</span> = d3.<span class="property">event</span>.<span class="property">x</span>;</span></span><br><span class="line"><span class="language-javascript"> d.<span class="property">fy</span> = d3.<span class="property">event</span>.<span class="property">y</span>;</span></span><br><span class="line"><span class="language-javascript"> }</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript"> <span class="keyword">const</span> dragended = <span class="keyword">function</span>(<span class="params">d</span>){</span></span><br><span class="line"><span class="language-javascript"> <span class="keyword">if</span> (!d3.<span class="property">event</span>.<span class="property">active</span>) {</span></span><br><span class="line"><span class="language-javascript"> simulation.<span class="title function_">alphaTarget</span>(<span class="number">0</span>);</span></span><br><span class="line"><span class="language-javascript"> }</span></span><br><span class="line"><span class="language-javascript"> <span class="comment">//让它回到原来的位置</span></span></span><br><span class="line"><span class="language-javascript"> d.<span class="property">fx</span> = <span class="literal">null</span>;</span></span><br><span class="line"><span class="language-javascript"> d.<span class="property">fy</span> = <span class="literal">null</span>;</span></span><br><span class="line"><span class="language-javascript"> }</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript"> d3.<span class="title function_">json</span>(<span class="string">'./data/miserables.json'</span>).<span class="title function_">then</span>(<span class="function"><span class="params">data</span> =></span> {</span></span><br><span class="line"><span class="language-javascript"> <span class="variable language_">console</span>.<span class="title function_">log</span>(data);</span></span><br><span class="line"><span class="language-javascript"> <span class="title function_">render_init</span>();</span></span><br><span class="line"><span class="language-javascript"> <span class="keyword">const</span> group = d3.<span class="title function_">select</span>(<span class="string">'#maingroup'</span>)</span></span><br><span class="line"><span class="language-javascript"> simulation = d3.<span class="title function_">forceSimulation</span>()</span></span><br><span class="line"><span class="language-javascript"> .<span class="title function_">nodes</span>(data.<span class="property">nodes</span>)</span></span><br><span class="line"><span class="language-javascript"> .<span class="title function_">force</span>(<span class="string">"link"</span>,d3.<span class="title function_">forceLink</span>(data.<span class="property">links</span>).<span class="title function_">id</span>(<span class="function"><span class="params">d</span> =></span> d.<span class="property">id</span>))</span></span><br><span class="line"><span class="language-javascript"> .<span class="title function_">force</span>(<span class="string">"manyBody"</span>,d3.<span class="title function_">forceManyBody</span>())</span></span><br><span class="line"><span class="language-javascript"> .<span class="title function_">force</span>(<span class="string">"center"</span>,d3.<span class="title function_">forceCenter</span>(innerWidth/<span class="number">2</span>,innerHeight/<span class="number">2</span>)) </span></span><br><span class="line"><span class="language-javascript"> .<span class="title function_">on</span>(<span class="string">"tick"</span>,tick)</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript"> <span class="comment">//注意设置stroke</span></span></span><br><span class="line"><span class="language-javascript"> link = group.<span class="title function_">append</span>(<span class="string">'g'</span>).<span class="title function_">attr</span>(<span class="string">'class'</span>,<span class="string">'linkgroup'</span>).<span class="title function_">selectAll</span>(<span class="string">'line'</span>)</span></span><br><span class="line"><span class="language-javascript"> .<span class="title function_">data</span>(data.<span class="property">links</span>)</span></span><br><span class="line"><span class="language-javascript"> .<span class="title function_">enter</span>().<span class="title function_">append</span>(<span class="string">'line'</span>)</span></span><br><span class="line"><span class="language-javascript"> .<span class="title function_">attr</span>(<span class="string">'stroke-width'</span>, <span class="function"><span class="params">d</span> =></span> <span class="title class_">Math</span>.<span class="title function_">sqrt</span>(d.<span class="property">value</span>))</span></span><br><span class="line"><span class="language-javascript"> .<span class="title function_">attr</span>(<span class="string">'stroke'</span>, <span class="string">'green'</span>)</span></span><br><span class="line"><span class="language-javascript"> .<span class="title function_">attr</span>(<span class="string">'stroke-opacity'</span>,<span class="number">0.6</span> )</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript"> nodes = group.<span class="title function_">append</span>(<span class="string">'g'</span>).<span class="title function_">attr</span>(<span class="string">"class"</span>, <span class="string">"nodegroup"</span>)</span></span><br><span class="line"><span class="language-javascript"> .<span class="title function_">selectAll</span>(<span class="string">'.node'</span>)</span></span><br><span class="line"><span class="language-javascript"> .<span class="title function_">data</span>(data.<span class="property">nodes</span>)</span></span><br><span class="line"><span class="language-javascript"> .<span class="title function_">join</span>(<span class="string">'g'</span>)</span></span><br><span class="line"><span class="language-javascript"> .<span class="title function_">attr</span>(<span class="string">'class'</span>,<span class="string">'node'</span> )</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript"> <span class="keyword">var</span> circle = nodes.<span class="title function_">append</span>(<span class="string">'circle'</span>)</span></span><br><span class="line"><span class="language-javascript"> .<span class="title function_">attr</span>(<span class="string">'r'</span>, <span class="number">5</span>)</span></span><br><span class="line"><span class="language-javascript"> .<span class="title function_">attr</span>(<span class="string">'fill'</span>, <span class="function"><span class="params">d</span> =></span> <span class="title function_">color</span>(d.<span class="property">group</span>))</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript"> <span class="keyword">var</span> label = nodes.<span class="title function_">append</span>(<span class="string">'text'</span>)</span></span><br><span class="line"><span class="language-javascript"> .<span class="title function_">attr</span>(<span class="string">'x'</span>, <span class="number">6</span>)</span></span><br><span class="line"><span class="language-javascript"> .<span class="title function_">attr</span>(<span class="string">'y'</span>, <span class="number">3</span> )</span></span><br><span class="line"><span class="language-javascript"> .<span class="title function_">html</span>(<span class="function"><span class="params">d</span> =></span> d.<span class="property">id</span> )</span></span><br><span class="line"><span class="language-javascript"> .<span class="title function_">attr</span>(<span class="string">'font-size'</span>, <span class="string">'12px'</span>)</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript"> <span class="keyword">const</span> dragFunc = d3.<span class="title function_">drag</span>()</span></span><br><span class="line"><span class="language-javascript"> .<span class="title function_">on</span>(<span class="string">'start'</span>,dragstarted)</span></span><br><span class="line"><span class="language-javascript"> .<span class="title function_">on</span>(<span class="string">'drag'</span>,dragged)</span></span><br><span class="line"><span class="language-javascript"> .<span class="title function_">on</span>(<span class="string">'end'</span>,dragended)</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript"> <span class="title function_">dragFunc</span>(nodes)<span class="comment">//拖曳对象</span></span></span><br><span class="line"><span class="language-javascript"> </span></span><br><span class="line"><span class="language-javascript"> })</span></span><br><span class="line"><span class="language-javascript"></span><span class="tag"></<span class="name">script</span>></span></span><br><span class="line"><span class="tag"></<span class="name">body</span>></span></span><br><span class="line"><span class="tag"></<span class="name">html</span>></span></span><br></pre></td></tr></table></figure>
<h2 id="效果"><a href="#效果" class="headerlink" title="效果"></a>效果</h2><h3 id="初始"><a href="#初始" class="headerlink" title="初始"></a>初始</h3><p><img src="https://raw.githubusercontent.com/zqqcee/img_repo/main/img/202307060008684.png" alt="在这里插入图片描述"></p>
<h3 id="拖曳"><a href="#拖曳" class="headerlink" title="拖曳"></a>拖曳</h3><p><img src="https://raw.githubusercontent.com/zqqcee/img_repo/main/img/202307060008035.png" alt="在这里插入图片描述"></p>
]]></content>
<categories>
<category>others</category>
</categories>
<tags>
<tag>javascript</tag>
</tags>
</entry>
<entry>
<title>仿Jfinal 为了提供数据结构普适性而重写数据分页工具</title>
<url>/2022/08/02/2aaad364c9c0/</url>
<content><![CDATA[<h3 id="提出目的"><a href="#提出目的" class="headerlink" title="提出目的"></a>提出目的</h3><p><strong>注</strong>:这种方法无法体现分页的本质,重写分页算法的目的是为了让分页算法能够普适各种数据类型。即向分页算法中投入任何数据结构都可以进行分页</p>
<h3 id="分页工具类代码"><a href="#分页工具类代码" class="headerlink" title="分页工具类代码"></a>分页工具类代码</h3><p><strong>注:本文仅贴出代码,不介绍逻辑。 具体分页逻辑可以自行查看代码进行理解</strong></p>
<ul>
<li>1.PageMe.java <strong>(继承JFinal封装的Page类,是分类方法返回的数据类型。该类使用java泛型类机制,让分类算法普适于任何数据结构)</strong><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> cc</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">PageMe</span><T> <span class="keyword">extends</span> <span class="title class_">Page</span><T> {</span><br><span class="line"> <span class="type">boolean</span> firstPage;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isFirstPage</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> firstPage;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setFirstPage</span><span class="params">(<span class="type">boolean</span> firstPage)</span> {</span><br><span class="line"> <span class="built_in">this</span>.firstPage = firstPage;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isLastPage</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> lastPage;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setLastPage</span><span class="params">(<span class="type">boolean</span> lastPage)</span> {</span><br><span class="line"> <span class="built_in">this</span>.lastPage = lastPage;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="type">boolean</span> lastPage;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
</li>
</ul>
<ul>
<li>2.PageKit.java <strong>(分页工具类,内有分页方法paginate() )</strong><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> cc</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">PageKit</span><T> {</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">* <span class="doctag">@return</span> PageMe<T> </span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"> <span class="keyword">public</span> PageMe<T> <span class="title function_">paginate</span><span class="params">(<span class="type">int</span> pageNumber, <span class="type">int</span> pageSize, List<T> data)</span> {</span><br><span class="line"> PageMe<T> page = <span class="keyword">new</span> <span class="title class_">PageMe</span><>();</span><br><span class="line"> page.setTotalRow(data.size());</span><br><span class="line"> page.setTotalPage(pageSize == <span class="number">0</span> ? <span class="number">1</span> : (<span class="type">int</span>) Math.ceil((data.size() / pageSize)));</span><br><span class="line"> <span class="comment">//基础数据</span></span><br><span class="line"> <span class="keyword">if</span> (pageNumber < <span class="number">1</span>) {</span><br><span class="line"> <span class="comment">//如果pageNumber不符合规范,返回全部数据</span></span><br><span class="line"> page.setList(data);</span><br><span class="line"> <span class="keyword">return</span> page;</span><br><span class="line"> }</span><br><span class="line"> page.setFirstPage(pageNumber <= <span class="number">1</span>);</span><br><span class="line"> page.setLastPage(pageNumber == page.getTotalPage());</span><br><span class="line"> page.setPageNumber(pageNumber);</span><br><span class="line"> page.setPageSize(pageSize);</span><br><span class="line"> <span class="comment">//根据前端传过来的数据配置</span></span><br><span class="line"> <span class="keyword">if</span> (data.size()==<span class="number">0</span>){</span><br><span class="line"> page.setList(Collections.emptyList());</span><br><span class="line"> <span class="keyword">return</span> page;</span><br><span class="line"> }</span><br><span class="line"> <span class="type">int</span> <span class="variable">beginIndex</span> <span class="operator">=</span> (pageNumber - <span class="number">1</span>) * pageSize;</span><br><span class="line"> <span class="type">int</span> <span class="variable">endIndex</span> <span class="operator">=</span> pageNumber * pageSize;</span><br><span class="line"> endIndex = Math.min(endIndex, data.size());</span><br><span class="line"> beginIndex = Math.min(beginIndex, data.size());</span><br><span class="line"> <span class="comment">//case1: beginIndex在范围内,endIndex在范围外</span></span><br><span class="line"> List<T> renderData;</span><br><span class="line"> <span class="keyword">if</span> (beginIndex < data.size() && endIndex > data.size()) {</span><br><span class="line"> renderData = data.subList(beginIndex, endIndex);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">//case2: beginIndex在范围外</span></span><br><span class="line"> <span class="keyword">if</span> (beginIndex >= data.size()) {</span><br><span class="line"> renderData = <span class="literal">null</span>;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">//case3:取中间数据</span></span><br><span class="line"> renderData = data.subList(beginIndex, endIndex);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> page.setList(renderData);</span><br><span class="line"> <span class="keyword">return</span> page;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="使用方法"><a href="#使用方法" class="headerlink" title="使用方法"></a>使用方法</h3><strong>注:pageNumber和pageSize一般由前端给,后端负责接受这两个值。这里示例为了方便直接定义</strong><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">pageNumber</span> <span class="operator">=</span> <span class="number">1</span> ; <span class="comment">//第一页</span></span><br><span class="line"><span class="type">int</span> <span class="variable">pageSize</span> <span class="operator">=</span> <span class="number">8</span> ;<span class="comment">//一页八条数据</span></span><br><span class="line"></span><br><span class="line">List<Record> data; <span class="comment">// 一个装着元素类型为Record的List</span></span><br><span class="line">PageKit<Record> pageKit = <span class="keyword">new</span> <span class="title class_">PageKit</span><>(); <span class="comment">// 每一条数据类型为Record</span></span><br><span class="line">PageMe<Record> data = pageKit.paginate(pageNumber, pageSize, data);</span><br></pre></td></tr></table></figure>
</li>
</ul>
<p>最后直接把data返回给前端即可</p>
<h3 id="适用范围"><a href="#适用范围" class="headerlink" title="适用范围"></a>适用范围</h3><p> 由于表设计有问题,导致一条sql语句很难将表连接起来,因此无法使用Jfianl自带的分页方法时,可以考虑这种。 本文给出的分页思想是:<strong>先将数据库中的数据查出来,并拼接成前端需要的数据类型</strong>(如前端需要学生名字,学生班级,语文成绩,<strong>但是他们在不同的表中又很难使用一条sql语句进行拼接</strong>)。<br>但是,分页的目的本应是提高查找效率,本文提出的分页没有提高查找效率。</p>
]]></content>
<categories>
<category>thought</category>
</categories>
<tags>
<tag>java</tag>
</tags>
</entry>
<entry>
<title>【解题笔记】 leetcode寻找两个正序数组的中位数</title>
<url>/2022/07/29/024b948f3297/</url>
<content><![CDATA[<h2 id="问题转化"><a href="#问题转化" class="headerlink" title="问题转化"></a>问题转化</h2><p>首先,考虑只有一个有序数组的情况:寻找中位数的问题可以转化为寻找一条分割线,满足以下两个条件:</p>
<ul>
<li>这条分割线在数组元素个素为奇数的时候,分割线左边的元素比右边多一个,中位数就是分割线左边的元素。</li>
<li>数组元素个数为偶数的时候,分割线左边的元素与右边的元素一样多。中位数是分割线左右两个元素的平均值。</li>
</ul>
<p>下面考虑两个有序数组,我们可以在两个数组上都划分一条分割线,这两条分割线有以下两个条件 :</p>
<ul>
<li>两条分割线左边的元素个数 = 两条分割线右边的元素个数</li>
<li>两条分割线左边的元素均小于右边的元素<br>此时,这道题就从寻找中位数转化为了寻找满足上述两个条件的分割线。题目要求时间复杂度为<code>O(log (m+n))</code>,因此能够直接联想到使用二分查找法来找分割线。<h2 id="解题步骤"><a href="#解题步骤" class="headerlink" title="解题步骤"></a>解题步骤</h2>寻找满足上述两个条件的分割线,那么我们就<strong>围绕上述两个条件</strong>来编码:为了描述方便,将nums1设置为长度较短的数组,nums2设置为长度较长的数组。<h3 id="第一个条件:"><a href="#第一个条件:" class="headerlink" title="第一个条件:"></a>第一个条件:</h3>要考虑奇数和偶数的情况,如果两个数组长度之和为奇数,那么我们就规定左边元素比右边元素多;如果两个数组长度之和为偶数,那么两边元素相等。由于Java的除法是向下取整(即5/2=2),因此可以讲奇偶两种情况合并,得到左边元素的总个数<script type="math/tex; mode=display">
totalLeft = \frac{m+n+1}{2}~~~~~~~~//其中m,n分别代表两个数组的长度</script></li>
</ul>
<h3 id="第二个条件:"><a href="#第二个条件:" class="headerlink" title="第二个条件:"></a>第二个条件:</h3><p>要使分割线左边元素均小于右边的元素,因为两个数组均为有序数组,那么满足以下条件即可:<br>设<code>i</code>为nums1分割线右边的元素,<code>j</code>为nums2分割线右边的元素。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">nums1[i-<span class="number">1</span>]<=nums2[j] && nums2[j-<span class="number">1</span>]<=nums1[i]</span><br></pre></td></tr></table></figure>
<h3 id="根据上述两个条件编码:"><a href="#根据上述两个条件编码:" class="headerlink" title="根据上述两个条件编码:"></a>根据上述两个条件编码:</h3><ul>
<li><p>据此,根据第一个条件,我们可以知道<code>i</code>和<code>j</code>的等量关系,即<code>i+j=totalLeft</code>。知道这个等量关系以后就很好办了,我们每次只需要移动<code>i</code>,让<code>j=totalLeft-i</code>即可。</p>
</li>
<li><p>根据第二个条件,我们只需要比较<code>nums1[i-1]</code>与<code>nums2[j]</code>的大小关系即可。如果前者大,说明分割线太靠右了;反之,继续向右寻找看还有没有满足条件的。<br>如下图:此时<code>i</code>指向元素2,<code>j</code>指向元素4。<code>1<4</code>,因此<code>nums1</code>的分割线右移。由<code>j=totalLeft-i</code>得,<code>nums2</code>的分割线左移。</p>
</li>
</ul>
<p><img src="https://raw.githubusercontent.com/zqqcee/img_repo/main/img/202305181521495.png" alt=""><br>用<strong>二分查找法</strong>编码:</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">//第一步:将长度最短的数组设置为nums1</span></span><br><span class="line"><span class="keyword">if</span> (nums2.length < nums1.length) {</span><br><span class="line"> <span class="type">int</span>[] temp = nums1;</span><br><span class="line"> nums1 = nums2;</span><br><span class="line"> nums2 = temp;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">//第二步:设置分割线左边元素的个数</span></span><br><span class="line"><span class="type">int</span> <span class="variable">m</span> <span class="operator">=</span> nums1.length;</span><br><span class="line"><span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> nums2.length;</span><br><span class="line"><span class="type">int</span> <span class="variable">totalLeft</span> <span class="operator">=</span> (m + n + <span class="number">1</span>) / <span class="number">2</span>; <span class="comment">//合并奇数和偶数的情况</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">//第三步,设置left与right,代表nums1分割线的查找区间。注:right需要设置为nums1.length,因为i可以为nums1.length,此时分割线就在nums1的最右边</span></span><br><span class="line"><span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"><span class="type">int</span> <span class="variable">right</span> <span class="operator">=</span> nums1.length;</span><br><span class="line"></span><br><span class="line"><span class="comment">/*两个条件:</span></span><br><span class="line"><span class="comment"> 1. 分隔线左边的元素个数等于分隔线右边的元素个素</span></span><br><span class="line"><span class="comment"> 2. 分隔线左边的所有元素均小于分隔线右边的元素个素</span></span><br><span class="line"><span class="comment"> 即nums1[i-1] <= nums2[j] && nums2[j-1] <= num1[i]</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"> 注:i是nums1分割线右边的第一个元素,它的下标 = 分隔线左边元素的个数;</span></span><br><span class="line"><span class="comment"> j同理,因此: i + j = totalLeft,可以根据该表达式,由i确定j。</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">while</span> (left < right) {</span><br><span class="line"> <span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> left + (right - left + <span class="number">1</span>) / <span class="number">2</span>;</span><br><span class="line"> <span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> totalLeft - i;</span><br><span class="line"> <span class="keyword">if</span> (nums1[i - <span class="number">1</span>] > nums2[j]) {</span><br><span class="line"> <span class="comment">//说明nums1的分隔线太靠右了,需要在[left,i-1处继续寻找]</span></span><br><span class="line"> right = i - <span class="number">1</span>;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">//需要在[i,right]处继续寻找</span></span><br><span class="line"> left = i;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//第四步:分割线划分完毕,确定两个数组分割线右边的位置i,j。此时left所指向的元素是nums1分割线右边的元素</span></span><br><span class="line"><span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> left;</span><br><span class="line"><span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> totalLeft - i;</span><br></pre></td></tr></table></figure>
<h3 id="极端情况:"><a href="#极端情况:" class="headerlink" title="极端情况:"></a>极端情况:</h3><p>下面讨论四种分割线划分的极端情况,仅以两种情况举例说明</p>
<ul>
<li><p><code>nums1</code>的分割线在数组最左边<br><img src="https://raw.githubusercontent.com/zqqcee/img_repo/main/img/202305181521620.png" alt="分割线在数组最左边"> </p>
<ul>
<li>因为<code>nums1</code>分隔线左边没有元素,因此可以得出两个数组分割线左边的最大值肯定在<code>nums2</code>中。</li>
<li>此时要把<code>nums1[i-1]</code>设置为无限小的值,使得最后选择左边元素最大值的时候不要选中它。</li>
</ul>
</li>
<li><p><code>nums1</code>的分割线在数组最右边<br> <img src="https://raw.githubusercontent.com/zqqcee/img_repo/main/img/202305181521338.png" alt="分割线在数组最右边"></p>
<ul>
<li>因为<code>nums1</code>分隔线右边没有元素,因此可以得出两个数组分割线右边的最小值肯定在<code>nums2</code>中。</li>
<li>此时要把<code>nums1[i]</code>设置为无限大的值,使得最后选择右边元素最小值的时候不要选中它。</li>
</ul>
</li>
<li><code>nums2</code>的分割线在数组最左边</li>
<li><code>nums2</code>的分割线在数组最左边<br>因此,考虑到四种极端情况,要在获得中位数前加上以下代码:<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">nums1LeftMax</span> <span class="operator">=</span> i == <span class="number">0</span> ? Integer.MIN_VALUE : nums1[i - <span class="number">1</span>];</span><br><span class="line"><span class="comment">//此时nums1分割线左边没有元素了,因此nums1分割线左边元素的最大值要设置成无限小,在比较时直接选中nums2分割线的左边元素,其余同理</span></span><br><span class="line"><span class="type">int</span> <span class="variable">nums1RightMin</span> <span class="operator">=</span> i == m ? Integer.MAX_VALUE : nums1[i];</span><br><span class="line"><span class="type">int</span> <span class="variable">nums2LeftMax</span> <span class="operator">=</span> j == <span class="number">0</span> ? Integer.MIN_VALUE : nums2[j - <span class="number">1</span>];</span><br><span class="line"><span class="type">int</span> <span class="variable">nums2RightMin</span> <span class="operator">=</span> j == n ? Integer.MAX_VALUE : nums2[j];</span><br></pre></td></tr></table></figure>
<h3 id="得到中位数"><a href="#得到中位数" class="headerlink" title="得到中位数"></a>得到中位数</h3>分割线划分完毕后,即可求得中位数:</li>
<li>数组长度和为奇数:两条分割线左边元素的最大值</li>
<li>数组长度和为偶数:两条分割线左边元素最大值与右边元素最小值的平均值<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">//最后一步:中位数</span></span><br><span class="line"><span class="keyword">if</span> ((m + n) % <span class="number">2</span> == <span class="number">0</span>) {</span><br><span class="line"> <span class="comment">//偶数</span></span><br><span class="line"> <span class="keyword">return</span> (<span class="type">double</span>)(Math.max(nums1LeftMax, nums2LeftMax) + Math.min(nums1RightMin, nums2RightMin)) / <span class="number">2</span>;</span><br><span class="line">}</span><br><span class="line"><span class="keyword">return</span> Math.max(nums1LeftMax, nums2LeftMax);</span><br></pre></td></tr></table></figure>
<h2 id="注意"><a href="#注意" class="headerlink" title="注意"></a>注意</h2>在写二分查找法时,如果<strong>查找到右区间时</strong>使用<code>left=middle</code>的方式编码,那么需要注意避免<strong>死循环</strong>的情况。<br>比如某个区间只有两个数<code>[i,j]</code>,如果<code>left=i</code>,<code>right=j</code>,那么若中间值一直不动,就会陷入死循环。因此确定中间值的时候应该使用<code>left + (right - left + 1) / 2</code>,这样就能保证如果二分查找查到了右区间,左边界加一。详情可见代码</li>
</ul>
<h2 id="完整代码"><a href="#完整代码" class="headerlink" title="完整代码"></a>完整代码</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> {</span><br><span class="line"> <span class="keyword">public</span> <span class="type">double</span> <span class="title function_">findMedianSortedArrays</span><span class="params">(<span class="type">int</span>[] nums1, <span class="type">int</span>[] nums2)</span> {</span><br><span class="line"></span><br><span class="line"> <span class="comment">//第一步:将长度最短的数组设置为nums1</span></span><br><span class="line"> <span class="keyword">if</span> (nums2.length < nums1.length) {</span><br><span class="line"> <span class="type">int</span>[] temp = nums1;</span><br><span class="line"> nums1 = nums2;</span><br><span class="line"> nums2 = temp;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"> <span class="comment">//第二步:设置分割线左边元素的个数</span></span><br><span class="line"> <span class="type">int</span> <span class="variable">m</span> <span class="operator">=</span> nums1.length;</span><br><span class="line"> <span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> nums2.length;</span><br><span class="line"> <span class="type">int</span> <span class="variable">totalLeft</span> <span class="operator">=</span> (m + n + <span class="number">1</span>) / <span class="number">2</span>; <span class="comment">//合并奇数和偶数的情况</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"> <span class="comment">//第三步,设置left与right,代表nums1分割线的查找区间。注:right需要设置为nums1.length,因为i可以为nums1.length,此时分割线就在nums1的最右边</span></span><br><span class="line"> <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"> <span class="type">int</span> <span class="variable">right</span> <span class="operator">=</span> nums1.length;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/*两个条件:</span></span><br><span class="line"><span class="comment"> 1. 分隔线左边的元素个数等于分隔线右边的元素个素</span></span><br><span class="line"><span class="comment"> 2. 分隔线左边的所有元素均小于分隔线右边的元素个素</span></span><br><span class="line"><span class="comment"> 即nums1[i-1] <= nums2[j] && nums2[j-1] <= num1[i]</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"> 注:i是nums1分割线右边的第一个元素,它的下标 = 分隔线左边元素的个数;</span></span><br><span class="line"><span class="comment"> j同理,因此: i + j = totalLeft,可以根据该表达式,由i确定j。</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">while</span> (left < right) {</span><br><span class="line"> <span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> left + (right - left + <span class="number">1</span>) / <span class="number">2</span>;</span><br><span class="line"> <span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> totalLeft - i;</span><br><span class="line"> <span class="keyword">if</span> (nums1[i - <span class="number">1</span>] > nums2[j]) {</span><br><span class="line"> <span class="comment">//说明nums1的分隔线太靠右了,需要在[left,i-1处继续寻找]</span></span><br><span class="line"> right = i - <span class="number">1</span>;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">//需要在[i,right]处继续寻找</span></span><br><span class="line"> left = i;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//第四步:分割线划分完毕,确定两个数组分割线右边的位置i,j。此时left所指向的元素是nums1分割线右边的元素</span></span><br><span class="line"> <span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> left;</span><br><span class="line"> <span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> totalLeft - i;</span><br><span class="line"></span><br><span class="line"> <span class="comment">//第五步:确定中位数,无论是奇数还是偶数,中位数都只与两个数组分割线左边元素的最大值x 和 右边元素的最小值y 有关。</span></span><br><span class="line"> <span class="comment">//因为设定分割线左边元素等于右边元素,或大于一,因此中位数=x 或中位数=(x+y)/2</span></span><br><span class="line"> <span class="type">int</span> <span class="variable">nums1LeftMax</span> <span class="operator">=</span> i == <span class="number">0</span> ? Integer.MIN_VALUE : nums1[i - <span class="number">1</span>];</span><br><span class="line"> <span class="comment">//此时nums1分割线左边没有元素了,因此nums1分割线左边元素的最大值要设置成无限小,在比较时直接选中nums2分割线的左边元素,其余同理</span></span><br><span class="line"> <span class="type">int</span> <span class="variable">nums1RightMin</span> <span class="operator">=</span> i == m ? Integer.MAX_VALUE : nums1[i];</span><br><span class="line"> <span class="type">int</span> <span class="variable">nums2LeftMax</span> <span class="operator">=</span> j == <span class="number">0</span> ? Integer.MIN_VALUE : nums2[j - <span class="number">1</span>];</span><br><span class="line"> <span class="type">int</span> <span class="variable">nums2RightMin</span> <span class="operator">=</span> j == n ? Integer.MAX_VALUE : nums2[j];</span><br><span class="line"></span><br><span class="line"> <span class="comment">//最后一步:中位数</span></span><br><span class="line"> <span class="keyword">if</span> ((m + n) % <span class="number">2</span> == <span class="number">0</span>) {</span><br><span class="line"> <span class="comment">//偶数</span></span><br><span class="line"> <span class="keyword">return</span> (<span class="type">double</span>)(Math.max(nums1LeftMax, nums2LeftMax) + Math.min(nums1RightMin, nums2RightMin)) / <span class="number">2</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> Math.max(nums1LeftMax, nums2LeftMax);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
]]></content>
<categories>
<category>note</category>
</categories>
<tags>
<tag>leetcode</tag>
</tags>
</entry>
<entry>
<title>【趟坑记录】d3.zoom()的正确使用姿势 @d3.v7</title>
<url>/2023/07/20/065545eb7050/</url>
<content><![CDATA[<h1 id="【趟坑记录】d3-zoom-的正确使用姿势-d3-v7"><a href="#【趟坑记录】d3-zoom-的正确使用姿势-d3-v7" class="headerlink" title="【趟坑记录】d3.zoom()的正确使用姿势 @d3.v7"></a>【趟坑记录】<code>d3.zoom()</code>的正确使用姿势 @d3.v7</h1><p>在开发一个D3应用的时候遇到了一个<code>zoom</code>相关的问题,记录解决思路与方案</p>
<h3 id="问题重现"><a href="#问题重现" class="headerlink" title="问题重现"></a>问题重现</h3><p>最近在开发一个D3应用的时候遇到了一个<code>zoom</code>相关的问题,应用里有一个功能叫<strong>全景聚焦</strong>。我们都知道画布由两个标签组成(见<a href="https://zqqcee.github.io/2023/03/24/3423a90bb58e/">实现autoZoom(),画布自适应放缩并居中@D3.js-v5</a>),最外层的是固定视口<code><svg></code>,一般将<code>zoom</code>事件绑定在<code><svg></code>上;内层是具体的画布,是一个<code><g></code>标签,在<code><svg></code>中的放缩与平移操作都作用在<code><g></code>上,修改<code><g></code>的<code>transform</code>属性。这么做是为了避免用户将<code><svg></code>元素拖动到窗口之外后丢失拖动焦点,无法将其拖回。而如果使<code><svg></code>不动,<code><g></code>被拖动,那么拖动焦点就不会丢失,用户将<code><g></code>元素移动至视口外后,还能将其拖回来。</p>
<p>我之前习惯这么写拖动平移:</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> svg = d3.<span class="title function_">select</span>(<span class="string">'#viewport'</span>)</span><br><span class="line"> .<span class="title function_">attr</span>(<span class="string">'width'</span>, width)</span><br><span class="line"> .<span class="title function_">attr</span>(<span class="string">'height'</span>, height)</span><br><span class="line"><span class="keyword">const</span> g = svg.<span class="title function_">append</span>(<span class="string">'g'</span>)</span><br><span class="line"> .<span class="title function_">attr</span>(<span class="string">'id'</span>, <span class="string">'container'</span>)</span><br><span class="line"> .<span class="title function_">attr</span>(<span class="string">'width'</span>, width)</span><br><span class="line"> .<span class="title function_">attr</span>(<span class="string">'height'</span>, height)</span><br><span class="line"></span><br><span class="line">svg.<span class="title function_">call</span>(</span><br><span class="line"> d3.<span class="title function_">zoom</span>().<span class="title function_">on</span>(<span class="string">'zoom'</span>, <span class="function">(<span class="params">e</span>) =></span> {</span><br><span class="line"> <span class="keyword">const</span> transform = <span class="string">`translate(<span class="subst">${e.transform.x}</span>,<span class="subst">${e.transform.y}</span>) scale(<span class="subst">${e.transform.k}</span>)`</span></span><br><span class="line"> g.<span class="title function_">attr</span>(<span class="string">'transform'</span>, transform)</span><br><span class="line"> })</span><br><span class="line">)</span><br></pre></td></tr></table></figure>
<p>在一些业务场景中,往往需要对<code><g></code>元素进行特定的平移与放缩。如:自动缩放至视口中央,放大至当前的1.5倍。然而,在其他直接地方修改了<code><g></code> 的<code>‘transform’</code>属性后,如:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> offsetX = <span class="number">10</span>;</span><br><span class="line"><span class="keyword">const</span> offsetY = <span class="number">10</span>;</span><br><span class="line">g.<span class="title function_">attr</span>(<span class="string">'transform'</span>,<span class="string">`translate(<span class="subst">${offsetX}</span>,<span class="subst">${offsetY}</span>)`</span></span><br></pre></td></tr></table></figure>
<p>,问题就出现了,如下:</p>
<p><img src="https://raw.githubusercontent.com/zqqcee/img_repo/main/img/202307200945951.gif" alt="bugreproduce"></p>
<p>可以看到,在设置了特定的<code>'transform'</code>后,再进行拖动,会出现瞬移。</p>
<h3 id="原因分析"><a href="#原因分析" class="headerlink" title="原因分析"></a>原因分析</h3><p>因为监听的<code>zoom</code>事件是通过<code>e.transform</code>来进行放缩的。而在修改<code><g></code>元素的<code>‘transform’</code>属性为一个特定值后,再进行拖动,会从上一次的<code>e.tranform</code>值开始修改,因此会出现错误。</p>
<p>举例说明:</p>
<ol>
<li>用户拖动,<code>e.transform</code>的数值修改为了<code>transform_1</code></li>
<li>有一个自动放缩函数<code>autoZoom</code>,将<code><g></code>的<code>'transform'</code>修改为了<code>transform_2</code></li>
<li>用户再次进行拖动,<code><g></code>的<code>'transform'</code>会从<code>transform_1</code>开始修改,因此会出现从<code>transform_2</code>到<code>transform_1</code>的瞬移。</li>
</ol>
<h3 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h3><p>得知原因之后,解决方案也非常明了。就是在任何需要进行放缩平移的地方,都将<code>transform</code>进行缓存,下一次再需要进行放缩平移操作时,从上一次的<code>transform</code>开始进行更改即可。</p>
<p>一开始我想的解决方案是在每次鼠标拖动时都记录一个偏移量,但是这个偏移量比较难获取,心想<code>d3</code>这么大个库应该不至于用这么蠢的办法,应该有更好用的方案。</p>
<p>查了一下官方的API,发现了一个叫<code>zoomTransform(node)</code>的接口,这个接口传入的是一个<code>HTML node</code>,需要用<code>d3.select(xx).node()</code>来获得,可以获取这个<code>node</code>的放缩数据。官方文档是这么说的:</p>
<blockquote>
<p>Internally, an element’s transform is stored as <em>element</em>.__zoom; however, you should use this method rather than accessing it directly. If the given <em>node</em> has no defined transform, returns the transform of the closest ancestor, or if none exists, the identity transformation</p>
<hr>
<p>在内部,元素的变换存储为 <strong>element.__zoom</strong>;但是,<strong>您应该使用此方法(指的是zoomTransform)而不是直接访问它</strong>。如果给定节点没有定义的变换,则返回最近祖先的变换,或者如果不存在,则返回恒等变换。返回的变换表示以下形式的二维变换矩阵(略):</p>
<p>These properties should be considered read-only; instead of mutating a transform, use <a href="https://d3js.org/d3-zoom#transform_scale"><em>transform</em>.scale</a> and <a href="https://d3js.org/d3-zoom#transform_translate"><em>transform</em>.translate</a> to derive a new transform.</p>
<hr>
<p>这些属性应被视为只读;使用transform.scale和transform.translate来派生新的变换,而不是改变变换。(下文将介绍如何派生新的变换)</p>
</blockquote>
<p>进一步查看了源码,发现在<code>svg.call(zoom)</code>这个操作后,<code><svg></code>这个HTML node就会绑上一个<code>__zoom</code> 属性,这个<code>__zoom</code>属性记录的是<code>transform</code>参数,也就是我们对<code><svg></code>进行的放缩平移变换。为此我还特定打印了一下,发现确实如此:</p>
<p><img src="/Users/zqqcee/Library/Application Support/typora-user-images/image-20230720100136154.png" alt="log的结果"></p>
<p>那现在事情就变得很简单了,可以转变一下思路。之前我一直希望能够在<code>autoZoom()</code>之后,获得<code>"zoom"</code>事件的偏移量,使得我能够接着这个<code>'transform'</code>值修改。那么既然我无法获得偏移量,可以尝试在<code>autoZoom()</code>方法中不要直接修改<code><g></code>的<code>'transform'</code>属性,而去修改<code><svg>.__zoom</code>值。</p>
<h4 id="放缩平移写法"><a href="#放缩平移写法" class="headerlink" title="放缩平移写法"></a>放缩平移写法</h4><p>在一开始时,使用d3.zoom()创建放缩对象<code>zoom</code>,并在任何时刻都使用<code><svg></code>来<code>call(zoom)</code>修改放缩值。在绑定<code>"zoom"</code>事件时,因为<code><svg></code> <code>call</code>了<code>zoom</code>,因此任何偏移量都会记录在<code><svg></code>,在修改<code><g></code>的<code>'transform'</code>属性时,可以直接使用<code>d3.zoomTransform(svg.node())</code>来获得<code><svg>.__zoom</code>来进行应用。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> svg = d3.<span class="title function_">select</span>(<span class="string">'body'</span>).<span class="title function_">append</span>(<span class="string">'svg'</span>);</span><br><span class="line"><span class="keyword">const</span> g = svg.<span class="title function_">append</span>(<span class="string">'g'</span>);</span><br><span class="line"><span class="keyword">const</span> zoom = d3.<span class="title function_">zoom</span>().<span class="title function_">on</span>(<span class="string">'zoom'</span>,<span class="function">()=></span>{</span><br><span class="line"> g.<span class="title function_">attr</span>(<span class="string">'transform'</span>, d3.<span class="title function_">zoomTransform</span>(svg.<span class="title function_">node</span>()));</span><br><span class="line">})</span><br></pre></td></tr></table></figure>
<h4 id="特殊修改-39-transform-39-函数的写法"><a href="#特殊修改-39-transform-39-函数的写法" class="headerlink" title="特殊修改'transform'函数的写法"></a>特殊修改<code>'transform'</code>函数的写法</h4><p>这里需要说明一下<code>autoZoom()</code>的写法,假设我们现在已经计算出了<code>'transform'</code>数值<code>transformX</code>,<code>transformY</code>,<code>k</code>。现在需要修改<code><svg></code>的<code>__zoom</code>属性为当前的<code>'transform'</code>数值。</p>
<p>查阅了官方文档,找到了可以使用的API:</p>
<ul>
<li><code>d3.zoomIdentity</code>。这个API可以创建一个新的<code>'transform':{x:0,y:0,k:1}</code>,并允许使用<code>transform.translate(x,y), transform.scale(k)</code>对其进行更改。</li>
<li><code>selection.call(zoom.transform,new_transform);</code>使用这个接口能够将<code><svg>.__zoom</code>修改为<code>new_transform</code></li>
</ul>
<p>综上,代码为:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> new_transform = d3.<span class="property">zoomIdentity</span>.<span class="title function_">translate</span>(transformX, transformY).<span class="title function_">scale</span>(k);</span><br><span class="line">d3.<span class="title function_">select</span>(<span class="string">'svg'</span>).<span class="title function_">call</span>(zoom.<span class="property">transform</span>,new_transform);</span><br></pre></td></tr></table></figure>
<h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>简而言之,任何对<code><g></code>的放缩与平移操作,都需要作用在<code><svg></code>上,并且使用<code><svg>.__zoom()</code>来修改。</p>
<p>完整代码:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">//zoom事件绑定</span></span><br><span class="line"><span class="keyword">const</span> svg = d3.<span class="title function_">select</span>(<span class="string">'body'</span>).<span class="title function_">append</span>(<span class="string">'svg'</span>);</span><br><span class="line"><span class="keyword">const</span> g = svg.<span class="title function_">append</span>(<span class="string">'g'</span>);</span><br><span class="line"><span class="keyword">const</span> zoom = d3.<span class="title function_">zoom</span>().<span class="title function_">on</span>(<span class="string">'zoom'</span>,<span class="function">()=></span>{</span><br><span class="line"> g.<span class="title function_">attr</span>(<span class="string">'transform'</span>, d3.<span class="title function_">zoomTransform</span>(svg.<span class="title function_">node</span>()));</span><br><span class="line">})</span><br><span class="line"></span><br><span class="line"><span class="comment">//需要修改特定transform的函数,以autoZoom为例</span></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">autoZoom</span> = (<span class="params">transformX,transformY,k</span>) =>{</span><br><span class="line"> <span class="keyword">const</span> new_transform = d3.<span class="property">zoomIdentity</span>.<span class="title function_">translate</span>(transformX, transformY).<span class="title function_">scale</span>(k);</span><br><span class="line"> d3.<span class="title function_">select</span>(<span class="string">'svg'</span>).<span class="title function_">call</span>(zoom.<span class="property">transform</span>,new_transform);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<categories>
<category>visual analytics</category>
</categories>
<tags>
<tag>javascript</tag>
</tags>
</entry>
<entry>
<title>由“object is not extensible”报错引发的思考及解决方案@React-Toolkit/@Immer.js</title>
<url>/2022/11/13/3e5d23898d6d/</url>
<content><![CDATA[<h2 id="问题重述"><a href="#问题重述" class="headerlink" title="问题重述"></a>问题重述</h2><p> 最近在做一个数据浏览平台,如图所示</p>
<p><img src="https://raw.githubusercontent.com/zqqcee/img_repo/main/img/202305181518651.png" alt="数据浏览平台"></p>
<p> 大致的编码逻辑是左上角的数据集选择器,控制全局UI的改变。比如<strong>左部的树形控件数据</strong>,<strong>画布中的节点链接图</strong>等等,都是根据当前所选的数据集来定的。这种组件间的状态复用,自然而然就想到把数据集作为一个状态来交给redux管理。</p>
<h3 id="使用redux-toolkit"><a href="#使用redux-toolkit" class="headerlink" title="使用redux-toolkit"></a>使用redux-toolkit</h3><p> 好的,现在开始查redux官方文档。因为刚学会react,教程中redux的store中使用的是<code>createStore()</code>创建的,但是这个方法目前已经弃用了,官方建议使用的是<code>configureStore()</code>。经过一番文档的查阅,开始使用<code>createSlice()</code>来重写reducer。</p>
<h4 id="使用createSlice"><a href="#使用createSlice" class="headerlink" title="使用createSlice()"></a>使用<code>createSlice()</code></h4><p> 这里直接贴上我这部分slice的<strong>错误代码</strong></p>
<ul>
<li><strong>创建slice</strong></li>
</ul>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// redux/optionSlice.js</span></span><br><span class="line"><span class="keyword">import</span> { createSlice } <span class="keyword">from</span> <span class="string">"@reduxjs/toolkit"</span>;</span><br><span class="line"><span class="keyword">import</span> { dataSets } <span class="keyword">from</span> <span class="string">"../utils/getData"</span>;</span><br><span class="line"><span class="keyword">import</span> { <span class="variable constant_">HIGHLIGHT</span> } <span class="keyword">from</span> <span class="string">"./constant"</span>;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> optionSlice = <span class="title function_">createSlice</span>({</span><br><span class="line"> <span class="attr">name</span>: <span class="string">'option'</span>,</span><br><span class="line"> <span class="attr">initialState</span>: {</span><br><span class="line"> <span class="attr">data</span>: dataSets[<span class="string">"case1"</span>],</span><br><span class="line"> <span class="attr">mode</span>: <span class="variable constant_">HIGHLIGHT</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">reducers</span>: {</span><br><span class="line"> <span class="comment">//这里对于state的解释在下文</span></span><br><span class="line"> <span class="attr">changedata</span>: (<span class="function">(<span class="params">state, action</span>) =></span> {</span><br><span class="line"> state.<span class="property">data</span> = action.<span class="property">payload</span></span><br><span class="line"> }),</span><br><span class="line"> <span class="attr">changemode</span>: (<span class="function">(<span class="params">state, action</span>) =></span> {</span><br><span class="line"> state.<span class="property">mode</span> = action.<span class="property">payload</span></span><br><span class="line"> })</span><br><span class="line"> }</span><br><span class="line">})</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> { changedata, changemode } = optionSlice.<span class="property">actions</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> optionSlice.<span class="property">reducer</span></span><br></pre></td></tr></table></figure>
<p><code>slice</code>有两个导出,一个是在内部负责操作状态的action;一个是reducer</p>
<p>我还有一另外一个<code>selectionSlice</code>负责管理其他的状态,这里考虑到篇幅就不给出了。</p>
<h4 id="在index-js中融合两个silce"><a href="#在index-js中融合两个silce" class="headerlink" title="在index.js中融合两个silce"></a>在<code>index.js</code>中融合两个silce</h4><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// redux/index.js</span></span><br><span class="line"><span class="keyword">import</span> optionReducer <span class="keyword">from</span> <span class="string">'./optionSlice'</span>; <span class="comment">//注意,这里引入的是slice中导出的reducer,slice有两个导出:reducer和action</span></span><br><span class="line"><span class="keyword">import</span> selectionReducer <span class="keyword">from</span> <span class="string">"./selectionSlice"</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> reducers = {</span><br><span class="line"> <span class="attr">option</span>: optionReducer,</span><br><span class="line"> <span class="attr">selection</span>: selectionReducer</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="在store-js配置store,并使用-lt-Provider-store-store-gt-让所有组件都可以使用redux中管理的状态"><a href="#在store-js配置store,并使用-lt-Provider-store-store-gt-让所有组件都可以使用redux中管理的状态" class="headerlink" title="在store.js配置store,并使用<Provider store={store}>让所有组件都可以使用redux中管理的状态"></a>在store.js配置store,并使用<code><Provider store={store}></code>让所有组件都可以使用redux中管理的状态</h4><ul>
<li>配置<code>store</code></li>
</ul>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">// redux/store.js</span></span><br><span class="line"><span class="keyword">import</span> { configureStore } <span class="keyword">from</span> <span class="string">'@reduxjs/toolkit'</span></span><br><span class="line"><span class="keyword">import</span> { reducers } <span class="keyword">from</span> <span class="string">'./index'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> store = <span class="title function_">configureStore</span>(</span><br><span class="line"> {</span><br><span class="line"> <span class="attr">reducer</span>: reducers,<span class="comment">//这里内置了combineReducer</span></span><br><span class="line"> }</span><br><span class="line">);</span><br></pre></td></tr></table></figure>
<ul>
<li>添加<code>Provider</code></li>
</ul>
<p> 在App标签外部套上<code><Provider></code>标签</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> { createRoot } <span class="keyword">from</span> <span class="string">'react-dom/client'</span></span><br><span class="line"><span class="keyword">import</span> <span class="title class_">App</span> <span class="keyword">from</span> <span class="string">'./App'</span></span><br><span class="line"><span class="keyword">import</span> { <span class="title class_">BrowserRouter</span> } <span class="keyword">from</span> <span class="string">"react-router-dom"</span>;</span><br><span class="line"><span class="keyword">import</span> { <span class="title class_">Provider</span> } <span class="keyword">from</span> <span class="string">'react-redux'</span>;</span><br><span class="line"><span class="keyword">import</span> { store } <span class="keyword">from</span> <span class="string">'./redux/store'</span></span><br><span class="line"></span><br><span class="line"><span class="title function_">createRoot</span>(<span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">'root'</span>)).<span class="title function_">render</span>(</span><br><span class="line"> <span class="language-xml"><span class="tag"><<span class="name">Provider</span> <span class="attr">store</span>=<span class="string">{store}</span>></span></span></span><br><span class="line"><span class="language-xml"> <span class="tag"><<span class="name">App</span> /></span></span></span><br><span class="line"><span class="language-xml"> <span class="tag"></<span class="name">Provider</span>></span></span></span><br><span class="line">)</span><br></pre></td></tr></table></figure>
<h3 id="绘制节点链接图"><a href="#绘制节点链接图" class="headerlink" title="绘制节点链接图"></a>绘制节点链接图</h3><p> 用户选择一份数据集,就会把这份数据集交给redux管理,在其他组件中如果想要取用数据集,使用<code>useSelector(state => state.option.data)</code>即可取用。<strong><font color="red">问题就发生在这一步</font></strong></p>
<p> 我先简述一下我的代码:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// componnets/Canvas/index.jsx</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">Canvas</span>(<span class="params"></span>){</span><br><span class="line"> <span class="keyword">const</span> data = <span class="title function_">useSelector</span>(<span class="function"><span class="params">state</span> =></span> state.<span class="property">option</span>.<span class="property">data</span>)</span><br><span class="line"> </span><br><span class="line"> <span class="title function_">useLayoutEffect</span>(<span class="function">()=></span>{</span><br><span class="line"> <span class="title function_">initCanvas</span>() <span class="comment">//drawLayout</span></span><br><span class="line"> },[data])</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">const</span> <span class="title function_">initCanvas</span> = (<span class="params"></span>) => {</span><br><span class="line"> <span class="comment">// .....append canvas</span></span><br><span class="line"> <span class="keyword">const</span> nodes = data.<span class="property">nodes</span></span><br><span class="line"> <span class="keyword">const</span> links = data.<span class="property">links</span></span><br><span class="line"> </span><br><span class="line"> <span class="comment">//append circle,line......</span></span><br><span class="line"> </span><br><span class="line"> <span class="keyword">let</span> simulation = d3.<span class="title function_">forceSimulation</span>(nodes)</span><br><span class="line"> .<span class="title function_">force</span>(<span class="string">"link"</span>, d3.<span class="title function_">forceLink</span>(links).<span class="title function_">id</span>(<span class="function"><span class="params">d</span> =></span> {</span><br><span class="line"> <span class="keyword">return</span> d.<span class="property">mgmt_ip</span></span><br><span class="line"> }).<span class="title function_">strength</span>(<span class="number">0.5</span>).<span class="title function_">distance</span>(<span class="number">10</span>))</span><br><span class="line"> <span class="comment">//......some force option</span></span><br><span class="line"> .<span class="title function_">on</span>(<span class="string">"tick"</span>,<span class="function">()=></span>{</span><br><span class="line"> <span class="comment">//refresh canvas</span></span><br><span class="line"> })</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> </span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <span class="language-xml"><span class="tag"><<span class="name">div</span>></span></span></span><br><span class="line"><span class="language-xml"> <span class="tag"><<span class="name">div</span> <span class="attr">className</span>=<span class="string">"container"</span>></span> <span class="tag"></<span class="name">div</span>></span></span></span><br><span class="line"><span class="language-xml"> <span class="tag"></<span class="name">div</span>></span></span></span><br><span class="line"> )</span><br><span class="line"> </span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p> 这个代码很简单,我在<code>useLayoutEffect()</code>这个钩子里编写了一个画布初始化函数<code>initCanvas()</code>。目的是让组件挂载前,先在一个<code><div></code>中添加一个canvas,并绘制出数据。</p>
<h3 id="报错重现"><a href="#报错重现" class="headerlink" title="报错重现"></a>报错重现</h3><p> <strong><font color="maroon">结果这个代码直接报红了,报了一个我从没见过的错误: “Uncaught TypeError:Cannot add property vx,object is extensible”</font></strong></p>
<p><img src="https://raw.githubusercontent.com/zqqcee/img_repo/main/img/202305181519528.png" alt=""></p>
<p> 从这个报错信息很容易就能知道,是在我设置力模拟器时,调用<code>d3.forceLink(links)</code>绑定连边,和绑定节点时,无法像边数据和点数据中添加vx,vy等属性导致的。</p>
<p> 为了进一步验证这个特点,我用以下代码验证了我拿到的数据是否真的不可拓展</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> nodes = data.<span class="property">nodes</span></span><br><span class="line"><span class="keyword">const</span> links = data.<span class="property">links</span></span><br><span class="line">nodes.<span class="title function_">forEach</span>(<span class="function"><span class="params">node</span> =></span> {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(node.<span class="title function_">isExtensible</span>())</span><br><span class="line">})</span><br><span class="line">links.<span class="title function_">forEach</span>(<span class="function"><span class="params">link</span>=></span>{</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(link.<span class="title function_">isExtensible</span>())</span><br><span class="line">})</span><br></pre></td></tr></table></figure>
<p> 毫无意外,控制台输出了清一色的<code>false</code>。</p>
<h2 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h2><h3 id="采用拷贝对象的方式解决(笨方法)"><a href="#采用拷贝对象的方式解决(笨方法)" class="headerlink" title="采用拷贝对象的方式解决(笨方法)"></a>采用拷贝对象的方式解决(笨方法)</h3><p> 所以这个问题可以基本确定是因为我的数据不可拓展造成的,虽然不知道为什么。但是解决这个的办法无非就是让我的数据能够被拓展。但是搜了半天解除不可拓展性的办法,找不到。于是只能采用拷贝对象的方式,拷贝一份新的对象。</p>
<p> 拷贝分两种方式:浅拷贝与深拷贝。在有指针的情况下,<strong>浅拷贝只是增加了一个指针指向已经存在的内存</strong>,而深拷贝就是<strong>增加一个指针并且申请一个新的内存</strong>,<strong>使这个增加的指针指向这个新的内存</strong>。显然,我们需要使用深拷贝,申请一个新的内存存放拷贝的对象。</p>
<p> <code>nodes</code>与<code>links</code>数组中存放的obj如图所示:</p>
<figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">{</span></span><br><span class="line"> nodes<span class="punctuation">:</span><span class="punctuation">[</span></span><br><span class="line"> <span class="punctuation">{</span></span><br><span class="line"> id<span class="punctuation">:</span>xxxx<span class="punctuation">,</span></span><br><span class="line"> role<span class="punctuation">:</span>xxxx<span class="punctuation">,</span></span><br><span class="line"> type<span class="punctuation">:</span>xxxx</span><br><span class="line"> <span class="punctuation">}</span></span><br><span class="line"><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line"> links<span class="punctuation">:</span><span class="punctuation">[</span></span><br><span class="line"> <span class="punctuation">{</span></span><br><span class="line"> source<span class="punctuation">:</span>xxxx<span class="punctuation">,</span></span><br><span class="line"> target<span class="punctuation">:</span>xxxx<span class="punctuation">,</span></span><br><span class="line"> <span class="punctuation">}</span></span><br><span class="line"><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">}</span></span><br></pre></td></tr></table></figure>
<p> 因此我们使用对象拓展符<code>{...node},{...link}</code>即可完成深拷贝,具体代码如下:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> newNode = nodes.<span class="title function_">map</span>(<span class="function"><span class="params">node</span> =></span> ({...node}));</span><br><span class="line"><span class="keyword">const</span> newLink = links.<span class="title function_">map</span>(<span class="function"><span class="params">link</span> =></span> ({...link}))</span><br></pre></td></tr></table></figure>
<p> 接着我们使用newNode和newLink替换原来的nodes和links,就OK了。</p>
<h3 id="把redux中存储的数据对象替换为数据名"><a href="#把redux中存储的数据对象替换为数据名" class="headerlink" title="把redux中存储的数据对象替换为数据名"></a>把redux中存储的数据对象替换为数据名</h3><p> 上面的办法显然很蠢······。<strong>我慢慢开始意识到这个对象的不可拓展性很可能是redux帮我处理的</strong>,因为<strong>我们在redux中存放的数据应该由对应的reducer来进行更改</strong>,如果外部能够更改会导致UI组件中获取的状态出现错误。</p>
<p> 因为发现这个问题已经很晚了,我没有急着去验证的想法是不是对的,因为我想赶紧把我的蠢方法换掉,让我的程序看起来别那么烂。我之前建立了一个函数帮我提供数据集,代码如下:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> case1 <span class="keyword">from</span> <span class="string">"../assets/case1.json"</span></span><br><span class="line"><span class="keyword">import</span> case2 <span class="keyword">from</span> <span class="string">"../assets/case2.json"</span></span><br><span class="line"><span class="keyword">import</span> case3 <span class="keyword">from</span> <span class="string">"../assets/case3.json"</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> <span class="title function_">generate</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="keyword">const</span> datasets = {</span><br><span class="line"> case1,</span><br><span class="line"> case2,</span><br><span class="line"> case3</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> datasets;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> dataSets = <span class="title function_">generate</span>();</span><br></pre></td></tr></table></figure>
<p> 这么做的目的是我在组件中直接使用<code>import {dataSets} from "../util/getData.js"</code>就能获取到全部数据集了。</p>
<p> 写到这,应该很明白了。正确的思路应该是将数据集的名字,如case1,case2,case3……交给redux来管理,用户每次切换数据集,就通知reducer更改当前的数据集名称。在组件中如果想要使用数据的话就以下代码来获取。这么做显然比把整份数据交给redux管理更加合理。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> {dataSets} <span class="keyword">from</span> <span class="string">"../util/getData.js</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">function Component(){</span></span><br><span class="line"><span class="string"> const dataName = useSelector(state => state.option.dataName)</span></span><br><span class="line"><span class="string"> const data = dataSets[dataName]</span></span><br><span class="line"><span class="string"> </span></span><br><span class="line"><span class="string"> //func body</span></span><br><span class="line"><span class="string"> return ....</span></span><br><span class="line"><span class="string">}</span></span><br><span class="line"><span class="string"></span></span><br></pre></td></tr></table></figure>
<p>这里贴上一个正确代码,和之前相比,我把交给redux管理的状态从<code>data</code>换成了<code>dataName</code></p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// redux/optionSlice.js</span></span><br><span class="line"><span class="keyword">import</span> { createSlice } <span class="keyword">from</span> <span class="string">"@reduxjs/toolkit"</span>;</span><br><span class="line"><span class="keyword">import</span> { <span class="variable constant_">HIGHLIGHT</span> } <span class="keyword">from</span> <span class="string">"./constant"</span>;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">/**data option */</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> optionSlice = <span class="title function_">createSlice</span>({</span><br><span class="line"> <span class="attr">name</span>: <span class="string">'option'</span>,</span><br><span class="line"> <span class="attr">initialState</span>: {</span><br><span class="line"> <span class="attr">dataName</span>: <span class="string">"case1"</span>,</span><br><span class="line"> <span class="attr">mode</span>: <span class="variable constant_">HIGHLIGHT</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">reducers</span>: {</span><br><span class="line"> <span class="attr">changedata</span>: (<span class="function">(<span class="params">state, action</span>) =></span> {</span><br><span class="line"> state.<span class="property">dataName</span> = action.<span class="property">payload</span></span><br><span class="line"> }),</span><br><span class="line"> <span class="attr">changemode</span>: (<span class="function">(<span class="params">state, action</span>) =></span> {</span><br><span class="line"> state.<span class="property">mode</span> = action.<span class="property">payload</span></span><br><span class="line"> })</span><br><span class="line"> }</span><br><span class="line">})</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> { changedata, changemode } = optionSlice.<span class="property">actions</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> optionSlice.<span class="property">reducer</span></span><br></pre></td></tr></table></figure>
<p> 其实已经发现区别了,在修改之前,我把整个数据集<code>data = {nodes:[...],links:[...]}</code>作为了整个状态存放到了redux中。而修改之后,我只存了<strong>数据集的名称</strong>,使用的时候用这个<strong>名称</strong>去一个存放了所有dataSets的地方取。这显然是一种更加合理的编码方式。</p>
<h2 id="产生原因分析"><a href="#产生原因分析" class="headerlink" title="产生原因分析"></a>产生原因分析</h2><h4 id="immer-js-gt-不可变数据结构"><a href="#immer-js-gt-不可变数据结构" class="headerlink" title="immer.js => 不可变数据结构"></a>immer.js => 不可变数据结构</h4><p> 基本能够初步确定redux-toolkit在返回新状态值的时候,设置了返回的obj是不可扩展的。为了验证我的猜想,我去redux-toolkit官网找到了下面这篇<code>Writing Reducers with Immer</code></p>
<p><img src="https://raw.githubusercontent.com/zqqcee/img_repo/main/img/202305181519759.png" alt="在这里插入图片描述"></p>
<p> Immer,Immer是什么?读这篇文章第一句话</p>
<blockquote>
<p>Redux Toolkit’s <a href="https://redux-toolkit.js.org/api/createReducer"><code>createReducer</code></a> and <a href="https://redux-toolkit.js.org/api/createSlice"><code>createSlice</code></a> automatically use [Immer]<br>(<a href="https://immerjs.github.io/immer/">https://immerjs.github.io/immer/</a>) internally to let you write simpler immutable update logic using “mutating” syntax. This helps simplify most reducer implementations.</p>
<p>译文:Redux Toolkit<a href="https://redux-toolkit.js.org/api/createReducer"><code>createReducer</code></a>并在内部<a href="https://redux-toolkit.js.org/api/createSlice"><code>createSlice</code></a>自动使用<a href="https://immerjs.github.io/immer/">Immer</a>让您使用“mutating”语法编写更简单的不可变的更新逻辑。这有助于简化大多数 reducer 实现。</p>
</blockquote>
<p> <code>immutable update logic</code>不可变的更新逻辑,我想我找到答案了。于是我去google了<code>immer.js</code>,</p>
<p> 在它的中文官方文档中,有一段这么介绍的话:</p>
<blockquote>
<p>Immer can be used in any context in which immutable data structures need to be used. For example in combination with React state, React or Redux reducers, or configuration management. Immutable data structures allow for (efficient) change detection: if the reference to an object didn’t change, the object itself did not change. In addition, it makes cloning relatively cheap: Unchanged parts of a data tree don’t need to be copied and are shared in memory with older versions of the same state.</p>
<p>译文:<strong>Immer 可以在需要使用不可变数据结构的任何上下文中使用</strong>。例如与 React state、React 或 <strong>Redux reducers</strong> 或者 configuration management 结合使用。<strong>不可变的数据结构允许(高效)的变化检测</strong>:如果对对象的引用没有改变,那么对象本身也没有改变。此外,它<strong>使克隆对象相对便宜</strong>:<strong>数据树的未更改部分不需要复制,并且在内存中与相同状态的旧版本共享</strong></p>
</blockquote>
<p> 看完这两段话,比较抽象,直接看官方给的代码示例:</p>
<ul>
<li><strong>有一个Todo列表,我们要对它进行更新</strong></li>
</ul>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> baseState = [</span><br><span class="line"> {</span><br><span class="line"> <span class="attr">title</span>: <span class="string">"Learn TypeScript"</span>,</span><br><span class="line"> <span class="attr">done</span>: <span class="literal">true</span></span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> <span class="attr">title</span>: <span class="string">"Try Immer"</span>,</span><br><span class="line"> <span class="attr">done</span>: <span class="literal">false</span></span><br><span class="line"> }</span><br><span class="line">]</span><br></pre></td></tr></table></figure>
<ul>
<li>不使用Immer</li>
</ul>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">//不使用immer</span></span><br><span class="line"><span class="keyword">const</span> nextState = baseState.<span class="title function_">slice</span>() <span class="comment">// 浅拷贝数组</span></span><br><span class="line">nextState[<span class="number">1</span>] = {</span><br><span class="line"> <span class="comment">// 替换第一层元素</span></span><br><span class="line"> ...nextState[<span class="number">1</span>], <span class="comment">// 浅拷贝第一层元素</span></span><br><span class="line"> <span class="attr">done</span>: <span class="literal">true</span> <span class="comment">// 期望的更新</span></span><br><span class="line">}</span><br><span class="line"><span class="comment">// 因为 nextState 是新拷贝的, 所以使用 push 方法是安全的,</span></span><br><span class="line"><span class="comment">// 但是在未来的任意时间做相同的事情会违反不变性原则并且导致 bug!</span></span><br><span class="line">nextState.<span class="title function_">push</span>({<span class="attr">title</span>: <span class="string">"Tweet about it"</span>})</span><br></pre></td></tr></table></figure>
<ul>
<li>使用Immer</li>
</ul>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> produce <span class="keyword">from</span> <span class="string">"immer"</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> nextState = <span class="title function_">produce</span>(baseState, <span class="function"><span class="params">draft</span> =></span> {</span><br><span class="line"> draft[<span class="number">1</span>].<span class="property">done</span> = <span class="literal">true</span></span><br><span class="line"> draft.<span class="title function_">push</span>({<span class="attr">title</span>: <span class="string">"Tweet about it"</span>})</span><br><span class="line">})</span><br></pre></td></tr></table></figure>
<p> 从上可以看出,使用Immer会把更改应用当前的草稿<code>draft</code>上,它是当前状态的代理,一旦我们完成了所有的更改,Immer会根据<code>draft</code>上<code>state</code>的更改生成新的<code>nextState</code>,工作原理示意图如下:</p>
<p><img src="https://raw.githubusercontent.com/zqqcee/img_repo/main/img/202305181519683.png" alt=""></p>
<p> 引用官方文档中的一段话,来解释Immer的作用</p>
<blockquote>
<p>Using Immer is like having a personal assistant. The assistant takes a letter (the current state) and gives you a copy (draft) to jot changes onto. Once you are done, the assistant will take your draft and produce the real immutable, final letter for you (the next state).</p>
<p>使用 Immer 就像拥有一个私人助理。助手拿一封信(当前状态)并给您一份副本(草稿)以记录更改。完成后,助手将接受您的草稿并为您生成真正不变的最终信件(下一个状态)。</p>
</blockquote>
<p> 这个“私人助理”其实是一个代理对象Proxy,我在redux中也做了进一步的验证。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// /redux/optionSlice.js</span></span><br><span class="line"></span><br><span class="line"><span class="attr">reducers</span>: {</span><br><span class="line"> <span class="attr">changedata</span>: (<span class="function">(<span class="params">state, action</span>) =></span> {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(state)</span><br><span class="line"> state.<span class="property">dataName</span> = action.<span class="property">payload</span></span><br><span class="line"> }),</span><br><span class="line"> <span class="attr">changemode</span>: (<span class="function">(<span class="params">state, action</span>) =></span> {</span><br><span class="line"> state.<span class="property">mode</span> = action.<span class="property">payload</span></span><br><span class="line"> })</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
<p> 我在代码中打印了state,并在控制台查看了它的输出,确实是一个Proxy对象。</p>
<p><img src="https://raw.githubusercontent.com/zqqcee/img_repo/main/img/202305181520917.png" alt="在这里插入图片描述"></p>
<h3 id="Redux-toolkit中createSlice-的使用"><a href="#Redux-toolkit中createSlice-的使用" class="headerlink" title="Redux-toolkit中createSlice()的使用"></a>Redux-toolkit中<code>createSlice()</code>的使用</h3><p>在浅了解了Immer.js后,我回到官方文档中阅读剩余部分。并整理了以下对我可能有帮助的点 </p>
<h4 id="状态的不可变性,为什么会引入Immer-js"><a href="#状态的不可变性,为什么会引入Immer-js" class="headerlink" title="状态的不可变性,为什么会引入Immer.js"></a>状态的不可变性,为什么会引入Immer.js</h4><p> 要分析状态的不可变性,首先我们要引入的一个问题是Redux中不可改变状态的几个原因。官方文档中列出了五条原因,但我认为最重要的是第一条:<strong>会导致bug,例如UI无法正确更新显示最新值</strong>。</p>
<p> 那么redux不能更改原始状态,我们如何返回更新后的状态呢?答案是在Reducer中只能拷贝原始值,修改副本并返回副本。如:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// ✅ This is safe, because we made a copy</span></span><br><span class="line"><span class="keyword">return</span> {</span><br><span class="line"> ...state,</span><br><span class="line"> <span class="attr">value</span>: <span class="number">123</span>,</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p> 这也让我想到了之前在写类组件时,必须要用拷贝的方式修改,如</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="title function_">setState</span>(<span class="function"><span class="params">state</span> =></span> {</span><br><span class="line"> {...state,<span class="attr">key</span>:newValue}</span><br><span class="line">})</span><br></pre></td></tr></table></figure>
<p> 我猜和Immer.js也有关系。</p>
<p> 这样修改当然OK没有问题,<strong>但是如果状态之中嵌套了许多层,那么我们需要对每一层都进行拷贝</strong>,这样的代码维护方式显然是灾难一样的存在!这里我贴上官网给的例子。</p>
<blockquote>
<p>手动编写不可变的更新逻辑很困难,并且<strong>在 reducer 中意外改变状态是 Redux 用户最常犯的一个错误</strong>。</p>
</blockquote>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">handwrittenReducer</span>(<span class="params">state, action</span>) {</span><br><span class="line"> <span class="keyword">return</span> {</span><br><span class="line"> ...state,</span><br><span class="line"> <span class="attr">first</span>: {</span><br><span class="line"> ...state.<span class="property">first</span>,</span><br><span class="line"> <span class="attr">second</span>: {</span><br><span class="line"> ...state.<span class="property">first</span>.<span class="property">second</span>,</span><br><span class="line"> [action.<span class="property">someId</span>]: {</span><br><span class="line"> ...state.<span class="property">first</span>.<span class="property">second</span>[action.<span class="property">someId</span>],</span><br><span class="line"> <span class="attr">fourth</span>: action.<span class="property">someValue</span>,</span><br><span class="line"> },</span><br><span class="line"> },</span><br><span class="line"> },</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p> 所以,引入了Immer,Immer是一个库,简化了编写不可变更新逻辑的过程。Immer的工作流程我们在上文中已经介绍过了,这里不做过多赘述,值得注意的是,ReactToolkit的<code>createReducer</code>和<code>createSlice</code>都在内部使用了Immer。上文我也已经验证过了<code>state</code>是一个代理。</p>
<h4 id="更改状态的两种方式:reset与replace"><a href="#更改状态的两种方式:reset与replace" class="headerlink" title="更改状态的两种方式:reset与replace"></a>更改状态的两种方式:<code>reset</code>与<code>replace</code></h4><ul>
<li>reset</li>
</ul>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// reset 修改</span></span><br><span class="line"><span class="attr">reducers</span>: {</span><br><span class="line"> <span class="title function_">todoDeleted</span>(<span class="params">state, action.payload</span>) {</span><br><span class="line"> <span class="comment">// Construct a new array immutably</span></span><br><span class="line"> <span class="keyword">const</span> newTodos = state.<span class="property">todos</span>.<span class="title function_">filter</span>(<span class="function"><span class="params">todo</span> =></span> todo.<span class="property">id</span> !== action.<span class="property">payload</span>)</span><br><span class="line"> <span class="comment">// "Mutate" the existing state to save the new array</span></span><br><span class="line"> state.<span class="property">todos</span> = newTodos</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
<ul>
<li>replace</li>
</ul>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// replace 替换</span></span><br><span class="line"><span class="attr">reducers</span>: {</span><br><span class="line"> <span class="title function_">todoDeleted</span>(<span class="params">state, action.payload</span>) {</span><br><span class="line"> <span class="comment">// Construct a new result array immutably and return it</span></span><br><span class="line"> <span class="keyword">return</span> state.<span class="title function_">filter</span>(<span class="function"><span class="params">todo</span> =></span> todo.<span class="property">id</span> !== action.<span class="property">payload</span>)</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
<p>这里有一个易错的地方,就是有一些修改函数会有默认返回值,那么在修改状态后有一个返回值,reducer就不知道应该使用哪个值作为最新的状态了。如</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="attr">reducers</span>: {</span><br><span class="line"> <span class="comment">// ❌ ERROR: mutates state, but also returns new array size!</span></span><br><span class="line"> <span class="attr">brokenReducer</span>: <span class="function">(<span class="params">state, action</span>) =></span> state.<span class="title function_">push</span>(action.<span class="property">payload</span>),</span><br><span class="line"> <span class="comment">// ✅ SAFE: the `void` keyword prevents a return value</span></span><br><span class="line"> <span class="attr">fixedReducer1</span>: <span class="function">(<span class="params">state, action</span>) =></span> <span class="keyword">void</span> state.<span class="title function_">push</span>(action.<span class="property">payload</span>),</span><br><span class="line"> <span class="comment">// ✅ SAFE: curly braces make this a function body and no return</span></span><br><span class="line"> <span class="attr">fixedReducer2</span>: <span class="function">(<span class="params">state, action</span>) =></span> {</span><br><span class="line"> state.<span class="title function_">push</span>(action.<span class="property">payload</span>)</span><br><span class="line"> },</span><br></pre></td></tr></table></figure>
<h4 id="如何输出当前状态"><a href="#如何输出当前状态" class="headerlink" title="如何输出当前状态"></a>如何输出当前状态</h4><p> 想要从reducer中记录正在进行的状态以查看它在更新时的样子,这个场景是很常见的。但不幸的是,直接输出<code>state</code>是一个<code>Proxy</code>对象。为了解决这个问题,Immer提供了一个函数<code>current()</code>,如果需要查看状态可以使用它</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="attr">reducers</span>: {</span><br><span class="line"> <span class="title function_">todoToggled</span>(<span class="params">state, action</span>) {</span><br><span class="line"> <span class="comment">// ❌ ERROR: logs the Proxy-wrapped data</span></span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(state)</span><br><span class="line"> <span class="comment">// ✅ CORRECT: logs a plain JS copy of the current data</span></span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">current</span>(state))</span><br><span class="line"> },</span><br><span class="line"> },</span><br></pre></td></tr></table></figure>
<h4 id="为什么会引入Immer?"><a href="#为什么会引入Immer?" class="headerlink" title="为什么会引入Immer?"></a>为什么会引入Immer?</h4><p> 下面三点是我对官方文档的一个总结与复述</p>
<ul>
<li><p>使用Immer的优点</p>
<ul>
<li>Immer极大简化了不可变的更新逻辑</li>
<li>减少了reducer更新状态的编写错误。引入Immer后,无需创建副本,直接进行修改即可。(相当于你把修改的工作交给了一个代理,由代理帮你进行修改)</li>
</ul>
</li>
<li><p>Immer在性能上的权衡</p>
<ul>
<li>无需考虑,reducer几乎从来都不是Redux应用中的性能瓶颈</li>
</ul>
</li>
<li><p>是否考虑未来将Immer设置为可选项?</p>
<ul>
<li>我有预感很多人在简单看了Redux-toolkit文档就拿去用了以后,都会给它们提Issue。因为这个对象的不可变性稍微不留意就会出错(但是习惯了它们的写法以后其实效率提升很多)。官方文档中也给出了为什么不打算将Immer设置为可选项的理由,它们说React-toolkit的架构是通过直接导入Immer来实现的,需要在应用程序加载期间立即同步使用Immer。</li>
</ul>
<blockquote>
<p>And finally: <strong>Immer is built into RTK by default because we believe it is the best choice for our users!</strong> We <em>want</em> our users to be using Immer, and consider it to be a critical non-negotiable component of RTK. The great benefits like simpler reducer code and preventing accidental mutations far outweigh the relatively small concerns.</p>
<p>最后:<strong>Immer 默认内置在 React-toolkit 中,因为我们相信它是我们用户的最佳选择!</strong>我们希望我们的用户使用 Immer,并将其视为 React-toolkit 的关键组件。更简单的 reducer 代码和防止意外突变等巨大好处,远远超过了那些可以被忽视的问题。</p>
</blockquote>
</li>
</ul>
<h2 id="思考"><a href="#思考" class="headerlink" title="思考"></a>思考</h2><p> 这是我解决问题的完整过程,最近在做项目,写了好久的文档,好久没有沉淀自己的代码能力了。碰巧周日,碰巧遇到了一个值得记录的问题,赶紧把自己的思考过程落实在了文字。</p>
<p> 从组件中选择状态升格为全局这是一个值得思考的问题,我也认为这是很考验一个React写手能力的工作。最近刚入门React,浅记录一下解决问题的全过程。</p>
]]></content>
<categories>
<category>thought</category>
</categories>
<tags>
<tag>javascript</tag>
</tags>
</entry>
<entry>
<title>在博客中应用ipad-cursor,@Hexo(Theme:NexT)</title>
<url>/2023/07/23/ebae3e5deab8/</url>
<content><![CDATA[<h2 id="起因"><a href="#起因" class="headerlink" title="起因"></a>起因</h2><p>前两天睡前刷<code>twitter</code>,偶然间刷到一个很棒的项目<code>ipad-cursor</code>,想把这个用在自己的博客中。问了一下作者<a href="https://github.com/CatsJuice">@CatsJuice</a>,发现暂未提供关于Hexo的支持,于是想着自己把这个功能加上,说不定还能写个<code>hexo-plugin</code>。</p>
<h2 id="效果"><a href="#效果" class="headerlink" title="效果"></a>效果</h2><p><img src="https://raw.githubusercontent.com/zqqcee/img_repo/main/img/202307281006407.gif" alt="屏幕录制2023-07-24 下午4.35.30"></p>
<h2 id="使用方法"><a href="#使用方法" class="headerlink" title="使用方法"></a>使用方法</h2><h3 id="方法1"><a href="#方法1" class="headerlink" title="方法1"></a>方法1</h3><p>只需要为想要添加<code>ipad-cursor</code>效果的元素添加属性<code>data-cursor:block</code>或<code>data-cursor:text</code>即可。</p>
<ul>
<li>创建<code>ipad-cursor-hexo</code>目录,放置在<code>/themes/next/source/js</code>下,</li>
<li><p>创建两个js文件:<code>config.js</code>,<code>index.js</code>。</p>
</li>
<li><p>找到需要添加<code>data-cursor</code>的元素</p>
<p>方式:直接在DevTools中选择,即可定位,如果元素没有<code>class</code>与<code>id</code>不方便定位,可以在<code>hexo</code>工程下找到这个元素对应的<code>.swig</code>文件,添加上<code>id</code>,方便选择器选择。</p>
</li>
<li><p>使用js为其添加属性<code>document.querySelector('xx')?.setAttribute('data-cursor','block')</code></p>
</li>
<li><p>创建初始化函数<code>init</code>,监听<code>DOMContentLoaded</code></p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">init</span> = (<span class="params"></span>) => {</span><br><span class="line"> <span class="variable language_">document</span>.<span class="title function_">querySelectorAll</span>(<span class="string">'*'</span>).<span class="title function_">forEach</span>(<span class="function"><span class="params">_</span> =></span> _.<span class="property">style</span>.<span class="property">cursor</span> = <span class="string">'none'</span>);</span><br><span class="line"> <span class="comment">//document.querySelector('xxx').setAttribute('data-cursor','block');</span></span><br><span class="line"> <span class="comment">//document.querySelector('xxx').setAttribute('data-cursor','block');</span></span><br><span class="line"> <span class="comment">//......</span></span><br><span class="line"> cursor.<span class="title function_">initCursor</span>();</span><br><span class="line">}</span><br><span class="line"><span class="variable language_">document</span>.<span class="title function_">on</span>(<span class="string">'DOMContentLoaded'</span>,init);</span><br></pre></td></tr></table></figure>
</li>
<li><p>注:如果<code>selector</code>难以选择,考虑直接修改<code>.swig</code>文件,在标签上直接添加。</p>
</li>
</ul>
<p><code>ipad-cursor</code>具体用法请参考:<a href="https://github.com/CatsJuice/ipad-cursor">github/ipad-cursor</a></p>
<hr>
<h3 id="方法2-使用ipad-cursor-hexo插件,【推荐-🔥🔥🔥】"><a href="#方法2-使用ipad-cursor-hexo插件,【推荐-🔥🔥🔥】" class="headerlink" title="方法2: 使用ipad-cursor-hexo插件,【推荐 🔥🔥🔥】"></a>方法2: 使用ipad-cursor-hexo插件,【推荐 🔥🔥🔥】</h3><p>使用<a href="https://github.com/zqqcee/ipad-cursor-hexo">ipad-cursor-hexo</a>进行设置,ipad-cursor-hexo是一个使用<strong>配置项</strong>进行ipad-cursor配置的库,只需要按照要求编写配置项,只需不超过10行代码,即可在你的博客中添加ipad-cursor</p>
<p><strong>步骤:</strong></p>
<ul>
<li><p><strong>创建一个名为<code>ipad-cursor</code>的文件夹,放在目录<code>${SourcePath}/themes/next/source/js</code>下</strong></p>
</li>
<li><p><strong>创建一个js文件,名为<code>index.js</code>,在 <code>${SourcePath}/themes/next/source/js/ipad-cursor-hexo</code>中</strong></p>
</li>
<li><p><strong>编写配置文件</strong></p>
<ul>
<li><p>首先,你可以配置需要在哪些标签上绑定样式,比如:</p>
<p>如果你想要在<code><div id="article"></div></code> 上,添加<code>data-cursor="text"</code>的属性,那么你应该编写如下配置项,对象的key是你想要在<code>document.querySelectorAll</code>传入的内容 </p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">const config = {</span><br><span class="line"> "div#article":{</span><br><span class="line"> type:'text',</span><br><span class="line"> },</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>如果你想要配置它的 <code>cursor-style</code>, 你可以添加如下配置项</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">const config = {</span><br><span class="line"> "div#article":{</span><br><span class="line"> type:'text',</span><br><span class="line"> style:'radius:50%'</span><br><span class="line"> },</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>如果你想要将所有的type为text的<code><article></code>内的所有<code><a></code>修改为block,你应该使用如下配置语法:</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">const config = {</span><br><span class="line"> "div#article":{</span><br><span class="line"> type:'text',</span><br><span class="line"> style:'radius:50%'</span><br><span class="line"> children:{</span><br><span class="line"> "a":{</span><br><span class="line"> type:"block",</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
</li>
<li><p>其次,你可以配置cursor的样式,详见:<a href="https://github.com/CatsJuice/ipad-cursor#config">ipad-cursor config</a></p>
</li>
<li><p>最后,你可以配置一些副作用,比如让<code>img</code>标签不能被选中,因为被选中的<code><img></code>会变暗</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">effect</span> = (<span class="params"></span>) => {</span><br><span class="line"> <span class="variable language_">document</span>.<span class="title function_">querySelector</span>(<span class="string">'img'</span>).<span class="property">style</span>.<span class="property">userSelect</span> = <span class="string">'none'</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
</li>
</ul>
</li>
<li><p>将<code>index.js</code>添加到hexo blog中</p>
<ul>
<li>打开 <code>${SourcePath}/themes/next/layout/_partials/head/head.swig</code>文件</li>
<li>在其中添加<code><script src="/js/ipad-cursor-hexo/index.js" type="module"></script></code></li>
</ul>
</li>
<li><p>让<code>document</code>监听<code>"DOMContentLoaded"</code>事件,等DOM加载完毕后,执行<code>init</code>函数,<code>index.js</code>的完整代码如下:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> init <span class="keyword">from</span> <span class="string">"https://unpkg.com/ipad-cursor-hexo@latest"</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> { config, cursorConfig } <span class="keyword">from</span> <span class="string">"./config.js"</span></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">effect</span> = (<span class="params"></span>) => {</span><br><span class="line"> <span class="variable language_">document</span>.<span class="title function_">querySelector</span>(<span class="string">'img'</span>).<span class="property">style</span>.<span class="property">userSelect</span> = <span class="string">'none'</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="variable language_">document</span>.<span class="title function_">addEventListener</span>(<span class="string">'DOMContentLoaded'</span>, <span class="function">() =></span> <span class="title function_">init</span>(config, cursorConfig,effect));</span><br></pre></td></tr></table></figure>
</li>
</ul>
<p>详情请见:<a href="https://github.com/zqqcee/ipad-cursor-hexo">zqqcee/ipad-cursor-hexo</a>,欢迎star🌟!</p>
<p>欢迎访问我的个人博客,查看效果 <a href="https://zqqcee.github.io/">zqqcee</a>🔥</p>
<h2 id="遇到的问题"><a href="#遇到的问题" class="headerlink" title="遇到的问题"></a>遇到的问题</h2><ul>
<li><p>鼠标样式不生效问题:</p>
<ul>
<li><p>这个问题主要呈现在当鼠标放置到<code><a>,<p></code>等标签中,鼠标会变为一个小手点击的样式。</p>
</li>
<li><p>NexT主题中,在一些地方设置了cursor的属性,<code>ipad-cursor</code>的原理是先设置<code>cursor:none</code>,再添加一个<code><div></code>随着鼠标移动。但是<code>cursor:none</code>会被NexT主题设置的样式覆盖掉。</p>
</li>
<li><p><strong>解决方法:</strong>需要手动将所有元素的style重新设置<code>cursor:none</code></p>
</li>
</ul>
</li>
<li><p>载入文章后cursor失效问题:</p>
<ul>
<li>这个问题主要呈现在当载入文章时,<code>cursor</code>失效。</li>
<li>因为已经绑定了<code>document.on('DOMContentLoaded')</code>事件,排除ipad-cursor错误使用问题。进一步排查,发现在<code><head></code>中使用了相对路径引入js文件,而在载入文章后,相对路径改变,造成<code>ipad-cursor-hexo.js</code>文件无法生效问题</li>
<li><strong>解决方法</strong>:在<code><head></code>使用绝对路径引入</li>
</ul>
</li>
</ul>
]]></content>
<categories>
<category>thought</category>
</categories>
<tags>
<tag>javascript</tag>
</tags>
</entry>
<entry>
<title>大型节点链接图快速渲染方案 @web worker的使用与改进</title>
<url>/2022/08/02/773f2fecc59b/</url>
<content><![CDATA[<h1 id="大型节点链接图快速渲染方案"><a href="#大型节点链接图快速渲染方案" class="headerlink" title="大型节点链接图快速渲染方案"></a>大型节点链接图快速渲染方案</h1><p> 虽然Canvas可以用于渲染万级数据量,但是当节点数超过1w时,尽管一次渲染的时间很短,但还是会产生视觉上的卡顿。为此,我们继续调研了一些优化方案,包括</p>
<ul>
<li>基于Web Worker计算和渲染并行技术【实现并优化】</li>
<li>基于Canvas的离屏渲染技术【实现】</li>
<li>以及基于Canvas的3D框架—WebGL技术。【未尝试】</li>
</ul>
<h2 id="1-基于Web-Worker的计算与渲染的并行方法"><a href="#1-基于Web-Worker的计算与渲染的并行方法" class="headerlink" title="1 基于Web Worker的计算与渲染的并行方法"></a>1 基于Web Worker的计算与渲染的并行方法</h2><p> 浏览器渲染页面是一个复杂的过程,因为浏览器内核是多线程的,整个过程需要涉及到多个线程,其中最重要的就是JS引擎线程和GUI渲染线程,其中JS引擎线程用来执行脚本文件,依照代码逻辑计算页面元素的位置;而GUI线程则将这些对应的页面元素渲染到页面上。为了防止在渲染过程中因元素的位置发生变化而导致渲染出错,<strong>浏览器将GUI渲染线程与JS引擎设置为互斥的关系</strong>,即当JS引擎执行时GUI线程会被挂起,GUI更新会被保存在一个队列中等到JS引擎空闲时被执行。在对一个图进行布局的时候,需要多次迭代以至于图中所有节点的位置达到稳定,那么就<strong>需要JS引擎线程和GUI线程串行执行</strong>,先利用JS引擎线程计算出下一次布局所有元素的位置,然后再利用GUI线程将所有元素渲染至页面上,以此类推,直到所有的迭代都完成。我们猜测这是导致大规模数据在渲染过程中产生卡顿的原因,因为计算节点下一次迭代的过程需要耗时,无法直接进行连续渲染。</p>
<p> <strong>Web Worker</strong>是一种可为JavaScript创造多线程环境,并将一些高密度计算任务分配给子线程运行的方法,其具体工作流程如下图所示。我们尝试使用Web Worker将可视化工作流并行化来解决渲染卡顿的问题。我们将渲染工作与布局工作分开,具体操作如下:声明一个Worker子线程来执行高时间复杂度的布局迭代计算工作,并将每一次迭代后的计算结果返回给主线程。而主线程通过接收子线程的计算结果,进行每次迭代的布局渲染。</p>
<p><img src="https://raw.githubusercontent.com/zqqcee/img_repo/main/img/image-20220802200137368.png" alt="image-20220802200137368"></p>
<p>由于布局渲染的方法有两种,分别为基于矢量的<strong>SVG</strong>布局渲染方法以及基于位图的<strong>Canvas</strong>布局渲染方法,我同时利用Web Worker进行计算与渲染的并行计算优化,实验Web Worker的有效性。</p>
<h4 id="实验数据规模:"><a href="#实验数据规模:" class="headerlink" title="实验数据规模:"></a><strong>实验数据规模:</strong></h4><ul>
<li><strong>case1:</strong> node(114),link(183)</li>
<li><strong>case2:</strong> node(121),link(334)</li>
<li><strong>case3:</strong> node(207),link(458)</li>
<li><strong>case4:</strong> node(368),link(617)</li>
<li><strong>case5:</strong> node(589),link(1057)</li>
<li><strong>case6:</strong> node(1079),link(2345)</li>
<li><strong>case7:</strong> node(301),link(480)</li>
<li><strong>case8:</strong> node(385),link(144)</li>
<li><strong>case9:</strong> node(429),link(910)</li>
<li><strong>case10:</strong> node(2345),link(5217)</li>
<li><strong>case11:</strong> node(1589),link(5217)</li>
</ul>
<h3 id="1-1-基于Web-Worker计算与渲染并行的SVG布局渲染方法"><a href="#1-1-基于Web-Worker计算与渲染并行的SVG布局渲染方法" class="headerlink" title="1.1 基于Web Worker计算与渲染并行的SVG布局渲染方法"></a>1.1 基于Web Worker计算与渲染并行的SVG布局渲染方法</h3><h4 id="实验结果:"><a href="#实验结果:" class="headerlink" title="实验结果:"></a>实验结果:</h4><div class="table-container">
<table>
<thead>
<tr>
<th><strong>案例</strong></th>
<th><strong>Case1</strong></th>
<th><strong>Case2</strong></th>
<th><strong>Case3</strong></th>
<th><strong>Case4</strong></th>
<th><strong>Case5</strong></th>
<th><strong>Case6</strong></th>
<th><strong>Case7</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td>节点数</td>
<td>114</td>
<td>121</td>
<td>207</td>
<td>384</td>
<td>589</td>
<td>1079</td>
<td>301</td>
</tr>
<tr>
<td>未使用Web Worker</td>
<td>1877</td>
<td>1705</td>
<td>2001</td>
<td>2757</td>
<td>4934</td>
<td>7336</td>
<td>2247</td>
</tr>
<tr>
<td>使用Web Worker</td>
<td>1001</td>
<td>2544</td>
<td>2460</td>
<td>3037</td>
<td>5460</td>
<td>11509</td>
<td>3830</td>
</tr>
<tr>
<td>速度提升(ms)</td>
<td>87.51%</td>
<td>-32.98%</td>
<td>-18.66%</td>
<td>-9.22%</td>
<td>-9.63%</td>
<td>-36.26%</td>
<td>-41.33%</td>
</tr>
<tr>
<td><strong>案例</strong></td>
<td><strong>Case8</strong></td>
<td><strong>Case9</strong></td>
<td><strong>Case10</strong></td>
<td><strong>Case11</strong></td>
<td><strong>3-1(3k)</strong></td>
<td><strong>1-1(6k)</strong></td>
<td><strong>6-1(1w)</strong></td>
</tr>
<tr>
<td>节点数</td>
<td>114</td>
<td>429</td>
<td>2345</td>
<td>1589</td>
<td>3228</td>
<td>7987</td>
<td>18460</td>
</tr>
<tr>
<td>未使用Web Worker</td>
<td>1869</td>
<td>4148</td>
<td>16431</td>
<td>11954</td>
<td>23276</td>
<td>50808</td>
<td>201359</td>
</tr>
<tr>
<td>使用Web Worker</td>
<td>2753</td>
<td>5344</td>
<td>29163</td>
<td>20176</td>
<td>29797</td>
<td>98663</td>
<td>—</td>
</tr>
<tr>
<td>速度提升(ms)</td>
<td>-32.11%</td>
<td>-22.38%</td>
<td>-43.66%</td>
<td>-40.75%</td>
<td>-21.88%</td>
<td>-48.50%</td>
<td>—</td>
</tr>
</tbody>
</table>
</div>
<p> 在基于矢量的SVG布局渲染方法的基础上,经过Web Worker计算与渲染并行的优化实验可知,只有在案例1中使用Web Worker的耗时比不使用Web Worker有所提升,但在其他的案例中,通过将计算和渲染分为两个线程反而会造成耗时成本增加。我们分析导致这个问题的原因是:<strong>在计算和渲染中,一次渲染的时间远远大于一次布局迭代计算的时间</strong>。这样就会出现子线程的布局结果早已计算完毕,但主线程的渲染工作还未完成的情况,消息队列会因此堆积大量子线程发送的布局结果 ,而每次都需要从消息队列中取出数据存在的耗时比在主线程上完成布局迭代计算的时间成本还高。因此,经过实验可知,<strong>如果选择SVG作为渲染方法,使用Web Worker无法给用户带来良好的视觉体验</strong>。</p>
<blockquote>
<p>完整代码见<a href="https://github.com/zqqcee/large_scale_Vis/blob/main/src/svgWorker.html">github -> svgWorker</a></p>
</blockquote>
<h3 id="1-2-基于Web-Worker计算与渲染并行的Canvas布局渲染方法"><a href="#1-2-基于Web-Worker计算与渲染并行的Canvas布局渲染方法" class="headerlink" title="1.2 基于Web Worker计算与渲染并行的Canvas布局渲染方法"></a>1.2 基于Web Worker计算与渲染并行的Canvas布局渲染方法</h3><p> 当选择使用Canvas作为渲染方法的时候,由于Canvas单次渲染的时间很短,不会出现消息队列大量堆积子线程发送的布局结果。我们猜想,在这种情况下,使用Web Worker对渲染性能的提升是有效的。因此,我们对Canvas开展了计算与渲染并行对实验。具体的实验(<strong>数据规模同上</strong>)如下表所示:</p>
<div class="table-container">
<table>
<thead>
<tr>
<th><strong>案例</strong></th>
<th><strong>Case1</strong></th>
<th><strong>Case2</strong></th>
<th><strong>Case3</strong></th>
<th><strong>Case4</strong></th>
<th><strong>Case5</strong></th>
<th><strong>Case6</strong></th>
<th><strong>Case7</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td>节点数</td>
<td>114</td>
<td>121</td>
<td>207</td>
<td>384</td>
<td>589</td>
<td>1079</td>
<td>301</td>
</tr>
<tr>
<td>未使用Web Worker</td>
<td>56</td>
<td>76</td>
<td>103</td>
<td>169</td>
<td>267</td>
<td>516</td>
<td>160</td>
</tr>
<tr>
<td>使用Web Worker</td>
<td>121</td>
<td>122</td>
<td>143</td>
<td>256</td>
<td>452</td>
<td>576</td>
<td>213</td>
</tr>
<tr>
<td>速度提升(ms)</td>
<td>-53.72%</td>
<td>-37.70%</td>
<td>-27.97%</td>
<td>-33.98%</td>
<td>-40.9%</td>
<td>-10.4%</td>
<td>-24.88%</td>
</tr>
<tr>
<td><strong>案例</strong></td>
<td><strong>Case8</strong></td>
<td><strong>Case9</strong></td>
<td><strong>Case10</strong></td>
<td><strong>Case11</strong></td>
<td><strong>3-1</strong></td>
<td><strong>1-1</strong></td>
<td><strong>6-1</strong></td>
</tr>
<tr>
<td>节点数</td>
<td>114</td>
<td>429</td>
<td>2345</td>
<td>1589</td>
<td>3228</td>
<td>7987</td>
<td>18460</td>
</tr>
<tr>
<td>未使用Web Worker</td>
<td>88</td>
<td>197</td>
<td>1333</td>
<td>1346</td>
<td>1739</td>
<td>5120</td>
<td>12337</td>
</tr>
<tr>
<td>使用Web Worker</td>
<td>183</td>
<td>244</td>
<td>1908</td>
<td>1390</td>
<td>1755</td>
<td>5072</td>
<td>13837</td>
</tr>
<tr>
<td>速度提升(ms)</td>
<td>-51.91%</td>
<td>-19.26%</td>
<td>-30.14%</td>
<td>-3.17%</td>
<td>-0.91%</td>
<td>0.95%</td>
<td>-10.84%</td>
</tr>
</tbody>
</table>
</div>
<p> 对比起svg渲染,使用canvas可以明显地提升渲染性能。主要原因是canvas的单词渲染耗时大幅缩短。但是经过多次实验对比,发现<strong>在节点数量在超过3k时,会出现明显的卡顿,渲染效果仍并不理想</strong>。更糟糕的是,比起不使用Web Worker,使用Web Worker时Canvas的渲染表现反而更差劲了,用户界面仍然存在非常明显的卡顿。</p>
<blockquote>
<p>完整代码见 <a href="https://github.com/zqqcee/large_scale_Vis/blob/main/src/d3-canvas-worker.html">github -> canvas worker</a></p>
</blockquote>
<h2 id="2-优化I-O损耗"><a href="#2-优化I-O损耗" class="headerlink" title="2 优化I/O损耗"></a>2 优化I/O损耗</h2><p> 在实验过程中我们发现,尽管使用Canvas可以缩短单次渲染的时间,不会出现消息队列大量堆积Worker线程发送的布局结果,但用户界面渲染仍出现较明显的卡顿。经过<strong>性能分析</strong>,我们发现<strong>主要是主线程中的一个数据接收函数占用了大量时间</strong>,这个函数在主线程中的作用是接收Worker线程发送过来的数据。当节点数量超过1w时,主线程与Worker线程的数据交换会占用大量时间。因此,需要尽可能缩短主线程与Worker线程的数据交换的时间以达到流畅渲染的目的。</p>
<p> 而Worker线程与主线程之间进行数据传递的方式有两种:一种是通过<strong>对象拷贝</strong>的方式,另一种是通过<strong>转移对象引用的所有权</strong>的方式。使用对象拷贝的方式,通过内部的克隆算法,将主线程的数据拷贝一份,传给worker。这样worker改变数据并不会影响到主线程。</p>
<p><img src="https://raw.githubusercontent.com/zqqcee/img_repo/main/img/clip_image006.jpg" alt="img"></p>
<p> 另一种通过转移的方式(Transferrable Objects),<strong>不做任何拷贝,而是直接将数据值的引用所有权转移给 worker。</strong>如果一个对象的引用所有权被转移,主线程不会再持有该对象的引用,那么该对象在它被发送的上下文中将变得不可用,并且只对它被转移到的Worker线程可用。</p>
<p><img src="https://raw.githubusercontent.com/zqqcee/img_repo/main/img/clip_image008.jpg" alt="img"></p>
<p> 我们实验了这两种数据传递方式的性能指标,发现在单次数据传递的耗时上,使用转移的方式明显优于对象拷贝。下图是在1w节点数据集上,两种数据传递方式的实验结果。</p>
<p><img src="https://raw.githubusercontent.com/zqqcee/img_repo/main/img/clip_image010.jpg" alt="img"></p>
<p><img src="https://raw.githubusercontent.com/zqqcee/img_repo/main/img/clip_image012.jpg" alt="img"></p>
<p> 可以看出性能面板上,使用对象拷贝方式一次数据传递的任务耗时107.0ms,而使用传递的方式任务耗时仅有21.2ms。<strong>使用转移的方式进行数据传递,要求传递的对象必须为如ArrayBuffer等的指定格式</strong>,因此这<strong>牺牲了数据的可读性</strong>,但能大幅提升数据I/O性能。对此,我们进行了数据I/O的性能实验。我们测试了主线程与Worker线程单次I/O的时间损耗,实验结果如下表所示:</p>
<div class="table-container">
<table>
<thead>
<tr>
<th><strong>案例</strong></th>
<th><strong>Case1</strong></th>
<th><strong>Case2</strong></th>
<th><strong>Case3</strong></th>
<th><strong>Case4</strong></th>
<th><strong>Case5</strong></th>
<th><strong>Case6</strong></th>
<th><strong>Case7</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td>节点数</td>
<td>114</td>
<td>121</td>
<td>207</td>
<td>384</td>
<td>589</td>
<td>1079</td>
<td>301</td>
</tr>
<tr>
<td>未使用Web Worker</td>
<td>56</td>
<td>76</td>
<td>103</td>
<td>169</td>
<td>267</td>
<td>516</td>
<td>160</td>
</tr>
<tr>
<td>使用对象拷贝</td>
<td>28</td>
<td>18</td>
<td>22</td>
<td>24</td>
<td>32</td>
<td>32</td>
<td>20</td>
</tr>
<tr>
<td>使用Transfer Object</td>
<td>34</td>
<td>26</td>
<td>30</td>
<td>30</td>
<td>43</td>
<td>74</td>
<td>35</td>
</tr>
<tr>
<td>速度提升(ms)</td>
<td>17.65%</td>
<td>30.77%</td>
<td>26.67%</td>
<td>20.00%</td>
<td>25.58%</td>
<td>56.76%</td>
<td>42.86%</td>
</tr>
<tr>
<td><strong>案例</strong></td>
<td><strong>Case8</strong></td>
<td><strong>Case9</strong></td>
<td><strong>Case10</strong></td>
<td><strong>Case11</strong></td>
<td><strong>3-1</strong></td>
<td><strong>1-1</strong></td>
<td><strong>6-1</strong></td>
</tr>
<tr>
<td>节点数</td>
<td>114</td>
<td>429</td>
<td>2345</td>
<td>1589</td>
<td>3228</td>
<td>7987</td>
<td>18460</td>
</tr>
<tr>
<td>未使用Web Worker</td>
<td>88</td>
<td>197</td>
<td>1333</td>
<td>1346</td>
<td>1739</td>
<td>5120</td>
<td>12337</td>
</tr>
<tr>
<td>使用对象拷贝</td>
<td>21</td>
<td>27</td>
<td>41</td>
<td>47</td>
<td>66</td>
<td>145</td>
<td>452</td>
</tr>
<tr>
<td>使用Transfer Object</td>
<td>28</td>
<td>35</td>
<td>117</td>
<td>126</td>
<td>151</td>
<td>483</td>
<td>1151</td>
</tr>
<tr>
<td>速度提升(ms)</td>
<td>25.00%</td>
<td>22.86%</td>
<td>64.96%</td>
<td>62.70%</td>
<td>56.29%</td>
<td>69.98%</td>
<td>60.73%</td>
</tr>
</tbody>
</table>
</div>
<p> 对实验结果进行分析,由表可以看出,随着节点数的增加,使用Transferrable Objects的数据传输方式,对数据I/O性能的提升效果显著。当节点数量超过2k时,数据I/O的速度平均能够提升65%。</p>
<h3 id="2-1-将json转为ArrayBuffer处代码实现"><a href="#2-1-将json转为ArrayBuffer处代码实现" class="headerlink" title="2.1 将json转为ArrayBuffer处代码实现"></a>2.1 将json转为ArrayBuffer处代码实现</h3><p> <strong>只需要将links数据处理并传到worker线程中</strong></p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">//DEFINE IN MAIN</span></span><br><span class="line"> <span class="keyword">let</span> nodeInfoMap = {},</span><br><span class="line"> e = <span class="number">0</span>,</span><br><span class="line"> linkInfoMap = {};</span><br><span class="line"> <span class="comment">//创建node地图</span></span><br><span class="line"> data.<span class="property">nodes</span>.<span class="title function_">forEach</span>(<span class="function"><span class="params">n</span> =></span> {</span><br><span class="line"> nodeInfoMap[n.<span class="property">id</span>] || (nodeInfoMap[n.<span class="property">id</span>] = {</span><br><span class="line"> <span class="attr">index</span>: e,</span><br><span class="line"> <span class="attr">id</span>: n.<span class="property">id</span></span><br><span class="line"> }, e++)</span><br><span class="line"> })</span><br><span class="line"> <span class="comment">//创建i,linkbuffer的原型</span></span><br><span class="line"> <span class="keyword">let</span> i = [];</span><br><span class="line"> data.<span class="property">links</span>.<span class="title function_">forEach</span>(<span class="function"><span class="params">e</span> =></span> {</span><br><span class="line"> <span class="keyword">let</span> r = <span class="string">""</span>.<span class="title function_">concat</span>(e.<span class="property">source</span>, <span class="string">"-"</span>).<span class="title function_">concat</span>(e.<span class="property">target</span>); <span class="comment">//每条边对应的唯一id</span></span><br><span class="line"> linkInfoMap[r] || (i.<span class="title function_">push</span>(nodeInfoMap[e.<span class="property">source</span>].<span class="property">index</span>, nodeInfoMap[e.<span class="property">target</span>].<span class="property">index</span>),</span><br><span class="line"> linkInfoMap[r] = {</span><br><span class="line"> <span class="attr">id</span>: r</span><br><span class="line"> })</span><br><span class="line"> });</span><br><span class="line"> <span class="comment">//得到linkbuffer</span></span><br><span class="line"> <span class="keyword">let</span> linkBuffer = <span class="keyword">new</span> <span class="title class_">Int32Array</span>(i);</span><br></pre></td></tr></table></figure>
<blockquote>
<p>完整代码见 <a href="https://github.com/zqqcee/large_scale_Vis/blob/main/src/d3-canvas-worker-transfer.html">github -> worker-transfer</a></p>
</blockquote>
<h3 id="1-1-3-基于Web-Worker-离屏渲染的优化方法—zqc实验【已完成】"><a href="#1-1-3-基于Web-Worker-离屏渲染的优化方法—zqc实验【已完成】" class="headerlink" title="1.1.3 基于Web Worker+离屏渲染的优化方法—zqc实验【已完成】"></a>1.1.3 基于Web Worker+离屏渲染的优化方法—zqc实验【已完成】</h3><p> 在正常的渲染过程中,CPU会将计算好的内容提交到GPU,GPU渲染完成后将渲染结果放入缓冲区,随后显示器会显示缓冲区中的数据。其中GPU屏幕渲染有以下两种方式:</p>
<p>(1)当前屏幕渲染(On-Screen Rendering):指的是GPU的渲染操作作用于当前所显示的屏幕缓冲区;</p>
<p>(2)离屏渲染(Off-Screen Rendering):指的是GPU在当前屏幕缓冲区之外,新开辟一个缓冲区进行渲染操作。渲染的结果不会直接呈现到当前屏幕上,而是等待合适的时机才会显示。相当于在某个时间直接将已经渲染好的图片显示在屏幕上,则不必再执行所有绘图指令。</p>
<p> 实现离屏渲染的基本思路,是要将需要重复渲染的图形缓存为图片,在渲染时将图片直接从缓存中读取至另外的画布上。这样做的目的是希望减少在主画布中原生Canvas渲染接口的调用次数,以提升渲染效率。由于在大图可视化任务中,我们需要大量重复地绘制圆点,因此我们尝试将它们缓存为图片,在渲染时直接读取至除主画布外的画布上即可。</p>
<p> 由于浏览器的离屏渲染技术是基于Canvas的,所以在这个部分,我们只对Canvas采取离屏渲染优化。大图可视化任务可分为布局与渲染两个子任务,离屏渲染技术只能提升渲染这一子任务的性能,而对布局这一子任务的性能没有任何影响。因此,将离屏渲染应用于大图可视化中具有一项前提条件:计算布局的Worker线程速度快于主线程渲染的速度。</p>
<p> 如下图实验结果所示,使用canvas渲染大图的过程中,计算布局的Worker线程的时间开销远远高于主线程渲染。因此在基于Canvas的大图可视化任务中,使用离屏渲染并不会达到提升性能的目的。</p>
<p><img src="https://raw.githubusercontent.com/zqqcee/img_repo/main/img/clip_image016.jpg" alt="img"></p>
<blockquote>
<p>完整代码见<a href="https://github.com/zqqcee/large_scale_Vis/blob/main/src/d3-canvas-worker-offscreen.html">github -> offscreen</a></p>
</blockquote>
]]></content>
<categories>
<category>visual analytics</category>
</categories>
<tags>
<tag>javascript</tag>
</tags>
</entry>
<entry>
<title>实现autoZoom(),画布自适应放缩并居中@D3.js-v5</title>
<url>/2023/03/24/3423a90bb58e/</url>
<content><![CDATA[<h1 id="实现autoZoom-画布自适应放缩并居中-D3-js-v5"><a href="#实现autoZoom-画布自适应放缩并居中-D3-js-v5" class="headerlink" title="实现autoZoom(),画布自适应放缩并居中 @D3.js-v5"></a>实现<code>autoZoom()</code>,画布自适应放缩并居中 @D3.js-v5</h1><h3 id="需求陈述:"><a href="#需求陈述:" class="headerlink" title="需求陈述:"></a>需求陈述:</h3><p> 画出了一张节点链接图,虽然可以固定布局中心,但每次使用不同屏幕时,这个布局中心总是会改变,导致节点链接图无法位于画布中央,且大小不适宜,因此需要实现一个自适应放缩方法,使画布按照屏幕的尺寸进行放缩,并将元素居中展示。</p>
<p><img src="https://raw.githubusercontent.com/zqqcee/img_repo/main/img/202303241514591.png" alt="image-20230324144012412"></p>
<h3 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h3><p> 这是一个画布的嵌套方式。</p>
<p><img src="https://raw.githubusercontent.com/zqqcee/img_repo/main/img/202303241514564.png" alt=""></p>
<ul>
<li>首先,创建一个<code><svg></code>标签(图中为灰绿色),长宽与用户界面/组件的长宽相同。这个<code><svg></code>只是一个包裹的容器,一般是不直接在其中放置图元的。</li>
<li>接着,在<code><svg></code>内部创建一个<code><g></code>标签(图中为黑色),我们真正需要绘制的图元,都会放置在这个<code><g></code>标签中。</li>
<li>为<code><svg></code>标签绑定<code>d3.zoom()</code>事件,并将这个<code>zoom</code>事件的<code>transform</code>对象,应用在<g>中</li>
</ul>
<p> 只要理解了最后一步,就理解了这整个流程。为什么要把<code>d3.zoom()</code>绑定在外部的<code><svg></code>标签上呢?我们需要设想一个场景:假设我们把zoom事件绑定在了内部的<code><g></code>标签上,那么当用户将<g>标签全部拖动到<svg>外部时,就没办法拖回来了。因为此时用户鼠标已经无法选中<g>标签了。比如下面这种情况:</p>
<p><img src="https://raw.githubusercontent.com/zqqcee/img_repo/main/img/202303241517212.png" alt="image-20230324145631127"></p>
<p> 因此,为了避免用户将画布拖走后无法拖回来,我们应该设置一个“静止”的窗口,将拖动和放缩事件绑定在这上面,并且将这个事件作用来这个静止窗口内部的元素上。这里的静止窗口就是<code><svg></code>,而事件作用的元素就是<code><g></code>,这也解释了为什么要选用这种嵌套的形式。</p>
<p> 理解了这点,代码就很好写了。我们只需要向放缩的函数中传入外部<svg>的id,内部<g>的id,<code>zoomObj</code>即可。这里还可以传入<code>padding</code>,和<code>duration</code>,设置画布的左右间隙和补间动效。</p>
<h3 id="代码实现"><a href="#代码实现" class="headerlink" title="代码实现"></a>代码实现</h3><h5 id="设置用户摁下ctrl键,就自适应放缩并居中"><a href="#设置用户摁下ctrl键,就自适应放缩并居中" class="headerlink" title="设置用户摁下ctrl键,就自适应放缩并居中"></a>设置用户摁下<code>ctrl</code>键,就自适应放缩并居中</h5><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> zoomObj = d3.<span class="title function_">zoom</span>().<span class="title function_">scaleExtent</span>([<span class="number">1</span> / <span class="number">50</span>, <span class="number">2</span>]);</span><br><span class="line"><span class="variable language_">document</span>.<span class="property">onkeydown</span> = <span class="function">(<span class="params">e</span>) =></span> {</span><br><span class="line"> <span class="keyword">if</span> (e.<span class="property">keyCode</span> === <span class="number">17</span>) {</span><br><span class="line"> <span class="title function_">autoZoom</span>(</span><br><span class="line"> zoomObj,<span class="comment">//传入zoomObj</span></span><br><span class="line"> <span class="string">'svgContainer'</span>,<span class="comment">//<svg>的id</span></span><br><span class="line"> <span class="string">'svg'</span>,<span class="comment">//<g>的id</span></span><br><span class="line"> {</span><br><span class="line"> <span class="attr">row</span>: <span class="number">20</span>,</span><br><span class="line"> <span class="attr">col</span>: <span class="number">10</span></span><br><span class="line"> },<span class="comment">//间隙参数(自定)</span></span><br><span class="line"> <span class="number">1000</span> <span class="comment">// 补间时长(自定)</span></span><br><span class="line"> )</span><br><span class="line"> s</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h5 id="autoZoom-函数实现"><a href="#autoZoom-函数实现" class="headerlink" title="autoZoom()函数实现"></a><code>autoZoom()</code>函数实现</h5><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="comment">//autoZoom() function body</span></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> <span class="doctag">@param</span> zoomObj: 放缩对象,设置了放缩比率</span></span><br><span class="line"><span class="comment"> <span class="doctag">@param</span> svgContainerId: 容器 <svg></span></span><br><span class="line"><span class="comment"> <span class="doctag">@param</span> svgBodyId: 画布 <g></span></span><br><span class="line"><span class="comment"> <span class="doctag">@param</span> marginParam: 间隙参数(自定义)</span></span><br><span class="line"><span class="comment"> <span class="doctag">@param</span> duration: 补间时长</span></span><br><span class="line"><span class="comment">**/</span></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">autoZoom</span> = (<span class="params">zoomObj, svgContainerId, svgBodyId, marginParam, duration</span>) => {</span><br><span class="line"> <span class="keyword">const</span> svgContainer = <span class="variable language_">document</span>.<span class="title function_">querySelector</span>(<span class="string">`#<span class="subst">${svgContainerId}</span>`</span>);</span><br><span class="line"> <span class="keyword">const</span> svgBody = d3.<span class="title function_">select</span>(<span class="string">`#<span class="subst">${svgBodyId}</span>`</span>);</span><br><span class="line"> <span class="keyword">if</span> (!svgContainer) {</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">const</span> viewBox = svgBody.<span class="title function_">node</span>().<span class="title function_">getBBox</span>();<span class="comment">//必须用d3.select,才有getBox,获取到长和宽</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">//svg(它是静止的)</span></span><br><span class="line"> <span class="keyword">const</span> containerWidth = svgContainer.<span class="property">clientWidth</span><span class="comment">//svg标签的宽</span></span><br><span class="line"> <span class="keyword">const</span> containerHeight = svgContainer.<span class="property">clientHeight</span><span class="comment">//svg标签的高</span></span><br><span class="line"> </span><br><span class="line"> <span class="comment">// margin setting</span></span><br><span class="line"> <span class="keyword">const</span> rowMargin = marginParam.<span class="property">row</span></span><br><span class="line"> <span class="keyword">const</span> colMargin = marginParam.<span class="property">col</span></span><br><span class="line"> </span><br><span class="line"> <span class="comment">//计算放缩倍数</span></span><br><span class="line"> <span class="keyword">const</span> scale = <span class="title class_">Math</span>.<span class="title function_">min</span>((containerWidth - rowMargin) / viewBox.<span class="property">width</span>, (containerHeight - colMargin) / viewBox.<span class="property">height</span>)</span><br><span class="line"> </span><br><span class="line"> <span class="comment">//计算如果要居中,画布需要的偏移量</span></span><br><span class="line"> <span class="keyword">const</span> offsetX = (containerWidth - rowMargin) / <span class="number">2</span> - (viewBox.<span class="property">x</span> + viewBox.<span class="property">width</span> / <span class="number">2</span>) * scale</span><br><span class="line"> <span class="keyword">const</span> offsetY = (containerHeight - colMargin) / <span class="number">2</span> - (viewBox.<span class="property">y</span> + viewBox.<span class="property">height</span> / <span class="number">2</span>) * scale</span><br><span class="line"></span><br><span class="line"> <span class="comment">// d3.zoomIdentity:缩放参数,返回Transform{k:1,x:0,y:0}</span></span><br><span class="line"> <span class="keyword">const</span> t = d3.<span class="property">zoomIdentity</span>.<span class="title function_">translate</span>(offsetX + rowMargin / <span class="number">2</span>, offsetY).<span class="title function_">scale</span>(scale)</span><br><span class="line"> </span><br><span class="line"> <span class="comment">//计算完毕得到放缩参数t,<svg>标签调用zoomObj和计算好的t</span></span><br><span class="line"> d3.<span class="title function_">select</span>(<span class="string">`#<span class="subst">${svgContainerId}</span>`</span>).<span class="title function_">transition</span>().<span class="title function_">duration</span>(duration).<span class="title function_">call</span>(zoomObj.<span class="property">transform</span>, t)</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
]]></content>
<categories>
<category>visual analytics</category>
</categories>
<tags>
<tag>javascript</tag>
</tags>
</entry>
<entry>
<title>浅析Debounce与Throttle的区别</title>
<url>/2022/08/02/173a07e755e3/</url>
<content><![CDATA[<p>这两天在学习前端知识,在Vue的官方教程中看到了这两个概念,查阅相关资料后,做以下整理。</p>