Select可见数据

Select Visible Data

从这个GitHub issue:

I solved this getting the start date that is showing in the visualization and the end date, then i calculated the days between that dates. Depending of the amount of days between, i change the time format for the axis label.

This is a lot of useful for me because i'm zoom-in and zoom-out through this chart and when i zoom-in i need to know more info about the date and when i zoom-out i need to know less info about the date.

I hope that this will be useful for other people.

如何做到这一点?具体来说,我不知道如何从交互式绘图中获取开始日期和结束日期。

从 JupyterLab 执行此操作可能有点棘手,工作 around some issues,但这是几个单元格中的解决方案:

首先是示例数据:

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

然后是 Jinja for JupyterLab,因此您可以通过 Python.

将 Vega-Lite 架构替换为 Vega-Embed
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})
        d = getattr(IPython.display, line.strip(), IPython.display.display)
        return d(r)

IPython.get_ipython().register_magics(JinjaMagics)

然后是示例模式,稍微复杂一点以演示一些陷阱。

import altair

s =\
  { "mark": "bar"
  , "encoding":
    { "x":
      { "type": "temporal"
      , "bin": "binned"
      , "field": "start"
      , "axis":
        { "tickCount": "day"
          # For whatever reason a custom format function won't work
          # without a transform array.
        , "formatType": "interactiveFormatDate"
        , "format": "test"
        }
      }
    , "x2": {"field": "end"}
    , "y": {"type": "quantitative", "field": "value"}
    }
  , "selection":
    { "interactive":
      { "type": "interval"
      , "bind": "scales"
      , "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"}
  }

s["data"] = altair.utils.data.to_values(df)
# While a data array can be named, transform array results can't.
# Look at the Vega source to get the resulting name.
s["data"]["name"] = "exp"
s

使用 Vega-Embed 的一些导入。

# Tag Vega-Embed div's with UUIDs ensuring the correct div is targeted.
import uuid
import json

最后,Vega-Embed。平心而论,我不确定这是在获取脚本还是使用 JupyterLab 打包的脚本。

%%jinja HTML
{% set visid = uuid.uuid4() %}
<html>
<head>
    <style>.vega-embed.has-actions {width:90%}</style>
    <!-- Import Vega & Vega-Lite (does not have to be from CDN) -->
    <script src="https://cdn.jsdelivr.net/npm/vega@5"></script>
    <script src="https://cdn.jsdelivr.net/npm/vega-lite@4"></script>
    <!-- Import vega-embed -->
    <script src="https://cdn.jsdelivr.net/npm/vega-embed@6"></script>
</head>
<body>

<div id="vis-{{visid}}"></div>

<script type="text/javascript">
    vega.expressionFunction('interactiveFormatDate', function(datum, params) {
        // Data points come from both the data fields backing the current channel, 
        // and from the axis domain. Temporal domain axis points are 'Date' objects
        // and are a subset of the visible domain. Data field points are always
        // included even when offscreen and are of whatever type provided to vega.
        var view = this.context.dataflow;
        // These are hacks because you have to inspect the generated vega to
        // determine the dataset names.
        var selection = view.data("interactive_store");
        var data = view.data("data_0");
        // Unless the view has been shifted the selection will be empty
        if (selection.length == 0){
            var d1 = data[0]["start"];
            var d2 = data[data.length-1]["start"];
        }
        else if (selection.length != 0){
            var d1 = selection[0]["values"][0][0];
            var d2 = selection[0]["values"][0][1];
        }
        var dd = (d2 - d1)/1000/60/60/24;
        var tf = vega.timeFormatLocale().timeFormat;
        var tl = ["%d %b", "%Y"];
        return tl.map(x => tf(x)(datum));
    });
    var spec = {{ json.dumps(s)}}
    vegaEmbed('#vis-{{visid}}', spec).then(function(result) {
        // Access the Vega view instance
        // (https://vega.github.io/vega/docs/api/view/) as result.view
    }).catch(console.error);
</script>
</body>
</html>

最烦人的方面是缺少 HTML/JavaScript 单元格的正确语法突出显示。