Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Importer and Exporter Examples

JSON Importer Example

New in Version 4
In this section a simple JSON document importer is implemented. The format of the JSON supported by this importer is just an example and not based on any existing format.

When the script is run through the File -> Import -> Import via Script -> Open Script… command, it will ask for a file to import and then a dialog allowing for two options: Create new document and whether Connections are predecessors or successors:

Diagram Example

Example JSON File

{
  "groups": [
    {
      "children": [
        4,
        2,
        3
      ],
      "index": 1,
      "title": "G"
    }
  ],
  "nodes": [
    {
      "connections": [],
      "index": 2,
      "title": "E",
      "type": "Action"
    },
    {
      "connections": [],
      "index": 3,
      "title": "D",
      "type": "Action"
    },
    {
      "connections": [
        2,
        3
      ],
      "index": 4,
      "title": "C",
      "type": "Intermediate Effect"
    },
    {
      "connections": [],
      "index": 5,
      "title": "B",
      "type": "Action"
    },
    {
      "connections": [
        4,
        5
      ],
      "index": 6,
      "title": "A",
      "type": "Goal"
    }
  ]
}

The format of this JSON file allows for a list of “nodes” that are interpreted as entities and “groups” which are interpreted as, well, groups. Only fields for titles, entity classes, edges (as “connections”) and children elements of groups are parsed. It is left to an excessive to the reader to add annotations, junctors, project management data, etc.

The example file generates the following diagram:

Generated Diagram Example

JSON Importer Code

# import_json_example.py
# import the contents of a simple JSON file into Flying Logic
# option for having connections be to predecessors or successors
# creates entities, groups and edges, but not junctors
# Copyright 2025 Flying Logic

import json
from javax.swing import Box, BoxLayout, JLabel, JCheckBox, JComboBox

importItemLabel = "Import from JSON Example"

# importDocument: required function for an importer
# parameters
#      file: filename of the file to import
def importDocument(file):
	# make a UI using Java
	masterBox = Box(BoxLayout.Y_AXIS)

	# new document?
	controlBox = Box(BoxLayout.X_AXIS)
	newDocCheckbox = JCheckBox("Create new document")
	controlBox.add(newDocCheckbox)
	controlBox.add(Box.createHorizontalGlue())
	masterBox.add(controlBox)

	# connections meaning
	controlBox = Box(BoxLayout.X_AXIS)
	controlBox.add(JLabel("Connections are: "))
	connectionsComboBox = JComboBox(["Predecessors", "Successors"])
	controlBox.add(connectionsComboBox)
	masterBox.add(controlBox)

	# display dialog and collect options
	if 0 == Application.request("JSON Import Settings", masterBox, ("Cancel", "OK")):
		return

	# get options from user choices
	createNewDocument = newDocCheckbox.isSelected()
	connectionsAsSuccessors = (connectionsComboBox.selectedIndex == 1)

	# create a new doc if desired
	if createNewDocument:
		theDoc = Application.newDocument()
	else:
		theDoc = document

	# open and parse JSON file
	with open(file, "r") as infile:
		json_object = infile.read()
		data = json.loads(json_object)

		entityDict = { }
		groupDict = { }

		# process "nodes" as entities
		# we will create all entties and then deal with connections
		if 'nodes' in data:
			for node in data['nodes']:
				# with None parameter, no need to clear selection
				entity = theDoc.addEntityToTarget(None)[0]
				entity.title = node['title']
				entity.entityClass = theDoc.getEntityClassByName(node['type'])
				entityDict[node['index']] = (entity, node['connections'])

		# process "groups" as groups
		# we will create all groups and then deal with children
		if 'groups' in data:
			for node in data['groups']:
				# with None parameter, no need to clear selection
				group = theDoc.newGroup(None)[0]
				group.title = node['title']
				groupDict[node['index']] = (group, node['children'])

		# process connections
		for data in entityDict.values():
			entity = data[0]
			for connection in data[1]:
				other = entityDict[connection][0]
				if connectionsAsSuccessors:
					theDoc.connect(entity, other)
				else:
					theDoc.connect(other, entity)

		# process children, which can be an entity or group
		for data in groupDict.values():
			group = data[0]
			for index in data[1]:
				if index in entityDict:
					child = entityDict[index][0]
				else:
					child = groupDict[index][0]
				child.parent = group

Example DOT Exporter

