Vega sort heatmap by clicking on axis tick label

141 views
Skip to first unread message

Aaron Friedman

unread,
Mar 24, 2021, 10:49:18 AM3/24/21
to vega-js
I have created the below configuraton from taking a Vega Lite example, getting its parsed spec, and modifying it. The original chart was a heatmap that you can click to select a square/bin with axes to the left and bottom. I moved the x-axis is to the top and made it  clickable. If you click one of the tick labels on the x-axis to broadcasts 'datum' to a text mark. The result is text that displays the value you clicked overlayed on the original clickable graph.

I wish to further modify this configuration to provide the ability to sort by a specifc column. When the user clicks on the axis tick, instead of broadcasting the data to an overlay, I wish to sort the rows based on the intensity values for that specific column. For instance clicking on the tick label for second column here:
   x
1 3 1 1
2 1 2 2
3 2 3 3

would create:
   x
2 1 2 2
3 2 3 3
1 3 1 1

I would assume if this is possible it would involve tying a Transform to my axis tick signal. Is this possible in Vega? If so how do I acheive this?

The configuration in the Vega editor:
https://vega.github.io/editor/#/url/vega/N4IgJAzgxgFgpgWwIYgFwhgF0wBwqgegIDc4BzJAOjIEtMYBXAI0poHsDp5kTykSArJQBWENgDsQAGhBMkUANZkATmwbiAJmhAB3GHTjSQOJBo01xZNAJkRMATwA2h9FDiPHRjUkwpUAbVBxJAQXDBoyGEcIrAB9OzZlQwBfKSCQsLEGZTdYgAYjYiRHBjgINECQeUwGYu0AQSMcJPMoTDgtdEaZKDVxTDQARgBmVNBq2s8uppaaNo7tACEjXvUB1DyxqrbJhpmOufbOkABhFb71zbTtmrr0ZZlmg-nj7pBV-rQr8Z27kAfjLMXktzmsht8brt7vtWkdtGcehc0AA2LYTP4IwHPOHTRFgjZo35TU4ww4LaF4z4E67o4mYp6w8kkynrACcyQAultgqFtN5fPkjFkcpk1CLBTJMMokOIIAAzRIICqgBw4MI0DRwfo0OU0ODKIxIcroWLEMgQDWxEBbVVhXWOdoGmRwAAezW0NAgADVihqABT8hgIfwAHXeFzDHIAlAACABkcZjnoAYhYDH6ANSB4Nhj6YSNR61cjm2CLBRzGyo8zJl4qxMhwTCxF2xJAuz2xRxIJjuQrFUraIwSZUgOCkfrGkAAAWQygUzdb7Ygne77nwUGiiiMDBw-LC2coRRKKS512r2hbdjgOD7x7QACYIef0DoNfRt7ufGE5JoICY3AGbDIBYfoAOQuqBUaUM4lj0FIMZ5PBeSxgAVDGl7tDe3IZNo9jxJht4Dqgj7Yby6DwDEAwyDue7aD+Gh-vIcCAcB4hgfYkHQVqZBwQhSGoTGeFXlhZ44eg6h0IRLjAKkIDDgEoBjlqmCTggagQIgbCkB+tHoJ6AAqO7OH6KhqDgfpRrGAD8MamTuFkxqgMYSQMnKkWE+iRNEkRUSANFftoxCOAAyu4cBtOw4gAEplGwjikH6YaeVElH4YkcBhvBYYSRImUxlKpTwQVcCFu52jJd5cQ1DgzhDpICmjuOKkjsKbjaNAbBqkYtraBucwKEW1Gfu0fI+EG8aJgYCAWZQs4KLNSBzraMYAIQALxrTGoF2TgoExjZwAuU5YZ5bq7gMU5FWpdVzixGdjgMfBR6lPgMb+H6U0zZ6XqJBIbA0PtMYHtmjlA2NCBRqGICmualqRlyoPiAwHhGAqIpoMVWxKROLVim16DEHqOjdfYXXoBoTCOH1W6ckNukgEjKNuaJZHhF511GXAd16g95QyM9LiVD16AAKKozzxwwxaGhWsz6Ss1dPmxJgbBkGQtX8-2LhysUGkyPJlTY81DWtZkvRdZKpNhNTA2035w1hEblAQPocqYAA0nA9jWtcRuVqApuBUTJNkyAFNU5utslvb9M6xWJ5leRMSVU2anmHK3v6-VhtNcaAe1sSitVZzPsxwF6Bpzq9iJWzKVKwkSR5UXTYq2rziA4zjig83yuc-BPet+rcAd8jXdOcV-fJ9dqtD4DA+c4jo+lcWMhzf7DNiSAa8h2ESRtEKDi1QEIB7wM0cWI61Q0NpqATyAcqqEqqCgPyfhhz4SCCrJWq9JqaCgP5Eaz8QB2FUAoOAAB1N8MB-4gAFg+WS9opgBygMUO0NAUYyHuscPMpdQFsHASOdodhtAwR4jAAMH8a49wbhlEAlkJoxiCqFZwEUJD6TKJgahU964q0btIMGNQIZSTol2Gm1x4GoE7nbTq8g6DewasQ9YIAVpkPoJQ3w3D2a8PSmGBhAAffRTCQphTYeIDhdgtF1ziLQvK2ZCyazvKgQYWxJF5EoAIO2AAvCwmoXREM4doVR3F1GvysSnNK-CDFGOYaYzAkULFcKSjwmxfC6HwXsSI5xritZfDtpqaAygaA4HifJfOZByzaDDLSY6IAYwZhjB9b0voNAaKDFDWkBZAbZg6USSMoMTogCzODXptxHBdIaWGAA3DGBkZINC1PqY076LS2k5ixIyDQXSbI9LDHMl4-TalhmGUIqG+yjgTJjNMmMeZFkNL9GjZAXDdnhjWJGLKIA9GlRkP44B0A0EXnFudbQ5yFiyVfBod8fzUFHxAP4mQ9EhiyQUSggF6BM73wltoWkpcKI+Vgf82FGLEXZOSGS64wsQDtBdL5H+bA-7AMAdJOFsDJGDCuCAFFcDcmoAEBy6l6xymVPQBaCpdYGxNhbG2DsXYeyeEQRITAwUaBeOZZI4YqIsEYOQdypxJ8wVkrtoSsoI5nwsstqHRFMgNBASQBYWBr9RoCgKFq4F6BQVaFsIkdYmMZDSksMy4SBKC4Xnwtea0skTBmAsGQAAkuIcQ+oviPFMOYSwAB5Bgjo8kszCBiylVqw62vtcAx15MP4SkxW6yEdQvXKB9coUosl-UNgJQRP5Ib0VhqwpG1NMb42JoNBsFN0aM1ZqTdSeW6DMFUqtqQiwcBFpeGLfVF+H8nWfxdVWh6vUkTNplK28ii7MDIBvDIC++ocBxXLhgDcRhVWqDQHHDSds2wmpNjCsI8K5JFOUtoFWZ6QAqA1E+3WcBJR0FhR6owsr3D1G8vVEAzg3YwdXI4eolgj73gAOyISQ2hxYRp3ALu0AgDUGgNYgB8ZoV0QxnTiF-sy2DFZYFmrmguaVy5mN8xABe6UEUb53yZbApBrKeWyDvOSjAWkJ2gFE8AyR+qtCGvJaiolQ5f1UiQ3AFDMhgOdGfeBqlkGwg4pkNRvxSLo69HELqKwwCW3MulAgG80LuB5vcI4NgOgVBwC1JTAcUnOMOpXaB+OhqgA 

Full configuration:
{
  "background": "white",
  "padding": 5,
  "style": "cell",
  "data": [
    {"name": "highlight_store"},
    {
      "name": "source_0",
      "values": [
        {"actual": "A", "predicted": "A", "count": 13},
        {"actual": "A", "predicted": "B", "count": 0},
        {"actual": "A", "predicted": "C", "count": 0},
        {"actual": "B", "predicted": "A", "count": 0},
        {"actual": "B", "predicted": "B", "count": 10},
        {"actual": "B", "predicted": "C", "count": 6},
        {"actual": "C", "predicted": "A", "count": 0},
        {"actual": "C", "predicted": "B", "count": 0},
        {"actual": "C", "predicted": "C", "count": 9}
      ]
    },
    {
      "name": "data_0",
      "source": "source_0",
      "transform": [
        {"type": "identifier", "as": "_vgsid_"},
        {
          "type": "filter",
          "expr": "isValid(datum[\"count\"]) && isFinite(+datum[\"count\"])"
        }
      ]
    }
  ],
  "signals": [
    {
      "name": "signal_get_x_axis_label",
      "value": "",
      "on": [{"events": "@mark_x_axis_labels:click", "update": "datum.value"}]
    },
    {"name": "x_step", "value": 20},
    {"name": "width", "update": "bandspace(domain('x').length, 0, 0) * x_step"},
    {"name": "y_step", "value": 20},
    {
      "name": "height",
      "update": "bandspace(domain('y').length, 0, 0) * y_step"
    },
    {
      "name": "unit",
      "value": {},
      "on": [
        {"events": "mousemove", "update": "isTuple(group()) ? group() : unit"}
      ]
    },
    {
      "name": "highlight",
      "update": "vlSelectionResolve(\"highlight_store\", \"union\", true, true)"
    },
    {
      "name": "highlight_tuple",
      "on": [
        {
          "events": [{"source": "scope", "type": "click"}],
          "update": "datum && item().mark.marktype !== 'group' ? {unit: \"\", fields: highlight_tuple_fields, values: [(item().isVoronoi ? datum.datum : datum)[\"_vgsid_\"]]} : null",
          "force": true
        },
        {"events": [{"source": "view", "type": "dblclick"}], "update": "null"}
      ]
    },
    {
      "name": "highlight_tuple_fields",
      "value": [{"type": "E", "field": "_vgsid_"}]
    },
    {
      "name": "highlight_toggle",
      "value": false,
      "on": [
        {
          "events": [{"source": "scope", "type": "click"}],
          "update": "event.shiftKey"
        },
        {"events": [{"source": "view", "type": "dblclick"}], "update": "false"}
      ]
    },
    {
      "name": "highlight_modify",
      "on": [
        {
          "events": {"signal": "highlight_tuple"},
          "update": "modify(\"highlight_store\", highlight_toggle ? null : highlight_tuple, highlight_toggle ? null : true, highlight_toggle ? highlight_tuple : null)"
        }
      ]
    }
  ],
  "marks": [
    {
      "name": "marks",
      "type": "rect",
      "style": ["rect"],
      "interactive": true,
      "from": {"data": "data_0"},
      "encode": {
        "update": {
          "strokeWidth": {"value": 2},
          "fill": {"scale": "fill", "field": "count"},
          "stroke": [
            {
              "test": "length(data(\"highlight_store\")) && vlSelectionTest(\"highlight_store\", datum)",
              "value": "black"
            },
            {"value": null}
          ],
          "opacity": [
            {
              "test": "!length(data(\"highlight_store\")) || vlSelectionTest(\"highlight_store\", datum)",
              "value": 1
            },
            {"value": 0.5}
          ],
          "zindex": [
            {
              "test": "!length(data(\"highlight_store\")) || vlSelectionTest(\"highlight_store\", datum)",
              "value": 1
            },
            {"value": 0}
          ],
          "description": {
            "signal": "\"actual: \" + (isValid(datum[\"actual\"]) ? datum[\"actual\"] : \"\"+datum[\"actual\"]) + \"; predicted: \" + (isValid(datum[\"predicted\"]) ? datum[\"predicted\"] : \"\"+datum[\"predicted\"]) + \"; count: \" + (format(datum[\"count\"], \"\"))"
          },
          "x": {"scale": "x", "field": "predicted"},
          "width": {"scale": "x", "band": 1},
          "y": {"scale": "y", "field": "actual"},
          "height": {"scale": "y", "band": 1}
        }
      }
    },
    {
      "type": "text",
      "encode": {
        "update": {
          "x": {"value": 10},
          "y": {"value": 50},
          "text": {"signal": "signal_get_x_axis_label"},
          "fontSize": {"value": 36},
          "fill": {"value": "red"}
        }
      }
    }
  ],
  "scales": [
    {
      "name": "x",
      "type": "band",
      "domain": {"data": "data_0", "field": "predicted", "sort": true},
      "range": {"step": {"signal": "x_step"}},
      "paddingInner": 0,
      "paddingOuter": 0
    },
    {
      "name": "y",
      "type": "band",
      "domain": {"data": "data_0", "field": "actual", "sort": true},
      "range": {"step": {"signal": "y_step"}},
      "paddingInner": 0,
      "paddingOuter": 0
    },
    {
      "name": "fill",
      "type": "linear",
      "domain": {"data": "data_0", "field": "count"},
      "range": "heatmap",
      "interpolate": "hcl",
      "zero": false
    }
  ],
  "axes": [
    {
      "scale": "x",
      "orient": "top",
      "grid": false,
      "title": "predicted",
      "labelAlign": "left",
      "labelAngle": 270,
      "labelBaseline": "middle",
      "zindex": 1,
      "encode": {
        "labels": {
          "name": "mark_x_axis_labels",
          "interactive": true,
          "update": {"fill": {"value": "blue"}},
          "hover": {"fill": {"value": "red"}}
        }
      }
    },
    {
      "scale": "y",
      "orient": "left",
      "grid": false,
      "title": "actual",
      "zindex": 1
    }
  ],
  "config": {
    "range": {"ramp": {"scheme": "yellowgreenblue"}},
    "axis": {"domain": false}
  }
}

Reply all
Reply to author
Forward
0 new messages