-
Notifications
You must be signed in to change notification settings - Fork 0
/
jam-design2colors.R
1460 lines (1403 loc) · 53.1 KB
/
jam-design2colors.R
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
#' Convert experiment design into categorical colors
#'
#' Convert experiment design into categorical colors
#'
#' The general goal is to assign categorical colors relevant to
#' the experimental design of an analysis. The basic logic:
#'
#' 1. Assign categorical colors to experimental groups.
#' 2. Shade these colors light-to-dark based upon secondary factors.
#' 3. For step 1 above, optionally assign similar color hues by class.
#'
#' When there are multiple factors in a design, the general guidance:
#'
#' * Define `group_colnames` using the first two factors in the design.
#' * Define `class_colnames` using one of these two factors.
#' Values in `group_colnames` will be assigned rainbow categorical colors,
#' with extra spacing between each class. Values in one class will be
#' assigned similar color hues, for example one class may be red/orange,
#' another class may be blue/purple.
#' * Optionally choose another factor to use as `lightness_colnames`.
#' When there are multiple unique values per group, they will be
#' shaded from light to dark within the group color hue.
#'
#' It is sometimes helpful to create a column for `class_colnames`,
#' for example when a series of treatments can be categorized
#' by the type of treatment (agonists, antagonists, inhibitors,
#' controls).
#'
#' Franky, we tend to try a few combinations until the output seems
#' intuitive. Then we assign specific values from other columns
#' using `color_sub`. Typically for `numeric` columns we assign
#' a color to the colname, and for `categorical` colors we assign
#' colors to values in the column.
#'
#' Version 0.0.69.900 and higher: When the cardinality of group/class
#' values is not 1-to-many, either the group/class assignments
#' are switched in order to create 1-to-many cardinality, or
#' a combination of the two vectors is used to create the appropriate
#' cardinality.
#'
#' ## When no group_colnames or class_colnames are defined
#'
#' By default, the unique rownames are used as if they were groups,
#' then colors are assigned using the same logic as usual. Any
#' other column whose values are 1-to-1 match with rownames will
#' inherit the same colors, otherwise `character` and `factor`
#' columns will be assigned categorical colors, and `numeric`
#' columns will be assigned a color gradient.
#'
#' ## Categorical colors
#'
#' At its simplest a set of groups can be assigned categorical colors.
#'
#' * colors should be visibly distinct from one another
#' * colors should generally be distinct across forms of color-blindness
#' * colors should be consistent across plots, figures, tables
#'
#' Finally, colors may be pre-defined using a named vector of colors.
#' These colors will be propagated to other entries in the table.
#'
#' ## Light-to-dark gradient
#'
#' The light-to-dark gradient is intended for ordered sub-divisions,
#' for example:
#'
#' * across time points in a time series
#' * across treatment doses in an ordered series
#' * across ordered measurements first-to-last
#'
#' ## Group class
#'
#' The group classification is intended to assign color hues
#' for similar groups:
#'
#' * antagonists, agonists, untreated
#' * treated, untreated
#' * wildtype, mutant form 1, mutant form 2, etc.
#'
#' For example, antagonists may be assigned colors blue-to-purple;
#' agonists may be assigned colors red-to-orange; with a pronounced
#' color hue "gap" between antagonists and agonists.
#'
#' ## Additional categorical color assignment
#'
#' Finally, other annotations associated with samples are assigned
#' categorical colors, visibly distinct from other color assignments.
#'
#' For entries associated with only one design color, for example "Sample_ID",
#' "Sample Name", "Lane Number", or "Well Number",
#' they inherit the design color.
#'
#' For entries associated with more than one design color, for example
#' "Batch", "Date", or perhaps "Dose", they will be assigned a unique
#' color.
#'
#' * additional annotations unique to design colors inherit the design colors
#' * additional categorical colors should not duplicate existing colors
#'
#' ## Future ideas
#'
#' * Assign "additional factors" to colors based upon `class`
#'
#' * Currently "additional factors" are only tested by class_group and
#' class_group_lightness.
#' * It could be useful to test versus `class` alone (if supplied)
#' * Goal would be to assign color hue using the mean color hue in the class.
#' * Otherwise the class may be assigned a color inconsistent with the
#' range of color hues.
#'
#' * Handle numeric columns by applying color gradient
#'
#' * A truly numeric column (not just integer index values) could
#' use `circlize::colorRamp2()` to apply color gradient
#'
#'
#' @param x `data.frame` with columns to be colorized,
#' `DataFrame` from Bioconductor `S4Vectors` package,
#' `tbl_df` from the `tibble` package, or `matrix`. In all cases
#' the data is coerced to `data.frame` without changing colnames,
#' and without imposing factor values per column, unless factors
#' were already encoded.
#' @param group_colnames `character` or `intger` vector indicating
#' which `colnames(x)` to use, in order, for group color assignment.
#' @param lightness_colnames `character` or `intger` vector indicating
#' which `colnames(x)` to use, in order, for group lightness gradient.
#' @param class_colnames `character` or `intger` vector indicating
#' higher-level grouping of `group_colnames`
#' @param preset `character` string passed to `colorjam::h2hwOptions()`,
#' which defines the hues around a color wheel, used when selecting
#' categorical colors. Some shortcuts:
#' * `"dichromat"`: (default) uses color-blindness friendly color wheel,
#' minimizing effects of three types of color blindness mainly by
#' removing large chunks of the green color space.
#' * `"ryb"`: red-yellow-blue color wheel, which emphasizes the yellow
#' part of the wheel as a major color, as opposed to computer
#' monitor default that represents the red-green-blue color components.
#' * `"ryb2"`: red-yellow-blue color wheel, version 2, adjusted
#' to reduce effects of greenish-yellow for aesthetics.
#' * `"rgb"`: default red-green-blue color wheel used by computer
#' monitors to mimic the components of human vision.
#' @param phase,rotate_phase `integer` value, `phase` is passed to
#' `colorjam::rainbowJam()` to define the light/dark pattern phasing,
#' which has 6 positions, and negative values reverse the order.
#' Categorical colors are assigned to the class/group combinations,
#' after which `phase + rotate_phase` is used for categorical colors
#' for any remaining values.
#' @param class_pad `integer` zero or greater, indicating the number
#' of empty hues to insert as a spacer between hues when the class
#' changes in a sequence of class/group values. Higher values will
#' ensure the hues in each class are more distinct from each other
#' across class, and more similar to each other within class.
#' @param end_hue_pad `integer` used to pad hues at the end of a
#' color wheel sequence, typically useful to ensure the last color
#' is not similar to the first color.
#' @param desat `numeric` vector extended to length=2, used to desaturate
#' class/group colors, then remaining colors, in order. The intended
#' effect is to have class/group colors visibly more colorful than
#' remaining colors assigned to other factors.
#' @param dex `numeric` vector passed to `jamba::color2gradient()` to
#' define the darkness expansion factor, where 1 applies a moderate
#' effect, and higher values apply more dramatic light-to-dark
#' effect. When `dex` has length=2, the second value is used only
#' for columns where colors are assigned by `colnames(x)`
#' using `color_sub`.
#' @param Crange,Lrange `numeric` ranges passed to `colorjam::rainbowJam()`
#' to define slightly less saturated colors than the default rainbow
#' palette.
#' @param color_sub `character` vector of R colors, where `names(color_sub)`
#' assign each color to a character string. It is intended to allow
#' specific color assignments upfront.
#' * `colnames(x)`: when `names(color_sub)` matches a column name in `x`,
#' the color is assigned to that color using a color gradient across
#' the unique character values in that column. Values are assigned in
#' order of their appearance in `x` unless the column is a `factor`,
#' in which case colors are assigned to `levels`.
#' @param color_sub_max `numeric` optional value used to define a fixed
#' upper limit to a color gradient when a color is applied to
#' a `numeric` column.
#' * When one value is defined for `color_sub_max` it is used for all
#' `numeric` columns uniformly.
#' * When multiple values are defined for `color_sub_max`, then
#' `names(color_sub_max)` are used to associate to the appropriate
#' column matching with `colnames(x)`.
#' @param na_color `character` string with R color, used to assign a
#' specific color to `NA` values.
#' (This assignment is not yet implemented.)
#' @param shuffle_colors `logical` indicating whether to shuffle categorical
#' color assignments for values not already assigned by
#' class/group/lightness, nor by `color_sub`. The effect is that colors
#' are less likely to be similar for adjacent column values.
#' @param force_consistent_colors `logical` indicating whether to force
#' color substitutions across multiple columns, when those columns
#' share one or more of the same values.
#' Note: This scenario is most
#' likely to occur when using `color_sub` to assign colors to a
#' specific column in `colnames(x)`, and where that column may contain
#' one or more values already assigned a color earlier in the process.
#' For example: class/group/lightness defines colors for these columns,
#' then all columns are checked for cardinality with these color
#' assignments. Columns not appearing in `color_sub` will be colorized
#' when their cardinality is compatible with group colors, otherwise
#' specific values may be assigned colors via `color_sub`, then all
#' remaining values are assigned categorical colors. This process defines
#' colors for each column. The last step reassigns colors consistently
#' using the first value-color association that appears in the list,
#' to make sure all assignments are consistent. This last step is subject
#' of the argument `force_consistent_colors`. In either case, the
#' output `color_sub` will only have one color assigned per value.
#' * Default `TRUE`: When colors are being assigned to column values
#' `color_sub`, if the value had been assigned a color in a previous
#' column, for example by `group_colnames` color assignment, then the
#' first assignment is applied to all subsequent assignments.
#' * Optional `FALSE`: Color assignments are re-used where applicable,
#' except when overridden by `color_sub` for a particular column. In
#' that case, color assignments are maintained for each specific column.
#' @param plot_type `character` string indicating a type of plot for results:
#' * `"table"`: plots a color table equal to the input `data.frame` where
#' background cells indicate color assignments.
#' * `"list"`: plots colors defined for each column in `x` using
#' `jamba::showColors()`
#' * `"none"`: no plot is produced
#' @param return_type `character` string indicating the data format to return:
#' * `"list"`: a `list` of colors, named by `colnames(x)`.
#' * `"df"`: a `data.frame` in order of `x` with colors assigned to each cell.
#' * `"vector"`: a `character` vector of R colors, named by assigned
#' factor level.
#' @param verbose `logical` indicating whether to print verbose output.
#' @param debug `character` string used to enable detailed debugging output.
#' @param ... additional arguments are passed to downstream functions.
#'
#' @family jam color functions
#'
#' @returns output depends upon argument `return_type`:
#' * `"list"`: returns a `list` of colors defined by `colnames(x)`,
#' suitable for use with `ComplexHeatmap::HeatmapAnnotation()` for example.
#' * `"df"`: returns `data.frame` of colors with same dimensions as the
#' input `x`. Suitable for use with `jamba::imageByColors()` for example.
#' * `"vector"`: returns `character` vector of R colors, whose names represent
#' values in `x`, where the values should be substituted with the color.
#' Suitable for use with `ggplot2::color_manual(values=colors)`.
#'
#' In all cases, the `attributes()` of the returned object also includes
#' colors in the other two formats: `"color_sub"`, `"color_df"`, and
#' `"color_list"`.
#'
#' @examples
#' df <- data.frame(
#' genotype=rep(c("WT", "GeneAKO", "GeneBKO"), c(4, 8, 8)),
#' treatment=rep(rep(c("control", "treated"), each=2), 5),
#' class=rep(c("WT", "KO"), c(4, 16)),
#' time=c(rep("early", 4),
#' rep(rep(c("early", "late"), each=4), 2)))
#' df$sample_group <- jamba::pasteByRow(df[,c("genotype", "treatment", "time")])
#' df$sample_name <- jamba::makeNames(df$sample_group);
#' df$age <- sample(40:80, size=nrow(df));
#' df
#'
#' dfc <- design2colors(df,
#' group_colnames="genotype",
#' lightness_colnames="treatment",
#' class_colnames="class",
#' color_sub=c(age="dodgerblue"))
#'
#' # same as above except assign colors to columns and some values
#' dfc <- design2colors(df,
#' group_colnames="sample_group",
#' lightness_colnames="treatment",
#' class_colnames="genotype",
#' class_pad=5,
#' preset="dichromat2",
#' color_sub=c(KO="darkorchid3",
#' treatment="navy",
#' time="dodgerblue"))
#'
#' # same as above except assign specific group colors
#' dfc <- design2colors(df,
#' group_colnames="genotype",
#' lightness_colnames="treatment",
#' class_colnames="class",
#' preset="dichromat2",
#' color_sub=c(
#' WT="gold",
#' KO="purple3",
#' age="firebrick",
#' GeneAKO="firebrick3",
#' GeneBKO="dodgerblue",
#' treatment="navy",
#' time="darkorchid4"))
#'
#' dfc2 <- design2colors(df,
#' group_colnames="genotype",
#' lightness_colnames=c("time", "treatment"),
#' class_colnames="class",
#' preset="dichromat2")
#'
#' dfc3 <- design2colors(df,
#' group_colnames=c("genotype"),
#' lightness_colnames=c("time", "treatment"),
#' class_colnames="genotype",
#' preset="dichromat2")
#'
#' df1 <- df;
#' df2 <- subset(df, time %in% "early");
#' df12 <- rbind(df1, df2);
#' dfc12 <- design2colors(df12,
#' group_colnames="genotype",
#' lightness_colnames=c("time", "treatment"),
#' class_colnames="class",
#' preset="dichromat",
#' color_sub=c(
#' treatment="steelblue",
#' time="dodgerblue"
#' ))
#'
#'
#' @export
design2colors <- function
(x,
group_colnames=NULL,
lightness_colnames=NULL,
class_colnames=NULL,
ignore_colnames=NULL,
preset="dichromat2",
phase=1,
rotate_phase=-1,
class_pad=2,
end_hue_pad=0,
hue_offset=0,
desat=c(0, 0.4),
dex=c(2, 5),
Crange=NULL,#c(70, 120),
Lrange=NULL,#c(45, 90),
color_sub=NULL,
color_sub_max=NULL,
na_color="grey75",
shuffle_colors=FALSE,
force_consistent_colors=TRUE,
plot_type=c("table",
"list",
"none"),
return_type=c("list",
"df",
"vector"),
verbose=FALSE,
debug=c("none",
"cardinality"),
...)
{
#
if (length(x) == 0) {
return(NULL)
}
debug <- match.arg(debug);
# Convert SummarizedExperiment to data.frame using colData(x)
if ("SummarizedExperiment" %in% class(x)) {
x <- data.frame(check.names=FALSE,
SummarizedExperiment::colData(x));
} else if (inherits(x, "DataFrame")) {
x <- data.frame(check.names=FALSE,
stringsAsFactors=FALSE,
x);
} else if (inherits(x, "tbl_df") || inherits(x, "matrix")) {
x <- as.data.frame(x);
}
# optionally ignore some colnames
if (any(ignore_colnames %in% colnames(x))) {
x <- x[,setdiff(colnames(x), ignore_colnames), drop=FALSE];
}
# validate arguments
plot_type <- match.arg(plot_type);
return_type <- match.arg(return_type);
if (length(class_pad) != 1 || class_pad < 0 ) {
class_pad <- 1;
}
desat <- rep(desat,
length.out=2);
dex <- rep(dex,
length.out=2);
# validate color_sub as supplied
# - note that color names should be converted to hex
# in order to be compatible with some downstream tools
# such as knitr::kable(), jamba::kable_coloring().
# - recognized color ramps are left as-is as character names
# - all other values are converted to NA and ignored
if (length(color_sub) > 0 && "character" %in% class(color_sub)) {
# note if this step fails, re-use the input unchanged
Rcolors <- grDevices::colors();
# match hex or Rcolors
color_sub1 <- ifelse(
color_sub %in% Rcolors |
grepl("^#[0-9A-Fa-f]{6,8}$", color_sub),
color_sub,
NA)
color_sub1[!is.na(color_sub1)] <- jamba::rgb2col(
col2rgb(color_sub1[!is.na(color_sub1)]))
if (any(is.na(color_sub1))) {
# NA values may be color gradients
color_sub1_ramps <- sapply(color_sub[is.na(color_sub1)], function(i){
j <- tryCatch({
jamba::getColorRamp(i, n=3)
}, error=function(e){
NA
})
length(j) == 3
})
color_sub1[is.na(color_sub1)] <- ifelse(
color_sub1_ramps,
color_sub[is.na(color_sub1)],
NA)
}
names(color_sub1) <- names(color_sub)
color_sub <- color_sub1[!is.na(color_sub1)];
}
# handle each argument of colnames
if (length(group_colnames) > 0) {
if (is.numeric(group_colnames)) {
group_colnames <- jamba::rmNA(colnames(x)[group_colnames]);
}
}
if (length(lightness_colnames) > 0) {
if (is.numeric(lightness_colnames)) {
lightness_colnames <- jamba::rmNA(colnames(x)[lightness_colnames]);
}
}
if (length(class_colnames) > 0) {
if (is.numeric(class_colnames)) {
class_colnames <- jamba::rmNA(colnames(x)[class_colnames]);
}
}
# handle empty group_colnames
if (length(group_colnames) == 0 && length(class_colnames) == 0) {
group_colnames <- "added_rownames";
x$added_rownames <- rownames(x);
}
# review colnames before cardinality checks
if (verbose || "cardinality" %in% debug) {
if (length(lightness_colnames) > 0) {
jamba::printDebug("design2colors(): ",
"input lightness_colnames: ", lightness_colnames);
}
if (length(group_colnames) > 0) {
jamba::printDebug("design2colors(): ",
"input group_colnames: ", group_colnames);
}
if (length(class_colnames) > 0) {
jamba::printDebug("design2colors(): ",
"input class_colnames: ", class_colnames);
}
}
# check cardinality of class and group
card_changed <- FALSE;
if (length(class_colnames) > 0) {
if (length(group_colnames) == 0) {
group_colnames <- class_colnames;
if (verbose) {
jamba::printDebug("design2colors(): ",
c("No ","group_colnames",
" supplied, using ","class_colnames."),
sep="")
}
}
class_group_card <- cardinality(
x[, gsub("^-", "", class_colnames), drop=FALSE],
x[, gsub("^-", "", group_colnames), drop=FALSE]);
if ("cardinality" %in% debug) {
jamba::printDebug("design2colors(): ",
"class-to-group cardinality: ",
jamba::cPaste(class_group_card[c("from", "to")], sep="-to-"));
}
if (class_group_card["from"] != 1) {
card_changed <- TRUE;
if (class_group_card["to"] == 1) {
# cardinality is X-to-1
# switch group_colnames,class_colnames
group_colnames1 <- class_colnames;
class_colnames <- group_colnames;
group_colnames <- group_colnames1;
if (verbose || "cardinality" %in% debug) {
jamba::printDebug("design2colors(): ",
"due to ",
jamba::cPaste(class_group_card[c("from", "to")],
sep="-to-"),
c(" cardinality, ",
"group_colnames", " and ", "class_colnames",
" were switched."),
sep="")
}
} else {
# cardinality shows no X-to-1 relationship
if (class_group_card["from"] > class_group_card["to"]) {
# assign lightness_colnames if empty
if (length(lightness_colnames) == 0) {
lightness_colnames <- group_colnames;
for (ig in gsub("^-", "", lightness_colnames)) {
if (!ig %in% names(color_sub)) {
color_sub[ig] <- "grey45"
}
}
}
# combine class into group
group_colnames <- c(group_colnames, class_colnames)
if (verbose || "cardinality" %in% debug) {
jamba::printDebug("design2colors(): ",
"due to ",
jamba::cPaste(class_group_card[c("from", "to")],
sep="-to-"),
c(" cardinality, ",
"class_colnames",
" was combined into ",
"group_colnames."),
sep="")
}
} else {
# assign lightness_colnames if empty
if (length(lightness_colnames) == 0) {
lightness_colnames <- class_colnames;
for (ig in gsub("^-", "", lightness_colnames)) {
if (!ig %in% names(color_sub)) {
color_sub[ig] <- "grey45"
}
}
}
# switch group and class
group_colnames1 <- class_colnames;
class_colnames <- group_colnames;
group_colnames <- group_colnames1;
# now combine class into group
group_colnames <- c(group_colnames, class_colnames)
if (verbose || "cardinality" %in% debug) {
jamba::printDebug("design2colors(): ",
"due to ",
jamba::cPaste(class_group_card[c("from", "to")],
sep="-to-"),
c(" cardinality, ",
"group_colnames", " and ", "class_colnames",
" were switched."),
sep="")
jamba::printDebug("design2colors(): ",
"due to ",
jamba::cPaste(class_group_card[c("from", "to")],
sep="-to-"),
c(" cardinality, ",
"class_colnames",
" was combined into ",
"group_colnames."),
sep="")
}
}
}
# stop("class_colnames must have 1-to-X cardinality with group_colnames.");
}
}
# assign lightness_colnames if non-empty and not assigned
if (length(lightness_colnames) > 0) {
# check cardinality
group_lightness_card <- cardinality(
x[, gsub("^-", "", group_colnames), drop=FALSE],
x[, gsub("^-", "", lightness_colnames), drop=FALSE]);
if (group_lightness_card["from"] != 1) {
for (ig in gsub("^-", "", lightness_colnames)) {
if (!ig %in% names(color_sub)) {
color_sub[ig] <- "grey45"
}
}
}
}
# review colnames after cardinality checks
if (verbose || (card_changed && "cardinality" %in% debug)) {
jamba::printDebug("design2colors(): ",
"resolved lightness_colnames: ", lightness_colnames);
jamba::printDebug("design2colors(): ",
"resolved group_colnames: ", group_colnames);
jamba::printDebug("design2colors(): ",
"resolved class_colnames: ", class_colnames);
}
# sort by class, group, lightness to make downstream steps consistent
x_input <- x;
# quickly convert certain column types for better processing
# - convert table to numeric vector
for (icol in colnames(x_input)) {
if (is.table(x_input[[icol]])) {
x_input[[icol]] <- as.vector(x_input[[icol]]);
}
}
# iterate each column and convert to factor if needed
all_colnames1 <- gsub("^[-]---", "",
c(class_colnames,
group_colnames,
lightness_colnames));
for (xcol in all_colnames1) {
if (xcol %in% colnames(x)) {
if (!is.factor(x[[xcol]])) {
x[[xcol]] <- factor(x[[xcol]],
levels=unique(x[[xcol]]));
}
# jamba::printDebug("xcol:", xcol, ", levels:", levels(x[[xcol]]));# debug
} else {
xcol1 <- gsub("^-", "", xcol);
if (xcol1 %in% colnames(x)) {
if (!is.factor(x[[xcol1]])) {
x[[xcol1]] <- factor(x[[xcol1]],
levels=rev(unique(x[[xcol1]])));
}
# jamba::printDebug("xcol1:", xcol1, ", levels:", levels(x[[xcol1]]));# debug
}
}
}
x <- jamba::mixedSortDF(x,
byCols=c(class_colnames,
group_colnames,
lightness_colnames));
# jamba::printDebug("Sorted class,group,lightness data.frame:");print(x);# debug
class_colnames <- gsub("^[-]", "", class_colnames);
group_colnames <- gsub("^[-]", "", group_colnames);
lightness_colnames <- gsub("^[-]", "", lightness_colnames);
all_colnames <- c(class_colnames,
group_colnames,
lightness_colnames);
# convert character to factor to apply order each appears
for (icol in all_colnames) {
if (!is.factor(x[[icol]])) {
x[[icol]] <- factor(x[[icol]],
levels=unique(x[[icol]]));
}
}
# generate output per class, group, lightness values
xlist <- list(
class=jamba::pasteByRowOrdered(x[,class_colnames, drop=FALSE]),
group=jamba::pasteByRowOrdered(x[,group_colnames, drop=FALSE]),
lightness=jamba::pasteByRowOrdered(x[,lightness_colnames, drop=FALSE])
)
xlist <- jamba::rmNULL(xlist, nullValue="");
xdf <- data.frame(check.names=FALSE,
xlist);
# add class_group and class_group_lightness column values
xdf$class_group <- jamba::pasteByRowOrdered(
xdf[,c("group"), drop=FALSE]);
xdf$class_group_lightness <- jamba::pasteByRowOrdered(
xdf[,c("group", "lightness"), drop=FALSE]);
# sort table by class, group, lightness
xdf <- jamba::mixedSortDF(xdf,
byCols=c("class",
"group",
"lightness",
"class_group",
"class_group_lightness"));
if (verbose) {
jamba::printDebug("design2colors(): ",
"full class, group, lightness df:");
print(xdf);
}
udf <- unique(xdf);
if (verbose) {
jamba::printDebug("design2colors(): ",
"unique class, group, lightness df:");
print(udf);
}
gdf <- unique(udf[,c("class", "group", "class_group"), drop=FALSE]);
gdf$real <- 1;
gdf0 <- head(gdf, 1);
gdf0[1,] <- NA
rownames(gdf0) <- "class_pad";
if (verbose) {
jamba::printDebug("design2colors(): ",
"unique class, group df:");
print(gdf);
}
if (length(unique(gdf$class)) > 1) {
ibreaks <- jamba::breaksByVector(gdf$class)$breakPoints;
if (length(ibreaks) > 1) {
for (k1 in rev(seq_along(ibreaks))) {
k <- ibreaks[k1];
if (k1 == 1) {
hcp1 <- floor(class_pad * 1 / 2);
hcp2 <- class_pad;
} else if (k1 == length(ibreaks)) {
hcp1 <- 0;
hcp2 <- ceiling(class_pad * 1 / 2);
} else {
hcp1 <- 0;
hcp2 <- class_pad;
}
gdf_list <- c(
rep(list(gdf0), hcp1),
list(head(gdf, k)),
rep(list(gdf0), hcp2),
list(tail(gdf, -k)));
gdf <- jamba::rbindList(gdf_list);
}
}
}
if (length(end_hue_pad) > 0 && end_hue_pad > 0) {
gdf_list <- c(
list(gdf),
rep(list(gdf0), end_hue_pad));
gdf <- jamba::rbindList(gdf_list);
}
if (verbose) {
jamba::printDebug("design2colors(): ",
"expanded unique class, group df:");
print(gdf);
}
# assign color hue
n <- nrow(gdf);
#hue_offset <- 0;
hue_seq <- head(seq(from=0 + hue_offset,
to=360 + hue_offset,
length.out=n + end_hue_pad + 1), n);
gdf$hue <- hue_seq;
# subset of color_sub which is not a color ramp
color_sub_atomic <- jamba::vigrep("^#|[a-zA-Z]", color_sub)
# assign colors
class_group_hue1 <- jamba::nameVector(gdf[,c("hue", "class_group")])
class_group_hue <- colorjam::hw2h(class_group_hue1,
preset=preset);
# populate proper phase values
if (length(phase) == 1) {
phase_seq <- seq_len(sum(!is.na(gdf$real))) + abs(phase) - 1;
if (phase < 0) {
phase_seq <- rev(phase_seq);
}
} else {
phase_seq <- rep(abs(phase),
length.out=sum(!is.na(gdf$real)));
}
gdf$phase <- 1;
gdf$phase[!is.na(gdf$real)] <- phase_seq;
# calculate all required colors
use_step <- colorjam::colorjam_presets(preset=preset)$default_step;
use_colors <- colorjam::rainbowJam(n=nrow(gdf),
phase=gdf$phase,
preset=preset,
Crange=Crange,
Lrange=Lrange,
...);
gdf$class_group_color <- use_colors;
# subset for real entries, removing optional filler entries between classes
gdf <- subset(gdf, !is.na(real))
class_group_color <- gdf$class_group_color;
names(class_group_color) <- gdf$class_group;
# check for color_sub defined for each class_group
if (any(as.character(gdf$class_group) %in% names(color_sub_atomic))) {
# jamba::printDebug("Match color_sub to group name.");
imatch <- match(gdf$class_group, names(color_sub_atomic));
gdf$class_group_color[!is.na(imatch)] <- color_sub_atomic[
imatch[!is.na(imatch)]]
class_group_color <- gdf$class_group_color;
names(class_group_color) <- gdf$class_group;
}
# get mean color hue per class
class_hues <- NULL;
if (length(class_colnames) > 0) {
# calculate mean hue per class
class_color <- sapply(split(gdf$class_group_color, gdf$class), function(icolors){
#ihue <- jamba::col2hcl(icolors)["H",]
if (length(icolors) == 2) {
icolors <- icolors[c(1, 2, 2)];
}
icolor1 <- colorjam::blend_colors(icolors,
c_weight=0.9,
preset="ryb2")
# now determine most vibrant color with this hue
if (jamba::col2hcl(icolor1)["C",] < 85) {
icolor2 <- colorjam::vibrant_color_by_hue(
jamba::col2hcl(icolor1)["H",]);
# now blend the actual average with a more vibrant color in this hue
colorjam::blend_colors(c(icolor1, icolor2));
} else {
icolor1
}
})
gdf$class_color <- class_color[as.character(gdf$class)];
if (all(as.character(gdf$class) %in% names(color_sub_atomic))) {
class_color <- color_sub_atomic[as.character(gdf$class)];
}
color_sub_atomic[names(class_color)] <- class_color;
# assign into color_sub which has potential to overwrite
# color gradient if assigned to the same value
color_sub[names(class_color)] <- class_color;
}
if (verbose) {
jamba::printDebug("design2colors(): ",
"expanded unique class, group df, with colors:");
print(gdf);
}
# optionally rotate phase
phase <- phase + rotate_phase;
udf$class_group_hue <- class_group_hue[as.character(udf$class_group)]
udf$class_group_color <- class_group_color[as.character(udf$class_group)]
xdf$class_group_color <- class_group_color[as.character(xdf$class_group)];
class_group_lightness_color <- NULL;
if (any(duplicated(udf$class_group_color))) {
class_group_lightness_color <- jamba::color2gradient(udf$class_group_color,
dex=dex[1]);
names(class_group_lightness_color) <- udf$class_group_lightness;
udf$class_group_lightness_color <- class_group_lightness_color;
xdf$class_group_lightness_color <- class_group_lightness_color[as.character(xdf$class_group_lightness)];
} else {
class_group_lightness_color <- jamba::nameVector(
udf$class_group_color,
as.character(udf$class_group_lightness));
udf$class_group_lightness_color <- class_group_lightness_color;
xdf$class_group_lightness_color <- class_group_lightness_color[as.character(xdf$class_group_lightness)];
}
# jamba::printDebug("design2colors(): ",
# "udf:");
# print(udf);
# jamba::printDebug("design2colors(): ",
# "xdf:");
# print(xdf);
kcolnames <- intersect(
c("class_group_lightness_color",
"class_group_color"),
colnames(udf));
################################################################
# assign group colors when cardinality is appropriate
# Note: use all columns including class, group, lightness
# which allows various combinations to be detected and assigned.
if (length(rownames(x)) == 0 ||
any(!grepl("^[0-9]+$", rownames(x)))) {
# if rownames are entirely integers, do not assign categorical colors
iter_colnames <- jamba::nameVector(colnames(x));
} else {
iter_colnames <- jamba::nameVector(c("rownames", colnames(x)));
}
xmatch <- match(rownames(x), rownames(xdf));
new_colors <- lapply(iter_colnames, function(icol) {
# new in version 0.0.52.900: do not assign colors
# when color_sub is already defined
if (length(color_sub) > 0 && icol %in% names(color_sub)) {
return(NULL);
}
# skip numeric columns which will be assigned gradient colors
if (is.numeric(x[[icol]])) {
return(NULL);
}
if ("rownames" %in% icol) {
ivalues <- data.frame(`rownames`=rownames(x));
} else {
ivalues <- x[,icol, drop=FALSE]
}
for (kcolname in kcolnames) {
# jamba::printDebug(" kcolname: ", kcolname);
idf <- unique(data.frame(check.names=FALSE,
ivalues,
xdf[xmatch, kcolname, drop=FALSE]));
# jamba::printDebug("idf:");print(idf);# debug
if (any(duplicated(idf[[icol]]))) {
next;
} else {
kcolors <- jamba::nameVector(
as.character(idf[[2]]),
as.character(idf[[1]]));
# printDebug(kcolors);
return(kcolors);
}
}
return(NULL);
});
# create color vector
new_colors_v <- unlist(unname(new_colors));
new_color_list <- c(new_colors);
add_new_colors <- unlist(unname(new_color_list));
color_sub_1 <- color_sub;
color_sub <- c(color_sub_1,
add_new_colors)
# also add discrete colors to color_sub_atomic
color_sub_atomic[names(add_new_colors)] <- add_new_colors
############################################
# now generate colors for remaining columns
add_color_functions <- NULL;
colname_colors <- NULL;
# any empty element in new_colors lacks color assignment
if (any(lengths(new_colors) == 0)) {
add_colors_v1 <- NULL;
add_colnames <- names(new_colors)[lengths(new_colors) == 0];
######################################
# if color_sub matches colname,
# use that color with gradient effect
colname_colnames <- NULL;
if (any(add_colnames %in% names(color_sub))) {
colname_colnames <- intersect(add_colnames, names(color_sub));
add_colnames <- setdiff(add_colnames, colname_colnames);
# iterate remaining colnames and assign colors
colname_colors <- lapply(jamba::nameVector(colname_colnames), function(icol){
if (verbose > 1) {
jamba::printDebug("design2colors(): ",
"colname_icol: ", icol);
}
# handle by column data type
if (is.factor(x_input[[icol]])) {
if (verbose > 1) {
jamba::printDebug("design2colors(): ",
c(" is.factor=", "TRUE"), sep="");
}
ivalues <- levels(x_input[[icol]]);
} else if (is.numeric(x_input[[icol]])) {
# numeric columns will receive gradient color function
if (verbose > 1) {
jamba::printDebug("design2colors(): ",
c(" is.numeric=", "TRUE"), sep="");
}
color_max <- NULL;
if (length(color_sub_max) == 1) {
color_max <- color_sub_max
} else if (icol %in% names(color_sub_max)) {
color_max <- color_sub_max[[icol]];
}
icolors <- assign_numeric_colors(x=x_input[[icol]],
restrict_pretty_range=FALSE,
color_max=color_max,
color=color_sub[[icol]]);
return(icolors);
} else {
if (verbose > 1) {
jamba::printDebug("design2colors(): ",
c(" is.factor=", "FALSE"), sep="");
}
ivalues <- unique(as.character(x_input[[icol]]));
}
# use ivalues to define colors
icolors <- jamba::nameVector(
jamba::color2gradient(color_sub[[icol]],
dex=dex[2],
n=length(ivalues)),
ivalues);
if (verbose > 1) {
jamba::printDebug("design2colors(): ",
"ivalues: ", ivalues);
jamba::printDebug("design2colors(): ",
"icolors:")
jamba::printDebugI(icolors);
}
icolors;
});
if (verbose > 1) {
jamba::printDebug("design2colors(): ",
"colname_colors: ");
print_color_list(colname_colors);
}
new_color_list[names(colname_colors)] <- colname_colors;
new_color_list_fn <- (jamba::sclass(new_color_list) %in% "function")
new_color_atomic <- unlist(unname(new_color_list[!new_color_list_fn]));
color_sub <- c(color_sub,
new_color_atomic)
color_sub_atomic <- c(color_sub_atomic,
new_color_atomic)
}
###################################################
# all other values are assigned categorical colors
if (verbose > 1) {
jamba::printDebug("design2colors(): ",
"add_colnames: ", add_colnames);
}
# assemble all remaining column values for color assignment
if (length(add_colnames) == 0) {
add_numeric_colnames <- NULL;
} else {
add_numeric_colnames <- add_colnames[sapply(add_colnames, function(icol){
is.numeric(x_input[[icol]])})];
}
# assemble all unique values that need colors,
# using column name itself for numeric columns