The code below exports a document to a DOT (GraphViz) file, but with less features than the native export option in Flying Logic.

When the script is run through the File -> Export -> Export via Script -> Open Script… command, it will export the diagram into the file specified.

# export_dot.py
# a simple DOT format exporter, less complete then the native version in Flying Logic
# Copyright 2013 Arciem LLC

# required variable that provided the label for the item in Flying Logic export menu
exportMenuLabel = "Export Diagram to simple DOT format"

# exportDocument: required function for an exporter
# parameters
# file: filename of the file to export
def exportDocument(file):
  # open output file using Python file I/O
  fh = open(file, 'w')

  fh.write("digraph graphname {\n")
  for elem in document.all:
    if elem.isGroup or elem.isEdge:
      continue

    # use the element unique id’s (eid) to create unique id’s in DOT
    if elem.isEntity:
      fh.write("\tn" + str(elem.eid) + " [label=\"" + elem.title + "\"];\n")
    if elem.isJunctor:
      fh.write("\tn" + str(elem.eid) + " [label=\"" + elem.operator.abbreviation + "];\n")
    for outEdge in elem.outEdges:
      fh.write("\tn" + str(elem.eid) + " -> n" + str(outEdge.target.eid) + ";\n")
      fh.write("\t}\n")

  fh.close()

Example Task Duration to CSV Exporter

The code below exports a document with project management information to a CSV (Comma-Separated Values) file. In addition to task names and durations being exported, completion percentage and outline level; i.e., depth in groups, are included. Entities and junctors are treated as tasks, while groups are considered summaries.

# Flying Logic export project management task data to CSV
# Copyright 2025 ARCIEM LLC
 
from datetime import datetime
# The most common Java libraries are available for import.
from java.io import FileWriter
# Conveniently, Flying Logic uses the Apache's CSV Java library, so it can be imported.
from org.apache.commons.csv import CSVFormat, CSVRecord, CSVPrinter

# label for menu item in FL
exportItemLabel = "Export Task Durations (CSV)"
# exported file should have extension .json
defaultExtensions = ["csv"]

