visidata 2.11.1__py3-none-any.whl → 3.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (256) hide show
  1. visidata/__init__.py +72 -91
  2. visidata/_input.py +259 -42
  3. visidata/_open.py +84 -29
  4. visidata/_types.py +21 -3
  5. visidata/_urlcache.py +17 -4
  6. visidata/aggregators.py +78 -25
  7. visidata/apps/__init__.py +0 -0
  8. visidata/apps/vdsql/__about__.py +8 -0
  9. visidata/apps/vdsql/__init__.py +5 -0
  10. visidata/apps/vdsql/__main__.py +27 -0
  11. visidata/apps/vdsql/_ibis.py +748 -0
  12. visidata/apps/vdsql/bigquery.py +61 -0
  13. visidata/apps/vdsql/clickhouse.py +53 -0
  14. visidata/apps/vdsql/setup.py +40 -0
  15. visidata/apps/vdsql/snowflake.py +67 -0
  16. visidata/apps/vgit/__init__.py +13 -0
  17. {vgit → visidata/apps/vgit}/blame.py +5 -2
  18. {vgit → visidata/apps/vgit}/branch.py +31 -16
  19. {vgit → visidata/apps/vgit}/config.py +3 -3
  20. visidata/apps/vgit/diff.py +169 -0
  21. visidata/apps/vgit/gitsheet.py +161 -0
  22. {vgit → visidata/apps/vgit}/grep.py +6 -5
  23. visidata/apps/vgit/log.py +81 -0
  24. {vgit → visidata/apps/vgit}/main.py +18 -5
  25. {vgit → visidata/apps/vgit}/remote.py +8 -4
  26. visidata/apps/vgit/repos.py +71 -0
  27. {vgit → visidata/apps/vgit}/setup.py +6 -4
  28. visidata/apps/vgit/stash.py +69 -0
  29. visidata/apps/vgit/status.py +204 -0
  30. {vgit → visidata/apps/vgit}/statusbar.py +2 -0
  31. visidata/basesheet.py +63 -51
  32. visidata/canvas.py +208 -93
  33. visidata/choose.py +6 -6
  34. visidata/clean_names.py +29 -0
  35. visidata/clipboard.py +73 -17
  36. visidata/cliptext.py +220 -46
  37. visidata/cmdlog.py +88 -114
  38. visidata/color.py +142 -56
  39. visidata/column.py +121 -129
  40. visidata/ddw/input.ddw +74 -79
  41. visidata/ddw/regex.ddw +57 -0
  42. visidata/ddwplay.py +33 -14
  43. visidata/deprecated.py +77 -3
  44. visidata/desktop/visidata.desktop +7 -0
  45. visidata/editor.py +12 -6
  46. visidata/errors.py +6 -2
  47. visidata/experimental/__init__.py +0 -0
  48. visidata/experimental/diff_sheet.py +29 -0
  49. visidata/experimental/digit_autoedit.py +6 -0
  50. visidata/experimental/gdrive.py +89 -0
  51. visidata/experimental/google.py +37 -0
  52. visidata/experimental/gsheets.py +79 -0
  53. visidata/experimental/live_search.py +37 -0
  54. visidata/experimental/liveupdate.py +45 -0
  55. visidata/experimental/mark.py +133 -0
  56. visidata/experimental/noahs_tapestry/__init__.py +1 -0
  57. visidata/experimental/noahs_tapestry/tapestry.py +147 -0
  58. visidata/experimental/rownum.py +73 -0
  59. visidata/experimental/slide_cells.py +26 -0
  60. visidata/expr.py +8 -4
  61. visidata/extensible.py +22 -4
  62. visidata/features/__init__.py +0 -0
  63. visidata/features/addcol_audiometadata.py +42 -0
  64. visidata/features/addcol_histogram.py +34 -0
  65. visidata/features/canvas_save_svg.py +69 -0
  66. visidata/features/change_precision.py +46 -0
  67. visidata/features/cmdpalette.py +197 -0
  68. visidata/features/colorbrewer.py +363 -0
  69. visidata/{colorsheet.py → features/colorsheet.py} +17 -16
  70. visidata/features/command_server.py +105 -0
  71. visidata/features/currency_to_usd.py +70 -0
  72. visidata/{customdate.py → features/customdate.py} +2 -0
  73. visidata/features/dedupe.py +132 -0
  74. visidata/{describe.py → features/describe.py} +17 -15
  75. visidata/features/errors_guide.py +26 -0
  76. visidata/features/expand_cols.py +202 -0
  77. visidata/{fill.py → features/fill.py} +3 -1
  78. visidata/{freeze.py → features/freeze.py} +11 -6
  79. visidata/features/graph_seaborn.py +79 -0
  80. visidata/features/helloworld.py +10 -0
  81. visidata/features/hint_types.py +17 -0
  82. visidata/{incr.py → features/incr.py} +5 -0
  83. visidata/{join.py → features/join.py} +107 -53
  84. visidata/features/known_cols.py +21 -0
  85. visidata/features/layout.py +62 -0
  86. visidata/{melt.py → features/melt.py} +32 -21
  87. visidata/features/normcol.py +118 -0
  88. visidata/features/open_config.py +7 -0
  89. visidata/features/open_syspaste.py +18 -0
  90. visidata/features/ping.py +157 -0
  91. visidata/features/procmgr.py +208 -0
  92. visidata/features/random_sample.py +6 -0
  93. visidata/{regex.py → features/regex.py} +47 -31
  94. visidata/features/reload_every.py +55 -0
  95. visidata/features/rename_col_cascade.py +30 -0
  96. visidata/features/scroll_context.py +60 -0
  97. visidata/features/select_equal_selected.py +11 -0
  98. visidata/features/setcol_fake.py +65 -0
  99. visidata/{slide.py → features/slide.py} +77 -21
  100. visidata/features/sparkline.py +48 -0
  101. visidata/features/status_source.py +20 -0
  102. visidata/{sysedit.py → features/sysedit.py} +2 -1
  103. visidata/features/sysopen_mailcap.py +46 -0
  104. visidata/features/term_extras.py +13 -0
  105. visidata/{transpose.py → features/transpose.py} +5 -4
  106. visidata/features/type_ipaddr.py +73 -0
  107. visidata/features/type_url.py +11 -0
  108. visidata/{unfurl.py → features/unfurl.py} +9 -9
  109. visidata/{window.py → features/window.py} +2 -2
  110. visidata/form.py +50 -21
  111. visidata/freqtbl.py +81 -33
  112. visidata/fuzzymatch.py +414 -0
  113. visidata/graph.py +105 -33
  114. visidata/guide.py +200 -0
  115. visidata/help.py +75 -44
  116. visidata/hint.py +39 -0
  117. visidata/indexsheet.py +109 -0
  118. visidata/input_history.py +55 -0
  119. visidata/interface.py +58 -0
  120. visidata/keys.py +20 -16
  121. visidata/loaders/__init__.py +9 -0
  122. visidata/loaders/_pandas.py +61 -21
  123. visidata/loaders/api_airtable.py +70 -0
  124. visidata/loaders/api_bitio.py +102 -0
  125. visidata/loaders/api_matrix.py +148 -0
  126. visidata/loaders/api_reddit.py +306 -0
  127. visidata/loaders/api_zulip.py +249 -0
  128. visidata/loaders/archive.py +41 -7
  129. visidata/loaders/arrow.py +7 -7
  130. visidata/loaders/conll.py +49 -0
  131. visidata/loaders/csv.py +25 -7
  132. visidata/loaders/eml.py +3 -4
  133. visidata/loaders/f5log.py +1204 -0
  134. visidata/loaders/fec.py +325 -0
  135. visidata/loaders/fixed_width.py +2 -4
  136. visidata/loaders/frictionless.py +3 -3
  137. visidata/loaders/geojson.py +8 -5
  138. visidata/loaders/google.py +48 -0
  139. visidata/loaders/graphviz.py +4 -4
  140. visidata/loaders/hdf5.py +4 -4
  141. visidata/loaders/html.py +54 -12
  142. visidata/loaders/http.py +84 -30
  143. visidata/loaders/imap.py +20 -10
  144. visidata/loaders/jrnl.py +52 -0
  145. visidata/loaders/json.py +83 -29
  146. visidata/loaders/jsonla.py +74 -0
  147. visidata/loaders/lsv.py +15 -11
  148. visidata/loaders/mailbox.py +40 -0
  149. visidata/loaders/markdown.py +1 -3
  150. visidata/loaders/mbtiles.py +4 -5
  151. visidata/loaders/mysql.py +11 -13
  152. visidata/loaders/npy.py +7 -7
  153. visidata/loaders/odf.py +4 -1
  154. visidata/loaders/orgmode.py +428 -0
  155. visidata/loaders/pandas_freqtbl.py +14 -20
  156. visidata/loaders/parquet.py +62 -6
  157. visidata/loaders/pcap.py +3 -3
  158. visidata/loaders/pdf.py +4 -3
  159. visidata/loaders/png.py +19 -13
  160. visidata/loaders/postgres.py +9 -8
  161. visidata/loaders/rec.py +7 -3
  162. visidata/loaders/s3.py +342 -0
  163. visidata/loaders/sas.py +5 -5
  164. visidata/loaders/scrape.py +186 -0
  165. visidata/loaders/shp.py +6 -5
  166. visidata/loaders/spss.py +5 -6
  167. visidata/loaders/sqlite.py +68 -28
  168. visidata/loaders/texttables.py +1 -1
  169. visidata/loaders/toml.py +60 -0
  170. visidata/loaders/tsv.py +61 -19
  171. visidata/loaders/ttf.py +19 -7
  172. visidata/loaders/unzip_http.py +6 -5
  173. visidata/loaders/usv.py +1 -1
  174. visidata/loaders/vcf.py +16 -16
  175. visidata/loaders/vds.py +10 -7
  176. visidata/loaders/vdx.py +30 -5
  177. visidata/loaders/xlsb.py +8 -1
  178. visidata/loaders/xlsx.py +145 -25
  179. visidata/loaders/xml.py +6 -3
  180. visidata/loaders/xword.py +4 -4
  181. visidata/loaders/yaml.py +15 -5
  182. visidata/macros.py +129 -42
  183. visidata/main.py +119 -94
  184. visidata/mainloop.py +101 -155
  185. visidata/man/parse_options.py +2 -2
  186. visidata/man/vd.1 +302 -149
  187. visidata/man/vd.txt +291 -154
  188. visidata/memory.py +3 -3
  189. visidata/menu.py +104 -423
  190. visidata/metasheets.py +59 -141
  191. visidata/modify.py +78 -23
  192. visidata/motd.py +3 -3
  193. visidata/mouse.py +137 -0
  194. visidata/movement.py +43 -35
  195. visidata/optionssheet.py +99 -0
  196. visidata/path.py +113 -32
  197. visidata/pivot.py +73 -47
  198. visidata/plugins.py +65 -192
  199. visidata/pyobj.py +55 -205
  200. visidata/rename_col.py +20 -0
  201. visidata/save.py +37 -20
  202. visidata/search.py +54 -10
  203. visidata/selection.py +84 -5
  204. visidata/settings.py +162 -25
  205. visidata/sheets.py +239 -260
  206. visidata/shell.py +51 -21
  207. visidata/sidebar.py +162 -0
  208. visidata/sort.py +11 -4
  209. visidata/statusbar.py +114 -104
  210. visidata/stored_list.py +43 -0
  211. visidata/stored_prop.py +38 -0
  212. visidata/tests/benchmark.csv +52 -0
  213. visidata/tests/conftest.py +3 -3
  214. visidata/tests/test_cliptext.py +39 -0
  215. visidata/tests/test_commands.py +65 -7
  216. visidata/tests/test_edittext.py +2 -2
  217. visidata/tests/test_features.py +28 -0
  218. visidata/tests/test_menu.py +14 -0
  219. visidata/tests/test_path.py +13 -4
  220. visidata/text_source.py +53 -0
  221. visidata/textsheet.py +10 -3
  222. visidata/theme.py +44 -0
  223. visidata/themes/__init__.py +0 -0
  224. visidata/themes/ascii8.py +84 -0
  225. visidata/themes/asciimono.py +84 -0
  226. visidata/themes/light.py +17 -0
  227. visidata/threads.py +89 -40
  228. visidata/tuiwin.py +22 -0
  229. visidata/type_currency.py +22 -3
  230. visidata/type_date.py +31 -9
  231. visidata/type_floatsi.py +5 -1
  232. visidata/undo.py +17 -5
  233. visidata/utils.py +106 -23
  234. visidata/vdobj.py +28 -17
  235. visidata/windows.py +10 -0
  236. visidata/wrappers.py +9 -3
  237. visidata-3.0.1.data/data/share/applications/visidata.desktop +7 -0
  238. {visidata-2.11.1.data → visidata-3.0.1.data}/data/share/man/man1/vd.1 +302 -149
  239. {visidata-2.11.1.data → visidata-3.0.1.data}/data/share/man/man1/visidata.1 +302 -149
  240. visidata-3.0.1.data/scripts/vd2to3.vdx +9 -0
  241. {visidata-2.11.1.dist-info → visidata-3.0.1.dist-info}/METADATA +12 -8
  242. visidata-3.0.1.dist-info/RECORD +258 -0
  243. {visidata-2.11.1.dist-info → visidata-3.0.1.dist-info}/WHEEL +1 -1
  244. vgit/__init__.py +0 -1
  245. vgit/gitsheet.py +0 -164
  246. visidata/layout.py +0 -44
  247. visidata/misc.py +0 -5
  248. visidata-2.11.1.data/scripts/vgit +0 -9
  249. visidata-2.11.1.dist-info/RECORD +0 -155
  250. {vgit → visidata/apps/vgit}/__main__.py +0 -0
  251. {vgit → visidata/apps/vgit}/abort.py +0 -0
  252. /visidata/{repeat.py → features/repeat.py} +0 -0
  253. {visidata-2.11.1.data → visidata-3.0.1.data}/scripts/vd +0 -0
  254. {visidata-2.11.1.dist-info → visidata-3.0.1.dist-info}/LICENSE.gpl3 +0 -0
  255. {visidata-2.11.1.dist-info → visidata-3.0.1.dist-info}/entry_points.txt +0 -0
  256. {visidata-2.11.1.dist-info → visidata-3.0.1.dist-info}/top_level.txt +0 -0
