-
Notifications
You must be signed in to change notification settings - Fork 0
/
search.json
1297 lines (1297 loc) · 400 KB
/
search.json
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
[
{
"objectID": "index.html",
"href": "index.html",
"title": "HoloViz Blog",
"section": "",
"text": "Panel 1.5.0 Release\n\n\n\n\n\n\nrelease\n\n\npanel\n\n\n\nRelease announcement for Panel 1.5\n\n\n\n\n\nSep 13, 2024\n\n\nPhilipp Rudiger\n\n\n\n\n\n\n\n\n\n\n\n\nPlotting made easy with hvPlot: 0.9 and 0.10 releases\n\n\n\n\n\n\nrelease\n\n\nhvplot\n\n\n\nRelease announcement for hvPlot 0.9 and 0.10, including: Polars integration, Xarray support added to the Explorer, Large timeseries exploration made easier, and more!\n\n\n\n\n\nMay 6, 2024\n\n\nMaxime Liquet\n\n\n\n\n\n\n\n\n\n\n\n\nPanel 1.4.0 Release\n\n\n\n\n\n\nrelease\n\n\npanel\n\n\n\nRelease announcement for Panel 1.4\n\n\n\n\n\nMar 28, 2024\n\n\nPhilipp Rudiger\n\n\n\n\n\n\n\n\n\n\n\n\nHoloViews Streams for Exploring Multidimensional Data\n\n\n\n\n\n\nholoviews\n\n\nstreams\n\n\n\nExplores a 4D dataset (time, level, lat, lon) dataset using HoloViews and Panel.\n\n\n\n\n\nMar 20, 2024\n\n\nAndrew Huang\n\n\n\n\n\n\n\n\n\n\n\n\nEvaluate and filter LLM output using logprobs & colored text\n\n\n\n\n\n\nshowcase\n\n\npanel\n\n\nai\n\n\nllm\n\n\nchatbot\n\n\nopenai\n\n\n\nHave you ever wanted to evaluate the confidence of LLM’s output? Utilize log probabilities!\n\n\n\n\n\nFeb 5, 2024\n\n\nAndrew Huang\n\n\n\n\n\n\n\n\n\n\n\n\nPanel AI Chatbot Tips: Memory and Downloadable Conversations\n\n\n\n\n\n\nshowcase\n\n\npanel\n\n\nai\n\n\nllm\n\n\nchatbot\n\n\n\nIn this blog post, we’ll explore how to build a simple AI chatbot, enhance it with memory capabilities, and finally, implement a feature to download conversations for further fine-tuning.\n\n\n\n\n\nDec 22, 2023\n\n\nAndrew Huang, Sophia Yang\n\n\n\n\n\n\n\n\n\n\n\n\nBuild an AI Chatbot to Run Code and Tweak plots\n\n\n\n\n\n\nshowcase\n\n\npanel\n\n\nai\n\n\nllm\n\n\nchatbot\n\n\n\nPowered by Panel and Mixtral 8x7B\n\n\n\n\n\nDec 22, 2023\n\n\nAndrew Huang, Sophia Yang\n\n\n\n\n\n\n\n\n\n\n\n\nParam 2.0 release\n\n\n\n\n\n\nrelease\n\n\nparam\n\n\n\nRelease announcement for Param 2.0\n\n\n\n\n\nDec 22, 2023\n\n\nMaxime Liquet\n\n\n\n\n\n\n\n\n\n\n\n\nBuild a Mixtral Chatbot with Panel\n\n\n\n\n\n\nshowcase\n\n\npanel\n\n\nai\n\n\nllm\n\n\nchatbot\n\n\n\nWith Mistral API, Transformers, and llama.cpp\n\n\n\n\n\nDec 13, 2023\n\n\nAndrew Huang, Philipp Rudiger, Sophia Yang\n\n\n\n\n\n\n\n\n\n\n\n\nBuild a RAG chatbot to answer questions about Python libraries\n\n\n\n\n\n\nshowcase\n\n\npanel\n\n\n\nAccess the Python universe with Fleet Context and Panel\n\n\n\n\n\nDec 7, 2023\n\n\nAndrew Huang, Sophia Yang\n\n\n\n\n\n\n\n\n\n\n\n\nReviving the blog with Quarto\n\n\n\n\n\n\nannouncement\n\n\n\nAnnouncing the migration of our blog to Quarto.\n\n\n\n\n\nNov 19, 2023\n\n\nMaxime Liquet\n\n\n\n\n\n\n\n\n\n\n\n\nPanel 1.3.0 Release\n\n\n\n\n\n\nrelease\n\n\npanel\n\n\n\nRelease announcement for Panel 1.3\n\n\n\n\n\nOct 24, 2023\n\n\nPhilipp Rudiger\n\n\n\n\n\n\n\n\n\n\n\n\nBuilding custom Panel widgets using ReactiveHTML\n\n\n\n\n\n\nshowcase\n\n\npanel\n\n\n\nBuilding custom Panel widgets using ReactiveHTML\n\n\n\n\n\nAug 17, 2023\n\n\nAndrew Huang, Sophia Yang\n\n\n\n\n\n\n\n\n\n\n\n\nHoloViz Survey Results\n\n\n\n\n\n\nsurvey\n\n\n\nResults from first HoloViz user survey\n\n\n\n\n\nJul 14, 2023\n\n\nDemetris Roumis\n\n\n\n\n\n\n\n\n\n\n\n\nBuilding an interactive ML dashboard in Panel\n\n\n\n\n\n\nshowcase\n\n\npanel\n\n\n\nBuilding an interactive ML dashboard in Panel\n\n\n\n\n\nJun 6, 2023\n\n\nAndrew Huang, Sophia Yang, Philipp Rudiger\n\n\n\n\n\n\n\n\n\n\n\n\nPanel 1.0 RC\n\n\n\n\n\n\nannouncement\n\n\npanel\n\n\n\nAnnouncing the availability of a Panel 1.0 release candidate\n\n\n\n\n\nApr 28, 2023\n\n\nPhilipp Rudiger\n\n\n\n\n\n\n\n\n\n\n\n\nPanel 0.14.0 Release\n\n\n\n\n\n\nrelease\n\n\npanel\n\n\n\nRelease announcement for Panel 0.14\n\n\n\n\n\nOct 5, 2022\n\n\nPhilipp Rudiger\n\n\n\n\n\n\n\n\n\n\n\n\nhvPlot 0.8.0 Release\n\n\n\n\n\n\nrelease\n\n\nhvplot\n\n\n\nRelease announcement for hvPlot 0.8.0\n\n\n\n\n\nAug 25, 2022\n\n\nMaxime Liquet\n\n\n\n\n\n\n\n\n\n\n\n\nPanel 0.13.0 Release\n\n\n\n\n\n\nrelease\n\n\npanel\n\n\n\nRelease announcement for Panel 0.13\n\n\n\n\n\nMar 18, 2022\n\n\nPhilipp Rudiger\n\n\n\n\n\n\n\n\n\n\n\n\nPanel 0.12.0 Release\n\n\n\n\n\n\nrelease\n\n\npanel\n\n\n\nRelease announcement for Panel 0.12\n\n\n\n\n\nJul 19, 2021\n\n\nPhilipp Rudiger\n\n\n\n\n\n\n\n\n\n\n\n\nDatashader 0.13 Release\n\n\n\n\n\n\nrelease\n\n\npanel\n\n\n\nRelease announcement for Datashader 0.13\n\n\n\n\n\nJun 23, 2021\n\n\nJames A. Bednar\n\n\n\n\n\n\n\n\n\n\n\n\nPanel 0.11.0 Release\n\n\n\n\n\n\nrelease\n\n\npanel\n\n\n\nRelease announcement for Panel 0.11\n\n\n\n\n\nFeb 3, 2021\n\n\nPhilipp Rudiger\n\n\n\n\n\n\n\n\n\n\n\n\nPanel 0.10.0 Release\n\n\n\n\n\n\nrelease\n\n\npanel\n\n\n\nRelease announcement for Panel 0.10\n\n\n\n\n\nOct 22, 2020\n\n\nPhilipp Rudiger\n\n\n\n\n\n\n\n\n\n\n\n\nHoloViews 1.13 Release\n\n\n\n\n\n\nrelease\n\n\nholoviews\n\n\n\nRelease announcement for HoloViews 1.13\n\n\n\n\n\nJun 15, 2020\n\n\nPhilipp Rudiger\n\n\n\n\n\n\n\n\n\n\n\n\nPanel 0.8.0 Release\n\n\n\n\n\n\nrelease\n\n\npanel\n\n\n\nRelease announcement for Panel 0.8\n\n\n\n\n\nJan 31, 2020\n\n\nPhilipp Rudiger\n\n\n\n\n\n\n\n\n\n\n\n\nPanel 0.7.0 Release\n\n\n\n\n\n\nrelease\n\n\npanel\n\n\n\nRelease announcement for Panel 0.7\n\n\n\n\n\nNov 18, 2019\n\n\nPhilipp Rudiger\n\n\n\n\n\n\n\n\n\n\n\n\nPyViz at SciPy 2019\n\n\n\n\n\n\nscipy\n\n\n\nDiscussion about PyViz landscape at SciPy 2019 BoF\n\n\n\n\n\nJul 12, 2019\n\n\nJames A. Bednar, Thomas Caswell\n\n\n\n\n\n\n\n\n\n\n\n\nPyViz.org and HoloViz.org\n\n\n\n\n\n\nannouncement\n\n\n\nAnnouncing HoloViz splitting off from PyViz\n\n\n\n\n\nJul 2, 2019\n\n\nJames A. Bednar\n\n\n\n\n\n\n\n\n\n\n\n\nPanel Announcement\n\n\n\n\n\n\nannouncement\n\n\npanel\n\n\n\nPublic Announcement of the Panel library\n\n\n\n\n\nMay 28, 2019\n\n\nPhilipp Rudiger\n\n\n\n\n\n\n\n\n\n\n\n\nhvPlot Announcement\n\n\n\n\n\n\nannouncement\n\n\nhvplot\n\n\n\nAnnouncing the release of hvPlot\n\n\n\n\n\nJan 31, 2019\n\n\nPhilipp Rudiger\n\n\n\n\n\n\n\n\n\n\n\n\nGeoViews 1.5 Release\n\n\n\n\n\n\nrelease\n\n\ngeoviews\n\n\n\nRelease announcement for GeoViews 1.5\n\n\n\n\n\nMay 14, 2018\n\n\nPhilipp Rudiger\n\n\n\n\n\n\n\n\n\n\n\n\nHoloViews 1.10 Release\n\n\n\n\n\n\nrelease\n\n\nholoviews\n\n\n\nRelease announcement for HoloViews 1.10\n\n\n\n\n\nApr 24, 2018\n\n\nPhilipp Rudiger\n\n\n\n\n\n\nNo matching items\n\n\n \n\n Back to top"
},
{
"objectID": "posts/reactivehtml/index.html",
"href": "posts/reactivehtml/index.html",
"title": "Building custom Panel widgets using ReactiveHTML",
"section": "",
"text": "No library can cover all the specialized widgets a user may want… but a good one makes it easy for the user to create their own specialized widget that can be used alongside the library!\nPanel is one of those cool libraries–you can create interactive web apps and data dashboards straight from Python code, but if you need more than what’s built-in, you can also create your own custom widgets using Panel’s ReactiveHTML class!\nThe ReactiveHTML class lets you add a dash of HTML to your Python code and, just as the name implies, make that HTML come alive with reactivity! If desired or needed, you can throw in some Jinja2 and/or Javascript into the mix too. In this blog post, we will demo how to use ReactiveHTML for creating: 1. collapsible sections 2. toggle icons\nAnd demonstrate how we can integrate these components into built-in Panel components.\nLet’s get to it!"
},
{
"objectID": "posts/reactivehtml/index.html#bootstrapping-with-chatgpt",
"href": "posts/reactivehtml/index.html#bootstrapping-with-chatgpt",
"title": "Building custom Panel widgets using ReactiveHTML",
"section": "Bootstrapping with ChatGPT",
"text": "Bootstrapping with ChatGPT\nTo get started using ReactiveHTML, you need an HTML template. If you’re unfamiliar with HTML, don’t fret; there are tons of examples so ChatGPT can synthesize an example easily!\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Details Tag Example</title>\n</head>\n\n<body>\n <h1>Expandable Content</h1>\n\n <details>\n <summary>Click to expand</summary>\n <p>This is some hidden content that can be expanded and collapsed.</p>\n </details>\n\n <p>Other content on the page...</p>\n</body>\n\n</html>\nIf we save this code to index.html and open it, we get the following:\n\n\n\nSince we just want the collapsible section, let’s only extract the details tag and start building our custom ReactiveHTML widget.\nimport panel as pn\npn.extension()\n\nclass CollapsibleSection(pn.reactive.ReactiveHTML):\n\n _template = \"\"\"\n <details>\n <summary>Click to expand</summary>\n <p>This is some hidden content that can be expanded and collapsed.</p>\n </details>\n \"\"\"\n\nCollapsibleSection()"
},
{
"objectID": "posts/reactivehtml/index.html#making-the-html-reactive",
"href": "posts/reactivehtml/index.html#making-the-html-reactive",
"title": "Building custom Panel widgets using ReactiveHTML",
"section": "Making the HTML Reactive",
"text": "Making the HTML Reactive\nNow for the cool part: we can replace the static contents with dynamic contents in 1–2–3: 1. Add a content param of generic Parameter type to the class. 2. Update the <p> element to a <div> element containing an id attribute. 3. Replace the static contents with ${contents} inside the new <div> element.\nclass CollapsibleSection(pn.reactive.ReactiveHTML):\n\n contents = param.Parameter()\n \n _template = \"\"\"\n <details>\n <summary>Click to expand</summary>\n <div id=\"contents\">${contents}</div>\n </details>\n \"\"\"\n\nCollapsibleSection(contents=\"New dynamic contents\")\n\n\n\nUsers can also update the section’s contents dynamically!\n\n\n\nAnd it’s not limited to just strings, but any Panel component! How awesome is that!?"
},
{
"objectID": "posts/reactivehtml/index.html#implementing-additional-parameters",
"href": "posts/reactivehtml/index.html#implementing-additional-parameters",
"title": "Building custom Panel widgets using ReactiveHTML",
"section": "Implementing Additional Parameters",
"text": "Implementing Additional Parameters\nIf you’ve been following along, you may have noticed that the section collapses every time contents is updated.\nWe can prevent this by adding the open attribute to the details element.\nclass CollapsibleSection(pn.reactive.ReactiveHTML):\n\n contents = param.Parameter()\n \n _template = \"\"\"\n <details open=true>\n <summary>Click to expand</summary>\n <div id=\"contents\">${contents}</div>\n </details>\n \"\"\"\nIt doesn’t have to be static either–making it dynamic is as easy as before! 1. Add an opened param of Boolean type to the class. 2. Update details element to include an id attribute. 3. Replace true with ${opened}.\nclass CollapsibleSection(pn.reactive.ReactiveHTML):\n\n contents = param.Parameter()\n opened = param.Boolean()\n \n _template = \"\"\"\n <details id=\"opened\" open=${opened}>\n <summary>Click to expand</summary>\n <div id=\"contents\">${contents}</div>\n </details>\n \"\"\"\nNow opened can be controlled dynamically as well!\n\n\n\nNow, as an exercise, try making the summary element reactive too!"
},
{
"objectID": "posts/reactivehtml/index.html#displaying-an-icon",
"href": "posts/reactivehtml/index.html#displaying-an-icon",
"title": "Building custom Panel widgets using ReactiveHTML",
"section": "Displaying an Icon",
"text": "Displaying an Icon\nMaking collapsible sections only required HTML. To build upon that, let’s demonstrate how to trigger Python functions inside the HTML template!\nHere’s some code to start out:\n\nicon is watched and used to initialize _svg, which is requested from tabler-icons. The _svg is then used in the HTML template.\n\nimport requests\nclass ToggleIcon(pn.reactive.ReactiveHTML):\n\n icon = param.String(default=\"thumb-up\")\n \n _svg = param.String()\n \n _template = \"\"\"\n <div id=\"icon\">${_svg}</div>\n \"\"\"\n \n @pn.depends(\"icon\", watch=True, on_init=True)\n def _update_icon(self):\n response = requests.get(\n f\"https://tabler-icons.io/static/tabler-icons/icons/\"\n f\"{self.icon}.svg\"\n )\n svg = response.text\n self._svg = svg\nRunning this will result in an icon."
},
{
"objectID": "posts/reactivehtml/index.html#toggling-active",
"href": "posts/reactivehtml/index.html#toggling-active",
"title": "Building custom Panel widgets using ReactiveHTML",
"section": "Toggling Active",
"text": "Toggling Active\nBut… it doesn’t live up to its name of “ToggleIcon” though, so let’s fix it!\nThe first step is adding an active parameter of Boolean type and making _update_icon depend on it, appending -filled if active.\nclass ToggleIcon(pn.reactive.ReactiveHTML):\n\n icon = param.String(default=\"thumb-up\")\n \n active = param.Boolean(default=False)\n \n _svg = param.String()\n \n _template = \"\"\"\n <div id=\"icon\">${_svg}</div>\n \"\"\"\n \n @pn.depends(\"icon\", \"active\", watch=True, on_init=True)\n def _update_icon(self):\n filled = \"-filled\" if self.active else \"\"\n response = requests.get(\n f\"https://tabler-icons.io/static/tabler-icons/icons/\"\n f\"{self.icon}{filled}.svg\"\n )\n svg = response.text\n self._svg = svg\n\n\n\nThe next step is adding the ability to click on the icon to toggle it!\nTo do so, create a method that toggles active upon click, named _click_icon and use that as the onclick attribute in the div element.\nclass ToggleIcon(pn.reactive.ReactiveHTML):\n\n icon = param.String(default=\"thumb-up\")\n \n active = param.Boolean(default=False)\n \n _svg = param.String()\n \n _template = \"\"\"\n <div id=\"icon\" onclick=${_click_icon}>${_svg}</div>\n \"\"\"\n \n def _click_icon(self, event):\n self.active = not self.active\n \n \n @pn.depends(\"icon\", \"active\", watch=True, on_init=True)\n def _update_icon(self):\n filled = \"-filled\" if self.active else \"\"\n response = requests.get(\n f\"https://tabler-icons.io/static/tabler-icons/icons/\"\n f\"{self.icon}{filled}.svg\"\n )\n svg = response.text\n self._svg = svg\nNow when you repeatedly click it, it should switch between filled and unfilled!"
},
{
"objectID": "posts/reactivehtml/index.html#polishing-the-design",
"href": "posts/reactivehtml/index.html#polishing-the-design",
"title": "Building custom Panel widgets using ReactiveHTML",
"section": "Polishing the Design",
"text": "Polishing the Design\nIt’s great that the icon is clickable, but how does the user know? 🤷\nFortunately, there’s an easy solution: add cursor: pointer as an inline style (or stylesheet). Now you can see a little hand when you hover over the icon.\nclass ToggleIcon(pn.reactive.ReactiveHTML):\n icon = param.String(default=\"thumb-up\")\n \n active = param.Boolean(default=False)\n\n _svg = param.String()\n\n _template = \"\"\"\n <div id=\"icon\" onclick=${_click_icon} style=\"cursor: pointer;\">${_svg}</div>\n \"\"\"\n\n def _click_icon(self, event):\n self.active = not self.active\n \n \n @pn.depends(\"icon\", \"active\", watch=True, on_init=True)\n def _update_icon(self):\n filled = \"-filled\" if self.active else \"\"\n response = requests.get(\n f\"https://tabler-icons.io/static/tabler-icons/icons/\"\n f\"{self.icon}{filled}.svg\"\n )\n svg = response.text\n self._svg = svg\nAnother thing to note is every time the icon is clicked, it has to request the icon; to speed things up, we can add caching!\nclass ToggleIcon(pn.reactive.ReactiveHTML):\n icon = param.String(default=\"thumb-up\")\n \n active = param.Boolean(default=False)\n \n _svg = param.String()\n \n _template = \"\"\"\n <div id=\"icon\" onclick=${_click_icon} style=\"cursor: pointer;\">${_svg}</div>\n \"\"\"\n \n def _click_icon(self, event):\n self.active = not self.active\n \n @pn.cache\n def _fetch_svg(self, icon, active):\n filled = \"-filled\" if active else \"\"\n response = requests.get(\n f\"https://tabler-icons.io/static/tabler-icons/icons/\"\n f\"{icon}{filled}.svg\"\n )\n svg = response.text\n return svg \n \n @pn.depends(\"icon\", \"active\", watch=True, on_init=True)\n def _update_icon(self):\n self._svg = self._fetch_svg(self.icon, self.active)\n\nToggleIcon(active=True)\nGreat, clicking repeatedly now feels much more responsive than before!\nFinally, before we wrap things up, we can implement custom size…\nimport param\nimport panel as pn\nimport requests\npn.extension()\n\nclass ToggleIcon(pn.reactive.ReactiveHTML):\n icon = param.String(default=\"thumb-up\")\n\n active = param.Boolean(default=False)\n\n _svg = param.String()\n\n _template = \"\"\"\n <div id=\"icon\" onclick=${_click_icon} style=\"cursor: pointer;\">${_svg}</div>\n \"\"\"\n\n def _click_icon(self, event):\n self.active = not self.active\n\n @pn.cache\n def _fetch_svg(self, icon, active):\n filled = \"-filled\" if active else \"\"\n response = requests.get(\n f\"https://tabler-icons.io/static/tabler-icons/icons/\" f\"{icon}{filled}.svg\"\n )\n svg = response.text\n return svg\n\n @pn.depends(\"icon\", \"active\", watch=True, on_init=True)\n def _update_icon(self):\n svg = self._fetch_svg(self.icon, self.active)\n if self.width:\n svg = svg.replace('width=\"24\"', f'width=\"{self.width}\"')\n if self.height:\n svg = svg.replace('height=\"24\"', f'height=\"{self.height}\"')\n self._svg = svg\nFor a big thumbs up!"
},
{
"objectID": "posts/param_release_2.0/index.html",
"href": "posts/param_release_2.0/index.html",
"title": "Param 2.0 release",
"section": "",
"text": "We are very happy to announce the release of Param 2.0, a Python library that lets you write classes whose attributes, a.k.a. Parameters, are dynamically validated and whose updates can trigger actions you register. This major release includes:\n\nimproved inheritance of Parameter attributes, now applying even when the value is None, eliminating a previously very confusing limitation\nextensive clean up of the Parameterized namespace, with public methods (deprecated for a while) moved to the .param namespace and reduced and consolidated private members\nintroduction of the new allow_refs and nested_refs Parameter attributes to allow linking a Parameter value from a reference (e.g. other Parameter value, param.depends function/method)\nenhancement of the objects attribute of Selector Parameters, so it’s easier to update/access whether it’s been instantiated from a list or from a dictionary\nintroduction of a rich HTML representation for Parameterized instances and classes, which is automatically displayed in notebooks\nan [experimental] preview of reactive expressions, which form a new declarative and reactive API for writing dynamic code without needing explicit callbacks or dependency declarations.\n\n🌟 An easy way to support Param is to give it a star on Github! 🌟\nFind all the changes brought by Param 2.0 in the release notes and see how to migrate your code from Param 1.x to 2.0 in the upgrade guide."
},
{
"objectID": "posts/param_release_2.0/index.html#tldr",
"href": "posts/param_release_2.0/index.html#tldr",
"title": "Param 2.0 release",
"section": "",
"text": "We are very happy to announce the release of Param 2.0, a Python library that lets you write classes whose attributes, a.k.a. Parameters, are dynamically validated and whose updates can trigger actions you register. This major release includes:\n\nimproved inheritance of Parameter attributes, now applying even when the value is None, eliminating a previously very confusing limitation\nextensive clean up of the Parameterized namespace, with public methods (deprecated for a while) moved to the .param namespace and reduced and consolidated private members\nintroduction of the new allow_refs and nested_refs Parameter attributes to allow linking a Parameter value from a reference (e.g. other Parameter value, param.depends function/method)\nenhancement of the objects attribute of Selector Parameters, so it’s easier to update/access whether it’s been instantiated from a list or from a dictionary\nintroduction of a rich HTML representation for Parameterized instances and classes, which is automatically displayed in notebooks\nan [experimental] preview of reactive expressions, which form a new declarative and reactive API for writing dynamic code without needing explicit callbacks or dependency declarations.\n\n🌟 An easy way to support Param is to give it a star on Github! 🌟\nFind all the changes brought by Param 2.0 in the release notes and see how to migrate your code from Param 1.x to 2.0 in the upgrade guide."
},
{
"objectID": "posts/param_release_2.0/index.html#what-is-param",
"href": "posts/param_release_2.0/index.html#what-is-param",
"title": "Param 2.0 release",
"section": "What is Param?",
"text": "What is Param?\nParam is a Python library that lets you write classes whose parameters, are equipped with metadata and are dynamically validated, which simplifies writing classes with a well-defined and defendable API. Additionally, a parameter can be watched to register a callback that is triggered when the parameter value changes.\nHere’s a simple example usage of Param, where we create a User class that inherits from param.Parameterized and declares a couple of parameters, including age that can only be a positive integer. We also declare that the callback method submit_data should be called whenever the value of the three declared parameters is updated.\n\nimport param\n\nclass User(param.Parameterized):\n age = param.Integer(bounds=(0, None), doc='User age')\n fullname = param.String(doc='User full name', constant=True)\n country = param.Selector(default='en', objects=['en', 'fr', 'de'], doc='User country')\n\n @param.depends('age', 'fullname', 'country', watch=True)\n def submit_data(self):\n print(f'Submit data: {self.age=}, {self.fullname=}, {self.country=}')\n\nuser = User(age=33, fullname='John Doe')\nuser.age, user.fullname, user.country\n\n(33, 'John Doe', 'en')\n\n\nAn error is raised if we try to set the value of one of these parameters to a value that doesn’t satisfy the parameter definition:\n\nwith param.exceptions_summarized():\n user.country = 'es'\n\nValueError: Selector parameter 'User.country' does not accept 'es'; valid options include: '[en, fr, de]'\n\n\nWhen we update the value of the parameters of this instance, the callback submit_data gets automatically called:\n\nuser.age += 1\n\nSubmit data: self.age=34, self.fullname='John Doe', self.country='en'\n\n\n\nuser.country = 'fr'\n\nSubmit data: self.age=34, self.fullname='John Doe', self.country='fr'\n\n\nThese two features, dynamic parameter validation and and callback handling, makes Param an excellent library for supporting GUI code bases. Param is indeed the backbone of the HoloViz libraries HoloViews and Panel, with its integration in Panel being pushed to the extent that its users can easily convert Param objects into visual components. For instance, we can easily create a form from the user object, and to make it a real application we’d just have to update the callback to send data to an API or a database.\n\nimport panel as pn\npn.extension()\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\npn.Param(user)"
},
{
"objectID": "posts/param_release_2.0/index.html#param-2.0",
"href": "posts/param_release_2.0/index.html#param-2.0",
"title": "Param 2.0 release",
"section": "Param 2.0!",
"text": "Param 2.0!\nParam 2.0 is a major new release available for Python 3.8 and above, significantly streamlining, simplifying, and improving the Param API. We would like to thank @minimav, @sdrobert, @droumis, @Hoxbro, @jbednar, @maximlt, @jlstevens and @philippjfr for their contributions. We would also like to thank @ceball, who made the first plans for Param 2.0 quite a few years ago, and we are glad to be delivering on them at last!\nJoin us on Github to contribute to Param directly or come chat with us on our Forum or Discord.\n\nImproved Parameter attributes inheritance\nA Parameter object has attributes like default, doc, bounds, etc. When redefining a Parameter in a subclass, attributes not specified in the subclass are meant to be inherited from the superclass(es). Parameter attribute inheritance is not a new feature; it’s a core design of Param. However, throughout the Param 1.x series, inheritance was arguably broken in subtle ways! The issues traced back to using None as the specific sentinel value for allowing inheritance, when None is also often a valid Parameter value. Let’s have a look at two cases that have been fixed in Param 2.0.\nIn this example, in subclass B, we want to re-set the default value of x to None and to narrow the bounds of y:\nclass A(param.Parameterized):\n\n x = param.Number(default=5.0)\n y = param.Number(default=5.0, bounds=(-10, 10))\n\nclass B(A):\n\n x = param.Number(default=None)\n y = param.Number(bounds=(0, 10))\n\nb = B()\nprint(b.x, b.y)\n# Param < 2: 5.0, 0.0 :(\n# Param 2.0: None, 5.0 :)\nAs you can see for versions preceding Param 2.0, for x the explicit None value we provided in B was discarded entirely because None was interpreted incorrectly as “inherit from the parent class” when provided for an attribute value. Confusingly, None was treated differently for default values, where in y the default value of the Number Parameter (0.0) was used for B.x instead of the value of 5.0 explicitly declared in A. Param 2.0 now inherits values consistently from superclasses, getting None for x and 5.0 for y.\nThese are just two of the most common cases where parameter attribute inheritance changes can affect your code, but there are many other cases, affecting any attribute where None is a legal value. The new behavior should be much more predictable and intuitive, avoiding subtle cases where your code would previously have behaved inappropriately without necessarily having any obvious error. Fixing this was already enough to be worth the major bump to Param 2.0!\n\n\nCleaner Parameterized namespace\nParameterized classes are created by inheriting from param.Parameterized and as such your Parameterized classes will inherit from all of its members. In Param 2.0 we have removed many (many!) methods from this namespace that were deprecated a long ago and moved them to the .param namespace. We’ve also limited the number of private members to a minimum to minimize the risk of collision with members you’d like to create. To be able to defend a reasonably small API surface we require you not to override:\n\n.param: namespace from which you can access the Parameter objects and many methods that used to be on the Parameterized namespace\n._param__parameters and ._param__private: private namespaces required for internal reasons\n.name: Parameterized classes are equipped by default with a name String Parameter, though in Param 2.x you can now finally redefine it if you want it to behave differently!\n\n\n\nLink Parameters with allow_refs\nParameters have all gained the allow_refs and nested_refs attributes, bringing an exceptionally useful feature that was available in Panel since version 1.2 to Param. Declaring a Parameter with allow_refs=True (False by default) allows setting this Parameter value with a reference, to automatically mirror the value of the reference. Supported references include class/instance Parameter objects, functions/methods decorated with param.depends, reactive functions and expressions, asynchronous generators, and custom objects transformed into a valid reference with a hook registered with param.parameterized.register_reference_transform. This capability enables more intricate relationships between parameters, allowing for automatic value synchronization and forming the basis for reactive programming. Additionally, nested_refs indicate whether references should be resolved even when they are nested inside a container.\nLet’s see how this works with a simple example; you can learn more about it in the documentation. We create two classes U and V, instantiate them as u and v, and one-directionally link v.b with u.a, so that any change in u.a is reflected immediately in v.b.\n\nclass U(param.Parameterized): \n a = param.Number()\n \nclass V(param.Parameterized):\n b = param.Number(allow_refs=True)\n\n @param.depends('b', watch=True)\n def updated_b(self):\n print('v.b was updated:', self.b)\n\nu = U(a=3.14)\nv = V(b=u.param.a)\n\nv.b\n\n3.14\n\n\nThe link between u.a and v.b has already been established, v.b is equal to the value of u.a after instantiation.\nUpdating u.a triggers the callback we registered on b in V.\n\nu.a = 1.57\n\nv.b was updated: 1.57\n\n\nAnd indeed the value of v.b reflects the new value of u.a.\n\nv.b\n\n1.57\n\n\n\n\nImproved Selector objects attribute\nThe objects attribute of a Selector Parameter was previously highly confusing, because it accepted either a dictionary or a list for initialization but then was accessible only as a list, making it difficult to watch or update the objects. There is now a ListProxy wrapper around Selector.objects (with forward and backward compatibility) to easily update objects and watch objects updates.\n\nclass SelectorObjectsDemo(param.Parameterized):\n slist = param.Selector(objects=[1, 2, 3])\n sdict = param.Selector(objects=dict(a=1, b=2, c=3))\n\n @param.depends('slist:objects', watch=True)\n def updated_list_objects(self):\n print('Updating slist.objects:', self.param['slist'].objects)\n\n @param.depends('sdict:objects', watch=True)\n def updated_dict_objects(self):\n print('Updating sdict.objects:', self.param['sdict'].objects)\n\nsdemo = SelectorObjectsDemo()\n\n\nsdemo.param['slist'].objects.append(4)\n\nUpdating slist.objects: [1, 2, 3, 4]\n\n\n\n# Now supported thanks to the ListProxy wrapper\nsdemo.param.sdict.objects['d'] = 5\n\nUpdating sdict.objects: [1, 2, 3, 5]\n\n\n\n\nRich HTML representation\nParameterized classes and instances now have a rich HTML representation that is displayed automatically in a Jupyter/IPython notebook. For a class or instance p, just return p.param in a notebook cell to see a table of all the Parameters of the class/instance, their state, type, and range, plus the docstring on hover. It is likely we will improve the content and design of this repr based on feedback, so please let us know what you think!\n\nuser.param\n\n\n\n \n User()\n \n \n \n\n\n\nName\nValue\nType\nRange\n\n\nage\n34\nInteger\n>=0\n\n\ncountry\n'fr'\nSelector\n'en', 'fr', 'de'\n\n\nfullname\n'John Doe'\nString\nconstant\n\n\nname\n'User00002'\nString\nnullable constant\n\n\n\n\n \n\n\n\n\n\nExperimental new Reactive Expressions (rx)\nParam is widely used for building web apps in the HoloViz ecosystem, where packages have added various mechanisms for dynamic updates (e.g. pn.bind and pn.depends in Panel, and .interactive in hvPlot). These mechanisms were already built on Param and can be used far more widely than just in those packages, so that functionality has now been generalized, streamlined, and moved into Param. Nearly any Python expression can now be made reactive with param.rx(), at which point it will collect and be able to replay any operations (e.g. method calls) performed on them. This reactive programming approach lets you take just about any existing Python workflow and replace attributes with widgets or other reactive values, creating an app with fine-grained user control without having to design callbacks, event handlers, or any other complex logic! rx support is still experimental while we get feedback about the API, packaging, and documentation, but it’s fully ready to try out and give us suggestions!\nWe’ll just show you a glimpse of what rx is capable of, and then you can head over to the documentation to learn more about it. Expect also a follow-up blog post that will dive more into it, describe how it is used throughout the HoloViz ecosystem (Panel in particular), and explain how it could be adopted more widely by other ecosystems and frameworks.\nWe start by importing rx and then simply create a reactive object by wrapping a Python object with rx(<obj>).\n\nfrom param import rx\n\ni = rx(1)\nrepr(i)\n\n'<param.reactive.rx object at 0x132ba3c10>'\n\n\nThe object returned and stored on the variable i acts as a proxy of the wrapped object 1, i.e. we can use it as if it was the wrapped object.\n\ni + 10\n\n\n\n\n\n \n\n\n\n\n\ni * 10\n\n\n\n\n\n \n\n\n\n\nWhen we set up a reactive expression j = i + 1, where i has been made reactive, any change to i will automatically trigger an update in j, eliminating the need for manual event handling. No more callbacks, Param takes care of recording the whole pipeline of operations applied to the root reactive object and replays it automatically when it’s updated, to finally update the value of the output. If you are executing this code in a live notebook, uncomment the following cell to see the previous cells update automatically with the new value. (Or just watch the GIF below to see how it works!)\n\n#i.rx.value *=2\n\n\n\n\n\n\n\n\nAnd more\nFind all the changes brought by Param 2.0 in the release notes and see how to migrate your code from Param 1.x to 2.0 in the upgrade guide."
},
{
"objectID": "posts/panel_release_1.4/index.html#what-is-panel",
"href": "posts/panel_release_1.4/index.html#what-is-panel",
"title": "Panel 1.4.0 Release",
"section": "What is Panel?",
"text": "What is Panel?\nPanel is an open-source Python library that lets you easily build powerful tools, dashboards and complex applications entirely in Python. It has a batteries-included philosophy, putting the PyData ecosystem, powerful data tables and much more at your fingertips. High-level reactive APIs and lower-level callback based APIs ensure you can quickly build exploratory applications, but you aren’t limited if you build complex, multi-page apps with rich interactivity. Panel is a member of the HoloViz ecosystem, your gateway into a connected ecosystem of data exploration tools."
},
{
"objectID": "posts/panel_release_1.4/index.html#new-release",
"href": "posts/panel_release_1.4/index.html#new-release",
"title": "Panel 1.4.0 Release",
"section": "New release!",
"text": "New release!\nWe are very pleased to announce the 1.4.0 release of Panel! This release packs many exciting new features, specifically:\n\nAdd EditableTemplate to support dashboard builder UI in Jupyter (#5802)\nAdd ChatAreaInput as default text input widget for ChatInterface (#6379)\nAdd NestedSelect widget (#5791, #6011)\nImprove --autoreload by using watchfiles and selectively reloading packages (#5894, #6459)\nAdd Panel tutorials (#5525, #6208, #6212, #6388, #6425, #6466, #6491)\nAdd DateRangePicker widget (#6027)\nAdd Feed layout and use it as a layout for ChatFeed (#6031, #6296)\nAdd WebP pane (#6035)\nAdd ButtonIcon (#6138)\nAdd Textual pane (#6181)\n\nWe really appreciate all the work that went into this release and especially want to call out @MarcSkovMadsen’s effort in putting together the new tutorial materials. There’s more work to do but it’s a huge step forward and we’re excited to hear your feedback. We want to extend a special thanks to our amazing new crop of new contributors including @atisor73, @OSuwaidi, @suryako, @Davide-sd, @doraaki, @mayonnaisecolouredbenz7, @CTPassion, @j01024, @l3ender and @Coderambling. Next we want to recognize our returning contributors @vaniisgh, @cdeil, @limx0m, and @TheoMaturin, and finally the dedicated crew of core contributors which include @maximlt, @Hoxbro, @MarcSkovMadsen, @ahuang11, @mattpap and @philippjfr.\n\nIf you are using Anaconda, you can get latest Panel with conda install panel , and using pip you can install it with pip install panel."
},
{
"objectID": "posts/panel_release_1.4/index.html#tutorials",
"href": "posts/panel_release_1.4/index.html#tutorials",
"title": "Panel 1.4.0 Release",
"section": "Tutorials",
"text": "Tutorials\nLast year we decided to embark on a project to completely re-architect our documentation leaning heavily on the Diataxis framework. In doing so we got rid of the long form user guides and migrated most of the material into How-to guides. Overall the feedback about this change was very positive as it became easier to find what you were looking for, but it also made it more difficult for new users to learn about Panel.\nThe new tutorials aim to address this gap by providing a guided set of lessons that aim to get you from a complete novice to an advanced user. The tutorial is split into Basic, Intermediate and Advanced sections and is structured as a series of lessons culminating in topic focused guides on building specific applications, e.g. a chat app, monitoring dashboard or a to-do app.\n\nDesigning meaningful and engaging lessons is a huge undertaking so we hope to gather your feedback to continue refining the material. We want to especially thank Marc Skov Madsen for putting such a tremendous amount of effort into these materials."
},
{
"objectID": "posts/panel_release_1.4/index.html#dashboard-builder-ui",
"href": "posts/panel_release_1.4/index.html#dashboard-builder-ui",
"title": "Panel 1.4.0 Release",
"section": "Dashboard Builder UI",
"text": "Dashboard Builder UI\nThe biggest new feature in this release is the addition of a dashboard builder UI that allows building a dashboard layout entirely using a drag and drop interface. Our aim has always been to empower users to quickly share their analyses and results with others and the dashboard builder UI allows building applications without even importing Panel at all. The UI is built on top of a new template component - the EditableTemplate. This template leverages interact.js and Muuri to provide a smooth user experience when laying out components on the page. Let us see it in action:\n\n\n\nAs the video demonstrates the dashboard builder allows us to take a notebook with or without Panel components re-arrange, delete and move around cell outputs and markdown cells to build a dashboard."
},
{
"objectID": "posts/panel_release_1.4/index.html#autoreload",
"href": "posts/panel_release_1.4/index.html#autoreload",
"title": "Panel 1.4.0 Release",
"section": "Autoreload",
"text": "Autoreload\nIn previous versions, Panel’s autoreload feature was a handy tool for developers, allowing them to see changes in their applications in real time without the need for manual refreshes. In this release we have outsourced the detection of changes to the watchfiles, not only have we improved the speed of file detection changes, but we have also enhanced the reliability and responsiveness of the autoreload process. To get the benefits of this improved autoreload experience install watchfiles with pip or conda.\nAs part of the improvements we have already improved the handling around of reloading of local modules and entire packages. The improved autoreload mechanism now offers more robust support for detecting changes within your project’s modules and packages, ensuring that any update, no matter how small or where it occurs, will trigger a reload."
},
{
"objectID": "posts/panel_release_1.4/index.html#chat-improvements",
"href": "posts/panel_release_1.4/index.html#chat-improvements",
"title": "Panel 1.4.0 Release",
"section": "Chat Improvements",
"text": "Chat Improvements\nAs the world of LLMs and chat bots built around them continues to advance we are working hard on building the simplest and most feature rich Chat interface with out-of-the-box support for multi-modal outputs and much more.\nThe two main enhancements of the ChatInterface in this release were the addition of two new base components:\n\nFeed\nThe Feed layout added to Panel makes it possible to build a (near) infinitely scrollable layout making it possible for us to support chats with huge message histories containing 100s or 1000s of messages without having to render it all at once.\n\n\n\nChatAreaInput\nThe old TextInput and TextArea used as the text entry widget for the ChatInterface were providing a sub-optimal user experience. Therefore we introduced the ChatAreaInput with behavior closer to what you’re used to from familar chat interfaces like OpenAI including send on <Enter> and multi-line input using <Shift>+<Enter> key combinations. We aim to continue refining this component over time:\n\npn.chat.ChatAreaInput(placeholder='Start a chat...')\n\n\n\n\n\n \n\n\n\n\n\n\nOther enhancements\nThere are a whole host of other improvements for the chat components in this release:\n\nEvery part of a ChatMessage can now be styled\nAddition of more default avatars\nThe ability to add custom objects to the header and footer of a ChatMessage\n\n\npn.Row(\n pn.pane.Markdown(f'```css\\n{stylesheet}```', margin=0, height=300, styles={'overflow-y': 'scroll'}),\n pn.chat.ChatMessage(\n \"Style me up!\",\n show_activity_dot=True,\n header_objects=['I am a custom header'],\n footer_objects=['I am a custom footer'],\n stylesheets=[stylesheet], width=600\n )\n)"
},
{
"objectID": "posts/panel_release_1.4/index.html#reactive-expressions-references",
"href": "posts/panel_release_1.4/index.html#reactive-expressions-references",
"title": "Panel 1.4.0 Release",
"section": "Reactive Expressions & References",
"text": "Reactive Expressions & References\nIn Panel 1.3.0 we introduced new concepts around reactive expressions using the param.rx API and the ability to bind reactive references to parameters. This release continues to build on this support in a number of ways:\n\n(Asynchronous) Generator Expressions\nStreaming in Panel has long been possible by registering periodic callbacks, but as we are moving more and more towards a more reactive approach to writing applications we have been improving the support for building applications with (asynchronous) generators. If you upgrade to Param 2.1 you can now use rx expressions that are driven by a generator.\nTo provide a simple demonstration of this approach let us build a generator that emits a value every 0.1 seconds. By wrapping the generator in pn.rx we can now perform standard arithmetic on this dynamic value, e.g. by adding an offset derived from a widget value:\n\ndef gen():\n while True:\n time.sleep(0.1)\n yield np.random.randn()\n\noffset = pn.widgets.FloatSlider(value=0, end=5, name='Offset')\n\npn.Column(\n offset,\n pn.widgets.Number(value=pn.rx(gen)+offset, format='{value:.3f}', width=250, font_size='36pt')\n);\n\n\nThe ability to perform operations on dynamic values derived from generators, parameter values and widgets in this way provides a concise way to express complex data pipelines in a declarative way and thanks to support for binding the reactive references you can easily tie these dynamic values to visual outputs. To discover more about this read some of the new explanation materials on reactivity in Panel and references in Param.\n\n\nSkipping updates\nOne sticking point when working with reactive functions created using the pn.bind API was that there was no way to signal that you did not want to update the output until some condition was met. In Param 2.1 and Panel 1.4 it is now possible to Skip updates by raising a param.Skip exception.\nA good example of this is a form that gathers some inputs but should not run until a button is clicked. In our add callback we return Skip until the button triggers a calculate event at which point we yield a message to say we are Calculating and then yield the result:\n\na_slider = pn.widgets.FloatSlider()\nb_slider = pn.widgets.FloatSlider()\nbutton = pn.widgets.Button(name='Calculate')\n\ndef add(a, b, calculate):\n if not calculate:\n return pn.param.Skip\n yield '**Calculating...**'\n time.sleep(1)\n yield f'**{a} + {b} = {a+b:.1f}**'\n\npn.Column(a_slider, b_slider, button, pn.bind(add, a_slider, b_slider, button));"
},
{
"objectID": "posts/panel_release_1.4/index.html#new-components",
"href": "posts/panel_release_1.4/index.html#new-components",
"title": "Panel 1.4.0 Release",
"section": "New Components",
"text": "New Components\nNo new Panel release would be complete without the addition of at least a few new components. In addition to some of the new Chat components we also have a few new panes and widgets for you.\n\nTextual\nTextual is a Rapid Application Development framework for Python allowing users to build so called TUIs. As the popularity of this framework has grown and more and a growing number of tools offer Textual UIs we decided to explore whether it would be possible to embed these apps inside Panel, and quickly the Textual pane was born - wrap any Textual application in Panel and use it from within a notebook or in your standalone application:\n\n\n\nNew widgets\nEvery new Panel release must come with at least a few new widgets, in this release we have three main new additions for you the NestedSelect widget to simplify selection of values with a hierarchical relationship, the DateRangePicker, a simplified version of the DatetimeRangePicker without the time selection and the ButtonIcon.\n\n\nNestedSelect\nThe NestedSelect widget makes it possible to express hierarchical relationship either using a static definition or by providing a callback to generate the valid values:\n\nnested_select = pn.widgets.NestedSelect(\n options={\n \"Africa\": {\n \"Ghana\": [\"Accra\", \"Kumasi\", \"Tamale\"],\n \"Nigeria\": [\"Abuja\", \"Kano\", \"Ibadan\", \"Lagos\"],\n },\n \"Asia\": {\n \"Thailand\": [\"Bangkok\", \"Chiang Mai\", \"Nakhon Ratchasima\"],\n \"Vietnam\": [\"Da Nang\", \"Hanoi\", \"Ho Chi Minh City\"],\n },\n },\n levels=[\"Continent\", \"Country\", \"City\"],\n)\n\npn.Row(nested_select, pn.pane.ParamRef(nested_select.param.value));\n\n\n\n\nDateRangePicker\nThe DateRangePicker widget provides a simple but welcome addition to the widget pantheon as the smaller brother of the DatetimeRangePicker.\n\npn.widgets.DateRangePicker(value=(dt.date(2023, 11, 17), dt.date(2024, 3, 27)), name='DateRangePicker');\n\n\n\n\nButtonIcon\nThe new ButtonIcon is to the ToggleIcon what Button is to the Toggle widget. Sometimes you just want to be able to click on an icon and trigger an event:\n\npn.GridBox(\n pn.widgets.ButtonIcon(icon=\"heart\", size=\"8em\", description=\"Favorite\"),\n pn.widgets.ButtonIcon(icon=\"clipboard\", active_icon=\"check\", size=\"8em\", description=\"Copy\"),\n ncols=2\n)"
},
{
"objectID": "posts/panel_release_1.4/index.html#roadmap",
"href": "posts/panel_release_1.4/index.html#roadmap",
"title": "Panel 1.4.0 Release",
"section": "Roadmap",
"text": "Roadmap\nThe Panel 1.4.0 release was quite feature packed but we already have a number of major new features in the pipline.\n\nESM Components\nIn Panel 1.5.0 we plan to finally provide a more robust replacement for ReactiveHTML based on ESM modules with a modern development experience including hotreloading, ability to import JS modules and React based components.\n\n\nSession Handling & Scaling\nRecently we found various opportunities for optimizing and parallelizing session creation and session handling including moving the session creation to threads and opening up the ability to re-connect to a session if the websocket is closed."
},
{
"objectID": "posts/panel_release_1.4/index.html#changelog",
"href": "posts/panel_release_1.4/index.html#changelog",
"title": "Panel 1.4.0 Release",
"section": "Changelog",
"text": "Changelog\n\nFeatures\n\nAdd EditableTemplate to support dashboard builder UI in Jupyter (#5802)\nAdd ChatAreaInput as default text input widget for ChatInterface (#6379)\nAdd NestedSelect widget (#5791, #6011)\nAdd Panel tutorials (#5525, #6208, #6212, #6388, #6425, #6466, #6491)\nAdd DateRangePicker widget (#6027)\nAdd Feed layout and use it as layout for ChatFeed (#6031, #6296)\nAdd WebP pane (#6035)\nAdd ButtonIcon (#6138)\nAdd Textual pane (#6181)\n\n\n\nEnhancements\n\nImprove --autoreload by using watchfiles and selectively reloading packages (#5894, #6459) Load loading indicator from file instead of inlining (#6112)\nAllow providing additional stylesheets in card_params (#6242)\nAdd scroll options to permanently toggle on layouts (#6266)\nAllow choosing position of frozen columns on Tabulator (#6309)\nAdd help message on ChatFeed (#6311)\nEnsure CSS can be applied to every aspect of ChatMessage (#6346)\nAdd HoloViz logos as ChatMessage avatars (#6348)\nAdd gap parameter to FlexBox (#6354)\nSet default step of DatetimeRangeSlider to 1 minute (#6373)\nAdd support for passing objects reference to FlexBox (#6387)\nAllow editable sliders to be embedded (#6391)\nAdd message into css_classes to ChatMessage markup (#6407)\nAllow appending objects to the ChatMessage header & footer (#6410)\nAdd ability to declare icon label (#6411)\nAdd title and settings and fix datetime to Perspective (#6482)\nWarn user if loading extension in VSCode or Colab without jupyter_bokeh (#6485)\nThrottle updates to Boolean indicators (#6481)\nAdd ParamRef baseclass for ParamFunction and ParamMethod (#6392)\nAdd ability to Skip Param<Ref|Function|Method> updates (#6396)\nAdd Param<Ref|Method|Function> and ReactiveExpr to panes module (#6432)\nSet up param.rx display accessor on import (#6470)\nAllow using Carto tiles in DeckGL (#6531)\nImprove VTKJS binary serialization (#6559)\nEnsure component CSS is pre-loaded if possible, avoiding flicker on load (#6563)\n\n\n\nStyling\n\nEnsure navbar toggle icon is correct color in BootstrapTemplate (#6111)\nChange loading background filters to work better in dark themes (#6112)\nImprove styling of FileInput widget (#6479)\nImprove Jupyter Preview error handling and error template (#6496)\nAdd scale animation to icons on hover and click (#6376)\nRedesign index pages (#6497)\nImprove Tabulator editor text color in Fast design (#6512)\nEnsure BootstrapTemplate hamburger icon is white (#6562)\n\n\n\nCompatibility & Version Updates\n\nBump Perspective version to 2.9.0 (#5722)\nUpgrade to Bokeh 3.4.x (#6072)\nUpgrade Vizzu to 0.9.3 (#6476)\nBump JSONEditor version to 10.0.1 (#6477)\nUpgrade to PyScript Next and Pyodide 0.25.0 in panel convert (#6490)\nBump vtk.js version to 30.1.0 (#6559)\n\n\n\nBug fixes\n\nAdd resize handler for FloatPanel (#6201)\nFix serving of global template in notebook (#6210)\nEnsure Tabulator renders in collapsed Card (#6223)\nFix issues with VTK, VTKVolume and VTKJS due to webgpu renderer (#6244)\nEnsure HTML and other markup panes can be emptied (#6303)\nEnsure collapsed Card does not cause stretching (#6305)\nEnsure notebook preview always uses server resources (#6317)\nRemove animation from loading spinner without spin (#6324)\nEnsure model is only added/removed from Document once (#6342)\nEnsure loading_indicator resets when configured with context manager (#6343)\nFix modal overflow and resizing issues (#6355)\nEnsure that ripple matches notification size (#6360)\nFully re-render CodeEditor on render calls ensuring it displays correctly (#6361)\nEnsure FileDownload button has correct height (#6362)\nEnsure HTML model is redrawn if stylesheets is emptied (#6365)\nAllow providing custom template (#6383)\nEnsure Debugger renders without error (#6423)\nEnsure pending writes are dispatched in order and only from correct thread (#6443)\nEnsure layout reuses model if available (#6446)\nImproved exception handler in unlocked message dispatch (#6447)\nFix display of interactive Matplotlib (#6450)\nEnsure Plotly pane renders and hides correctly in Card (#6468)\nFix issues rendering widget components with Fast design (#6474)\nFix binary serialization from JS -> Pyodide (#6490)\nAvoid overeager garbage collection (#6518)\nFix floating point error in IntRangeSlider (#6516)\nLoad JS modules from relative path (#6526)\nEnsure no events are dispatched before the websocket is open (#6528)\nEnsure Markdown parsing does not choke on partial links (#6535)\nFixes to ensure larger PDFs can be rendered (#6538)\nEnsure IPywidget comms are only opened once (#6542)\nFixes for message handling in Jupyter Preview context (#6547)\nFix unnecessary loading of ReactiveHTML resources (#6552)\nEnsure Template.raw_css has higher precedence than default template CSS (#6554)\nAvoid asyncio event loop startup issues in some contexts (#6555)\nEnsure column subset is retained on Tabulator.style (#6560)\nEnsure bokeh mathjax bundle when mathjax extension is loaded in notebook (#6564)\n\n\n\nChat Components\n\nEnsure ChatInterface respect supplied default user (#6290)\nEnsure ChatMessage internals correctly respect Design (#6304)\nFix ChatInterface stop button for synchronous functions (#6312)\nInclude stylesheets downstream, including layouts in ChatMessage (#6405)\nEnsure ChatMessage header updates dynamically (#6441)\nEnsure streaming ChatMessage on ChatInterface and mention serialize (#6452)\nEnsure ChatInterface supports chat input without value_input parameter (#6505)\nEnsure word breaks to avoid overflow in ChatMessage (#6187, #6509)\nEnsure nested disabled state stays disabled on ChatFeed (#6507)\nAllow streaming None as the initial ChatMessage value (#6522)\n\n\n\nDocumentation\n\nAdd Roadmap to documentation (#5443)\nRefactor ReactiveHTML docs (#5448, #6358)\nImprove HoloViews reference guide (#6065)\nImprove the user experience for resetting Jupyterlite (#6198)\nAdd explanation docs about APIs (#6289, #6469)\nAdd section headers to Chat reference documentation (#6370)\nMigrate gallery to new Anaconda DSP instance (#6413)\nImprove home page (#6422)\nAdding AWS deployment to documentation (#6434)\nUpdate Streamlit comparison (#6467)\nAdd logging how-to guide (#6511)\nDocument pygments dependency for code syntax highlighting (#6519)\nAdd how-to guide on configuring PyCharm (#6525)\n\n\n\nDeprecations & Removals\n\nRemove Ace alias for CodeEditor\nRemove ChatBox which has been replaced by panel.chat components\nRemove HTML.style which is now replaced with HTML.styles\nRemove Trend.title which is now replaced by Trend.name\nRemove Viewable.app which is now replaced with pn.io.notebook.show_server\nRemove Viewable.background which is now replaced with Viewable(styles={'background': ...})\nRemove Viewable.pprint which is now replaced with print(Viewable(...))"
},
{
"objectID": "posts/panel_release_0.8/index.html",
"href": "posts/panel_release_0.8/index.html",
"title": "Panel 0.8.0 Release",
"section": "",
"text": "We are very pleased to announce the 0.8.0 release of Panel! This release focuses primarily on solidifying existing functionality, significantly improving performance, and fixing a number of important bugs. This release also contains some exciting new functionality and new components. We want to thank the many recent contributors (a full list is provided at the bottom), particularly Marc Skov Madsen (the author of awesome-panel.org) and Xavier Artusi (who has been hard at work at improving VTK support). This release introduced only minimal changes in existing APIs while adding a small number of new ones, reflecting the fact that Panel is now relatively stable and is progressing steadily towards a 1.0 release.\nIf you are using Anaconda, you can get latest Panel with conda install -c pyviz panel , and using pip you can install it with pip install panel."
},
{
"objectID": "posts/panel_release_0.8/index.html#new-components",
"href": "posts/panel_release_0.8/index.html#new-components",
"title": "Panel 0.8.0 Release",
"section": "New components",
"text": "New components\nThis release contains fewer entirely new components than in other recent releases. Even so, we are very excited about the new functionality offered by these new panes and widgets.\n\nDeckGL\ndeck.gl is a WebGL-powered framework for visual exploratory data analysis of large datasets. With the addition of the DeckGL pane in Panel it is now straightforward to embed deck.gl plots into a Panel app, either specified as raw JSON or using the pydeck bindings. Just like the Plotly and Vega panes, Panel will extract any embedded datasets and use binary serialization to send the dataset to the frontend efficiently.\n\n\n\n\nDeckGL example (as shown in the reference gallery)\n\n\n\nThe DeckGL pane (like other panes) can also be easily and efficiently updated from Python, making it possible to build complex apps quickly and easily. Below is a demo (adapted from a pydeck example) demonstrating Conway’s Game of Life rendered using deck.gl and linked to widgets that control the updates to the plot:\n\n\n\n\nDeckGL Game of Life (as shown in the gallery).\n\n\n\nIn addition to being able to efficiently update the DeckGL plot, Panel’s Deck support also allows accessing click and hover events as well as the current range.\n\n\nJSON\nMany of the components in Panel can be specified using a JSON file (e.g. Vega and Deck.GL plots), and JSON is a very common interchange format for files in general. You could already have viewed the contents of such a file using a Markdown pane or an ACE widget, but Panel now provides a compact JSON pane that lets you explore the contents of such a file in a tree-like format.\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\nJSON pane (as shown in the reference gallery).\n\n\n\n\n\nVTKVolume (improved)\nThe VTKVolume pane for 3D volumetric rendering has been greatly improved for this release. In particular, this release includes the ability to view 2D slices and control many other properties from both Python and Javascript:\n\n\n\n\nVTKVolume pane as shown in the reference gallery.\n\n\n\n\n\nFileSelector\nAnother frequently requested feature was adding a file browser which would let you browse files on the server (not the local machine) and select them. The new FileSelector widget lets you explore the specified path and select one or more files:\n\n\n\n\nFileSelector widget as shown in the reference gallery."
},
{
"objectID": "posts/panel_release_0.8/index.html#enhancements",
"href": "posts/panel_release_0.8/index.html#enhancements",
"title": "Panel 0.8.0 Release",
"section": "Enhancements",
"text": "Enhancements\nThe real focus in this release was addressing a number of important usability issues, such as making Panel’s use of Bokeh’s layout engine more scalable, improving Markdown handling, improving the debugging experience in the notebook, and much more.\n\nPerformance Improvements\nPanel is built on top of Bokeh, which provides a powerful engine to lay out arbitrary objects on an HTML page. However because this layout engine has to solve a lot of constraints simultaneously, it has to measure the size of all the different components on the page. This process has serious performance implications for deeply nested layouts or layouts that contain a lot of raw HTML contents where the size is not known ahead of time. In this release, we have added caching of the extents to many of the core Panel components. As a result, many complex dashboards which weren’t feasible before or required building a custom template can now be built entirely using Panel’s layouts, without performance issues.\nIn many complex, real-world dashboards you should now get significantly improved performance and a much more responsive UI. In our testing, the layout engine for our more complex apps achieved 10-100x speedups.\n\n\nImproved JavaScript-based linking\nPanel has always supported linking directly between components using JavaScript, and the previous release added support for arbitrary JS callbacks in the last release. These links are particular useful for export to static HTML files, as they continue functioning even without a Python server present. However, when compared to the Python API, js-links often had a variety of limitations. Certain widgets would return a value of a different type than they would in Python, and the property names sometimes did not match those in Python. These seemingly small differences meant that Python-based linking would often not translate directly to Javascript. In this release we defined “transforms” for many different components that will automatically smooth over these differences. A much wider range of parameters can now be controlled directly from Javascript, without ever having to call back into Python and without the user having to write any JavaScript linking code.\nThis change has also allowed us to update all the reference gallery examples with section on controls, where users can interactively explore the effect of various parameters directly on the website.\n\naudio = pn.pane.Audio('http://ccrma.stanford.edu/~jos/mp3/pno-cs.mp3', name='Audio')\n\npn.Row(hspace, audio.controls(['loop', 'time', 'paused', 'volume'], jslink=True), audio, hspace)\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\nAudio pane controls as shown in the reference gallery.\n\n\n\n\n\nEasier debugging\nIn previous releases of Panel all print output or errors in Python, triggered by a change on the frontend, would get routed to the Javascript console. This was obscure and made it hard to debug apps built in the notebook. Starting in this release Panel will display all print output and errors at the top of the cell where they were triggered. This can be controlled using the panel.config.console_output option, which can be set to 'accumulate' (the default), 'replace', or 'disable'.\n\n\n\n\nOutput handling in the notebook\n\n\n\n\n\nBetter Markdown handling\nThe Markdown support in Panel makes it very easy to add some explanatory text, titles, images, and code to an app or dashboard. In this release the Markdown pane finally supports syntax highlighting for source code by default, and will normalize the indentation to make it easier to define Markdown in multi-line string.\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\nMarkdown pane with syntax highlighting\n\n\n\n\n\nResponsive Plotly and Vega\nResponsive plots adapt to the size of their container, such as a browser window, which is important for making dashboards that fill the screen or plots that work well on both desktop and mobile devices. Previously only Bokeh plots could be responsive, but now all interactive plotting libraries can support responsive embedding.\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\nResponsive plots as shown in the Vega and Plotly reference gallery.\n\n\n\n\n\nImproved Tabs\nTabs are one of the most versatile layout components in Panel, allowing you to make single pages that work like multi-page apps. Previously, all content would be rendered in every tab, whether or not the tab was currently (or ever!) selected. In this release we added a dynamic option, which ensures that a tab is only populated when it is selected. This option makes it possible to render a larger number of tabs and/or tabs with more content without sending the data to the browser and rendering the contents all at once. For example, below we have created a set of tabs containing complex plots from a number of different libraries. Rendering all of these simultaneously would be expensive, but with the dynamic option any number of such plots can be used in the same app:\n\n\n\n\nDynamic Tabs as shown in the gallery\n\n\n\n\n\npn.serve\nWhen serving from the commandline it has always been possible to serve multiple apps at once (just supply all of their filenames to the panel serve command). However, when developing apps interactively there has so far not been any easy way to serve multiple apps at once. In this release we have added a panel.serve function that accepts a dictionary and will serve each of the apps at the endpoint defined by the key. Additionally, if no root app has been defined (by declaring '/' in the dictionary) it will automatically generate an index to let you select between the apps:\npn.serve({'DeckGL': deckgl_pane, 'Plotly': plotly_pane, 'Vega': vega_pane})\n\n\n\n\nMultiple apps served using pn.serve"
},
{
"objectID": "posts/panel_release_0.8/index.html#documentation",
"href": "posts/panel_release_0.8/index.html#documentation",
"title": "Panel 0.8.0 Release",
"section": "Documentation",
"text": "Documentation\nA special shoutout goes to Marc Skov Madsen who started the awesome-panel project to demonstrate some of the advanced capabilities in Panel. Another huge thank you to Stephane Junod for adding a new complex multi-app Django example."
},
{
"objectID": "posts/panel_release_0.8/index.html#roadmap",
"href": "posts/panel_release_0.8/index.html#roadmap",
"title": "Panel 0.8.0 Release",
"section": "Roadmap",
"text": "Roadmap\nWe are steadily marching towards Panel’s 1.0 release in the coming months, with a stable API and feature set. In the meantime, we are hoping to add some important new features:\n\nDefault templates (built on Bootstrap and Materialize CSS frameworks) that make it easy to generate more polished looking apps\nSecurity and authentification features that allow users to use standard OAuth services such as GitHub or Twitter or a simple username/password form\nSupport for using ipywidgets inside a Panel app\nDropping Python 2 support\n\nWe expect to make another release in just a few weeks for compatibility with the upcoming Bokeh 2.0 release."
},
{
"objectID": "posts/panel_release_0.8/index.html#changelog",
"href": "posts/panel_release_0.8/index.html#changelog",
"title": "Panel 0.8.0 Release",
"section": "Changelog",
"text": "Changelog\n\nMajor Features & Enhancements\n\nAdded new DeckGL pane (#1019, #1027)\nMajor improvements to support for JS linking (#1007)\nHuge performance improvements when nesting a lot of components deeply (#867, #888, #895, #988)\nAdd support for displaying callback errors and print output in the notebook simplifying debugging (#977)\nAdd support for dynamically populating Tabs (#995)\nAdded FileSelector widget to browse the servers file system and select files (#909)\nAdd pn.serve function to serve multiple apps at once on the same serve (#963)\nAdd a JSON pane to display json data in a tree format (#953)\n\n\n\nEnhancements\n\nUpdated Parameter mappings (#999)\nEnsure that closed tabs update Tabs.objects (#973)\nFixed HoloViews axis linking across Template roots (#980)\nMerge FactorRange when linking HoloViews axes (#968)\nExpose title and other kwargs on .show() (#962)\nLet FileInput widget set filename (#956) [@Italirz]\nExpose further bokeh CLI commands and added help (#951)\nEnable responsive sizing for Vega/altair pane (#949)\nAdded encode parameter to SVG pane (#913)\nImprove Markdown handling including syntax highlighting and indentation (#881)\nAdd ability to define Template variables (#815)\nAllow configuring responsive behavior globally (#851)\nEnsure that changes applied in callbacks are reflected on the frontend immediately (#857)\nAdd ability to add axes coordinates to VTK view (#817) [@xavArtley]\nAdd config option for safe_embed which ensures all state is recorded (#1040)\nImplemented __signature__ for tab completion (#1029)\n\n\n\nBug fixes\n\nFixed DataFrame widget selection parameter (#989)\nFixes for rendering long strings on Windows systems (#986)\nEnsure that panel does not modify user objects (#967)\nFix multi-level expand Param subobject (#965)\nEnsure load_notebook is executed only once (#1000)\nFixed bug updating StaticText on server (#964)\nDo not link HoloViews axes with different types (#937)\nEnsure that integer sliders are actually integers (#876)\nEnsure that GridBox contents maintain size (#971)\n\n\n\nCompatibility\n\nCompatibility for new Param API (#992, #998) [@jlstevens]\nChanges for compatibility with Vega5 and altair 4 (#873, #889, #892, #927, #933)\n\n\n\nBackwards compatibility\n\nThe Ace pane has been deprecated in favor of the Ace widget (#908) [@kgullikson88]\n\n\n\nDocs\n\nUpdated Django multiple app example and user guide (#928) [@stefjunod]\nClarify developer installation instructions, and fix up some metadata. (#952, #978)\nAdded Param reference notebook (#944)\nAdded Divider reference notebook"
},
{
"objectID": "posts/panel_release_0.8/index.html#contributors",
"href": "posts/panel_release_0.8/index.html#contributors",
"title": "Panel 0.8.0 Release",
"section": "Contributors",
"text": "Contributors\nMany thanks to the many contributors to this release:\n\nPhilipp Rudiger (@philippjfr): Maintainer & lead developer\nXavier Artusi (@xavArtley): VTK support\nStephane Junod (@stefjunod): Django documentation\nJames A. Bednar (@jbednar): Documentation\nMarc Skov Madsen (@MarcSkovMadsen): DeckGL pane and Param documentation\nChris Ball (@ceball): Build infrastructure and testing\nJean-Luc Stevens (@jstevens): Param compatibility\nMateusz Paprocki (@mattpap): Build infrastructure\nJacob Barkhak (@JacobBarhak): Progress bars for embedding\nKevin Gullikson (@kgullikson88): Converting Ace pane to widget\nLeopold Talirz (@ltalirz): Fix for FileInput.filename"
},
{
"objectID": "posts/panel_release_0.14/index.html",
"href": "posts/panel_release_0.14/index.html",
"title": "Panel 0.14.0 Release",
"section": "",
"text": "What is Panel?\nPanel is an open-source library that lets you create custom interactive web apps and dashboards by connecting widgets to plots, images, tables, and text - all while writing only Python!\nPanel integrates seamlessly with your existing work:\nPlease check out the Panel website to find out more.\nNew release!\nWe are very pleased to announce the 0.14 release of Panel! This release focuses on three main themes:\nYou can now easily:\nMany, many thanks to everyone who filed issues or contributed to this release. In particular we would like to thank @janimo, @xavArtley, @thuydotm, @jmosbacher, @dmarx, 2WoLpH, @ipopa144, @sdc50 and the core team consisting of @philippjfr, @Hoxbro, @maximlt and @MarcSkovMadsen.\nIf you are using Anaconda, you can get the latest Panel with conda install -c pyviz panel , and using pip you can install it with pip install panel."
},
{
"objectID": "posts/panel_release_0.14/index.html#run-panel-apps-entirely-in-your-browser",
"href": "posts/panel_release_0.14/index.html#run-panel-apps-entirely-in-your-browser",
"title": "Panel 0.14.0 Release",
"section": "Run Panel apps entirely in your browser",
"text": "Run Panel apps entirely in your browser\nPanel lets you write dashboards and other applications in Python that are accessed using a web browser. You typically have to deploy your Panel app to a web server. This introduces some pains\n\nDeployment of a web server is a separate skill that takes time and is costly\nThe communication between the server and browser introduces latency, it takes time to communicate back and forth.\n\nPanel 0.14 flips that on its head. It is now possible to run a large set of Panel Apps directly in the browser, with no separate server needed and super-fast response times!\nThe underlying technology involved is called WebAssembly (or WASM). More specifically, Pyodide pioneered the ability to install Python libraries, manipulate the web page’s DOM from Python, and execute regular Python code entirely in the browser. A number of libraries have sprung up around Python in WASM, including PyScript.\nThere are three ways you can run Panel apps in your browser:\n\nAutomatically converting Panel applications into a Pyodide/PyScript based application\nManually installing Panel in the browser and using it to render components.\nEmbedding Panel in your Sphinx documentation.\n\nFind detailed documentation about each in the documentation.\n\nConvert apps\nLet us suppose you have an existing Panel application that runs an XGBoost classifier on the iris dataset (thanks to Bojan Tunguz for the example):\n\n\nExpand to see the code\n\nimport panel as pn\n\nfrom sklearn.datasets import load_iris\nfrom sklearn.metrics import accuracy_score\nfrom xgboost import XGBClassifier\n\npn.extension(sizing_mode=\"stretch_width\", template=\"fast\")\npn.state.template.param.update(title=\"XGBoost Example\")\n\niris_df = load_iris(as_frame=True)\n\nn_trees = pn.widgets.IntSlider(start=2, end=30, name=\"Number of trees\")\nmax_depth = pn.widgets.IntSlider(start=1, end=10, value=2, name=\"Maximum Depth\") \nbooster = pn.widgets.Select(options=['gbtree', 'gblinear', 'dart'], name=\"Booster\")\n\ndef pipeline(n_trees, max_depth, booster):\n model = XGBClassifier(max_depth=max_depth, n_estimators=n_trees, booster=booster)\n model.fit(iris_df.data, iris_df.target)\n accuracy = round(accuracy_score(iris_df.target, model.predict(iris_df.data)) * 100, 1)\n return pn.indicators.Number(\n name=\"Test score\",\n value=accuracy,\n format=\"{value}%\",\n colors=[(97.5, \"red\"), (99.0, \"orange\"), (100, \"green\")],\n align='center'\n )\n\npn.Row(\n pn.Column(booster, n_trees, max_depth, width=320).servable(area='sidebar'),\n pn.Column(\n \"Simple example of training an XGBoost classification model on the small Iris dataset.\",\n iris_df.data.head(),\n \"Adjust the hyperparameters to re-run the XGBoost classifier. \"\n \"The training accuracy score will adjust accordingly:\",\n pn.bind(pipeline, n_trees, max_depth, booster)\n ).servable(),\n)\n\n\nTo convert this app to a Pyodide based app that runs entirely in your browser just follow these three simple steps:\n\nRun panel convert script.py --to pyodide-worker --out pyodide\nRun python3 -m http.server to start a web server locally\nOpen http://localhost:8000/pyodide/script.html to try out the app.\n\nThe resulting application should look something like this:\n\nThe Panel conversion script has some additional features:\n\nOutput standalone Pyodide or PyScript HTML files or generate a separate script that runs your app in a WebWorker.\nPre-render the application so the user is not staring at a blank page while the application loads.\nGenerate an index page if you want to convert multiple applications.\nAutomatically generate a manifest and service worker to turn your application into a Progressive Web App that can be run offline.\n\nTry out a few converted apps right now (Note: loading these applications will download a Python runtime ~40MB):\n\nXGBoost Classifier\nhvPlot Explorer\nDeckGL Game of Life\nStreaming Indicators\n\nThe Streaming example demonstrates the potential speed ups from running Python data viz in the browser. Before when running on a server an update frequency of 500ms was realistic. Now with Panel running in the browser 25ms is no problem!\n\nFind out more about converting applications in the documentation.\n\n\nSphinx extension\nIn order to better demonstrate the interactive features of Panel in our documentation we decided to write a Sphinx extension that would turn static embedded content into live material that can be run entirely in your browser. When you visit certain pages in our documentation you will now see a Play button on the code cells. When you click it, it will warn you that executing the cells will download a Python runtime and once you confirm it will fetch Pyodide, all required libraries and finally render the output.\n\nCaches pyodide and Python packages so you only download them once.\nStatus updates while the Pyodide runtime is loading.\nSupport for rendering most common MIME types just like in Jupyter notebook.\nSupport for stdout and stderr handling.\nEmbeds static output while building the docs.\n\nLet us look at the extension in action:\n\nFor now this is experimental but we will be working hard to release a standalone Sphinx extension. For now see the documentation to set this up yourself.\n\n\nUse Panel in PyScript & Pyodide\nYou can of course also easily leverage Panel by writing your own Pyodide and PyScript code. We have a detailed guide how to convert your own applications, leverage Panel from PyScript & Pyodide and how to use the Sphinx extension in the Panel documentation. We also hope to split out the Sphinx extension into its own package in the near future."
},
{
"objectID": "posts/panel_release_0.14/index.html#simplified-caching",
"href": "posts/panel_release_0.14/index.html#simplified-caching",
"title": "Panel 0.14.0 Release",
"section": "Simplified Caching",
"text": "Simplified Caching\nSince the very early days Panel has had a global cache in the form of the pn.state.cache dictionary (and the pn.state.as_cached helper function), which allows you to cache data and other objects across sessions easily. This is a very simple approach but still requires some manual effort to set up, for that reason we have decided to include a new pn.cache decorator in this release which automatically memoizes a function, e.g. if we have a function to load_data given some path the pn.cache function will cache the output for the given path:\n\n@pn.cache(max_items=10, policy='LRU', ttl=60, to_disk=False)\ndef load_data(path):\n return ... # Load some data\n\nThe pn.cache decorator provides a number of configurable options including:\n\nThe caching policy including least-recently-used ('LRU'), least-frequently-used ('LFU') and first-in-first-out ('FIFO') options.\nThe max_items in the cache before an item is evicted according to the specified policy.\nThe maximum time-to-live (ttl) before a cached value expires.\nWhether to cache to_disk by using the diskcache library. Ensuring that even when an application is restarted the cache can persist.\n\nBy combining caching with pn.bind or pn.depends it is now simpler than ever to build highly performant applications. Take this example which will cache each CSV the first time it is selected:\nselect = pn.widgets.Select(options={\n 'Penguins': 'https://raw.githubusercontent.com/mwaskom/seaborn-data/master/penguins.csv',\n 'Diamonds': 'https://raw.githubusercontent.com/mwaskom/seaborn-data/master/diamonds.csv',\n 'Titanic': 'https://raw.githubusercontent.com/mwaskom/seaborn-data/master/titanic.csv',\n 'MPG': 'https://raw.githubusercontent.com/mwaskom/seaborn-data/master/mpg.csv'\n})\n\n@pn.cache\ndef fetch_data(url):\n return pd.read_csv(url)\n\npn.Column(select, pn.bind(pn.widgets.Tabulator, pn.bind(fetch_data, select), page_size=10))\nRead more about pn.cache in the documentation."
},
{
"objectID": "posts/panel_release_0.14/index.html#easily-defer-loading-of-components",
"href": "posts/panel_release_0.14/index.html#easily-defer-loading-of-components",
"title": "Panel 0.14.0 Release",
"section": "Easily defer loading of components",
"text": "Easily defer loading of components\nOne drawback of the current server architecture of Panel (which is inherited from Bokeh) is that before an application can be served the whole script or notebook has to be evaluated. This means that the user won’t see anything until that process completes. While expensive computations can and should be deferred until the initial page is rendered using the pn.state.onload mechanism this requires writing callbacks.\nInstead you can now easily enable a defer_load option and perform any expensive computations in a callback. For example we might load some data and render a Tabulator widget inside a function:\n\ndef expensive_component():\n return pn.widgets.Tabulator(load_data())\n\npn.panel(expensive_component, defer_load=True);\n\nIn a server application Panel will initially render a loading spinner in place of the component and then resolve the component after the initial page is rendered.\nIf you want to defer loading of all components (that have been wrapped in a function) you can also set the global configuration option by setting pn.config.defer_load = True or using pn.extension(..., defer_load=True).\nLet us have a look at what this looks like in practice:"
},
{
"objectID": "posts/panel_release_0.14/index.html#simpler-exception-handling",
"href": "posts/panel_release_0.14/index.html#simpler-exception-handling",
"title": "Panel 0.14.0 Release",
"section": "Simpler Exception Handling",
"text": "Simpler Exception Handling\nSurfacing errors to users and logging them has so far required developers to manually catch those errors. To simplify this we now support providing an exception_handler to the global pn.config. This makes it simple to intercept exceptions triggered by user interactions and notifying them of the issue or logging the error:"
},
{
"objectID": "posts/panel_release_0.14/index.html#jupyter-server",
"href": "posts/panel_release_0.14/index.html#jupyter-server",
"title": "Panel 0.14.0 Release",
"section": "Jupyter Server",
"text": "Jupyter Server\nSince Panel 0.12 we have had support for previewing Panel applications in Jupyter. This worked by launching a Panel server alongside Jupyter which would serve the applications. This was fine for Jupyter installations where the kernel and Jupyter itself were defined in the same environment. However in many configurations the Jupyter runs in a distinct environment and the user can choose between one or more kernels. In this release we have re-implemented a server that runs entirely within a Jupyter kernel and when previewing a notebook we will automatically default to the kernel selected in the notebook’s metadata. Alternatively you can select a kernel using a query argument, e.g. ?kernel=Python. See an example below:\n\nWhen you install Panel it will automatically register a server extension that registers a /panel-preview/render/ endpoint on your Jupyter server and allow you to render any notebook or script as a Panel app."
},
{
"objectID": "posts/panel_release_0.14/index.html#faster-loading-of-resources",
"href": "posts/panel_release_0.14/index.html#faster-loading-of-resources",
"title": "Panel 0.14.0 Release",
"section": "Faster loading of resources",
"text": "Faster loading of resources\nIn the past CDN resources were variously loaded from unpkg.com and cdn.jsdelivr.net. These CDN providers proved to be slightly unreliable leading to highly variable latencies (see the results below). Therefore we decided to manage our own CDN at cdn.holoviz.org. By default all CDN resources will now be loaded from there with significantly increased reliability and lower latencies.\n\nimport requests\n\nold = 'https://unpkg.com/@holoviz/panel@0.14.0/dist/panel.min.js'\nnew = 'https://cdn.holoviz.org/panel/0.14.0/dist/panel.min.js'\n\nfor cdn in (old, new):\n print(f\"Timing for panel.min.js loaded from {cdn.split('/')[2]}\") \n %timeit -r 10 -n 1 requests.get(cdn, headers=h)\n\nTiming for panel.min.js loaded from unpkg.com\n1.21 s ± 3.3 s per loop (mean ± std. dev. of 10 runs, 1 loop each)\n\nTiming for panel.min.js loaded from cdn.holoviz.org\n105 ms ± 3.45 ms per loop (mean ± std. dev. of 10 runs, 1 loop each)"
},
{
"objectID": "posts/panel_release_0.14/index.html#other-enhancements",
"href": "posts/panel_release_0.14/index.html#other-enhancements",
"title": "Panel 0.14.0 Release",
"section": "Other enhancements",
"text": "Other enhancements\n\nGeneral\n\nEnsure OAuth redirects to requested app and retains query arguments (#3555)\nAdd extension entry point (#3738)\nEnsure location.unsync unsets query params (#3806)\nDo not flicker busy indicator during --autoreload check (#3804)\nImprove ReactiveHTML loop support and validation (#3813)\nImprove robustness of state.curdoc in threaded and async contexts (#3776, #3810, #3834)\nAllow setting the Oauth provider using environment variables (#3698)\nEnsure that URL query parameters are preserved during OAuth (#3656)\nEnsure components do not re-render if background or loading parameters change (#3599)\nAdd bundling of shared resources (#3894)\n\n\n\nAdmin page\n\nAdd ability to define admin dashboard plugins (#3668)\nUpdate Admin Logs page to use Tabulator (#3694)\nAllow serving admin panel with pn.serve (#3798)\n\n\n\nPane\n\nImprove Markdown code syntax highlighting (#3758)\nSupport declaring Perspective.plugin_config pane (#3814)\n\n\n\nWidgets\n\nAllow None value on numeric sliders and LiteralInput (#3174)\nAdd hard bounds to editable sliders (#3739)\nImplement Player.value_throttled (#3756)\nDo not calculate embed state for disabled widgets (#3757)\nSupport datetime bounds for DatetimePicker and DatetimeRangePicker (#3788)\nAdd Tabulator as default param.DataFrame widget (#3912)\nAdd pn.widget helper function (#1826, #3589)\n\nFor more details see the full release notes."
},
{
"objectID": "posts/panel_release_0.14/index.html#roadmap",
"href": "posts/panel_release_0.14/index.html#roadmap",
"title": "Panel 0.14.0 Release",
"section": "Roadmap",
"text": "Roadmap\nIn the previous minor release we declared that it would be the last minor release before the Panel 1.0 release. Obviously that is not how things happened. Note that 1.0 will introduce major changes and we will be looking to you to provide feedback and help test the release. So look out for announcements of alpha, beta and release candidate releases and help make sure Panel 1.0 will be the success we hope it will be.\n\nSummary\nPanel 1.0 should make it really, really simple and fun to create performant data apps for Pythonistas across development and deployment environments. To achieve this, we want to:\n\nFinalize the core apis of Panel to make them simple and efficient to use\nHeavily improve the documentation. We started by building the technical foundation. Now we are ready for the documentation.\nIdentify performance bottlenecks and remove them.\nUpgrade to Bokeh 3.0 which will provide a much better foundation for styling, performance and Panel in general.\n\n\n\nDocumentation & Website\nAs part of the 0.14 release we did a bunch of work to make the documentation more interactive and organize it a little better. However there is a ton of work to do and we are rolling out a user survey to better understand where we are falling short (Note: This survey will close in early Nov 2022). We have also begun a more general effort to redesign our documentation leaning heavily on the Diataxis framework to make our documentation more accessible and approachable. Look forward to a completely overhauled How-To guide and much more.\n\n\nNative applications\nWith the Beeware project continuing to make strides we now have a basic prototype for compiling Panel application for different platforms including iOS and Android!\n\n\nRewrite of the layout engine\nPanel is built on top of Bokeh which was originally a plotting library but included an extremely powerful server architecture that has allowed us to build this entire ecosystem on top of. One of the legacies of Bokeh being primarily a plotting library was that it included a layout engine to ensure plots could be easily aligned. Unfortunately this also had severe downsides, specifically since this so called “managed layout” had to perform expensive computations to measure the size of different components on the page. This is why when you build complex nested layouts using rows, columns and grids you could sometimes slow down your application.\nThe Bokeh 3.0 release is now fast approaching and we have begun upgrading Panel to support the new CSS based unmanaged layout, which will free us from the performance bottlenecks of the past. This will result in a bright new future for Panel but it may also be also be a little disruptive in the short term. As soon as development versions of Bokeh 3.0 and Panel 1.0 are available we would therefore appreciate if you could provide us with feedback about any regressions related to layouts in your own applications so we can minimize the upgrade path.\n\n\nCSS & Styling\nAnother major change resulting from the upgrade to Bokeh 3.0 will be in the way styling is managed. In the past you had the ability to modify styling of Panel/Bokeh components by constructing somewhat brittle CSS rules. This will now be a thing of the past as we will expose the stylesheets for all components directly in Python. This will afford much greater and simplified control over the styling of components but will also disrupt anyone who relied on applying CSS stylesheets directly. We again hope to minimize the disruptions related to this change and will provide a detailed migration guide.\n\n\nHelp us!\nPanel is an open-source project and we are always looking for new contributors. Join us the discussion on the Discourse and we would be very excited to get you started contributing! Also please get in touch with us if you work at an organization that would like to support future Panel development, fund new Panel features, or set up a support contract.\n\n\nSponsors\nMany thanks to our existing sponsors:"
},
{
"objectID": "posts/panel_release_0.12/index.html",
"href": "posts/panel_release_0.12/index.html",
"title": "Panel 0.12.0 Release",
"section": "",
"text": "What is Panel?\nPanel is an open-source library that lets you create custom interactive web apps and dashboards by connecting widgets to plots, images, tables, and text - all while writing only Python!\nPanel integrates seamlessly with your existing work:\nPlease check out the Panel website to find out more.\nNew release!\nWe are very pleased to announce the 0.12 release of Panel! This release focuses on adding a number of powerful features requested by our users, including:\nHowever, as Panel is moving towards a 1.0 release the large number of bug fixes are almost of equal importance. For a full overview of the changes in this release view the release notes.\nMany, many thanks to everyone who filed issues or contributed to this release. In particular we would like to thank @douglas-raillard-arm, @mathrick, @jlstevens, @hyamanieu, @Liam-Deacon, @Stubatiger, @ablythed, @syamajala, @Hoxbro, @jbednar, @brl0, @OBITORASU, @fleming79, dhruvbalwada and @rmorshea for contributing various fixes and improvements and the core developers @xavArtley, @MarcSkovMadsen and @philippjfr for continuing to push the development of Panel.\nIf you are using Anaconda, you can get the latest Panel with conda install -c pyviz panel , and using pip you can install it with pip install panel."
},
{
"objectID": "posts/panel_release_0.12/index.html#roadmap",
"href": "posts/panel_release_0.12/index.html#roadmap",
"title": "Panel 0.12.0 Release",
"section": "Roadmap",
"text": "Roadmap\nThis release has included a ton of great features but many of the roadmap items from the previous release are still open.\n\nBetter debugging and profiling\nWe also want to make the process of desiging, building, debugging, and optimizing apps easier. We plan to develop new tools to visualize Panel and Param callback and dependency graphs, to help developers understand how data and events propagate through their panels. To help them identify performance bottlenecks, these graphs will be annotated with timing information so that the slow steps can easily be identified.\n\n\nDocumentation overhaul\nAs we approach a Panel 1.0 release we want to overhaul the documentation so it becomes much easier to find the information you are looking for.\n\n\nHelp us!\nPanel is an open-source project and we are always looking for new contributors. Join us the discussion on the Discourse and we would be very excited to get you started contributing! Also please get in touch with us if you work at an organization that would like to support future Panel development, fund new Panel features, or set up a support contract.\n\n\nSponsors\nMany thanks to our existing sponsors:\n\n\n\n\n \n\n\n \n\n\n\n</div>"
},
{
"objectID": "posts/panel_release_0.10/index.html",
"href": "posts/panel_release_0.10/index.html",
"title": "Panel 0.10.0 Release",
"section": "",
"text": "What is Panel?\nPanel is an open-source library that lets you create custom interactive web apps and dashboards by connecting widgets to plots, images, tables, and text - all while writing only Python!\nPanel integrates seamlessly with your existing work:\nPlease check out the Panel website to find out more.\nNew release!\nWe are very pleased to announce the 0.10 release of Panel! This release focuses on adding a number of powerful features requested by our users, including:\nMany, many thanks to the people who contributed to this release, including @philippjfr (author, maintainer, release manager), @MarkSkovMadsen (alert pane, templates, docs), @xavArtley (VTK improvements, templates, input/spinner widgets), @maximlt (panel serve), @jbednar (docs, reviewing), @kebowen (templates), @ahuang11 (datepicker), @nghenzi (react template, bugfixes), @nritsche (panel serve), @ltalirz (autocomplete input), @BoBednar (docs), @tmikolajczyk, @halilbay, @Hoxbro, and @ceball (testing and automation).\nIf you are using Anaconda, you can get the latest Panel with conda install -c pyviz panel , and using pip you can install it with pip install panel."
},
{
"objectID": "posts/panel_release_0.10/index.html#templates-and-themes",
"href": "posts/panel_release_0.10/index.html#templates-and-themes",
"title": "Panel 0.10.0 Release",
"section": "Templates and Themes",
"text": "Templates and Themes\nPanel has always made it easy to build simple applications quickly and allowed more advanced users to use the Jinja templating system to build more polished dashboards. However in many applications and for many users, building such a custom template added a lot of complexity. This release therefore adds a number of predefined BasicTemplate classes, which are in fact anything but basic. They allow users to easily populate a clean dashboard and build on a number of popular CSS frameworks, straight out of the box. Users can currently choose from:\n\nReactTemplate: Builds on the react-grid-layout framework, making it easy to build responsive, resizable and draggable grid layouts.\n\n\n\n\n\nMaterialTemplate: Builds on the Material UI framework using material-components-web\n\n\n\n\n\nGoldenTemplate: Builds on Golden Layout with the ability to resize individual tabs\n\n\n\n\n\nBootstrapTemplate: Builds on the Bootstrap framework.\n\n\n\nVanillaTemplate: Builds on pure CSS and JS without any external CSS framework."
},
{
"objectID": "posts/panel_release_0.10/index.html#card-and-accordion-layouts",
"href": "posts/panel_release_0.10/index.html#card-and-accordion-layouts",
"title": "Panel 0.10.0 Release",
"section": "Card and Accordion layouts",
"text": "Card and Accordion layouts\nThe set of standard layout types that are available also expanded this release to include Card and Accordion layouts, which can be incredibly useful when you do not want all the content visible by default.\n\nw1 = pn.widgets.TextInput(name='Text:')\nw2 = pn.widgets.FloatSlider(name='Slider')\n\ncard = pn.Card(w1, w2, title='Card', background='WhiteSmoke')\ncard\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\nClicking on the +/- button will expand and collapse the card. Similarly the Accordion allows stacking multiple cards on top of each other, providing a compact way to lay out multiple components. If toggle=True is set, only one of the cards will be expanded at a time, ensuring a consistently compact layout.\n\nfrom bokeh.plotting import figure\n\np1 = figure(width=300, height=300, name='Scatter', margin=5)\np1.scatter([0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 2, 1, 0])\n\np2 = figure(width=300, height=300, name='Line', margin=5)\np2.line([0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 2, 1, 0])\n\naccordion = pn.Accordion(('Scatter', p1), p2, toggle=True)\naccordion"
},
{
"objectID": "posts/panel_release_0.10/index.html#ipywidgets-support",
"href": "posts/panel_release_0.10/index.html#ipywidgets-support",
"title": "Panel 0.10.0 Release",
"section": "ipywidgets support",
"text": "ipywidgets support\nThe Bokeh and ipywidgets ecosystems are both thriving but until recently they have not played very well together. Since version 0.7, Panel has supported rendering as an ipywidget, so that Panel components can be used in a library like Voilà:\n\nimport ipywidgets as ipw\nfrom ipyleaflet import Map, VideoOverlay\n\nslider = pn.widgets.IntSlider()\n\npn.Row(\n ipw.VBox([ipw.Label('This is a Bokeh/Panel widget inside an ipywidgets layout:'), pn.ipywidget(slider)]),\n height=100\n)\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\nIn this release we have also added support for using ipywidgets directly inside your Panel app and even deploying it on the (Bokeh-based) Panel server. Just install ipywidgets_bokeh with conda install -c bokeh ipywidget_bokeh or using pip with pip install ipywidgets_bokeh.\n\nm = Map(center=(25, -115), zoom=4)\n\nvideo = VideoOverlay(\n url=\"https://www.mapbox.com/bites/00188/patricia_nasa.webm\",\n bounds=((13, -130), (32, -100))\n)\n\nm.add_layer(video);\n\npn.Column(\n '# ipyleaflet in Panel', m,\n sizing_mode='stretch_width'\n)\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\nSee more examples on how to leverage the IPyWidget in Panel see the reference examples."
},
{
"objectID": "posts/panel_release_0.10/index.html#busy-indicators",
"href": "posts/panel_release_0.10/index.html#busy-indicators",
"title": "Panel 0.10.0 Release",
"section": "Busy indicators",
"text": "Busy indicators\nWhen working with long running computations you often want to give your users an indication that the application is loading or processing. In the past, users had to use custom GIFs to indicate a busy status. This release introduces a growing set of BusyIndicators that allow signalling a busy status visually.\nCurrently, BooleanStatus and LoadingSpinner types are available. The former simply displays a filled circle to indicate when the app is busy and an empty circle when it is not, while the latter spins to indicate the busy status:\n\nfrom panel.widgets.indicators import BooleanStatus, LoadingSpinner\n\npn.Column(\n pn.Row(\n *(BooleanStatus(value=True, width=50, height=50, color=color)\n for color in LoadingSpinner.param.color.objects)\n ),\n pn.Row(\n *(LoadingSpinner(value=True, width=50, height=50, color=color)\n for color in LoadingSpinner.param.color.objects)\n ),\n)\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\nThis release also adds the pn.state.sync_busy method, which links the indicators to a global busy state. Now, whenever the application is busy processing a request, an indicator can automatically display this state:\nspinner = LoadingSpinner()\npn.state.sync_busy(spinner)\nThis method makes it very straightforward to include such an indicator in your application. As a bonus, the templates mentioned above automatically add such an indicator to the header, so you don’t need to do anything at all to have a busy indicator if you use one of the templates."
},
{
"objectID": "posts/panel_release_0.10/index.html#echarts",
"href": "posts/panel_release_0.10/index.html#echarts",
"title": "Panel 0.10.0 Release",
"section": "ECharts",
"text": "ECharts\nThe ECharts library supports a wide range of plot types and makes it easy to generate polished plots with animated transitions. Panel now supports rendering ECharts from a simple JSON-style specification:\n\nechart = {\n 'title': {\n 'text': 'ECharts entry example'\n },\n 'tooltip': {},\n 'legend': {\n 'data':['Sales']\n },\n 'xAxis': {\n 'data': [\"shirt\",\"cardign\",\"chiffon shirt\",\"pants\",\"heels\",\"socks\"]\n },\n 'yAxis': {},\n 'series': [{\n 'name': 'Sales',\n 'type': 'bar',\n 'data': [5, 20, 36, 10, 10, 20]\n }],\n};\n\nplot_type = pn.widgets.Select(name=\"Plot type\", options=['bar', 'line', 'scatter'], value='bar')\n\nechart_pane = pn.pane.ECharts(echart, height=480, width=640)\n\nplot_type.jscallback(args={'echart': echart_pane}, value=\"\"\"\nechart.data.series[0].type = cb_obj.value\nechart.properties.data.change.emit()\n\"\"\")\n\npn.Column(plot_type, echart_pane)"
},
{
"objectID": "posts/panel_release_0.10/index.html#indicators",
"href": "posts/panel_release_0.10/index.html#indicators",
"title": "Panel 0.10.0 Release",
"section": "Indicators",
"text": "Indicators\nThis release also includes a number of ValueIndicator types, similar to the BooleanIndicator components we discussed above. A ValueIndicator provides a quick way to visualize a scalar, numeric value, which comes in very useful for building BI dashboards.\n\nDial\n\ndial = pn.indicators.Dial(\n name='Failure Rate', value=72, format='{value}%', bounds=(0, 100),\n colors=[(0.33, 'green'), (0.66, 'gold'), (1, 'red')]\n)\n\npn.Row(dial.clone(value=10), dial.clone(value=42), dial.clone(value=93))\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\nGauge\n\ngauge = pn.indicators.Gauge(\n name='Failure Rate', value=72, format='{value}%',\n colors=[(.33, 'green'), (.66, 'gold'), (1, 'red')]\n)\n\npn.Row(gauge.clone(value=10), gauge.clone(value=42), gauge.clone(value=93))\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\nNumber\n\nnumber = pn.indicators.Number(\n name='Failure Rate', value=72, format='{value}%',\n colors=[(33, 'green'), (66, 'gold'), (100, 'red')]\n)\n\npn.Row(number.clone(value=10), number.clone(value=42), number.clone(value=93))"
},
{
"objectID": "posts/panel_release_0.10/index.html#deferred-loading",
"href": "posts/panel_release_0.10/index.html#deferred-loading",
"title": "Panel 0.10.0 Release",
"section": "Deferred loading",
"text": "Deferred loading\nAnother useful bit of functionality for making apps feel more responsive, particularly during initial rendering, is the new pn.state.onload method, which allows scheduling a task once the page is rendered. In this way developers can defer long running computations until after the initial app is rendered, so that the user actually sees something on screen from the start. As a simple example, you might have a Column with a message indicating that the app is still loading, which is then replaced with the real contents once the application is rendered:\nstocks_url = 'https://raw.githubusercontent.com/vega/datalib/master/test/data/stocks.csv'\n\nselect_ticker = pn.widgets.Select(name='Stock Ticker')\n\ndef load_data():\n pn.state.cache['stocks'] = df = pd.read_csv(stocks_url, parse_dates=['date']).set_index('symbol')\n symbols = list(df.index.unique())\n select_ticker.options = symbols\n select_ticker.value = symbols[0]\n\npn.state.onload(load_data)\n\n@pn.depends(select_ticker)\ndef plot_ticker(ticker):\n if 'stocks' not in pn.state.cache or ticker:\n return pn.indicators.LoadingSpinner(value=True)\n return pn.state.cache['stocks'].loc[ticker].hvplot.line('date', 'price')\n\npn.Row(select_ticker, plot_ticker).servable()"
},
{
"objectID": "posts/panel_release_0.10/index.html#authenticated-apps",
"href": "posts/panel_release_0.10/index.html#authenticated-apps",
"title": "Panel 0.10.0 Release",
"section": "Authenticated Apps",
"text": "Authenticated Apps\nAnother common problem users face when publishing an application is to add some form of authentication in front of it, whether to limit the users that can view the app, provide customized content in the app, or simply to keep track of the users who log in. This release ships with a number of OAuth providers, which make it extremely easy to put an authenticated login page in front of your app. After registering an application with one of the currently supported identity providers:\n\nazure: Azure Active Directory\nbitbucket: Bitbucket\ngithub: GitHub\ngitlab: GitLab\ngoogle: Google\nokta: Okta\n\nyou can pass in the OAuth configuration as CLI options to panel serve, via environment variables, or you can configure it directly on pn.config. Once configured, the user will be prompted to log in when visiting the app and the information in the returned token will be available from the pn.state.user and pn.state.user_info objects. Read more about authentication in our user guide."
},
{
"objectID": "posts/panel_release_0.10/index.html#read-and-write-the-url",
"href": "posts/panel_release_0.10/index.html#read-and-write-the-url",
"title": "Panel 0.10.0 Release",
"section": "Read and write the URL",
"text": "Read and write the URL\nBeing able to manipulate the URL bar directly from Python makes a wide range of functionality possible in your apps. The new Location component which mirrors the Javascript window.location object makes this possible and can be accessed from pn.state.location in a Panel application. Not only can you now forward the user to a new location by updating the url, but you can sync the query parameters with some state in your application, letting your users share links to the application that persist some of the state. That way users can configure your app for their usage, then save or send a URL that lets the same state be restored when the URL is visited.\nYou can manipulate the parameters on the Location component directly, which will then be reflected in the URL bar in the browser:\n\npn.state.location\n\nLocation(hash='', hostname='', href='', name='Location01452', pathname='', port='', protocol='', reload=False, search='')\n\n\nOr use the update_query method to easily update the URL by passing explicit keywords:\npn.state.location.update_query(a=1, b=3)\nThe URL will now reflect these parameters:\nhttp://localhost:5006/app?a=1&b3\nYou can even link the location state automatically with a parameter, e.g. a widget value, which means that on load the widget will reflect the URL parameter and subsequently automatically updates the URL with the current widget value so that the state can be restored when the URL is visited:\nwidget = pn.widgets.FloatSlider(name='FloatSlider')\npn.state.location.sync(widget, {'value': 'slider_value'})\nThe URL query parameters will now automatically update to reflect the current slider value:\nhttp://localhost:5006/app?slider_value=0"
},
{
"objectID": "posts/panel_release_0.10/index.html#serving-static-assets",
"href": "posts/panel_release_0.10/index.html#serving-static-assets",
"title": "Panel 0.10.0 Release",
"section": "Serving static assets",
"text": "Serving static assets\nAnother feature users have frequently requested is the ability to publish additional static resources (data files, images, or other media or downloadable objects) alongside the apps and dashboards. This release provides the ability to easily specify static directories to serve, both on the commandline:\npanel serve some_script.py --static-dirs assets=./assets\nand programmatically:\npn.serve(panel_obj, static_dirs={'assets': './assets'})\nIn both cases the ./assets folder will be served alongside the dashboard at the /assets endpoint."
},
{
"objectID": "posts/panel_release_0.10/index.html#rest-apis",
"href": "posts/panel_release_0.10/index.html#rest-apis",
"title": "Panel 0.10.0 Release",
"section": "REST APIs",
"text": "REST APIs\nIn addition to serving static assets, you may also want to publish a live REST API alongside your app, to allow automated querying of the state of the app or some data from it. Panel now ships with an extensible set of REST providers that make it trivial to serve up some data or other info with your app. By default Panel ships with two REST providers:\n\nParam REST Provider\nThe Param REST provider is an easy entry point to publishing data with Panel. Using the pn.state.publish method you can set up an endpoint to publish the parameters of any Parameterized object. As a very simple example, if you serve the following application with panel serve app.py --rest-provider param:\nimport panel as pn\n\nslider = pn.widgets.FloatSlider()\npn.state.publish('slider', slider, ['value'])\n\nslider.servable()\nYou will be able to visit: http://localhost:5006/rest/freq, which will return the current slider value:\n{\"value\": 0}\nThis of course works not only for widgets and other panel components, but any Parameterized class, as long as the parameters are JSON serializable.\n\n\nTranquilizer REST Provider\nThe Param provider makes it easy to publish parameter values as-is, while the new Tranquilizer provider makes it easy to publish functions that compute results before returning them. Below we declare a select widget that we sync with the global cache. In the tranquilized order function, we then return a string whose contents depend on the most recently selected order:\nimport panel as pn\n\nfrom tranquilizer import tranquilize\n\nselect = pn.widgets.Select(options=['Cheddar', 'Mozarella', 'Parmeggiano'], value='Cheddar')\n\ndef update_cache(event):\n pn.state.cache['cheese'] = event.new\n\nselect.param.watch(update_cache, 'value')\n\n@tranquilize()\ndef order():\n '''I'd like to buy some cheese!'''\n return f\"I'm afraid we're fresh out of {pn.state.cache['cheese'].lower()}, sir.\"\n\nselect.servable()\nNow when we serve this app with panel serve app.py --rest-provider tranquilizer, visit http://localhost:5006, select a cheese, and hit http://localhost:5006/rest/order we get back:\n\"I'm afraid we're fresh out of mozarella, sir.\"\nBoth of these providers make it very easy to set up a REST endpoint, and other providers can easily be registered."
},
{
"objectID": "posts/panel_release_0.10/index.html#roadmap",
"href": "posts/panel_release_0.10/index.html#roadmap",
"title": "Panel 0.10.0 Release",
"section": "Roadmap",
"text": "Roadmap\nThis release has included a ton of great features and we have many more features planned. Some highlights include:\n\nPolishing templates and themes\nThis release brought the addition of a number of templates and accompanying themes. While we spent a lot of time making sure these would be general there are many improvements and tweaks that we want to make based on user feedback, so please let us know if you find issues.\n\n\nCustom components\nWe want to provide expert users with the power to develop their own HTML objects (including custom WebComponents), then have them appear as Panel components synchronized bi-directionally with the HTML state. To allow this we are working on an API to connect attributes and properties on HTML elements into Python objects, allowing custom components (e.g. new widget types) to be used within Panel just like existing widgets and panes are.\n\n\nTemplated layouts\nComplementing the ability to define individual custom components, we want to allow users to declare custom layouts by writing small HTML template strings the components will be inserted into. This will make it possible to leverage custom CSS or JS frameworks, e.g. to build custom types of responsive grids that can be used just like the current Panel layouts (Row, Column, etc.).\n\n\nResponsive grids\nIn addition to allowing users to build custom layouts using their favorite CSS/JS frameworks, we also want to ship a well-suported responsive grid layout that reflows components on the page based on the size of the browser tab. Reflowing will make it much easier to provide a great experience on mobile devices.\n\n\nBetter debugging and profiling\nWe also want to make the process of desiging, building, debugging, and optimizing apps easier. We plan to develop new tools to visualize Panel and Param callback and dependency graphs, to help developers understand how data and events propagate through their panels. To help them identify performance bottlenecks, these graphs will be annotated with timing information so that the slow steps can easily be identified.\n\n\nDocumentation overhaul\nAs we approach a Panel 1.0 release we want to overhaul the documentation so it becomes much easier to find the information you are looking for.\n\n\nHelp us!\nPanel is an open-source project and we are always looking for new contributors. Join us the discussion on the Discourse and we would be very excited to get you started contributing! Also please get in touch with us if you work at an organization that would like to support future Panel development, fund new Panel features, or set up a support contract.\n\n\nSponsors\nMany thanks to our existing sponsors:"
},
{
"objectID": "posts/panel_1rc/index.html",
"href": "posts/panel_1rc/index.html",
"title": "Panel 1.0 RC",
"section": "",
"text": "We are thrilled to announce the availability of the Panel 1.0 release candidate for general testing! We want to extend our thanks for your patience as we’ve been working tirelessly to adapt to major changes in Bokeh 3.x, improving the API and polishing our user experience. This significant undertaking has paved the way for a new, performant layout engine and greater customizability by exposing CSS stylesheets APIs directly in Python. We believe this release represents a major step forward in customizability, performance, and usability.\nWe kindly ask for your help in testing the Panel 1.0 release candidate. The bottom up rewrite of the layout engine and component rendering may cause some disruption in the short term so we wanted to get these changes in your hands to gather feedback before the full release. To get the RC release today use:\npip install panel==1.0.0rc1\n# OR\nconda install -c pyviz/label/dev panel=1.0.0rc1\nIf you encounter problems please report them on our issue tracker or join the discussion on our Discourse and Discord servers.\nTo help you with the transition, we have prepared a comprehensive migration guide, which will provide you with a detailed walkthrough of the key updates, the rationale behind them, and the critical considerations to keep in mind when adapting your applications to this new version. Additionally please refer to the development documentation, which includes detailed how-to guides walking you through your learning journey.\nTo get you excited about the new release a host of new features in the Panel 1.0 release candidate:\n\nOverhauled documentation based on the Diataxis framework, featuring a how-to guide for easier navigation\nRunnable documentation directly in the docs, via JupyterLite, and an improved app gallery\nPer-component theming and styling using Bootstrap and Material Design systems\nThe ability to bind parameters and reactive functions directly to component parameters\nThe ability to reuse sessions for fast time to initial render performance.\nEnhanced Markdown rendering with markdown-it-py\nA Vizzu pane for plots, featuring beautiful animated transitions\nA FloatPanel layout offering a free-floating, draggable dock panel\nA Swipe layout for before-and-after comparisons of any component(s)\nA Switch toggle widget for added interactivity\nBasicAuth for straightforward password-based authentication\nMuch, much more.\n\nThere are also still a few items left items we hope to wrap up before the final release:\n\nWe are still working on fully supporting ipywidgets rendering\nAdding a number of missing how-to guides to improve your learning journey\nFinal theming/styling tweaks to ensure your applications look modern and polished\nGeneral bug fixes as reported by you\n\nYour invaluable feedback will contribute to the most robust and powerful release of Panel yet. Thank you once again for your continued support, and we look forward to seeing the incredible applications you create with Panel 1.0!\nWarm regards,\nThe Panel Team\n\n\n\n Back to top"
},
{
"objectID": "posts/hvplot_release_0.10/index.html#what-is-hvplot",
"href": "posts/hvplot_release_0.10/index.html#what-is-hvplot",
"title": "Plotting made easy with hvPlot: 0.9 and 0.10 releases",
"section": "What is hvPlot?",
"text": "What is hvPlot?\nhvPlot is an open-source library that offers powerful high-level functionality for data exploration and visualization that doesn’t require you to learn a new API. You can get powerful interactive and compositional Bokeh, Matplotlib, or Plotly plots by simply replacing .plot with .hvplot. hvPlot makes all the analytical power of the HoloViz ecosystem available, using the APIs you already know."
},
{
"objectID": "posts/hvplot_release_0.10/index.html#new-release",
"href": "posts/hvplot_release_0.10/index.html#new-release",
"title": "Plotting made easy with hvPlot: 0.9 and 0.10 releases",
"section": "New release!",
"text": "New release!\nWe are very pleased to announce the 0.10 release of hvPlot! And since we missed announcing the 0.9 release, we are also going to introduce it in this blog post 😊 These releases pack some exciting improvements, specifically:\n\nPolars integration (0.9)\nXarray support added to the Explorer, with a few other enhancements (0.9)\nLarge time series exploration made even easier (0.9 and 0.10)\nImproved contributor experience (0.10)\nEnhanced plotting API (0.10)\nDocumentation enhancements (0.9 and 0.10)\n\nBefore diving into detailing each one of these items, we would like to thank everyone who contributed to these releases, including @rdesai9 (first contribution!), @dogbunny (first contribution!), @bikegeek (first contribution!), @iuryt (first contribution!), @MarcoGorelli (first contribution!), @kevinheavey (first contribution!), @jsignell, @MarcSkovMadsen, @ahuang11, @droumis, @Hoxbro, @maximlt and @philippjfr.\n\nIf you are using Anaconda, you can get latest hvPlot with conda install hvplot , and using pip you can install it with pip install hvplot.\n\n🌟 An easy way to support hvPlot is to give it a star on Github! 🌟"
},
{
"objectID": "posts/hvplot_release_0.10/index.html#polars-integration-0.9",
"href": "posts/hvplot_release_0.10/index.html#polars-integration-0.9",
"title": "Plotting made easy with hvPlot: 0.9 and 0.10 releases",
"section": "Polars integration (0.9)",
"text": "Polars integration (0.9)\nPolars is an alternative DataFrame implementation written in Rust that has become pretty popular. Quite naturally, hvPlot users started to ask for Polars support which was added in version 0.9.0 by Simon (@Hoxbro). This integration in hvPlot allows its users to easily generate plots from Polars DataFrames after importing hvplot.polars. Soon after, Polars’ developers took the decision to directly add a plotting API to Polars which landed in version 0.20.3. We were very pleased to see that they built it on top of hvPlot’s API, simply forwarding .plot calls to hvPlot! We took this as a confirmation of hvPlot’s approach that consists in building a powerful but simple API based on the .plot API originally designed by Pandas.\nReproducing an example from Polars’ documentation, you can see that Polars users can now directly call .plot on their DataFrame instance.\n\nimport polars as pl\n\ndf = pl.DataFrame(\n {\n \"length\": [1, 4, 6],\n \"width\": [4, 5, 6],\n \"species\": [\"setosa\", \"setosa\", \"versicolor\"],\n }\n)\nplot = df.plot.scatter(x=\"length\", y=\"width\", by=\"species\")\nplot\n\n\n\n\n\n \n\n\n\n\nWhile we’re very happy with the direction this is going, we are well aware that this first integration is pretty basic as hvPlot has to cast Polars DataFrames to Pandas DataFrames as a pre-processing step (selecting only the columns that will be used by hvPlot). Going forward, we are very interested in adding first-class support for Polars directly into HoloViews, which, when upstreamed to hvPlot, will allow us to stop casting to Pandas and will help preserving the performance benefits brought by using Polars."
},
{
"objectID": "posts/hvplot_release_0.10/index.html#explorer-enhancements-0.9",
"href": "posts/hvplot_release_0.10/index.html#explorer-enhancements-0.9",
"title": "Plotting made easy with hvPlot: 0.9 and 0.10 releases",
"section": "Explorer enhancements (0.9)",
"text": "Explorer enhancements (0.9)\nThe Explorer is a Panel-based graphical interface that offers a simple way to select and visualize the kind of plot you want to see your data with, and many options to customize that plot. Pushed by Andrew (@ahuang11), the 0.9 series of releases gradually improved it, two of the main changes include adding Xarray support and displaying code snippets. The Explorer is also now available on the main plotting namespace with .hvplot.explorer().\n\nds = xr.tutorial.open_dataset(\"air_temperature\")\nds.hvplot.explorer(x=\"lon\", y=\"lat\")"
},
{
"objectID": "posts/hvplot_release_0.10/index.html#large-time-series-0.9-and-0.10",
"href": "posts/hvplot_release_0.10/index.html#large-time-series-0.9-and-0.10",
"title": "Plotting made easy with hvPlot: 0.9 and 0.10 releases",
"section": "Large time series (0.9 and 0.10)",
"text": "Large time series (0.9 and 0.10)\nHoloViews, Datashader and Bokeh have recently been improved to make it easier to explore very large time series. For example, in version 0.9 hvPlot exposed the auto-ranging and downsampling features added to HoloViews. In version 0.10, Demetris (@droumis) contributed the new guide Large Timeseries Data guide describing all the ways hvPlot can help you exploring this sort of data.\n\ndf = pd.read_parquet(\"https://datasets.holoviz.org/sensor/v1/data.parq\")\ndf0 = df[df.sensor == \"0\"]\n\nWith autorange=\"y\", you can ensure the data in the viewport is automatically ranged to maximise the use of the y-axis.\n\ndf0.hvplot(x=\"time\", y=\"value\", autorange=\"y\", title=\"autorange\");\n\n\nIdeally, to explore large timeseries you should be able to display all the data in your browser, except that, you may crash it if the dataset is too large 🙃! The Large Timeseries Data guide goes over a few methods that are exposed in hvPlot to let you explore even the largest datasets. One option consists in downsampling the dataset before rendering it. hvPlot lets you now downsample timeseries appropriately with the Largest Triangle Three Buckets (LTTB) algorithm, which allows data points not contributing significantly to the visible shape to be dropped, reducing significantly the amount of data to send to the browser but preserving the appearance (and particularly the envelope, i.e. highest and lowest values in a region).\nAs you can see below, the downsampled timeseries looks very close to the original one, while preserving most of its properties. Note that the downsampled timeseries is re-computed on every zoom and pan event based on the data available in the viewport.\n\ndf0.hvplot(x=\"time\", y=\"value\", color='#003366', label = \"All the data\") * \\\ndf0.hvplot(x=\"time\", y=\"value\", color='#00B3B3', label=\"LTTB\", title=\"LTTB\",\n alpha=.8, downsample=True);"
},
{
"objectID": "posts/hvplot_release_0.10/index.html#improved-contributor-experience-0.10",
"href": "posts/hvplot_release_0.10/index.html#improved-contributor-experience-0.10",
"title": "Plotting made easy with hvPlot: 0.9 and 0.10 releases",
"section": "Improved contributor experience (0.10)",
"text": "Improved contributor experience (0.10)\nThere’s still a lot of work to do to improve hvPlot and finally release version 1.0. We would love for the community to contribute more to the project, and to that end we have started to streamline the overall contributor experience.\n\nThe HoloViz ecosystem has relied for many years on a custom developer tool called pyctdev, which was less and less maintained and made it paradoxically quite challenging for contributors to set up their development environment. pyctdev belongs now to the past! We have migrated to a more classic approach by which users can install a development environment with either pip or conda. Check out the developer guide for more details, and expect more improvements in that area over the next months.\nLike many others in the Python ecosystem, we have adopted ruff as hvPlot’s formatter and linter, running automatically on commits thanks to pre-commit. The code base was never automatically formatted and linting was pretty loose, so this is all going in the right direction!"
},
{
"objectID": "posts/hvplot_release_0.10/index.html#enhanced-plotting-api-0.10",
"href": "posts/hvplot_release_0.10/index.html#enhanced-plotting-api-0.10",
"title": "Plotting made easy with hvPlot: 0.9 and 0.10 releases",
"section": "Enhanced plotting API (0.10)",
"text": "Enhanced plotting API (0.10)\nThe HoloViz project received a NumFocus Small Development Grant to revitalize the HoloViz website for enhanced Learning and community engagement. This currently ongoing project, conducted by @Azaya89 and @jtao1, has mostly focused on modernizing the HoloViz Examples Gallery, with for instance, migrating some pure HoloViews code to hvPlot. This work has highlighted small gaps in hvPlot’s API, that prevented us from fully migrating away from HoloViews even for very simple use cases.\nThe tiles parameter has gained support for xyzservices tile providers, increasing greatly the number of tiles available. The new tiles_opts parameter accepts a dictionary of options that are applied to the tile layer created when tiles is set.\n\nimport xyzservices.providers as xyz\n\ndf = pd.DataFrame({\n 'City': ['Paris', 'London', 'Berlin'],\n 'x': [277183.93, -13950.96, 1496476.64],\n 'y': [6241780.53, 6713002.08, 6891684.23],\n})\n\ndf.hvplot.points(x='x', y='y', tiles=xyz.CartoDB.Positron, tiles_opts={'alpha': 0.5})\n\n\n\n\n\n \n\n\n\n\nThe new bgcolor parameter allows setting the background color.\n\nNUM = 1_000_000\ndists = [\n pd.DataFrame(dict(x=np.random.normal(x, s, NUM), y=np.random.normal(y, s, NUM)))\n for x, y, s in [\n ( 5, 2, 0.20), \n ( 2, -4, 0.10), \n (-2, -3, 0.50), \n (-5, 2, 1.00), \n ( 0, 0, 3.00)]\n]\ndf_large_data = pd.concat(dists, ignore_index=True)\ndf_large_data.hvplot.points(\n 'x', 'y', datashade=True, cnorm='eq_hist', aspect=1, colorbar=False,\n cmap='fire', bgcolor='black'\n)\n\n\n\n\n\n \n\n\n\n\nThe new robust parameter mimics the behavior of Xarray’s robust parameter, providing a simple way to limit the colormap range to the values between the 2nd and 98th percentiles.\n\nairtemps = xr.tutorial.open_dataset(\"air_temperature\")\nair_outliers = airtemps.air.isel(time=0).copy()\nair_outliers[0, 0] = 100\nair_outliers[-1, -1] = 400\n(air_outliers.hvplot() + air_outliers.hvplot(robust=True)).cols(1)"
},
{
"objectID": "posts/hvplot_release_0.10/index.html#improved-documentation-0.9-and-0.10",
"href": "posts/hvplot_release_0.10/index.html#improved-documentation-0.9-and-0.10",
"title": "Plotting made easy with hvPlot: 0.9 and 0.10 releases",
"section": "Improved documentation (0.9 and 0.10)",
"text": "Improved documentation (0.9 and 0.10)\nThe reference gallery was extended to include more Xarray examples.\n\nWe have also clarified the structure and content of the Geographic data guide. Finally, we have replaced Google Analytics (which we didn’t use much anyway!) with GoatCounter."
},
{
"objectID": "posts/hvplot_release_0.10/index.html#roadmap",
"href": "posts/hvplot_release_0.10/index.html#roadmap",
"title": "Plotting made easy with hvPlot: 0.9 and 0.10 releases",
"section": "Roadmap",
"text": "Roadmap\nThe improvements introduced above are in line with the newly established roadmap which includes:\n\nDocumentation, documentation, documentation!\nStreamline the developer experience\nBug fixes\nFigure out the future of the .interactive API\nExpose features from HoloViews\nImprove the Explorer\nIncrease hvPlot’s presence within PyData\nPrepare for hvPlot 1.0 in 2025\n\nJoin us on Github, Discourse or Discord to help us make all this happen!"
},
{
"objectID": "posts/hv_release_1.13/index.html",
"href": "posts/hv_release_1.13/index.html",
"title": "HoloViews 1.13 Release",
"section": "",
"text": "We are very pleased to announce the release of HoloViews 1.13.x!\nSince we did not release blog posts for other 1.13 we will use this opportunity the many great features that have been added in this release. Note that this post primarily focuses on exciting new functionality for a full summary of all features, enhancements and bug fixes see the releases page in the HoloViews documentation.\nMajor features:\n\nAdd link_selection function to make custom linked brushing simple (#3951)\nlink_selection builds on new support for much more powerful data-transform pipelines: new Dataset.transform method (#237, #3932), dim expressions in Dataset.select (#3920), arbitrary method calls on dim expressions (#4080), and Dataset.pipeline and Dataset.dataset properties to track provenance of data\nAdd Annotators to allow easily drawing, editing, and annotating visual elements (#1185)\nCompletely replaced custom Javascript widgets with Panel-based widgets allowing for customizable layout (#84, #805)\nAdd HSpan, VSpan, Slope, Segments and Rectangles elements (#3510, #3532, #4000)\nAdd support for cuDF GPU dataframes, cuPy backed xarrays, and GPU datashading (#3982)\nAdd spatialpandas support and redesigned geometry interfaces for consistent roundtripping (#4120)\nAdd explicit .df and .xr namespaces to dim expressions to allow using dataframe and xarray APIs (#4320)\n\nOther Features:\n\nSupport GIF rendering with Bokeh and Plotly backends (#2956, #4017)\nSupport for Plotly Bars, Bounds, Box, Ellipse, HLine, HSpan, Histogram, RGB, VLine and VSpan plots\nAdd support for linked streams in Plotly backend to enable rich interactivity (#3880, #3912)\nSupport for datashading Area, Spikes, Segments and Polygons (#4120)\nHeatMap now supports mixed categorical/numeric axes (#2128)\nUse __signature__ to generate .opts tab completions (#4193)\n\n\nIf you are using Anaconda, HoloViews can most easily be installed by executing the command conda install -c pyviz holoviews . Otherwise, use pip install holoviews.\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n \n\n\n\n\n\n \n \n\n\n\n\nLinked brushing\nDatasets very often have more dimensions than can be shown in a single plot, which is why HoloViews offers so many ways to show the data from each of these dimensions at once (via layouts, overlays, grids, holomaps, etc.). However, even once the data has been displayed, it can be difficult to relate data points between the various plots that are laid out together. For instance, “is the outlier I can see in this x,y plot the same datapoint that stands out in this w,z plot”? “Are the datapoints with high x values in this plot also the ones with high w values in this other plot?” Since points are not usually visibly connected between plots, answering such questions can be difficult and tedious, making it difficult to understand multidimensional datasets. Linked brushing (also called “brushing and linking”) offers an easy way to understand how data points and groups of them relate across different plots. Here “brushing” refers to selecting data points or ranges in one plot, with “linking” then highlighting those same points or ranges in other plots derived from the same data.\nIn HoloViews 1.13.x Jon Mease and Philipp Rudiger worked hard on providing a simple way to expose this functionality in HoloViews by leveraging many of the existing features in HoloViews. The entry point for using this functionality is the link_selections function which automatically creates views for the selected and unselected data and indicators for the current selection.\nBelow we will create a number of plots from Gorman et al.’s penguin dataset and then applies the linked_selections function:\n\ncolor_dim = hv.dim('Species').categorize({\n 'Adelie Penguin': '#1f77b4',\n 'Gentoo penguin': '#ff7f0e',\n 'Chinstrap penguin': '#2ca02c'\n})\n\nscatter = hv.Scatter(penguin_ds, 'Culmen Length (mm)', ['Culmen Depth (mm)', 'Species']).opts(\n color=color_dim, tools=['hover']\n)\nbars = hv.Bars(penguin_ds, 'Species', 'Individual ID').aggregate(function=np.count_nonzero).opts(\n xrotation=45, color=color_dim\n)\nhist = penguin_ds.hist('Body Mass (g)', groupby='Species', adjoin=False, normed=False).opts(\n hv.opts.Histogram(show_legend=False, fill_color=color_dim)\n)\nviolin = hv.Violin(penguin_ds, ['Species', 'Sex'], 'Flipper Length (mm)').opts(\n split='Sex', xrotation=45, show_legend=True, legend_position='right', frame_width=240,\n cmap='Category20'\n)\n\nhv.link_selections(scatter+hist+bars+violin, selection_mode='union').cols(2);\n\n\nAs we can see the linked selections functionality allows us to link a variety of plot types together and cross-filter on them using both box-select and lasso-select tools. However the real power behind the linked selections support is the fact that it allows us to select on the raw data and automatically replays complex pipelines of operations, e.g. below is a dashboard built in just a few lines of Python code that generates histograms and datashaded plots of 11 million Taxi trips and then links them automatically. In this way we can gain insights into large and complex datasets, e.g. identifying where Taxi trips departing at Newark airport in NYC drop off their passengers:\n\n\nTo read more about linked brushing see the corresponding user guide.\n\n\nGPU support\nThe Rapids initiative started by NVIDIA has made huge strides over the last couple of years and in particular the cuDF library has brought a GPU backed DataFrame API to the PyData ecosystem. Since the cuDF and cupy libraries are now mature enough we developed a cuDF interface for HoloViews. You can now pass a cuDF DataFrame directly to HoloViews and it will leverage the huge performance gains when computing aggregates, ranges, histograms and thanks to the work of the folks at NVIDIA and Jon Mease you can now directly leverage GPU accelerated Datashader to interactively explore huge datasets with amazing latency, e.g. using the NYC taxi datasets you can easily achieve 10x performance improvements when computing histograms and datashaded plots further speeding up the dashboard presented above without changing a single line of HoloViews code - a cuDF behaves as a drop-in replacement for a Pandas or Dask dataframe as far as HoloViews is concerned.\n\n\nData pipelines\nHoloViews has for a long time to declare pipelines of operations to apply to some visualization. However if the transform involved some complex manipulation of the underlying data we would have to manually unpack the data, transform it some way and then create a new element to display it. This meant that it was hard to leverage the fact that HoloViews is agnostic about the data format, in many cases users would either have to know about the type of the data or access it as a NumPy array, which can leave performance on the table or cause unnecessary memory copies. Therefore we added an API to easily transform data, which also supports the dynamic nature of the existing .apply API.\nTo demonstrate this new feature we will load an xarray dataset of air temperatures:\n\nair_temp = xr.tutorial.load_dataset('air_temperature')\nair_temp\n\nq = pn.widgets.FloatSlider(name='quantile')\n\nquantile_expr = hv.dim('air').xr.quantile(q, dim='time')\nquantile_expr\n\n\n\n\nShow/Hide data repr\n\n\n\n\n\nShow/Hide attributes\n\n\n\n\n\n\n\nxarray.DatasetDimensions:lat: 25lon: 53time: 2920Coordinates: (3)lat(lat)float3275.0 72.5 70.0 ... 20.0 17.5 15.0standard_name :latitudelong_name :Latitudeunits :degrees_northaxis :Yarray([75. , 72.5, 70. , 67.5, 65. , 62.5, 60. , 57.5, 55. , 52.5, 50. , 47.5,\n 45. , 42.5, 40. , 37.5, 35. , 32.5, 30. , 27.5, 25. , 22.5, 20. , 17.5,\n 15. ], dtype=float32)lon(lon)float32200.0 202.5 205.0 ... 327.5 330.0standard_name :longitudelong_name :Longitudeunits :degrees_eastaxis :Xarray([200. , 202.5, 205. , 207.5, 210. , 212.5, 215. , 217.5, 220. , 222.5,\n 225. , 227.5, 230. , 232.5, 235. , 237.5, 240. , 242.5, 245. , 247.5,\n 250. , 252.5, 255. , 257.5, 260. , 262.5, 265. , 267.5, 270. , 272.5,\n 275. , 277.5, 280. , 282.5, 285. , 287.5, 290. , 292.5, 295. , 297.5,\n 300. , 302.5, 305. , 307.5, 310. , 312.5, 315. , 317.5, 320. , 322.5,\n 325. , 327.5, 330. ], dtype=float32)time(time)datetime64[ns]2013-01-01 ... 2014-12-31T18:00:00standard_name :timelong_name :Timearray(['2013-01-01T00:00:00.000000000', '2013-01-01T06:00:00.000000000',\n '2013-01-01T12:00:00.000000000', ..., '2014-12-31T06:00:00.000000000',\n '2014-12-31T12:00:00.000000000', '2014-12-31T18:00:00.000000000'],\n dtype='datetime64[ns]')Data variables: (1)air(time, lat, lon)float32241.2 242.5 243.5 ... 296.19 295.69long_name :4xDaily Air temperature at sigma level 995units :degKprecision :2GRIB_id :11GRIB_name :TMPvar_desc :Air temperaturedataset :NMC Reanalysislevel_desc :Surfacestatistic :Individual Obsparent_stat :Otheractual_range :[185.16 322.1 ]array([[[241.2 , 242.5 , 243.5 , ..., 232.79999, 235.5 ,\n 238.59999],\n [243.79999, 244.5 , 244.7 , ..., 232.79999, 235.29999,\n 239.29999],\n [250. , 249.79999, 248.89 , ..., 233.2 , 236.39 ,\n 241.7 ],\n ...,\n [296.6 , 296.19998, 296.4 , ..., 295.4 , 295.1 ,\n 294.69998],\n [295.9 , 296.19998, 296.79 , ..., 295.9 , 295.9 ,\n 295.19998],\n [296.29 , 296.79 , 297.1 , ..., 296.9 , 296.79 ,\n 296.6 ]],\n\n [[242.09999, 242.7 , 243.09999, ..., 232. , 233.59999,\n 235.79999],\n [243.59999, 244.09999, 244.2 , ..., 231. , 232.5 ,\n 235.7 ],\n [253.2 , 252.89 , 252.09999, ..., 230.79999, 233.39 ,\n 238.5 ],\n ...,\n [296.4 , 295.9 , 296.19998, ..., 295.4 , 295.1 ,\n 294.79 ],\n [296.19998, 296.69998, 296.79 , ..., 295.6 , 295.5 ,\n 295.1 ],\n [296.29 , 297.19998, 297.4 , ..., 296.4 , 296.4 ,\n 296.6 ]],\n\n [[242.29999, 242.2 , 242.29999, ..., 234.29999, 236.09999,\n 238.7 ],\n [244.59999, 244.39 , 244. , ..., 230.29999, 232. ,\n 235.7 ],\n [256.19998, 255.5 , 254.2 , ..., 231.2 , 233.2 ,\n 238.2 ],\n ...,\n [295.6 , 295.4 , 295.4 , ..., 296.29 , 295.29 ,\n 295. ],\n [296.19998, 296.5 , 296.29 , ..., 296.4 , 296. ,\n 295.6 ],\n [296.4 , 296.29 , 296.4 , ..., 297. , 297. ,\n 296.79 ]],\n\n ...,\n\n [[243.48999, 242.98999, 242.09 , ..., 244.18999, 244.48999,\n 244.89 ],\n [249.09 , 248.98999, 248.59 , ..., 240.59 , 241.29 ,\n 242.68999],\n [262.69 , 262.19 , 261.69 , ..., 239.39 , 241.68999,\n 245.18999],\n ...,\n [294.79 , 295.29 , 297.49 , ..., 295.49 , 295.38998,\n 294.69 ],\n [296.79 , 297.88998, 298.29 , ..., 295.49 , 295.49 ,\n 294.79 ],\n [298.19 , 299.19 , 298.79 , ..., 296.09 , 295.79 ,\n 295.79 ]],\n\n [[245.79 , 244.79 , 243.48999, ..., 243.29 , 243.98999,\n 244.79 ],\n [249.89 , 249.29 , 248.48999, ..., 241.29 , 242.48999,\n 244.29 ],\n [262.38998, 261.79 , 261.29 , ..., 240.48999, 243.09 ,\n 246.89 ],\n ...,\n [293.69 , 293.88998, 295.38998, ..., 295.09 , 294.69 ,\n 294.29 ],\n [296.29 , 297.19 , 297.59 , ..., 295.29 , 295.09 ,\n 294.38998],\n [297.79 , 298.38998, 298.49 , ..., 295.69 , 295.49 ,\n 295.19 ]],\n\n [[245.09 , 244.29 , 243.29 , ..., 241.68999, 241.48999,\n 241.79 ],\n [249.89 , 249.29 , 248.39 , ..., 239.59 , 240.29 ,\n 241.68999],\n [262.99 , 262.19 , 261.38998, ..., 239.89 , 242.59 ,\n 246.29 ],\n ...,\n [293.79 , 293.69 , 295.09 , ..., 295.29 , 295.09 ,\n 294.69 ],\n [296.09 , 296.88998, 297.19 , ..., 295.69 , 295.69 ,\n 295.19 ],\n [297.69 , 298.09 , 298.09 , ..., 296.49 , 296.19 ,\n 295.69 ]]], dtype=float32)Attributes: (5)Conventions :COARDStitle :4x daily NMC reanalysis (1948)description :Data is from NMC initialized reanalysis\n(4x/day). These are the 0.9950 sigma level values.platform :Modelreferences :http://www.esrl.noaa.gov/psd/data/gridded/data.ncep.reanalysis.html\n\n\nSeeing that this dataset has an 'air' variable we can write a so called dim expression to express a transform which performs a quantile quantile aggregation along the 'time' dimension:\n\nq = pn.widgets.FloatSlider(name='quantile')\n\nquantile_expr = hv.dim('air').xr.quantile(q, dim='time')\nquantile_expr\n\ndim('air').xr.quantile(FloatSlider(name='quantile'), dim='time')\n\n\nAs you can see the slider we have created is a valid argument to this transform and if we now apply this transform the pipeline is reevaulated whenever the slider value changes:\n\ntemp_ds = hv.Dataset(air_temp, ['lon', 'lat'])\n\ntransformed = temp_ds.apply.transform(air=quantile_expr).apply(hv.Image)\n\npn.Column(q, transformed.opts(colorbar=True, width=400))\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\nIn this way we can build transformation pipelines using familiar APIs (pandas or xarray) without losing the ability to inject dynamic parameters driven by widgets or other sources. To read more about data pipelines see the Transforming Elements and Data Processing Pipelines user guides.\n\n\nAnnotators\nThis release also introduced annotating functionality which allows editing, adding and labelling different a range of element types. At the moment it is possible to annotate the following element types:\n\nPoints/Scatter\nCurve\nRectangles\nPath\nPolygons\n\nAs an example we will create a set of Points and use the annotate function to enable the annotator functionality:\n\ncells = hv.Image(calcium_array[:, :, 0])\n\npoints = hv.Points([(-0.275, -0.0871875), (-0.2275, -0.1996875), (0.1575, 0.0003125)]).opts(\n padding=0, aspect='square', frame_width=400, responsive=False, active_tools=['point_draw']\n)\n\nannotator = hv.annotate.instance()\n\nhv.annotate.compose(cells, annotator(points, name='Cell Annotator', annotations={'Label': str}))\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\nIf you select the PointDraw tool from the toolbar you will now be able to add new points, drag existing points around and edit their position and labels via the table. Once we are done we can access the edited data on the annotator object:\n\nannotator.annotated.dframe()\n\n\n\n\n\n\n\n\nx\ny\nLabel\n\n\n\n\n0\n-0.2750\n-0.087188\n\n\n\n1\n-0.2275\n-0.199687\n\n\n\n2\n0.1575\n0.000313\n\n\n\n\n\n\n\n\n\n\nNew elements\nThe addition of new visual elements always increases the power of a plotting library significantly. In this a number of elements were added to draw specific geometries and annotate plots.\n\nRectangles & Segments\nThe ability to draw rectangles and segments provides powerful low-level primitives to render higher-level plots, e.g. below we can see an OHLC plot, usually used to indicate the movement of stocks over time, generated using the new Rectangles and Segments elements:\n\ndef OHLC(N):\n xs = np.arange(N)\n ys = np.random.randn(N+1).cumsum()\n\n O = ys[1:]\n C = ys[:-1]\n H = np.max([O, C], axis=0) + np.random.rand(N)\n L = np.min([O, C], axis=0) - np.random.rand(N)\n return (xs, ys, O, H, L, C)\n\nxs, ys, O, H, L, C = OHLC(50)\nboxes = hv.Rectangles((xs-0.25, O, xs+0.25, C))\nsegments = hv.Segments((xs, L, xs, H))\n\n# Color boxes where price decreased red and where price increased green\ncolor_exp = (hv.dim('y0')>hv.dim('y1')).categorize({True: 'green', False: 'red'})\n\nboxes.opts(width=1000, color=color_exp, xlabel='Time', ylabel='Price') * segments.opts(color='black')\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\nHSpan and VSpan\nThe ability to draw shaded regions with unlimited extent allows highlighting notable regions along the x- or y-axis of a plot. The new HSpan and VSpan annotation elements allow you to do exactly that, here we mark the regions of the timeseries that are one standard deviation above and below the mean:\n\nys = np.random.randn(1000).cumsum()\n\nymean, ystd, ymin, ymax = ys.mean(), ys.std(), ys.min(), ys.max()\n\ntimeseries = hv.Curve(ys)\n\ntimeseries * hv.HSpan(ymean+ystd, ymax) * hv.HSpan(ymean-ystd, ymin)\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\nSlope\nAnother helpful annotation is the ability to draw an infinite sloping line on a plot, complementing the existing HLine and VLine elements. The Slope element can be used to display a regression line for example:\n\nscatter = penguin_ds.to(hv.Scatter, 'Body Mass (g)', 'Flipper Length (mm)', 'Species').overlay()\n\nscatter * scatter.apply(hv.Slope.from_scatter, per_element=True).opts(legend_position='bottom_right', frame_width=400)\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\nPanel based widgets\nHoloViews has shipped with a set of widgets to explore multi-dimensional parameter spaces since its first public release. These widgets were written as a weekend project and did not follow many of the best practices of Javascript development. This meant they were hard to extend, exhibit a variety of issues related to character encoding and were not at all customizable. In HoloViews 1.13.0 we completely replaced most of the rendering machinery and widget code with Panel widgets making them easier to maintain, customize and extend.\nSpecifically so far the widgets have always been located at the right of a plot, but now we have full flexibility to override this:\n\ncalcium_hmap = hv.HoloMap({i: hv.Image(calcium_array[:, :, i]) for i in range(10)}, 'Time')\n\nhv.output(calcium_hmap, widget_location='bottom')\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\nSpatialpandas and polygon datashading\nHoloViews has long had strong support for both gridded and tabular data, with geometry data support being more spotty. In HoloViews 1.13.0 the core model around support for geometry data was redesigned from the ground up, in particular HoloViews can now convert natively between different geometry storage backends including the native dictionary format, geopandas (if GeoViews is installed) and the new addition called spatialpandas. Spatialpandas is closely modeled on GeoPandas but does not have the same heavy GIS dependencies and is highly optimized, efficiently make use of pandas extension arrays. All of this means that spatialpandas is significantly more performant than geopandas and can also be directly ingested into datashader, making it possible to render thousands or even millions of geometries, including polygons, very quickly.\n\nnyc_buildings = hv.Polygons(buildings, ['x', 'y'], 'type')\n\ndatashade(nyc_buildings, aggregator=ds.by('type', ds.count()), color_key=glasbey);\n\n\n\n\nIn addition to Polygons this release also brings support for datashading a range of other plot types including Area, Spikes and Segments:\n\nxs, ys, O, H, L, C = OHLC(1000000)\n\narea = hv.Area((xs, O))\n\nsegments = hv.Segments((xs, L, xs, H))\n\n(datashade(area, aggregator='any') + datashade(segments) + datashade(hv.Spikes(O))).opts(shared_axes=False)\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\nImproved Plotly support\nThe Plotly backend has long been only partially supported with a wide swath of element types not being implemented. This release brought feature parity between bokeh and plotly backends much closer by implementing a wide range of plot types including:\n\nBars\nBounds\nBox\nEllipse\nHLine/VLine\nHSpan/VSpan\nHistogram\nRGB\n\nBelow we can see examples of each of the element types:\n\n(bars + hist + path + rgb + hspan + shapes).opts(shared_axes=False).cols(2)\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\nAdditionally the Plotly backend now supports interactivity with support linked streams allowing for deep interactivity, e.g. linked brushing is also supported:\n\n\n\nGIF support for Bokeh and Plotly\nIt has long been possible to generate GIFs with HoloViews using the Matplotlib backend. In this release however we have finally extended that support to both the Bokeh and Plotly backends, e.g. here we create a GIF zooming in on the Empire State building in the building dataset:\n\nempire_state_loc = -73.9857, 40.7484\n\ndef nyc_zoom(zoom):\n x, y = empire_state_loc\n width = (0.05-0.005*zoom)\n return datashade(nyc_buildings, aggregator=ds.by('type', ds.any()), color_key=glasbey[::-1],\n x_range=(x-width, x+width), y_range=(y-width, y+width), dynamic=False, min_alpha=0)\n\nhmap = hv.HoloMap({i: nyc_zoom(i) for i in range(10)}).opts(\n xaxis=None, yaxis=None, title='', toolbar=None, framewise=True,\n width=600, height=600, show_frame=False, backend='bokeh'\n)\n\nhv.output(hmap, holomap='gif', backend='bokeh', fps=2)\n\n\n\n\n\n\nWhat’s next?\nIn the coming months we will finally be focusing on a HoloViews 2.0 release where the main aims are:\n\nSplitting plotting and data components into separate packages\nAPI cleanup\nMore consistent styling between backends\n\nAdditionally we are continuing to work on some exciting features:\n\nFurther work on the Plotly backend making it a more equal citizen in the HoloViz ecosystem\nAdditions of new data interfaces including Vaex and Ibis\nBetter support for Pandas multi-indexes\n\n\n\n\n\n Back to top"
},
{
"objectID": "posts/hugging_face_template/index.html",
"href": "posts/hugging_face_template/index.html",
"title": "Building an interactive ML dashboard in Panel",
"section": "",
"text": "Demo of the image classification app.\nHoloViz Panel is a versatile Python library that empowers developers and data scientists to build interactive visualizations with ease. Whether you’re working on machine learning projects, developing web applications, or designing data dashboards, Panel provides a powerful set of tools and features to enhance your data exploration and presentation capabilities. In this blog post, we will delve into the exciting features of HoloViz Panel, explore how it can revolutionize your data visualization workflows, and demonstrate how you can make an app like this using about 100 lines of code.\nTry out the app and check out the code:"
},
{
"objectID": "posts/hugging_face_template/index.html#harnessing-the-power-of-mlai",
"href": "posts/hugging_face_template/index.html#harnessing-the-power-of-mlai",
"title": "Building an interactive ML dashboard in Panel",
"section": "Harnessing the Power of ML/AI",
"text": "Harnessing the Power of ML/AI\nML/AI has become an integral part of data analysis and decision-making processes. With Panel, you can seamlessly integrate ML models and results into your visualizations. In this blog post, we will explore how to make an image classification task using the OpenAI CLIP model.\nCLIP is pretrained on a large dataset of image-text pairs, enabling it to understand images and corresponding textual descriptions and work for various downstream tasks such as image classification.\nThere are two ML-related functions we used to perform the image classification task. The first function load_processor_model enables us to load a pre-trained CLIP model from Hugging Face. The second function get_similarity_score calculates the degree of similarity between the image and a provided list of class labels.\n@pn.cache\ndef load_processor_model(\n processor_name: str, model_name: str\n) -> Tuple[CLIPProcessor, CLIPModel]:\n processor = CLIPProcessor.from_pretrained(processor_name)\n model = CLIPModel.from_pretrained(model_name)\n return processor, model\n\ndef get_similarity_scores(class_items: List[str], image: Image) -> List[float]:\n processor, model = load_processor_model(\n \"openai/clip-vit-base-patch32\", \"openai/clip-vit-base-patch32\"\n )\n inputs = processor(\n text=class_items,\n images=[image],\n return_tensors=\"pt\", # pytorch tensors\n )\n outputs = model(**inputs)\n logits_per_image = outputs.logits_per_image\n class_likelihoods = logits_per_image.softmax(dim=1).detach().numpy()\n return class_likelihoods[0]"
},
{
"objectID": "posts/hugging_face_template/index.html#binding-widgets-for-interactivity",
"href": "posts/hugging_face_template/index.html#binding-widgets-for-interactivity",
"title": "Building an interactive ML dashboard in Panel",
"section": "Binding Widgets for Interactivity",
"text": "Binding Widgets for Interactivity\nOne of the key strengths of Panel is its ability to bind widgets to functions. This functionality provides an intuitive interface for users to manipulate the underlying data and gain deeper insights through interaction.\n\nPython Function\nIn our example, we have a process_input function, which formats the similarity score we get from the image classification model to a Panel object with a good-looking UI. The actual function utilizes async; if you’re unfamiliar with async, don’t worry! We will explain it in a later section, but note async is not a requirement of using Panel–Panel simply supports it!\nasync def process_inputs(class_names: List[str], image_url: str):\n \"\"\"\n High level function that takes in the user inputs and returns the\n classification results as panel objects.\n \"\"\"\n ...\n yield results\n\n\nPanel Widgets\nThere are two widgets that we use to interact with this function.\n\nimage_url is a TextInput widget, which allows entering any string as the image URL.\nclass_names is another TextInput widget, which accepts possible class names for the model to classify.\n\nimage_url = pn.widgets.TextInput(\n name=\"Image URL to classify\",\n value=pn.bind(random_url, randomize_url),\n)\nclass_names = pn.widgets.TextInput(\n name=\"Comma separated class names\",\n placeholder=\"Enter possible class names, e.g. cat, dog\",\n value=\"cat, dog, parrot\",\n)\n\n\nBinding Widgets to Function\nBased on the process_inputs function signature, it accepts two parameters: class_names and image_url. We can bind each arg/kwarg to a widget using pn.bind like this:\ninteractive_result = pn.panel(\n pn.bind(process_inputs, image_url=image_url, class_names=class_names),\n height=600,\n)\n\nThe first positional argument is the function name.\nThe keyword arguments after match the function’s signature, and thus the widgets’ values are bound to the function’s keyword arguments.\n\nTo clarify, if the widget was named image_url_input instead of image_url, then the call would be:\npn.bind(process_inputs, image_url=image_url_input, ...)"
},
{
"objectID": "posts/hugging_face_template/index.html#adding-template-design-styling",
"href": "posts/hugging_face_template/index.html#adding-template-design-styling",
"title": "Building an interactive ML dashboard in Panel",
"section": "Adding Template Design Styling",
"text": "Adding Template Design Styling\nThe aesthetics of your applications and dashboards play a critical role in engaging your audience. Panel enables you to add styling based off popular designs like Material or Fast to your visualizations, allowing you to create visually appealing and professional-looking interfaces.\nIn this example, we used a bootstrap template, where we can control what we’d like to show in multiple areas such as title and main, and we can specify sizes and colors for various components:\npn.extension(design=\"bootstrap\", sizing_mode=\"stretch_width\")\nWe also set the Progress bar design to Material.\nrow_bar = pn.indicators.Progress(\n ...\n design=pn.theme.Material,\n)\nNote, you can use styles and stylesheets too!"
},
{
"objectID": "posts/hugging_face_template/index.html#caching-for-expensive-tasks",
"href": "posts/hugging_face_template/index.html#caching-for-expensive-tasks",
"title": "Building an interactive ML dashboard in Panel",
"section": "Caching for Expensive Tasks",
"text": "Caching for Expensive Tasks\nSome data processing tasks can be computationally expensive, causing sluggish performance. Panel offers caching mechanisms that allow you to store the results of expensive computations and reuse them when needed, significantly improving the responsiveness of your applications.\nIn our example, we cached the output of the load_processor_model using the pn.cache decorator. This means that we don’t need to download and load the model multiple times. This step will make your app feel much more responsive!\nAdditional note: for further responsiveness, there’s defer_loading and loading indicators.\n@pn.cache\ndef load_processor_model(\n processor_name: str, model_name: str\n) -> Tuple[CLIPProcessor, CLIPModel]:\n processor = CLIPProcessor.from_pretrained(processor_name)\n model = CLIPModel.from_pretrained(model_name)\n return processor, model"
},
{
"objectID": "posts/hugging_face_template/index.html#bridging-functionality-with-javascript",
"href": "posts/hugging_face_template/index.html#bridging-functionality-with-javascript",
"title": "Building an interactive ML dashboard in Panel",
"section": "Bridging Functionality with JavaScript",
"text": "Bridging Functionality with JavaScript\nWhile Panel provides a rich set of interactive features, you may occasionally require additional functionality that can be achieved through JavaScript. It’s easy to integrate JavaScript code with Panel visualizations to extend their capabilities. By bridging the gap between Python and JavaScript, you can create advanced visualizations and add interactive elements that go beyond the scope of Panel’s native functionality.\nAt the bottom of our app, you might have observed a collection of icons representing Panel’s social media accounts, including LinkedIn and Twitter. When you click on any of these icons, you will be automatically redirected to the respective social media profiles. This seamless click and redirect functionality is made possible through Panel’s JavaScript integration with the js_on_click method:\nfooter_row = pn.Row(pn.Spacer(), align=\"center\")\nfor icon, url in ICON_URLS.items():\n href_button = pn.widgets.Button(icon=icon, width=35, height=35)\n href_button.js_on_click(code=f\"window.open('{url}')\")\n footer_row.append(href_button)\nfooter_row.append(pn.Spacer())"
},
{
"objectID": "posts/hugging_face_template/index.html#understanding-sync-vs.-async-support",
"href": "posts/hugging_face_template/index.html#understanding-sync-vs.-async-support",
"title": "Building an interactive ML dashboard in Panel",
"section": "Understanding Sync vs. Async Support",
"text": "Understanding Sync vs. Async Support\nAsynchronous programming has gained popularity due to its ability to handle concurrent tasks efficiently. We’ll discuss the differences between synchronous and asynchronous execution and explore Panel’s support for asynchronous operations. Understanding these concepts will enable you to leverage async capabilities within Panel, providing enhanced performance and responsiveness in your applications.\nUsing async to your function allows collaborative multitasking within a single thread and allows IO tasks to happen in the background. For example, when we fetch a random image to the internet, we don’t know how long we’d need to wait and we don’t want to stop our program while waiting. Async enables concurrent execution, allowing us to perform other tasks while waiting and ensuring a responsive application. Be sure to add the corresponding awaits too.\nasync def open_image_url(image_url: str) -> Image:\n async with aiohttp.ClientSession() as session:\n async with session.get(image_url) as resp:\n return Image.open(io.BytesIO(await resp.read()))\nIf you are unfamiliar with async, it’s also possible to rewrite this in sync too! async is not a requirement of using Panel!\ndef open_image_url(image_url: str) -> Image:\n with requests.get(image_url) as resp:\n return Image.open(io.BytesIO(resp.read()))"
},
{
"objectID": "posts/hugging_face_template/index.html#other-ideas-to-try",
"href": "posts/hugging_face_template/index.html#other-ideas-to-try",
"title": "Building an interactive ML dashboard in Panel",
"section": "Other Ideas to Try",
"text": "Other Ideas to Try\nHere we only explored one idea; there’s so much more you can try:\n\nInteractive Text Generation: Utilize Hugging Face’s powerful language models, such as GPT or Transformer, to generate interactive text. Combine Panel’s widget binding capabilities with Hugging Face models to create dynamic interfaces where users can input prompts or tweak parameters to generate custom text outputs.\nSentiment Analysis and Text Classification: Build interactive dashboards using Hugging Face’s pre-trained sentiment analysis or text classification models. With Panel, users can input text samples, visualize predicted sentiment or class probabilities, and explore model predictions through interactive visualizations.\nLanguage Translation: Leverage Hugging Face’s translation models to create interactive language translation interfaces. With Panel, users can input text in one language and visualize the translated output, allowing for easy experimentation and exploration of translation quality.\nNamed Entity Recognition (NER): Combine Hugging Face’s NER models with Panel to build interactive NER visualizations. Users can input text and visualize identified entities, highlight entity spans, and explore model predictions through an intuitive interface.\nChatbots and Conversational AI: With Hugging Face’s conversational models, you can create interactive chatbots or conversational agents. Panel enables users to have interactive conversations with the chatbot, visualize responses, and customize the chatbot’s behavior through interactive widgets.\nModel Fine-tuning and Evaluation: Use Panel to create interactive interfaces for fine-tuning and evaluating Hugging Face models. Users can input custom training data, adjust hyperparameters, visualize training progress, and evaluate model performance through interactive visualizations.\nModel Comparison and Benchmarking: Build interactive interfaces with Panel to compare and benchmark different Hugging Face models for specific NLP tasks. Users can input sample inputs, compare model predictions, visualize performance metrics, and explore trade-offs between different models.\n\nCheck out our app gallery for other ideas! Happy experimenting!"
},
{
"objectID": "posts/hugging_face_template/index.html#join-our-community",
"href": "posts/hugging_face_template/index.html#join-our-community",
"title": "Building an interactive ML dashboard in Panel",
"section": "Join Our Community",
"text": "Join Our Community\nThe Panel community is vibrant and supportive, with experienced developers and data scientists eager to help and share their knowledge. Join us and connect with us:\n\nDiscord\nDiscourse\nTwitter\nLinkedIn\nGithub"
},
{
"objectID": "posts/gv_release_1.5/index.html",
"href": "posts/gv_release_1.5/index.html",
"title": "GeoViews 1.5 Release",
"section": "",
"text": "We are very pleased to announce the release of GeoViews 1.5!\nThis release contains a large number of features and improvements. Some highlights include:\nMajor feature:\nNew components:\nNew features:\nEnhancements:\nPlus many other bug fixes, enhancements and documentation improvements. For full details, see the Release Notes.\nIf you are using Anaconda, GeoViews can most easily be installed by executing the command conda install -c pyviz geoviews . Otherwise, you can also use pip install geoviews as long as you satisfy the cartopy dependency yourself."
},
{
"objectID": "posts/gv_release_1.5/index.html#bokeh-support-for-projections",
"href": "posts/gv_release_1.5/index.html#bokeh-support-for-projections",
"title": "GeoViews 1.5 Release",
"section": "Bokeh support for projections",
"text": "Bokeh support for projections\nIn the past the Bokeh backend for GeoViews only supported displaying plots in Web Mercator coordinates. In this release this limitation was lifted and plots may now be projected to almost all supported Cartopy projections (to see the full list see the user guide):\n\ncities = pd.read_csv(gv_path+'/cities.csv', encoding=\"ISO-8859-1\")\npoints = gv.Points(cities[cities.Year==2050], ['Longitude', 'Latitude'], ['City', 'Population'])\nfeatures = gf.ocean * gf.land * gf.coastline\n\noptions = dict(width=600, height=350, global_extent=True,\n show_bounds=True, color='black', tools=['hover'], axiswise=True,\n color_index='Population', size_index='Population', size=0.002, cmap='viridis')\n\n(features * points.options(projection=ccrs.Mollweide(), **options) +\n features * points.options(projection=ccrs.PlateCarree(), **options))"
},
{
"objectID": "posts/gv_release_1.5/index.html#new-elements",
"href": "posts/gv_release_1.5/index.html#new-elements",
"title": "GeoViews 1.5 Release",
"section": "New elements",
"text": "New elements\nThe other main enhancements to GeoViews in the 1.5 release come from the addition of a wide array of new elements, some of which were recently added in HoloViews and others which have been newly made aware of geographic coordinate systems and added to Geoviews.\n\nGraph\nThe first such addition is the new Graph element which was added to HoloViews 1.9 and has now been made aware of geographic coordinates. The example below (available in the gallery) demonstrates how to use the Graph element to display airport routes from Hawaii with great-circle paths:\n\n\n\n\n\n\n\n\n\n\nVectorField\nAnother element that has been available in HoloViews and now been made aware of geographic coordinates is VectorField, useful for displaying vector quantities on a map. Like most HoloViews and GeoViews elements it can be rendered using both Bokeh (left) and Matplotlib (right):\n\n\n\n\n\n\n\n\n\n\nTriMesh\nAlso building on the graph capabilities is the TriMesh element, which allows defining arbitrary meshes from a set of nodes and a set of simplices (triangles defined as lists of node indexes). The TriMesh element allows easily visualizing Delaunay triangulations and even very large meshes, thanks to corresponding support added to datashader. Below we can see a small TriMesh displayed as a wire frame and an interpolated datashaded mesh of the Chesapeake Bay containing 1M triangles:\n\n\n\n\n\n\n\n\n\n\n\n\nQuadMesh\nGeoViews has long had an Image element that supports regularly sampled, rectilinear meshes similar to matplotlib’s imshow. To plot irregularly sampled rectilinear and curvilinear meshes, GeoViews now also has a QuadMesh element (akin to matplotlib’s pcolormesh). Below is a curvilinear mesh loaded from xarray:\n\n\n\n\n\n\n\n\n\n\nHexTiles\nAnother often requested feature is a hexagonal bin plot, which can be very helpful in visualizing large collections of points. Thanks to the recent addition of a hex tiling glyph in the bokeh 0.12.15 release it was straightforward to add this support in the form of a [HexTiles element]((http://holoviews.org/reference/elements/bokeh/HexTiles.html), which supports both simple bin counts and weighted binning, and fixed or variable hex sizes.\nBelow we can see a HexTiles plot of ~7 million points representing the NYC population, where each hexagonal bin is scaled and colored by the bin value:\n\n\n\n\n\n\n\n\n\n\nLabels\nThe existing Text element allows adding text to a plot, but only one item at a time, which is not suitable for plotting the large collections of text items that many users have been requesting. The new Labels element provides vectorized text plotting, which is probably most often used to annotate data points or regions of another plot type. Here we select the 20 most populous cities in 2050, plot them using the Points element, and use the Labels element to label each point:"
},
{
"objectID": "posts/gv_release_1.5/index.html#features",
"href": "posts/gv_release_1.5/index.html#features",
"title": "GeoViews 1.5 Release",
"section": "Features",
"text": "Features\nApart from the new collection of elements that were added, GeoViews 1.5 also comes with an impressive set of new features and enhancements.\n\nInbuilt Tile Sources\nSince plotting on top of a map tile source is such a common and useful feature, a new tile_sources module has been added to GeoViews. The new geoviews.tile_sources module includes a number of commonly used tile sources from CartoDB, Stamen, ESRI, OpenStreetMap and Wikipedia, a small selection of which is shown below:\n\nimport geoviews.tile_sources as gvts\n\n(gvts.CartoLight + gvts.CartoEco + gvts.ESRI + gvts.OSM + gvts.StamenTerrain + gvts.Wikipedia).cols(3)\n\n\n\n\n\n\nDatashader & xESMF regridding\nWhen working with mesh and raster data in a geographic context it is frequently useful to regrid the data. In this release we have improved support for regridding and rasterizing rectilinear and curvilinear grids and trimeshes using the Datashader and xESMF libraries. For a detailed overview of these capabilities see the user guide. As a quick summary:\n\nDatashader provides capabilities to quickly rasterize and regrid data of all kinds (Image, RGB, HSV, QuadMesh, TriMesh, Path, Points and Contours) but does not support complex interpolation and weighting schemes\nxESMF can regrid between general recti- and curvi-linear grids (Image and QuadMesh) with all ESMF regridding algorithms, such as bilinear, conservative and nearest neighbour\n\nBelow you can see the curvilinear mesh displayed above regridded and interpolated using xESMF:\n\n\nReuse existing file: bilinear_(-179.877, 179.749)_(16.334, 89.638)_400x400.nc\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nHover now displays lat/lon coordinates\nAs you may have noticed when hovering over some of the plots in this blog post, the hover tooltips now automatically format coordinates as latitudes and longitudes rather than the previous (and mostly useless) Web Mercator coordinates.\n\n\nOperations now CRS aware\nIn the past when operations defined in HoloViews were applied to GeoViews elements, the coordinate reference system (CRS) of the data was ignored and a HoloViews element was returned. Thanks to the ability to register pre- and post-processors for operations, operations such as datashade, rasterize, contours and bivariate_kde will now retain the coordinate system of the data.\nAs a simple example we will use the bivariate_kde operation from HoloViews to generate a density map from a set of points. Here the PlateCarree crs is retained throughout the operation so that the returned Contours element is appropriately projected on top of the tile source:\n\nfrom holoviews.operation.stats import bivariate_kde\n\npopulation = gv.Points(cities[cities.Year==2050], ['Longitude', 'Latitude'], 'Population')\n\ngvts.StamenTerrainRetina * bivariate_kde(population, bandwidth=0.1).options(\n width=500, height=450, show_legend=False, is_global=True\n).relabel('Most populous city density map')\n\n\n\n\n\n\nProjection operation improved\nThe gv.project operation provides a high-level wrapper for projecting all GeoViews element types and now has better handling for polygons and paths as well as all the new element types added in this release."
},
{
"objectID": "posts/gv_release_1.5/index.html#improved-documentation-gallery",
"href": "posts/gv_release_1.5/index.html#improved-documentation-gallery",
"title": "GeoViews 1.5 Release",
"section": "Improved documentation & gallery",
"text": "Improved documentation & gallery\nThis release was also accompanied by an overhaul of the existing documentation, specifically an improved user guide on projections and a whole new gallery with a wide (and expanding) selection of examples."
},
{
"objectID": "posts/ds_release_0.13/index.html#what-is-datashader",
"href": "posts/ds_release_0.13/index.html#what-is-datashader",
"title": "Datashader 0.13 Release",
"section": "What is Datashader?",
"text": "What is Datashader?\nDatashader is an open-source Python library for rendering large datasets quickly and accurately. Datashader provides highly optimized, scalable support for rasterizing your data into a fixed-size array for pixel-based displays, while avoiding overplotting and other issues that make it difficult to work with large datasets. Datashader works well on its own, but it is even more powerful when embedded into an interactive plotting library like Bokeh, Plotly, or (now!) Matplotlib."
},
{
"objectID": "posts/ds_release_0.13/index.html#announcing-datashader-0.13",
"href": "posts/ds_release_0.13/index.html#announcing-datashader-0.13",
"title": "Datashader 0.13 Release",
"section": "Announcing Datashader 0.13!",
"text": "Announcing Datashader 0.13!\nWe are very pleased to announce the 0.12.1 and 0.13 releases of Datashader! These releases include new features from a slew of different contributors, plus maintenance and bug fixes from Jim Bednar, Philipp Rudiger, Peter Roelants, Thuy Do Thi Minh, Chris Ball, and Jean-Luc Stevens.\nWhat’s new: - Matplotlib Artist for Datashader - Much more powerful categorical plotting - dynspread that actually works! - Aggregate spreading - Anti aliasing (experimental) - Datashader support in Dash - Inspect_points for interactive exploration in HoloViews"
},
{
"objectID": "posts/ds_release_0.13/index.html#matplotlib-artist-for-datashader",
"href": "posts/ds_release_0.13/index.html#matplotlib-artist-for-datashader",
"title": "Datashader 0.13 Release",
"section": "Matplotlib Artist for Datashader",
"text": "Matplotlib Artist for Datashader\nThanks to Nezar Abdennur (nvictus), Trevor Manz, Thomas Caswell, and Philipp Rudiger.\nDatashader works best when embedded in an interactive plotting library so that data can be revealed at every spatial scale by zooming and panning. Thomas Caswell made a draft of Datashader support for Matplotlib during SciPy 2016 when Datashader was first announced, but there was still a lot of work needed to make it general. Various people made suggestions, but largely the sketch sat patiently waiting for someone to finish it. In the meantime, Thomas Robitaille made a simpler points-only renderer https://github.com/astrofrog/mpl-scatter-density, which is useful if that’s all that’s needed. During sprints at SciPy 2020, Nezar Abdennur and Trevor Manz rescuscitated Tom’s work, and it’s now been released at last! You can now use all the power of Datashader with any of Matplotlib’s many backends, e.g. here for the osx backend:\nimport matplotlib.pyplot as plt, dask.dataframe as dd \nimport datashader as ds, colorcet as cc \nimport datashader.transfer_functions as tf \nfrom datashader.mpl_ext import dsshow \n%matplotlib osx \n\ndf = dd.read_parquet('data/nyc_taxi_wide.parq').compute() \n\ndsshow(df, ds.Point('dropoff_x', 'dropoff_y'), norm='eq_hist', \n cmap=cc.gray[::-1], shade_hook=tf.dynspread); \n\n\n\n\nSee getting_started/Interactivity to see how to use it."
},
{
"objectID": "posts/ds_release_0.13/index.html#much-more-powerful-categorical-plotting",
"href": "posts/ds_release_0.13/index.html#much-more-powerful-categorical-plotting",
"title": "Datashader 0.13 Release",
"section": "Much more powerful categorical plotting",
"text": "Much more powerful categorical plotting\nThanks to Michael Ihde (@maihde), Oleg Smirnov, Philipp Rudiger, and Jim Bednar.\nOne of Datashader’s most powerful features is its categorical binning and categorical colormapping, which allow detailed understanding of how the distribution of data differs by some other variable, such as this plot of how population is segregated by race in New York City:\n\nTo build such a plot, Datashader calculates a stack of aggregate arrays simulaneously, one per category, instead of a single aggregate array as in the non-categorical case.\nPreviously, categorical binning and plotting was limited to a count() reduction, i.e., counting how many datapoints fell into each pixel, by category, implemented using a special cat_count() reduction. Categorical plotting has now been fully generalized into a new ds.by() reduction, which accepts a categorical column along with count() or any other reduction (max(), min(), mean(), sum(), etc.). Thus it’s now possible to plot the mean value of any column, per pixel, per category. See the Pipeline docs for details.\nYou can also now use categorical binning and plotting with numerical columns using new functions category_modulo and category_binning, which opens up entirely new applications for Datashader. category_binning effectively gives Datashader the power to do 3D aggregations of numeric axes, not just the usual 2D. For instance, by(category_binning('z', 0, 10, 16)) will bin by the floating-point column z, counting datapoints in each of 16 categories (0: 0<=z<10, 1: 10<=x<20, etc.). Combining category_binning with by, you can now do complex 3D binning like computing the maximum age in each (x, y, weight) range:\ncat = ds.category_binning('weight', lower=0, higher=200, nbins=10)\nagg = canvas.points(df,'x','y', agg=ds.by(cat, ds.max('age')))\ncategory_modulo is useful when working with very large numbers of unsorted integers, using a modulo operator on an integer column to reduce a large number of columns down to something more tractable for plotting.\nSee #875 and #927 for details on by, category_modulo, and category_binning (currently documented only at https://github.com/holoviz/datashader/pull/927#issuecomment-725991064)."
},
{
"objectID": "posts/ds_release_0.13/index.html#dynspread-that-actually-works",
"href": "posts/ds_release_0.13/index.html#dynspread-that-actually-works",
"title": "Datashader 0.13 Release",
"section": "dynspread that actually works!",
"text": "dynspread that actually works!\nThanks to Jim Bednar.\nDatashader’s points plotting is designed to aggregate datapoints by pixel, accurately counting how many datapoints fell into each pixel. For large datasets, such a plot will accuratelyl reveal the spatial distribution of the data over the axes plotted. However, a consequence is that an individual data point not surrounded by others will show up as a single pixel, which can be difficult to see on a high-resolution monitor, and it is almost impossible to see its color. To alleviate this issue and make it easier to go back and forth between the big picture and individual datapoints, Datashader has long offered the dynspread output-transformation function, which takes each pixel and dilates it (increases it in size) until the density of such points reaches a specified metric value. However, dynspread never worked very well in practice, always either doing no spreading or one step of spreading (a 3x3 kernel). After a fresh look at the code, it became clear that the first step of spreading was artificially increasing the amount of estimated pixel density, making it very unlikely that a second or third step would ever be done.\ndynspread now spreads each pixel by an integer radius px up to the maximum radius max_px, stopping earlier if a specified fraction of data points have non-empty neighbors within the radius. This new definition provides predictable, well-behave dynspread behavior even for large values of max_px, making isolated datapoints easily visible. (#1001)\n\n\n\n\nNote that this definition is only compatible with points, as they are spatially isolated; any usage of dynspread with datatypes other than points should be replaced with spread(), which will do what was probably intended by the original dynspread call anyway (i.e., to make a line or polygon edge thicker)."
},
{
"objectID": "posts/ds_release_0.13/index.html#aggregate-spreading",
"href": "posts/ds_release_0.13/index.html#aggregate-spreading",
"title": "Datashader 0.13 Release",
"section": "Aggregate spreading",
"text": "Aggregate spreading\nThanks to Jean-Luc Stevens.\nSpreading previously worked only on RGB arrays, not numerical aggregate arrays, which meant that Datashader users had to choose between seeing isolated datapoints and having interactive features like Bokeh’s hover tool and colorbars that require access to the numerical aggregate values. spread and dynspread now work equally well with either RGB aggregates or numerical aggregates, and we now recommend that users spread at the numerical aggregate level in all supported cases. E.g. in holoviews, use spread(rasterize(obj)).opts(cnorm='eq_hist', cmap='fire') (or cnorm='log') instead of datashade(obj, cmap='fire'), and you’ll now have colorbar and hover support using Bokeh 2.3.3 or later. (#771\nimport dask.dataframe as dd, holoviews as hv\nfrom holoviews.operation.datashader import rasterize, dynspread\nimport bokeh, datashader as ds\nhv.extension(\"bokeh\")\n\ndf = dd.read_parquet('data/nyc_taxi_wide.parq').compute()\npts = hv.Points(df, ['dropoff_x', 'dropoff_y'])\nopts = hv.opts.Image(cnorm='log', colorbar=True, width=700, tools=['hover'])\ndynspread(rasterize(pts)).opts(opts)"
},
{
"objectID": "posts/ds_release_0.13/index.html#anti-aliasing-experimental",
"href": "posts/ds_release_0.13/index.html#anti-aliasing-experimental",
"title": "Datashader 0.13 Release",
"section": "Anti-aliasing (experimental)",
"text": "Anti-aliasing (experimental)\nThanks to Valentin Haenel.\nDatashader’s line aggregations (also used in trimesh and network plotting) count how many times a line crosses a given pixel. The resulting line plots are very blocky, because of binary transitions between rows and columns depending on where the underlying line lands in the aggregate array grid. To improve appearance of such lines (at a cost of making them less easy to interpret as counts of crossings), Datashader now supports antialiased lines. This support is only partial and is still experimental; it’s enabled by adding antialias=True to the Canvas.line() method call and is currently restricted to sum and max reductions only, and to a single-pixel line width. (#916)\n\n\nThe remaining updates listed below are shipped in other packages, not Datashader itself, but provide additional power for Datashader users."
},
{
"objectID": "posts/ds_release_0.13/index.html#datashader-support-in-dash",
"href": "posts/ds_release_0.13/index.html#datashader-support-in-dash",
"title": "Datashader 0.13 Release",
"section": "Datashader support in Dash",
"text": "Datashader support in Dash\nThanks to Jon Mease.\nThe Dash package for deploying data-science dashboards now supports Datashader using the high-level HoloViews Plotly backend. HoloViews Plotly, Matplotlib, and Bokeh plots can now be deployed using either a Bokeh-based server, which supports user-specific state that makes programmimg simpler, or a Dash-based server, which has a stateless model that can support larger numbers of concurrent users on a given set of server hardware."
},
{
"objectID": "posts/ds_release_0.13/index.html#inspect-function-for-interactive-exploration-in-holoviews",
"href": "posts/ds_release_0.13/index.html#inspect-function-for-interactive-exploration-in-holoviews",
"title": "Datashader 0.13 Release",
"section": "inspect function for interactive exploration in HoloViews",
"text": "inspect function for interactive exploration in HoloViews\nThanks to Jean-Luc Stevens and Philipp Rudiger.\nHoloViews has always been an easy way to work with interactive Datashader plots by handling user events, requesting an updated Datashader plot, and rendering the results. However, the resulting plots always showed only an aggregated view of the data, no matter how much the user zoomed in. HoloViews 1.14.4 now ships with inspect_points() and inspect_polygons wrapped in a general inspect function that uses Datashader’s aggregate to determine if there is data in a local region, then queries the original dataset to return those specific points and all their metadata. The result is that you can now view all of your data using Datashader, while still being able to see individual data points using hover or selection.\nSee the new ship_traffic example for how to use inspect_points and the NYC Buildings example for how to use inspect_polygons. Also see HoloViews linked brushing for related functionality that supports linked selections on Datashader and other plots."
},
{
"objectID": "posts/ds_release_0.13/index.html#help-us",
"href": "posts/ds_release_0.13/index.html#help-us",
"title": "Datashader 0.13 Release",
"section": "Help us!",
"text": "Help us!\nDatashader is an open-source project and we are always looking for new contributors. Join us the discussion on the Discourse and we would be very excited to get you started contributing! Also please get in touch with us if you work at an organization that would like to support future Datashader development, fund new Datashader features, or set up a support contract."
},
{
"objectID": "posts/quarto_migration/index.html",
"href": "posts/quarto_migration/index.html",
"title": "Reviving the blog with Quarto",
"section": "",
"text": "Following the tradition, we have decided that our first post after migrating to Quarto would be about the migration itself!"
},
{
"objectID": "posts/quarto_migration/index.html#why-change",
"href": "posts/quarto_migration/index.html#why-change",
"title": "Reviving the blog with Quarto",
"section": "Why change?",
"text": "Why change?\nThe HoloViz blog dates back to 2018 and at the time Pelican was chosen as the static site generator together with the pelican-jupyter plugin to add support to authoring blog posts from Jupyter Notebooks. While this combination served us well over the years, we observed that the notebook plugin was deprecated and that there was not much interest among our maintainers and contributors to update the existing site which was starting to show its age. We were in desperate need of a change!\n\n\nPelican version of the blog"
},
{
"objectID": "posts/quarto_migration/index.html#choosing-a-framework",
"href": "posts/quarto_migration/index.html#choosing-a-framework",
"title": "Reviving the blog with Quarto",
"section": "Choosing a framework",
"text": "Choosing a framework\nOne of our key requirements was to build the site from Jupyter Notebooks as the HoloViz tools have first-class notebook support and that is how we generally build our documentation websites. For that purpose we’re usually using Sphinx together with MyST-NB and some other custom extensions. However, except from the ABlog extension, the Sphinx ecosystem didn’t seem to provide what we were looking after and ABlog lacked some features we were potentially interested in (e.g. good integration for sharing on social media). This didn’t leave us with many options other than Quarto!\nQuarto is a recent open-source project that was announced in July 2022 and that is sponsored by Posit (formerly known as RStudio). It extends R Markdown, adding for instance, Jupyter Notebook support. We started experimenting with Quarto once we noticed increasing discussion about it from HoloViz users; we wanted to make sure our tools were working well in that ecosystem and the blog seemed to be a good place to start.\nWe were quickly convinced that Quarto was the right choice: the user experience was smooth, their documentation was clear and all in one place (unlike the Sphinx ecosystem where we had to navigate between various extension websites) and it appeared to support all the features we required. The only point that made us hesitate was that Quarto extensions have to be authored in Lua and none of us had any experience in that language. We decided that this wasn’t a blocker and went ahead with the migration."
},
{
"objectID": "posts/quarto_migration/index.html#migrating-to-quarto",
"href": "posts/quarto_migration/index.html#migrating-to-quarto",
"title": "Reviving the blog with Quarto",
"section": "Migrating to Quarto",
"text": "Migrating to Quarto\nThe migration all happened in this PR:\n\nWe had to convert the <post>.ipynb-meta sidecar files used by the pelican-jupyter files to the special header Quarto needs at the beginning of every document.\nThe notebooks themselves needed few changes, except to handle the nested and indented raw HTML included in Markdown cells that wasn’t displayed as HTML by Quarto but partially wrapped in a <code> HTML element. Removing the indentation fixed this problem (wrapping it in :::{=html} <... ::: would also have worked).\nWe had to move all the posts to the /posts directory which meant that the links to our old blog posts changed. We set up redirect links using the aliases document option to preserve these old links.\nWe decided that we preferred the default listing layout instead of grid.\nWe made some minor styling changes to align it with the styling of other HoloViz websites.\n\n\n\n\nQuarto version of the blog\n\n\nWhile the migration was quick and went smoothly, we listed a few issues that we might fix in future iterations. We are not too surprised that we have a few minor issues as our blog posts often contain a lot of complex HTML and Javascript that aren’t always easy to handle. We welcome contributions!"
},
{
"objectID": "posts/quarto_migration/index.html#easier-contribution",
"href": "posts/quarto_migration/index.html#easier-contribution",
"title": "Reviving the blog with Quarto",
"section": "Easier contribution",
"text": "Easier contribution\nMoving to Quarto improved the contributor experience, with a solid VSCode extension and a nice and fast preview mode, and again their excellent documentation.\nWe also made our infrastructure easier to manage which improved the contributor experience:\n\nthe site is no longer hosted on AWS but on Github Pages\na development version has been deployed, it is re-built and re-deployed automatically on every Pull Request event\nthe main site is re-built and re-deployed whenever a Pull Request is merged\n\nIf you feel like contributing to the HoloViz blog, head over to its Github repo and follow the instructions!"
},
{
"objectID": "posts/openai_logprobs_colored/index.html",
"href": "posts/openai_logprobs_colored/index.html",
"title": "Evaluate and filter LLM output using logprobs & colored text",
"section": "",
"text": "In many cases, there’s no indication of how confident the model is in its output; LLMs simply try to generate the most likely text based on the input and the model’s training data.\nHowever, with the logprobs parameter, we can now visualize the confidence of the model’s output.\nThis blog demonstrates how to color the text based on the log probabilities of the tokens. The higher the log probability, the more confident the model is in the token.\nThis is useful if you want to…\n\nbetter understand how your system prompt is affecting the model’s output\ncalibrate the model’s temperature to achieve the desired confidence level\nfilter out low-confidence outputs to lessen hallucinations\nsee whether incorporating retrieval augmented generation (RAG) can increase the confidence of the model’s output\nevaluate whether the model’s version affects the confidence of the output\n\n\n\n\nDemo"
},
{
"objectID": "posts/openai_logprobs_colored/index.html#introduction",
"href": "posts/openai_logprobs_colored/index.html#introduction",
"title": "Evaluate and filter LLM output using logprobs & colored text",
"section": "",
"text": "In many cases, there’s no indication of how confident the model is in its output; LLMs simply try to generate the most likely text based on the input and the model’s training data.\nHowever, with the logprobs parameter, we can now visualize the confidence of the model’s output.\nThis blog demonstrates how to color the text based on the log probabilities of the tokens. The higher the log probability, the more confident the model is in the token.\nThis is useful if you want to…\n\nbetter understand how your system prompt is affecting the model’s output\ncalibrate the model’s temperature to achieve the desired confidence level\nfilter out low-confidence outputs to lessen hallucinations\nsee whether incorporating retrieval augmented generation (RAG) can increase the confidence of the model’s output\nevaluate whether the model’s version affects the confidence of the output\n\n\n\n\nDemo"
},
{
"objectID": "posts/openai_logprobs_colored/index.html#tldr",
"href": "posts/openai_logprobs_colored/index.html#tldr",
"title": "Evaluate and filter LLM output using logprobs & colored text",
"section": "TLDR",
"text": "TLDR\nHere’s the full code below.\nHighlights:\n\nPanel to create a chat interface and input widgets to control LLM’s parameters\nTastyMap to generate a limited color palette to map to the log probabilities\nthe logprobs is extracted from the model’s response to use for coloring the text\n\nContinue reading for a simple version of the following code, which additionally features playground-like widgets to control the model’s parameters and system prompt.\nimport os\nimport re\n\nimport numpy as np\nimport panel as pn\nimport tastymap as tm\nfrom openai import AsyncOpenAI\n\npn.extension()\n\nCOLORMAP = \"viridis_r\"\nNUM_COLORS = 8\nSYSTEM_PROMPT = \"\"\"\nBased on the text, classify as one of these options:\n- Feature\n- Bug\n- Docs\nAnswer in one word; no other options are allowed.\n\"\"\".strip()\n\n\ndef color_by_logprob(text, log_prob):\n linear_prob = np.round(np.exp(log_prob) * 100, 2)\n # select index based on probability\n color_index = int(linear_prob // (100 / (len(colors) - 1)))\n\n # Generate HTML output with the chosen color\n if \"'\" in text:\n html_output = f'<span style=\"color: {colors[color_index]};\">{text}</span>'\n else:\n html_output = f\"<span style='color: {colors[color_index]}'>{text}</span>\"\n return html_output\n\n\ndef custom_serializer(content):\n pattern = r\"<span.*?>(.*?)</span>\"\n matches = re.findall(pattern, content)\n if not matches:\n return content\n return matches[0]\n\n\nasync def respond_to_input(contents: str, user: str, instance: pn.chat.ChatInterface):\n if api_key_input.value:\n aclient.api_key = api_key_input.value\n elif not os.environ[\"OPENAI_API_KEY\"]:\n instance.send(\"Please provide an OpenAI API key\", respond=False, user=\"ChatGPT\")\n\n # add system prompt\n if system_input.value:\n system_message = {\"role\": \"system\", \"content\": system_input.value}\n messages = [system_message]\n else:\n messages = []\n\n # gather messages for memory\n if memory_toggle.value:\n messages += instance.serialize(custom_serializer=custom_serializer)\n else:\n messages.append({\"role\": \"user\", \"content\": contents})\n\n # call API\n response = await aclient.chat.completions.create(\n model=model_selector.value,\n messages=messages,\n stream=True,\n logprobs=True,\n temperature=temperature_input.value,\n max_tokens=max_tokens_input.value,\n seed=seed_input.value,\n )\n\n # stream response\n message = \"\"\n async for chunk in response:\n choice = chunk.choices[0]\n content = choice.delta.content\n log_probs = choice.logprobs\n if content and log_probs:\n log_prob = log_probs.content[0].logprob\n message += color_by_logprob(content, log_prob)\n yield message\n\n\ntmap = tm.cook_tmap(COLORMAP, NUM_COLORS)\ncolors = tmap.to_model(\"hex\")\n\naclient = AsyncOpenAI()\napi_key_input = pn.widgets.PasswordInput(\n name=\"API Key\",\n placeholder=\"sk-...\",\n width=150,\n)\nsystem_input = pn.widgets.TextAreaInput(\n name=\"System Prompt\",\n value=SYSTEM_PROMPT,\n rows=1,\n auto_grow=True,\n)\nmodel_selector = pn.widgets.Select(\n name=\"Model\",\n options=[\"gpt-3.5-turbo\", \"gpt-4\"],\n width=150,\n)\ntemperature_input = pn.widgets.FloatInput(\n name=\"Temperature\", start=0, end=2, step=0.01, value=1, width=100\n)\nmax_tokens_input = pn.widgets.IntInput(name=\"Max Tokens\", start=0, value=256, width=100)\nseed_input = pn.widgets.IntInput(name=\"Seed\", start=0, end=100, value=0, width=100)\nmemory_toggle = pn.widgets.Toggle(\n name=\"Include Memory\", value=False, width=100, margin=(22, 5)\n)\nchat_interface = pn.chat.ChatInterface(\n callback=respond_to_input,\n callback_user=\"ChatGPT\",\n callback_exception=\"verbose\",\n)\n\npn.Column(\n pn.Row(\n api_key_input,\n system_input,\n model_selector,\n temperature_input,\n max_tokens_input,\n seed_input,\n memory_toggle,\n align=\"center\",\n ),\n pn.Row(tmap._repr_html_(), align=\"center\"),\n chat_interface,\n).show()"
},
{
"objectID": "posts/openai_logprobs_colored/index.html#building-the-app",
"href": "posts/openai_logprobs_colored/index.html#building-the-app",
"title": "Evaluate and filter LLM output using logprobs & colored text",
"section": "Building the app",
"text": "Building the app\nTo get started, I usually envision the key components of the app and then build them out one by one.\nAs the first step, let’s try to extract the log probabilities from the model’s streaming response.\nfrom openai import AsyncOpenAI\n\naclient = AsyncOpenAI()\n\nasync def get_log_probs(contents: str):\n response = await aclient.chat.completions.create(\n model=\"gpt-3.5-turbo\",\n messages=[{\"role\": \"user\", \"content\": contents}],\n stream=True,\n logprobs=True,\n )\n\n token_log_probs = {}\n async for chunk in response:\n choice = chunk.choices[0]\n content = choice.delta.content\n log_probs = choice.logprobs\n if content and log_probs:\n log_prob = log_probs.content[0].logprob\n token_log_probs[content] = log_prob\n return token_log_probs\n\nlog_probs = await get_log_probs(\"Say dog or cat.\")\nlog_probs\nOutput: {'Dog': -0.32602254, '.': -0.4711762}\nThese are the log probabilities of the tokens in the response, but they are not exactly intuitive.\nWe can convert these log probabilities to linear probabilities using this formula.\n\nimport numpy as np\n\nfor token, log_prob in log_probs.items():\n linear_prob = np.round(np.exp(log_prob) * 100, 2)\n print(f\"{token}: {linear_prob}%\")\nOutput:\nDog: 72.18%\n.: 62.43%\nNow that we have the linear probabilities, we can map them to a color palette using TastyMap.\nLet’s first try coloring some text in Panel.\nimport panel as pn\n\npn.extension()\n\ntext = \"This is a test sentence.\"\ncolor = \"red\"\nhtml_output = f\"<span style='color: {color}'>{text}</span>\"\npn.pane.Markdown(html_output)\n\n\n\nred sentence\n\n\nGreat, the text is now colored in red.\nWith that knowledge, we can map the linear probabilities to a color palette using TastyMap and display the colorbar.\n\nimport panel as pn\nimport tastymap as tm\n\npn.extension()\n\nCOLORMAP = \"viridis_r\"\nNUM_COLORS = 8\n\ndef color_by_logprob(text, log_prob):\n linear_prob = np.round(np.exp(log_prob) * 100, 2)\n # select index based on probability\n color_index = int(linear_prob // (100 / (len(colors) - 1)))\n\n # Generate HTML output with the chosen color\n if \"'\" in text:\n html_output = f'<span style=\"color: {colors[color_index]};\">{text}</span>'\n else:\n html_output = f\"<span style='color: {colors[color_index]}'>{text}</span>\"\n return html_output\n\n\ntmap = tm.cook_tmap(COLORMAP, NUM_COLORS)\ncolors = tmap.to_model(\"hex\")\nhtml = \"\"\nfor token, log_prob in log_probs.items():\n html += color_by_logprob(token, log_prob)\n\npn.Column(tmap._repr_html_(), pn.pane.HTML(html))\nNext, we can link everything together in a simple chat interface using Panel.\nUse the callback keyword argument to specify the function that will handle the user’s input.\nHere, we use the respond_to_input function to handle the user’s input, which\n\nsends the user’s input to the OpenAI API\nreceives the model’s response\nextracts the log probabilities from the response\ncolors the text based on the log probabilities\nyields (streams) the colored text back to the chat interface\n\nimport panel as pn\nimport tastymap as tm\n\npn.extension()\n\nCOLORMAP = \"viridis_r\"\nNUM_COLORS = 8\n\ndef color_by_logprob(text, log_prob):\n linear_prob = np.round(np.exp(log_prob) * 100, 2)\n # select index based on probability\n color_index = int(linear_prob // (100 / (len(colors) - 1)))\n\n # Generate HTML output with the chosen color\n if \"'\" in text:\n html_output = f'<span style=\"color: {colors[color_index]};\">{text}</span>'\n else:\n html_output = f\"<span style='color: {colors[color_index]}'>{text}</span>\"\n return html_output\n\nasync def respond_to_input(contents: str, user: str, instance: pn.chat.ChatInterface):\n response = await aclient.chat.completions.create(\n model=\"gpt-3.5-turbo\",\n messages=[{\"role\": \"user\", \"content\": contents}],\n stream=True,\n logprobs=True,\n )\n\n message = \"\"\n async for chunk in response:\n choice = chunk.choices[0]\n content = choice.delta.content\n log_probs = choice.logprobs\n if content and log_probs:\n log_prob = log_probs.content[0].logprob\n message += color_by_logprob(content, log_prob)\n yield message\n\ntmap = tm.cook_tmap(COLORMAP, NUM_COLORS)\ncolors = tmap.to_model(\"hex\")\n\nchat_interface = pn.chat.ChatInterface(\n callback=respond_to_input,\n callback_user=\"ChatGPT\",\n callback_exception=\"verbose\",\n)\nchat_interface.send(\"Say dog or cat.\")\npn.Column(\n tmap._repr_html_(),\n chat_interface,\n align=\"center\",\n).servable()\n\n\n\nsimple app"
},
{
"objectID": "posts/openai_logprobs_colored/index.html#conclusion",
"href": "posts/openai_logprobs_colored/index.html#conclusion",
"title": "Evaluate and filter LLM output using logprobs & colored text",
"section": "Conclusion",
"text": "Conclusion\nCongrats! You’ve built a chat interface that colors the text based on the log probabilities of the tokens in the model’s response.\nFeel free to study the code above and modify it to suit your needs; in the TLDR section, I have additionally added widgets to control the model’s parameters and system prompt!\nIf you are interested in learning more about how to build AI chatbots in Panel, please read our related blog posts:\n\nBuild a Mixtral Chatbot with Panel\nBuilding AI Chatbots with Mistral and Llama2\nBuilding a Retrieval Augmented Generation Chatbot\nHow to Build Your Own Panel AI Chatbots\nBuild a RAG chatbot to answer questions about Python libraries\nBuild an AI Chatbot to Run Code and Tweak plots\n\nIf you find Panel useful, please consider giving us a star on Github (https://github.com/holoviz/panel). If you have any questions, feel free to ask on our Discourse. Happy coding!"
},
{
"objectID": "posts/ai_chatbot_tips_memory_download/index.html",
"href": "posts/ai_chatbot_tips_memory_download/index.html",
"title": "Panel AI Chatbot Tips: Memory and Downloadable Conversations",
"section": "",
"text": "In this blog post, we’ll explore how to build a simple AI chatbot, enhance it with memory capabilities, and finally, implement a feature to download conversations for further fine-tuning.\nWe will cover:\nBefore we get started, let’s first make sure we install needed packages like panel, mistralai, openai in our Python environment and save our API keys as environment variables:\nexport MISTRAL_API_KEY=\"TYPE YOUR API KEY\"\nexport OPENAI_API_KEY=\"TYPE YOUR API KEY\""
},
{
"objectID": "posts/ai_chatbot_tips_memory_download/index.html#mistral-models",
"href": "posts/ai_chatbot_tips_memory_download/index.html#mistral-models",
"title": "Panel AI Chatbot Tips: Memory and Downloadable Conversations",
"section": "Mistral models",
"text": "Mistral models\nIn this blog post, we will only use the Mistral API. If you are interested in using Mistral models locally, check out our previous blog post Build a Mixtral Chatbot with Panel to see how we used Mistral API, transformers, llama.cpp, and Panel to create AI chatbots that use the Mixtral 8x7B Instruct model.\nWhen we do not need to keep our conversation history, we are only sending one round of user message to model. Thus, in this example, the messages that get sent to the model are defined as [ChatMessage(role=\"user\", content=contents)].\nimport os\nimport panel as pn\nfrom mistralai.async_client import MistralAsyncClient\nfrom mistralai.models.chat_completion import ChatMessage\n\npn.extension()\n\n\nasync def callback(contents: str, user: str, instance: pn.chat.ChatInterface):\n model = \"mistral-small\"\n messages = [\n ChatMessage(role=\"user\", content=contents)\n ]\n response = client.chat_stream(model=model, messages=messages)\n\n message = \"\"\n async for chunk in response:\n part = chunk.choices[0].delta.content\n if part is not None:\n message += part\n yield message\n\n\nclient = MistralAsyncClient(api_key=os.environ[\"MISTRAL_API_KEY\"])\nchat_interface = pn.chat.ChatInterface(callback=callback, callback_user=\"Mixtral\")\nchat_interface.send(\n \"Send a message to get a reply from Mixtral!\", user=\"System\", respond=False\n)\nchat_interface.servable()"
},
{
"objectID": "posts/ai_chatbot_tips_memory_download/index.html#openai-models",
"href": "posts/ai_chatbot_tips_memory_download/index.html#openai-models",
"title": "Panel AI Chatbot Tips: Memory and Downloadable Conversations",
"section": "OpenAI models",
"text": "OpenAI models\nThe code of using OpenAI models looks very similar. We are using OpenAI’s API with async/await to use the asynchronous client. To use async, we simply import AsyncOpenAI instead of OpenAI and add await with the API call.\nimport panel as pn\nfrom openai import AsyncOpenAI\n\npn.extension()\n\n\nasync def callback(contents: str, user: str, instance: pn.chat.ChatInterface):\n messages = [{\"role\": \"user\", \"content\": contents}]\n response = await aclient.chat.completions.create(\n model=\"gpt-3.5-turbo\",\n messages=messages,\n stream=True,\n )\n message = \"\"\n async for chunk in response:\n part = chunk.choices[0].delta.content\n if part is not None:\n message += part\n yield message\n\n\naclient = AsyncOpenAI()\nchat_interface = pn.chat.ChatInterface(callback=callback, callback_user=\"ChatGPT\")\nchat_interface.send(\n \"Send a message to get a reply from ChatGPT!\", user=\"System\", respond=False\n)\nchat_interface.servable()"
},
{
"objectID": "posts/ai_chatbot_tips_memory_download/index.html#mistral-models-1",
"href": "posts/ai_chatbot_tips_memory_download/index.html#mistral-models-1",
"title": "Panel AI Chatbot Tips: Memory and Downloadable Conversations",
"section": "Mistral models",
"text": "Mistral models\n\nimport os\nimport panel as pn\nfrom mistralai.async_client import MistralAsyncClient\nfrom mistralai.models.chat_completion import ChatMessage\n\npn.extension()\n\n\nasync def callback(contents: str, user: str, instance: pn.chat.ChatInterface):\n model = \"mistral-small\"\n messages = [\n ChatMessage(**message)\n for message in instance.serialize()[1:]\n ]\n response = client.chat_stream(model=model, messages=messages)\n\n message = \"\"\n async for chunk in response:\n part = chunk.choices[0].delta.content\n if part is not None:\n message += part\n yield message\n\n\nclient = MistralAsyncClient(api_key=os.environ[\"MISTRAL_API_KEY\"])\nchat_interface = pn.chat.ChatInterface(callback=callback, callback_user=\"Mixtral\")\nchat_interface.send(\n \"Send a message to get a reply from Mixtral!\", user=\"System\", respond=False\n)\nchat_interface.servable()\nHere in this example, the model indeed knows what we were talking about previously."
},
{
"objectID": "posts/ai_chatbot_tips_memory_download/index.html#openai-models-1",
"href": "posts/ai_chatbot_tips_memory_download/index.html#openai-models-1",
"title": "Panel AI Chatbot Tips: Memory and Downloadable Conversations",
"section": "OpenAI models",
"text": "OpenAI models\nThe code for OpenAI models is even simpler. Simply change messages to instance.serialize()[1:], you will send all the chat history except for the first message to OpenAI API.\nimport panel as pn\nfrom openai import AsyncOpenAI\n\npn.extension()\n\n\nasync def callback(contents: str, user: str, instance: pn.chat.ChatInterface):\n messages = instance.serialize()[1:]\n response = await aclient.chat.completions.create(\n model=\"gpt-3.5-turbo\",\n messages=messages,\n stream=True,\n )\n message = \"\"\n async for chunk in response:\n part = chunk.choices[0].delta.content\n if part is not None:\n message += part\n yield message\n\n\naclient = AsyncOpenAI()\nchat_interface = pn.chat.ChatInterface(callback=callback, callback_user=\"ChatGPT\")\nchat_interface.send(\n \"Send a message to get a reply from ChatGPT!\", user=\"System\", respond=False\n)\nchat_interface.servable()"
},
{
"objectID": "posts/ai_chatbot_tips_memory_download/index.html#mistral-models-2",
"href": "posts/ai_chatbot_tips_memory_download/index.html#mistral-models-2",
"title": "Panel AI Chatbot Tips: Memory and Downloadable Conversations",
"section": "Mistral models",
"text": "Mistral models\nWhat we are adding here the file_download widget. When we click this button, it will execute the download_history function, which just dump our chat history (chat_interface.serialize()) into a json file and save into the history.json file.\nThe output is a well-formatted json file that can easily be used for future model fine-tuning.\nimport os\nimport panel as pn\nfrom mistralai.async_client import MistralAsyncClient\nfrom mistralai.models.chat_completion import ChatMessage\nfrom io import StringIO\nimport json\n\npn.extension()\n\n\nasync def callback(contents: str, user: str, instance: pn.chat.ChatInterface):\n model = \"mistral-small\"\n messages = [\n ChatMessage(**message)\n for message in instance.serialize()[1:]\n ]\n print(messages)\n response = client.chat_stream(model=model, messages=messages)\n\n message = \"\"\n async for chunk in response:\n part = chunk.choices[0].delta.content\n if part is not None:\n message += part\n yield message\n\ndef download_history():\n buf = StringIO()\n json.dump(chat_interface.serialize(), buf)\n buf.seek(0)\n return buf\n\nfile_download = pn.widgets.FileDownload(\n callback=download_history, filename=\"history.json\"\n)\nheader = pn.Row(pn.HSpacer(), file_download)\n\n\nclient = MistralAsyncClient(api_key=os.environ[\"MISTRAL_API_KEY\"])\nchat_interface = pn.chat.ChatInterface(\n callback=callback, \n callback_user=\"Mixtral\",\n header=header\n )\nchat_interface.send(\n \"Send a message to get a reply from Mixtral!\", user=\"System\", respond=False\n)\nchat_interface.servable()"
},
{
"objectID": "posts/ai_chatbot_tips_memory_download/index.html#openai-models-2",
"href": "posts/ai_chatbot_tips_memory_download/index.html#openai-models-2",
"title": "Panel AI Chatbot Tips: Memory and Downloadable Conversations",
"section": "OpenAI models",
"text": "OpenAI models\nAdding exactly the same code, we can also easily download all conversation with OpenAI models:\nimport panel as pn\nfrom openai import AsyncOpenAI\nfrom io import StringIO\nimport json\n\npn.extension()\n\n\nasync def callback(contents: str, user: str, instance: pn.chat.ChatInterface):\n messages = instance.serialize()[1:]\n response = await aclient.chat.completions.create(\n model=\"gpt-3.5-turbo\",\n messages=messages,\n stream=True,\n )\n message = \"\"\n async for chunk in response:\n part = chunk.choices[0].delta.content\n if part is not None:\n message += part\n yield message\n\ndef download_history():\n buf = StringIO()\n json.dump(chat_interface.serialize(), buf)\n buf.seek(0)\n return buf\n\nfile_download = pn.widgets.FileDownload(\n callback=download_history, filename=\"history.json\"\n)\nheader = pn.Row(pn.HSpacer(), file_download)\n\naclient = AsyncOpenAI()\nchat_interface = pn.chat.ChatInterface(\n callback=callback, \n callback_user=\"ChatGPT\",\n header=header\n )\nchat_interface.send(\n \"Send a message to get a reply from ChatGPT!\", user=\"System\", respond=False\n)\nchat_interface.servable()"
},
{
"objectID": "posts/mixtral/index.html",
"href": "posts/mixtral/index.html",
"title": "Build a Mixtral Chatbot with Panel",
"section": "",
"text": "Mistral AI just announced the Mixtral 8x7B and the Mixtral 8x7B Instruct models. These models have shown really amazing performance, outperforming Llama 2 and GPT 3.5 in many benchmarks. They’ve quickly became the most popular open weights models in the AI world. In this blog post, we will walk you through how to build AI chatbots with the Mixtral 8x7B Instruct model using the Panel chat interface. We will cover three methods:"
},
{
"objectID": "posts/mixtral/index.html#build-a-panel-chatbot",
"href": "posts/mixtral/index.html#build-a-panel-chatbot",
"title": "Build a Mixtral Chatbot with Panel",
"section": "Build a Panel chatbot",
"text": "Build a Panel chatbot\nBefore we build a Panel chatbot, let’s make sure we install mistralai and panel in our Python environment and set up Mistal API key as an environment variable: export MISTRAL_API_KEY=\"TYPE YOUR KEY\".\n\nWe wrap the code above in a function callback.\nThe key to building a Panel chatbot is to define pn.chat.ChatInterface. Specifically, in the callback method, we need to define how the chat bot responds to user message – the callback function.\nTo turn a Python file or a notebook into a deployable app, simply append .servable() to the Panel object chat_interface.\n\n\"\"\"\nDemonstrates how to use the `ChatInterface` to create a chatbot using\nMistral API.\n\"\"\"\nimport os\nimport panel as pn\nfrom mistralai.client import MistralClient\nfrom mistralai.models.chat_completion import ChatMessage\n\npn.extension()\n\nasync def callback(contents: str, user: str, instance: pn.chat.ChatInterface):\n\n model = \"mistral-small\"\n messages = [ChatMessage(role=\"user\", content=contents)]\n response = client.chat_stream(model=model, messages=messages)\n \n message = \"\"\n for chunk in response:\n part = chunk.choices[0].delta.content\n if part is not None:\n message += part\n yield message\n\n\nclient = MistralClient(api_key=os.environ[\"MISTRAL_API_KEY\"])\nchat_interface = pn.chat.ChatInterface(callback=callback, callback_user=\"Mixtral\")\nchat_interface.send(\n \"Send a message to get a reply from Mixtral!\", user=\"System\", respond=False\n)\nchat_interface.servable()\nTo launch a server using CLI and interact with this app, simply run panel serve app.py and you can interact with the model:"
},
{
"objectID": "posts/mixtral/index.html#build-a-panel-chatbot-1",
"href": "posts/mixtral/index.html#build-a-panel-chatbot-1",
"title": "Build a Mixtral Chatbot with Panel",
"section": "Build a Panel chatbot",
"text": "Build a Panel chatbot\nSame as what we saw in Method 1, we wrap the code above in a function callback, and define the callback in the pn.chat.ChatInterface function:\nimport panel as pn\nfrom transformers import AutoTokenizer, TextStreamer\nimport transformers\nimport torch\n\npn.extension()\n\nasync def callback(contents: str, user: str, instance: pn.chat.ChatInterface):\n messages = [{\"role\": \"user\", \"content\": contents}]\n prompt = pipeline.tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)\n streamer = TextStreamer(tokenizer, skip_prompt=True)\n outputs = pipeline(prompt, streamer=streamer, max_new_tokens=256, do_sample=True, temperature=0.7, top_k=50, top_p=0.95)\n message = \"\"\n for token in outputs[0][\"generated_text\"]:\n message += token\n yield message\n \nmodel = \"mistralai/Mixtral-8x7B-Instruct-v0.1\"\n\ntokenizer = AutoTokenizer.from_pretrained(model)\npipeline = transformers.pipeline(\n \"text-generation\",\n model=model,\n model_kwargs={\"torch_dtype\": torch.float16, \"load_in_4bit\": True},\n)\nchat_interface = pn.chat.ChatInterface(callback=callback, callback_user=\"Mixtral\")\nchat_interface.send(\n \"Send a message to get a reply from Mixtral!\", user=\"System\", respond=False\n)\nchat_interface.servable()\nRun panel serve app.py in CLI to interact with this app. Here is an example of our interaction with the model:"
},
{
"objectID": "posts/mixtral/index.html#set-up",
"href": "posts/mixtral/index.html#set-up",
"title": "Build a Mixtral Chatbot with Panel",
"section": "Set up",
"text": "Set up\nFirst, we need to download llama-cpp-python, which is a Python binding for llama.cpp. Depending on your computer, the steps to install it might look different. Since we are using a Macbook M1 Pro with a Metal GPU. Here are the steps to install llama-cpp-python with Metal: https://llama-cpp-python.readthedocs.io/en/latest/install/macos/. Here is what I did:\n!CMAKE_ARGS=\"-DLLAMA_METAL=on\" FORCE_CMAKE=1 pip install llama-cpp-python\nSecond, let’s download the 4-bit quantized version of the Mixtral-8x7B-Instruct model form Hugging Face. Note that this file is quite big, about 26GB.\nwget https://huggingface.co/TheBloke/Mixtral-8x7B-Instruct-v0.1-GGUF/resolve/main/mixtral-8x7b-instruct-v0.1.Q4_0.gguf\nBecause Mixtral is not merged in llama.cpp yet, we need to do the following steps.\n# REF: https://github.com/abetlen/llama-cpp-python/issues/1000\ngit clone https://github.com/ggerganov/llama.cpp\ncd llama.cpp\ngit checkout mixtral\nmake -j\nmake libllama.so\nFinally, don’t forget to install the other needed packages such as transformers and panel."
},
{
"objectID": "posts/mixtral/index.html#run-mixtral",
"href": "posts/mixtral/index.html#run-mixtral",
"title": "Build a Mixtral Chatbot with Panel",
"section": "Run Mixtral",
"text": "Run Mixtral\nBelow is the Python code for running Mixtral with llama.cpp. Here are the steps:\n\nWe first need to define an environment variable LLAMA_CPP_LIB directed to the libllama.so file, which is saved under the llama.cpp directly we got from git clone earlier.\nThen we define our llm pointing to the mixtral-8x7b-instruct-v0.1.Q4_0.gguf file we downloaded from wget.\nNote that we need to load the tokenizer from the Mixtral-8x7B-Instruct model and format the input text the way the model expects.\nThen we can get responses from llm.create_completion. The default max_tokens is 16. To get reasonable good responses, let’s increase this number to 256.\n\nimport os\nos.environ[\"LLAMA_CPP_LIB\"] = \"/PATH WHERE YOU SAVED THE llama.cpp DIRECTORY FROM GIT CLONE/llama.cpp/libllama.so\"\n\nfrom llama_cpp import Llama\nfrom transformers import AutoTokenizer\n\nllm = Llama(\n model_path=\"./mixtral-8x7b-instruct-v0.1.Q4_0.gguf\",\n n_gpu_layers=0,\n)\n\nmodel = \"mistralai/Mixtral-8x7B-Instruct-v0.1\"\ntokenizer = AutoTokenizer.from_pretrained(model)\nmessages = [{\"role\": \"user\", \"content\": \"Explain what a Mixture of Experts is in less than 100 words.\"}]\nprompt = tokenizer.apply_chat_template(messages, tokenize=False)\n\nresponse = llm.create_completion(prompt, max_tokens=256)\nresponse['choices'][0]['text']\nHere you can see the code running in Jupyter Notebook cells. Please be patient as this will take some time. After a few minutes, the model outputs results based on our input prompt:"
},
{
"objectID": "posts/mixtral/index.html#build-a-panel-chatbot-2",
"href": "posts/mixtral/index.html#build-a-panel-chatbot-2",
"title": "Build a Mixtral Chatbot with Panel",
"section": "Build a Panel chatbot",
"text": "Build a Panel chatbot\n\nSame as what we have seen before, let’s wrap the code logic above in a function called callback, which is how we want our chatbot to respond to user messages.\nThen in pn.chat.ChatInterface, we define callback as this callback function.\n\nimport os\nos.environ[\"LLAMA_CPP_LIB\"] = \"/PATH WHERE YOU SAVED THE llama.cpp DIRECTORY FROM GIT CLONE/llama.cpp/libllama.so\"\n\nfrom llama_cpp import Llama\nfrom transformers import AutoTokenizer\nimport panel as pn\n\npn.extension()\n\nasync def callback(contents: str, user: str, instance: pn.chat.ChatInterface):\n\n messages = [{\"role\": \"user\", \"content\": contents}]\n prompt = tokenizer.apply_chat_template(messages, tokenize=False)\n response = llm.create_completion(prompt, max_tokens=256, stream=True)\n\n message = \"\"\n for chunk in response:\n message += chunk['choices'][0]['text']\n yield message\n \nmodel = \"mistralai/Mixtral-8x7B-Instruct-v0.1\"\ntokenizer = AutoTokenizer.from_pretrained(model)\nllm = Llama(\n model_path=\"./mixtral-8x7b-instruct-v0.1.Q4_0.gguf\",\n n_gpu_layers=0,\n)\nchat_interface = pn.chat.ChatInterface(\n callback=callback, \n callback_user=\"Mixtral\",\n message_params={\"show_reaction_icons\": False}\n )\nchat_interface.send(\n \"Send a message to get a reply from Mixtral!\", user=\"System\", respond=False\n)\nchat_interface.servable()\n\nFinally, we can run panel serve app.py to interact with this app. As you can see in this gif, it’s actually quite slow generating each word because we are running on a local Macbook."
},
{
"objectID": "posts/pyviz_scipy_bof_2019/index.html",
"href": "posts/pyviz_scipy_bof_2019/index.html",
"title": "PyViz at SciPy 2019",
"section": "",
"text": "The Python Data Visualization Birds-of-a-Feather session at the scientific Python conference brought together a dozen different authors of Python packages for visualizing data. Each author was asked to state one thing that they found exciting right now about Python data viz from their own perspective, along with another issue that they found frustrating or that needs attention. Panelists then voted on a few issues brought up in the introductions, and answered a variety of questions from the audience. Our notes from the meeting are below for all those interested."
},
{
"objectID": "posts/pyviz_scipy_bof_2019/index.html#panelist-introductions",
"href": "posts/pyviz_scipy_bof_2019/index.html#panelist-introductions",
"title": "PyViz at SciPy 2019",
"section": "Panelist introductions",
"text": "Panelist introductions\nJames A. Bednar (Panel, hvPlot, Datashader, Colorcet, GeoViews) Intro and overview of python viz landscape and overview of the new pyviz.org website, including live status of 60+ Python viz tools. Excited about dashboarding in Python – now a real thing that other languages should be jealous of! Frustrated by interoperability issues, from trying to assemble various libraries to solve big problems.\nThomas Caswell (Matplotlib/PyQtGraph) Diversity in PyViz libraries shows wide usage across domains; diversity is a feature, not a bug. No perfect solution for every domain. PyQtgraph - great for high speed desktop but please don’t try to do web dashboarding with it. Matplotlib is mostly in maintenance and housekeeping mode at the moment, but starting to think of what Matplotlib 4 should look like. PyQtgraph being revived after being dormant for a while; moving to py3 only release.\nJon Mease (Plotly) Independent contractor, speaking for the Python interface. Excited about V4 of Plotly, new themes, having it run in more places, integration of seaborn-style high-level API. Frustrated by the fact that the choice of library is often dictated by what interface you use—cmd line, Spyder, Jupyter, etc.\nMadicken Munk (yt) yt is trying to expand to other non-astronomy domains. Having a major release in the next year. Switching to external unit conversion system, from Nathan Goldbaum. Creating a domain context system to make it easier to integrate with new domains. Frustrating is that it is difficult to separate out domain-specific stuff. yt naming still very astro-specific, even though the functionality is largely domain agnostic. yt maintains Jupyter widget library built on rust compiled to WebAssembly.\nJosef Heinen (GR) GR focuses on speed and transparency, used in Matplotlib but language agnostic, which is useful for scientists working with multiple languages. Can be integrated into QT, GTK, etc. Currently transpiling software into JS which will allow browser use and will enable matplotlib browser web backend. He personally prefers Julia.\nJean-Luc Stevens (HoloViews) HoloViews is a layer/API on top of other libraries (Matplotlib, Bokeh, Plotly). Focuses on exploratory work. Working on maturing, polishing, and further documenting the system, which is now used as a lower-level base for other libraries like hvPlot. Frustration - fragmented, quickly moving ecosystem makes integration difficult.\nDavid Hoese (VisPy) Python wrapper around OpenGL. High level interface. Excited about improved number of contributors to VisPy. Frustrations 1. Platform support for OpenGL – Apple has dropped OS X support! 2. Backwards compatibility concerns makes it difficult to maintain VisPy – new features are hard to support without breaking support for older standards.\nFilipe Fernandes (Folium) Folium is widely used, but is not a healthy project. Only use folium if you are already on it. Will be discontinued in 2-3 years. For new projects use alternatives, e.g. ipyleaflet.\nThomas Robitaille (Glue) Glue provides multidimensional analysis. Uses other packages for viz. Excited about Jupyter ecosystem to dashboards to desktop apps. Frustrated by the number of viz packages; not sure which to use and which to contribute missing functionality to. It’s, especially difficult when they all have such different governance models; it’s not always clear which ones you can have impact on, so it is difficult to invest in substantial efforts.\nMatthew McCormick (VTK) Will update pyviz.org soon with more information. 2D/3D spatial viz. New version vtkjs supports WebGL. Provides volume rendering in the browser, now with Jupyter widgets. Would like to see - lots of progress in packaging, but need dashboarding tools and Qt and be able to create single-file applications using things like pyinstaller.\nMartin Renou (ipywidgets) Pushing widget libraries outside of Python into C++. Also Voila library for dashboarding. Open issue if you are unable to convert notebook to dashboard.\nJulia Signell (Bokeh) Exciting: very stable now, much nicer than it was a few years ago. Please try again if you previously found rough edges. Bokeh is not supported by only one company, with widely spread developers, NumFocus support, and a completely open model. Go to discourse.bokeh.org if you want to get involved.\nBrian Granger (Jupyter/Altair) (based on Vega/Vegaliite). Excited about the impact seen of having a declarative viz grammar. Key lesson learned: start with data model and not the API. Enables building bindings for different languages, and offers lessons for other viz libs. Frustrating - packaging, not just Python and C, but JS is also involved. Really challenging issues.\nJake Vanderplas (pdVega, Altair) Excited about the ongoing efforts in pandas to expand its plotting API to target backends beyond matplotlib (pandas#14130)."
},
{
"objectID": "posts/pyviz_scipy_bof_2019/index.html#votes",
"href": "posts/pyviz_scipy_bof_2019/index.html#votes",
"title": "PyViz at SciPy 2019",
"section": "Votes",
"text": "Votes\nVote: Raise your hand if your project is truly ready and willing to accept substantial community contributions. (All voted yes!)\nVote: Are there too many viz libraries? (2-5 voted yes, depending on caveats)"
},
{
"objectID": "posts/pyviz_scipy_bof_2019/index.html#audience-questions",
"href": "posts/pyviz_scipy_bof_2019/index.html#audience-questions",
"title": "PyViz at SciPy 2019",
"section": "Audience Questions:",
"text": "Audience Questions:\n1. I work a lot in web dev; what is the state of PyViz libraries ability to make viz accessible?\nFor dashboarding libraries like Voila and Panel, some aspects are currently only solved at the JS/HTML level, by using a responsive template that supports mobile devices, larger fonts for low vision, etc. Most of those issues have not been taken on by the Python packages directly. Even when using such a template, it is up to the users to use good colors, etc., though Colorcet and Viscm offer good colorblind-safe colormaps that can help. Textual summary of graphical representations is an open area. Guides to making viz accessible would be an excellent addition to PyViz.org. Suggestion from Marinna Martini: www.w3.org/WAI.\n2. What are good Python options for displaying real-time data from sensors?\nPyQtGraph was designed for this use case, providing high frame rates for many sensors. VisPy is also great for this, with examples in the repo for how to do this using various choices of backend. Bokeh Spectrogram example is good, though not quite as high performance as native GUI systems. GR is also an option, with examples in documentation. HoloViews has a streaming data guide and integrates with Streamz lib for easy plotting of streaming data sources, using Datashader when needed for large datasets. VTK based tools for images/point sets have fairly good support for real-time usage. Plotly.py with Dash and Matplotlib also have ways to do this.\n3. What support is available for Dask and CuPy data structures?\nVisPy has to convert to Numpy first. hvPlot works with Dask arrays and dataframes directly.\n4. Is there support for CMYK-safe colormaps, so that figures are perceptually uniform when printed, e.g. on conference posters?\nNot that anyone on the panel is aware.\n5. Is there anyone fighting against the emerging consensus around tidy dataframes as input structure, which is annoying in practice after investing in well-structured multi-indexes?\nhvPlot supports wide data formats, though not currently multi-indexes directly. Altair assumes a tidy dataframe. Altair only covers a small subset of viz space, with a complex SQL-like pipeline, and hence needs a constrained data format. Altair may need helper tools to convert to tidy formats. hvPlot is a good example of this approach; it’s a high-level wrapper that works well with wide (non-tidy) data formats, converting it to the tidy format expected by HoloViews.\n6.Will there be changes to backend for alternate display formats, etc. in Matplotlib?\nShort answer: yes. Long answer: still in planning stages. Better export paths are needed that are more semantic that can go to Bokeh and Altair etc. We need many more libraries that are wrappers on the main libraries. Building those helper libraries needs to be easy and simple to spin up."
},
{
"objectID": "posts/tweak-mpl-chat/index.html",
"href": "posts/tweak-mpl-chat/index.html",
"title": "Build an AI Chatbot to Run Code and Tweak plots",
"section": "",
"text": "Have you wasted hours tweaking a plot for a presentation or academic paper, like searching StackOverflow on how to change the font size of the labels? The future is now; let LLMs improve your plots for you!\nIn this blog post, we will build an AI chatbot with Panel and Mixtral 8x7b that will help you generate code and execute code to tweak a Matplotlib plot. It has two functionalities:"
},
{
"objectID": "posts/tweak-mpl-chat/index.html#step-0-import-packages",
"href": "posts/tweak-mpl-chat/index.html#step-0-import-packages",
"title": "Build an AI Chatbot to Run Code and Tweak plots",
"section": "Step 0: Import packages",
"text": "Step 0: Import packages\nNow let’s move on to the actual code. Make sure you install the required packages panel and mistralai in your Python environment and import the needed packages:\nimport re\nimport os\nimport panel as pn\nfrom mistralai.async_client import MistralAsyncClient\nfrom mistralai.models.chat_completion import ChatMessage\nfrom panel.io.mime_render import exec_with_return\n\npn.extension(\"codeeditor\", sizing_mode=\"stretch_width\")"
},
{
"objectID": "posts/tweak-mpl-chat/index.html#step-1-define-default-behaviors",
"href": "posts/tweak-mpl-chat/index.html#step-1-define-default-behaviors",
"title": "Build an AI Chatbot to Run Code and Tweak plots",
"section": "Step 1: Define default behaviors",
"text": "Step 1: Define default behaviors\nHere is the code for this step, we can define the following:\n\nThe LLM model we would like to use: LLM_MODEL=\"mistral-small\"\nThe system message:\n\nYou are a renowned data visualization expert\nwith a strong background in matplotlib.\nYour primary goal is to assist the user\nin edit the code based on user request\nusing best practices. Simply provide code \nin code fences (```python). You must have `fig`\nas the last line of code\n\nThe format of user content where we combine the user message the the current Python code.\nThe default Matplotlib plot that users see when they interact with the chatbot.\n\nFeel free to change any of these default settings according to your own use cases."
},
{
"objectID": "posts/tweak-mpl-chat/index.html#step-2-define-the-callback-function",
"href": "posts/tweak-mpl-chat/index.html#step-2-define-the-callback-function",
"title": "Build an AI Chatbot to Run Code and Tweak plots",
"section": "Step 2: Define the callback function",
"text": "Step 2: Define the callback function\nThis function defines how our chatbot responds to user messages. This code looks a little more complex than our examples in previous blog posts because the AI need to respond not only the text, but also the code. - We keep all the message history as a list in messages - When users send a message, we combine both the text of the message and the current state of the code from the code_editor widget (see Step 3) as add to the messages list. - We send all these messages to the Mistral model. - Then we extract Python code from the model output and update the Python code in code_editor.\nclient = MistralAsyncClient(api_key=os.environ[\"MISTRAL_API_KEY\"])\n\nasync def callback(content: str, user: str, instance: pn.chat.ChatInterface):\n # system\n messages = [SYSTEM_MESSAGE]\n\n # history\n messages.extend([ChatMessage(**message) for message in instance.serialize()[1:-1]])\n\n # new user contents\n user_content = USER_CONTENT_FORMAT.format(\n content=content, code=code_editor.value\n )\n messages.append(ChatMessage(role=\"user\", content=user_content))\n\n # stream LLM tokens\n message = \"\"\n async for chunk in client.chat_stream(model=LLM_MODEL, messages=messages):\n if chunk.choices[0].delta.content is not None:\n message += chunk.choices[0].delta.content\n yield message\n\n # extract code\n llm_code = re.findall(r\"```python\\n(.*)\\n```\", message, re.DOTALL)[0]\n if llm_code.splitlines()[-1].strip() != \"fig\":\n llm_code += \"\\nfig\"\n code_editor.value = llm_code"
},
{
"objectID": "posts/tweak-mpl-chat/index.html#step-3-define-widgets",
"href": "posts/tweak-mpl-chat/index.html#step-3-define-widgets",
"title": "Build an AI Chatbot to Run Code and Tweak plots",
"section": "Step 3: Define widgets",
"text": "Step 3: Define widgets\n\nChatInterface: Panel provides a built-in ChatInterface widget that provides a user-friendly front-end chatbot interface for various kinds of messages.callback points to the function that we defined in the last step. It executes when a user sends a message.\n\nchat_interface = pn.chat.ChatInterface(\n callback=callback,\n show_clear=False,\n show_undo=False,\n show_button_name=False,\n message_params=dict(\n show_reaction_icons=False,\n show_copy_icon=False,\n ),\n height=700,\n callback_exception=\"verbose\",\n)\n\nmatplotlib_pane is a Panel object that shows the Matplotlib plot from the Python code. How does execute Python code and return and return the plot? The secret is the exec_with_return function, which will executes a code snippet and returns the resulting output. By default, matplotlib_pane executes the default Matplotlib code we defined in Step 1.\n\nmatplotlib_pane = pn.pane.Matplotlib(\n exec_with_return(DEFAULT_MATPLOTLIB),\n sizing_mode=\"stretch_both\",\n tight=True,\n)\n\n\n\n\n\n\ncode_editor is another Panel object that allows embedding a code editor.\n\ncode_editor = pn.widgets.CodeEditor(\n value=DEFAULT_MATPLOTLIB,\n sizing_mode=\"stretch_both\",\n)\n\n\n\n\n\n\nHow does the plot get updated?\nWhenever the code changes, the plot gets updates. Specifically, the matplotlib_pane watches for the code changes in code_editor using the param.watch method.\n# watch for code changes\ndef update_plot(event):\n matplotlib_pane.object = exec_with_return(event.new)\ncode_editor.param.watch(update_plot, \"value\")\nSo when does the code get updated?\n\nWhenever the AI assistant outputs Python code, this Python code will become the new value of code_editor. This is defined in the callback function in Step 2.\nWhenever we change code directly in the code_editor, the code will change and the plot will update automatically."
},
{
"objectID": "posts/tweak-mpl-chat/index.html#step-4-define-layout",
"href": "posts/tweak-mpl-chat/index.html#step-4-define-layout",
"title": "Build an AI Chatbot to Run Code and Tweak plots",
"section": "Step 4: Define layout",
"text": "Step 4: Define layout\nFinally we can define how we’d like each widget to place in our app.\n# lay them out\ntabs = pn.Tabs(\n (\"Plot\", matplotlib_pane),\n (\"Code\", code_editor),\n)\n\nsidebar = [chat_interface]\nmain = [tabs]\ntemplate = pn.template.FastListTemplate(\n sidebar=sidebar,\n main=main,\n sidebar_width=600,\n main_layout=None,\n accent_base_color=\"#fd7000\",\n header_background=\"#fd7000\",\n)\ntemplate.servable()\nThen run panel serve app.py to launch a server using CLI and interact with this app."
},
{
"objectID": "posts/fleet_ai/index.html",
"href": "posts/fleet_ai/index.html",
"title": "Build a RAG chatbot to answer questions about Python libraries",
"section": "",
"text": "Interested in asking questions about Python’s latest and greatest libraries? This is the chatbot for you! Fleet Context offers 4M+ high-quality custom embeddings of the top 1000+ Python libraries, while Panel can provide a Chat Interface UI to build a Retrieval-Augmented Generation (RAG) chatbot with Fleet Context.\nWhy is this chatbot useful? It’s because most language models are not trained on the most up-to-date Python package docs and thus do not have information about the recent Python libraries like llamaindex, LangChain, etc. To be able to answer questions about these libraries, we can retrieve relevant information from Python library docs and generate valid and improved responses based on retrieved information.\nRun the app: https://huggingface.co/spaces/ahuang11/panel-fleet\nCode: https://huggingface.co/spaces/ahuang11/panel-fleet/tree/main"
},
{
"objectID": "posts/fleet_ai/index.html#command-line-interface",
"href": "posts/fleet_ai/index.html#command-line-interface",
"title": "Build a RAG chatbot to answer questions about Python libraries",
"section": "1. Command line interface",
"text": "1. Command line interface\nOnce we define the OpenAI environment variable export OPENAI_API_KEY=xxx, we can run context in the command line and start ask questions about Python libraries. For example, here I asked “what is HoloViz Panel?”. What I really like about Fleet is that it provides references for us to check."
},
{
"objectID": "posts/fleet_ai/index.html#python-console",
"href": "posts/fleet_ai/index.html#python-console",
"title": "Build a RAG chatbot to answer questions about Python libraries",
"section": "2. Python console",
"text": "2. Python console\nWe can query embeddings directly from the provided hosted vector database with the query method from the context library. When we ask a question “What is HoloViz Panel?”, it returned defined number (k=2) of related text chunks from the Panel docs.\nNote that the returned results include many metadata such as library_id, page_id, parent, section_id, title, text, type, etc., which are available for us to use and query."
},
{
"objectID": "posts/fleet_ai/index.html#import-packages",
"href": "posts/fleet_ai/index.html#import-packages",
"title": "Build a RAG chatbot to answer questions about Python libraries",
"section": "0. Import packages",
"text": "0. Import packages\nBefore we get started, let’s make sure we install the needed packages and import the packages:\nfrom context import query\nfrom openai import AsyncOpenAI\nimport panel as pn\npn.extension()"
},
{
"objectID": "posts/fleet_ai/index.html#define-the-system-prompt",
"href": "posts/fleet_ai/index.html#define-the-system-prompt",
"title": "Build a RAG chatbot to answer questions about Python libraries",
"section": "1. Define the system prompt",
"text": "1. Define the system prompt\nFull credit to the Fleet Context team, we took this system prompt and tweaked it a bit from their code:\n# taken from fleet context\nSYSTEM_PROMPT = \"\"\"\nYou are an expert in Python libraries. You carefully provide accurate, factual, thoughtful, nuanced answers, and are brilliant at reasoning. If you think there might not be a correct answer, you say so.\nEach token you produce is another opportunity to use computation, therefore you always spend a few sentences explaining background context, assumptions, and step-by-step thinking BEFORE you try to answer a question.\nYour users are experts in AI and ethics, so they already know you're a language model and your capabilities and limitations, so don't remind them of that. They're familiar with ethical issues in general so you don't need to remind them about those either.\nYour users are also in a CLI environment. You are capable of writing and running code. DO NOT write hypothetical code. ALWAYS write real code that will execute and run end-to-end.\nInstructions:\n- Be objective, direct. Include literal information from the context, don't add any conclusion or subjective information.\n- When writing code, ALWAYS have some sort of output (like a print statement). If you're writing a function, call it at the end. Do not generate the output, because the user can run it themselves.\n- ALWAYS cite your sources. Context will be given to you after the text ### Context source_url ### with source_url being the url to the file. For example, ### Context https://example.com/docs/api.html#files ### will have a source_url of https://example.com/docs/api.html#files.\n- When you cite your source, please cite it as [num] with `num` starting at 1 and incrementing with each source cited (1, 2, 3, ...). At the bottom, have a newline-separated `num: source_url` at the end of the response. ALWAYS add a new line between sources or else the user won't be able to read it. DO NOT convert links into markdown, EVER! If you do that, the user will not be able to click on the links.\nFor example:\n**Context 1**: https://example.com/docs/api.html#pdfs\nI'm a big fan of PDFs.\n**Context 2**: https://example.com/docs/api.html#csvs\nI'm a big fan of CSVs.\n### Prompt ###\nWhat is this person a big fan of?\n### Response ###\nThis person is a big fan of PDFs[1] and CSVs[2].\n1: https://example.com/docs/api.html#pdfs\n2: https://example.com/docs/api.html#csvs\n\"\"\""
},
{
"objectID": "posts/fleet_ai/index.html#define-chat-interface",
"href": "posts/fleet_ai/index.html#define-chat-interface",
"title": "Build a RAG chatbot to answer questions about Python libraries",
"section": "2. Define chat interface",
"text": "2. Define chat interface\nThe key component of defining a Panel chat interface is pn.chat.ChatInterface. Specifically, in the callback method, we need to define how the chat bot responds – the answer function.\nIn this function, we: - Initialize the system prompt - Used the Fleet Context query method to query k=3 relevant text chunks for our given question - We format the retrieved text chunks, URLs, and user message into the required OpenAI message format - We provide the message history into an OpenAI model. - Then we stream the responses asynchronously from OpenAI.\nasync def answer(contents, user, instance):\n # start with system prompt\n messages = [{\"role\": \"system\", \"content\": SYSTEM_PROMPT}]\n\n # add context to the user input\n context = \"\"\n fleet_responses = query(contents, k=3)\n for i, response in enumerate(fleet_responses):\n context += (\n f\"\\n\\n**Context {i}**: {response['metadata']['url']}\\n\"\n f\"{response['metadata']['text']}\"\n )\n instance.send(context, avatar=\"🛩️\", user=\"Fleet Context\", respond=False)\n\n # get history of messages (skipping the intro message)\n # and serialize fleet context messages as \"user\" role\n messages.extend(\n instance.serialize(role_names={\"user\": [\"user\", \"Fleet Context\"]})[1:]\n )\n\n openai_response = await client.chat.completions.create(\n model=MODEL, messages=messages, temperature=0.2, stream=True\n )\n\n message = \"\"\n async for chunk in openai_response:\n token = chunk.choices[0].delta.content\n if token:\n message += token\n yield message\n\n\nclient = AsyncOpenAI()\nintro_message = pn.chat.ChatMessage(\"Ask me anything about Python libraries!\", user=\"System\")\nchat_interface = pn.chat.ChatInterface(intro_message, callback=answer, callback_user=\"OpenAI\")"
},
{
"objectID": "posts/fleet_ai/index.html#format-everything-in-a-template",
"href": "posts/fleet_ai/index.html#format-everything-in-a-template",
"title": "Build a RAG chatbot to answer questions about Python libraries",
"section": "3. Format everything in a template",
"text": "3. Format everything in a template\nFinally we format everything in a template and run panel serve app.py in the command line to get the final app:\ntemplate = pn.template.FastListTemplate(\n main=[chat_interface], \n title=\"Panel UI of Fleet Context 🛩️\"\n)\ntemplate.servable()\n\n\n\nDemo of the Python Library Document RAG Chatbot\n\n\n\nNow, you should have a working AI chatbot that can answer questions about Python libraries. If you would like to add more complex RAG features. LlamaIndex has incorporated it into its system. Here is a guide if you would like to experiment Fleet Context with LlamaIndex: Fleet Context Embeddings - Building a Hybrid Search Engine for the Llamaindex Library."
},
{
"objectID": "posts/holoviews_streams/index.html",
"href": "posts/holoviews_streams/index.html",
"title": "HoloViews Streams for Exploring Multidimensional Data",
"section": "",
"text": "Follow along to build an app that uses a 4D dataset (level, time, lat, lon) and explore it by"
},
{
"objectID": "posts/holoviews_streams/index.html#basics",
"href": "posts/holoviews_streams/index.html#basics",
"title": "HoloViews Streams for Exploring Multidimensional Data",
"section": "Basics",
"text": "Basics\n\nImport the necessary libraries\nMost of the time, using Python is just knowing what’s out there and importing it!\n\nimport param\nimport numpy as np\nimport xarray as xr\nimport panel as pn\nimport hvplot.xarray\nimport geoviews as gv\nimport holoviews as hv\nfrom geoviews.streams import PolyDraw\nfrom metpy.interpolate import cross_section\nimport cartopy.crs as ccrs\n\npn.extension()\ngv.extension(\"bokeh\")\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nGetting something working\nBelow I show three ways to download a piece of the NCEP Reanalysis dataset from NOAA.\nIt’s one of my favorite datasets for testing and writing examples because it’s so straightforward to use: - no API key required, which means no need to sign up, verify email, etc. - can be small or large, if 4X daily, concatenated across times, variables, etc - is multi-dimensional (time, level, lat, lon)\nBelow are three variations of downloading a dataset. Note, 1 only works in notebooks; 2 and 3 work in both notebooks and scripts.\nSince I usually work in a Jupyter notebook, I like to use 1 due to its simplicity–just a ! + wget + copied url and an optional --no-clobber, -nc flag.\n\n# 1.\n!wget -nc https://downloads.psl.noaa.gov/Datasets/ncep.reanalysis/Dailies/pressure/air.2024.nc\n\n# 2.\n# import subprocess\n# subprocess.run(\"wget https://downloads.psl.noaa.gov/Datasets/ncep.reanalysis/Dailies/pressure/air.2024.nc\", shell=True)\n\n# 3.\n# import requests\n# with requests.get(\"https://downloads.psl.noaa.gov/Datasets/ncep.reanalysis/Dailies/pressure/air.2024.nc\") as response:\n# response.raise_for_status()\n# with open(\"air.2024.nc\", \"wb\") as f:\n# f.write(response.content)\n\nFile ‘air.2024.nc’ already there; not retrieving.\n\n\n\nThe hardest part for any projects is getting started (something about static friction > kinetic friction).\nHowever, once you get started, things get easier, so what I usually do is take baby steps and get something shown up front immediately.\nFortunately, XArray + hvPlot makes it possible!\n\nds = xr.open_dataset(\"air.2024.nc\", drop_variables=[\"time_bnds\"])\n\nds\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<xarray.Dataset>\nDimensions: (level: 17, lat: 73, lon: 144, time: 80)\nCoordinates:\n * level (level) float32 1e+03 925.0 850.0 700.0 ... 50.0 30.0 20.0 10.0\n * lat (lat) float32 90.0 87.5 85.0 82.5 80.0 ... -82.5 -85.0 -87.5 -90.0\n * lon (lon) float32 0.0 2.5 5.0 7.5 10.0 ... 350.0 352.5 355.0 357.5\n * time (time) datetime64[ns] 2024-01-01 2024-01-02 ... 2024-03-20\nData variables:\n air (time, level, lat, lon) float32 ...\nAttributes:\n Conventions: COARDS\n title: mean daily NMC reanalysis (2014)\n history: created 2013/12 by Hoop (netCDF2.3)\n description: Data is from NMC initialized reanalysis\\n(4x/day). It co...\n platform: Model\n dataset_title: NCEP-NCAR Reanalysis 1\n References: http://www.psl.noaa.gov/data/gridded/data.ncep.reanalysis...xarray.DatasetDimensions:level: 17lat: 73lon: 144time: 80Coordinates: (4)level(level)float321e+03 925.0 850.0 ... 20.0 10.0units :millibaractual_range :[1000. 10.]long_name :Levelpositive :downGRIB_id :100GRIB_name :hPaaxis :Zarray([1000., 925., 850., 700., 600., 500., 400., 300., 250., 200.,\n 150., 100., 70., 50., 30., 20., 10.], dtype=float32)lat(lat)float3290.0 87.5 85.0 ... -87.5 -90.0units :degrees_northactual_range :[ 90. -90.]long_name :Latitudestandard_name :latitudeaxis :Yarray([ 90. , 87.5, 85. , 82.5, 80. , 77.5, 75. , 72.5, 70. , 67.5,\n 65. , 62.5, 60. , 57.5, 55. , 52.5, 50. , 47.5, 45. , 42.5,\n 40. , 37.5, 35. , 32.5, 30. , 27.5, 25. , 22.5, 20. , 17.5,\n 15. , 12.5, 10. , 7.5, 5. , 2.5, 0. , -2.5, -5. , -7.5,\n -10. , -12.5, -15. , -17.5, -20. , -22.5, -25. , -27.5, -30. , -32.5,\n -35. , -37.5, -40. , -42.5, -45. , -47.5, -50. , -52.5, -55. , -57.5,\n -60. , -62.5, -65. , -67.5, -70. , -72.5, -75. , -77.5, -80. , -82.5,\n -85. , -87.5, -90. ], dtype=float32)lon(lon)float320.0 2.5 5.0 ... 352.5 355.0 357.5units :degrees_eastlong_name :Longitudeactual_range :[ 0. 357.5]standard_name :longitudeaxis :Xarray([ 0. , 2.5, 5. , 7.5, 10. , 12.5, 15. , 17.5, 20. , 22.5,\n 25. , 27.5, 30. , 32.5, 35. , 37.5, 40. , 42.5, 45. , 47.5,\n 50. , 52.5, 55. , 57.5, 60. , 62.5, 65. , 67.5, 70. , 72.5,\n 75. , 77.5, 80. , 82.5, 85. , 87.5, 90. , 92.5, 95. , 97.5,\n 100. , 102.5, 105. , 107.5, 110. , 112.5, 115. , 117.5, 120. , 122.5,\n 125. , 127.5, 130. , 132.5, 135. , 137.5, 140. , 142.5, 145. , 147.5,\n 150. , 152.5, 155. , 157.5, 160. , 162.5, 165. , 167.5, 170. , 172.5,\n 175. , 177.5, 180. , 182.5, 185. , 187.5, 190. , 192.5, 195. , 197.5,\n 200. , 202.5, 205. , 207.5, 210. , 212.5, 215. , 217.5, 220. , 222.5,\n 225. , 227.5, 230. , 232.5, 235. , 237.5, 240. , 242.5, 245. , 247.5,\n 250. , 252.5, 255. , 257.5, 260. , 262.5, 265. , 267.5, 270. , 272.5,\n 275. , 277.5, 280. , 282.5, 285. , 287.5, 290. , 292.5, 295. , 297.5,\n 300. , 302.5, 305. , 307.5, 310. , 312.5, 315. , 317.5, 320. , 322.5,\n 325. , 327.5, 330. , 332.5, 335. , 337.5, 340. , 342.5, 345. , 347.5,\n 350. , 352.5, 355. , 357.5], dtype=float32)time(time)datetime64[ns]2024-01-01 ... 2024-03-20long_name :Timedelta_t :0000-00-01 00:00:00standard_name :timeaxis :Tavg_period :0000-00-01 00:00:00coordinate_defines :startactual_range :[1963536. 1965432.]array(['2024-01-01T00:00:00.000000000', '2024-01-02T00:00:00.000000000',\n '2024-01-03T00:00:00.000000000', '2024-01-04T00:00:00.000000000',\n '2024-01-05T00:00:00.000000000', '2024-01-06T00:00:00.000000000',\n '2024-01-07T00:00:00.000000000', '2024-01-08T00:00:00.000000000',\n '2024-01-09T00:00:00.000000000', '2024-01-10T00:00:00.000000000',\n '2024-01-11T00:00:00.000000000', '2024-01-12T00:00:00.000000000',\n '2024-01-13T00:00:00.000000000', '2024-01-14T00:00:00.000000000',\n '2024-01-15T00:00:00.000000000', '2024-01-16T00:00:00.000000000',\n '2024-01-17T00:00:00.000000000', '2024-01-18T00:00:00.000000000',\n '2024-01-19T00:00:00.000000000', '2024-01-20T00:00:00.000000000',\n '2024-01-21T00:00:00.000000000', '2024-01-22T00:00:00.000000000',\n '2024-01-23T00:00:00.000000000', '2024-01-24T00:00:00.000000000',\n '2024-01-25T00:00:00.000000000', '2024-01-26T00:00:00.000000000',\n '2024-01-27T00:00:00.000000000', '2024-01-28T00:00:00.000000000',\n '2024-01-29T00:00:00.000000000', '2024-01-30T00:00:00.000000000',\n '2024-01-31T00:00:00.000000000', '2024-02-01T00:00:00.000000000',\n '2024-02-02T00:00:00.000000000', '2024-02-03T00:00:00.000000000',\n '2024-02-04T00:00:00.000000000', '2024-02-05T00:00:00.000000000',\n '2024-02-06T00:00:00.000000000', '2024-02-07T00:00:00.000000000',\n '2024-02-08T00:00:00.000000000', '2024-02-09T00:00:00.000000000',\n '2024-02-10T00:00:00.000000000', '2024-02-11T00:00:00.000000000',\n '2024-02-12T00:00:00.000000000', '2024-02-13T00:00:00.000000000',\n '2024-02-14T00:00:00.000000000', '2024-02-15T00:00:00.000000000',\n '2024-02-16T00:00:00.000000000', '2024-02-17T00:00:00.000000000',\n '2024-02-18T00:00:00.000000000', '2024-02-19T00:00:00.000000000',\n '2024-02-20T00:00:00.000000000', '2024-02-21T00:00:00.000000000',\n '2024-02-22T00:00:00.000000000', '2024-02-23T00:00:00.000000000',\n '2024-02-24T00:00:00.000000000', '2024-02-25T00:00:00.000000000',\n '2024-02-26T00:00:00.000000000', '2024-02-27T00:00:00.000000000',\n '2024-02-28T00:00:00.000000000', '2024-02-29T00:00:00.000000000',\n '2024-03-01T00:00:00.000000000', '2024-03-02T00:00:00.000000000',\n '2024-03-03T00:00:00.000000000', '2024-03-04T00:00:00.000000000',\n '2024-03-05T00:00:00.000000000', '2024-03-06T00:00:00.000000000',\n '2024-03-07T00:00:00.000000000', '2024-03-08T00:00:00.000000000',\n '2024-03-09T00:00:00.000000000', '2024-03-10T00:00:00.000000000',\n '2024-03-11T00:00:00.000000000', '2024-03-12T00:00:00.000000000',\n '2024-03-13T00:00:00.000000000', '2024-03-14T00:00:00.000000000',\n '2024-03-15T00:00:00.000000000', '2024-03-16T00:00:00.000000000',\n '2024-03-17T00:00:00.000000000', '2024-03-18T00:00:00.000000000',\n '2024-03-19T00:00:00.000000000', '2024-03-20T00:00:00.000000000'],\n dtype='datetime64[ns]')Data variables: (1)air(time, level, lat, lon)float32...long_name :mean Daily Air temperatureunits :degKprecision :2GRIB_id :11GRIB_name :TMPvar_desc :Air temperaturelevel_desc :Pressure Levelsstatistic :Meanparent_stat :Individual Obsvalid_range :[150. 350.]dataset :NCEP Reanalysis Daily Averagesactual_range :[177.2 316.52496][14296320 values with dtype=float32]Indexes: (4)levelPandasIndexPandasIndex(Index([1000.0, 925.0, 850.0, 700.0, 600.0, 500.0, 400.0, 300.0, 250.0,\n 200.0, 150.0, 100.0, 70.0, 50.0, 30.0, 20.0, 10.0],\n dtype='float32', name='level'))latPandasIndexPandasIndex(Index([ 90.0, 87.5, 85.0, 82.5, 80.0, 77.5, 75.0, 72.5, 70.0, 67.5,\n 65.0, 62.5, 60.0, 57.5, 55.0, 52.5, 50.0, 47.5, 45.0, 42.5,\n 40.0, 37.5, 35.0, 32.5, 30.0, 27.5, 25.0, 22.5, 20.0, 17.5,\n 15.0, 12.5, 10.0, 7.5, 5.0, 2.5, 0.0, -2.5, -5.0, -7.5,\n -10.0, -12.5, -15.0, -17.5, -20.0, -22.5, -25.0, -27.5, -30.0, -32.5,\n -35.0, -37.5, -40.0, -42.5, -45.0, -47.5, -50.0, -52.5, -55.0, -57.5,\n -60.0, -62.5, -65.0, -67.5, -70.0, -72.5, -75.0, -77.5, -80.0, -82.5,\n -85.0, -87.5, -90.0],\n dtype='float32', name='lat'))lonPandasIndexPandasIndex(Index([ 0.0, 2.5, 5.0, 7.5, 10.0, 12.5, 15.0, 17.5, 20.0, 22.5,\n ...\n 335.0, 337.5, 340.0, 342.5, 345.0, 347.5, 350.0, 352.5, 355.0, 357.5],\n dtype='float32', name='lon', length=144))timePandasIndexPandasIndex(DatetimeIndex(['2024-01-01', '2024-01-02', '2024-01-03', '2024-01-04',\n '2024-01-05', '2024-01-06', '2024-01-07', '2024-01-08',\n '2024-01-09', '2024-01-10', '2024-01-11', '2024-01-12',\n '2024-01-13', '2024-01-14', '2024-01-15', '2024-01-16',\n '2024-01-17', '2024-01-18', '2024-01-19', '2024-01-20',\n '2024-01-21', '2024-01-22', '2024-01-23', '2024-01-24',\n '2024-01-25', '2024-01-26', '2024-01-27', '2024-01-28',\n '2024-01-29', '2024-01-30', '2024-01-31', '2024-02-01',\n '2024-02-02', '2024-02-03', '2024-02-04', '2024-02-05',\n '2024-02-06', '2024-02-07', '2024-02-08', '2024-02-09',\n '2024-02-10', '2024-02-11', '2024-02-12', '2024-02-13',\n '2024-02-14', '2024-02-15', '2024-02-16', '2024-02-17',\n '2024-02-18', '2024-02-19', '2024-02-20', '2024-02-21',\n '2024-02-22', '2024-02-23', '2024-02-24', '2024-02-25',\n '2024-02-26', '2024-02-27', '2024-02-28', '2024-02-29',\n '2024-03-01', '2024-03-02', '2024-03-03', '2024-03-04',\n '2024-03-05', '2024-03-06', '2024-03-07', '2024-03-08',\n '2024-03-09', '2024-03-10', '2024-03-11', '2024-03-12',\n '2024-03-13', '2024-03-14', '2024-03-15', '2024-03-16',\n '2024-03-17', '2024-03-18', '2024-03-19', '2024-03-20'],\n dtype='datetime64[ns]', name='time', freq=None))Attributes: (7)Conventions :COARDStitle :mean daily NMC reanalysis (2014)history :created 2013/12 by Hoop (netCDF2.3)description :Data is from NMC initialized reanalysis\n(4x/day). It consists of most variables interpolated to\npressure surfaces from model (sigma) surfaces.platform :Modeldataset_title :NCEP-NCAR Reanalysis 1References :http://www.psl.noaa.gov/data/gridded/data.ncep.reanalysis.html\n\n\n\nbase_map = ds.hvplot(\"lon\", \"lat\")\nbase_map\n\n\n\n\n\n\nCustomizing\nAdd keywords such as coastline, cmap, and framewise=False (for consistent colorbar) to the call for a much more polished plot!\nFor better compatibility, I convert longitudes from 0:360 to -180:180 and sort–many packages just work better that way.\n\n# for interactivity purposes on the blog, limit the number of times and levels\nds_sel = ds.isel(time=slice(0, 3), level=slice(0, 8))\nds_sel[\"lon\"] = (ds_sel[\"lon\"] + 180) % 360 - 180\nds_sel = ds_sel.sortby(\"lon\")\n\nmap_plot = ds_sel.hvplot(\n \"lon\",\n \"lat\",\n coastline=True,\n cmap=\"RdYlBu_r\",\n clabel=\"Air Temperature [K]\",\n framewise=False,\n dynamic=False,\n)\nmap_plot\n\n\n\n\n\n \n\n\n\n\n\n\nFixed latitude cross section\nWe can easily show a static, vertical cross section of the dataset too!\n\nds_cs = ds_sel.sel(lat=50) # cross section across 50°N\n\n# cs -> cross section\ncs_plot = ds_cs.hvplot(\n \"lon\",\n \"level\",\n cmap=\"RdYlBu_r\",\n clabel=\"Air Temperature [K]\",\n flip_yaxis=True,\n framewise=False,\n dynamic=False,\n)\n\ncs_plot\n\n/Users/ahuang/miniconda3/envs/panel/lib/python3.10/site-packages/holoviews/core/data/xarray.py:340: UserWarning: The `squeeze` kwarg to GroupBy is being removed.Pass .groupby(..., squeeze=False) to disable squeezing, which is the new default, and to silence this warning.\n for k, v in dataset.data.groupby(index_dims[0].name):\nWARNING:param.Image10741: Image dimension level is not evenly sampled to relative tolerance of 0.001. Please use the QuadMesh element for irregularly sampled data or set a higher tolerance on hv.config.image_rtol or the rtol parameter in the Image constructor.\nWARNING:param.Image10741: Image dimension level is not evenly sampled to relative tolerance of 0.001. Please use the QuadMesh element for irregularly sampled data or set a higher tolerance on hv.config.image_rtol or the rtol parameter in the Image constructor.\nWARNING:param.Image10779: Image dimension level is not evenly sampled to relative tolerance of 0.001. Please use the QuadMesh element for irregularly sampled data or set a higher tolerance on hv.config.image_rtol or the rtol parameter in the Image constructor.\nWARNING:param.Image10779: Image dimension level is not evenly sampled to relative tolerance of 0.001. Please use the QuadMesh element for irregularly sampled data or set a higher tolerance on hv.config.image_rtol or the rtol parameter in the Image constructor.\nWARNING:param.Image10817: Image dimension level is not evenly sampled to relative tolerance of 0.001. Please use the QuadMesh element for irregularly sampled data or set a higher tolerance on hv.config.image_rtol or the rtol parameter in the Image constructor.\nWARNING:param.Image10817: Image dimension level is not evenly sampled to relative tolerance of 0.001. Please use the QuadMesh element for irregularly sampled data or set a higher tolerance on hv.config.image_rtol or the rtol parameter in the Image constructor.\n\n\n\n\n\n\n \n\n\n\n\n\n\nDiagonal cross section\nThis is only a cross section across a fixed latitude; what if we wanted a cross section across a diagonal?\nWe can use MetPy’s cross_section function to interpolate the data along any line!\nIt’s crucial to note that the start and end keywords follow latitude-longitude (y, x) pair, NOT (x, y)!\n\nds_sel = ds_sel.metpy.parse_cf() # so it contains proper metadata for metpy to recognize\n\nds_cs = cross_section(ds_sel.isel(time=0), start=(50, -130), end=(50, -50))\nds_cs\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<xarray.Dataset>\nDimensions: (level: 8, index: 100)\nCoordinates:\n * level (level) float32 1e+03 925.0 850.0 700.0 600.0 500.0 400.0 300.0\n time datetime64[ns] 2024-01-01\n metpy_crs object Projection: latitude_longitude\n lon (index) float64 -130.0 -129.4 -128.7 ... -51.3 -50.65 -50.0\n lat (index) float64 50.0 50.27 50.53 50.79 ... 50.79 50.53 50.27 50.0\n * index (index) int64 0 1 2 3 4 5 6 7 8 9 ... 91 92 93 94 95 96 97 98 99\nData variables:\n air (level, index) float64 281.1 280.8 280.5 ... 225.2 225.4 225.7\nAttributes:\n Conventions: COARDS\n title: mean daily NMC reanalysis (2014)\n history: created 2013/12 by Hoop (netCDF2.3)\n description: Data is from NMC initialized reanalysis\\n(4x/day). It co...\n platform: Model\n dataset_title: NCEP-NCAR Reanalysis 1\n References: http://www.psl.noaa.gov/data/gridded/data.ncep.reanalysis...xarray.DatasetDimensions:level: 8index: 100Coordinates: (6)level(level)float321e+03 925.0 850.0 ... 400.0 300.0units :millibaractual_range :[1000. 10.]long_name :Levelpositive :downGRIB_id :100GRIB_name :hPaaxis :Z_metpy_axis :verticalarray([1000., 925., 850., 700., 600., 500., 400., 300.], dtype=float32)time()datetime64[ns]2024-01-01long_name :Timedelta_t :0000-00-01 00:00:00standard_name :timeaxis :Tavg_period :0000-00-01 00:00:00coordinate_defines :startactual_range :[1963536. 1965432.]_metpy_axis :timearray('2024-01-01T00:00:00.000000000', dtype='datetime64[ns]')metpy_crs()objectProjection: latitude_longitudearray(<metpy.plots.mapping.CFProjection object at 0x29f604970>,\n dtype=object)lon(index)float64-130.0 -129.4 ... -50.65 -50.0_metpy_axis :x,longitudearray([-130. , -129.351248, -128.695292, -128.032086, -127.361592,\n -126.683773, -125.998601, -125.30605 , -124.606102, -123.898745,\n -123.183973, -122.461787, -121.732197, -120.995218, -120.250873,\n -119.499196, -118.740227, -117.974015, -117.200619, -116.420107,\n -115.632558, -114.838059, -114.036707, -113.228612, -112.413892,\n -111.592677, -110.765106, -109.931333, -109.091518, -108.245835,\n -107.394468, -106.537612, -105.675472, -104.808265, -103.936217,\n -103.059565, -102.178555, -101.293442, -100.404492, -99.511978,\n -98.61618 , -97.717388, -96.815898, -95.912011, -95.006036,\n -94.098285, -93.189075, -92.278726, -91.367562, -90.455909,\n -89.544091, -88.632438, -87.721274, -86.810925, -85.901715,\n -84.993964, -84.087989, -83.184102, -82.282612, -81.38382 ,\n -80.488022, -79.595508, -78.706558, -77.821445, -76.940435,\n -76.063783, -75.191735, -74.324528, -73.462388, -72.605532,\n -71.754165, -70.908482, -70.068667, -69.234894, -68.407323,\n -67.586108, -66.771388, -65.963293, -65.161941, -64.367442,\n -63.579893, -62.799381, -62.025985, -61.259773, -60.500804,\n -59.749127, -59.004782, -58.267803, -57.538213, -56.816027,\n -56.101255, -55.393898, -54.69395 , -54.001399, -53.316227,\n -52.638408, -51.967914, -51.304708, -50.648752, -50. ])lat(index)float6450.0 50.27 50.53 ... 50.27 50.0units :degrees_northactual_range :[ 90. -90.]long_name :Latitudestandard_name :latitudeaxis :Y_metpy_axis :y,latitudearray([50. , 50.265546, 50.527419, 50.785545, 51.039847, 51.290249,\n 51.536674, 51.779045, 52.017283, 52.251309, 52.481044, 52.706406,\n 52.927317, 53.143694, 53.355457, 53.562525, 53.764816, 53.96225 ,\n 54.154746, 54.342222, 54.5246 , 54.701799, 54.873741, 55.040346,\n 55.201539, 55.357244, 55.507384, 55.651888, 55.790683, 55.923699,\n 56.050868, 56.172123, 56.2874 , 56.396638, 56.499777, 56.596761,\n 56.687534, 56.772047, 56.850251, 56.922102, 56.987556, 57.046577,\n 57.099129, 57.145181, 57.184707, 57.217681, 57.244084, 57.263901,\n 57.277118, 57.283729, 57.283729, 57.277118, 57.263901, 57.244084,\n 57.217681, 57.184707, 57.145181, 57.099129, 57.046577, 56.987556,\n 56.922102, 56.850251, 56.772047, 56.687534, 56.596761, 56.499777,\n 56.396638, 56.2874 , 56.172123, 56.050868, 55.923699, 55.790683,\n 55.651888, 55.507384, 55.357244, 55.201539, 55.040346, 54.873741,\n 54.701799, 54.5246 , 54.342222, 54.154746, 53.96225 , 53.764816,\n 53.562525, 53.355457, 53.143694, 52.927317, 52.706406, 52.481044,\n 52.251309, 52.017283, 51.779045, 51.536674, 51.290249, 51.039847,\n 50.785545, 50.527419, 50.265546, 50. ])index(index)int640 1 2 3 4 5 6 ... 94 95 96 97 98 99array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,\n 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,\n 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53,\n 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71,\n 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89,\n 90, 91, 92, 93, 94, 95, 96, 97, 98, 99])Data variables: (1)air(level, index)float64281.1 280.8 280.5 ... 225.4 225.7long_name :mean Daily Air temperatureunits :degKprecision :2GRIB_id :11GRIB_name :TMPvar_desc :Air temperaturelevel_desc :Pressure Levelsstatistic :Meanparent_stat :Individual Obsvalid_range :[150. 350.]dataset :NCEP Reanalysis Daily Averagesactual_range :[177.2 316.52496]array([[281.09997559, 280.76368769, 280.45708445, 280.18001488,\n 279.92457186, 279.66897535, 279.4432314 , 279.24705874,\n 279.1026052 , 278.99049885, 278.8890329 , 278.53752648,\n 278.21761199, 277.86324677, 277.475166 , 277.10850135,\n 276.73981715, 276.34316267, 275.95026938, 275.58729498,\n 275.20912721, 274.8342246 , 274.53270269, 274.26925149,\n 274.1177599 , 274.09114056, 274.11417166, 274.18790955,\n 274.34082436, 274.53863933, 274.76976125, 274.9679607 ,\n 275.19255603, 275.39273183, 275.43323969, 275.48083168,\n 275.42385311, 275.16859959, 274.90441341, 274.44634984,\n 273.81966965, 273.17851963, 272.32861886, 271.40317403,\n 270.46713435, 269.44720815, 268.4226989 , 267.43395472,\n 266.56309104, 265.69699705, 264.99147203, 264.44770917,\n 263.91229286, 263.65724747, 263.49747162, 263.34734035,\n 263.44185191, 263.54462225, 263.66976489, 263.84940542,\n 264.04116025, 264.17790005, 264.24720727, 264.3315709 ,\n 264.23685665, 264.04528804, 263.8685412 , 263.47953678,\n 263.02759741, 262.57462565, 262.00225298, 261.39037597,\n 260.75233771, 260.20017068, 259.59973066, 258.93662619,\n 258.6162301 , 258.27618664, 257.94369214, 258.10151435,\n...\n 220.35413252, 220.44009887, 220.60529945, 220.80155964,\n 221.05597032, 221.31881072, 221.5903184 , 221.86369459,\n 222.07076988, 222.2833265 , 222.48612577, 222.58546997,\n 222.68656906, 222.76915975, 222.781198 , 222.79376623,\n 222.78120947, 222.72421114, 222.66762066, 222.58824944,\n 222.48964914, 222.39116211, 222.27927327, 222.16344389,\n 222.04820375, 221.90873689, 221.76963523, 221.62212846,\n 221.44703718, 221.27355286, 221.09237004, 220.90366218,\n 220.71708126, 220.54850203, 220.38616891, 220.22512836,\n 220.15189339, 220.07541237, 220.02827945, 220.07754574,\n 220.11345151, 220.21374406, 220.39025654, 220.54392091,\n 220.7779465 , 221.04121986, 221.27005798, 221.58287227,\n 221.89564483, 222.17295905, 222.46951649, 222.74494707,\n 222.99090821, 223.23492649, 223.4748167 , 223.7093426 ,\n 223.87815131, 224.09605569, 224.32771623, 224.46335932,\n 224.591225 , 224.73813728, 224.79490047, 224.81806807,\n 224.88133099, 224.9253183 , 224.89764013, 224.92222066,\n 224.99996023, 224.96708267, 224.88958545, 224.84637986,\n 224.80732101, 224.77151335, 224.78130192, 224.83704081,\n 224.96595264, 225.15171911, 225.38800341, 225.67500305]])Indexes: (2)levelPandasIndexPandasIndex(Index([1000.0, 925.0, 850.0, 700.0, 600.0, 500.0, 400.0, 300.0], dtype='float32', name='level'))indexPandasIndexPandasIndex(Index([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,\n 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,\n 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53,\n 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71,\n 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89,\n 90, 91, 92, 93, 94, 95, 96, 97, 98, 99],\n dtype='int64', name='index'))Attributes: (7)Conventions :COARDStitle :mean daily NMC reanalysis (2014)history :created 2013/12 by Hoop (netCDF2.3)description :Data is from NMC initialized reanalysis\n(4x/day). It consists of most variables interpolated to\npressure surfaces from model (sigma) surfaces.platform :Modeldataset_title :NCEP-NCAR Reanalysis 1References :http://www.psl.noaa.gov/data/gridded/data.ncep.reanalysis.html\n\n\nSince the x dimension is now index, we need to also properly format the xticks labels with lat and lon coordinates.\n\nxticks = [\n (i, f\"({abs(lon):.0f}°W, {lat:.0f}°N)\") # format the xticks\n for i, (lat, lon) in enumerate(zip(ds_cs[\"lat\"], ds_cs[\"lon\"]))\n]\n\nds_cs.hvplot(\n \"index\",\n \"level\",\n cmap=\"RdYlBu_r\",\n xticks=xticks[::15],\n xlabel=\"Coordinates\",\n clabel=\"Air Temperature [K]\",\n flip_yaxis=True,\n framewise=False,\n dynamic=False,\n)\n\nWARNING:param.Image11026: Image dimension level is not evenly sampled to relative tolerance of 0.001. Please use the QuadMesh element for irregularly sampled data or set a higher tolerance on hv.config.image_rtol or the rtol parameter in the Image constructor.\nWARNING:param.Image11026: Image dimension level is not evenly sampled to relative tolerance of 0.001. Please use the QuadMesh element for irregularly sampled data or set a higher tolerance on hv.config.image_rtol or the rtol parameter in the Image constructor.\n\n\n\n\n\n\n \n\n\n\n\n\n\nJoined together\nFinally, we can lay both plots by “adding” them.\n\n(map_plot + cs_plot).cols(1)"
},
{
"objectID": "posts/holoviews_streams/index.html#checkpoint-1",
"href": "posts/holoviews_streams/index.html#checkpoint-1",
"title": "HoloViews Streams for Exploring Multidimensional Data",
"section": "Checkpoint 1",
"text": "Checkpoint 1\nHere’s a cleaned up, copy/pastable version of the code thus far!\nimport subprocess\nfrom pathlib import Path\n\nimport param\nimport numpy as np\nimport xarray as xr\nimport panel as pn\nimport hvplot.xarray\nimport geoviews as gv\nimport holoviews as hv\nfrom geoviews.streams import PolyDraw\nfrom metpy.interpolate import cross_section\nimport cartopy.crs as ccrs\n\npn.extension()\ngv.extension(\"bokeh\")\n\nif not Path(\"air.2024.nc\").exists():\n subprocess.run(\"wget https://downloads.psl.noaa.gov/Datasets/ncep.reanalysis/Dailies/pressure/air.2024.nc\", shell=True)\n\n# process data\nds = xr.open_dataset(\"air.2024.nc\", drop_variables=[\"time_bnds\"])\nds_sel = ds.isel(time=slice(0, 3), level=slice(0, 10)).metpy.parse_cf()\nds_sel[\"lon\"] = (ds_sel[\"lon\"] + 180) % 360 - 180\nds_sel = ds_sel.sortby(\"lon\")\nds_cs = cross_section(ds_sel.isel(time=0), start=(50, -130), end=(50, -50))\n\n# visualize data\nmap_plot = ds_sel.hvplot(\n \"lon\",\n \"lat\",\n coastline=True,\n cmap=\"RdYlBu_r\",\n clabel=\"Air Temperature [K]\",\n framewise=False,\n dynamic=False,\n)\n\nxticks = [\n (i, f\"({abs(lon):.0f}°W, {lat:.0f}°N)\")\n for i, (lat, lon) in enumerate(zip(ds_cs[\"lat\"], ds_cs[\"lon\"]))\n]\ncs_plot = ds_cs.hvplot(\n \"index\",\n \"level\",\n xticks=xticks[::15],\n xlabel=\"Coordinates\",\n cmap=\"RdYlBu_r\",\n clabel=\"Air Temperature [K]\",\n flip_yaxis=True,\n framewise=False,\n dynamic=False,\n)\n\n(map_plot + cs_plot).cols(1)"
},
{
"objectID": "posts/holoviews_streams/index.html#working-with-streams",
"href": "posts/holoviews_streams/index.html#working-with-streams",
"title": "HoloViews Streams for Exploring Multidimensional Data",
"section": "Working with streams",
"text": "Working with streams\nNow that we have a foundation, we can attach a stream to the plo to allow users to interact with the plot.\nTo see what streams are available, I check out the HoloViews Reference Gallery.\nSince I want to draw a line across the map to eventually show a cross section, I chose PolyDraw.\n\n\nMinimal example\nTo start using:\n\nclick on the PolygonDraw tool in the toolbar\ndouble tap on the plot to start drawing a polygon\nsingle tap on each vertex of the polygon\ndouble tap on the last vertex to finish drawing\n\n\ncs_path = gv.Path(([-80, -50, -30], [28, 48, 18]), crs=ccrs.PlateCarree())\nstream = PolyDraw(source=cs_path, num_objects=1)\n\ncs_path\n\n\n\n\nWe can access the data from the drawn path using the stream.data attribute.\n\nstream.data\n\n{'xs': [array([-80. , -53.67907524, -50. , -35.41679382,\n -30. ])],\n 'ys': [array([28. , 45.54728317, 48. , 26.12519073, 18. ])]}\n\n\nLet’s make something happen when we draw a path on the map by using a DynamicMap.\nThe DynamicMap will mirror the vertexes of the drawn data.\n\nimport geoviews as gv\n\ndef copy_and_shift_up(data):\n # error handling; return empty points if there's no data or there are no valid edges\n if not data or not data[\"xs\"] or data[\"xs\"][0][0] == data[\"xs\"][0][1]:\n return gv.Points({\"Longitude\": [], \"Latitude\": []})\n\n xs = data[\"xs\"][0] # 0 to select first edge\n ys = data[\"ys\"][0]\n return gv.Points({\"Longitude\": xs, \"Latitude\": ys}).opts(color=\"red\")\n\n\ncs_path = gv.Path(([-80, -50, -30], [28, 48, 18]), crs=ccrs.PlateCarree()).opts(active_tools=[\"poly_draw\"])\nstream = PolyDraw(source=cs_path, num_objects=1)\n\ncs_path_shifted = gv.DynamicMap(copy_and_shift_up, streams=[stream])\ncs_path + cs_path_shifted\n\n\n\n\n\nWe can see that the right plot reacts to changes to the drawn path on the left plot.\n\n\nInteractive cross section\nNow, let’s take a step back to get back to the original goal, which is we want to create a cross section plot based on the path drawn on the map.\nWe can do this by:\n\nLinking the cross section path (cs_path) to the map by overlaying and laying out the map alongside the cross section plot.\nWrapping the cross section computation and plot inside a DynamicMap so that changes to the cs_path data changes triggers an update to the cross section.\nUsing a for loop for the cross section computation to handle multiple edges / segments drawn.\n\nSince the data returned from cs_path ranges from -180 to 180, we’ll need to match that in our dataaset too.\n\ndef create_cross_section(data):\n if not data or not data[\"xs\"] or data[\"xs\"][0][0] == data[\"xs\"][0][1]:\n return hv.Image([]).opts(width=730, colorbar=True)\n\n xs = data[\"xs\"][0]\n ys = data[\"ys\"][0]\n ds_cs_list = []\n for i in range(len(xs) - 1): # create cross section for each segment\n ds_cs_list.append(\n cross_section(\n ds_sel.isel(time=0),\n start=(ys[0 + i], xs[0 + i]),\n end=(ys[1 + i], xs[1 + i]),\n )\n )\n ds_cs = xr.concat(ds_cs_list, dim=\"index\")\n\n xticks = [\n (i, f\"({abs(lon):.0f}°W, {lat:.0f}°N)\")\n for i, (lat, lon) in enumerate(zip(ds_cs[\"lat\"], ds_cs[\"lon\"]))\n ]\n cs_plot = ds_cs.hvplot(\n \"index\",\n \"level\",\n xticks=xticks[::15],\n xlabel=\"Coordinates\",\n cmap=\"RdYlBu_r\",\n flip_yaxis=True,\n dynamic=False,\n )\n return cs_plot\n\n# create stream\ncs_path = gv.Path([], crs=ccrs.PlateCarree()).opts(color=\"red\", line_width=2)\nstream = PolyDraw(source=cs_path, num_objects=1)\n\n# attach stream\ncs_plot = gv.DynamicMap(create_cross_section, streams=[stream])\n\n# layout\nmap_overlay = (map_plot * cs_path).opts(active_tools=[\"poly_draw\"])\n(map_overlay + cs_plot).cols(1)\n\n\n\n\nWARNING:param.Image41637: Image dimension level is not evenly sampled to relative tolerance of 0.001. Please use the QuadMesh element for irregularly sampled data or set a higher tolerance on hv.config.image_rtol or the rtol parameter in the Image constructor.\n\n\n\n\n\ncross_section\n\n\n\n\nSyncing time slider across plots\nSince the time slider only affects the first plot, we’ll need to convert the HoloMap overlay into a pn.pane.HoloViews object to extract the time slider.\nWe can then easily extract the widget from the map_plot and use it with the cs_plot!\n\nmap_pane = pn.pane.HoloViews(map_overlay)\n\nCall widget box to get the time slider.\n\ntime_slider = map_pane.widget_box[0]\ntime_slider\n\n\n\n\nWe change:\n\nour callback slightly to include the time slider’s param value (very important to use .param.value instead of .value or else it won’t update!)\nuse sel(time=value) instead of isel(time=0).\n\n\ndef create_cross_section(data, value): # new kwarg\n if not data or not data[\"xs\"] or data[\"xs\"][0][0] == data[\"xs\"][0][1]:\n return hv.Image([]).opts(width=730, clabel=\"Air Temperature [K]\", colorbar=True)\n\n xs = data[\"xs\"][0]\n ys = data[\"ys\"][0]\n\n ds_cs_list = []\n for i in range(len(xs) - 1):\n ds_cs_list.append(\n cross_section(\n ds_sel.sel(time=value),\n start=(ys[0 + i], xs[0 + i]),\n end=(ys[1 + i], xs[1 + i]),\n )\n )\n ds_cs = xr.concat(ds_cs_list, dim=\"index\")\n ds_cs[\"index\"] = np.arange(len(ds_cs[\"index\"]))\n\n xticks = [\n (i, f\"({abs(lon):.0f}°W, {lat:.0f}°N)\")\n for i, (lat, lon) in enumerate(zip(ds_cs[\"lat\"], ds_cs[\"lon\"]))\n ]\n cs_plot = ds_cs.hvplot(\n \"index\",\n \"level\",\n xticks=xticks[::15],\n xlabel=\"Coordinates\",\n cmap=\"RdYlBu_r\",\n clabel=\"Air Temperature [K]\",\n flip_yaxis=True,\n dynamic=False,\n )\n return cs_plot\n\ncs_plot = gv.DynamicMap(create_cross_section, streams=[stream, time_slider.param.value]) # new stream\n\nNow, let’s put everything together!\nWe need to use pn.Column instead of adding here because map_overlay is no longer a HoloViews object.\n\npn.Row(pn.Column(map_pane, cs_plot), map_pane.widget_box)"
},
{
"objectID": "posts/holoviews_streams/index.html#checkpoint-2",
"href": "posts/holoviews_streams/index.html#checkpoint-2",
"title": "HoloViews Streams for Exploring Multidimensional Data",
"section": "Checkpoint 2",
"text": "Checkpoint 2\nHere’s the copy pastable code for the second checkpoint:\nimport subprocess\nfrom pathlib import Path\n\nimport param\nimport numpy as np\nimport xarray as xr\nimport panel as pn\nimport hvplot.xarray\nimport geoviews as gv\nimport holoviews as hv\nfrom geoviews.streams import PolyDraw\nfrom metpy.interpolate import cross_section\nimport cartopy.crs as ccrs\n\npn.extension()\ngv.extension(\"bokeh\")\n\ndef create_cross_section(data, value):\n if not data or not data[\"xs\"] or data[\"xs\"][0][0] == data[\"xs\"][0][1]:\n return hv.Image([]).opts(width=730, clabel=\"Air Temperature [K]\", colorbar=True)\n\n xs = data[\"xs\"][0]\n ys = data[\"ys\"][0]\n\n ds_cs_list = []\n for i in range(len(xs) - 1):\n ds_cs_list.append(\n cross_section(\n ds_sel,\n start=(ys[0 + i], xs[0 + i]),\n end=(ys[1 + i], xs[1 + i]),\n )\n )\n ds_cs = xr.concat(ds_cs_list, dim=\"index\")\n ds_cs[\"index\"] = np.arange(len(ds_cs[\"index\"]))\n\n xticks = [\n (i, f\"({abs(lon):.0f}°W, {lat:.0f}°N)\")\n for i, (lat, lon) in enumerate(zip(ds_cs[\"lat\"], ds_cs[\"lon\"]))\n ]\n cs_plot = ds_cs.hvplot(\n \"index\",\n \"level\",\n xticks=xticks[::15],\n xlabel=\"Coordinates\",\n cmap=\"RdYlBu_r\",\n clabel=\"Air Temperature [K]\",\n flip_yaxis=True,\n dynamic=False,\n )\n return cs_plot\n\nif not Path(\"air.2024.nc\").exists():\n subprocess.run(\"wget https://downloads.psl.noaa.gov/Datasets/ncep.reanalysis/Dailies/pressure/air.2024.nc\", shell=True)\n\n# process data\nds = xr.open_dataset(\"air.2024.nc\", drop_variables=[\"time_bnds\"])\nds_sel = ds.isel(time=slice(0, 3), level=slice(0, 10)).metpy.parse_cf()\nds_sel[\"lon\"] = (ds_sel[\"lon\"] + 180) % 360 - 180\nds_sel = ds_sel.sortby(\"lon\")\n\n# create base map\nmap_plot = ds_sel.hvplot(\n \"lon\",\n \"lat\",\n coastline=True,\n cmap=\"RdYlBu_r\",\n clabel=\"Air Temperature [K]\",\n framewise=False,\n dynamic=False,\n)\n\n# create stream\ncs_path = gv.Path([], crs=ccrs.PlateCarree()).opts(color=\"red\", line_width=2)\nstream = PolyDraw(source=cs_path, num_objects=1)\n\n# overlay\nmap_overlay = (map_plot * cs_path).opts(active_tools=[\"poly_draw\"])\nmap_pane = pn.pane.HoloViews(map_overlay)\n\n# attach stream\ntime_slider = map_pane.widget_box[0]\ncs_plot = gv.DynamicMap(create_cross_section, streams=[stream, time_slider.param.value])\n\npn.Row(pn.Column(map_pane, cs_plot), map_pane.widget_box)"
},
{
"objectID": "posts/holoviews_streams/index.html#encapsulating-into-param-class",
"href": "posts/holoviews_streams/index.html#encapsulating-into-param-class",
"title": "HoloViews Streams for Exploring Multidimensional Data",
"section": "Encapsulating into param class",
"text": "Encapsulating into param class\nNow, as you may notice, things are getting a tad complex and out of hand.\nFor the finale, I’ll demonstrate how to convert this into an extensible pn.viewable.Viewer class.\nThe main things I changed was:\n\nhvPlot -> HoloViews\nCreating a class to watch time and label\nManually creating DynamicMaps for each plot and writing their own custom callbacks\nMove streams to @param.depends\n\n\nimport param\nimport numpy as np\nimport xarray as xr\nimport panel as pn\nimport hvplot.xarray\nimport geoviews as gv\nimport holoviews as hv\nfrom geoviews.streams import PolyDraw\nfrom metpy.interpolate import cross_section\nimport cartopy.crs as ccrs\n\npn.extension()\ngv.extension(\"bokeh\")\n\n\nclass DataExplorer(pn.viewable.Viewer):\n\n ds = param.ClassSelector(class_=xr.Dataset)\n\n time = param.Selector()\n\n level = param.Selector()\n\n def __init__(self, ds: xr.Dataset, **params):\n super().__init__(**params)\n self.ds = ds\n\n # populate selectors\n self.param[\"time\"].objects = list(\n ds[\"time\"].dt.strftime(\"%Y-%m-%d %H:%M\").values\n )\n self.param[\"level\"].objects = list(ds[\"level\"].values)\n\n self.time = self.param[\"time\"].objects[0]\n self.level = self.param[\"level\"].objects[0]\n\n @param.depends(\"time\", \"level\")\n def _update_map(self):\n ds_sel = self.ds.sel(time=self.time, level=self.level)\n return gv.Image(\n ds_sel,\n kdims=[\"lon\", \"lat\"],\n vdims=[\"air\"],\n ).opts(\n cmap=\"RdYlBu_r\",\n clabel=\"Air Temperature [K]\",\n responsive=True,\n xaxis=None,\n yaxis=None,\n )\n\n @param.depends(\"_stream.data\", \"time\")\n def _update_cross_section(self):\n data = self._stream.data\n if not data or not data[\"xs\"]:\n data[\"xs\"] = [[-80, -80]]\n data[\"ys\"] = [[18, 28]]\n\n ds_sel = self.ds.sel(time=self.time)\n ds_sel = ds_sel.metpy.parse_cf()\n\n xs = data[\"xs\"][0]\n ys = data[\"ys\"][0]\n\n ds_cs_list = []\n for i in range(len(xs) - 1):\n ds_cs_list.append(\n cross_section(\n ds_sel,\n start=(ys[0 + i], xs[0 + i]),\n end=(ys[1 + i], xs[1 + i]),\n )\n )\n ds_cs = xr.concat(ds_cs_list, dim=\"index\")\n ds_cs[\"index\"] = np.arange(len(ds_cs[\"index\"]))\n\n xticks = [\n (i, f\"({lon:.0f}°E, {lat:.0f}°N)\")\n for i, (lat, lon) in enumerate(zip(ds_cs[\"lat\"], ds_cs[\"lon\"]))\n ]\n x_indices = np.linspace(0, len(xticks) - 1, 10).astype(int)\n xticks = [xticks[i] for i in x_indices]\n cs_plot = hv.Image(ds_cs, kdims=[\"index\", \"level\"], vdims=[\"air\"]).opts(\n xticks=xticks,\n xlabel=\"Coordinates\",\n cmap=\"RdYlBu_r\",\n clabel=\"Air Temperature [K]\",\n invert_yaxis=True,\n responsive=True,\n xrotation=45,\n )\n return cs_plot\n\n def __panel__(self):\n # create widgets\n time_slider = pn.widgets.DiscreteSlider.from_param(self.param[\"time\"])\n level_slider = pn.widgets.DiscreteSlider.from_param(self.param[\"level\"])\n\n # create plots\n self._cs_path = gv.Path([], crs=ccrs.PlateCarree()).opts(\n color=\"red\", line_width=2\n )\n self._stream = PolyDraw(source=self._cs_path, num_objects=1)\n\n map_plot = gv.DynamicMap(self._update_map)\n coastline = gv.feature.coastline()\n map_overlay = (map_plot * self._cs_path * coastline).opts(\n active_tools=[\"poly_draw\"]\n )\n\n self._cs_plot = gv.DynamicMap(self._update_cross_section).opts(framewise=False)\n\n sidebar = pn.Column(time_slider, level_slider)\n main = pn.Row(map_overlay, self._cs_plot, sizing_mode=\"stretch_both\")\n return pn.template.FastListTemplate(\n sidebar=[sidebar],\n main=[main],\n ).show()\n\n\nds = xr.open_dataset(\"air.2024.nc\", drop_variables=[\"time_bnds\"])\nDataExplorer(ds)\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\nLaunching server at http://localhost:58591\n\n\n\n\n\n\n\n\ntemplate\n\n\nNow, you could extend this easily and cleanly by adding new methods, like if you added a points stream:\n @param.depends(\"_point_stream.data\", \"level\")\n def _update_cross_section_timeseries(self):\n ...\nOur Discourse community is here for you!"
},
{
"objectID": "posts/hv_release_1.10/index.html",
"href": "posts/hv_release_1.10/index.html",
"title": "HoloViews 1.10 Release",
"section": "",
"text": "We are very pleased to announce the release of HoloViews 1.10!\nThis release contains a large number of features and improvements. Some highlights include:\nJupyterLab support:\nNew components:\nPlus many other bug fixes, enhancements and documentation improvements. For full details, see the Release Notes.\nIf you are using Anaconda, HoloViews can most easily be installed by executing the command conda install -c pyviz holoviews . Otherwise, use pip install holoviews."
},
{
"objectID": "posts/hv_release_1.10/index.html#jupyterlab-support",
"href": "posts/hv_release_1.10/index.html#jupyterlab-support",
"title": "HoloViews 1.10 Release",
"section": "JupyterLab support",
"text": "JupyterLab support\nWith JupyterLab now coming out of the alpha release stage, we have finally made HoloViews compatible with JupyterLab by creating the jupyterlab_pyviz extension. The extension can be installed with:\njupyter labextension install @pyviz/jupyterlab_pyviz\n\nThe JupyterLab extension provides all the interactivity of the classic notebook, and so both interfaces are now fully supported. Both classic notebook and JupyterLab now make it easier to work with streaming plots, because deleting or re-executing a cell in the classic notebook or JupyterLab now cleans up the plot and ensures that any streams are unsubscribed."
},
{
"objectID": "posts/hv_release_1.10/index.html#new-elements",
"href": "posts/hv_release_1.10/index.html#new-elements",
"title": "HoloViews 1.10 Release",
"section": "New elements",
"text": "New elements\nThe main improvement in this release is the addition of a large number of elements. A number of these elements build on the Graph element introduced earlier in the 1.9 release, including the Sankey, Chord and TriMesh elements. Other new elements include HexTiles for binning many points on a hexagonal grid, Violins for comparing distributions across multiple variables, Labels for plotting large collections of text labels, and Div for displaying arbitrary HTML alongside Bokeh-based plots and tables.\n\nSankey\nThe new Sankey element is a pure-Python port of d3-sankey. Like most other elements, it can be rendered using both Matplotlib and Bokeh. In Bokeh, all the usual interactivity will be supported, such as providing hover information and interactively highlighting connected nodes and edges. Here we have rendered energy flow to SVG with matplotlib:\n\n\n\nChord\nThe Chord element had been requested a number of times, because it had previously been supported in the now deprecated Bokeh Charts package. Thanks to Bokeh’s graph support, hovering and tapping on the Chord nodes highlights connected nodes, helping you make sense of even densely interconnected graphs:\n\n\n\n\n\n\n\n\n\n\nTriMesh\nAlso building on the graph capabilities is the TriMesh element, which allows defining arbitrary meshes from a set of nodes and a set of simplices (triangles defined as lists of node indexes). The TriMesh element allows easily visualizing Delaunay triangulations and even very large meshes, thanks to corresponding support added to datashader. Below we can see an example of a TriMesh colored by vertex value and an interpolated datashaded mesh of the Chesapeake Bay containing 1M triangles:\n\n\n\n\n\n\n\n\n\n\n\n\nHexTiles\nAnother often requested feature is the addition of a hexagonal bin plot, which can be very helpful in visualizing large collections of points. Thanks to the recent addition of a hex tiling glyph in the bokeh 0.12.15 release it was straightforward to add this support in the form of a [HexTiles element]((http://holoviews.org/reference/elements/bokeh/HexTiles.html), which supports both simple bin counts and weighted binning, and fixed or variable hex sizes.\nBelow we can see a HexTiles plot of ~7 million points representing the NYC population, where each hexagonal bin is scaled and colored by the bin value:\n\n\n\n\n\n\n\n\n\n\nViolin\nViolin elements have been one of the most frequently requested plot types since the Matplotlib-only Seaborn interface was deprecated from HoloViews. With this release a native implementation of violins was added for both Matplotlib and Bokeh, which allows comparing distributions across one or more independent variables:\n\n\n\nRadial HeatMap\nThanks to the contributions of Franz Woellert, the existing HeatMap element has now gained support for radial heatmaps. Radial heatmaps are useful for plotting quantities varying over some cyclic variable, such as the day of the week or time of day. Below we can see how the daily number of Taxi rides changes over the course of a year:\n\n\n\nLabels\nThe existing Text element allows adding text to a plot, but only one item at a time, which is not suitable for plotting the large collections of text items that many users have been requesting. The new Labels element provides vectorized text plotting, which is probably most often used to annotate data points or regions of another plot type. Here we show that it can also be used on its own, to plot unicode emoji characters arranged by semantic similarity using the t-SNE dimensionality reduction algorithm:\n\n\n\nDiv\nThe Div element is exclusive to Bokeh and allows embedding arbitrary HTML in a Bokeh plot. One simple example of the infinite variety of possible uses for Div is to display Pandas summary tables alongside a plot:\n\nbars + hv.Div(df.describe().to_html())"
},
{
"objectID": "posts/hv_release_1.10/index.html#editing-tools",
"href": "posts/hv_release_1.10/index.html#editing-tools",
"title": "HoloViews 1.10 Release",
"section": "Editing Tools",
"text": "Editing Tools\nIn the Bokeh 0.12.15 release, a new set of interactive tools were added to edit and draw different glyph types. These tools are now available from HoloViews as the PointDraw, PolyDraw, BoxEdit, and PolyEdit streams classes, which make the drawn or edited data available to work with from Python. The drawing tools open up the possibility for very complex interactivity and annotations, allowing users to create even very complex types of interactive applications.\n\n \n\nOne example of the many workflows now supported is to draw regions of interest on an image using BoxEdit, computing the mean value over time for each such region:"
},
{
"objectID": "posts/hv_release_1.10/index.html#setting-options",
"href": "posts/hv_release_1.10/index.html#setting-options",
"title": "HoloViews 1.10 Release",
"section": "Setting options",
"text": "Setting options\nThe new .options() method present on all viewable objects makes it much simpler to set options without worrying about the underlying difference between plot, style, and norm options. A comparison between the two APIs demonstrates how much more readable and easy to type the new approach is:\n\n# New options API\nimg.options(cmap='RdBu_r', colorbar=True, width=360, height=300)\n\n# Old opts API\nimg.opts(plot=dict(colorbar=True, width=360), style=dict(cmap='RdBu_r'));\n\nEach option still belongs to one of the three categories internally, depending on whether it is processed by HoloViews or passed down into the underlying plotting library, but the user no longer usually has to remember which options are in which category.\nIt is also now possible to explicitly declare the backend for each option, which makes it easier to support multiple backends:\n\nimg.options(width=360, backend='bokeh').options(fig_inches=(6, 6), backend='matplotlib');"
},
{
"objectID": "posts/hv_release_1.10/index.html#image-hover",
"href": "posts/hv_release_1.10/index.html#image-hover",
"title": "HoloViews 1.10 Release",
"section": "Image hover",
"text": "Image hover\nThanks to coming changes in bokeh 0.12.16, HoloViews will finally support hovering over images to reveal the underlying values, e.g. here we can see the NYC census data this time aggregated using datashader:"
},
{
"objectID": "posts/hv_release_1.10/index.html#data-interfaces",
"href": "posts/hv_release_1.10/index.html#data-interfaces",
"title": "HoloViews 1.10 Release",
"section": "Data interfaces",
"text": "Data interfaces\nThe data interfaces that underlie HoloViews’ ability to work natively with a variety of data structures also saw further improvements.\n\nBinned and irregular data\nIt is now possible to declare binned data and irregular data, which has allowed Histogram and QuadMesh to finally support data interfaces. With this change, all Element types are now Dataset classes, with uniform architectures and supported usages.\n\n## Binned data\nn = 20\nx = np.arange(n+1) # Linear bins\ny = np.logspace(0, 2, n+1) # Log bins\nz = x*x[np.newaxis].T\n\n# Irregular data\ncoords = np.linspace(-1.5, 1.5, n)\nX,Y = np.meshgrid(coords, coords)\n\nQx = np.cos(Y) - np.cos(X) # 2D coordinate array\nQz = np.sin(Y) + np.sin(X) # 2D coordinate array\nZ = np.sqrt(X**2 + Y**2)\n\nhv.Histogram((x, y)) + hv.QuadMesh((y, x, z)) + hv.QuadMesh((Qx, Qz, Z))\n\n\n\n\n\n\nDask arrays\nHoloViews previously supported Dask arrays via XArray, but Dask arrays are now also supported directly, allowing operations on large datasets to be performed out-of-core simply by annotating the data with coordinates:\n\nimport dask.array as da\n\nn = 100\ndask_array = da.from_array(np.random.rand(n, n), chunks=10)\nhv.Image((range(n), range(n), dask_array))"
},
{
"objectID": "posts/hv_release_1.10/index.html#documentation-other-improvements",
"href": "posts/hv_release_1.10/index.html#documentation-other-improvements",
"title": "HoloViews 1.10 Release",
"section": "Documentation & other improvements",
"text": "Documentation & other improvements\nA new Colormap user guide provides an overview of the available colormaps and how to effectively choose a colormap to reveal your data. It also introduces the new hv.plotting.list_cmaps function, which makes it easy to query for a list of colormaps satisfying certain criteria (e.g. when providing a choice of appropriate colormaps to a user). For example, here is the output of hv.plotting.list_cmaps(category='Diverging', bg='light', reverse=False) when applied to an image, giving you a large number of alternatives that are all appropriate for this particular type of data:\n\nAn additional new Styling plots user guide provides an in-depth overview of how to control colors, cycles, palettes and cmaps, which are now consistently handled across backends and support new features such as discrete color_levels and symmetric color ranges:\n\nimg.options(color_levels=5, symmetric=True) + img.options(color_levels=11, symmetric=True)"
},
{
"objectID": "posts/hvplot_announcement/index.html",
"href": "posts/hvplot_announcement/index.html",
"title": "hvPlot Announcement",
"section": "",