OBJECTS THAT DRAW THEMSELVES

I have had people ask why they should delve into writing scripts for animating InDesign when the application provides so much capability from within its Animation panel. My answer is that using a script provides so much more capability. And, if one already knows how to write scripts, it is often much more intuitive. Further, the animation can be tied to all of the other automation involved in a project.

For instance, let’s say an online publication produced periodically includes an animated bar chart. The chart is always the same, just the data changes. This is an example of a task that lends itself well to using a script with an InDesign document template. The data could be read in from a text file, imported as XML, or otherwise. For simplicity, the demonstration script created in this blog post will list the data at the top of the script. It also will assume that the user has a web intent document open with a rectangle named “barChart” that serves to provide the chart background. Object styles have also been set up with one named “bar” for the bar, and one named “legend” for the bar’s text. The “legend” style also defines the paragraph style “barChart” to style its text. With this, the following script can create a simple bar chart.

Important: Make sure the document is saved in its pre-script state so you can revert it back to this state as you test your script (File > Revert).

Bar Chart Script

set barList to {{"Taxes", 400}, {"Insurance", 100}, {"Health care", 200}, {"Housing", 500}}
tell application "Adobe InDesign CC 2017"
    set rectRef to rectangle "barChart" of document 1
    set objStyle to object style "bar" of document 1
    set textObjStyle to object style "legend" of document 1
    set spreadRef to parent of rectRef
    copy geometric bounds of rectRef to {pTop, pLeft, py1, px1}
    set pBounds to geometric bounds of rectRef
    repeat with i from 1 to length of barList
	set pTop to pTop + 40
	set dataItem to item i of barList
	my makeBar(rectRef, pTop, pLeft, dataItem, objStyle, textObjStyle)
    end repeat
end tell
on makeBar(parentObj, pTop, pLeft, dataItem, objStyle, tStyle)
    tell application "Adobe InDesign CC 2017"
	set itemText to item 1 of dataItem
	set barWid to item 2 of dataItem
	tell parentObj
	   make text frame with properties {name:itemText, geometric bounds:{pTop, pLeft, (pTop + 30), (pLeft + 70)}, contents:itemText, applied object style:tStyle}
	   make rectangle with properties {name:itemText, geometric bounds:{pTop, (pLeft + 70), (pTop + 30), (pLeft + barWid)}, applied object style:objStyle}
	end tell
    end tell
end makeBar

Run the script to create the chart.

Screem grab of the Bar Chart before animating…Our simple Bar Chart

ANIMATING THE BARS

To add animation that will allow each bar to “grow” from left to right, the script will use the design option property to current appearance as part of the bar’s animation settings. The animation settings will also define the following:

  • xScale (horizontal scaling factors based on time frame)
  • yScale (vertical scaling factors based on time frame)
  • tOffsets (the transformation point proxy)
  • dTime (the duration for each individual bar)
  • a list defining true/false values for the properties initially hidden and hidden after

Let’s see how these are used to set the animation settings for each bar. First, define the values for the animation settings at the top of the script.

set xScale to {{0, 100}, {47, 0}} --bar scales horizontally in 47 time frames
set yScale to {{0, 100}, {47, 100}} --bar does not scale vertically
set tOffsets to {0, 0.5}
set dTime to 2 --duration for bar
set hideIt to {false, false}

Adding the above to the Bar Chart script, we will also add a call to a handler named growIt directly after the call to the makeBar handler inside the repeat block. This will set the animation settings for each of the bars.

tell application "Adobe InDesign CC 2017"
    set rectRef to rectangle "barChart" of document 1
    set objStyle to object style "bar" of document 1
    set textObjStyle to object style "legend" of document 1
    set spreadRef to parent of rectRef
    copy geometric bounds of rectRef to {pTop, pLeft, py1, px1}
    set pBounds to geometric bounds of rectRef
    repeat with i from 1 to length of barList
       set pTop to pTop + 40
       set dataItem to item i of barList
       my makeBar(spreadRef, pTop, pLeft, dataItem, objStyle, textObjStyle)
       set objName to (item 1 of item i of barList)
       set barRef to rectangle objName of spreadRef
       my growIt (barRef, dTime, xScale, yScale, tOffsets, hideIt)
    end repeat
end tell
(*The handler that animates the rectangle*)
on growIt(objRef, dTime, xScale, yScale, tOffsets, hideIt)
   tell application "Adobe InDesign CC 2017"
      tell animation settings of objRef  				
      	   set transform offsets to tOffsets
	   set duration to dTime
	   set design option to to current appearance
	   set initially hidden to item 1 of hideIt
	   set hidden after to false
	   set scale x array to xScale
	   set scale y array to yScale
       end tell
    end tell
end growIt

Make sure you include the makeBar handler with your script.

Revert the document back to its original state. Run the script and view the result in the EPUB Interactivity Preview panel (Window > Interactive > EPUB Interactivity Preview).

ANIMATING THE LEGENDS

Should you want to fade in the legends as well, you will need to add definitions of duration and opacity (opacity array) for the legends at the top of the script.