visidata/form.py CHANGED
@@ -1,16 +1,16 @@
1
1
  import functools
2
- from visidata import clipdraw, colors, BaseSheet, VisiData, VisiDataMetaSheet, vd, EscapeException, AttrDict, ENTER
2
+ from visidata import clipdraw, colors, BaseSheet, VisiData, VisiDataMetaSheet, vd, EscapeException, AttrDict, ENTER, dispwidth, AcceptInput
3
3
 
4
4
 
5
5
  @VisiData.api
6
6
  def open_mnu(vd, p):
7
- return FormSheet(p.name, source=p)
7
+ return FormSheet(p.base_stem, source=p)
8
8
 
9
9
 
10
10
  vd.save_mnu=vd.save_tsv
11
11
 
12
12
  class FormSheet(VisiDataMetaSheet):
13
- rowtype='labels' # rowdef: { .x .y .text .color .command .input .underline}
13
+ rowtype='labels' # rowdef: { .x .y .text .color .command .input .key}
14
14
 
15
15
  @VisiData.api
16
16
  def replayCommand(vd, longname, sheet=None, col='', row=''):
@@ -18,9 +18,16 @@ def replayCommand(vd, longname, sheet=None, col='', row=''):
18
18
 
19
19
 
20
20
  class FormCanvas(BaseSheet):
