Get started guide for programmatic generation of Graffle Graphs

Currently I’m using OmniGraffle to create a large infographic on the state of a number of projects and their dependencies. It started out quite small, but is now becoming unwieldy and the data it represents quite vast.

It occurred to me that it would be really convenient to create the base graph programmatically and then beautify manually for clarity. I’ve been learning about the OmniJS and JXA, but have real trouble finding examples that do the following:

  • read a file of values (JSON, CSV, or some other standard format)
  • create an new OG document
  • create the graph based on the values, with shapes and colors adapted to their meaning
  • saves the graph ready for further use.

During my journey through the tutorials I have learnt how to create the shapes I want, with the values and labels (in OmniJS), I have also learnt how to open a new OG document and draw a shape (JXA), but I am completely confused on how, or where (in which context) to open the file of values, read the values and plot them in the graph. Encoding an OmniJS script in JXA with embedded data seems an awfully convoluted way to solve my problem.

Is there a more straightforward way of programmatically creating a graph from values in a file?

This is what I have now:

function arrowHead(og,x,y,dx,dy) {
var a=[x,y+dy/2],
	b=[x-dx,y+dy],
	c=[x-dx+dy/2,y];
	
var arrow=og.Line({
	pointList: [ 
		a,b,c,
		b,c,a,
		c,a,b 
	],
	color: [0.3,0.4,0.5],
	drawsShadow: true,
    thickness: 3,
	strokeColor: [0.0, 0.0, 0.0]	
});

return arrow;

}

function filledArrow(og,x,y,dx,dy) {
var a=[x,y+dy/2],
	b=[x-dx,y+dy],
	c=[x-dx+dy/2,y];
	
var arrow=og.Shape({
	pointList: [ 
		a,a,a,
		b,b,b,
		c,c,c	
	],
	fill : "solid fill",
	fillColor : [0.4,0.3,0.2],
	color: [0.3,0.4,0.5],
	drawsShadow: true,
    thickness: 3,
	strokeColor: [0.0, 0.0, 0.0]	
});

return arrow;
}

function filledParallelogram(og, x, y, dx, dy) {
var a=[x,y+dy],
	b=[x+dx-dy/2,y+dy],
	c=[x+dx,y],
	d=[x+dy/2,y];
	
var pp = og.Shape({
	pointList: [
		a,a,a,
		b,b,b,
		c,c,c,
		d,d,d
	],
	fill : "solid fill",
	fillColor : [0.4,0.3,0.2],
	color: [0.3,0.4,0.5],
	drawsShadow: true,
    thickness: 3,
	strokeColor: [0.0, 0.0, 0.0]
});

return pp;
}


function run() {
 'use strict'

var og = Application('OmniGraffle'),
    ws = og.windows,
    w = ws.length ? ws[0] : undefined, // document window ?
    graphics = (w && w.id() !== -1 ? w.canvas.graphics : undefined);

if (graphics) {

    var shp = og.Shape({
            origin: [100, 40],
            size: [60, 20],
			text: "Text"
        });

	var arr = arrowHead(og,60,40,40,20);
	var farr = filledArrow(og, 100,40, 40,20);
	var pp = filledParallelogram(og, 200,40,100,20);

    var textRuns = (graphics.push(shp),shp.text.attributeRuns);
   var arrRuns = (graphics.push(arr), arr.text.attributeRuns);
   var farrRuns= (graphics.push(farr),farr.text.attributeRuns);
   var ppRuns = (graphics.push(pp),pp.text.attributeRuns);
           
    }
}

But it is not very fast, and I can see real issues when trying to do hundreds of elements this way.

2 Likes

This is more scaleable:

And this is a good (related) environment for experimentation and development:

https://observablehq.com/explore

Thanks for pointing this out.

From what I understand from this library, I would still need to export to SVG and then import into OmniGraffle. That sets this solution at the same distance to OmniGraffle as, for example R and python.

What I am looking for is to directly and programmatically create the objects of the graph on a layer so that I can do further beautifying within OmniGraffle. That gives me full control over text and shadow properties of the objects as they are created, instead of having to manipulate them afterwards.

Or did I miss an export feature in D3.js?

That sounds tricky - I would personally try to do all the rendering in D3, if the data size is large.

(OmniGraffle is something I reach for on smallish first sketch scales)

Here is an example of typography in D3:

and here, drop shadows:

I’ve successfully used Python and the ScriptingBridge framework (via the PyObjC bridge) to programmatically control OmniGraffle in the past. I’m not sure if this is going to be supported going forward by either OG or Apple. As of today (late Jan 2020) it still works.

That said, I have not seen any examples of “fast” scripting of OmniGraffle. As long as you can live with that, I believe the Python approach to be the most flexible.

I hope this response has been helpful.

thanks, good to know that this exists and works too.

Would you by any chance have an example, or point me somewhere that has a good example?

I pasted some source code in this forum back in 2017. Uncle Google was my teacher.

Found it! Brilliant!

Thanks for sharing. I think now I can resolve some of my errors. I appreciate it for sharing.

I’m surprised nobody has mentioned GraphViz .dot language. So I will. :-)

Even using GraphViz to generate an SVG file and then manually use this in OmniGraffle, I’ve always had to manually replace all the edges (lines) between the nodes as they aren’t connected to each other. Move one and the relationship is broken.

My point was writing javascript to emit .dot , rather than ingest it.