How to display output data: Graphical Element, Report, other ideas?

11593
8
Jump to solution
05-05-2014 01:25 PM
KatieGaut1
New Contributor III
Hello,
   I have a Python Add-in Button that I've written to collect user input via comboboxes and then highlight the applicable data.  I need to find a method to display the selected output data and just ONE of the fields.  It doesn't have to be fancy, but I'm having a heck of a time trying to do so in any fashion.  So far I've tried these routes:
1 - MessageBox:  No option to pass a variable or outside data
2 - Report:  My output is a table with many fields.  I can't find a way to select only one field for the report.  Also, it would be more ideal to not require the user to find and open a separate file.
3 - Text Element to Layout:  I can't seem to get my selected data passed as a graphic element.

.  Any thoughts or suggestions would be greatly appreciated!

Thanks,
Katie
Tags (2)
0 Kudos
1 Solution

Accepted Solutions
KatieGaut1
New Contributor III
Matt,
    Thanks so much for that option.  I actually found a much easier way to implement this in Arcpy using the simple messagebox.  I thought I couldn't use it to pass a variable, but I just had to convert the variable to a string and it works fine.  I wouldn't classify it as refined, but it works just great for my purposes.  That segment of my code is below:

y = 0         TestString = str (y + 1) + ': ' + str(SelectedWRs) + '= ' + str (Values) + ' cfs'         x = 1         counter = count.getOutput(0)         counter2 = int (counter)          while x < counter2:             y += 1             TestString = TestString + '\n' + str (y + 1) + ': ' + str(SelectedWRs) + '= ' + str (Values) + ' cfs'             x = x + 1                          ProtectedWaterBoolean = pythonaddins.MessageBox('Selected Management Point: ' + str(selection) + '\n\n' +                                                        'Water Rights with Protected Water on ' + str(SelectedDate) + ':' + '\n' + TestString + '\n\n' +                                                        'Total CFS Protected:  ' + str(ValuesSum) + '\n\n', 4)         

View solution in original post

0 Kudos
8 Replies
MattEiben
Occasional Contributor
What sort of data are you looking to display?  If it's something simple, you might be able to display it through a Tkinter window.
Here's an example of a plain Tkinter Text box that you can write whatever you want into.

from Tkinter import *
from ttk import *
import tkMessageBox

class DisplayResults():

    def __init__(self):
    
        self.root = Tk()
        self.root.title("Geoprocessing Results")
        self.root.minsize(width=770, height=300)
        self.root.config(padx=10, pady=10)

        Style().configure(".", font=("Helvetica", 10))
        
        self.frame = Frame()
        self.frame.pack(fill="both", expand=1)
    
        self.createWidgets()
        
        self.root.protocol("WM_DELETE_WINDOW", self.quitCallback)
        
    def createWidgets(self):
    
        self.textbox = Text(self.frame, height=20, width=120, relief="sunken", state="normal")
        self.textbox.pack(fill="both", expand=1)

        self.yscrollbar = Scrollbar(self.textbox, orient="vertical", cursor="arrow" ,command=self.textbox.yview)
        self.yscrollbar.pack(side="right", fill="y")
        self.textbox["yscrollcommand"]=self.yscrollbar.set
        
    def printToScreen(self, content):
    
        self.textbox.insert("end", content + "\n")
        
    def quitCallback(self):
        """
        If user closes the prompt window, the program will exit on 'OK'
        """
        if tkMessageBox.askokcancel("Quit", "Do you really wish to quit?"):
            self.root.destroy()
            sys.exit(1)

gui = DisplayResults()

gui.printToScreen("data")

gui.root.mainloop()
0 Kudos
KatieGaut1
New Contributor III
I apologize for the lack of clarity. My selected data is a table with 366 fields (an id and values for 365 days of the year). I need to display only the id and the value corresponding to the day of the year selected in the combobox. I can't seem to find a way to only display this selected data (without the other 364 days). I've tried to convert the table to a feature class in order to display attributes in a table but every conversion I've tried (to excel, to geodb, etc) has a record limit of 256 & there are no options to limit the field to my selected day. I've also tried reports, but have the same field number issue.

I will take a look at the TkInter option too, was just hoping for an easier solution as I feel like I may be missing something simple.

Thanks!
0 Kudos
MattEiben
Occasional Contributor
Could you turn off the visibility of the fields for the days you don't want?
Then any geoprocessing function would only output the fields you have turned on.

