diff --git a/composer.json b/composer.json
index f8dedcb0ea9c467d1d513562f2fa8b2a8baeec06..93a20161e4858dd2a9c261a84dbbaf6a8153f997 100644
--- a/composer.json
+++ b/composer.json
@@ -1,19 +1,19 @@
 {
     "name": "drupal/migrate_visualize",
-    "description": "Visualize Drupal Migrations",
     "type": "drupal-module",
+    "description": "Visualize Drupal Migrations",
     "license": "GPL-2.0-or-later",
+    "require": {
+        "clue/graph": "^0.9.3",
+        "graphp/graphviz": "^0.2.2",
+        "drupal/migrate_plus": "^5.2 || ^6.0"
+    },
     "authors": [
         {
             "name": "Chris Burgess",
             "email": "chris.burgess@catalyst.net.nz"
         }
     ],
-    "require": {
-        "clue/graph": "^0.9.3",
-        "graphp/graphviz": "^0.2.2",
-        "drupal/migrate_plus": "^5.2 || ^6.0"
-    },
     "support": {
         "issues": "https://www.drupal.org/project/issues/migrate_visualize",
         "slack": "#migrate",
diff --git a/migrate_visualize.routing.yml b/migrate_visualize.routing.yml
index 0d5308b5a9e5fdb637fa1fdf4514fa24eb868433..b48ea6c0b73ed765212ce66adaea142cb6aebd31 100644
--- a/migrate_visualize.routing.yml
+++ b/migrate_visualize.routing.yml
@@ -21,3 +21,7 @@ migrate_visualize.visualize:
     _controller: '\Drupal\migrate_visualize\Controller\VisualizeController::build'
   requirements:
     _permission: 'access migrate_visualize'
+  options:
+    parameters:
+      migration:
+        type: entity:migration
diff --git a/migrate_visualize.theme.inc b/migrate_visualize.theme.inc
index 798b01e058accb66612de068758161d0422eb8ed..3e737dba8f57f6a6f7cccd3835837520dc2d62c8 100644
--- a/migrate_visualize.theme.inc
+++ b/migrate_visualize.theme.inc
@@ -26,6 +26,7 @@ function _migrate_visualize_graph_type_attributes() {
     'process:pipeline' => ['color' => 1, 'shape' => 'box'],
     'process:meta' => ['color' => 2, 'shape' => 'box'],
     'process:pipeline:step' => ['color' => 3, 'shape' => 'box'],
+    'process:pipeline' => ['color' => 4, 'shape' => 'box'],
     'plugin' => ['color' => 'cornflowerblue2', 'shape' => 'box'],
     'destination' => ['color' => 'darkgoldenrod3', 'shape' => 'box'],
   ];
@@ -43,19 +44,6 @@ function template_preprocess_migration_visualize_graphviz(array &$variables) {
   /** @var \Fhaculty\Graph\Graph $graph */
   $graph = $variables['graph'];
 
-  // Base edge colors on origin vertex type.
-  $edgeTypes = [
-    'source' => ['colorscheme' => 'bupu9'],
-    'source:field' => ['colorscheme' => 'bupu9'],
-    'process' => ['colorscheme' => 'bupu9'],
-    'process:field' => ['colorscheme' => 'bupu9'],
-    'process:pipeline:step' => ['colorscheme' => 'bupu9'],
-    'plugin' => ['colorscheme' => 'bupu9'],
-    'process:plugin' => ['colorscheme' => 'bupu9'],
-    'destination' => ['color' => 'goldenrod3'],
-    'destination:field' => ['color' => 'goldenrod3'],
-  ];
-
   $graph->setAttribute('graphviz.graph.charset', 'iso-8859-1');
   $graph->setAttribute('graphviz.graph.rankdir', 'LR');
 
@@ -151,5 +139,37 @@ function template_preprocess_migration_visualize_mermaid(array &$variables) {
         $vertex->setGroup($groups[$groupId]);
       }
     }
+
+    $vertexType = $vertex->getAttribute('migrate_visualize.type');
   }
+
+  // Base edge colors on origin vertex type.
+  $edgeTypes = [
+    'source' => ['colorscheme' => 'bupu9'],
+    'source:field' => ['colorscheme' => 'bupu9'],
+    'process' => ['colorscheme' => 'bupu9'],
+    'process:field' => ['colorscheme' => 'bupu9'],
+    'process:pipeline:step' => ['colorscheme' => 'bupu9'],
+    'plugin' => ['colorscheme' => 'bupu9'],
+    'process:plugin' => ['colorscheme' => 'bupu9'],
+    'destination' => ['color' => 'goldenrod3'],
+    'destination:field' => ['color' => 'goldenrod3'],
+  ];
+
+  /** @var \Fhaculty\Graph\Edge\Directed $edge */
+  foreach ($graph->getEdges() as $k => $edge) {
+    $edge->setAttribute('graphviz.colorscheme', 'oranges9');
+    $edge->setAttribute('graphviz.color', $k % 6 + 2);
+    $edge->setAttribute('graphviz.penwidth', '2');
+    if ($label = $edge->getAttribute('migrate_visualize.label')) {
+      $edge->setAttribute('graphviz.label', $label);
+    }
+    $edgeType = $edge->getVertexStart()->getAttribute('migrate_visualize.type');
+    if (isset($edgeTypes[$edgeType])) {
+      foreach ($edgeTypes[$edgeType] as $attributeKey => $attributeValue) {
+        $edge->setAttribute("graphviz.{$attributeKey}", $attributeValue);
+      }
+    }
+  }
+
 }
diff --git a/src/Controller/VisualizeController.php b/src/Controller/VisualizeController.php
index ac1622a0ff6aa0978e9cb9d6c31bd45b40fe6e65..f99e5206187f021e0b2525175a172c9481cce59d 100644
--- a/src/Controller/VisualizeController.php
+++ b/src/Controller/VisualizeController.php
@@ -4,8 +4,8 @@ namespace Drupal\migrate_visualize\Controller;
 
 use Drupal\Core\Controller\ControllerBase;
 use Drupal\migrate\Plugin\MigrationPluginManagerInterface;
+use Drupal\migrate_plus\Entity\Migration;
 use Drupal\migrate_visualize\MigrateGraph;
-use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -52,25 +52,15 @@ class VisualizeController extends ControllerBase {
 
   /**
    * Visualize the migration.
-   *
-   * @todo Can the parameter be upcast?
-   * @see https://www.drupal.org/docs/8/api/routing-system/parameters-in-routes/how-upcasting-parameters-works
    */
-  public function build(string $migration) {
+  public function build(Migration $migration) {
+    $migrationPlugin = $this->migrationPluginManager->createInstance($migration->id(), $migration->toArray());
+
     $build['switcher'] = $this->formBuilder()
       ->getForm('Drupal\migrate_visualize\Form\VisualizeMigrationSwitcherForm');
 
-    $migrationInstance = $this->migrationPluginManager->createInstance($migration);
-    if ($migration !== '' && $migrationInstance === FALSE) {
-      $this->messenger()
-        ->addError($this->t('Migration not found: @migration', [
-          '@migration' => $migration,
-        ]));
-      throw new NotFoundHttpException();
-    }
-
     try {
-      $this->migrationGraph->graph($migrationInstance);
+      $this->migrationGraph->graph($migrationPlugin);
     }
     catch (\Exception $exception) {
       $this->messenger()
@@ -83,7 +73,7 @@ class VisualizeController extends ControllerBase {
     }
 
     $build['visualize'] = [
-      '#migration' => $migrationInstance,
+      '#migration' => $migrationPlugin,
       '#weight' => 110,
     ];
 
diff --git a/src/Form/SettingsForm.php b/src/Form/SettingsForm.php
index 0b8cf793dfddc1ca57f27ec742e78525b83a5d4e..5091edfd56c1e52b3cef0527ec0fa4bea2782b7a 100644
--- a/src/Form/SettingsForm.php
+++ b/src/Form/SettingsForm.php
@@ -4,7 +4,6 @@ namespace Drupal\migrate_visualize\Form;
 
 use Drupal\Core\Form\ConfigFormBase;
 use Drupal\Core\Form\FormStateInterface;
-use Graphp\GraphViz\GraphViz;
 use Fhaculty\Graph\Graph;
 use Fhaculty\Graph\Exception\UnexpectedValueException;
 
@@ -48,11 +47,15 @@ class SettingsForm extends ConfigFormBase {
     ];
 
     try {
+      // Crude, but throws an exception.
+      if (!class_exists('Graphp\GraphViz\GraphViz')) {
+        throw new \Exception('Class not found: Graphp\GraphViz\GraphViz');
+      }
       // Test if GraphViz works in this environment.
-      $graphViz = new GraphViz();
+      $graphViz = new \Graphp\GraphViz\GraphViz();
       $graphViz->createImageHTML(new Graph());
     }
-    catch (UnexpectedValueException $exception) {
+    catch (\Exception $exception) {
       // GraphViz doesn't work in this environment.
       $form['display_mode']['graphviz']['#disabled'] = TRUE;
       $form['display_mode']['graphviz']['#description'] .= ' ' . $this->t('Unavailable - see <a href="@docs">documentation</a>.', [
diff --git a/src/Form/VisualizeMigrationSwitcherForm.php b/src/Form/VisualizeMigrationSwitcherForm.php
index 9fb6bc968e47b98f04df71a75b673572f0177284..0f5e0c79ddce13974d43b5716ff9545a51e76933 100644
--- a/src/Form/VisualizeMigrationSwitcherForm.php
+++ b/src/Form/VisualizeMigrationSwitcherForm.php
@@ -101,7 +101,7 @@ class VisualizeMigrationSwitcherForm extends FormBase {
     ];
 
     if ($current = $this->currentRouteMatch->getParameter('migration')) {
-      $form['migration']['#default_value'] = $current;
+      $form['migration']['#default_value'] = $current->id();
     }
 
     $form['actions']['#type'] = 'actions';
diff --git a/src/MigrateGraph.php b/src/MigrateGraph.php
index 911bd7abaa99d749ee46dac3438cf7a22d6649d4..a357aae5a6a0a2be72681932e394222b4d550ea0 100644
--- a/src/MigrateGraph.php
+++ b/src/MigrateGraph.php
@@ -81,12 +81,26 @@ class MigrateGraph {
     return $this->graph;
   }
 
+  /**
+   * Make graph IDs "safe" (for MermaidJS).
+   *
+   * @param $id
+   *
+   * @return array|string|string[]|null
+   */
+  public function safeId($id) {
+    return preg_replace('/[^a-zA-Z0-9_]/', '_', $id);
+  }
+
   /**
    * Bypass PHP protections on object properties for analysis.
    *
    * Getting at process plugin configs seems complicated, but I'm pretty sure
    * this ain't how it's supposed to happen.
    *
+   * Hmm ... can we use $migration->toArray(), if we have the migration config
+   * entity, instead of picking the migration plugin object apart?
+   *
    * @var object $object
    *   The object to inspect.
    * @var string $propertyName
@@ -136,7 +150,8 @@ class MigrateGraph {
     // Gather source plugin meta values.
     foreach (['plugin'] as $configKey) {
       if (isset($configuration[$configKey]) && !$graph->hasVertex("source:meta:{$configKey}")) {
-        $metaVertex = $graph->createVertex("source:meta:{$configKey}");
+        $metaVertexId = $this->safeId("source:meta:{$configKey}");
+        $metaVertex = $graph->createVertex($metaVertexId);
         $metaVertex->setAttribute('migrate_visualize.discovery', 'source');
         $metaVertex->setAttribute('migrate_visualize.type', 'source:meta');
         $metaVertex->setAttribute('migrate_visualize.label', $this->t("@key: @value", [
@@ -149,7 +164,7 @@ class MigrateGraph {
     switch ($configuration['plugin']) {
       case 'embedded_data':
         foreach (reset($configuration['data_rows']) as $key => $value) {
-          $fieldVertexId = "source:field:{$key}";
+          $fieldVertexId = $this->safeId("source:field:{$key}");
           if (!$graph->hasVertex($fieldVertexId)) {
             $fieldVertex = $graph->createVertex($fieldVertexId);
             $fieldVertex->setAttribute('migrate_visualize.discovery', 'source');
@@ -168,7 +183,7 @@ class MigrateGraph {
     // (eg JSON, XML, CSV sources).
     if (isset($configuration['fields'])) {
       foreach ($configuration['fields'] as $field) {
-        $fieldVertexId = "source:field:{$field['name']}";
+        $fieldVertexId = $this->safeId("source:field:{$field['name']}");
         if (!$graph->hasVertex($fieldVertexId)) {
           $fieldVertex = $graph->createVertex($fieldVertexId);
           $fieldVertex->setAttribute('migrate_visualize.discovery', 'source');
@@ -193,7 +208,8 @@ class MigrateGraph {
     // Gather destination plugin meta values.
     foreach (['plugin', 'default_bundle'] as $configKey) {
       if (isset($configuration[$configKey]) && !$graph->hasVertex("destination:meta:{$configKey}")) {
-        $metaVertex = $graph->createVertex("destination:meta:{$configKey}");
+        $metaVertexId = $this->safeId("destination:meta:{$configKey}");
+        $metaVertex = $graph->createVertex($metaVertexId);
         $metaVertex->setAttribute('migrate_visualize.discovery', 'destination');
         $metaVertex->setAttribute('migrate_visualize.type', 'destination:meta');
         $metaVertex->setAttribute('migrate_visualize.label', $this->t("@key: @value", [
@@ -214,7 +230,8 @@ class MigrateGraph {
         $entityType = explode(':', $configuration['plugin'])[1];
         $fields = $this->entityFieldManager->getFieldDefinitions($entityType, $configuration['default_bundle']);
         foreach ($fields as $fieldId => $field) {
-          $fieldVertex = $graph->createVertex("destination:field:{$fieldId}");
+          $fieldVertexId = $this->safeId("destination:field:{$fieldId}");
+          $fieldVertex = $graph->createVertex($fieldVertexId);
           $fieldVertex->setAttribute('migrate_visualize.discovery', 'destination');
           $fieldVertex->setAttribute('migrate_visualize.type', 'destination:field');
           $fieldVertex->setAttribute('migrate_visualize.label', $this->t("@fieldId: @value", [
@@ -236,7 +253,7 @@ class MigrateGraph {
     $graph = $this->getGraph();
     /** @var \Drupal\migrate\Plugin\MigrateProcessInterface[] $plugins */
     foreach ($pipeline as $destinationPropertyName => $plugins) {
-      $processVertexId = "process:field:{$destinationPropertyName}";
+      $processVertexId = $this->safeId("process:field:{$destinationPropertyName}");
       $processVertex = $graph->createVertex($processVertexId);
       $processVertex->setAttribute('migrate_visualize.type', 'process:pipeline');
       $processVertex->setAttribute('migrate_visualize.label', $this->t("@fieldId", [
@@ -248,7 +265,7 @@ class MigrateGraph {
         $this->graphProcessPlugin($destinationPropertyName, $plugin, $processVertex, $pipelineStepId, $plugins);
       }
 
-      $destinationVertexId = "destination:field:{$destinationPropertyName}";
+      $destinationVertexId = $this->safeid("destination:field:{$destinationPropertyName}");
 
       if ($graph->hasVertex($destinationVertexId)) {
         $destinationVertex = $graph->getVertex($destinationVertexId);
@@ -292,7 +309,7 @@ class MigrateGraph {
       return;
     }
 
-    $pluginVertexId = "process:field:{$destinationPropertyName}:{$pipelineStepId}";
+    $pluginVertexId = $this->safeId("process:field:{$destinationPropertyName}:{$pipelineStepId}");
     $pluginVertex = $graph->createVertex($pluginVertexId);
 
     $pluginVertex->setAttribute('migrate_visualize.type', 'process:pipeline:step');
@@ -313,7 +330,7 @@ class MigrateGraph {
         }
 
         foreach ($sources as $sourceField) {
-          $sourceVertexId = "source:field:{$sourceField}";
+          $sourceVertexId = $this->safeId("source:field:{$sourceField}");
           if ($graph->hasVertex($sourceVertexId)) {
             $sourceVertex = $graph->getVertex($sourceVertexId);
           }
@@ -340,7 +357,7 @@ class MigrateGraph {
     if (count($plugins) == ($pipelineStepId + 1)) {
       $stepPipelineEdge = $pluginVertex->createEdgeTo($destinationVertex);
 
-      if ($graph->hasVertex($sourceVertexId)) {
+      if (isset($sourceVertexId) && $graph->hasVertex($sourceVertexId)) {
         if (!$sourceVertex->hasEdgeTo($pluginVertex)) {
           $sourceStepEdge = $sourceVertex->createEdgeTo($pluginVertex);
         }
diff --git a/tests/src/Functional/VisualizeControllerTest.php b/tests/src/Functional/VisualizeControllerTest.php
index e42b494afa88fa6cdf00e863961528e01c9a91ad..1a5780fe15647905dc2971dbfe435910b81373e0 100644
--- a/tests/src/Functional/VisualizeControllerTest.php
+++ b/tests/src/Functional/VisualizeControllerTest.php
@@ -92,7 +92,7 @@ class VisualizeControllerTest extends BrowserTestBase {
   public static function visualizeControllerProvider(): array {
     $cases = [];
     $cases[] = ['fruit_terms', 200, 'Fruit Terms'];
-    $cases[] = ['does_not_exist', 404, 'Migration not found'];
+    $cases[] = ['does_not_exist', 404, 'Page not found'];
     /* $cases[] = ['broken', 200, 'Unable to graph migration']; */
     return $cases;
   }