Francy Package

Francy is responsible for creating a representational structure that can be rendered using pretty much any UI framework in any language for any OS.

Francy JS renders this model in Jupyter, and allows interactivity with GAP.

Draw

Draw is the main function of Francy. It renders a canvas and all the child objects in Jupyter environment or any other environment which connects to GAP somehow, e.g. a webssh console with websockets, etc.

DrawSplash

DrawSplash uses Draw to generate the data and creates a Static HTML Page that can be embedded or viewed in any browser, in "offline" mode.

NOTE: When using this, there will be no interaction back to gap as there is no kernel orchestrating the communication between francy and gap! This might change in the future, but no plans to implement such functionality using websockets at the moment.

Load Package


In [1]:
LoadPackage("francy");


Out[1]:
true

Canvas

Canvas are the base where graphics are produced. A Canvas is constituted by a Main Menu and an area where the graphics are produced.

How to create a Canvas?

It is possible to set some default configurations for the canvas:

gap> defaults := CanvasDefaults;
gap> Sanitize(defaults);
rec( height := 600, width := 800, zoomToFit := true )
gap> SetWidth(defaults, 830);
gap> SetHeight(defaults, 630);
gap> SetZoomToFit(defaults, false);
gap> SetTexTypesetting(defaults, true);
gap> canvas := Canvas("Example Canvas", defaults);

Or it can be done after, by:

gap> canvas := Canvas("Example Canvas");
gap> SetWidth(canvas, 850);
gap> SetHeight(canvas, 650);
gap> SetZoomToFit(canvas, true);

In [19]:
canvas := Canvas("Callbacks in action");;
SetTexTypesetting(canvas, true);;
SetHeight(canvas, 450);;

graph := Graph(GraphType.UNDIRECTED);; # will go throughout graphs later
shape := Shape(ShapeType.CIRCLE, "$x^2$");; # will go throughout shapes later
Add(graph, shape);;
Add(canvas, graph);;

HelloWorld := function(name)
    Add(canvas, FrancyMessage(Concatenation("Hello, ", name))); #  will go throughout messages later
    return Draw(canvas);
end;;

callback1 := Callback(HelloWorld);;
arg1 := RequiredArg(ArgType.STRING, "Your Name?");;
Add(callback1, arg1);;

menu := Menu("Example Menu Holder");;
menu1 := Menu( "Hello Menu Action", callback1 );;
Add(menu, menu1);;

Add(canvas, menu);;
Add(canvas, menu1);;
Add(shape, menu1);;

Draw(canvas);


Out[19]:

Menus

Menus can be added to the Canvas, where they will be added to the Main Menu on the Top, or to Shapes, where they will appear as Context Menu - Mouse right click.

The Main Menu has by default a Menu entry called Francy with 3 Sub Menus: Zoom to Fit, Save to PNG and About. When a Graph is produce the Main Menu will also contain a Menu entry called Graph Options with 3 Sub Menus: Enable/Disable Drag, Enable/Disable Neighbours, Clear Selected Nodes.

Callbacks

A Callback is a function that is triggered in GAP and can be added to Menus and/or Shapes.

How to create a Callback?

Callbacks can be created in many different ways, and it will depend on what you want to do.

Callbacks are triggered with mouse events. Available TriggerTypes are:

  • TriggerType.DOUBLE_CLICK
  • TriggerType.RIGHT_CLICK
  • TriggerType.CLICK

NOTE: No matter what you choose for TriggerType on a callback that is used on a Menu will always default to TriggerType.CLICK!

Calling a Simple function that doesn't require any argument is the simplest form:

gap> MyFunction := function()
>    # Must return allways! This is because GAP CallFuncList is used and requires it
>    return;
> end;
gap> callback := Callback(MyFunction); # defaults to CLICK event
gap> callback := Callback(TriggerType.DOUBLE_CLICK, MyFunction);

Calling a Function with a "known" argument is also simple:

gap> canvas := Canvas("Callbacks in Action!");
gap> MyFunction := function(someKnownArg)
>    # Do some crazy computation
>    # Redraw
>  return Draw(canvas);
> end;
gap> something := NumericalSemigroup(10,11,19);
gap> callback := Callback(MyFunction, [something]);

What if we want the user to give some input? Well, this is the case you have "required" arguments:

gap> canvas := Canvas("Callbacks in Action!");
gap> MyFunction := function(someKnownArg, someUserInputArg)
>    # Do Some Crazy computation
>    # Redraw
>    return Draw(canvas);
> end;
gap> something := NumericalSemigroup(10,11,19);
gap> callback := Callback(MyFunction, [something]);
gap> arg := RequiredArg(ArgType.NUMBER, "Give me a Prime?");
gap> Add(callback, arg);