In ArcMap, you'd turn them off through the "fields tab" in the properties window for that feature layer. 

If you're dealing with ArcPy, you have to make another feature layer.  The key is to use the field info parameter when making your new feature layer.

Example:

visibleFields = ["ID", "DesiredField1", "DesiredField2"]

field_info = arcpy.Describe("FeatureLayer").fieldInfo
for index in xrange(0, field_info.count):
    if field_info.getfieldname(index) not in visibleFields:
        field_info.setvisible(index,"HIDDEN")

arcpy.MakeFeatureLayer_management("FeatureLayer","Feature_Layer_With_New_Visibilities","","",field_info)

# Run your reporting process on your new feature layer

# Clean up your feature layer with new visibilities when you're done with it.
arcpy.Delete_management("Feature_Layer_With_New_Visibilities")


Hope this helps!
0 Kudos
KatieGaut1
New Contributor III
Hi eibenm,
   Thanks so much for the response.  I am writing an add-in in arcpy.  Unfortunately, I can't use the visibility attribute as the field is user selected.  I have a combobox that allows the user to select a date that corresponds to the field.  I need to have something coded that automatically parses in this variable. 
    I can't create a feature layer either as the input data is a table.  Any other thoughts?

Thanks,
Katie
0 Kudos
MattEiben
Occasional Contributor
Ok, I think I see what you're after here.  Something like this may work:

dateField = # Method to get the date field you want

# Method that puts each row into a dictionary
def rows_as_dicts(cursor):
    colnames = cursor.fields
    for row in cursor:
        yield dict(zip(colnames, row))

# Record of the data you want in your new table
fields = ["id",dateField]
data = [row for row in rows_as_dicts(arcpy.da.SearchCursor("Your Table",fields))

# Create your new table -- Mine is "in_memory"
table = arcpy.CreateTable_management("in_memory", tableName)
arcpy.AddField_management(table, "id", "LONG", 9)
arcpy.AddField_management(table, dateField, "TEXT")

# Populate your table
with arcpy.da.InsertCursor(table, fields) as cursor:
    for row in data:
        cursor.insertRow([row["id"], row[dateField]])

# Run your reporting with your new in_memory table

# Clean up your temporary table
arcpy.Delete_management(table)


The first variable "dateField" is up to you to populate based of the field the user chooses from the combo box.  Essentially, it needs to be the name of the desired field.

The script goes through your existing table using the desired fields and puts it into memory using a list filled with dictionaries for each row.  It then creates a second in memory table (you can actually put it on your HDD if you want, though working in memory will be faster), add the fields desired, and populates it with the desired information from your first table.

Then you can run whatever reporting you want on your new table and remove it afterwards.
0 Kudos
KatieGaut1
New Contributor III
Hi Matt,
   I have the output now as a feature layer, but still having problems with the reporting.  Again, I just need to display the data to the user in a message box or table on the layout.  The data is simply a list of records with values and ideally the sum of those values.  Any thoughts on how to do this in arcpy (ArcMap 10.2.1)? 
(The dynamic tables won't work as I don't want to enable data driven pages.)

Thanks!
0 Kudos
MattEiben
Occasional Contributor
I've looking into displaying a table into the layout within ArcMap and there is a way to do it graphically, but no way exists through arcpy currently.  So, I've created a bit of a custom solution.  Essentially, a search cursor gets the data you want, and opens it up in a Tkinter table through a separate window.

The way you'd want to set this up is have your GUI File in the same "Install" directory as your main add-in Python file.  After the search cursor gets your information it needs, it'll dump it into a temporary file through the pickle module.  The GUI file is then called through a separate process, opens the data stored in the pickled file from ArcMap, and displays the data.

If your're not familiar with the pickle module, it's essentially a module that save a Python object to a file on disk where is can be opened by a separate process later for use.

I realize this is super roundabout, but since I couldn't find a native ArcMap solution, I'm working around the system a bit.

Here's the code you're want to include in your Python add-in file:

import arcpy
import pythonaddins
import pickle, os
from subprocess import Popen, PIPE

class YourPythonAddinClass(object):
    """
    Implementation for Create_New_Point_addin.createNewXYPoint (Button)
    """
    def __init__(self):
        ...
        ...
        ...

    def yourOnClickFunction(self):

        featureClass = arcpy.mapping.Layer("Your Feature Class")

        dateField = # Method to get the date field you want
        fields = ["id",dateField]

        data = [row for row in arcpy.da.SearchCursor(featureClass,fields)]
        data.insert(0, tuple(fields))

        dataDumpFile = os.path.join(os.path.dirname(os.path.abspath(__file__)), "datadump.p")
        pickle.dump(data, open(dataDumpFile,"wb"))

        open_GUI("listViewGUI.py",dataDumpFile)

def open_GUI(gui_file_name, file_location):
    file_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), gui_file_name)
    proc = Popen([file_path,file_location], shell=True, stdout=PIPE, bufsize=1)
    stdoutdata, stderrdata = proc.communicate()
    return stdoutdata


You'll of course want to get your "dateField" and know what name your Feature Class is when making a layer from it.

The code from the second file what I'm saving under the name "listViewGUI.py" in the same directory as your Python add-in file:

import Tkinter as tk
import tkFont, ttk, tkMessageBox
import math, pickle, sys

class ListBox(object):
    """use a ttk.TreeView as a multicolumn ListBox"""

    sampleHeader = ["Column 1","Column 2"]
    sampleData = [("data1","data1"),("data2","data2")]

    def __init__(self, header=sampleHeader, data=sampleData):

        self.root = tk.Tk()
        self.root.wm_title("Table Output")
        self.root.minsize(width=300, height=300)

        ttk.Style().configure('.', font=('Courier', 10))

        self.header = header
        self.data = data

        self.tree = None
        self._setup_widgets()
        self._build_tree()

        self.root.protocol("WM_DELETE_WINDOW", self.quitCallback)

        self.root.mainloop()

    def _setup_widgets(self):
        container = ttk.Frame()
        container.pack(fill='both', expand=True)

        # create a treeview with dual scrollbars
        self.tree = ttk.Treeview(columns=self.header, show="headings")
        vsb = ttk.Scrollbar(orient="vertical", command=self.tree.yview)

        self.tree.configure(yscrollcommand=vsb.set)
        self.tree.grid(column=0, row=0, sticky='nsew', in_=container)

        vsb.grid(column=1, row=0, sticky='ns', in_=container)

        container.grid_columnconfigure(0, weight=1)
        container.grid_rowconfigure(0, weight=1)

    def _build_tree(self):

        for col in self.header:
            self.tree.heading(col, text=col.title())
            # adjust the column's width to the header string
            self.tree.column(col, width=tkFont.Font().measure(col.title()))

        for item in self.data:
            self.tree.insert('', 'end', values=item)
            # adjust column's width if necessary to fit each value
            for ix, val in enumerate(item):
                col_w = tkFont.Font().measure(val)
                if self.tree.column(self.header[ix], width=None) < col_w:
                    self.tree.column(self.header[ix], width=int(math.floor(col_w * 1.75)))

    def quitCallback(self):
        """
        If user closes the prompt window, the program will exit on 'OK'
        """
        if tkMessageBox.askokcancel("Quit", "Do you really wish to quit?"):
            self.root.destroy()
            sys.exit(1)

def main(argv):
    data = pickle.load(open(argv[1], "rb"))

    header = data[0]
    data_list = data[1:]

    guiTable = ListBox(header=header, data=data_list)

main(sys.argv)


This class essentially receives your data from the Search Cursor and opens it in a Tkinter based table.

I've tested out this table view and it seems to display fine.  I've also run Tkinter windows through a Python add-in in this style before and it has worked great.  Sorry about the roundabout solution again, but I hope this helps!

Matt
0 Kudos
KatieGaut1
New Contributor III
Matt,
    Thanks so much for that option.  I actually found a much easier way to implement this in Arcpy using the simple messagebox.  I thought I couldn't use it to pass a variable, but I just had to convert the variable to a string and it works fine.  I wouldn't classify it as refined, but it works just great for my purposes.  That segment of my code is below:

y = 0         TestString = str (y + 1) + ': ' + str(SelectedWRs) + '= ' + str (Values) + ' cfs'         x = 1         counter = count.getOutput(0)         counter2 = int (counter)          while x < counter2:             y += 1             TestString = TestString + '\n' + str (y + 1) + ': ' + str(SelectedWRs) + '= ' + str (Values) + ' cfs'             x = x + 1                          ProtectedWaterBoolean = pythonaddins.MessageBox('Selected Management Point: ' + str(selection) + '\n\n' +                                                        'Water Rights with Protected Water on ' + str(SelectedDate) + ':' + '\n' + TestString + '\n\n' +                                                        'Total CFS Protected:  ' + str(ValuesSum) + '\n\n', 4)         
0 Kudos