tonyhffong / TermWin.jl

ncurses based data navigators
Other
26 stars 11 forks source link

TermWin.jl

TermWin Build Status

Introduction

TermWin.jl is a tool to help navigate tree-like data structure such as Expr, Dict, Array, Module, and DataFrame It uses a ncurses-based user interface. It also contains a backend framework for composing ncurses user interfaces.

It requires color support, xterm-256color strongly encouraged.

Most viewers have help text via the F1 key.

The advantages of using TermWin compared to other established GUI framework are that

For most users, the main function is tshow, which accepts almost anything, and should give you a fairly usable interactive representation.

Expr

using TermWin
ex = :( f(x) = x*x + 2x + 1 )
tshow(ex)

expression

Module

An excellent example of looking at modules is to see the TermWin module itself:

using TermWin
tshow( TermWin ) # to see what functionalities it implements

Functions and Methods

To show Function and MethodTable,

using TermWin
tshow( deleteat! ) # searchable, pivotable methods table
tshow( methods( deleteat! ) ) # ditto
tshow( methodswith( Set ) )

DataFrames

To show a dataframe, this is the minimum:

using TermWin
df = DataFrame( a = [1,2], b = ["c", "d"] )
tshow( df )

TermWin supports a wide range of configurations in showing dataframes. Here is a rather elaborate example:

using TermWin
using RDatasets
using Compat

df = dataset( "Ecdat", "Caschool" )
tshow( df;
    colorder = [ :EnrlTot, :Teachers, :Computer, :TestScr, :CompStu, "*" ],
    pivots = [ :County, :top5districts, :District ],
    initdepth = 2,
    aggrHints = @compat(Dict{Any,Any}(
        :TestScr => :( mean( :_, weights(:EnrlTot) ) ),
        :ExpnStu => :( mean( :_, weights(:EnrlTot) ) ),
        :CompStu => :( mean( :_, weights(:EnrlTot) ) ),
        :Str     => :( mean( :_, weights(:EnrlTot) ) )
        ) ),
    calcpivots = @compat( Dict{Symbol,Any}(
        :CountyStrBuckets     => CalcPivot( :(discretize( :Str, [ 14,16,18,20,22,24 ], rank = true, compact = true )), :County ),
        :CountyTestScrBuckets => CalcPivot( :(discretize( :TestScr, [ 600, 620, 640, 660, 680, 700],
                                    label = "score", rank = true, compact = false, reverse = true ) ), :County ),
        :top5districts        => CalcPivot( :(topnames( :District, :TestScr, 5 ) ) )
        ) ),
    views = [
        @compat(Dict{Symbol,Any}( :name => "ByStr",       :pivots => [ :CountyStrBuckets, :County, :District] ) ),
        @compat(Dict{Symbol,Any}( :name => "ByTestScr",   :pivots => [ :CountyTestScrBuckets, :County, :District] ) ),
        @compat(Dict{Symbol,Any}( :name => "Top5Schools", :pivots => [ :top5districts, :County ] ) )
    ],
    )

which will generate this output:

caschool

Here the "top5district" pivot (after County) is a "calculated pivot". It is not static. It is generated based on the path of the tree nodes, so as the user moves this pivot further up or down the pivot chain, the ranking would be adjusted to the context/subdataframe correctly. Another example of calculated pivot is discretization on aggregated values "CountyTestScrBuckets", which can be found on the view selector ('v' keyboard shortcut) in the same example. You can also change the pivot ordering without restarting the view using the 'p' shortcut.

Also, note that aggregation aggrHints is done via an expression that will be lifted into a compiled function. The argument column :_ is always replaced with the column name at compilation. Other required columns for the aggregation function (such as weights in this case) must be explicitly named. The convention of using colons to denote columns follows DataFramesMeta.jl

The dataframe viewer supports a large range of options:

TermWin provides a few commonly used aggregation functions for table data presentation:

On CalcPivot, TermWin provides

Other usage tips

tshow for most types accepts at a minimum the following keyword arguments:

tshow returns the widget which can be re-shown. This is especially useful with the dataframe and other container viewers, which remember the pivot states, the column order, etc.

Installation

As stated on the tin, TermWin requires ncurses. It is being developed on MacOS/iTerm. It also requires Lint.jl for a superficial code cleanliness test. (Not sure how to unit-test a GUI than actually using it manually.)

Pkg.add( "TermWin" )

If you are using iTerm2 on MacOS, it is known that its modern parser for CSI codes are not compatible to this package. You should disable it.

Using TermWin to compose dialogs and ncurses applications

Input Widgets

Numeric, text, date input field (See test/twentry.jl). Designed to maximize entry efficiency and accuracy (See F1 help screen). UTF-8 input and output are supported with correct cursor movement. Most European typesets, Han characters, Thai (with compound vowel issues), currency symbols, e.g. €, £, are fine..

It supports date type

Examples:

Getting a value or a string from a user:

One out of many selection:

Getting a multi-value selection:

General comments on code organization

The key type is TwObj. It is the type that renders something. TwScreen is just a typealias for TwObj, but it holds special role in

Many widgets can be used in both blocking and non-blocking manner, though some are more useful in blocking than non-blocking and others vice versa.

To use a widget for blocking use, instantiate that widget (if a container, put them inside too) and call

return_value = activateTwObj( widget )

To use a widget for non-blocking use, you need a container widget that is actually blocking and put it inside the container. See the function viewer for an example of mixing in a data entry field.

Focus and keystroke traffic logic

When a widget has the focus, it has first dip in interpreting any user keystroke. Only when the widget gives up (by returning :pass), the container (usually TwScreen) looks for other widgets also in play (when they have grabUnusedKeys set to true, e.g. Menu). After all widgets pass, then the container would try to interpret it itself. If a widget return :exit_ok, it’s instructing the container that it has got what it wanted. The container may still choose to switch focus, or exit -- the inject function would need to be overriden to tell TermWin what to do.