It is possible to add a confirmation message that gets displayed before executing the callback:

...
gap> SetConfirmMessage(callback, "This is a confirmation message! Click OK to proceed...");

Required Arguments type defines the data type. Available ArgTypes are:

  • ArgType.SELECT
  • ArgType.BOOLEAN
  • ArgType.STRING
  • ArgType.NUMBER

How to create a Menu?

Menus can include a Callback or not. Menus without callback are useful for holding Submenus.

gap> callback := Callback(MyCallbackFunction);
gap> menu := Menu("Example Holder Menu");
gap> submenu := Menu("I'm a Submenu!", callback);
gap> Add(menu, submenu);
gap> Add(canvas, menu);

Or as a top Menu:

gap> callback := Callback(MyCallbackFunction);
gap> menu := Menu("Menu", callback);
gap> Add(canvas, menu);

The same menu objects can be used in Shapes:

  • NOTE: Submenus are flatenned in context menus!*
gap> shape := Shape(SpaheType.CIRCLE); # will go throughout shapes and graphs later
gap> Add(shape, menu);

In [41]:
canvas2 := Canvas("Callbacks in action");;
SetHeight(canvas2, 450);;

graph := Graph(GraphType.UNDIRECTED);; # will go throughout graphs later
shape := Shape(ShapeType.CIRCLE);; # will go throughout shapes later
SetColor(shape, "#2E8B57");;
Add(graph, shape);;
Add(canvas2, graph);;

HelloWorld := function(name, node)
    Add(canvas2, FrancyMessage(Concatenation("Hello, ", name, " - Selected Node ID: ",String(node)))); #  will go throughout messages later
    return Draw(canvas2);
end;;

callback1 := Callback(HelloWorld);;
SetConfirmMessage(callback1, "This is a confirmation message...");;
arg1 := RequiredArg(ArgType.STRING, "Your Name?");;
arg2 := RequiredArg(ArgType.SELECT, "Selected Nodes");;
Add(callback1, arg1);;
Add(callback1, arg2);;

Add(shape, callback1);;

menu := Menu("Example Menu Holder");;
menu1 := Menu( "Hello Menu Action", callback1 );;
Add(menu, menu1);;

Add(canvas2, menu);;
Add(canvas2, menu1);;
Add(shape, menu1);;

Draw(canvas2);


Out[41]:

In [58]:
canvas3 := Canvas("Example Callbacks with Known Arguments");;
SetHeight(canvas3, 450);;

graph := Graph(GraphType.DIRECTED);;

shape := Shape(ShapeType.CIRCLE, "Click Me 1");;
shape1 := Shape(ShapeType.CIRCLE, "Click Me 2");;
Add(graph, shape);;
Add(graph, shape1);;

WhichNode := function(node)
    Add(canvas3, FrancyMessage(node!.title));
    return Draw(canvas3);
end;;

Add(shape, Callback(WhichNode, [shape]));; # similar to Add(shape, Callback(TriggerEvent.CLICK, WhichNode, [shape]));
Add(shape1, Callback(WhichNode, [shape1]));; # similar to Add(shape1, Callback(TriggerEvent.CLICK, WhichNode, [shape1]));

link := Link(shape, shape1);;
SetWeight(link, 2);;
SetColor(link, "red");;
SetTitle(link, "or");;
Add(graph, link);;

Add(canvas3, graph);;

Draw(canvas3);


Out[58]:

Messages

Messages are usefull for providing information to the user. Messages can be added to the Canvas and/or to Shapes.

Messages added to a Canvas are displayed as messages using colors to differentiate types, they appear on the top left corner and can be dismissed by clicking on them.

Messages added to a Shape are displayed as tooltips and their types are not taken in account, they appear when the user moves the mouse hover the Shape.

How to create Messages?

Once again, creating messages is fairly simple and depends on the purpose of the message.

Messages can be of the following types:

  • FrancyMessageType.INFO
  • FrancyMessageType.ERROR
  • FrancyMessageType.SUCCESS
  • FrancyMessageType.WARNING
  • FrancyMessageType.DEFAULT

The simplest Message with the default type would be:

gap> FrancyMessage("Hello", "World"); # title and text
gap> FrancyMessage("Hello"); # without title

Messages with a custom type:

