Each record in the Fact table is a transaction:

For example, the first record records a transaction with values v1 = 11 and v2 = 23 for a small blue office product in Paris, France on 2.2.2000.
There are two kinds of fields in the Fact table: Dimension fields, such as industry and year, and value fields, such as v1 and v2.
Dimension fields are hierarchically ordered within a dimension: the Product dimension is industry -> color -> size.
The initial view of the Fact table summarizes all values to the outermost level of two dimensions:

The OLAP window contains three components: a Results window, showing the aggregated data; a Slice control, which can be used to select within dimensions; and the Query window, which shows the SQL which generates the sliced result.
Additional controls along the top of the form:
Start resets the view to the initial aggregation.
Slices picks the dimension to slice:

Pivot swaps the X and Y axes:

Fs selects the aggregation function:

Ms selects the value to aggregate:
Xs selects the X dimension:

Ys selects the Y dimension:

and Detail shows the records in the Fact table underlying the current aggregation:

Clicking a cell in the Result table drills down into the aggregation. For example, Military + Italy =

For year 1997 only:

Clicking on an element in the row or column dimension disaggregates on that dimension only; for example, color = purple:

Observe that disaggregation on a dimension (or pair of dimensions) explodes the results into the next subdimension (or subdimensions). For example, disaggregating on Product.color = purple -> Product.size.
When disaggregation "bottoms out" on a dimension, the display shifts to show the underlying detail records. For example, clicking on size = medium produces:

The K implementation is about a page of code.
First, we set the fonts to something non-proportional:
\m f Courier-New \m l Courier-New
Then we define the Fact table. In a "real" application, we would read the table and its structural parameters off disk. In this demo version, we simply generate the data.
N:1000000
Fact.industry:`home`office`military N _draw 3 Fact.color:`red`blue`green`yellow`orange`purple N _draw 6 Fact.size:`tiny`small`medium`large`gigundo N _draw 5 Fact.city:t N _draw#t:`NY`LA`London`York`Bristol`Rome`Milan`Florence`Paris`Lyons Fact.country:(.+(t;`US`US`UK`UK`UK`Italy`Italy`Italy`France`France))Fact.city Fact.year:1995 1996 1997 1998 1999 2000 N _draw 6 Fact.month:N _draw 12 Fact.day:N _draw 30 Fact.v1:N _draw 1000 Fact.v2:1.*N _draw 1000
In our example, we will generate a million records (N) of random integer (v1) and float (v2) data. Notice that we need to do a little extra work to make sure that cities are generated in the right countries.
A function to derive the sorted, unique dimension values:
nub:{.+(x;{{x@<x}@?x}'Fact x)}
And the structural parameters of the Fact table:
H[`product`location`time]:nub'(`industry`color`size;`country`city;`year`month`day)
I..d:"@[H;_n;*!:]"
Q..d:"H;.()"
F..d:"H;*!T"
M..d:"*(!Fact)_dvl,/!:'H[]"
X..d:"H;*!`I"
Y..d:"H;(!`I)1"
A pair of functions to handle NA's and Infinities:
nan:(0N;0n;;`)@- 1+4::
inf:(0I;0i)@- 1+4::
Aggregation functions:
T[`sum`num`min`max]:(+/;+/;&/;|/)
sum:{x#@[(*/x)#*0#z;x _sv y;+;z]}
num:{z;x#@[&*/x;x _sv y;+;1]}
min:{x#@[(*/x)#inf z;x _sv y;&;z]}
max:{x#@[(*/x)#-inf z;x _sv y;|;z]}
The Results window, defined as a dependency:
Z..d:":[Detail;detail[I]Q;drill[I;Q;X;Y;F]M]"
Z..l..d:"\"Result:
\",:[.k.Detail;$*|^.k.Z[];1_,/\"
\",'$^.k.Z[]]"
The heart of the program is the drill-down function:
drill:{[i;q;x;y;f;m]
n:#:'s:{H[x;i x]}'x,y
d:f[n;s?/:'Fact[i x,y;j];Fact[m;j:true q]]
d:(,nan[s 1],s 1),(,/T[f]'(,/d;+d)),'(,T[f]d),d
a:.+(`e`k`bg;(0;".k.k[*_i].k.vn[]";bg))
t:.+(i[y,x],g:`$"x",'$!*n;d;a)
.[t;(~g;`l);:;$*s]}
true:{[q]{x@&z=Fact[y;x]}/[_n;!q;q[]]}
A function to get the detail records underlying the current aggregation:
detail:{[i;q].+(f;Fact[f:df i;true q];.,`e,0)}
df:{[i]((!Fact)_dvl,/!:'H[]),,/(!i){((!H x)?y)_!H x}'i[]
Background color:
vn:{`$(1+#$_d)_$_v}
bg:{:[((0=*_i)&~v=I Y)|I[X]=v:vn[];909090]}
Click function:
k:{:[(y=I Y)&x>0;ky x;(~y=IX)&x>0;kx[x]y]}
nx:{(x,*|x)1+x?y}
kx:{
Q[I X,Y]:(H[X;I X;-2+(!Z)?y];H[Y;I Y;-1+x])
if[I[X,Y]~i:(nx[!H X]I X;nx[!H Y]I Y);Detail::1]
I[X,Y]:i
}
ky:{
Q[I Y]:H[Y;I Y;-1+x]
if[I[Y]~i:nx[!H Y]I Y;Detail::1]
I[Y]:i
}
And the GUI:
Start:"H::;Detail:0"
Pivot:"@[_d;`X`Y;:;Y,X];"
Slices..d:".+(!H;(#!H)#,\"Slice..a:_i\")"
Xs:@[H;_n;:[;"if[~_i=Y;X:_i]"]]
Ys:@[H;_n;:[;"if[~_i=X;Y:_i]"]]
Fs:@[_n;!T;:[;"F:_i"]]
Ms:@[_n;(!Fact)_dvl,/!:'H[];:[;"M:_i"]]
Start..c:Pivot..c:Slices..c:Ms..c:Fs..c:Xs..c:Ys..c:`button
Detail:0
Detail..c:`check
query:{[d;i;q;x;y;f;m]select[d;f;m],where[q],,group[i;x]y}
select:{[d;f;m]("select",:[d;"*";($f),"(",($m),")"];"from Fact")}
where:{[q]:[0=#!q;();@[" and ",/:($!q){x,"=",value y}'q[];0;"where ",5_]]}
value:{:[4=4:x;"'",($x),"'";$x]}
group:{[i;x;y]"group by ",($i x),",",$i y}
Query..d:".k.query[Detail;.k.I;.k.Q;.k.X;.k.Y;.k.F].k.M"
Query..e:0
Slice..d:"sliced/[.+(!H;{.+(!x;x[];slicea[])}'H[]);!Q;Q[]]"
Slice..t:"if[`X _in _i;Q:slice[Q]Slice]"
Slice..c:`form
Slice..a..d:"*((!.k.H)_dvl .k.X,.k.Y),!.k.H"
slicea:{.+(`e`k`X`bg;(0;".k.c[]";();.k.bgc))}
sliced:{.[x;((!x)(y _in'!:'x[])?1;~y;`X);:;z]}
c:{@[~_v;`X;:;(d;0#d)(d:_v ._i)~(~_v)`X];.[_v;();::];}
bgc:{(;606060)(_v ._i)_in(),(~_v)`X}
slice:{[q;s]{x _di(!x)@&0=#:'x[]}q{@[x;!y;:;y[.;`X]]}/s[]}
.k..a:(`Start`Slices`Pivot`Fs`Ms`Xs`Ys`Detail;`Z;`Slice`Query)
`show$`.k