set textD to 0.5--duration for legend
set oArray to {{0,0}, {11, 100}}--opacity array

Next create a call to a fadeIn handler to apply the opacity settings to each legend. This will be within the repeat loop before the call to growIt:

set textRef to text frame objName of spreadRef
my fadeIn (textRef, textD, oArray)

The fadeIn handler only requires a couple of lines:

on fadeIn (objRef, dTime, oArray)
   tell application "Adobe InDesign CC 2017"
       tell animation settings of objRef
          set duration to dTime
          set opacity array to oArray
          set hidden after to false
       end tell
   end tell
end fadeIn

Revert the document back to its original state. Run the script, and view the animation in the EPUB Interactivity Preview panel.

Each legend fades in just before the bar animates. This may be good enough for your purpose, but the relative speed at which the bars animate is very noticeable. The short bar animates very slowly, while the longer bars animate quickly (same amount of time, but distance is different). To make it appear as if the bars animate at the same rate, we could lengthen the time it takes the longer bars to animate or shorten the time for the shorter bars. Let’s think this over. If we allow 47 frames as the duration for the longest bar, then the other bars should take a proportionately shorter time.

STEP BY STEP

To tackle this problem, first create a handler to return the length of the longest bar in the list. Since barList is a list of lists, we need to indicate the item of each list that will be used for comparison. This is the value of 2 passed as the second argument to the getLongest handler. You might want to test this in a separate script just to make sure you get it right:

set barList to {{"Taxes", 400}, {"Insurance", 100}, {"Health care", 200}, {"Housing", 500}}
set longest to getLongest (barList, 2)
longest --just to test
(*the getLongest handler*)
on getLongest(theList, theIndex)
   --set the first item to be the longest
   set longest to item theIndex of item 1 of theList
   --repeat through the list to see if there is an item that is longer
   repeat with i from 2 to length of theList
	set theItem to item theIndex of item i of theList
	if theItem > longest then
	   set longest to theItem
	end if
   end repeat
   return longest
end getLongest

CHANGE OBJECT TIMING

The script will now use the value returned from the getLongest handler to determine the timeFrame at which each of the bars will reach its maximum length (100%). It will also be used to calculate the duration of time involved. Adding the statements to determine these values, the script is now written as follows:

Animate BarChart

set barList to {{"Taxes", 400}, {"Insurance", 100}, {"Health care", 200}, {"Housing", 500}}
set maxFrame to 47
set tOffsets to {0, 0.5}
set dTime to 2
set hideIt to {false, false}
set textD to 0.5
set oArray to {{0, 0}, {11, 100}}
tell application "Adobe InDesign CC 2017"
    set rectRef to rectangle "barChart" of document 1
    set objStyle to object style "bar" of document 1
    set textObjStyle to object style "legend" of document 1
    set spreadRef to parent of rectRef
    copy geometric bounds of rectRef to {pTop, pLeft, py1, px1}
    set longest to my getLongest(barList, 2)
    repeat with i from 1 to length of barList
       --calculate timeFrame and duration based on longest and dTime
       set theDistance to item 2 of item i of barList
       --propTime will be a decimal value less than 1
       set propTime to theDistance / longest
       --multiply this times the maximum time frame length
       set timeFrame to round (propTime * maxFrame)
       --use this calculation to set xScale and yScale array
       set xScale to {{0, 100}, {timeFrame, 0}}
       set yScale to {{0, 100}, {timeFrame, 100}}
       --use the propTime percentage to determine the time duration
       set relTime to dTime * propTime
       --create the bar and legend
       set pTop to pTop + 40
       set dataItem to item i of barList
       my makeBar(spreadRef, pTop, pLeft, dataItem, objStyle, textObjStyle)
       set objName to (item 1 of item i of barList)
       --reference the text frame and bar by name
       set textRef to text frame objName of spreadRef
       my fadeIn(textRef, textD, oArray)
       set barRef to rectangle objName of spreadRef
       my growIt(barRef, relTime, xScale, yScale, tOffsets, hideIt)
    end repeat
end tell

Make sure to include the following handlers for the script here: makeBargrowItfadeIn, and getLongest

Revert the document back to its original. Run the script, and view the animation in the EPUB Interactivity Preview. Ah, the beauty of mathematics; each bar appears to be animating in at the same rate of speed.

TIMING

There may be one more thing you would want to add to the script to make your bar chart even more impressive: have the legends animate in at the same time its bar is animating. To do this you create a timing group for each bar and its legend. This is part of the spread’s timing settings. You may want to use the following script to investigate the current settings for the spread’s timing settings (default):

Test Script

tell application "Adobe InDesign CC 2017"
    set rectRef to rectangle "barChart" of document 1
    set spreadRef to parent of rectRef
    set testIt to properties of timing lists of timing settings of spreadRef
    try
	set testGroups to timing groups of timing list 1
    on error
	set testGroups to {}
    end try
end tell
{testIt, testGroups}

Notice that timing settings for a spread is just a list of timing lists which define the event that will trigger the groups in its list. When working with a spread’s timing settings, the practice is to delete all current timing settings, and create the ones needed. When you add statements for timing settings to the Test Script it will read as below. We can use this script to verify how timing settings works. Run the following with the Chart created using the Animate Bar Chart script.