gap> FrancyMessage(FrancyMessageType.INFO, "Hello", "World"); # title and text
gap> FrancyMessage(FrancyMessageType.INFO, "Hello World"); # without title

In [70]:
canvas4 := Canvas("Example Callbacks with Known Arguments");;
SetHeight(canvas4, 450);;

graph := Graph(GraphType.DIRECTED);;
Add(canvas4, graph);;

shape := Shape(ShapeType.CIRCLE, "Click Me");;
shape1 := Shape(ShapeType.CIRCLE, "Click Me");;
Add(graph, shape);;
Add(graph, shape1);;

WhichNode := function(c, node)
    Add(c, FrancyMessage(String(node!.title)));
    return Draw(c);
end;;

Add(shape, Callback(WhichNode, [canvas4, shape]));; # similar to Add(shape, Callback(TriggerEvent.CLICK, WhichNode, [shape]));
Add(shape1, Callback(WhichNode, [canvas4, shape1]));; # similar to Add(shape1, Callback(TriggerEvent.CLICK, WhichNode, [shape1]));

Draw(canvas4);


Out[70]:

In [88]:
canvas5 := Canvas("Example Canvas / Shape with Messages");;
SetTexTypesetting(canvas5, true);;
SetHeight(canvas5, 250);;

graph := Graph(GraphType.UNDIRECTED);; # will go throughout graphs later
shape := Shape(ShapeType.CIRCLE);; # will go throughout shapes later
Add(graph, shape);;
Add(canvas5, graph);;

Add(canvas5, FrancyMessage(FrancyMessageType.INFO, "Hello $x^2$"));;
Add(shape, FrancyMessage(FrancyMessageType.INFO, "Hello $x^2$"));;
Add(canvas5, FrancyMessage(FrancyMessageType.ERROR, "Oops", "Hello"));;
Add(shape, FrancyMessage(FrancyMessageType.ERROR, "Oops", "Hello"));;
Add(canvas5, FrancyMessage(FrancyMessageType.WARNING, "Hello"));;
Add(shape, FrancyMessage(FrancyMessageType.WARNING, "Hello"));;
Add(canvas5, FrancyMessage(FrancyMessageType.SUCCESS, "Hello"));;
Add(shape, FrancyMessage(FrancyMessageType.SUCCESS, "Hello"));;
Add(canvas5, FrancyMessage("Hello", "World"));;
Add(shape, FrancyMessage("Hello", "World"));;

Draw(canvas5);


Out[88]:

Graphs

Graphs, according to wikipedia: a graph is a structure amounting to a set of objects in which some pairs of the objects are in some sense "related"

In Francy, Graphs can be created using Shapes (nodes) and Links (edges). Francy, in this case the D3 library, will try its best to shape the graph according to a set of "forces". If the Shapes provide x and y coordinates, these will be used instead and the graph will be fixed to those.

Supported GraphTypes are:

  • GraphType.UNDIRECTED
  • GraphType.DIRECTED
  • GraphType.TREE

By default, Graphs are created with default options set to:

  • GraphDefaults.simulation [true] - does not work on type tree. applies d3 forces to the diagram and arranges the nodes without fixed positions
  • GraphDefaults.collapsed [true] - only works on type tree! whether the graph will be collapsed or not by default.

Supported ShapeTypes are (Node Shapes):

  • ShapeType.TRIANGLE
  • ShapeType.DIAMOND
  • ShapeType.CIRCLE
  • ShapeType.SQUARE
  • ShapeType.CROSS
  • ShapeType.STAR
  • ShapeType.WYE

By default, Shapes are created with default options set to:

  • ShapeDefaults.layer [0] - used to create hasse diagrams to set indexes
  • ShapeDefaults.size [10]
  • ShapeDefaults.x [0] - x position in canvas
  • ShapeDefaults.y [0] - y position in canvas

NOTE: Please note that Francy is not a Graph Library and thus no graph operations, in mathematical terms, are available

How to create Graphs?

Let's see how to create a graph of each type, starting with the Hasse. The Hasse diagram requires the layer to be set, in order to fix y positions to this layer option:

gap> graph := Graph(GraphType.UNDIRECTED);
gap> shape := Shape(ShapeType.CIRCLE, "Title");
gap> SetLayer(shape, 1);
gap> shape1 := Shape(ShapeType.CIRCLE);
gap> SetLayer(shape1, 2);
gap> link := Link(shape, shape1);
gap> Add(graph, shape);
gap> Add(graph, shape1);
gap> Add(graph, link);

