diff --git a/package-lock.json b/package-lock.json
index 593966d57584eb4e481515b94be6321f62d65bf9..50f3a4fb0f31c6244117c019a2a970ed5184b143 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -20,7 +20,7 @@
         "@angular/router": "18.1.1",
         "@asymmetrik/ngx-leaflet": "^18.0.1",
         "@compodoc/compodoc": "^1.1.25",
-        "@ghsc/disagg-d3": "^0.9.0",
+        "@ghsc/disagg-d3": "^0.12.0",
         "@ghsc/nshmp-lib-ng": "^18.15.0",
         "@ghsc/nshmp-template": "^18.0.3",
         "@ghsc/nshmp-utils-ts": "^3.8.1",
@@ -4272,9 +4272,9 @@
       "license": "MIT"
     },
     "node_modules/@ghsc/disagg-d3": {
-      "version": "0.9.0",
-      "resolved": "https://code.usgs.gov/api/v4/projects/4335/packages/npm/@ghsc/disagg-d3/-/@ghsc/disagg-d3-0.9.0.tgz",
-      "integrity": "sha1-FFrDhBgysXUcubuws7o7yK1WrCQ=",
+      "version": "0.12.0",
+      "resolved": "https://code.usgs.gov/api/v4/projects/4335/packages/npm/@ghsc/disagg-d3/-/@ghsc/disagg-d3-0.12.0.tgz",
+      "integrity": "sha1-1CgSNTwwouZmtGiJnHTBbOd354k=",
       "dependencies": {
         "@ghsc/nshmp-utils-ts": "^3.0.0",
         "d3": "^7.6.1"
diff --git a/package.json b/package.json
index 43b7c9b6c141fda6982e3e4501ede4ca56594544..8141a044bc2d724ed1758d2f996d243fb2aa010f 100644
--- a/package.json
+++ b/package.json
@@ -44,7 +44,7 @@
     "@angular/router": "18.1.1",
     "@asymmetrik/ngx-leaflet": "^18.0.1",
     "@compodoc/compodoc": "^1.1.25",
-    "@ghsc/disagg-d3": "^0.9.0",
+    "@ghsc/disagg-d3": "^0.12.0",
     "@ghsc/nshmp-lib-ng": "^18.15.0",
     "@ghsc/nshmp-template": "^18.0.3",
     "@ghsc/nshmp-utils-ts": "^3.8.1",
diff --git a/projects/nshmp-apps/src/app/hazard/disagg/components/content/content.component.html b/projects/nshmp-apps/src/app/hazard/disagg/components/content/content.component.html
index 2c11841d1df9540ce059156a505c372e6152a6cc..58c70ef6fbce0aad9d77bf718c3f346c40a6fc92 100644
--- a/projects/nshmp-apps/src/app/hazard/disagg/components/content/content.component.html
+++ b/projects/nshmp-apps/src/app/hazard/disagg/components/content/content.component.html
@@ -1,4 +1,4 @@
-<div class="height-full overflow-auto">
+<div class="height-full overflow-auto report">
   <div class="grid-container-widescreen">
     <!-- Report title, show only on print -->
     <div class="print-content-only">
@@ -42,32 +42,54 @@
 
         <!-- Summary report -->
         <mat-expansion-panel
-          class="summary-report print-page-break"
+          class="summary-report print-full-page"
           [expanded]="disaggData()"
           [disabled]="disaggData() === null"
         >
           <mat-expansion-panel-header>
-            <mat-panel-title>Disaggregation Summary</mat-panel-title>
+            <mat-panel-title
+              >Disaggregation Summary:
+              {{ formGroup.getRawValue().disaggComponent }}</mat-panel-title
+            >
           </mat-expansion-panel-header>
 
-          <app-disagg-summary />
+          <app-disagg-summary [componentData]="componentData()" />
         </mat-expansion-panel>
 
         <!-- Contributions -->
         <mat-expansion-panel
-          class="contributions print-page-break"
-          [expanded]="componentData() !== null"
-          [disabled]="componentData() === null"
+          class="contributions print-full-page"
+          [expanded]="componentData()?.sources.length > 0"
+          [disabled]="componentData()?.sources.length === 0"
         >
           <mat-expansion-panel-header>
-            <mat-panel-title>Disaggregation Contributions</mat-panel-title>
+            <mat-panel-title
+              >Disaggregation Contributions:
+              {{ formGroup.getRawValue().disaggComponent }}</mat-panel-title
+            >
           </mat-expansion-panel-header>
 
-          <app-disagg-contributors />
+          <app-disagg-contributors [componentData]="componentData()" />
+        </mat-expansion-panel>
+
+        <!-- Data -->
+        <mat-expansion-panel
+          class="print-display-none"
+          [expanded]="componentData()?.data.length > 0"
+          [disabled]="componentData()?.data.length === 0"
+        >
+          <mat-expansion-panel-header>
+            <mat-panel-title
+              >Disaggregation Data:
+              {{ formGroup.getRawValue().disaggComponent }}</mat-panel-title
+            >
+          </mat-expansion-panel-header>
+
+          <app-disagg-data [componentData]="componentData()" />
         </mat-expansion-panel>
 
         <!-- App metadata -->
-        <mat-expansion-panel expanded class="print-page-break">
+        <mat-expansion-panel class="print-page-break print-full-page" expanded>
           <mat-expansion-panel-header>
             <mat-panel-title>Application Metadata</mat-panel-title>
           </mat-expansion-panel-header>
diff --git a/projects/nshmp-apps/src/app/hazard/disagg/components/content/content.component.scss b/projects/nshmp-apps/src/app/hazard/disagg/components/content/content.component.scss
index 3094eb336b61d44fa8892d78b1b5160d064110e3..50361509a44fc2fdc39cf9b3d1e9aae8aa45c5e2 100644
--- a/projects/nshmp-apps/src/app/hazard/disagg/components/content/content.component.scss
+++ b/projects/nshmp-apps/src/app/hazard/disagg/components/content/content.component.scss
@@ -1,6 +1,7 @@
 @media print {
   mat-accordion {
     mat-expansion-panel {
+      overflow: visible;
       box-shadow: none !important;
     }
   }
diff --git a/projects/nshmp-apps/src/app/hazard/disagg/components/content/content.component.ts b/projects/nshmp-apps/src/app/hazard/disagg/components/content/content.component.ts
index 1e50c4085538f6241a74522fde885abd5fe23ec2..c7929d96387e4700b7df35b7f62765f98ba085dd 100644
--- a/projects/nshmp-apps/src/app/hazard/disagg/components/content/content.component.ts
+++ b/projects/nshmp-apps/src/app/hazard/disagg/components/content/content.component.ts
@@ -1,15 +1,12 @@
+import {AsyncPipe} from '@angular/common';
 import {Component, computed} from '@angular/core';
 import {MatDivider} from '@angular/material/divider';
-import {
-  MatAccordion,
-  MatExpansionPanel,
-  MatExpansionPanelHeader,
-  MatExpansionPanelTitle,
-} from '@angular/material/expansion';
+import {MatExpansionModule} from '@angular/material/expansion';
 import {NshmpLibNgAppMetadataComponent} from '@ghsc/nshmp-lib-ng/nshmp';
 
 import {AppService} from '../../services/app.service';
 import {DisaggContributorsComponent} from '../disagg-contributors/disagg-contributors.component';
+import {DisaggDataComponent} from '../disagg-data/disagg-data.component';
 import {DisaggSummaryComponent} from '../disagg-summary/disagg-summary.component';
 import {GeoDisaggComponent} from '../geo-disagg/geo-disagg.component';
 import {ParameterSummaryComponent} from '../parameter-summary/parameter-summary.component';
@@ -24,17 +21,16 @@ import {PlotsComponent} from '../plots/plots.component';
  */
 @Component({
   imports: [
-    MatAccordion,
-    MatExpansionPanel,
-    MatExpansionPanelHeader,
-    MatExpansionPanelTitle,
+    MatExpansionModule,
     PlotsComponent,
     MatDivider,
     GeoDisaggComponent,
     ParameterSummaryComponent,
+    NshmpLibNgAppMetadataComponent,
     DisaggSummaryComponent,
     DisaggContributorsComponent,
-    NshmpLibNgAppMetadataComponent,
+    DisaggDataComponent,
+    AsyncPipe,
   ],
   selector: 'app-content',
   standalone: true,
@@ -42,6 +38,13 @@ import {PlotsComponent} from '../plots/plots.component';
   templateUrl: './content.component.html',
 })
 export class ContentComponent {
+  componentData = this.service.componentData;
+  /** Disaggregation data */
+  disaggData = this.service.disaggData;
+  formGroup = this.service.formGroup;
+
+  hasData = computed(() => this.service.serviceResponse() !== null);
+
   /** Has geo disagg layers */
   hasLayers = computed(() => {
     const serviceResponse = this.service.serviceResponse();
@@ -61,12 +64,6 @@ export class ContentComponent {
     return layers !== undefined;
   });
 
-  /** Disaggregation component data */
-  componentData = this.service.componentData;
-
-  /** Disaggregation data */
-  disaggData = this.service.disaggData;
-
   /** Repo metadata */
   repositories = computed(() => this.service.usage()?.metadata.repositories);
 
diff --git a/projects/nshmp-apps/src/app/hazard/disagg/components/disagg-contributors/disagg-contributors.component.html b/projects/nshmp-apps/src/app/hazard/disagg/components/disagg-contributors/disagg-contributors.component.html
index 70779f8ec4b8d2c87d22fed5d6366d1c4b30a1e8..e13aeab01da343b42d1456b2908aff9f367943ce 100644
--- a/projects/nshmp-apps/src/app/hazard/disagg/components/disagg-contributors/disagg-contributors.component.html
+++ b/projects/nshmp-apps/src/app/hazard/disagg/components/disagg-contributors/disagg-contributors.component.html
@@ -1,70 +1,74 @@
 <!-- Disagg contributions -->
-@if (componentData$ | async; as componentData) {
-  <div class="disagg-contributions">
-    @if (serviceResponse()) {
-      <div class="print-display-none">
-        <button
-          mat-raised-button
-          color="primary"
-          (click)="
-            service.saveContributions(componentData(), form.getRawValue())
-          "
-        >
-          Export as CSV
-        </button>
-      </div>
+@if (componentData) {
+  @if (componentData?.sources?.length > 0) {
+    <div class="disagg-contributions">
+      @if (serviceResponse()) {
+        @if (showExportButton) {
+          <div class="print-display-none">
+            <button
+              mat-raised-button
+              color="primary"
+              (click)="
+                service.saveContributions(componentData, form.getRawValue())
+              "
+            >
+              Export as CSV
+            </button>
+          </div>
 
-      <mat-divider />
+          <mat-divider />
+        }
 
-      <div class="horizontal-scrolling">
-        <div>
-          <table #table class="contributing-sources grid-col-12">
-            <thead>
-              <tr>
-                <th nowrap>
-                  Source Set
-                  <mat-icon
-                    class="down-arrow"
-                    aria-label="Down arrow icon"
-                    fontIcon="subdirectory_arrow_right"
-                  />
-                  Source
-                </th>
-                <th nowrap>Type</th>
-                <th nowrap title="Distance (km)">r</th>
-                <th nowrap title="Magnitude">m</th>
-                <th nowrap title="Epsilon (mean values)">ε<sub>0</sub></th>
-                <th nowrap title="Longitude">lon</th>
-                <th nowrap title="Latitude">lat</th>
-                <th nowrap title="Azimuth">az</th>
-                <th nowrap title="Percent contributed">%</th>
-              </tr>
-            </thead>
-
-            <tbody>
-              @for (data of componentData()?.sources; track data) {
-                <tr [ngClass]="{'contributor-set': data.type === 'SET'}">
-                  @if (data.type === 'SET') {
-                    <td nowrap>{{ data?.name }}</td>
-                    <td nowrap>{{ data?.source }}</td>
-                    <td colspan="6"></td>
-                  } @else {
-                    <td nowrap class="indent-name">{{ data?.name }}</td>
-                    <td></td>
-                    <td nowrap>{{ data?.r | number: '1.2-2' }}</td>
-                    <td nowrap>{{ data?.m | number: '1.2-2' }}</td>
-                    <td nowrap>{{ dataEpsilon(data) | number: '1.2-2' }}</td>
-                    <td nowrap>{{ data?.longitude | formatLongitude }}</td>
-                    <td nowrap>{{ data?.latitude | formatLatitude }}</td>
-                    <td nowrap>{{ data?.azimuth | number: '1.2-2' }}</td>
-                  }
-                  <td nowrap>{{ data?.contribution }}</td>
+        <div class="horizontal-scrolling">
+          <div>
+            <table #table class="contributing-sources grid-col-12">
+              <thead>
+                <tr>
+                  <th nowrap>
+                    Source Set
+                    <mat-icon
+                      class="down-arrow"
+                      aria-label="Down arrow icon"
+                      fontIcon="subdirectory_arrow_right"
+                    />
+                    Source
+                  </th>
+                  <th nowrap>Type</th>
+                  <th nowrap title="Distance (km)">r</th>
+                  <th nowrap title="Magnitude">m</th>
+                  <th nowrap title="Epsilon (mean values)">ε<sub>0</sub></th>
+                  <th nowrap title="Longitude">lon</th>
+                  <th nowrap title="Latitude">lat</th>
+                  <th nowrap title="Azimuth">az</th>
+                  <th nowrap title="Percent contributed">%</th>
                 </tr>
-              }
-            </tbody>
-          </table>
+              </thead>
+
+              <tbody>
+                @for (data of componentData?.sources; track data) {
+                  <tr [ngClass]="{'contributor-set': data.type === 'SET'}">
+                    @if (data.type === 'SET') {
+                      <td nowrap>{{ data?.name }}</td>
+                      <td nowrap>{{ data?.source }}</td>
+                      <td colspan="6"></td>
+                    } @else {
+                      <td nowrap class="indent-name">{{ data?.name }}</td>
+                      <td></td>
+                      <td nowrap>{{ data?.r | number: '1.2-2' }}</td>
+                      <td nowrap>{{ data?.m | number: '1.2-2' }}</td>
+                      <td nowrap>{{ dataEpsilon(data) | number: '1.2-2' }}</td>
+                      <td nowrap>{{ data?.longitude | formatLongitude }}</td>
+                      <td nowrap>{{ data?.latitude | formatLatitude }}</td>
+                      <td nowrap>{{ data?.azimuth | number: '1.2-2' }}</td>
+                    }
+                    <td nowrap>{{ data?.contribution }}</td>
+                  </tr>
+                }
+              </tbody>
+            </table>
+          </div>
         </div>
-      </div>
-    }
-  </div>
+      }
+    </div>
+  }
 }
diff --git a/projects/nshmp-apps/src/app/hazard/disagg/components/disagg-contributors/disagg-contributors.component.ts b/projects/nshmp-apps/src/app/hazard/disagg/components/disagg-contributors/disagg-contributors.component.ts
index 3b84a7e865e32281bc750954bfc6f2bb75984e5d..e0fa11c3c2350d80fac1b7c8f438a3d3ed90249c 100644
--- a/projects/nshmp-apps/src/app/hazard/disagg/components/disagg-contributors/disagg-contributors.component.ts
+++ b/projects/nshmp-apps/src/app/hazard/disagg/components/disagg-contributors/disagg-contributors.component.ts
@@ -1,5 +1,5 @@
 import {AsyncPipe, DecimalPipe, NgClass} from '@angular/common';
-import {Component} from '@angular/core';
+import {Component, Input} from '@angular/core';
 import {MatButton} from '@angular/material/button';
 import {MatDivider} from '@angular/material/divider';
 import {MatIcon} from '@angular/material/icon';
@@ -7,8 +7,10 @@ import {
   FormatLatitudePipe,
   FormatLongitudePipe,
 } from '@ghsc/nshmp-lib-ng/hazard';
-import {DisaggSource} from '@ghsc/nshmp-utils-ts/libs/nshmp-haz/www/disagg-service';
-import {map} from 'rxjs';
+import {
+  DisaggComponentData,
+  DisaggSource,
+} from '@ghsc/nshmp-utils-ts/libs/nshmp-haz/www/disagg-service';
 
 import {AppService} from '../../services/app.service';
 
@@ -32,11 +34,12 @@ import {AppService} from '../../services/app.service';
   templateUrl: './disagg-contributors.component.html',
 })
 export class DisaggContributorsComponent {
-  /** Disaggregation data state */
-  componentData$ =
-    this.service.formGroup.controls.disaggComponent.valueChanges.pipe(
-      map(() => this.service.componentData)
-    );
+  /** Disaggregation component data state */
+  @Input({required: true})
+  componentData: DisaggComponentData;
+
+  @Input()
+  showExportButton = true;
 
   /** Form field state */
   form = this.service.formGroup;
diff --git a/projects/nshmp-apps/src/app/hazard/disagg/components/disagg-data/disagg-data.component.html b/projects/nshmp-apps/src/app/hazard/disagg/components/disagg-data/disagg-data.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..e4e80db7e5bc96ff66d803bd845ed52d921a145f
--- /dev/null
+++ b/projects/nshmp-apps/src/app/hazard/disagg/components/disagg-data/disagg-data.component.html
@@ -0,0 +1,48 @@
+@if (componentData) {
+  @if (componentData.data.length > 0) {
+    @if (showExportButton) {
+      <!-- Export button -->
+      <div class="print-display-none">
+        <button
+          class="export-button"
+          mat-raised-button
+          color="primary"
+          [disabled]="componentData.data === null"
+          (click)="service.saveComponentData()"
+        >
+          Export Data as CSV
+        </button>
+      </div>
+
+      <mat-divider />
+    }
+
+    <div class="horizontal-scrolling disagg-data">
+      <div>
+        <table class="grid-col-12">
+          <thead>
+            <tr>
+              <th nowrap>{{ metadata().rlabel }}</th>
+              <th nowrap>{{ metadata().mlabel }}</th>
+              @for (key of epsilonKeys(); track key) {
+                <th nowrap>{{ key }}</th>
+              }
+            </tr>
+          </thead>
+          <tbody>
+            @for (data of componentData.data; track data) {
+              <tr>
+                <td noWrap>{{ data.r }}</td>
+                <td nowrap>{{ data.m }}</td>
+
+                @for (binData of toBinData(data); track $index) {
+                  <td nowrap>{{ binData }}</td>
+                }
+              </tr>
+            }
+          </tbody>
+        </table>
+      </div>
+    </div>
+  }
+}
diff --git a/projects/nshmp-apps/src/app/hazard/disagg/components/disagg-data/disagg-data.component.scss b/projects/nshmp-apps/src/app/hazard/disagg/components/disagg-data/disagg-data.component.scss
new file mode 100644
index 0000000000000000000000000000000000000000..f42f51ca2b2b9ab2e7bb925d75787fb5abff4dd7
--- /dev/null
+++ b/projects/nshmp-apps/src/app/hazard/disagg/components/disagg-data/disagg-data.component.scss
@@ -0,0 +1,34 @@
+table {
+  th:first-child,
+  td:first-child {
+    text-align: left;
+  }
+
+  th,
+  td {
+    background: none;
+    border: none;
+    text-align: center;
+  }
+
+  th {
+    background-color: inherit;
+    border-bottom: 3px solid #ddd;
+    font-size: clamp(12px, 8px + 1vw, 16px);
+    padding: 1em;
+  }
+
+  td {
+    font-size: clamp(8px, 4px + 1vw, 14px);
+    padding: 0 clamp(0.25em, 0.1em + 1vw, 1em);
+  }
+
+  tr {
+    line-height: clamp(0.2em, 0.1em + 1.5vw, 1.5em);
+  }
+}
+
+.disagg-data {
+  max-height: 1000px;
+  overflow: scroll;
+}
diff --git a/projects/nshmp-apps/src/app/hazard/disagg/components/disagg-data/disagg-data.component.spec.ts b/projects/nshmp-apps/src/app/hazard/disagg/components/disagg-data/disagg-data.component.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d5c0d7072f5e243725fd3b5f3547df9ef7874ed9
--- /dev/null
+++ b/projects/nshmp-apps/src/app/hazard/disagg/components/disagg-data/disagg-data.component.spec.ts
@@ -0,0 +1,30 @@
+import {provideHttpClient} from '@angular/common/http';
+import {ComponentFixture, TestBed} from '@angular/core/testing';
+import {provideNoopAnimations} from '@angular/platform-browser/animations';
+import {provideRouter} from '@angular/router';
+
+import {DisaggDataComponent} from './disagg-data.component';
+
+describe('DisaggDataComponent', () => {
+  let component: DisaggDataComponent;
+  let fixture: ComponentFixture<DisaggDataComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [DisaggDataComponent],
+      providers: [
+        provideHttpClient(),
+        provideNoopAnimations(),
+        provideRouter([]),
+      ],
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(DisaggDataComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/projects/nshmp-apps/src/app/hazard/disagg/components/disagg-data/disagg-data.component.ts b/projects/nshmp-apps/src/app/hazard/disagg/components/disagg-data/disagg-data.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..092e0d9a1a4adf9915de235087700e7adb8495e5
--- /dev/null
+++ b/projects/nshmp-apps/src/app/hazard/disagg/components/disagg-data/disagg-data.component.ts
@@ -0,0 +1,50 @@
+import {AsyncPipe} from '@angular/common';
+import {Component, computed, Input} from '@angular/core';
+import {MatButton} from '@angular/material/button';
+import {MatDivider} from '@angular/material/divider';
+import {
+  DisaggComponentData,
+  DisaggData,
+} from '@ghsc/nshmp-utils-ts/libs/nshmp-haz/www/disagg-service';
+
+import {AppService} from '../../services/app.service';
+
+@Component({
+  imports: [AsyncPipe, MatDivider, MatButton],
+  selector: 'app-disagg-data',
+  standalone: true,
+  styleUrl: './disagg-data.component.scss',
+  templateUrl: './disagg-data.component.html',
+})
+export class DisaggDataComponent {
+  /** Disaggregation component data state */
+  @Input({required: true})
+  componentData: DisaggComponentData;
+
+  @Input()
+  showExportButton = true;
+
+  metadata = computed(() => this.service.serviceResponse().response.metadata);
+
+  constructor(public service: AppService) {}
+
+  toBinData(data: DisaggData): string[] {
+    const bins = this.service.serviceResponse().response.metadata.εbins;
+
+    const binData = bins.map(
+      bin => data.εdata.find(data => data.εbin === bin.id)?.value ?? 0
+    );
+
+    const total = binData.reduce((a, b) => a + b, 0);
+
+    return [total, ...binData].map(num => num.toExponential(2));
+  }
+
+  epsilonKeys(): string[] {
+    const keys = this.componentData.summary.find(
+      data => data.name.toLowerCase() === 'epsilon keys'
+    );
+
+    return ['ALL_ε', ...keys.data.map(data => `${data.name}=${data.value}`)];
+  }
+}
diff --git a/projects/nshmp-apps/src/app/hazard/disagg/components/disagg-summary/disagg-summary.component.html b/projects/nshmp-apps/src/app/hazard/disagg/components/disagg-summary/disagg-summary.component.html
index 18d9b7a89c2a4ec5917ad7002e1926d78c68889f..2ed8ee819af7907f514fa42f918c0b6f6a5d346a 100644
--- a/projects/nshmp-apps/src/app/hazard/disagg/components/disagg-summary/disagg-summary.component.html
+++ b/projects/nshmp-apps/src/app/hazard/disagg/components/disagg-summary/disagg-summary.component.html
@@ -1,21 +1,23 @@
-@if (componentData$ | async; as componentData) {
+@if (componentData) {
   <div class="disagg-summaries">
     <!-- Summaries -->
     <div class="summary-group">
-      <div class="print-display-none">
-        <button
-          mat-raised-button
-          color="primary"
-          (click)="service.saveSummary(componentData(), form.getRawValue())"
-        >
-          Export as Text
-        </button>
-      </div>
+      @if (showExportButton) {
+        <div class="print-display-none">
+          <button
+            mat-raised-button
+            color="primary"
+            (click)="service.saveSummary(componentData, form.getRawValue())"
+          >
+            Export as Text
+          </button>
+        </div>
 
-      <mat-divider />
+        <mat-divider />
+      }
 
       <div class="grid-row">
-        @for (summary of componentData()?.summary; track summary) {
+        @for (summary of componentData?.summary; track summary) {
           <div
             class="grid-col-12 mobile:grid-col-6 desktop-lg:grid-col-4 widescreen:grid-col-3 summary-values print-flex-basis-half"
           >
diff --git a/projects/nshmp-apps/src/app/hazard/disagg/components/disagg-summary/disagg-summary.component.ts b/projects/nshmp-apps/src/app/hazard/disagg/components/disagg-summary/disagg-summary.component.ts
index 78a0fb12ae721311ed99d38883a56bce5ecd5f85..95dc424623c5587f06f8e29c285148f28dc90742 100644
--- a/projects/nshmp-apps/src/app/hazard/disagg/components/disagg-summary/disagg-summary.component.ts
+++ b/projects/nshmp-apps/src/app/hazard/disagg/components/disagg-summary/disagg-summary.component.ts
@@ -1,9 +1,9 @@
 import {AsyncPipe} from '@angular/common';
-import {Component} from '@angular/core';
+import {Component, Input} from '@angular/core';
 import {MatButton} from '@angular/material/button';
 import {MatDivider} from '@angular/material/divider';
 import {MatList, MatListItem} from '@angular/material/list';
-import {map} from 'rxjs';
+import {DisaggComponentData} from '@ghsc/nshmp-utils-ts/libs/nshmp-haz/www/disagg-service';
 
 import {AppService} from '../../services/app.service';
 
@@ -19,10 +19,11 @@ import {AppService} from '../../services/app.service';
 })
 export class DisaggSummaryComponent {
   /** Disaggregation component data state */
-  componentData$ =
-    this.service.formGroup.controls.disaggComponent.valueChanges.pipe(
-      map(() => this.service.componentData)
-    );
+  @Input({required: true})
+  componentData: DisaggComponentData;
+
+  @Input()
+  showExportButton = true;
 
   /** Form field state */
   form = this.service.formGroup;
diff --git a/projects/nshmp-apps/src/app/hazard/disagg/components/plots/plots.component.html b/projects/nshmp-apps/src/app/hazard/disagg/components/plots/plots.component.html
index e0016b796e15432eab0b5ed16e7ff990860595c8..edd15e955156f04b1603574a53b9597484189c97 100644
--- a/projects/nshmp-apps/src/app/hazard/disagg/components/plots/plots.component.html
+++ b/projects/nshmp-apps/src/app/hazard/disagg/components/plots/plots.component.html
@@ -7,20 +7,20 @@
       [disabled]="disaggData() === null"
       (click)="exportReport()"
     >
-      Print Report
+      Print Report (PDF)
     </button>
   </div>
 
-  <!-- Export button -->
+  <!-- Export summary button -->
   <div>
     <button
       class="export-button"
       mat-raised-button
       color="primary"
       [disabled]="disaggData() === null"
-      (click)="service.saveComponentData()"
+      (click)="service.saveSummaryReport()"
     >
-      Export Data as CSV
+      Save Report (TXT)
     </button>
   </div>
 </div>
@@ -49,6 +49,7 @@
 
 <!-- Disagg plot -->
 <nshmp-lib-ng-hazard-disagg-plot
+  #disaggPlot
   [inputs]="{
     component: form.value.disaggComponent,
     imt: form.value.imt,
diff --git a/projects/nshmp-apps/src/app/hazard/disagg/components/plots/plots.component.ts b/projects/nshmp-apps/src/app/hazard/disagg/components/plots/plots.component.ts
index 9e5b92f31000b1232e92a6aa643dabbfffa04a4d..a990f887b96f452bad4adebc02ce4e06fc613f9c 100644
--- a/projects/nshmp-apps/src/app/hazard/disagg/components/plots/plots.component.ts
+++ b/projects/nshmp-apps/src/app/hazard/disagg/components/plots/plots.component.ts
@@ -1,11 +1,12 @@
 import {AsyncPipe} from '@angular/common';
 import {Component} from '@angular/core';
-import {ReactiveFormsModule} from '@angular/forms';
+import {FormsModule, ReactiveFormsModule} from '@angular/forms';
 import {MatButton} from '@angular/material/button';
 import {MatOption} from '@angular/material/core';
 import {MatDivider} from '@angular/material/divider';
 import {MatFormField, MatLabel} from '@angular/material/form-field';
 import {MatSelect} from '@angular/material/select';
+import {MatSliderModule} from '@angular/material/slider';
 import {NshmpLibNgHazardDisaggPlotComponent} from '@ghsc/nshmp-lib-ng/hazard';
 import {NshmpTemplateService} from '@ghsc/nshmp-template';
 
@@ -25,6 +26,8 @@ import {AppService} from '../../services/app.service';
     MatOption,
     AsyncPipe,
     ReactiveFormsModule,
+    MatSliderModule,
+    FormsModule,
   ],
   selector: 'app-plots',
   standalone: true,
diff --git a/projects/nshmp-apps/src/app/hazard/disagg/models/state.model.ts b/projects/nshmp-apps/src/app/hazard/disagg/models/state.model.ts
index 00a8de0fb470611f5e477747cde2e81f0a3180fd..c25958222a71f9796effafa1df9877d6a592e608 100644
--- a/projects/nshmp-apps/src/app/hazard/disagg/models/state.model.ts
+++ b/projects/nshmp-apps/src/app/hazard/disagg/models/state.model.ts
@@ -2,7 +2,9 @@ import {ResponseSpectra} from '@ghsc/nshmp-lib-ng/hazard';
 import {ServiceCallInfo} from '@ghsc/nshmp-lib-ng/nshmp';
 import {NshmpPlot} from '@ghsc/nshmp-lib-ng/plot';
 import {
+  DisaggComponentData,
   DisaggResponse,
+  DisaggResponseDataValues,
   DisaggUsage,
 } from '@ghsc/nshmp-utils-ts/libs/nshmp-haz/www/disagg-service';
 import {NshmMetadata} from '@ghsc/nshmp-utils-ts/libs/nshmp-haz/www/nshm-service';
@@ -14,6 +16,10 @@ import {Parameter} from '@ghsc/nshmp-utils-ts/libs/nshmp-ws-utils/metadata';
 export interface AppState {
   /** Available NSHMs */
   availableModels: Parameter[];
+  /** Current component data */
+  componentData: DisaggComponentData;
+  /** Current disagg data */
+  disaggData: DisaggResponseDataValues;
   /** Fault sections */
   faults: GeoJSON.FeatureCollection;
   /** NSHM service metadata */
diff --git a/projects/nshmp-apps/src/app/hazard/disagg/services/app.service.ts b/projects/nshmp-apps/src/app/hazard/disagg/services/app.service.ts
index a65a692aa720d9963661cadb5b95f3d164902873..cb0cfdb8e41cc57fe4fd989232d9a7b3e9936ad9 100644
--- a/projects/nshmp-apps/src/app/hazard/disagg/services/app.service.ts
+++ b/projects/nshmp-apps/src/app/hazard/disagg/services/app.service.ts
@@ -1,15 +1,26 @@
 import {HttpClient} from '@angular/common/http';
-import {computed, Injectable, Signal, signal} from '@angular/core';
+import {
+  computed,
+  Inject,
+  Injectable,
+  LOCALE_ID,
+  Signal,
+  signal,
+} from '@angular/core';
 import {AbstractControl, FormBuilder, Validators} from '@angular/forms';
 import {ActivatedRoute, Router} from '@angular/router';
 import {
   DisaggControlForm,
   DisaggTarget,
+  FormatLatitudePipe,
+  FormatLongitudePipe,
   HazardService,
   hazardUtils,
+  ReturnPeriodPipe,
 } from '@ghsc/nshmp-lib-ng/hazard';
 import {
   NshmpService,
+  nshmpUtils,
   RETURN_PERIOD_BOUNDS,
   ReturnPeriod,
   ServiceCallInfo,
@@ -70,11 +81,16 @@ export class AppService
     private hazardService: HazardService,
     private route: ActivatedRoute,
     private router: Router,
-    private http: HttpClient
+    private http: HttpClient,
+    @Inject(LOCALE_ID) private localId: string
   ) {
     super();
     this.addValidators();
     this.formGroup.controls.disaggComponent.disable();
+
+    this.formGroup.controls.disaggComponent.valueChanges.subscribe(() =>
+      this.updateComponentData()
+    );
   }
 
   /**
@@ -88,38 +104,14 @@ export class AppService
    * Returns the disagg response values observable.
    */
   get componentData(): Signal<DisaggComponentData> {
-    return computed(() => {
-      const {serviceResponse} = this.state();
-
-      if (serviceResponse === null) {
-        return null;
-      }
-
-      return [...serviceResponse.response.disaggs]
-        .pop()
-        .data.find(
-          disagg =>
-            disagg.component === this.formGroup.getRawValue().disaggComponent
-        );
-    });
+    return computed(() => this.state().componentData);
   }
 
   /**
    * Returns the disaggregation data observable.
    */
   get disaggData(): Signal<DisaggResponseDataValues> {
-    return computed(() => {
-      const {serviceResponse} = this.state();
-
-      if (serviceResponse === null) {
-        return null;
-      }
-
-      return serviceResponse.response.disaggs.find(
-        disagg =>
-          disagg.imt.value === this.formGroup.getRawValue().imt.toString()
-      );
-    });
+    return computed(() => this.state().disaggData);
   }
 
   get faults(): Signal<GeoJSON.FeatureCollection> {
@@ -237,6 +229,9 @@ export class AppService
           serviceResponse,
         });
 
+        this.updateComponentData();
+        this.updateDisaggData();
+
         this.formGroup.controls.disaggComponent.enable();
       });
   }
@@ -301,6 +296,8 @@ export class AppService
 
     return {
       availableModels: [],
+      componentData: null,
+      disaggData: null,
       faults: null,
       nshmServices: [],
       plots: null,
@@ -367,6 +364,17 @@ export class AppService
     this.nshmpService.saveAs(blob, `disagg-${data.component}.csv`);
   }
 
+  saveComponentSummaryReport(): void {
+    const blob = new Blob([this.summaryReportText(this.componentData())], {
+      type: 'text/csv;charset=utf-8;',
+    });
+
+    this.nshmpService.saveAs(
+      blob,
+      `disagg-summary-${this.componentData().component}.txt`
+    );
+  }
+
   /**
    * Save the contributions.
    *
@@ -396,6 +404,18 @@ export class AppService
     this.nshmpService.saveAs(blob, `disagg-summary-${data.component}.txt`);
   }
 
+  saveSummaryReport(): void {
+    const summaries = this.disaggData().data.map((data, index) =>
+      this.summaryReportText(data, index === 0)
+    );
+
+    const blob = new Blob(summaries, {
+      type: 'text/csv;charset=utf-8',
+    });
+
+    this.nshmpService.saveAs(blob, 'dissag-summary.txt');
+  }
+
   /**
    * Set the location form fields.
    *
@@ -408,6 +428,19 @@ export class AppService
     });
   }
 
+  updateComponentData(): void {
+    this.updateDisaggData();
+
+    const componentData = this.disaggData()?.data.find(
+      disagg =>
+        disagg.component === this.formGroup.getRawValue().disaggComponent
+    );
+
+    this.updateState({
+      componentData,
+    });
+  }
+
   updateState(state: Partial<AppState>): void {
     const updatedState = {
       ...this.state(),
@@ -446,20 +479,39 @@ export class AppService
    */
   private componentDataToCSV(
     componentData: DisaggComponentData,
-    formValues: DisaggControlForm
+    formValues: DisaggControlForm,
+    showMetadata = true
   ): string {
+    const metadata = this.serviceResponse().response.metadata;
+    const bins = metadata.εbins;
+    const keys = componentData.summary.find(
+      data => data.name.toLowerCase() === 'epsilon keys'
+    );
+
+    const headers = [
+      'Distance (km)',
+      'Magnitude (Mw)',
+      'ε total',
+      ...keys.data.map(data => data.name),
+    ];
+
     const csv = componentData.data.map(data => {
-      const εbin = data.εdata.map(εdata => εdata.εbin).join(', ');
-      const values = data.εdata.map(εdata => εdata.value).join(', ');
-      return (
-        'r, m, rBar, mBar, εBar\n' +
-        `${data.r}, ${data.m}, ${data.rBar}, ${data.mBar}, ${data.εBar}\n\n` +
-        `εbin, ${εbin}\n` +
-        `Value, ${values}\n`
+      const εvalues = bins.map(
+        bin => data.εdata.find(data => data.εbin === bin.id)?.value ?? 0.0
       );
+      const sum = εvalues.reduce((a, b) => a + b, 0);
+      const values = [sum, ...εvalues]
+        .map(num => num.toExponential(3))
+        .join(', ');
+
+      return `${data.r}, ${data.m}, ${values}`;
     });
 
-    return `${this.metadataCSV(formValues)}\n\n\n` + csv.join('\n\n');
+    const table = `${headers.join(', ')}\n${csv.join('\n')}`;
+
+    return showMetadata
+      ? `${this.metadataCSV(formValues)}\n\n\n` + table
+      : table;
   }
 
   /**
@@ -470,10 +522,13 @@ export class AppService
    */
   private contributorsToCSV(
     sources: DisaggSource[],
-    formValues: DisaggControlForm
+    formValues: DisaggControlForm,
+    showMetadata = true
   ): string {
+    const label = 'Disaggregation Contributions';
+
     if (sources === null || sources.length === 0) {
-      throw new Error('No contributing sources');
+      return `${label}: N/A\n\n`;
     }
     const headers = 'Source Set ↳ Source, Type, r, m, ε0, lon, lat, az, %';
 
@@ -488,13 +543,12 @@ export class AppService
       })
       .map(source => source.replace(/null/gi, ''))
       .join('');
-    return (
-      '' +
-      `${this.metadataCSV(formValues)}\n\n` +
-      'Disaggregation Contributions\n' +
-      `${headers}\n` +
-      `${csv}`
-    );
+
+    const table = `${label}\n${headers}\n ${csv}\n\n`;
+
+    return showMetadata
+      ? `${this.metadataCSV(formValues)}\n\n ${table}`
+      : table;
   }
 
   private initialFormSet(): void {
@@ -570,6 +624,28 @@ export class AppService
     );
   }
 
+  private metadataText(formValues: DisaggControlForm): string {
+    const usage = this.usage().response;
+
+    const imt = usage.model.imts.find(
+      imt => imt.value === formValues.imt.toString()
+    )?.display;
+
+    const vs30 =
+      formValues.siteClass === nshmpUtils.selectPlaceHolder().value
+        ? `${formValues.vs30} m/s`
+        : `${formValues.vs30} m/s (${formValues.siteClass})`;
+
+    return (
+      `Model: ${usage.model.name}\n` +
+      `Longitude: ${new FormatLongitudePipe(this.localId).transform(formValues.longitude)} \n` +
+      `Latitude: ${new FormatLatitudePipe(this.localId).transform(formValues.latitude)} \n` +
+      `IMT: ${imt} \n` +
+      `Return Period: ${new ReturnPeriodPipe().transform(formValues.returnPeriod)} \n` +
+      `VS30: ${vs30} \n\n`
+    );
+  }
+
   /**
    * Build the URL to call the appropriate backend service with the given values.
    *
@@ -602,6 +678,27 @@ export class AppService
     }
   }
 
+  private summaryReportText(
+    componentData: DisaggComponentData,
+    showMetadata = true
+  ): string {
+    const formValues = this.formGroup.getRawValue();
+    const firstLine = '*** Disaggregation of Seismic Hazard ***';
+    const metadata = showMetadata
+      ? `${firstLine}\n\n${this.metadataText(formValues)}`
+      : '';
+
+    return (
+      metadata +
+      `** Disaggregation Component: ${componentData.component} **\n\n` +
+      this.summaryToText(componentData.summary, formValues, false) +
+      '\n\n' +
+      this.componentDataToCSV(componentData, formValues, false) +
+      '\n\n' +
+      this.contributorsToCSV(componentData.sources, formValues, false)
+    );
+  }
+
   /**
    * Convert the disaggregation summaries to text.
    *
@@ -610,7 +707,8 @@ export class AppService
    */
   private summaryToText(
     summaries: DisaggSummary[],
-    formValues: DisaggControlForm
+    formValues: DisaggControlForm,
+    showMetadata = true
   ): string {
     const text = summaries.map(summary => {
       const name = summary.name;
@@ -621,7 +719,19 @@ export class AppService
       return `${name}:\n` + values.join('\n');
     });
 
-    return `${this.metadataCSV(formValues)}\n\n\n` + `${text.join('\n\n\n')}`;
+    return showMetadata
+      ? `${this.metadataCSV(formValues)}\n\n\n` + `${text.join('\n\n')}`
+      : `${text.join('\n\n')}`;
+  }
+
+  private updateDisaggData(): void {
+    const disaggData = this.state().serviceResponse?.response.disaggs.find(
+      disagg => disagg.imt.value === this.formGroup.getRawValue().imt.toString()
+    );
+
+    this.updateState({
+      disaggData,
+    });
   }
 
   private updateUrl(): void {
@@ -644,7 +754,7 @@ export class AppService
     this.updateState({
       serviceCallInfo: {
         ...this.state().serviceCallInfo,
-        usage: [nshmService.url],
+        usage: [`${nshmService.url}${this.endpoint}`],
       },
     });
   }
diff --git a/projects/nshmp-apps/src/styles/_print.scss b/projects/nshmp-apps/src/styles/_print.scss
index b033dcec23e07a4930366d63343e1e6ef0aea966..2103c6e0d5443850bf03872b229fa84d3f9e0674 100644
--- a/projects/nshmp-apps/src/styles/_print.scss
+++ b/projects/nshmp-apps/src/styles/_print.scss
@@ -23,6 +23,14 @@
     page-break-before: always !important;
   }
 
+  .print-full-page {
+    height: 10in;
+  }
+
+  mat-tab-header {
+    display: none !important;
+  }
+
   mat-accordion {
     mat-expansion-panel {
       .mat-expansion-indicator {