Test TimingGroup

tell application "Adobe InDesign CC 2017"
    set rectRef to rectangle "barChart" of document 1
    set spreadRef to parent of rectRef
    set tSettings to timing settings of spreadRef
    tell tSettings
	delete timing list 1
	set loadTimingList to make timing list with properties {trigger event:on page load}
	tell loadTimingList
	    set group1 to make timing group with properties {dynamic target:rectangle "Taxes" of spreadRef, delay seconds:0}
	    tell group1
		make timing target with properties {dynamic target:text frame "Taxes" of spreadRef, delay seconds:0}
	    end tell
	end tell
    end tell
end tell

Run the Test TimingGroup script. In the EPUB Interactive Preview panel, clear the Preview, and click the Play Preview button. Only the first bar animates. The text fades in so quickly that the fade in is barely noticeable.

Now that you have tested making a timing group, expand the top of the original Animate BarChart script to use the timing list and timing groups. Here is the top portion of the script. Make sure you keep the following handlers: makeBar, growItgetLongest.

Animate BarChart

set barList to {{"Taxes", 400}, {"Insurance", 100}, {"Health care", 200}, {"Housing", 500}}
set maxFrame to 47
set tOffsets to {0, 0.5}
set dTime to 2
set hideIt to {false, false}
set textD to 0.5
set oArray to {{0, 0}, {11, 100}}
tell application "Adobe InDesign CC 2017"
    set rectRef to rectangle "barChart" of document 1
    set objStyle to object style "bar" of document 1
    set tStyle to object style "legend" of document 1
    set spreadRef to parent of rectRef
    copy geometric bounds of rectRef to {pTop, pLeft, py1, px1}
    set pBounds to geometric bounds of rectRef
    set longest to my getLongest(barList, 2)
    set testList to {}
    repeat with i from 1 to length of barList
	set theDistance to item 2 of item i of barList
	set propTime to theDistance / longest
	set timeFrame to (round (propTime * maxFrame))
	set xScale to {{0, 100}, {timeFrame, 0}}
	set yScale to {{0, 100}, {timeFrame, 100}}
	set relTime to dTime * propTime
	set end of testList to relTime
	set pTop to pTop + 40
	set dataItem to item i of barList
	my makeBar(spreadRef, pTop, pLeft, dataItem, objStyle, tStyle)
	set objName to item 1 of dataItem
	set textRef to text frame objName of spreadRef
	my fadeIn(textRef, textD, oArray)
	set barRef to rectangle objName of spreadRef
	my growIt(barRef, relTime, xScale, yScale, tOffsets, hideIt)
    end repeat
    (*Timing settings added*)	
    set tSettings to timing settings of spreadRef
    tell tSettings
	delete timing list 1
	set loadTimingList to make timing list with properties {trigger event:on page load}
	tell loadTimingList
	    repeat with j from 1 to length of barList
		set thisName to item 1 of item j of barList
		set thisGroup to make timing group with properties {dynamic target:rectangle thisName of spreadRef, delay seconds:0}
		tell thisGroup
		    make timing target with properties {dynamic target:text frame thisName of spreadRef, delay seconds:0}
		end tell
	    end repeat
	end tell
    end tell	
end tell

Again, revert the document and run the script. View the animation in the EPUB Interactivity Preview panel.

To see the final document published online, Click here. Note: For the purpose of Publish Online, we set the trigger event for the spread timing list to on page click.

ONWARD AND UPWARD

This script works best with objects that are parallel to the page’s horizontal or vertical axis. To use the script with a line oriented otherwise (on an angle), create the line parallel to the page’s axis (horizontal or vertical), add animation, and then rotate. Here is a simple example.

Diagonal Line

set xScale to {{0, 100}, {47, 0}}
set yScale to {{0, 100}, {47, 100}}
set tOffsets to {0, 0.5}
set dTime to 2
set hideIt to {false, false}
tell application "Adobe InDesign CC 2017"
    set spreadRef to spread 1 of document 1
    set swatchRef to swatch "Black" of document 1
    tell spreadRef
	set lineRef to make graphic line with properties {name:"myLine", geometric bounds:{200, 30, 200, 500}, stroke weight:10, stroke color:swatchRef}
	set rotation angle of lineRef to 30
	tell animation settings of lineRef
	    set duration to 2
	    set transform offsets to tOffsets
	    set design option to to current appearance
	    set initially hidden to item 1 of hideIt
	    set scale x array to xScale
	    set scale y array to yScale
	end tell
    end tell
end tell

ON YOUR OWN

Add comments to the script and each of the handlers so you will be able to recognize functionality the next time you need this script or one similar. Always try to write your scripts using handlers with the idea of being able to reuse for other projects.

Try creating a bar chart with vertical bars instead of horizontal. Optionally, set it up so that bars have different colors for fills and stroke. Have fun.

Disclaimer: Demonstration scripts are provided to give users a guide from which to create their own scripts. No representation is given as to their completeness or reliability.