# class that encapsulates collecting and ordering tasks
class TaskDurationsCSV:
    def __init__(self):
        # list of Concerto tasks
        self.tasks = [ ]
        # map vertices to Concerto task
        self.eid_to_task = { }
        # map vertices to an index
        self.export_indices = { }
        # create optimized order for algorithm
        self.orderedVertices = self.makeChartOrderedVertices()

    # generate top-level fields to output (only "tasks" right now)
    def generate(self):
        # generate output tasks in "nice" hierarchical order
        return self.addChildrenOfGroup(None):

    def fldateToLocalDate(self, fldate):
        return datetime.strftime(datetime.strptime(str(fldate), "%Y-%m-%d"), "%x")

    def fltimeintervalToTimeOffset(self, fltimeinterval):
        h = str(fltimeinterval.hours)
        m = str()
        s = h + "h"
        if fltimeinterval.minutes > 0:
            s += " "
            if fltimeinterval.minutes < 10:
                s += " "
            s += str(fltimeinterval.minutes) + "m"
        return s

    # Calculate the implied completion value for junctor from its predecessors.
    # The lowest such value is hoe complete it is.
    def junctorCompletion(self, junctor):
        completion = 1.0
        for edge in junctor.inEdges:
            if not edge.isBackEdge:
                source = edge.source
                if source.isJunctor:
                    c = self.junctorCompletion(source)
                elif source.isEntity:
                    c = source.completion
                else:
                    c = self.groupCompletion(source)
                if c < completion:
                    completion = c
        return completion

    # Calculate the implied completion value for group from its children.
    # The lowest such value is hoe complete it is.
    def groupCompletion(self, group):
        completion = 1.0

        for child in group.children:
            if child.isJunctor:
                c = self.junctorCompletion(child)
            elif child.isEntity:
                c = child.completion
            else:
                c = self.groupCompletion(child)
            if c < completion:
                completion = c
        return completion

    # This recursive method generates tasks in hierarchical order like
    # Flying Logic's chart mode, but makes sure children of summary tasks
    # are listed directly below the their parent and has corrent outline level.
    def addChildrenOfGroup(self, group):
        for vertex in self.orderedVertices:
            if vertex.parent == group:
                if vertex.isJunctor:
                    # calculate the next row index
                    index = len(self.export_indices) + 1
                    self.export_indices[vertex.eid] = index

                    task = {
                        "row": index,
                        "level": str(self.nestingLevel(vertex)),
                        "name": vertex.operator.abbreviation,
                        "startDate": self.fldateToLocalDate(vertex.startDate),
                        "startTime": self.fltimeintervalToTimeOffset(vertex.startTime),
                        "finishDate": self.fldateToLocalDate(vertex.finishDate),
                        "finishTime": self.fltimeintervalToTimeOffset(vertex.finishTime),
                        "complete": self.junctorCompletion(vertex)
                    }

                    # append to task list and associate with Flying Logic vertex
                    self.tasks.append(task)
                    self.eid_to_task[vertex.eid] = task
                elif vertex.isEntity:
                    # calculate the next row index
                    index = len(self.export_indices) + 1
                    self.export_indices[vertex.eid] = index

                    # make a task object with proper values
                    task = {
                        "row": index,
                        "level": str(self.nestingLevel(vertex)),
                        "name": vertex.title,
                        "startDate": self.fldateToLocalDate(vertex.startDate),
                        "startTime": self.fltimeintervalToTimeOffset(vertex.startTime),
                        "finishDate": self.fldateToLocalDate(vertex.finishDate),
                        "finishTime": self.fltimeintervalToTimeOffset(vertex.finishTime),
                        "complete": vertex.completion
                    }

                    # append to task list and associate with Flying Logic vertex
                    self.tasks.append(task)
                    self.eid_to_task[vertex.eid] = task
                elif vertex.isGroup:
                    # calculate the next row index
                    index = len(self.export_indices) + 1
                    self.export_indices[vertex.eid] = index

                    # make a task object representing a group of tasks with proper values
                    name = vertex.title
                    if len(name) == 0:
                        name = "[group]"
                    task = {
                        "row": index,
                        "level": str(self.nestingLevel(vertex)),
                        "name": name,
                        "startDate": self.fldateToLocalDate(vertex.startDate),
                        "startTime": self.fltimeintervalToTimeOffset(vertex.startTime),
                        "finishDate": self.fldateToLocalDate(vertex.finishDate),
                        "finishTime": self.fltimeintervalToTimeOffset(vertex.finishTime),
                        "complete": self.groupCompletion(vertex)
                    }

                    # append to task list and associate with Flying Logic vertex
                    self.tasks.append(task)
                    self.eid_to_task[vertex.eid] = task

                    # recurse to add children
                    if not self.addChildrenOfGroup(vertex):
                        return False
        return True

    # calculate all parents (if any) of this vertex
    def vertexAncestry(self, vertex):
        result = [ ]
        current = vertex.parent
        while current != None:
            result.append(current)
            current = current.parent
        return result

    # caluclate vertex's nesting depth
    def nestingLevel(self, vertex):
        depth = 1
        current = vertex.parent
        while current != None:
            depth += 1
            current = current.parent
        return depth

    # create a new order for vertices that allows optimal export -- similar to algorithm in Flying Logic
    def makeChartOrderedVertices(self):
        groupsProcessed = [ ]
        chartOrderedVertices = [ ]

        for vertex in document.orderedVertices:
            if not vertex.isGroup:
                if vertex.parent != None:
                    for group in reversed(self.vertexAncestry(vertex)):
                        if not group in groupsProcessed:
                            chartOrderedVertices.append(group)
                            groupsProcessed.append(group)
                chartOrderedVertices.append(vertex)
            else:
                if len(vertex.children) == 0:
                    if not vertex in groupsProcessed:
                        chartOrderedVertices.append(vertex)
                        groupsProcessed.append(vertex)

        return chartOrderedVertices

# standard Flying Logic export function
def exportDocument(filename):
    if document.startDate == None:
        # show error dialog
        Application.alert("Error! Document does not have project management information!")
        return

    # make a new instance of the TaskDurationCSV and call the generate method
    taskdata = TaskDurationsCSV()
    taskdata.generate()

    # writing to CSV file
    printer = CSVPrinter(FileWriter(filename), CSVFormat.EXCEL)
    # write header row
    printer.printRecord("Level", "Task", "Start", "Finish", "Complete")
    for task in taskdata.tasks:
        printer.printRecord(task['level'], task['name'], task['startDate'] + " " + task['startTime'],
            task['finishDate'] + " " + task['finishTime'], str(int(task['complete'] * 100)) + "%")
    printer.close(True)