21
- rowtype='labels'
22
- def click(self, r):
23
- vd.replayCommand(r.command, sheet=self)
21
+ rowtype = 'labels'
22
+ pressedLabel = None
23
+
24
+ def onPressed(self, r):
25
+ if r.key:
26
+ self.pressedLabel = r
27
+
28
+ def onReleased(self, r):
29
+ if self.pressedLabel is r:
30
+ raise AcceptInput(r.key)
24
31
 
25
32
  def reload(self):
26
33
  self.rows = self.source.rows
@@ -38,12 +45,17 @@ class FormCanvas(BaseSheet):
38
45
  y = int(y)
39
46
  if y < 0: y += h
40
47
  if x < 0: x += w
41
- clipdraw(scr, y, x, r.text, colors[r.color])
42
- # underline first occurrence of r.underline in r.text
43
- if hasattr(r, 'underline') and r.underline:
44
- index = r.text.find(r.underline)
45
- clipdraw(scr, y, x+index, r.text[index:len(r.underline)+1], colors[r.color + " underline"])
46
- vd.onMouse(scr, y, x, 1, len(r.text), BUTTON1_RELEASED=lambda y,x,key,r=r,sheet=self: sheet.click(r))
48
+ color = r.color
49
+ if r is self.pressedLabel:
50
+ color += ' reverse'
51
+ clipdraw(scr, y, x, r.text, colors[color])
52
+ # underline first occurrence of r.key in r.text
53
+ if hasattr(r, 'key') and r.key:
54
+ index = r.text.find(r.key)
55
+ clipdraw(scr, y, x+index, r.text[index:len(r.key)+1], colors[color + " underline"])
56
+ vd.onMouse(scr, x, y, dispwidth(r.text), 1,
57
+ BUTTON1_PRESSED=lambda y,x,key,r=r,sheet=self: sheet.onPressed(r),
58
+ BUTTON1_RELEASED=lambda y,x,key,r=r,sheet=self: sheet.onReleased(r))
47
59
 
48
60
  def run(self, scr):
49
61
  vd.setWindows(vd.scrFull)
@@ -53,35 +65,52 @@ class FormCanvas(BaseSheet):
53
65
  maxh = max(int(r.y) for r in drawnrows)
54
66
  h, w = vd.scrFull.getmaxyx()
55
67
  y, x = max(0, (h-maxh)//2-1), max(0, (w-maxw)//2-1)
56
- self.scrForm = vd.scrFull.derwin(min(h-1, maxh+2), min(w-1, maxw+1), y, x)
68
+ self.scrForm = vd.subwindow(vd.scrFull, x, y, min(w-1, maxw+1), min(h-1, maxh+2))
57
69
  self.scrForm.keypad(1)
58
70
  curinput = inputs[0] if inputs else None
59
- while True:
60
- vd.draw_all()
71
+ vd.draw_all()
61
72
 
62
- self.scrForm.erase()
63
- self.scrForm.border()
64
- self.draw(self.scrForm)
73
+ self.scrForm.erase()
74
+ self.scrForm.border()
75
+ self.draw(self.scrForm)
65
76
 
77
+ while True:
66
78
  k = vd.getkeystroke(self.scrForm, self)
67
79
  if k in ['^C', '^Q', '^[', 'q']:
68
80
  return {}
69
81
  if curinput and k in curinput.keystrokes:
70
82
  return {curinput.input: k}
83
+ if k == 'KEY_MOUSE':
84
+ r = vd.parseMouse(form=self.scrForm)
85
+ if r.found:
86
+ f = vd.getMouse(r.x, r.y, r.keystroke)
87
+ if f:
88
+ try:
89
+ f(r.y, r.x, r.keystroke)
90
+ except AcceptInput as e:
91
+ return {curinput.input: e.args[0]}
92
+ except Exception as e:
93
+ vd.exceptionCaught(e)
94
+ break
95
+
96
+ if r.keystroke == 'BUTTON1_RELEASED':
97
+ self.pressedLabel = None
98
+
99
+ self.draw(self.scrForm)
71
100
 
72
101
 
73
102
  @functools.wraps(VisiData.confirm)
74
103
  @VisiData.api
75
104
  def confirm(vd, prompt, exc=EscapeException):
76
105
  'Display *prompt* on status line and demand input that starts with "Y" or "y" to proceed. Raise *exc* otherwise. Return True.'
77
- if vd.options.batch:
106
+ if vd.options.batch and not vd.options.interactive:
78
107
  return vd.fail('cannot confirm in batch mode: ' + prompt)
79
108
 
80
109
  form = FormSheet('confirm', rows=[
81
110
  AttrDict(x=2, y=0, text=' confirm ', color='yellow'),
82
111
  AttrDict(x=2, y=1, text=prompt, color='yellow'),
83
- AttrDict(x=.25, y=2, text=' yes ', color='black on yellow bold', underline='y'),
84
- AttrDict(x=.75, y=2, text=' no ', color='black on yellow bold', underline='n'),
112
+ AttrDict(x=.25, y=2, text=' yes ', color='black on yellow bold', key='y'),
113
+ AttrDict(x=.75, y=2, text=' no ', color='black on yellow bold', key='n'),
85
114
  AttrDict(input='yn', keystrokes=['y', 'n', ENTER]),
86
115
  ])
87
116
 
visidata/freqtbl.py CHANGED
@@ -5,8 +5,7 @@ from visidata import vd, asyncthread, vlen, VisiData, Column, AttrColumn, Sheet,
5
5
  from visidata.pivot import PivotSheet, PivotGroupRow
6
6
 
7
7
 
8
- vd.option('disp_histogram', '■', 'histogram element character')
9
- vd.option('disp_histolen', 50, 'width of histogram column')
8
+ vd.theme_option('disp_histogram', '■', 'histogram element character')
10
9
  vd.option('histogram_bins', 0, 'number of bins for histogram of numeric columns')
11
10
  vd.option('numeric_binning', False, 'bin numeric columns into ranges', replay=True)
12
11
 
@@ -14,7 +13,7 @@ vd.option('numeric_binning', False, 'bin numeric columns into ranges', replay=Tr
14
13
  @VisiData.api
15
14
  def valueNames(vd, discrete_vals, numeric_vals):
16
15
  ret = [ '+'.join(str(x) for x in discrete_vals) ]
17
- if numeric_vals != (0, 0):
16
+ if isinstance(numeric_vals, tuple) and numeric_vals != (0, 0):
18
17
  ret.append('%s-%s' % numeric_vals)
19
18
 
20
19
  return '+'.join(ret)
@@ -22,18 +21,37 @@ def valueNames(vd, discrete_vals, numeric_vals):
22
21
  class HistogramColumn(Column):
23
22
  def calcValue(col, row):
24
23
  histogram = col.sheet.options.disp_histogram
25
- histolen = col.sheet.options.disp_histolen
24
+ histolen = col.width-2
26
25
  return histogram*(histolen*len(row.sourcerows)//col.sheet.largest)
27
26
 
28
27
 
28
+ def makeFreqTable(sheet, *groupByCols):
29
+ return FreqTableSheet(sheet.name,
30
+ '%s_freq' % '-'.join(col.name for col in groupByCols),
31
+ groupByCols=groupByCols,
32
+ source=sheet)
33
+
34
+
29
35
  class FreqTableSheet(PivotSheet):
30
36
  'Generate frequency-table sheet on currently selected column.'
37
+ guide = '''# Frequency Table
38
+ This is a *frequency analysis* of _{sheet.groupByColsName}_ from the *{sheet.groupByCols[0].sheet}* sheet.
39
+
40
+ Each row on this sheet corresponds to a *bin* of rows on the source sheet that have a distinct value. The _count_ and _percent_ columns show how many rows on the source sheet are in this bin.
41
+
42
+ - `Enter` to open a copy of the source sheet, with only the rows in the current bin.
43
+ - `g Enter` to open a copy of the source sheet, with a combination of the rows from all selected bins.
44
+
45
+ ## Tips
46
+
47
+ - Use `+` on the source sheet, to add aggregators on other columns, and those metrics will appear as separate columns here.
48
+ - Selecting bins on this sheet will select those rows on the source sheet.
49
+ '''
31
50
  rowtype = 'bins' # rowdef FreqRow(keys, sourcerows)
32
51
 
33
- def __init__(self, sheet, *groupByCols):
34
- fqcolname = '%s_%s_freq' % (sheet.name, '-'.join(col.name for col in groupByCols))
35
- super().__init__(fqcolname, groupByCols, [], source=sheet)
36
- self.largest = 1
52
+ @property
53
+ def groupByColsName(self):
54
+ return '+'.join(c.name for c in self.groupByCols)
37
55
 
38
56
  def selectRow(self, row):
39
57
  self.source.select(row.sourcerows) # select all entries in the bin on the source sheet
@@ -46,10 +64,8 @@ class FreqTableSheet(PivotSheet):
46
64
  def updateLargest(self, grouprow):
47
65
  self.largest = max(self.largest, len(grouprow.sourcerows))
48
66
 
49
- @asyncthread
50
- def reload(self):
51
- 'Generate frequency table then reverse-sort by length.'
52
- super().initCols()
67
+ def resetCols(self):
68
+ super().resetCols()
53
69
 
54
70
  # add default bonus columns
55
71
  for c in [
@@ -58,34 +74,39 @@ class FreqTableSheet(PivotSheet):
58
74
  ]:
59
75
  self.addColumn(c)
60
76
 
61
- if self.options.disp_histolen and self.options.disp_histogram:
62
- c = HistogramColumn('histogram', type=str, width=self.options.disp_histolen+2)
77
+ if self.options.disp_histogram:
78
+ c = HistogramColumn('histogram', type=str, width=self.options.default_width*2)
63
79
  self.addColumn(c)
64
80
 
81
+ # if non-numeric grouping, reverse sort by count at end of load
82
+ if not any(vd.isNumeric(c) for c in self.groupByCols):
83
+ self._ordering = [('count', True)]
84
+
85
+ def loader(self):
86
+ 'Generate frequency table.'
65
87
  # two more threads
66
88
  vd.sync(self.addAggregateCols(),
67
89
  self.groupRows(self.updateLargest))
68
90
 
91
+ def afterLoad(self):
92
+ super().afterLoad()
69
93
  if self.nCols > len(self.groupByCols)+3: # hide percent/histogram if aggregations added
70
94
  self.column('percent').hide()
71
95
  self.column('histogram').hide()
72
96
 
73
- if not [c for c in self.groupByCols if vd.isNumeric(c)]:
74
- self.orderBy(self.column('count'), reverse=True)
75
-
76
97
  def openRow(self, row):
77
98
  'open copy of source sheet with rows that are grouped in current row'
78
99
  if row.sourcerows:
79
100
  vs = copy(self.source)
80
- vs.name += "_"+vd.valueNames(row.discrete_keys, row.numeric_key)
101
+ vs.names = vs.names + [vd.valueNames(row.discrete_keys, row.numeric_key)]
81
102
  vs.rows=copy(row.sourcerows)
82
- vs.source=self.source
83
103
  return vs
84
104
  vd.warning("no source rows")
85
105
 
86
106
  def openRows(self, rows):
87
107
  vs = copy(self.source)
88
- vs.name += "_several"
108
+ vs.names = vs.names + ["several"]
109
+ vs.source = self
89
110
  vs.rows = list(itertools.chain.from_iterable(row.sourcerows for row in rows))
90
111
  return vs
91
112
 
@@ -94,23 +115,50 @@ class FreqTableSheet(PivotSheet):
94
115
 
95
116
 
96
117
  class FreqTableSheetSummary(FreqTableSheet):
97
- 'Append a PivotGroupRow to FreqTable with only selectedRows.'
98
- @asyncthread
99
- def reload(self):
100
- FreqTableSheet.reload.__wrapped__(self)
118
+ 'Append a PivotGroupRow to FreqTableSheet with only selectedRows.'
119
+ def afterLoad(self):
101
120
  self.addRow(PivotGroupRow(['Selected'], (0,0), self.source.selectedRows, {}))
121
+ super().afterLoad()
102
122
 
103
- Sheet.addCommand('F', 'freq-col', 'vd.push(FreqTableSheet(sheet, cursorCol))', 'open Frequency Table grouped on current column, with aggregations of other columns')
104
- Sheet.addCommand('gF', 'freq-keys', 'vd.push(FreqTableSheet(sheet, *keyCols))', 'open Frequency Table grouped by all key columns on source sheet, with aggregations of other columns')
105
- Sheet.addCommand('zF', 'freq-summary', 'vd.push(FreqTableSheetSummary(sheet, Column("Total", sheet=sheet, getter=lambda col, row: "Total")))', 'open one-line summary for all rows and selected rows')
106
123
 
107
- ColumnsSheet.addCommand(ENTER, 'freq-row', 'vd.push(FreqTableSheet(source[0], cursorRow))', 'open a Frequency Table sheet grouped on column referenced in current row')
124
+ def makeFreqTableSheetSummary(sheet, *groupByCols):
125
+ return FreqTableSheetSummary(sheet.name,
126
+ '%s_freq' % '-'.join(col.name for col in groupByCols),
127
+ groupByCols=groupByCols,
128
+ source=sheet)
129
+
130
+
131
+ @VisiData.api
132
+ class FreqTablePreviewSheet(Sheet):
133
+ @property
134
+ def rows(self):
135
+ return self.source.cursorRow.sourcerows
136
+
137
+
138
+ FreqTableSheet.addCommand('', 'open-preview', 'vd.push(FreqTablePreviewSheet(sheet.name, "preview", source=sheet, columns=source.columns), pane=2); vd.options.disp_splitwin_pct=50', 'open split preview of source rows at cursor')
139
+
140
+ Sheet.addCommand('F', 'freq-col', 'vd.push(makeFreqTable(sheet, cursorCol))', 'open Frequency Table grouped on current column, with aggregations of other columns')
141
+ Sheet.addCommand('gF', 'freq-keys', 'vd.push(makeFreqTable(sheet, *keyCols))', 'open Frequency Table grouped by all key columns on source sheet, with aggregations of other columns')
142
+ Sheet.addCommand('zF', 'freq-summary', 'vd.push(makeFreqTableSheetSummary(sheet, Column("Total", sheet=sheet, getter=lambda col, row: "Total")))', 'open one-line summary for all rows and selected rows')
143
+
144
+ ColumnsSheet.addCommand(ENTER, 'freq-row', 'vd.push(makeFreqTable(source[0], cursorRow))', 'open a Frequency Table sheet grouped on column referenced in current row')
108
145
  vd.addMenuItem('Data', 'Frequency table', 'current row', 'freq-row')
109
146
 
110
147
  FreqTableSheet.addCommand('gu', 'unselect-rows', 'unselect(selectedRows)', 'unselect all source rows grouped in current row')
111
148
  FreqTableSheet.addCommand('g'+ENTER, 'dive-selected', 'vd.push(openRows(selectedRows))', 'open copy of source sheet with rows that are grouped in selected rows')
112
-
113
- vd.addGlobals({
114
- 'FreqTableSheet': FreqTableSheet,
115
- 'FreqTableSheetSummary': FreqTableSheetSummary,
116
- })
149
+ FreqTableSheet.addCommand('', 'select-first', 'for r in rows: source.select([r.sourcerows[0]])', 'select first source row in each bin')
150
+
151
+ FreqTableSheet.init('largest', lambda: 1)
152
+
153
+ vd.addGlobals(
154
+ makeFreqTable=makeFreqTable,
155
+ makeFreqTableSheetSummary=makeFreqTableSheetSummary,
156
+ FreqTableSheet=FreqTableSheet,
157
+ FreqTableSheetSummary=FreqTableSheetSummary,
158
+ HistogramColumn=HistogramColumn,
159
+ )
160
+
161
+ vd.addMenuItems('''
162
+ Data > Frequency table > current column > freq-col
163
+ Data > Frequency table > key columns > freq-keys
164
+ ''')