【问题标题】:Selection Transform as Text/Title选择转换为文本/标题
【发布时间】:2021-02-17 03:49:28
【问题描述】:

如何将选定点的变换显示为标题或副标题中的文本?

{
  "$schema": "https://vega.github.io/schema/vega-lite/v4.json",
  "data": {"url": "data/stocks.csv"},
  "transform": [{"filter": "datum.symbol==='GOOG'"}],
  "width": 800,
  "title": {
    "text": "Google's stock price over time.",
    "subtitle": "selected average: ???"
  },
  "selection": {
    "interval": {
      "type": "interval",
      "encodings": ["x"]
    }
  },
  "mark": "point",
  "encoding": {
    "x": {"field": "date", "type": "temporal"},
    "y": {"field": "price", "type": "quantitative"}
  }
}

求和变换不适合任何图形/视觉表示。一个想法是将其作为选择工具提示,但 is not possible。所以我选择了字幕。

【问题讨论】:

    标签: data-visualization vega-lite vega


    【解决方案1】:

    我认为没有一种简单的方法可以在字幕中插入计算值,但是您可以使用带有包含计算值的文本标记的层状图来实现相同的效果。

    例如(open in editor):

    {
      "data": {"url": "data/stocks.csv"},
      "transform": [{"filter": "datum.symbol==='GOOG'"}],
      "width": 800,
      "title": {"text": "Google's stock price over time."},
      "layer": [
        {
          "transform": [
            {"filter": {"selection": "interval"}},
            {"aggregate": [{"op": "sum", "field": "price", "as": "total_price"}]}
          ],
          "mark": "text",
          "encoding": {
            "x": {"value": 400},
            "y": {"value": -10},
            "text": {"field": "total_price", "type": "quantitative"}
          }
        },
        {
          "mark": "point",
          "encoding": {
            "x": {"field": "date", "type": "temporal"},
            "y": {"field": "price", "type": "quantitative"}
          },
          "selection": {"interval": {"type": "interval", "encodings": ["x"]}}
        }
      ]
    }
    

    请注意,文本层具有xy 编码值,以图表左上角的像素为单位。

    【讨论】:

      【解决方案2】:

      我本来想说有两种可能的方法,但后来@jakevdp 发布了his answer,所以我猜现在有三种。

      1. title.subtitle 中使用ExprRef。虽然documentation 没有明确支持这一点,但只有title.text 支持ExprRef 是没有意义的。所以我试了一下,效果很好。尽管 Vega 编辑器仍会引发模式验证警告。下面我有一个使用分箱数据的更复杂的示例。

        1. 过滤和聚合选择。这将修改当前数据流,并且 Vega-Lite 不支持多个数据流(除非使用层),因此您必须修补生成的 Vega。
        2. 使用title.encode.subtitle.update.text 访问标题的文本标记。再次,需要修补生成的 Vega。 (这是假设字幕不能是ExprRef)。

      (2) 的巧妙之处在于它允许向selection mark 添加文本标记,因此可以使文本跟随选择。也就是说,@jakevdp 的answer 也可以做到这一点,而且他的回答要简单得多。


      JavaScript 加载程序确保不会出现任何乱序:

      %%javascript
      
      function loadjs(ls, o) {
          let i = 0;
          let b = JSON.parse(document.querySelector('#jupyter-config-data').text)["baseUrl"];
          ls = ls.reduce((a,l) => {
              if (!l.path)
                  return a;
              let p = l.path;
              if (l.local)
                  p = "/" + [b,"/files",p].map(s => s.replace(/^\/+|\/+$/, "")).filter(i => i).join("/");
              if (document.querySelector("script[src='" + p + "']"))
                  return a;
              return [...a, p]; 
          }, [])
          function load() {
              if (i >= ls.length)
                  return o();
              let t = document.createElement("script");
              [t.type, t.src, t.onload] = ["text/javascript", ls[i], (i+1>=ls.length) ? o : load];
              i = i+1;
              document.head.appendChild(t);
          }
          return load();
      }
      window.loadjs = loadjs
      

      作为 IPython 魔法的 JupyterLab 单元的 JavaScript 加载器:

      import IPython
      import IPython.core.magic as ipymagic
      
      @ipymagic.magics_class
      class LoadJSMagics(ipymagic.Magics):
      
          @ipymagic.cell_magic
          def loadjs(self, line, cell):
              js = f"loadjs({line}, () => {{\n{cell}\n}});"
              return IPython.display.Javascript(js)
      
      IPython.get_ipython().register_magics(LoadJSMagics)
      

      JupyterLab 单元的 Jinja 模板作为 IPython 魔法:

      import jinja2
      import IPython
      import IPython.core.magic as ipymagic
      
      @ipymagic.magics_class
      class JinjaMagics(ipymagic.Magics):
      
          @ipymagic.cell_magic
          def jinja(self, line, cell):
              t = jinja2.Template(cell)
              r = t.render({k:v for k,v in self.shell.user_ns.items() if k not in self.shell.user_ns_hidden})
              IPython.get_ipython().run_cell(r)
              #d = getattr(IPython.display, line.strip(), IPython.display.display)
              #return d(r)
      
      IPython.get_ipython().register_magics(JinjaMagics)
      

      在 Pandas 中生成一些示例时态数据:

      import pandas as pd
      import numpy as np
      
      c1 = np.random.randint(1,6, size=15)
      c2 = pd.date_range(start="2021-01-01",end="2021-01-15")
      df = pd.DataFrame({"day": c2, "value": c1})
      df = df.drop([2, 5,6,7,13])
      df
      

      必要的导入:

      # Convert the Pandas dataframe to a format suitable for Vega-Lite.
      import altair
      # Tag Vega-Embed div's with UUIDs ensuring the correct div is targeted.
      import uuid
      import json
      
      vega_libs =\
          [ {"path": "https://cdn.jsdelivr.net/npm/vega@5"}
          , {"path": "/libs/vega-lite@4-fix.js", "local": True}
          , {"path": "https://cdn.jsdelivr.net/npm/vega-embed@6"}
          ]
      

      Vega-Lite:

      s =\
        { "title":
          { "text": "Daily Counts"
          , "subtitle": {"expr": "selectionSum(data('interval_store'), data('data_0'))"}
          , "subtitleFont": "monospace"
          }
        , "mark": "bar"
        , "encoding":
          { "x":
            { "type": "temporal"
            , "bin": "binned"
            , "field": "start"
            , "axis": { "tickCount": "day" }
            }
          , "x2": {"field": "end"}
          , "y": {"type": "quantitative", "field": "value"}
          }
        , "selection":
          { "interval":
            { "type": "interval"
            , "encodings": ["x"]
            }
          }
        , "transform":
          [ # Convert 'day' from 'string' to timestamp ('number')
            {"calculate": "toDate(datum.day)", "as": "day"}
            # Provide "start" and "end" as Date objects to match the
            # type of temporal domain objects
          , {"calculate": "timeOffset('hours', datum.day, -12)", "as": "start"}
          , {"calculate": "timeOffset('hours', datum.day, 12)", "as": "end"}
          ]
        , "height": 250
        , "width": "container"
        , "$schema": "https://vega.github.io/schema/vega-lite/v4.json"
        , "config": {"customFormatTypes": "True"}
        , "data": altair.utils.data.to_values(df)
        }
      

      最后运行 Vega-Lite:

      %%jinja
      %%loadjs {{json.dumps(vega_libs)}}
      
      {% set visid = uuid.uuid4() %}
      
      element.innerHTML = `
          <style>.vega-embed.has-actions {width:90%}</style>
          <div id="vis-{{visid}}"></div>
      `
      
      var spec = {{json.dumps(s)}}
      
      vega.expressionFunction("selectionSum", function(selection, data) {
          var view = this.context.dataflow;
          
          function intersects(i1, i2) {
              return (i1[1] >= i2[0] && i1[0] <= i2[1]);
          }
          
          function cmp_interval_pt(i0, i1, p) {
              if (i1 < p)
                  return -1;
              if (i0 > p)
                  return 1;
              return 0;
          }
          
          function cmp_primitive(a,b) {
              if (a < b)
                  return -1
              if (a > b)
                  return 1;
              return 0;
          }
      
          function bisect_left(l, v, fc=cmp_primitive) {
              return _bisect_left(l, 0, l.length, v, fc);
          }
      
          function _bisect_left(l, l0, l1, v, fc) {
              if (l1 <= l0)
                  return l0;
              var i = Math.floor((l0+l1)/2);
              var c = fc(l[i], v);
              if (c < 0)
                  l0 = i + 1;
              else
                  l1 = i;
              return _bisect_left(l, l0, l1, v, fc);
          }
      
          function bisect_right(l, v, fc=cmp_primitive) {
              return _bisect_right(l, 0, l.length, v, fc);
          }
      
          function _bisect_right(l, l0, l1, v, fc) {
              if (l1 <= l0)
                  return l0;
              var i = Math.floor((l0+l1)/2);
              var c = fc(l[i], v);
              if (c <= 0)
                  l0 = i + 1;
              else
                  l1 = i;
              return _bisect_right(l, l0, l1, v, fc);
          }
          
          function cmp_data(lv, v) {
              return cmp_interval_pt(lv.start, lv.end, v);
          }
          
          function constant_len_digits(s,l) {
              return " ".repeat(Math.max(0, l-s.toString().length)) + s
          }
              
          if (selection.length) {
              var r = selection[0]["values"][0];
              var d0 = bisect_left(data, r[0], cmp_data);
              var d1 = bisect_right(data, r[1], cmp_data);
              var s = data.slice(d0,d1).reduce((a,v)=>a+v.value, 0);
          }
          else
              var s = 0
          return `selected: ${constant_len_digits(s,3)}`;
      });
      
      vegaEmbed('#vis-{{visid}}', spec).then(function(result) {
      }).catch(console.error);
      

      结果:

      请注意,在着色和选择分箱区域时,您必须自己计算交点。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-03-18
        • 2013-09-14
        • 2016-02-02
        • 1970-01-01
        • 2016-11-06
        • 2022-05-24
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多