jtCount - An Omnigraffle AppleScript that Counts!

Hi there - I wanted to share an applescript that we created to count objects on a canvas, which comes in handy for a few things.

Calculate and estimate cost, labor; generate shopping lists in various contexts:
Count trees, shrubs, and other plants in a landscape design
Count outlets, can lights, switches, windows and doors in architectural plans
Count capacitors, resistors, transistors, diodes, relays, etc in electrical schematics
Count atoms, bonds, molecules, etc in chemistry diagrams
Count individual objects, object groups, groups of groups, and on!

You can read more about how we used it here: https://www.jtechcommunications.com/blog/blog-detail-12

Feel free to give us feedback!

You can also download the Omnigraffle template that counts, and the instructions on how to modify the applescript to count objects the way you want it to.

3 Likes

Attempting to open the template file causes OmniGraffle to crash.

For cases where the flexibility of a scripted solution might be helpful, we can write a generic function in osascript JS (Apple’s JavaScript for Automation)

Summing across a subset of shapes (reading numbers from shape text or specified user data keys):

 // [ogGraphic] -> String -> Number
  function sumAcrossSet(graphics, key) {
    key = key || 'text';

    return ((key === 'text') ? (
      graphics.text()
    ) : (
      graphics.userData().map(function (x) {
        return (x ? x[key] : 0);
      })
    )).reduce(function (a, v) {
      var n = (typeof v === 'string' ? parseFloat(v) : v);

      return a + (isNaN(n) ? 0 : n);
    }, 0);
  }

Example of use, with some tests (summing circles vs rectangles, text vs user data, and counting number of shapes):

To see how to define subtler sets of graphics, with multiple whose | where conditions, refer to the section Filtering Arrays in the JavaScript for Automation release notes. Batch-extracting texts or user data values from a set defined with a .whose() clause turns out to be very significantly faster than iterating through a collection reading text/data one shape at a time).

Full source with tests:

function run() {

  // GENERIC FUNCTION FOR SUMMING NUMBERS IN TEXT OR USER DATA ACROSS 
  // VARIOUS SETS OF OMNIGRAFFLE SHAPES

  // Sume of numeric values in text or keyed user data item in shape
  // key is optional, defaulting to 'text', otherwise specifying a user data key

  // [ogGraphic] -> String -> Number
  function sumAcrossSet(graphics, key) {
    key = key || 'text';

    return ((key === 'text') ? (
      graphics.text()
    ) : (
      graphics.userData().map(function (x) {
        return (x ? x[key] : 0);
      })
    )).reduce(function (a, v) {
      var n = (typeof v === 'string' ? parseFloat(v) : v);

      return a + (isNaN(n) ? 0 : n);
    }, 0);
  }

  // SOME SAMPLE TESTS

  var og = Application("OmniGraffle"),
    cnv = og.windows[0].canvas;

  // SUMMING ACROSS VARIOUS SETS OF SHAPES

  // Either summing numbers in the text of the shapes
  // or in specific user data keys:

  return {
    'All numbers in text': sumAcrossSet(cnv.graphics),
    'All user data dollars': sumAcrossSet(cnv.graphics, 'dollars'),
    'All user data dollars in Rectangles': sumAcrossSet(cnv.graphics.whose({
      name: "Rectangle"
    }), 'dollars'),
    'All user data dollars in Circles': sumAcrossSet(cnv.graphics.whose({
      name: "Circle"
    }), 'dollars'),
    'All numbers in text in Rectangles': sumAcrossSet(cnv.graphics.whose({
      name: "Rectangle"
    })),
    'All numbers in text in Circles': sumAcrossSet(cnv.graphics.whose({
      name: "Circle"
    })),
    'Number of circles': cnv.graphics.whose({
      name: "Circle"
    }).length
  }
}

Or, simplest example:

// refGraphics --> optional user data key --> Number
function sumAcrossSet(gs, k) {
    k = k || 'text';
    return ('text' === k ? gs.text() : gs.userData().map(function (u) {
        return u ? u[k] : 0;
    })).reduce(function (a, v) {
        var n = 'string' === typeof v ? parseFloat(v) : v;
        return a + (isNaN(n) ? 0 : n);
    }, 0);
}

var og = Application('com.omnigroup.OmniGraffle6'),
    gs = og.windows[0].canvas.graphics;

// sum of numbers in text of circles
// (text is read if no user data key is given)
sumAcrossSet(
    gs.whose({
        name: 'Circle'
    })
);