NOTE: This might change in order to ease the creation of HASSE diagrams without having to specify the layer. Suggestions are welcome!

A Directed graph instead is simpler:

gap> graph := Graph(GraphType.DIRECTED);
gap> shape := Shape(ShapeType.CIRCLE, "Title");
gap> shape1 := Shape(ShapeType.CIRCLE);
gap> link := Link(shape, shape1);
gap> Add(graph, shape);
gap> Add(graph, shape1);
gap> Add(graph, link);

Undirected graphs are as simple as Directed ones, but the arrows are not present:

gap> graph := Graph(GraphType.UNDIRECTED);
gap> shape := Shape(ShapeType.CIRCLE, "Title");
gap> shape1 := Shape(ShapeType.CIRCLE);
gap> link := Link(shape, shape1);
gap> Add(graph, shape);
gap> Add(graph, shape1);
gap> Add(graph, link);

Tree graphs are as simple as the previous ones, but you need to specify the parent node:

gap> graph := Graph(GraphType.TREE);
gap> shape := Shape(ShapeType.CIRCLE, "Title");
gap> shape1 := Shape(ShapeType.CIRCLE);
gap> Add(graph, shape);
gap> Add(graph, shape1);
gap> SetParentShape(shape1, shape);

NOTE: Blue nodes are clickable on trees and allows to expand and collapse.


In [101]:
canvas6 := Canvas("Example Hasse Graph");;
SetHeight(canvas6, 450);;

graph := Graph(GraphType.UNDIRECTED);;

shape := Shape(ShapeType.CIRCLE, "G");;
SetLayer(shape, 1);;
shape1 := Shape(ShapeType.CIRCLE, "1");;
SetLayer(shape1, 2);;
Add(graph, shape);;
Add(graph, shape1);;

link := Link(shape, shape1);;
Add(graph, link);;

Add(canvas6, graph);;

Draw(canvas6);


Out[101]:

In [114]:
canvas7 := Canvas("Example Directed Graph");;
SetHeight(canvas7, 450);;

graph := Graph(GraphType.DIRECTED);;

shape := Shape(ShapeType.CIRCLE, "G");;
SetLayer(shape, 1);;
shape1 := Shape(ShapeType.CIRCLE, "1");;
SetLayer(shape1, 2);;
Add(graph, shape);;
Add(graph, shape1);;

link := Link(shape, shape1);;
Add(graph, link);;

Add(canvas7, graph);;

Draw(canvas7);


Out[114]:

In [127]:
canvas8 := Canvas("Example Undirected Graph");;
SetHeight(canvas8, 450);;

graph := Graph(GraphType.UNDIRECTED);;

shape := Shape(ShapeType.CIRCLE, "G");;
SetLayer(shape, 1);;
shape1 := Shape(ShapeType.CIRCLE, "1");;
SetLayer(shape1, 2);;
Add(graph, shape);;
Add(graph, shape1);;

link := Link(shape, shape1);;
Add(graph, link);;

Add(canvas8, graph);;

Draw(canvas8);


Out[127]:

In [151]:
canvas9 := Canvas("Example Multiple Shapes Graph");;
SetHeight(canvas9, 450);;

graph := Graph(GraphType.UNDIRECTED);;

shapeG := Shape(ShapeType.DIAMOND, "G");;
Add(graph, shapeG);;
shape1 := Shape(ShapeType.WYE, "1");;
Add(graph, shape1);;

shapeSG1 := Shape(ShapeType.SQUARE, "SG1");;
Add(graph, shapeSG1);;
Add(graph, Link(shapeG, shapeSG1));;

shapeSG2 := Shape(ShapeType.TRIANGLE, "SG2");;
Add(graph, shapeSG2);;
Add(graph, Link(shapeSG1, shapeSG2));;
Add(graph, Link(shapeSG2, shape1));;

shapeSG3 := Shape(ShapeType.CROSS, "SG3");;
Add(graph, shapeSG3);;
Add(graph, Link(shapeSG1, shapeSG3));;
Add(graph, Link(shapeSG3, shape1));;

shapeSG4 := Shape(ShapeType.STAR, "SG4");;
Add(graph, shapeSG4);;
Add(graph, Link(shapeSG1, shapeSG4));;
Add(graph, Link(shapeSG4, shape1));;

Add(canvas9, graph);;

Draw(canvas9);


Out[151]:

In [168]:
canvas10 := Canvas("Example Tree Graph");;
SetHeight(canvas10, 450);;

graph := Graph(GraphType.TREE);;
SetCollapsed(graph, false);;

