Modifying MXD layout through python script?

2686
8
Jump to solution
10-20-2017 01:37 PM
AllenScully
Occasional Contributor II

Working on a big geoprocessing tool that generates a pdf map from an mxd using arcpy mapping.  For every run of the tool, we need the title to change based on some user inputs.

In addition, we have a feature class that gets truncated/appended during the script run.  In the legend, we are displaying a 'Name' attribute for this particular feature class.  The FC will always contain only 1 record, but we need the value in the legend to change to reflect the most current value in the FC.  

So basically I have 2 python/ArcMap layout questions - title taking user-input values and refreshing values in the legend (which are based on the symbology and values there).  

I've looked at dynamic text options, but these don't appear to have access to attribute-level values.

Thanks - 

Allen

0 Kudos
1 Solution

Accepted Solutions
RandyBurton
MVP Regular Contributor

Some thoughts...

ListLayoutElements may loop through TEXT_ELEMENTs in the same order.  If so, you might be able to set values using this order.  If you delete and add new elements, this order may change.

Have your search text set to a certain default value ("Element1", "Element2", etc.).  Have your script replace these values, print your PDF and then restore the text back to the default values (assuming you are resaving your map each time).

Text Elements also have other properties, such as position (elementPositionX, elementPositionY), which may help identify the elements.

See: TextElement

Edit:

Even better, name your text elements to identify them.  On the "Size and Position" property tab for the text element, there is a box for "Element Name", or you can use Python:

txtCount = 1

for elm in arcpy.mapping.ListLayoutElements(mxd,"TEXT_ELEMENT"):
    elm.name = "TextBox_{}".format(txtCount)
    txtCount += 1

for elm in arcpy.mapping.ListLayoutElements(mxd,"TEXT_ELEMENT"):
    if elm.name == "TextBox_1":
        elm.text = "new text"‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

View solution in original post

8 Replies
JamesCrandall
MVP Frequent Contributor

arcpy.mapping.ListLayoutElements should get you access to the title I'd think.

http://desktop.arcgis.com/en/arcmap/10.3/analyze/arcpy-mapping/listlayoutelements.htm

AllenScully
Occasional Contributor II

Thanks James - 

I've looked at ListLayoutElements briefly.  Experimenting just now, I can't seem to get it to work. I inserted a text element as a title in the layout (keeping 'Text' as the default value), then ran the following code:

for elm in arcpy.mapping.ListLayoutElements(ownerMxd, "TEXT_ELEMENT"):
if elm.text=="Text":
elm = permitType

permitType is a user-input variable (hard-coded for now while developing).  No errors, but the resulting PDF still just shows 'Text' in the title text box.  I'm sure I'm missing something obvious here.  I am running a 'refresh active view' and 'save' command for the mxd.

0 Kudos
AllenScully
Occasional Contributor II

And I already see my mistake - will keep testing.

0 Kudos
RandyBurton
MVP Regular Contributor

Using ListLayoutElements you can search for a text element that contains a specific phrase (assuming the phrase will always be the same) and then change that text:

mxd = arcpy.mapping.MapDocument("CURRENT")
for elm in arcpy.mapping.ListLayoutElements(mxd, "TEXT_ELEMENT"):
    if elm.text == "Text":
        print elm.text
        elm.text = "new text"
arcpy.RefreshActiveView()‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

But I would insert a map Title in Layout view as it uses dynamic text to access and display the title element from the document's properties.  Then use python to edit the title property:

mxd = arcpy.mapping.MapDocument("CURRENT")

print mxd.title
# prints map document property title
# found at File > Map Document Properties

# set map document title and refresh
mxd.title = "My Big Map"
arcpy.RefreshActiveView()‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

See: Working with dynamic text

If I understand your second question, something like this will rename a feature layer to a value found in a field in the feature's first row using SearchCursor.  Line 1 causes this to loop through all layers, but if you have a specific layer name, you might be able to just use a modification of line 3.

for layer in arcpy.mapping.ListLayers(mxd):
    # change layer.name to value in found in FieldName of first row of SearchCursor
    layer.name = arcpy.da.SearchCursor(layer, ("FieldName",)).next()[0]
arcpy.RefreshActiveView()‍‍‍‍‍‍‍‍‍‍‍‍‍
AllenScully
Occasional Contributor II

This is perfect - thanks Randy.  Working well so far.  One more scenario I think I understand but want to check on:

We will have 3 text elements at the bottom of the map that need to get updated with each run of the script.  Thinking this could be tricky since the value will change.  So

if elm.text == "Text":

obviously won't be "Text" after the initial run. I'm thinking with some wise use wildcards and consistency in the values that get written I can work with this?

0 Kudos
RandyBurton
MVP Regular Contributor

Some thoughts...

ListLayoutElements may loop through TEXT_ELEMENTs in the same order.  If so, you might be able to set values using this order.  If you delete and add new elements, this order may change.

Have your search text set to a certain default value ("Element1", "Element2", etc.).  Have your script replace these values, print your PDF and then restore the text back to the default values (assuming you are resaving your map each time).

Text Elements also have other properties, such as position (elementPositionX, elementPositionY), which may help identify the elements.

See: TextElement

Edit:

Even better, name your text elements to identify them.  On the "Size and Position" property tab for the text element, there is a box for "Element Name", or you can use Python:

txtCount = 1

for elm in arcpy.mapping.ListLayoutElements(mxd,"TEXT_ELEMENT"):
    elm.name = "TextBox_{}".format(txtCount)
    txtCount += 1

for elm in arcpy.mapping.ListLayoutElements(mxd,"TEXT_ELEMENT"):
    if elm.name == "TextBox_1":
        elm.text = "new text"‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍
AllenScully
Occasional Contributor II

This is perfect - each text value is populated with a variable in the script, so when done with the pdf generation I can still reference the variable and reset the value to the keyword - works like a charm:

Example: 

Set text value to desired attribute (extracted in variable creation)

for elm in arcpy.mapping.ListLayoutElements(ownerMxd, "TEXT_ELEMENT"):
if elm.text == "NHA":
elm.text = siteNHA  (this is the formatted + attribute-based variable)
print elm.text

Reset text value to default after pdf generation:

for elm in arcpy.mapping.ListLayoutElements(ownerMxd, "TEXT_ELEMENT"):
if elm.text == siteNHA: 
elm.text = "NHA"
print elm.text

Essentially just reversing the value in the 'if' and next elm.text = ... lines

Many thanks, this is super useful.

0 Kudos
AllenScully
Occasional Contributor II

Will add that I missed the last edit you had - I saw that and believe it would be simpler - would remove the additional step of re-setting the text values to their generic keywords.

0 Kudos