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 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.
In [1]:
LoadPackage("francy");
Out[1]:
Canvas are the base where graphics are produced. A Canvas is constituted by a Main Menu and an area where the graphics are produced.
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 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.
A Callback is a function that is triggered in GAP and can be added to Menus and/or Shapes.
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:
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:
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:
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 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.
Once again, creating messages is fairly simple and depends on the purpose of the message.
Messages can be of the following types:
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, 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:
By default, Graphs are created with default options set to:
Supported ShapeTypes are (Node Shapes):
By default, Shapes are created with default options set to:
NOTE: Please note that Francy is not a Graph Library and thus no graph operations, in mathematical terms, are available
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 are another graphical way to represent data.
Supported ChartTypes:
By default, Chart are created with default options set to:
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 [ ]: