Index: cck_schema.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/graphviz_filter/Attic/cck_schema.module,v
retrieving revision 1.1.2.4.2.5
diff -u -p -r1.1.2.4.2.5 cck_schema.module
--- cck_schema.module	3 Oct 2009 18:29:22 -0000	1.1.2.4.2.5
+++ cck_schema.module	23 Jan 2010 03:31:58 -0000
@@ -9,10 +9,17 @@ function cck_schema_menu() {
     'access arguments' => array('administer content types'),
     'weight' => 10,
   );
+  $items['admin/content/types/schema/dot'] = array(
+    'title' => t('Schema DOT file'),
+    'type' => MENU_NORMAL_ITEM,
+    'page callback' => 'cck_schema_dump',
+    'access arguments' => array('administer content types'),
+    'weight' => 10,
+  );
   return $items;
 }
 
-function cck_schema_render() {
+function cck_schema_generate() {
   require_once(_graphviz_create_filepath(variable_get('graphviz_filter_pear_path', ''), 'Image/GraphViz.php'));
   $G = new Image_Graphviz(TRUE);
   foreach (taxonomy_get_vocabularies() as $vid => $vocabulary) {
@@ -64,10 +71,28 @@ function cck_schema_render() {
   ));
 
   _cck_schema_relativity($G);
+  return $G;
+}
 
+function cck_schema_render() {
+  $G = cck_schema_generate();
   return graphviz_filter_render($G);
 }
 
+/**
+ * Return the DOT file for the cck schema
+ */
+function cck_schema_dump() {
+  $G = cck_schema_generate();
+  $dot = '/*
+* @formats = png
+* @imagemap = TRUE
+*/
+';
+  $dot .= $G->parse();
+  return "<pre>$dot</pre>";
+}
+
 function _cck_schema_nodereference_cardinality($field) {
   if ($field['required']) {
     if ($field['multiple']) {
Index: panels_schema.info
===================================================================
RCS file: panels_schema.info
diff -N panels_schema.info
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ panels_schema.info	23 Jan 2010 03:31:59 -0000
@@ -0,0 +1,7 @@
+; $Id:  Exp $
+name = Panels Schema
+description = Produces a pseudo-UML class diagram of panels using Graphviz.
+dependencies[] = panels
+dependencies[] = graphviz_filter
+package = panels
+core = 6.x
Index: panels_schema.module
===================================================================
RCS file: panels_schema.module
diff -N panels_schema.module
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ panels_schema.module	23 Jan 2010 03:31:59 -0000
@@ -0,0 +1,762 @@
+<?php
+// $Id:  $
+
+/**
+ * @file render the structure of the sites panels in a graphViz diagram
+ * 
+ * Functions here include: 
+ * 
+ * panels_schema_construct_*() : functions which return a 'record' array
+ * suitable to add to a graphviz object.
+ * 
+ * panels_schema_add_*() : which constructs AND adds a graphviz node, returning
+ * the element ID which probably needs to be linked to from the parent.
+ * 
+ * 
+ * 
+ */
+
+ 
+/**
+ * Implementation of hook_menu()
+ */
+function panels_schema_menu() {
+  $items['admin/build/panels/schema'] = array (
+    'title' => t('Schema'),
+    'type' => MENU_LOCAL_TASK,
+    'page callback' => 'panels_schema_render',
+    'access arguments' => array (
+      'use panels dashboard'
+    ),
+    'weight' => 10,
+  );
+  $items['admin/build/panels/dot'] = array (
+    'title' => t('Schema DOT file'),
+    'type' => MENU_NORMAL_ITEM,
+    'page callback' => 'panels_schema_dump',
+    'access arguments' => array (
+      'use panels dashboard'
+    ),
+  );
+  return $items;
+}
+
+/**
+ * Menu callback. Builds and displays the graph.
+ */
+function panels_schema_render() {
+  $G = panels_schema_generate();
+  return graphviz_filter_render($G);
+}
+
+/**
+ * Menu callback. Return the DOT file for the cck schema
+ */
+function panels_schema_dump() {
+  $G = panels_schema_generate();
+  $dot = '/*
+      * @formats = png
+      * @imagemap = TRUE
+      */
+      ';
+  $dot .= $G->parse();
+  return "<pre>$dot</pre>";
+}
+
+/**
+ * Create graph and add all panels pages to it. 
+ * 
+ */
+function panels_schema_generate() {
+  require_once (_graphviz_create_filepath(variable_get('graphviz_filter_pear_path', ''), 'Image/GraphViz.php'));
+  $G = new Image_Graphviz(TRUE);
+
+  // Keep a note of the views that we wwant to refer to
+  global $panels_schema_required_views;
+  
+  panels_schema_add_panels_pages($G);
+  panels_schema_add_panels_mini($G);
+  panels_schema_add_views($G, $panels_schema_required_views);
+  return $G;
+}
+
+/** 
+ * Retrieve all the 'panels' pages, build all the elements and add them to the
+ * graph.
+ * 
+ */
+function panels_schema_add_panels_pages(&$G) {
+  // The API for getting at panels is inpenetrable
+  // Taking some guesses by cribbing code from 
+  // @see page_manager_page_execute()
+
+  if (module_exists('page_manager')) {
+    module_load_include('inc', 'page_manager', 'plugins/tasks/page');
+    $pages = page_manager_page_load_all();
+    foreach ($pages as $id => $page) {
+      // Draw each 'page' and its sub-elements
+      panels_schema_add_page($G, $page);
+    }
+  }
+  return $G;
+}
+
+/** 
+ * Retrieve all the 'mini' panels, build all the elements and add them to the
+ * graph.
+ * 
+ */
+function panels_schema_add_panels_mini(&$G) {
+  if (module_exists('panels_mini')) {
+    $mini_panels = panels_mini_load_all();
+    foreach ($mini_panels as $id => $mini_panel) {
+      // Draw each mini and its sub-elements
+      $mini_panel_record = panels_schema_construct_panels_mini($G, $mini_panel);
+      $G->addNode($mini_panel_record['id'], $mini_panel_record, $mini_panel_record['group']);
+    }
+  }
+  return $G;
+}
+
+/**
+ * Build a mini panel.
+ * 
+ * @return $panel_record suitable for adding to the graph.
+ */
+function panels_schema_construct_panels_mini(&$G, $content_item) {
+  // $content item may EITHER be a panels_mini object, or a display describing it
+  if ($content_item->table == 'panels_mini') {
+    $mini_panel = $content_item;
+  }
+  else {
+    $mini_panel = panels_mini_load($content_item->subtype);
+  }
+
+  // It's largely the same as a 'pane'
+  $panel_record = panels_schema_construct_pane($G, $mini_panel);
+  $panel_record['URL'] = url("admin/build/panel-mini/{$mini_panel->name}");
+
+  // Also set properties on the 'global' cluster group that encloses this 'display' and its regions.
+  if ($panel_record['group']) {
+    $G->addcluster($panel_record['group'], 'mini panel', array('bgcolor' => 'darkslategray2'));
+  }
+
+  return $panel_record;
+}
+
+
+/**
+ * Add a given panels 'page' to the node graph
+ * 
+ * Returns the id of the created element
+ */
+function panels_schema_add_page(&$G, $page) {
+  // The best way to extract a meaningful representation of the view 
+  // without emulating swathes of existing code is to 'export' and use that structured object.
+  $page_export = page_manager_page_export($page, TRUE);
+  eval($page_export);
+
+  $fields = array (
+    'path' => $page->path,
+  );
+  $page_element_id = $page->name;
+  $page_record = panels_schema_construct_record($page->admin_title, $page->task, $page->name, $fields, $page->path);
+  $G->addNode($page_element_id, $page_record);
+
+  // PAINFUL
+  // Found a way to try and load all the 'displays' that are provided by 
+  // 'handlers' to the 'page's
+  
+  // Because a page may have several 'handlers' need to loop though them.
+  // This means that the one 'display' cannot always be expected to be the sole 
+  // representation of the 'page'. We need to allow for more than one.
+  $handlers = $page->default_handlers;
+  foreach ($handlers as $handler_id => $handler) {
+    // The interesting part of the handler is its 'display'
+    // Add that as a direct child of the 'page'
+    $display = $handler->conf['display'];
+    #$display = panels_panel_context_get_display($handler); 
+    
+    $display_record = panels_schema_construct_display($G, $handler);
+    $display_element_id = $display_record['id'];
+    $G->addNode($display_element_id, $display_record, $display_record['group']);
+    $G->addEdge(array ($page_element_id => $display_element_id));
+
+    // Set properties on the cluster group that encloses this 'display' and its regions.
+    if ($display_record['group']) {
+      $G->addcluster($display_record['group'], 'page display', array('bgcolor' => 'darkslategray3'));
+    }
+    // Add an extra ID to ensure unique names on the displays when we create them;
+    #dpm(get_defined_vars());
+    $display->parentid = $display_element_id;
+    
+    // Create the elements (blocks, views etc) that are displayed in the display regions
+    $region_ids = panels_schema_create_display_contents($G, $display, $display_record['group']);
+    foreach($region_ids as $region_id) {
+      $G->addEdge(array($display_element_id => $region_id), array('weight' => 10));
+    }
+
+    // * OPTIONAL * 
+    // Handlers are extra entities, but slighty outside of the structure
+    // This addition may be TMI and redundant
+    /*
+    $handler_element_id = panels_schema_add_handler($G, $handler);
+    $handler_element_id;
+    $G->addEdge(
+      array ($handler_element_id => $display_element_id), 
+      array ('style' => 'dashed')
+    );
+    */
+  } // each display
+  
+  return $page_element_id;
+}
+
+/**
+ * Prepare an element representing a 'display' based on 'handler' data
+ */
+function panels_schema_construct_display(&$G, $handler) {
+  // url is correct for 'pages' only?
+  $edit_url = "admin/build/pages/edit/{$handler->task}-{$handler->subtask}";
+
+  $fields = array (
+    'layout' => $handler->conf['display']->layout,
+    'display id' => $handler->conf['did'],
+    // Display ID must be unique per container it's in.
+    #'display id' => $handler->name .'-'. $handler->conf['did'],
+  );
+  #  dpm(get_defined_vars());
+  if (!empty($handler->conf['css_id'])) {
+    $fields['css_id'] = $handler->conf['css_id'];
+  }
+  
+  // Make up a label for this page+context+display
+  $label = "$handler->task $handler->subtask";
+  $type = $handler->conf['title'] . " display";
+  $record = panels_schema_construct_record($label, $type, $handler->name, $fields, $edit_url);
+  $record['group'] = $label;
+  
+  return $record;
+}
+
+/**
+ * Recurse into each content panel pane of the given display and make a
+ * container element for each region, then add the content elements to them
+ * 
+ * @return a list of the ids of the created content regions, Add them to the
+ * parent.
+ */
+function panels_schema_create_display_contents(&$G, $display, $group_id = '') {
+  if (! is_array($display->panels)) {
+    trigger_error('A contents display must contain a panels array, otherwise I don\'t understand it');
+    dpm(debug_backtrace());
+    return FALSE;
+  }
+  
+  // A pane is a group/region of content
+  $created_regions = array();
+  foreach ($display->panels as $panelname => $content_indexes) {
+    $region_id = "display-{$display->did}-$panelname";
+    $region_id = "display-{$display->parentid}-{$panelname}-{$display->did}";
+
+    // Create the container region
+    $subrecord = panels_schema_construct_record($panelname, 'panels_region', $region_id, array());
+    
+    // The regions are clustered with the parent (group_id is set), 
+    // but the /contents/ of the region are not.
+    $G->addNode($region_id, $subrecord, $group_id);
+    // Make a note.
+    $created_regions[$region_id] = $region_id;
+    
+    // Each region has some content
+    // List and link to the content items in each panel region
+    foreach ($content_indexes as $content_index) {
+      // Note my $content_index is called 'pid' by the panels system
+      $content_pane = $display->content[$content_index];
+
+      if (empty($content_pane)) {
+        trigger_error("No content where expected in the content array [$content_index]:");
+        #dpm(get_defined_vars());
+        next;
+      }
+      $content_element_record = panels_schema_construct_content($G, $content_pane);
+      $G->addNode($content_element_record['id'], $content_element_record);
+      $G->addEdge(array($region_id => $content_element_record['id']));
+    } // Added content elements to the panel
+  } // Added panels to the display
+  
+  return $created_regions;
+}
+  
+
+/**
+ * This renders the description of an individual content pane, and any theming
+ * info that may be set to apply to it.
+ * 
+ * Then, depending on the detected type, more info about the content that is to
+ * be rendered here is added.
+ * 
+ * Display may be handed off to individual element renderers - magically named
+ * callbacks.
+ * 
+ * @see panels_schema_create_display_contents()
+ */
+function panels_schema_construct_content(&$G, $content_item) {
+  // Beware, the id set here may be overridden later if the content item type is 
+  // found to be something we recognize. 
+  $content_element_id = "display_{$content_item->did}-panel_{$content_item->pid}";
+
+  // Try to render different types differently using different constructors
+  $constructor_func = "panels_schema_construct_{$content_item->type}";
+  if (function_exists($constructor_func)) {
+    $record = $constructor_func($G, $content_item);
+    # eg panels_schema_construct_block()
+    # eg panels_schema_construct_panels_mini()
+    # eg panels_schema_construct_panels_pane()
+  }
+  else {
+    // A mostly-default rendering of any views display
+    #dpm(array("No special renderer for $constructor_func" => $content_item));
+    $fields = array ();
+    if (!empty($content_item->css['css_id'])) {
+      $fields['css_id'] = $content_item->css['css_id'];
+    }
+    if (!empty($content_item->css['css_class'])) {
+      $fields['css_class'] = $content_item->css['css_class'];
+    }
+    if (!empty($content_item->configuration['override_title'])) {
+      $fields['override_title_text'] = $content_item->configuration['override_title_text'];
+    }
+    $record = panels_schema_construct_record($content_item->subtype, $content_item->type, $content_element_id, $fields);
+  }
+
+  return $record;
+}
+
+/**
+ * 
+ * Called magically from panels_schema_construct_content()
+ */
+function panels_schema_construct_pane(&$G, $pane) {
+  $pane_element_id = $pane->name;
+  $display = $pane->display;
+  $url = '';
+  $fields = array (
+    'category' => $pane->category,
+    'layout' => $display->layout,
+  );
+
+  $record = panels_schema_construct_record($pane->title, $pane->table, $pane_element_id, $fields, $url);
+  $record['group'] = $pane_element_id;
+
+  #dpm(get_defined_vars());
+  $display->parentid = $pane_element_id;
+
+  // Add the panels in this pane
+  $region_ids = panels_schema_create_display_contents($G, $display, $pane_element_id);
+  foreach($region_ids as $region_id) {
+    $G->addEdge(array($pane_element_id => $region_id));
+  }
+  return $record;
+}
+
+/**
+ * Define an element that represents a block
+ * 
+ * Called magically from panels_schema_construct_content()
+ */
+function panels_schema_construct_block($G, $content_item) {
+  list($block_module, $block_delta) = split('-', $content_item->subtype, 2);
+  $block_infos = module_invoke($block_module, 'block', 'list', $block_delta);
+  $block_info = $block_infos[$block_delta]['info'];
+  $block = module_invoke($block_module, 'block', 'view', $block_delta);
+  $record = array(
+    'shape' => 'box3d',
+    'type' => 'block',
+    'label' => $block_info, #$content_item->subtype,
+    'id' => "block-$block_module-$block_delta",
+    'URL' => url("admin/build/block/configure/$block_module/$block_delta"),
+  );
+  return $record;
+}
+
+/**
+ * Called magically from panels_schema_construct_content()
+ */
+function panels_schema_construct_views_panes(&$G, $content_item) {
+  $content_element_id = "display_{$content_item->did}-panel_{$content_item->pid}";
+
+  list($name, $display_id) = explode('-', $content_item->subtype);
+  $view_display_id = "view-{$content_item->subtype}";
+
+  // We get slightly different arguments when called from different contexts
+  if (! $display_id && @$content_item->configuration['display']) {
+    $display_id = $content_item->configuration['display'];
+    $view_display_id = "view-{$content_item->subtype}-{$display_id}";
+  }
+  $view = views_get_view($name);
+  $view->set_display($display_id);
+  $display = $view->display[$display_id];
+  
+  global $panels_schema_required_views;
+  $panels_schema_required_views[$view->name] = $view->name;
+  
+  $fields = array();
+  if (!empty($content_item->css['css_id'])) {
+    $fields['css_id'] = $content_item->css['css_id'];
+  }
+  if (!empty($content_item->css['css_class'])) {
+    $fields['css_class'] = $content_item->css['css_class'];
+  }
+  if (!empty($content_item->configuration['override_title'])) {
+    $fields['override_title_text'] = $content_item->configuration['override_title_text'];
+  }
+  if (!empty($display->display_title)) {
+    $fields['display_title'] = $display->display_title;
+  }
+  if (!empty($content_item->configuration['display'])) {
+    $fields['display'] = $content_item->configuration['display'];
+  }
+
+  $record = panels_schema_construct_record($content_item->subtype, $content_item->type, $content_element_id, $fields);
+
+  #dpm(get_defined_vars());
+
+  // Then point this element at the views pane it uses.
+  // It loosely REFERS to the view display, cannot contain it, 
+  // because the same view may occur in different places, and is owned by the view.module
+  // This edge (relationship) is undirected.
+  $G->addEdge(array($content_element_id => $view_display_id), array('style' => 'dotted', 'dir' => 'none'));
+  // If the view display was not already rendered, 
+  // then an edge will be created pointing to a floating node.
+
+  return $record;
+}
+
+/**
+ * Almost identical for panels_schema_construct_views_panes. It's for references
+ * to views elements that are not 'panes' - such as the default view.
+ * 
+ * Called magically from panels_schema_construct_content()
+ */
+function panels_schema_construct_views(&$G, $content_item) {
+  return panels_schema_construct_views_panes($G, $content_item);
+}
+
+/**
+ * Add a given panels 'handler' to the node graph
+ * 
+ * superflouous?
+ */
+function panels_schema_add_handler(&$G, $handler) {
+  $handler_id = $handler->handler;
+  $G->addNode($handler_id, array(
+    'shape' => 'ellipse',
+    'label' => 'handler: '. $handler->name,
+  ));
+  return $handler_id;
+}
+
+/**
+ * ####################
+ * ## Views
+ * ####################
+ */
+ 
+/**
+ * Create definitions for all known views
+ */ 
+function panels_schema_add_views(&$G, $view_ids = array()) {
+  if (empty($view_ids)) {
+    $views = views_get_all_views();
+  }
+  else {
+    foreach ($view_ids as $view_id) {
+      $views[$view_id] = views_get_view($view_id);
+    }
+  }
+  
+  $record_ids = array();
+  foreach ($views as $view_id => $view) {
+    $element_id = panels_schema_add_view($G, $view);
+    $record_ids[$element_id] = $element_id;
+  }
+  return $record_ids;
+}
+
+function panels_schema_add_view(&$G, $view) {
+  $view_record = panels_schema_construct_view($G, $view);
+  $G->addNode($view_record['id'], $view_record, $view_record['group']);
+  return $view_record['id'];
+}
+
+/**
+ * Define an alement that represents a views pane
+ * 
+ * We may need to trigger the rendering of the 'view' default definition just to
+ * support this pane which is a subset of the view.
+ */
+function panels_schema_construct_view(&$G, $view) {
+  # TODO views are HUGE
+  $label = $view->name;
+  $id = "view-{$view->name}";
+  $url = "admin/build/views/edit/$view->name";
+  $attributes = array();
+
+  // Bulk copy a few attributes definition to show onscreen
+  $copy_attributes = array('description', 'tag', 'base_table');
+  foreach($copy_attributes as $attname) {
+    if (!empty($view->$attname))
+    $attributes[$attname] = $view->$attname;
+  }
+  
+  $view_record = panels_schema_construct_record($label, 'view', $id, $attributes, $url);
+
+   // A view is a member of a group named after itself. 
+   // Maybe we will throw some extras in to jiun it, but a 'view' is pretty top-level.
+  $view_record['group'] = $id; 
+  if ($view_record['group']) {
+    $G->addcluster($view_record['group'], 'view definition', array('bgcolor' => 'bisque2'));
+  }
+  
+  #dpm(get_defined_vars());
+
+  // Add a child for each of the displays that this parent view provides.
+  foreach ($view->display as $display_id => $views_display) {
+    $view->set_display($display_id);
+    $views_display_element = panels_schema_construct_views_display($G, $views_display);
+    // When adding these, they are in the parent 'group'
+    $G->addNode($views_display_element['id'], $views_display_element, $id);
+    $G->addEdge(array($id => $views_display_element['id']));
+  }
+  return $view_record;
+}
+
+/**
+ * Construct various elements representing individual displays provided by a
+ * view. Different styling may happen depending on the display 'handler'
+ */
+function panels_schema_construct_views_display(&$G, $display) {
+  $view = $display->handler->view;
+#dpm($display);
+  // Try to render different types differently using different constructors
+  $constructor_func = "panels_schema_construct_views_{$display->display_plugin}";
+  if (function_exists($constructor_func)) {
+    $record = $constructor_func($G, $display);
+    # eg panels_schema_construct_views_block()
+  }
+  else {
+    // A mostly-default rendering of any views display
+    $label = $display->display_title;
+    $id = "view-{$view->name}-{$display->id}";
+    $url = url("admin/build/views/edit/$view->name", array('fragment' => "views-tab-{$display->id}", 'absolute' => TRUE));
+    $attributes = array(
+     'type' => 'views-' . $display->display_plugin,
+    );
+    
+    // Bulk copy a few attributes from the display options to show onscreen
+    $copy_attributes = array('title', 'pane_title', 'row_plugin');
+    foreach ($copy_attributes as $attname) {
+      if (!empty($display->display_options[$attname]))
+      $attributes[$attname] = $display->display_options[$attname];
+    }
+    $record = panels_schema_construct_record($label, 'view-'. $display->display_plugin, $id, $attributes, $url);
+  }
+#dpm($record);
+  return $record;
+}
+
+
+function panels_schema_construct_views_block(&$G, $display) {
+  $view = $display->handler->view;
+  $label = $display->display_title;
+  if (!empty($display->display_options['block_description'])) {
+    $label .= ": {$display->display_options['block_description']}";
+  }
+
+  $id = "view-{$view->name}-{$display->id}";
+  $url = url("admin/build/views/edit/$view->name", array('fragment' => "views-tab-{$display->id}", 'absolute' => TRUE));
+  $record = array(
+    'shape' => 'box3d',
+    'type' => 'views-block',
+    'label' => $label,
+    'id' => $id,
+    'URL' => $url,
+  );
+  return $record;
+}
+
+
+/**
+ * ####################
+ * ## Utility
+ * ####################
+ */
+
+/**
+ * General-purpose utility to quickly construct a 'record'
+ * 
+ * Depending on the type or other properties, extra rendering attributes may be
+ * added.
+ * 
+ * @param $label primary label
+ * @param $type type of the item
+ * @param $id 
+ * @param $attributes name-value array
+ * @param $url optional link
+ */
+function panels_schema_construct_record($label = '', $type = '', $id = NULL, $attributes = array (), $url = NULL) {
+  $string = $label;
+
+  if (!empty ($type))
+    $string = "$type: " . $string;
+  if (!empty ($id))
+    $attributes['ID'] = $id;
+
+  foreach ($attributes as $key => $value) {
+    $atts[] = "$key: $value";
+  }
+  if (!empty ($atts))
+    $string .= '|' . implode('\l', $atts) . '\l}';
+
+  $record = array (
+    'shape' => 'record',
+    'label' => '{' . $string . '}',
+    'id' => $id,
+    'group' => NULL,
+  );
+
+  if (!empty ($url))
+    $record['URL'] = url($url);
+
+  // Add optional styling
+  static $graphviz_styles;
+  if (! isset($graphviz_styles)) $graphviz_styles = module_invoke_all('graphviz_styles');
+  if (isset($graphviz_styles[$type])) {
+    $record = array_merge($record, $graphviz_styles[$type]);
+  }
+  #else {dpm("no special style setting for $type");}
+    
+  return $record;
+}
+
+
+/**
+ * Utility to dump a php object or array 
+ * Recursive.
+ * 
+ * Setting 'group' is intended to help apply some hints to the graph, getting
+ * all members of an array lined up together.
+ */
+function panels_schema_add_item(&$G, $item, $name='', $group ='') {
+  $record = panels_schema_construct_item($G, $item, $name, $group);
+  $G->addNode($record['id'], $record, $record['group']);
+  return $record['id'];
+}
+
+function panels_schema_construct_item(&$G, $item, $name='', $group ='') {
+  $type = gettype($item);
+  static $ids, $id_count, $done_objects;
+  if (empty($done_objects)) $done_objects = array();
+  $id_count ++;
+  $item_id = $type .'-'. $id_count;
+
+  // Prevent looping recursion by keeping a note of seen things
+  // and returning just the ID if it's already built
+  if (is_object($item) || is_array($item)) {
+    $item_id = md5(print_r($item, 1)); // this will return the internal obj id as a string
+  }
+  if (in_array($item_id, $done_objects)) {
+    return $item_id;
+  }
+  $done_objects[] = $item_id;
+
+  // Build object
+  $item = (array) $item;
+  $children = array();
+  foreach ($item as $varname => $value) {
+    if (is_array($value) || is_object($value)) {
+      $children[$varname] = $value;
+    }
+    else {
+      $attributes[] = "$varname: " . strip_tags($value);
+    }
+  }
+
+  $dot_string = $name;
+  if (!empty ($type)) {
+    $dot_string = "$type: " . $dot_string;
+  }
+  if (!empty ($attributes)) {
+    $dot_string .= '|' . implode('\l', $attributes) . '\l}';
+  }
+  $record = array (
+    'shape' => 'record',
+    'label' => '{' . $dot_string . '}',
+    'color' => 'blue',
+  );
+  if ($group) {$record['group'] = $group;}
+  $record['id'] = $item_id;
+  
+  // Now add the children
+  foreach ($children as $child_id => $child_item) {
+    if (! empty($child_item)) {
+      // show the children in a group together
+      $groupname = ($type == 'array') ? $item_id : '';
+      $child_item_id = panels_schema_add_item($G, $child_item, $child_id, $groupname);
+      $G->addEdge(array($item_id => $child_item_id), array('color' => 'blue'));
+    }
+  }
+  return $record;
+}
+
+/**
+ * Sorta hook to support styling properties when creating records
+ * 
+ * Collected here for better maintainence, and to allow overrides.
+ * Each element being rendered may have a 'type'. depending on the type, we will
+ * add the following GraphViz attributes.
+ * 
+ * Returns an array of attributes that we can use to style graph elements.
+ */
+function panels_schema_graphviz_styles() {
+  return array(
+    'panels_mini' => array(
+      #'shape' => 'ellipse',
+    ),
+    'views_panes' => array(
+      'fillcolor' => 'bisque1',
+      'style' => 'filled'
+    ),
+    // panels_region is a made-up container type, not directly reflecting a panels API object
+    'panels_region' => array(
+      'style' => 'dashed',
+    ),
+    'block' => array(
+      'shape' => 'box3d',
+    ),
+    'view' => array(
+      'shape' => 'Mrecord',
+    ),
+    'views' => array(
+      'fillcolor' => 'bisque2',
+      'style' => 'filled'
+    ),
+    'view-block' => array(
+      'shape' => 'box3d',
+      'style' => 'filled'
+    ),
+    'view-default' => array(
+      'style' => 'dashed'
+    ),
+    'view-panel_pane' => array(
+      'fillcolor' => 'bisque1',
+      'style' => 'filled'
+    ),
+    'view-page' => array(
+      'fillcolor' => 'bisque3',
+      'style' => 'filled'
+    ),
+  );
+}
+ 
