Is there a cleaner way to draw a heatmap using Lilaq?

Hello,

my current solution is this:

#let words = (
  "first",
  "second",
  "third",
  "fourth",
  "fifth",
  "sixth",
  "seventh",
  "eighth",
  "ninth",
  "tenth",
  "eleventh",
  "tvelveth"
);
#let len = words.len()

#let dia = lq.diagram(
  width: 10cm, height: 10cm,
  lq.colormesh(
    lq.linspace(0, len - 1, num: len),
    lq.linspace(0, len - 1, num: len),
    (x, y) => x * y, // for illustration purposes, for real data use the array
        // data
        //  .at(calc.floor(y))
        //  .at(calc.floor(x)),
    map: color.map.viridis
  ),

  xaxis: (
    ticks: words.map(rotate.with(-90deg, reflow: true)).enumerate(),
    subticks: none,
    position: top,
  ),

  yaxis: (
    ticks: words.enumerate(),
    subticks: none,
    inverted: true,
  ),
)

#let legend = lq.diagram(
  width: 0.25cm,
  height: 10cm,
  lq.colormesh(
    interpolation: "smooth",
    lq.linspace(0, 1),
    lq.linspace(0, 1),
    (x, y) => y
  ),
  xaxis: none,
  yaxis: (
    position: right,
    subticks: none,
    tick-distance: 0.2,
  )
)

#figure(
  grid(
    align: bottom,
    gutter: 0.5cm,
    columns: 2,
    dia,
    legend,
  ),
  scope: "parent",
  placement: auto,
  caption: [My heatmap]
) <heat-map>

And the result looks something like this:

Still, something tells me this can be implemented easier and cleaner. Any suggestions?

2 Likes

My suggestion is to request the addition of a continuous legend for the heatmap diagram.

#import "@preview/lilaq:0.3.0" as lq

/// Style for heatmap diagram.
#let heatmap-style(doc) = {
  set rotate(reflow: true)
  show: lq.set-diagram(
    xaxis: (subticks: none, position: top),
    yaxis: (subticks: none, inverted: true),
    width: 10cm,
    height: 10cm,
  )
  doc
}

/// Continuous legend style for heatmap diagram.
#let heatmap-legend-style(doc) = {
  show: lq.set-diagram(
    yaxis: (position: right, subticks: none, tick-distance: 0.2),
    xaxis: none,
    width: 0.25cm,
    height: 10cm,
  )
  doc
}

#let heatmap-with-legend(diagram, range) = grid(
  columns: 2,
  align: bottom,
  gutter: 0.5cm,
  heatmap-style(diagram),
  heatmap-legend-style(lq.diagram(lq.colormesh(
    interpolation: "smooth",
    range,
    range,
    (_, y) => y,
  ))),
)

#let words = (
  "first",
  "second",
  "third",
  "fourth",
  "fifth",
  "sixth",
  "seventh",
  "eighth",
  "ninth",
  "tenth",
  "eleventh",
  "twelveth",
)

#let length = words.len()
#let x = lq.linspace(2, 20, num: length)
#let y = lq.linspace(-4, 15, num: length)

#figure(
  heatmap-with-legend(
    lq.diagram(
      xaxis: (ticks: x.zip(words.map(rotate.with(-90deg)))),
      yaxis: (ticks: y.zip(words)),
      lq.colormesh(x, y, (x, y) => x * y, map: color.map.viridis),
    ),
    lq.linspace(0, 1),
  ),
  caption: [My heatmap],
) <heat-map>

image

1 Like

Hi @Dmitriy, let me tell you a secret but you gotta keep it ;)

There exists lq.colorbar and you can use it like this


#show: lq.set-diagram(
  height: 10cm,
)

#let heatmap = lq.colormesh(
  lq.linspace(0, len - 1, num: len),
  lq.linspace(0, len - 1, num: len),
  (x, y) => x * y, 
)

#lq.diagram(
  width: 10cm, 

  heatmap,

  xaxis: (
    ticks: words.map(rotate.with(-90deg, reflow: true)).enumerate(),
    subticks: none,
    position: top,
  ),

  yaxis: (
    ticks: words.enumerate(),
    subticks: none,
    inverted: true,
  ),
)
#lq.colorbar(heatmap)

It takes the numeric data and automatically scales everything as it should be.

This feature is (almost) entirely undocumented and for a reason: I’m not yet decided on the usage design − should it be something extra that the user needs to place on their own (like it is currently − more powerful but also more manual) or should it be just something you call within lq.diagram and it’s automatically placed somewhere? And there are many more questions concerning the API.

But you can try it out and give feedback on the rough idea of it (I know that there are still severe limitations, for example the height of the color bar can only be set through a set rule and the width not at all).

2 Likes

Oh and for some wicked reason, the tick labels sometimes don’t line up with the ticks on the color bar. I haven’t yet figured out why :/

2 Likes