shapeG := Shape(ShapeType.CIRCLE, "G");;
Add(graph, shapeG);;

shape1 := Shape(ShapeType.SQUARE, "1");;
Add(graph, shape1);;

shapeSG1 := Shape(ShapeType.CIRCLE, "SG1");;
Add(graph, shapeSG1);;

shapeSG2 := Shape(ShapeType.CIRCLE, "SG2");;
Add(graph, shapeSG2);;


SetParentShape(shapeG, shape1);;
SetParentShape(shapeSG1, shapeG);;
SetParentShape(shapeSG2, shapeG);;

Add(canvas10, graph);;

Draw(canvas10);


Out[168]:

Charts

Charts are another graphical way to represent data.

Supported ChartTypes:

  • ChartType.LINE
  • ChartType.BAR
  • ChartType.SCATTER

By default, Chart are created with default options set to:

  • ChartDefaults.labels [true]
  • ChartDefaults.legend [true]

How to create Charts?

Let's see how to create a Chart of each type, starting with the LINE. LINE Charts don't support providing a custom domain, this needs more work!

gap> chart := Chart(ChartType.LINE);
gap> SetAxisXTitle(chart, "X Axis");
gap> SetAxisYTitle(chart, "Y Axis");

gap> data1 := Dataset("data1", [100,20,30,47,90]);
gap> data2 := Dataset("data2", [51,60,72,38,97]);
gap> data3 := Dataset("data3", [50,60,70,80,90]);

gap> Add(chart, [data1, data2, data3]);

The same data in a Bar Chart:

gap> chart := Chart(ChartType.BAR);
gap> SetAxisXTitle(chart, "X Axis");
gap> SetAxisXDomain(chart, ["domain1", "domain2", "domain3", "domain4", "domain5"]);
gap> SetAxisYTitle(chart, "Y Axis");

gap> data1 := Dataset("data1", [100,20,30,47,90]);
gap> data2 := Dataset("data2", [51,60,72,38,97]);
gap> data3 := Dataset("data3", [50,60,70,80,90]);

gap> Add(chart, [data1, data2, data3]);

Same data in a SCATTER Chart:

gap> chart := Chart(ChartType.SCATTER);
gap> SetAxisXTitle(chart, "X Axis");
gap> SetAxisYTitle(chart, "Y Axis");

gap> data1 := Dataset("data1", [100,20,30,47,90]);
gap> data2 := Dataset("data2", [51,60,72,38,97]);
gap> data3 := Dataset("data3", [50,60,70,80,90]);

gap> Add(chart, [data1, data2, data3]);

NOTE: Charts need more work in general


In [179]:
canvas11 := Canvas("Example Line Chart");;
SetHeight(canvas11, 400);;

chart := Chart(ChartType.LINE);;
SetAxisXTitle(chart, "X Axis");;
SetAxisYTitle(chart, "Y Axis");;

data1 := Dataset("data1", [100,20,30,47,90]);;
data2 := Dataset("data2", [51,60,72,38,97]);;
data3 := Dataset("data3", [50,60,70,80,90]);;

Add(chart, [data1, data2, data3]);;
Add(canvas11, chart);;

Draw(canvas11);


Out[179]:

In [191]:
canvas12 := Canvas("Example Bar Chart");;
SetHeight(canvas12, 400);;

chart := Chart(ChartType.BAR);;
SetAxisXTitle(chart, "X Axis");;
SetAxisXDomain(chart, ["domain1", "domain2", "domain3", "domain4", "domain5"]);;
SetAxisYTitle(chart, "Y Axis");;

data1 := Dataset("data1", [100,20,30,47,90]);;
data2 := Dataset("data2", [51,60,72,38,97]);;
data3 := Dataset("data3", [50,60,70,80,90]);;

Add(chart, [data1, data2, data3]);;
Add(canvas12, chart);;

Draw(canvas12);


Out[191]:

In [202]:
canvas13 := Canvas("Example Scatter Chart");;
SetHeight(canvas13, 400);;

chart := Chart(ChartType.SCATTER);;
SetAxisXTitle(chart, "X Axis");;
SetAxisYTitle(chart, "Y Axis");;

data1 := Dataset("data1", [100,20,30,47,90]);;
data2 := Dataset("data2", [51,60,72,38,97]);;
data3 := Dataset("data3", [50,60,70,80,90]);;

Add(chart, [data1, data2, data3]);;
Add(canvas13, chart);;

Draw(canvas13);


Out[202]:

In [ ]: