cfad5f46e806d68168b04dad2891310a6c2dc8d6
jcasper
  Mon Mar 31 09:10:05 2025 -0700
More feature complete heatmaps (figure legend, color options), refs #31812

diff --git src/hg/hgTracks/heatmap.h src/hg/hgTracks/heatmap.h
index 39b8bd15f1c..f5b6ae490be 100644
--- src/hg/hgTracks/heatmap.h
+++ src/hg/hgTracks/heatmap.h
@@ -6,67 +6,68 @@
 
 struct heatmap
     {
     struct heatmap *next;
     char *label;
     int rows, cols;  // number of rows and columns
     double rowHeight, colWidth; // extent of a row or column in pixels; might be fractional
     int *rowStarts, *rowSizes;  // dimensions in pixels
     int *colStarts, *colSizes;  // dimensions in pixels
     char **rowLabels;
     char **colLabels; // not used at present, maybe later
     char **scores;
     char *special;
     char **itemLabels;
     boolean *isNA; // override for the scores table when a value was set to NA.  Consider defaulting to True
-    double highCap; // scores at or above this level are saturated in the heatmap
-    double lowCap; // scores at or below this level are saturated in the heatmap
-    double midPoint; // the score where we transition from the low spectrum to the high spectrum
-    struct rgbColor *lowColor, *midColor, *highColor;
-    Color *lowSpectrum;
-    Color *highSpectrum;
+    // Heatmap coloring is done by setting an ascending series of score thresholds, each with an associated color.
+    // The color for any given score is found by finding which pair of thresholds the score lies between, and
+    // then using a color spectrum that lies between the associated colors for those two thresholds.
+    int thresholdCount;
+    double *scoreThresholds;
+    struct rgbColor *thresholdColors;
+    Color **spectra;
+    char *legend;
     };
 
 
 struct heatmap *heatmapNew(int rows, int cols);
 /* Allocate a new heatmap structure and return it */
 
 void heatmapSetDrawSizes(struct heatmap *h, double rowHeight, double colWidth);
 /* Update the expected cell height and width in pixels for the heatmap */
 
 void heatmapAssignRowLabels(struct heatmap *h, char **rowLabels);
 /* Planned abstraction for assigning row labels in a heatmap */
 
 void heatmapAssignColumnLabels(struct heatmap *h, char **rowLabels);
 /* Planned abstraction for assigning column labels in a heatmap */
 
-struct heatmap *heatmapFromLF(struct track *tg, struct linkedFeatures *lf);
+struct heatmap *heatmapForLF(struct track *tg, struct linkedFeatures *lf);
 /* This takes a linkedFeatures structure from a bigBed and builds a heatmap out
  * of it.  In the current setup, the heatmap values and labels are each stored
  * in a 1-D comma-separated matrix that we convert to a 2-D matrix using
  * row-major ordering.
  * The column count is the number of exons in the LF structure; the number of
  * rows is in an extra field.  The full type definition is in hg/lib/heatmap.as.
  */
 
 void heatmapDrawItemAt(struct track *tg, void *item, struct hvGfx *hvg,
                          int x, int y, double scale, MgFont *font,
                          Color color, enum trackVisibility vis);
 
-void heatmapMakeSpectrum(struct heatmap *h, struct hvGfx *hvg, double lowScore, struct rgbColor *lowColor,
-        double midPoint, struct rgbColor *midColor, double highScore, struct rgbColor *highColor);
-/* Take high, low, and midpoints and associated colors, and create two spectrums (one for low scores, one for high)
+void heatmapMakeSpectrum(struct heatmap *h, struct hvGfx *hvg);
+/* Take the thresholds and associated colors, and create a spectrum for each pair of adjacent thresholds
  * that can be used to shade cells in the heatmap.
  */
 
 int heatmapItemRowCount(struct track *tg, void *item);
 /* This returns the number of rows in a spaceSaver structure that this heatmap will
  * occupy.  This is expected to be the number of rows in the heatmap plus 1.  The
  * extra row is for the item label for the heatmap, since the space to the left is
  * already occupied by the row labels.
  */
 
 void heatmapMethods(struct track *tg);
 /* This is intended to be invoked after commonBigBedMethods has already been applied to a track (really,
  * it's only expected to be invoked as part of bigBedMethods).
  * Cribbed from messageLineMethods, which also needs to check vis early on to decide what to do.
  * Here, we only want to set up to draw heatmaps if we're not in dense mode.