From c4aa989783addd85b54835390ef4ace58eb7129a Mon Sep 17 00:00:00 2001 From: hustcc Date: Thu, 18 May 2023 14:36:13 +0800 Subject: [PATCH] feat: add heatmap canvas readerer --- __tests__/data/heatmap.json | 2502 +++++++++++++++++ .../snapshots/static/HeatmapHeatmapBasic.png | Bin 0 -> 1289 bytes .../plots/static/heatmap-heatmap-basic.ts | 20 + __tests__/plots/static/index.ts | 1 + src/mark/heatmap.ts | 43 +- src/shape/heatmap/heatmap.ts | 27 +- src/shape/heatmap/renderer/index.ts | 171 ++ src/shape/heatmap/renderer/types.ts | 43 + 8 files changed, 2771 insertions(+), 36 deletions(-) create mode 100644 __tests__/data/heatmap.json create mode 100644 __tests__/integration/snapshots/static/HeatmapHeatmapBasic.png create mode 100644 __tests__/plots/static/heatmap-heatmap-basic.ts create mode 100644 src/shape/heatmap/renderer/index.ts create mode 100644 src/shape/heatmap/renderer/types.ts diff --git a/__tests__/data/heatmap.json b/__tests__/data/heatmap.json new file mode 100644 index 0000000000..f01f362a2e --- /dev/null +++ b/__tests__/data/heatmap.json @@ -0,0 +1,2502 @@ +[ + { + "g": 541, + "l": 85, + "tmp": 858 + }, + { + "g": 937, + "l": 465, + "tmp": 299 + }, + { + "g": 566, + "l": 131, + "tmp": 326 + }, + { + "g": 738, + "l": 130, + "tmp": 262 + }, + { + "g": 85, + "l": 207, + "tmp": 184 + }, + { + "g": 321, + "l": 273, + "tmp": 312 + }, + { + "g": 552, + "l": 153, + "tmp": 367 + }, + { + "g": 698, + "l": 454, + "tmp": 194 + }, + { + "g": 409, + "l": 440, + "tmp": 754 + }, + { + "g": 905, + "l": 84, + "tmp": 891 + }, + { + "g": 186, + "l": 127, + "tmp": 55 + }, + { + "g": 636, + "l": 117, + "tmp": 97 + }, + { + "g": 990, + "l": 29, + "tmp": 549 + }, + { + "g": 284, + "l": 79, + "tmp": 570 + }, + { + "g": 28, + "l": 313, + "tmp": 320 + }, + { + "g": 146, + "l": 204, + "tmp": 206 + }, + { + "g": 257, + "l": 327, + "tmp": 540 + }, + { + "g": 277, + "l": 412, + "tmp": 766 + }, + { + "g": 352, + "l": 343, + "tmp": 944 + }, + { + "g": 875, + "l": 76, + "tmp": 789 + }, + { + "g": 969, + "l": 160, + "tmp": 934 + }, + { + "g": 909, + "l": 153, + "tmp": 779 + }, + { + "g": 227, + "l": 194, + "tmp": 471 + }, + { + "g": 618, + "l": 316, + "tmp": 400 + }, + { + "g": 910, + "l": 98, + "tmp": 289 + }, + { + "g": 718, + "l": 181, + "tmp": 409 + }, + { + "g": 285, + "l": 448, + "tmp": 894 + }, + { + "g": 89, + "l": 303, + "tmp": 567 + }, + { + "g": 900, + "l": 195, + "tmp": 132 + }, + { + "g": 780, + "l": 453, + "tmp": 741 + }, + { + "g": 337, + "l": 243, + "tmp": 715 + }, + { + "g": 156, + "l": 206, + "tmp": 720 + }, + { + "g": 238, + "l": 11, + "tmp": 244 + }, + { + "g": 303, + "l": 174, + "tmp": 320 + }, + { + "g": 607, + "l": 234, + "tmp": 1 + }, + { + "g": 460, + "l": 127, + "tmp": 176 + }, + { + "g": 85, + "l": 435, + "tmp": 548 + }, + { + "g": 938, + "l": 198, + "tmp": 423 + }, + { + "g": 336, + "l": 387, + "tmp": 607 + }, + { + "g": 331, + "l": 1, + "tmp": 535 + }, + { + "g": 320, + "l": 342, + "tmp": 635 + }, + { + "g": 620, + "l": 178, + "tmp": 345 + }, + { + "g": 20, + "l": 475, + "tmp": 561 + }, + { + "g": 878, + "l": 427, + "tmp": 375 + }, + { + "g": 42, + "l": 412, + "tmp": 115 + }, + { + "g": 383, + "l": 12, + "tmp": 422 + }, + { + "g": 821, + "l": 442, + "tmp": 756 + }, + { + "g": 737, + "l": 319, + "tmp": 676 + }, + { + "g": 810, + "l": 180, + "tmp": 834 + }, + { + "g": 45, + "l": 11, + "tmp": 552 + }, + { + "g": 119, + "l": 422, + "tmp": 801 + }, + { + "g": 634, + "l": 198, + "tmp": 816 + }, + { + "g": 980, + "l": 168, + "tmp": 44 + }, + { + "g": 595, + "l": 496, + "tmp": 188 + }, + { + "g": 729, + "l": 100, + "tmp": 88 + }, + { + "g": 635, + "l": 479, + "tmp": 362 + }, + { + "g": 40, + "l": 384, + "tmp": 441 + }, + { + "g": 334, + "l": 238, + "tmp": 231 + }, + { + "g": 351, + "l": 362, + "tmp": 724 + }, + { + "g": 70, + "l": 217, + "tmp": 816 + }, + { + "g": 515, + "l": 245, + "tmp": 567 + }, + { + "g": 842, + "l": 209, + "tmp": 703 + }, + { + "g": 496, + "l": 226, + "tmp": 720 + }, + { + "g": 998, + "l": 52, + "tmp": 863 + }, + { + "g": 43, + "l": 51, + "tmp": 622 + }, + { + "g": 253, + "l": 210, + "tmp": 610 + }, + { + "g": 775, + "l": 491, + "tmp": 748 + }, + { + "g": 766, + "l": 157, + "tmp": 804 + }, + { + "g": 302, + "l": 202, + "tmp": 489 + }, + { + "g": 463, + "l": 126, + "tmp": 761 + }, + { + "g": 308, + "l": 88, + "tmp": 996 + }, + { + "g": 432, + "l": 239, + "tmp": 247 + }, + { + "g": 793, + "l": 96, + "tmp": 759 + }, + { + "g": 297, + "l": 173, + "tmp": 428 + }, + { + "g": 637, + "l": 437, + "tmp": 465 + }, + { + "g": 296, + "l": 490, + "tmp": 667 + }, + { + "g": 586, + "l": 172, + "tmp": 202 + }, + { + "g": 435, + "l": 100, + "tmp": 103 + }, + { + "g": 110, + "l": 399, + "tmp": 936 + }, + { + "g": 130, + "l": 19, + "tmp": 36 + }, + { + "g": 54, + "l": 442, + "tmp": 692 + }, + { + "g": 106, + "l": 71, + "tmp": 770 + }, + { + "g": 804, + "l": 365, + "tmp": 450 + }, + { + "g": 106, + "l": 5, + "tmp": 264 + }, + { + "g": 730, + "l": 139, + "tmp": 520 + }, + { + "g": 305, + "l": 320, + "tmp": 888 + }, + { + "g": 994, + "l": 230, + "tmp": 71 + }, + { + "g": 503, + "l": 401, + "tmp": 408 + }, + { + "g": 394, + "l": 210, + "tmp": 945 + }, + { + "g": 142, + "l": 176, + "tmp": 85 + }, + { + "g": 840, + "l": 470, + "tmp": 940 + }, + { + "g": 649, + "l": 243, + "tmp": 872 + }, + { + "g": 260, + "l": 427, + "tmp": 986 + }, + { + "g": 403, + "l": 333, + "tmp": 522 + }, + { + "g": 261, + "l": 174, + "tmp": 145 + }, + { + "g": 581, + "l": 109, + "tmp": 877 + }, + { + "g": 377, + "l": 363, + "tmp": 979 + }, + { + "g": 242, + "l": 49, + "tmp": 85 + }, + { + "g": 241, + "l": 201, + "tmp": 807 + }, + { + "g": 859, + "l": 120, + "tmp": 167 + }, + { + "g": 11, + "l": 413, + "tmp": 102 + }, + { + "g": 866, + "l": 35, + "tmp": 642 + }, + { + "g": 226, + "l": 288, + "tmp": 208 + }, + { + "g": 578, + "l": 449, + "tmp": 27 + }, + { + "g": 224, + "l": 386, + "tmp": 263 + }, + { + "g": 909, + "l": 230, + "tmp": 342 + }, + { + "g": 828, + "l": 252, + "tmp": 136 + }, + { + "g": 246, + "l": 336, + "tmp": 825 + }, + { + "g": 376, + "l": 227, + "tmp": 914 + }, + { + "g": 416, + "l": 313, + "tmp": 318 + }, + { + "g": 453, + "l": 184, + "tmp": 513 + }, + { + "g": 633, + "l": 382, + "tmp": 486 + }, + { + "g": 327, + "l": 188, + "tmp": 498 + }, + { + "g": 617, + "l": 25, + "tmp": 193 + }, + { + "g": 283, + "l": 224, + "tmp": 949 + }, + { + "g": 400, + "l": 18, + "tmp": 533 + }, + { + "g": 948, + "l": 123, + "tmp": 57 + }, + { + "g": 514, + "l": 477, + "tmp": 663 + }, + { + "g": 532, + "l": 405, + "tmp": 225 + }, + { + "g": 723, + "l": 357, + "tmp": 743 + }, + { + "g": 810, + "l": 170, + "tmp": 57 + }, + { + "g": 362, + "l": 275, + "tmp": 371 + }, + { + "g": 642, + "l": 421, + "tmp": 48 + }, + { + "g": 934, + "l": 431, + "tmp": 622 + }, + { + "g": 45, + "l": 39, + "tmp": 642 + }, + { + "g": 401, + "l": 401, + "tmp": 660 + }, + { + "g": 678, + "l": 257, + "tmp": 225 + }, + { + "g": 696, + "l": 201, + "tmp": 854 + }, + { + "g": 807, + "l": 372, + "tmp": 673 + }, + { + "g": 119, + "l": 402, + "tmp": 474 + }, + { + "g": 498, + "l": 142, + "tmp": 414 + }, + { + "g": 900, + "l": 338, + "tmp": 10 + }, + { + "g": 176, + "l": 143, + "tmp": 577 + }, + { + "g": 909, + "l": 134, + "tmp": 427 + }, + { + "g": 42, + "l": 443, + "tmp": 247 + }, + { + "g": 667, + "l": 97, + "tmp": 860 + }, + { + "g": 497, + "l": 463, + "tmp": 842 + }, + { + "g": 252, + "l": 364, + "tmp": 853 + }, + { + "g": 191, + "l": 40, + "tmp": 501 + }, + { + "g": 471, + "l": 334, + "tmp": 913 + }, + { + "g": 877, + "l": 157, + "tmp": 70 + }, + { + "g": 198, + "l": 24, + "tmp": 131 + }, + { + "g": 709, + "l": 434, + "tmp": 110 + }, + { + "g": 740, + "l": 352, + "tmp": 658 + }, + { + "g": 406, + "l": 322, + "tmp": 716 + }, + { + "g": 304, + "l": 288, + "tmp": 718 + }, + { + "g": 765, + "l": 241, + "tmp": 374 + }, + { + "g": 566, + "l": 224, + "tmp": 299 + }, + { + "g": 316, + "l": 284, + "tmp": 147 + }, + { + "g": 167, + "l": 213, + "tmp": 783 + }, + { + "g": 550, + "l": 314, + "tmp": 903 + }, + { + "g": 10, + "l": 266, + "tmp": 175 + }, + { + "g": 711, + "l": 408, + "tmp": 938 + }, + { + "g": 691, + "l": 338, + "tmp": 274 + }, + { + "g": 523, + "l": 36, + "tmp": 298 + }, + { + "g": 910, + "l": 17, + "tmp": 201 + }, + { + "g": 40, + "l": 125, + "tmp": 91 + }, + { + "g": 106, + "l": 233, + "tmp": 603 + }, + { + "g": 297, + "l": 64, + "tmp": 165 + }, + { + "g": 723, + "l": 178, + "tmp": 127 + }, + { + "g": 785, + "l": 93, + "tmp": 614 + }, + { + "g": 672, + "l": 465, + "tmp": 63 + }, + { + "g": 843, + "l": 301, + "tmp": 507 + }, + { + "g": 766, + "l": 450, + "tmp": 647 + }, + { + "g": 84, + "l": 225, + "tmp": 358 + }, + { + "g": 947, + "l": 478, + "tmp": 131 + }, + { + "g": 202, + "l": 455, + "tmp": 193 + }, + { + "g": 548, + "l": 399, + "tmp": 819 + }, + { + "g": 199, + "l": 62, + "tmp": 182 + }, + { + "g": 362, + "l": 229, + "tmp": 277 + }, + { + "g": 704, + "l": 358, + "tmp": 428 + }, + { + "g": 124, + "l": 24, + "tmp": 414 + }, + { + "g": 655, + "l": 363, + "tmp": 213 + }, + { + "g": 897, + "l": 184, + "tmp": 37 + }, + { + "g": 727, + "l": 296, + "tmp": 667 + }, + { + "g": 299, + "l": 305, + "tmp": 23 + }, + { + "g": 920, + "l": 39, + "tmp": 776 + }, + { + "g": 822, + "l": 195, + "tmp": 400 + }, + { + "g": 694, + "l": 229, + "tmp": 770 + }, + { + "g": 886, + "l": 342, + "tmp": 485 + }, + { + "g": 757, + "l": 375, + "tmp": 441 + }, + { + "g": 730, + "l": 170, + "tmp": 595 + }, + { + "g": 734, + "l": 238, + "tmp": 580 + }, + { + "g": 671, + "l": 11, + "tmp": 979 + }, + { + "g": 688, + "l": 37, + "tmp": 685 + }, + { + "g": 310, + "l": 492, + "tmp": 576 + }, + { + "g": 245, + "l": 488, + "tmp": 683 + }, + { + "g": 378, + "l": 293, + "tmp": 553 + }, + { + "g": 783, + "l": 45, + "tmp": 573 + }, + { + "g": 948, + "l": 93, + "tmp": 351 + }, + { + "g": 921, + "l": 17, + "tmp": 893 + }, + { + "g": 539, + "l": 372, + "tmp": 232 + }, + { + "g": 555, + "l": 31, + "tmp": 424 + }, + { + "g": 282, + "l": 435, + "tmp": 336 + }, + { + "g": 334, + "l": 305, + "tmp": 928 + }, + { + "g": 33, + "l": 160, + "tmp": 644 + }, + { + "g": 610, + "l": 446, + "tmp": 123 + }, + { + "g": 750, + "l": 209, + "tmp": 856 + }, + { + "g": 257, + "l": 259, + "tmp": 990 + }, + { + "g": 815, + "l": 21, + "tmp": 461 + }, + { + "g": 351, + "l": 130, + "tmp": 625 + }, + { + "g": 406, + "l": 367, + "tmp": 344 + }, + { + "g": 26, + "l": 207, + "tmp": 786 + }, + { + "g": 814, + "l": 93, + "tmp": 93 + }, + { + "g": 218, + "l": 311, + "tmp": 981 + }, + { + "g": 394, + "l": 310, + "tmp": 130 + }, + { + "g": 767, + "l": 110, + "tmp": 838 + }, + { + "g": 741, + "l": 369, + "tmp": 23 + }, + { + "g": 988, + "l": 241, + "tmp": 109 + }, + { + "g": 273, + "l": 80, + "tmp": 516 + }, + { + "g": 487, + "l": 457, + "tmp": 817 + }, + { + "g": 245, + "l": 135, + "tmp": 128 + }, + { + "g": 424, + "l": 228, + "tmp": 517 + }, + { + "g": 818, + "l": 219, + "tmp": 385 + }, + { + "g": 512, + "l": 225, + "tmp": 608 + }, + { + "g": 482, + "l": 113, + "tmp": 352 + }, + { + "g": 598, + "l": 148, + "tmp": 755 + }, + { + "g": 484, + "l": 168, + "tmp": 471 + }, + { + "g": 739, + "l": 187, + "tmp": 583 + }, + { + "g": 484, + "l": 363, + "tmp": 820 + }, + { + "g": 99, + "l": 61, + "tmp": 891 + }, + { + "g": 121, + "l": 340, + "tmp": 904 + }, + { + "g": 968, + "l": 321, + "tmp": 554 + }, + { + "g": 851, + "l": 474, + "tmp": 482 + }, + { + "g": 998, + "l": 414, + "tmp": 133 + }, + { + "g": 980, + "l": 21, + "tmp": 911 + }, + { + "g": 519, + "l": 381, + "tmp": 99 + }, + { + "g": 707, + "l": 55, + "tmp": 651 + }, + { + "g": 563, + "l": 422, + "tmp": 5 + }, + { + "g": 23, + "l": 495, + "tmp": 421 + }, + { + "g": 613, + "l": 477, + "tmp": 557 + }, + { + "g": 735, + "l": 432, + "tmp": 314 + }, + { + "g": 441, + "l": 161, + "tmp": 447 + }, + { + "g": 896, + "l": 327, + "tmp": 30 + }, + { + "g": 673, + "l": 58, + "tmp": 826 + }, + { + "g": 346, + "l": 165, + "tmp": 68 + }, + { + "g": 451, + "l": 225, + "tmp": 116 + }, + { + "g": 981, + "l": 301, + "tmp": 50 + }, + { + "g": 141, + "l": 354, + "tmp": 922 + }, + { + "g": 759, + "l": 301, + "tmp": 350 + }, + { + "g": 521, + "l": 145, + "tmp": 193 + }, + { + "g": 873, + "l": 441, + "tmp": 470 + }, + { + "g": 794, + "l": 41, + "tmp": 990 + }, + { + "g": 545, + "l": 302, + "tmp": 70 + }, + { + "g": 241, + "l": 98, + "tmp": 81 + }, + { + "g": 271, + "l": 495, + "tmp": 179 + }, + { + "g": 910, + "l": 190, + "tmp": 678 + }, + { + "g": 727, + "l": 380, + "tmp": 275 + }, + { + "g": 310, + "l": 133, + "tmp": 523 + }, + { + "g": 839, + "l": 474, + "tmp": 324 + }, + { + "g": 16, + "l": 453, + "tmp": 842 + }, + { + "g": 402, + "l": 80, + "tmp": 634 + }, + { + "g": 953, + "l": 370, + "tmp": 261 + }, + { + "g": 440, + "l": 369, + "tmp": 109 + }, + { + "g": 887, + "l": 184, + "tmp": 239 + }, + { + "g": 309, + "l": 105, + "tmp": 303 + }, + { + "g": 63, + "l": 360, + "tmp": 199 + }, + { + "g": 999, + "l": 455, + "tmp": 141 + }, + { + "g": 928, + "l": 257, + "tmp": 572 + }, + { + "g": 750, + "l": 3, + "tmp": 18 + }, + { + "g": 266, + "l": 309, + "tmp": 350 + }, + { + "g": 40, + "l": 52, + "tmp": 588 + }, + { + "g": 448, + "l": 203, + "tmp": 256 + }, + { + "g": 283, + "l": 264, + "tmp": 125 + }, + { + "g": 238, + "l": 31, + "tmp": 231 + }, + { + "g": 819, + "l": 461, + "tmp": 669 + }, + { + "g": 84, + "l": 22, + "tmp": 796 + }, + { + "g": 605, + "l": 284, + "tmp": 296 + }, + { + "g": 431, + "l": 86, + "tmp": 21 + }, + { + "g": 926, + "l": 182, + "tmp": 552 + }, + { + "g": 195, + "l": 468, + "tmp": 526 + }, + { + "g": 807, + "l": 188, + "tmp": 764 + }, + { + "g": 816, + "l": 326, + "tmp": 696 + }, + { + "g": 314, + "l": 125, + "tmp": 648 + }, + { + "g": 952, + "l": 50, + "tmp": 999 + }, + { + "g": 894, + "l": 112, + "tmp": 454 + }, + { + "g": 670, + "l": 437, + "tmp": 440 + }, + { + "g": 878, + "l": 494, + "tmp": 637 + }, + { + "g": 259, + "l": 278, + "tmp": 871 + }, + { + "g": 461, + "l": 449, + "tmp": 264 + }, + { + "g": 15, + "l": 50, + "tmp": 17 + }, + { + "g": 770, + "l": 151, + "tmp": 622 + }, + { + "g": 167, + "l": 59, + "tmp": 387 + }, + { + "g": 315, + "l": 412, + "tmp": 907 + }, + { + "g": 393, + "l": 110, + "tmp": 162 + }, + { + "g": 197, + "l": 71, + "tmp": 394 + }, + { + "g": 306, + "l": 354, + "tmp": 183 + }, + { + "g": 593, + "l": 113, + "tmp": 736 + }, + { + "g": 214, + "l": 249, + "tmp": 611 + }, + { + "g": 214, + "l": 78, + "tmp": 589 + }, + { + "g": 521, + "l": 218, + "tmp": 571 + }, + { + "g": 149, + "l": 299, + "tmp": 939 + }, + { + "g": 841, + "l": 379, + "tmp": 510 + }, + { + "g": 197, + "l": 127, + "tmp": 355 + }, + { + "g": 187, + "l": 340, + "tmp": 356 + }, + { + "g": 793, + "l": 171, + "tmp": 138 + }, + { + "g": 340, + "l": 184, + "tmp": 597 + }, + { + "g": 702, + "l": 317, + "tmp": 313 + }, + { + "g": 439, + "l": 383, + "tmp": 217 + }, + { + "g": 337, + "l": 137, + "tmp": 251 + }, + { + "g": 916, + "l": 99, + "tmp": 703 + }, + { + "g": 636, + "l": 405, + "tmp": 524 + }, + { + "g": 203, + "l": 234, + "tmp": 478 + }, + { + "g": 36, + "l": 138, + "tmp": 928 + }, + { + "g": 876, + "l": 20, + "tmp": 636 + }, + { + "g": 790, + "l": 97, + "tmp": 553 + }, + { + "g": 551, + "l": 73, + "tmp": 74 + }, + { + "g": 258, + "l": 296, + "tmp": 766 + }, + { + "g": 278, + "l": 219, + "tmp": 387 + }, + { + "g": 540, + "l": 309, + "tmp": 422 + }, + { + "g": 686, + "l": 418, + "tmp": 577 + }, + { + "g": 192, + "l": 184, + "tmp": 625 + }, + { + "g": 921, + "l": 317, + "tmp": 593 + }, + { + "g": 544, + "l": 24, + "tmp": 911 + }, + { + "g": 699, + "l": 344, + "tmp": 381 + }, + { + "g": 416, + "l": 92, + "tmp": 334 + }, + { + "g": 233, + "l": 60, + "tmp": 462 + }, + { + "g": 157, + "l": 377, + "tmp": 642 + }, + { + "g": 937, + "l": 483, + "tmp": 890 + }, + { + "g": 501, + "l": 402, + "tmp": 277 + }, + { + "g": 246, + "l": 270, + "tmp": 467 + }, + { + "g": 433, + "l": 118, + "tmp": 427 + }, + { + "g": 788, + "l": 499, + "tmp": 214 + }, + { + "g": 706, + "l": 332, + "tmp": 164 + }, + { + "g": 366, + "l": 315, + "tmp": 909 + }, + { + "g": 392, + "l": 272, + "tmp": 755 + }, + { + "g": 817, + "l": 393, + "tmp": 512 + }, + { + "g": 192, + "l": 189, + "tmp": 237 + }, + { + "g": 761, + "l": 50, + "tmp": 361 + }, + { + "g": 600, + "l": 267, + "tmp": 783 + }, + { + "g": 911, + "l": 369, + "tmp": 579 + }, + { + "g": 55, + "l": 206, + "tmp": 299 + }, + { + "g": 646, + "l": 92, + "tmp": 5 + }, + { + "g": 587, + "l": 311, + "tmp": 213 + }, + { + "g": 87, + "l": 428, + "tmp": 206 + }, + { + "g": 753, + "l": 87, + "tmp": 804 + }, + { + "g": 454, + "l": 483, + "tmp": 996 + }, + { + "g": 906, + "l": 268, + "tmp": 708 + }, + { + "g": 885, + "l": 179, + "tmp": 99 + }, + { + "g": 549, + "l": 274, + "tmp": 734 + }, + { + "g": 348, + "l": 154, + "tmp": 997 + }, + { + "g": 672, + "l": 319, + "tmp": 941 + }, + { + "g": 415, + "l": 118, + "tmp": 901 + }, + { + "g": 390, + "l": 252, + "tmp": 580 + }, + { + "g": 532, + "l": 346, + "tmp": 216 + }, + { + "g": 878, + "l": 19, + "tmp": 669 + }, + { + "g": 326, + "l": 309, + "tmp": 797 + }, + { + "g": 496, + "l": 497, + "tmp": 348 + }, + { + "g": 243, + "l": 189, + "tmp": 725 + }, + { + "g": 140, + "l": 310, + "tmp": 396 + }, + { + "g": 343, + "l": 433, + "tmp": 472 + }, + { + "g": 342, + "l": 133, + "tmp": 969 + }, + { + "g": 947, + "l": 137, + "tmp": 966 + }, + { + "g": 710, + "l": 423, + "tmp": 231 + }, + { + "g": 770, + "l": 296, + "tmp": 43 + }, + { + "g": 557, + "l": 173, + "tmp": 268 + }, + { + "g": 66, + "l": 178, + "tmp": 392 + }, + { + "g": 960, + "l": 455, + "tmp": 266 + }, + { + "g": 225, + "l": 147, + "tmp": 396 + }, + { + "g": 679, + "l": 384, + "tmp": 211 + }, + { + "g": 757, + "l": 66, + "tmp": 94 + }, + { + "g": 456, + "l": 206, + "tmp": 120 + }, + { + "g": 644, + "l": 111, + "tmp": 767 + }, + { + "g": 387, + "l": 276, + "tmp": 587 + }, + { + "g": 669, + "l": 409, + "tmp": 43 + }, + { + "g": 752, + "l": 462, + "tmp": 802 + }, + { + "g": 829, + "l": 75, + "tmp": 445 + }, + { + "g": 263, + "l": 282, + "tmp": 423 + }, + { + "g": 761, + "l": 496, + "tmp": 771 + }, + { + "g": 830, + "l": 492, + "tmp": 117 + }, + { + "g": 740, + "l": 92, + "tmp": 355 + }, + { + "g": 840, + "l": 339, + "tmp": 311 + }, + { + "g": 964, + "l": 196, + "tmp": 332 + }, + { + "g": 439, + "l": 289, + "tmp": 638 + }, + { + "g": 316, + "l": 194, + "tmp": 851 + }, + { + "g": 833, + "l": 178, + "tmp": 680 + }, + { + "g": 694, + "l": 458, + "tmp": 876 + }, + { + "g": 793, + "l": 305, + "tmp": 777 + }, + { + "g": 273, + "l": 363, + "tmp": 585 + }, + { + "g": 194, + "l": 356, + "tmp": 619 + }, + { + "g": 225, + "l": 56, + "tmp": 520 + }, + { + "g": 658, + "l": 342, + "tmp": 123 + }, + { + "g": 940, + "l": 464, + "tmp": 572 + }, + { + "g": 500, + "l": 181, + "tmp": 629 + }, + { + "g": 100, + "l": 60, + "tmp": 283 + }, + { + "g": 236, + "l": 263, + "tmp": 129 + }, + { + "g": 656, + "l": 395, + "tmp": 320 + }, + { + "g": 134, + "l": 306, + "tmp": 629 + }, + { + "g": 87, + "l": 79, + "tmp": 488 + }, + { + "g": 815, + "l": 433, + "tmp": 462 + }, + { + "g": 803, + "l": 223, + "tmp": 610 + }, + { + "g": 300, + "l": 68, + "tmp": 641 + }, + { + "g": 468, + "l": 133, + "tmp": 827 + }, + { + "g": 309, + "l": 147, + "tmp": 955 + }, + { + "g": 691, + "l": 215, + "tmp": 796 + }, + { + "g": 549, + "l": 481, + "tmp": 391 + }, + { + "g": 776, + "l": 29, + "tmp": 859 + }, + { + "g": 420, + "l": 478, + "tmp": 960 + }, + { + "g": 927, + "l": 7, + "tmp": 396 + }, + { + "g": 475, + "l": 160, + "tmp": 234 + }, + { + "g": 330, + "l": 463, + "tmp": 468 + }, + { + "g": 484, + "l": 206, + "tmp": 159 + }, + { + "g": 233, + "l": 245, + "tmp": 345 + }, + { + "g": 143, + "l": 144, + "tmp": 664 + }, + { + "g": 780, + "l": 247, + "tmp": 946 + }, + { + "g": 289, + "l": 391, + "tmp": 564 + }, + { + "g": 959, + "l": 183, + "tmp": 460 + }, + { + "g": 923, + "l": 192, + "tmp": 48 + }, + { + "g": 494, + "l": 318, + "tmp": 795 + }, + { + "g": 301, + "l": 20, + "tmp": 737 + }, + { + "g": 926, + "l": 135, + "tmp": 941 + }, + { + "g": 164, + "l": 268, + "tmp": 385 + }, + { + "g": 197, + "l": 318, + "tmp": 763 + }, + { + "g": 921, + "l": 325, + "tmp": 171 + }, + { + "g": 614, + "l": 464, + "tmp": 192 + }, + { + "g": 195, + "l": 103, + "tmp": 822 + }, + { + "g": 399, + "l": 261, + "tmp": 473 + }, + { + "g": 928, + "l": 410, + "tmp": 359 + }, + { + "g": 746, + "l": 77, + "tmp": 574 + }, + { + "g": 362, + "l": 422, + "tmp": 833 + }, + { + "g": 23, + "l": 83, + "tmp": 615 + }, + { + "g": 445, + "l": 295, + "tmp": 682 + }, + { + "g": 177, + "l": 8, + "tmp": 976 + }, + { + "g": 740, + "l": 448, + "tmp": 840 + }, + { + "g": 95, + "l": 265, + "tmp": 208 + }, + { + "g": 423, + "l": 278, + "tmp": 145 + }, + { + "g": 336, + "l": 255, + "tmp": 42 + }, + { + "g": 718, + "l": 207, + "tmp": 806 + }, + { + "g": 669, + "l": 171, + "tmp": 124 + }, + { + "g": 235, + "l": 64, + "tmp": 978 + }, + { + "g": 945, + "l": 167, + "tmp": 749 + }, + { + "g": 280, + "l": 294, + "tmp": 165 + }, + { + "g": 979, + "l": 0, + "tmp": 745 + }, + { + "g": 3, + "l": 101, + "tmp": 646 + }, + { + "g": 611, + "l": 91, + "tmp": 990 + }, + { + "g": 966, + "l": 97, + "tmp": 778 + }, + { + "g": 335, + "l": 51, + "tmp": 487 + }, + { + "g": 562, + "l": 354, + "tmp": 171 + }, + { + "g": 261, + "l": 161, + "tmp": 377 + }, + { + "g": 178, + "l": 309, + "tmp": 813 + }, + { + "g": 726, + "l": 344, + "tmp": 208 + }, + { + "g": 801, + "l": 194, + "tmp": 854 + }, + { + "g": 25, + "l": 136, + "tmp": 703 + }, + { + "g": 218, + "l": 210, + "tmp": 849 + }, + { + "g": 181, + "l": 19, + "tmp": 311 + }, + { + "g": 522, + "l": 392, + "tmp": 282 + }, + { + "g": 875, + "l": 360, + "tmp": 260 + }, + { + "g": 978, + "l": 481, + "tmp": 389 + }, + { + "g": 885, + "l": 335, + "tmp": 294 + }, + { + "g": 218, + "l": 270, + "tmp": 566 + }, + { + "g": 362, + "l": 316, + "tmp": 349 + }, + { + "g": 95, + "l": 359, + "tmp": 674 + }, + { + "g": 767, + "l": 184, + "tmp": 985 + }, + { + "g": 791, + "l": 378, + "tmp": 827 + }, + { + "g": 799, + "l": 426, + "tmp": 716 + }, + { + "g": 437, + "l": 183, + "tmp": 409 + }, + { + "g": 305, + "l": 85, + "tmp": 451 + }, + { + "g": 859, + "l": 357, + "tmp": 382 + }, + { + "g": 90, + "l": 61, + "tmp": 621 + }, + { + "g": 933, + "l": 347, + "tmp": 62 + }, + { + "g": 693, + "l": 208, + "tmp": 123 + }, + { + "g": 304, + "l": 222, + "tmp": 809 + }, + { + "g": 72, + "l": 243, + "tmp": 116 + }, + { + "g": 657, + "l": 9, + "tmp": 441 + }, + { + "g": 65, + "l": 199, + "tmp": 147 + }, + { + "g": 503, + "l": 208, + "tmp": 86 + }, + { + "g": 257, + "l": 274, + "tmp": 221 + }, + { + "g": 879, + "l": 32, + "tmp": 269 + }, + { + "g": 979, + "l": 432, + "tmp": 344 + }, + { + "g": 948, + "l": 52, + "tmp": 80 + }, + { + "g": 973, + "l": 181, + "tmp": 811 + }, + { + "g": 584, + "l": 438, + "tmp": 394 + }, + { + "g": 645, + "l": 357, + "tmp": 89 + }, + { + "g": 387, + "l": 457, + "tmp": 20 + }, + { + "g": 686, + "l": 240, + "tmp": 829 + }, + { + "g": 419, + "l": 185, + "tmp": 722 + }, + { + "g": 869, + "l": 365, + "tmp": 455 + }, + { + "g": 376, + "l": 434, + "tmp": 586 + }, + { + "g": 219, + "l": 418, + "tmp": 619 + }, + { + "g": 458, + "l": 186, + "tmp": 808 + }, + { + "g": 26, + "l": 427, + "tmp": 922 + }, + { + "g": 61, + "l": 116, + "tmp": 688 + }, + { + "g": 247, + "l": 243, + "tmp": 645 + }, + { + "g": 990, + "l": 93, + "tmp": 844 + }, + { + "g": 757, + "l": 273, + "tmp": 81 + }, + { + "g": 396, + "l": 387, + "tmp": 15 + }, + { + "g": 518, + "l": 425, + "tmp": 268 + }, + { + "g": 246, + "l": 48, + "tmp": 423 + }, + { + "g": 841, + "l": 426, + "tmp": 530 + }, + { + "g": 844, + "l": 69, + "tmp": 571 + }, + { + "g": 818, + "l": 31, + "tmp": 27 + }, + { + "g": 248, + "l": 180, + "tmp": 168 + }, + { + "g": 251, + "l": 185, + "tmp": 462 + }, + { + "g": 727, + "l": 57, + "tmp": 15 + }, + { + "g": 636, + "l": 175, + "tmp": 7 + }, + { + "g": 310, + "l": 127, + "tmp": 494 + }, + { + "g": 380, + "l": 442, + "tmp": 609 + }, + { + "g": 923, + "l": 192, + "tmp": 43 + }, + { + "g": 161, + "l": 487, + "tmp": 817 + }, + { + "g": 596, + "l": 320, + "tmp": 718 + } +] \ No newline at end of file diff --git a/__tests__/integration/snapshots/static/HeatmapHeatmapBasic.png b/__tests__/integration/snapshots/static/HeatmapHeatmapBasic.png new file mode 100644 index 0000000000000000000000000000000000000000..5bcd9e9bed3547416bf1272c84e454230d708479 GIT binary patch literal 1289 zcmeAS@N?(olHy`uVBq!ia0y~yU}|7sV0^&A1{5*9c;^X_VoUONcVYMsf(!O8pUl9( zvd`1SF{EP7+k=d}K>0%pzWe9$nJ)k`N5N; */ export const Heatmap: MC = (options) => { return (index, scale, value, coordinate) => { - const { x: X, y: Y, x1: X1, y1: Y1, size: S } = value; - const [width, height] = coordinate.getSize(); - const offset = createBandOffset(scale, value, options); - const xy: (i: number) => Vector2 = (i) => { - const x = X1 ? (+X[i] + +X1[i]) / 2 : +X[i]; - const y = Y1 ? (+Y[i] + +Y1[i]) / 2 : +Y[i]; - return [x, y]; - }; - const P = S - ? Array.from(index, (i) => { - const [cx, cy] = xy(i); - const r = +S[i]; - const a = r / width; - const b = r / height; - const p1: Vector2 = [cx - a, cy - b]; - const p2: Vector2 = [cx + a, cy + b]; - return [ - coordinate.map(offset(p1, i)), - coordinate.map(offset(p2, i)), - ] as Vector2[]; - }) - : Array.from( - index, - (i) => [coordinate.map(offset(xy(i), i))] as Vector2[], - ); - return [index, P]; + const { x: X, y: Y, size: S, color: C } = value; + const P = Array.from(index, (i) => { + // Default size = 20. + const r = S ? +S[i] : 20; + //Warning: x, y, value, radius. + return [...coordinate.map([+X[i], +Y[i]]), C[i], r] as unknown as Vector2; + }); + + return [[0], [P]]; }; }; @@ -54,6 +36,7 @@ Heatmap.props = { ...baseGeometryChannels({ shapes }), { name: 'x', required: true }, { name: 'y', required: true }, + { name: 'color', scale: 'identity', required: true }, { name: 'size' }, ], preInference: [ @@ -61,9 +44,5 @@ Heatmap.props = { { type: 'maybeZeroY' }, { type: 'maybeZeroX' }, ], - postInference: [ - ...basePostInference(), - { type: 'maybeSize' }, - ...tooltip2d(), - ], + postInference: [...basePostInference(), ...tooltip2d()], }; diff --git a/src/shape/heatmap/heatmap.ts b/src/shape/heatmap/heatmap.ts index a6bd997ea8..6111fedc17 100644 --- a/src/shape/heatmap/heatmap.ts +++ b/src/shape/heatmap/heatmap.ts @@ -1,13 +1,16 @@ +import { max as d3max, min as d3min } from 'd3-array'; import { Image as GImage } from '@antv/g'; import { applyStyle, getShapeTheme } from '../utils'; import { select } from '../../utils/selection'; import { ShapeComponent as SC, Vector2 } from '../../runtime'; +import { HeatmapRenderer } from './renderer'; +import type { HeatmapRendererOptions } from './renderer/types'; -export type HeatmapOptions = Record; +export type HeatmapOptions = HeatmapRendererOptions; export const Heatmap: SC = (options) => { const { ...style } = options; - return (points, value, coordinate, theme) => { + return (points: number[][], value, coordinate, theme) => { const { mark, shape, defaultShape, color, transform } = value; const { defaultColor, @@ -16,16 +19,32 @@ export const Heatmap: SC = (options) => { ...shapeTheme } = getShapeTheme(theme, mark, shape, defaultShape); + const [width, height] = coordinate.getSize(); + const data = points.map((p: number[]) => ({ + x: p[0], + y: p[1], + value: p[2], + radius: p[3], + })); + const min = d3min(points, (p) => p[2]); + const max = d3max(points, (p) => p[2]); + + const ctx = HeatmapRenderer(width, height, min, max, data, { ...style }); + return select(new GImage()) .call(applyStyle, shapeTheme) - .style('src', '') + .style('x', 0) + .style('y', 0) + .style('width', width) + .style('height', height) + .style('src', ctx.canvas) .style('transform', transform) .call(applyStyle, style) .node(); }; }; -Box.props = { +Heatmap.props = { defaultMarker: 'point', defaultEnterAnimation: 'fadeIn', defaultUpdateAnimation: 'morphing', diff --git a/src/shape/heatmap/renderer/index.ts b/src/shape/heatmap/renderer/index.ts new file mode 100644 index 0000000000..dcfb8fc9dc --- /dev/null +++ b/src/shape/heatmap/renderer/index.ts @@ -0,0 +1,171 @@ +import { HeatmapRendererData, HeatmapRendererOptions } from './types'; + +/** + * Get a point with template. + * @param radius + * @param blurFactor + * @returns + */ +function getPointTemplate(radius: number, blurFactor: number) { + const tplCanvas = document.createElement('canvas'); + const tplCtx = tplCanvas.getContext('2d'); + const x = radius; + const y = radius; + tplCanvas.width = tplCanvas.height = radius * 2; + + if (blurFactor === 1) { + tplCtx.beginPath(); + tplCtx.arc(x, y, radius, 0, 2 * Math.PI, false); + tplCtx.fillStyle = 'rgba(0,0,0,1)'; + tplCtx.fill(); + } else { + const gradient = tplCtx.createRadialGradient( + x, + y, + radius * blurFactor, + x, + y, + radius, + ); + gradient.addColorStop(0, 'rgba(0,0,0,1)'); + gradient.addColorStop(1, 'rgba(0,0,0,0)'); + tplCtx.fillStyle = gradient; + tplCtx.fillRect(0, 0, 2 * radius, 2 * radius); + } + return tplCanvas; +} + +/** + * Get a color palette with len = 256 base on gradient. + * @param gradientConfig + * @returns + */ +function getColorPalette(gradientConfig: any) { + const paletteCanvas = document.createElement('canvas'); + const paletteCtx = paletteCanvas.getContext('2d'); + + paletteCanvas.width = 256; + paletteCanvas.height = 1; + + const gradient = paletteCtx.createLinearGradient(0, 0, 256, 1); + for (const key in gradientConfig) { + gradient.addColorStop(+key, gradientConfig[key]); + } + + paletteCtx.fillStyle = gradient; + paletteCtx.fillRect(0, 0, 256, 1); + + return paletteCtx.getImageData(0, 0, 256, 1).data; +} + +/** + * Draw all circle with alpha. + */ +function drawAlpha( + shadowCtx, + min: number, + max: number, + data: HeatmapRendererData[], + options: HeatmapRendererOptions, +) { + const { blur } = options; + let len = data.length; + while (len--) { + const { x, y, value: v, radius } = data[len]; + // Ff value is bigger than max, use max as value. + const value = Math.min(v, max); + const rectX = x - radius; + const rectY = y - radius; + + // TODO: cache for performance. + const tpl = getPointTemplate(radius, blur); + // Value from minimum / value range, => [0, 1]. + const templateAlpha = (value - min) / (max - min); + // Small values are not visible because globalAlpha < .01 cannot be read from imageData. + shadowCtx.globalAlpha = Math.max(templateAlpha, 0.01); + shadowCtx.drawImage(tpl, rectX, rectY); + } + return shadowCtx; +} + +function colorize( + shadowCtx, + maxWidth: number, + maxHeight: number, + palette, + options: HeatmapRendererOptions, +) { + const { maxOpacity, minOpacity, useGradientOpacity } = options; + const x = 0; + const y = 0; + const width = maxWidth; + const height = maxHeight; + + const img = shadowCtx.getImageData(x, y, width, height); + const imgData = img.data; + const len = imgData.length; + + for (let i = 3; i < len; i += 4) { + const alpha = imgData[i]; + const offset = alpha * 4; + + if (!offset) { + continue; + } + + // Should be in [min, max], min >= 0. + const finalAlpha = Math.max( + 0, + Math.min(maxOpacity, Math.max(minOpacity, alpha)), + ); + // Update rgba. + imgData[i - 3] = palette[offset]; + imgData[i - 2] = palette[offset + 1]; + imgData[i - 1] = palette[offset + 2]; + imgData[i] = useGradientOpacity ? palette[offset + 3] : finalAlpha; + } + + return img; +} + +/** + * Render a heatmap with canvas. + * See [heatmap.js](https://github.com/pa7/heatmap.js/blob/master/src/renderer/canvas2d.js). + */ +export function HeatmapRenderer( + width: number, + height: number, + min: number, + max: number, + data: HeatmapRendererData[], + options: HeatmapRendererOptions, +) { + const opts = { + blur: 0.5, + gradient: { + 0.25: 'rgb(0,0,255)', + 0.55: 'rgb(0,255,0)', + 0.85: 'yellow', + 1.0: 'rgb(255,0,0)', + }, + ...options, + opacity: (options.opacity || 0.65) * 255, + maxOpacity: (options.opacity || 1) * 255, + minOpacity: (options.opacity || 0) * 255, + }; + + const canvas = document.createElement('canvas'); + const shadowCanvas = document.createElement('canvas'); + + const ctx = canvas.getContext('2d'); + const shadowCtx = shadowCanvas.getContext('2d'); + + const palette = getColorPalette(opts.gradient); + + drawAlpha(shadowCtx, min, max, data, opts); + const img = colorize(shadowCtx, width, height, palette, opts); + + ctx.putImageData(img, 0, 0); + + return ctx; +} diff --git a/src/shape/heatmap/renderer/types.ts b/src/shape/heatmap/renderer/types.ts new file mode 100644 index 0000000000..413694005f --- /dev/null +++ b/src/shape/heatmap/renderer/types.ts @@ -0,0 +1,43 @@ +export type HeatmapRendererOptions = { + /** + * A background color string in form of hexcode, color name, or rgb(a). + */ + backgroundColor?: string; + /** + * An gradient string that represents the gradient (syntax: number string [0,1] : color string). + */ + gradient?: Record; + /** + * The radius each datapoint will have (if not specified on the datapoint itself). + */ + radius?: number; + /** + * A global opacity for the whole heatmap, default = 0.6. + * This overrides maxOpacity and minOpacity if set! + */ + opacity?: number; + /** + * The maximal opacity the highest value in the heatmap will have. (will be overridden if opacity set). + */ + maxOpacity?: number; + /** + * The minimum opacity the lowest value in the heatmap will have (will be overridden if opacity set). + */ + minOpacity?: number; + /** + * The blur factor that will be applied to all datapoints, default = 0.5. + * The higher the blur factor is, the smoother the gradients will be. + */ + blur?: number; + /** + * Use gradient opacity. + */ + useGradientOpacity?: boolean; +}; + +export type HeatmapRendererData = { + x: number; + y: number; + value: number; + radius: number; +};