
heatmap with text annotations

Hi everyone!
I'm trying to plot a heatmap with text annotations in Igor Pro. Below is a minimal working example (MWE) that demonstrates what I want to achieve.
However, it seems that the 2D wave gets transposed during imaging (`AppendImage`), which makes it confusing to correctly place text annotations and set tick labels. I have to treat rows as columns (and vice versa) to compensate for this behavior, which feels counterintuitive and goes against the literal meaning of the variable names.
Is there a more elegant or idiomatic way to handle this in Igor Pro?
Here is the MWE in Igor Pro:
#pragma TextEncoding = "UTF-8" #pragma rtGlobals=3 // Use modern global access method and strict wave access. Function heatmap() // == Input Wave == SetRandomSeed 3389 make/O/N=(3,5) heatmapData = gnoise(1) // == Imaging == Variable numRow = DimSize(heatmapData, 0); Variable numCol = DimSize(heatmapData, 1); Make/O/N=(numCol,numRow) heatmapDataTransposed = heatmapData[q][p] printf "(Debug) numRow: %d, numCol: %d\n", numRow, numCol Display AppendImage heatmapData ModifyImage heatmapData ctab= {*,*,Rainbow,0} make/O/N=(numCol) xTick = p make/O/N=(numRow) yTick = p Make/O/T/N=(numCol) xTickLabel Make/O/T/N=(numRow) yTickLabel Variable row, col String xTickStr, yTickStr for(col = 0; col < numCol; col++) sprintf xTickStr, "X%d", col xTickLabel[col] = xTickStr endfor for(row = 0; row < numRow; row++) sprintf yTickStr, "Y%d", row yTickLabel[row] = yTickStr endfor ModifyGraph userticks(left)={xTick,xTickLabel} ModifyGraph userticks(bottom)={yTick,yTickLabel} ModifyGraph width={Aspect,1} SetAxis/A/R left ColorScale/C/N=text0/F=0/Z=1/B=1/A=RC/E image=heatmapData,axisRange={-2,2} Variable xAttach String tagName, textLabel for (col=0; col < numCol; col++) for (row = 0; row < numRow; row++) xAttach = Sub2Ind({numRow, numCol}, {row, col}) sprintf tagName, "tag_%d_%d", row, col sprintf textLabel, "%0.2f", heatmapData[row][col] print tagName, xAttach, textLabel Tag/C/N=$(tagName)/F=0/B=1/Z=1/X=0.00/Y=0.00/L=0 heatmapData, xAttach,textLabel endfor endfor End Function Sub2Ind(dimSizes, indices) // dimSizes: 1D wave of sizes, e.g. {3,4,5} // indices: 1D wave of 0-based subscripts, e.g. {1,2,0} // Returns: 0-based linear index (column-major order) Wave dimSizes, indices Variable numDimensions = numpnts(dimSizes) Variable numIndices = numpnts(indices) if (numIndices != numDimensions) Abort "Number of indices must match number of dimensions" endif Variable i String errorMessageStr // Check for out-of-bounds indices for (i = 0; i < numDimensions; i += 1) if (indices[i] < 0 || indices[i] >= dimSizes[i]) sprintf errorMessageStr, "Index out of bounds: indices[%d] = %d, expected 0 to %d", i, indices[i], dimSizes[i] - 1 Abort errorMessageStr endif endfor Variable linearIndex = 0 Variable stride = 1 for (i = 0; i < numDimensions; i += 1) linearIndex += indices[i] * stride stride *= dimSizes[i] endfor return linearIndex End
For comparison, here's a Python version using `matplotlib` that produces the expected result.
(Note: The random values differ, possibly due to different random number generators, but that’s not a concern.)
import numpy as np import matplotlib.pyplot as plt def heatmap_demo(): # Generate data np.random.seed(3389) data = np.random.normal(loc=0.0, scale=1.0, size=[3, 5]) # Shape: (rows, columns) print(data) # Define labels num_rows, num_cols = data.shape x_labels = [f"X{i + 1}" for i in range(num_cols)] y_labels = [f"Y{i + 1}" for i in range(num_rows)] # Create the heatmap fig, ax = plt.subplots() im = ax.imshow(data, cmap='rainbow') # Set axis ticks ax.set_xticks(np.arange(num_cols)) ax.set_yticks(np.arange(num_rows)) # Set tick labels ax.set_xticklabels(x_labels) ax.set_yticklabels(y_labels) # Rotate x-axis labels if needed plt.setp(ax.get_xticklabels(), rotation=0, ha="center", va="top") # Add text annotations for i in range(num_rows): for j in range(num_cols): text = f"{data[i, j]:.2f}" ax.text(j, i, text, ha="center", va="center", color="black") # Make layout tight and show plt.title("Heatmap with Custom Tick Labels") plt.colorbar(im) plt.tight_layout() plt.show() def main(): heatmap_demo() if __name__ == "__main__": main()


what about if you change the make command to:
make/O/N=(5,3) heatmapData = gnoise(1)
May 7, 2025 at 06:32 am - Permalink
You may be confused by the table and the image representations of an array within Igor. Note that the 'rows' are always displayed on the horizontal / x axis both in image and line plots while columns run along the vertical / y axis (unless you deliberately swap axes). In a table, however, the representation is flipped, i.e., rows always run vertical and columns horizontal in a table. This has apparently historic reasons, about which you can read more here:
https://www.wavemetrics.com/forum/general/translating-matlab-matrix-mul…
May 7, 2025 at 09:14 am - Permalink
In reply to what about if you change the… by ChrLie
Thank you for your reply! I worked around the issue in a similar way by using
heatmapDataTransposed
instead ofheatmapData
, which makes me feel better.May 8, 2025 at 05:41 am - Permalink
In reply to You may be confused by the… by chozo
Thank you for clearing that up.
May 8, 2025 at 05:44 am - Permalink