diff --git a/__tests__/src/components/OpenSeadragonViewer.test.js b/__tests__/src/components/OpenSeadragonViewer.test.js
index 6c4e66698e7a8def24169fef778a8145838ece62..cce744544cc69697dac7491a23c8972e1c6dfa56 100644
--- a/__tests__/src/components/OpenSeadragonViewer.test.js
+++ b/__tests__/src/components/OpenSeadragonViewer.test.js
@@ -4,6 +4,7 @@ import OpenSeadragon from 'openseadragon';
 import { OpenSeadragonViewer } from '../../../src/components/OpenSeadragonViewer';
 import OpenSeadragonCanvasOverlay from '../../../src/lib/OpenSeadragonCanvasOverlay';
 import Annotation from '../../../src/lib/Annotation';
+import CanvasWorld from '../../../src/lib/CanvasWorld';
 
 jest.mock('openseadragon');
 jest.mock('../../../src/lib/OpenSeadragonCanvasOverlay');
@@ -34,6 +35,7 @@ describe('OpenSeadragonViewer', () => {
         updateViewport={updateViewport}
         t={k => k}
         classes={{ controls: 'controls' }}
+        canvasWorld={new CanvasWorld([])}
       >
         <div className="foo" />
       </OpenSeadragonViewer>,
@@ -108,6 +110,7 @@ describe('OpenSeadragonViewer', () => {
           viewer={{ x: 1, y: 0, zoom: 0.5 }}
           config={{}}
           updateViewport={updateViewport}
+          canvasWorld={new CanvasWorld([])}
           t={k => k}
         >
           <div className="foo" />
diff --git a/__tests__/src/components/WindowViewer.test.js b/__tests__/src/components/WindowViewer.test.js
index f66019f014df5d493b0b4f0457cf2b0191697231..1fc591a9bb0d30bab47b8aef2dc71735704cf8a9 100644
--- a/__tests__/src/components/WindowViewer.test.js
+++ b/__tests__/src/components/WindowViewer.test.js
@@ -8,7 +8,7 @@ import fixture from '../../fixtures/version-2/019.json';
 import emptyCanvasFixture from '../../fixtures/version-2/emptyCanvas.json';
 import otherContentFixture from '../../fixtures/version-2/299843.json';
 
-let canvases = manifesto.create(fixture).getSequences()[0].getCanvases();
+let currentCanvases = manifesto.create(fixture).getSequences()[0].getCanvases();
 
 let mockWindow = {
   canvasIndex: 0,
@@ -23,7 +23,7 @@ function createWrapper(props) {
       infoResponses={{}}
       fetchInfoResponse={() => {}}
       fetchAnnotation={() => {}}
-      canvases={canvases}
+      currentCanvases={[currentCanvases[1]]}
       window={mockWindow}
       {...props}
     />,
@@ -32,10 +32,8 @@ function createWrapper(props) {
 
 describe('WindowViewer', () => {
   let wrapper;
-  beforeEach(() => {
-    wrapper = createWrapper();
-  });
   it('renders properly', () => {
+    wrapper = createWrapper();
     expect(wrapper.matchesElement(
       <>
         <OSDViewer>
@@ -44,31 +42,6 @@ describe('WindowViewer', () => {
       </>,
     )).toBe(true);
   });
-  describe('currentCanvases', () => {
-    it('by default returns a single canvas', () => {
-      expect(wrapper.instance().currentCanvases().length).toEqual(1);
-    });
-    describe('book view', () => {
-      it('when the first canvas is selected', () => {
-        wrapper = createWrapper({
-          window: {
-            canvasIndex: 0,
-            view: 'book',
-          },
-        });
-        expect(wrapper.instance().currentCanvases().length).toEqual(1);
-      });
-      it('when the second canvas is selected', () => {
-        wrapper = createWrapper({
-          window: {
-            canvasIndex: 1,
-            view: 'book',
-          },
-        });
-        expect(wrapper.instance().currentCanvases().length).toEqual(2);
-      });
-    });
-  });
   describe('currentInfoResponses', () => {
     describe('returns only available infoResponses', () => {
       it('isFetching is false', () => {
@@ -131,38 +104,32 @@ describe('WindowViewer', () => {
       });
     });
   });
-  it('when view type changes', () => {
-    expect(wrapper.instance().canvasGroupings.groupings().length).toEqual(3);
-    wrapper.setProps({
-      window: {
-        canvasIndex: 0,
-        view: 'book',
-      },
-    });
-    expect(wrapper.instance().canvasGroupings.groupings().length).toEqual(2);
-  });
 
   describe('componentDidMount', () => {
     it('does not call fetchInfoResponse for a canvas that has no images', () => {
       const mockFnCanvas0 = jest.fn();
       const mockFnCanvas2 = jest.fn();
-      canvases = manifesto.create(emptyCanvasFixture).getSequences()[0].getCanvases();
+      const canvases = manifesto.create(emptyCanvasFixture).getSequences()[0].getCanvases();
+
+      currentCanvases = [canvases[0]];
+
       mockWindow = {
         canvasIndex: 0,
         view: 'single',
       };
       wrapper = createWrapper(
         {
-          canvases,
+          currentCanvases,
           fetchInfoResponse: mockFnCanvas0,
           window: mockWindow,
         },
       );
       expect(mockFnCanvas0).toHaveBeenCalledTimes(1);
 
+      currentCanvases = [canvases[2]];
       wrapper = createWrapper(
         {
-          canvases,
+          currentCanvases,
           fetchInfoResponse: mockFnCanvas2,
           window: { canvasIndex: 2, view: 'single' },
         },
@@ -171,9 +138,11 @@ describe('WindowViewer', () => {
     });
     it('calls fetchAnnotation when otherContent is present', () => {
       const mockFnAnno = jest.fn();
-      canvases = manifesto.create(otherContentFixture).getSequences()[0].getCanvases();
+      const canvases = manifesto.create(otherContentFixture).getSequences()[0].getCanvases();
+      currentCanvases = [canvases[0]];
+
       wrapper = createWrapper(
-        { canvases, fetchAnnotation: mockFnAnno },
+        { currentCanvases, fetchAnnotation: mockFnAnno },
       );
       expect(mockFnAnno).toHaveBeenCalledTimes(1);
     });
@@ -182,16 +151,17 @@ describe('WindowViewer', () => {
   describe('componentDidUpdate', () => {
     it('does not call fetchInfoResponse for a canvas that has no images', () => {
       const mockFn = jest.fn();
-      canvases = manifesto.create(emptyCanvasFixture).getSequences()[0].getCanvases();
+      const canvases = manifesto.create(emptyCanvasFixture).getSequences()[0].getCanvases();
+      currentCanvases = [canvases[2]];
       mockWindow = {
         canvasIndex: 2,
         view: 'single',
       };
       wrapper = createWrapper(
-        { canvases, fetchInfoResponse: mockFn, window: mockWindow },
+        { currentCanvases, fetchInfoResponse: mockFn, window: mockWindow },
       );
 
-      wrapper.setProps({ window: { canvasIndex: 3, view: 'single' } });
+      wrapper.setProps({ currentCanvases: [canvases[3]], window: { canvasIndex: 3, view: 'single' } });
 
       expect(mockFn).toHaveBeenCalledTimes(0);
     });
diff --git a/__tests__/src/selectors/index.test.js b/__tests__/src/selectors/index.test.js
index b814ddaeadecb3065bc890f7ac21b220ff6a9f17..cf556213dc32c9f745b3c989cdbb0df104cf0739 100644
--- a/__tests__/src/selectors/index.test.js
+++ b/__tests__/src/selectors/index.test.js
@@ -1,128 +1,65 @@
 import {
-  getAllOrSelectedAnnotations,
+  getAllOrSelectedAnnotationsOnCanvases,
   getAnnotationResourcesByMotivation,
-  getIdAndContentOfResources,
   getLanguagesFromConfigWithCurrent,
   getSelectedAnnotationIds,
-  getSelectedTargetAnnotations,
-  getSelectedTargetsAnnotations,
-  getSelectedTargetAnnotationResources,
 } from '../../../src/state/selectors';
-import Annotation from '../../../src/lib/Annotation';
-import AnnotationResource from '../../../src/lib/AnnotationResource';
 
-describe('getSelectedTargetAnnotations', () => {
-  it('returns annotations for the given canvasId that have resources', () => {
+describe('getAnnotationResourcesByMotivation', () => {
+  it('returns an array of annotation resources (filtered by the passed in array of motiviations)', () => {
+    const expected = [
+      ['oa:commenting'],
+      ['sc:something-else', 'oa:commenting'],
+    ];
+
     const state = {
       annotations: {
-        abc123: {
-          annoId1: { '@id': 'annoId1', json: { resources: ['aResource'] } },
-          annoId2: { '@id': 'annoId2' },
-          annoId3: { '@id': 'annoId3', json: { resources: [] } },
+        cid1: {
+          annoId1: {
+            id: 'annoId1',
+            json: {
+              resources: [
+                { '@id': 'annoId1', motivation: 'oa:commenting' },
+                { '@id': 'annoId2', motivation: 'oa:not-commenting' },
+                { '@id': 'annoId3', motivation: ['sc:something-else', 'oa:commenting'] },
+              ],
+            },
+          },
         },
       },
-    };
-
-    expect(getSelectedTargetAnnotations(state, 'abc123').length).toEqual(1);
-  });
-
-  it('returns an empty array if there are no annotations', () => {
-    const state = { annotations: { xyz321: {} } };
-    const expected = [];
-
-    expect(getSelectedTargetAnnotations({}, 'abc123')).toEqual(expected);
-    expect(getSelectedTargetAnnotations(state, 'abc123')).toEqual(expected);
-  });
-});
-
-describe('getSelectedTargetsAnnotations', () => {
-  it('returns annotations for multiple canvasIds', () => {
-    const state = {
-      annotations: {
-        abc123: {
-          annoId1: { '@id': 'annoId1', json: { resources: ['aResource'] } },
-          annoId2: { '@id': 'annoId2' },
-          annoId3: { '@id': 'annoId3', json: { resources: [] } },
+      manifests: {
+        mid: {
+          json: {
+            '@context': 'http://iiif.io/api/presentation/2/context.json',
+            '@id':
+             'http://iiif.io/api/presentation/2.1/example/fixtures/19/manifest.json',
+            '@type': 'sc:Manifest',
+            sequences: [
+              {
+                canvases: [
+                  {
+                    '@id': 'cid1',
+                  },
+                ],
+              },
+            ],
+          },
         },
-        def456: {
-          annoId4: { '@id': 'annoId4', json: { resources: ['helloWorld'] } },
+      },
+      windows: {
+        abc123: {
+          canvasIndex: 0,
+          manifestId: 'mid',
         },
       },
     };
 
-    expect(getSelectedTargetsAnnotations(state, ['abc123', 'def456']).length).toEqual(2);
-  });
-
-  it('returns an empty array if there are no annotations', () => {
-    const state = { annotations: { xyz321: {} } };
-    const expected = [];
-
-    expect(getSelectedTargetsAnnotations({}, ['abc123'])).toEqual(expected);
-    expect(getSelectedTargetsAnnotations(state, ['abc123'])).toEqual(expected);
-  });
-});
-
-describe('getAnnotationResourcesByMotivation', () => {
-  const annotations = [
-    new Annotation({ resources: [{ motivation: 'oa:commenting' }] }),
-    new Annotation({ resources: [{ motivation: 'oa:not-commenting' }] }),
-    new Annotation({ resources: [{ motivation: ['sc:something-else', 'oa:commenting'] }] }),
-  ];
-
-  it('returns an array of annotation resources (filtered by the passed in array of motiviations)', () => {
-    const expected = [
-      ['oa:commenting'],
-      ['sc:something-else', 'oa:commenting'],
-    ];
-
     expect(
-      getAnnotationResourcesByMotivation(annotations, ['something', 'oa:commenting']).map(r => r.motivations),
+      getAnnotationResourcesByMotivation(state, { motivations: ['something', 'oa:commenting'], windowId: 'abc123' }).map(r => r.motivations),
     ).toEqual(expected);
   });
 });
 
-describe('getIdAndContentOfResources', () => {
-  it('returns an array if id/content objects from the annotation resources', () => {
-    const annotations = [
-      new AnnotationResource({ '@id': 'theId', on: 'example.com', resource: { chars: 'The Content' } }),
-    ];
-    const expected = [
-      {
-        content: 'The Content',
-        id: 'theId',
-        targetId: 'example.com',
-      },
-    ];
-
-    expect(getIdAndContentOfResources(annotations)).toEqual(expected);
-  });
-
-  it('provides an ID as a UUID if the annotation does not have one', () => {
-    const annotations = [
-      new AnnotationResource({ resource: { chars: 'The Content' } }),
-    ];
-
-    expect(getIdAndContentOfResources(annotations)[0].id).toEqual(
-      expect.stringMatching(/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/),
-    );
-  });
-
-  it('handles resource arrays', () => {
-    const annotations = [
-      new AnnotationResource({ '@id': 'theId', on: 'example.com', resource: [{ chars: 'The' }, { chars: 'Content' }] }),
-    ];
-    const expected = [
-      {
-        content: 'The Content',
-        id: 'theId',
-        targetId: 'example.com',
-      },
-    ];
-
-    expect(getIdAndContentOfResources(annotations)).toEqual(expected);
-  });
-});
-
 describe('getLanguagesFromConfigWithCurrent', () => {
   it('returns an array of objects with locale, label, and current properties', () => {
     const state = {
@@ -154,8 +91,29 @@ describe('getLanguagesFromConfigWithCurrent', () => {
 
 it('getSelectedAnnotationIds returns an array of selected annotation IDs from state', () => {
   const state = {
+    manifests: {
+      mid: {
+        json: {
+          '@context': 'http://iiif.io/api/presentation/2/context.json',
+          '@id':
+           'http://iiif.io/api/presentation/2.1/example/fixtures/19/manifest.json',
+          '@type': 'sc:Manifest',
+          sequences: [
+            {
+              canvases: [
+                {
+                  '@id': 'tid1',
+                },
+              ],
+            },
+          ],
+        },
+      },
+    },
     windows: {
       wid: {
+        canvasIndex: 0,
+        manifestId: 'mid',
         selectedAnnotations: {
           tid1: ['aid1', 'aid2'],
           tid2: ['aid3'],
@@ -164,33 +122,12 @@ it('getSelectedAnnotationIds returns an array of selected annotation IDs from st
     },
   };
 
-  expect(getSelectedAnnotationIds(state, 'wid', ['tid2'])).toEqual(
-    ['aid3'],
-  );
-  expect(getSelectedAnnotationIds(state, 'wid', ['tid1', 'tid2'])).toEqual(
-    ['aid1', 'aid2', 'aid3'],
+  expect(getSelectedAnnotationIds(state, { windowId: 'wid' })).toEqual(
+    ['aid1', 'aid2'],
   );
 });
 
-it('getSelectedTargetAnnotationResources filters the annotation resources by the annotationIds passed in', () => {
-  const state = {
-    annotations: {
-      cid1: {
-        annoId1: { id: 'annoId1', json: { resources: [{ '@id': 'annoId1' }, { '@id': 'annoId2' }] } },
-      },
-    },
-  };
-
-  expect(
-    getSelectedTargetAnnotationResources(state, ['cid1'], ['annoId1'])[0].resources.length,
-  ).toBe(1);
-
-  expect(
-    getSelectedTargetAnnotationResources(state, ['cid1'], ['annoId1', 'annoId2'])[0].resources.length,
-  ).toBe(2);
-});
-
-describe('getAllOrSelectedAnnotations', () => {
+describe('getAllOrSelectedAnnotationsOnCanvases', () => {
   it('returns all annotations if the given window is set to display all', () => {
     const state = {
       annotations: {
@@ -198,13 +135,32 @@ describe('getAllOrSelectedAnnotations', () => {
           annoId1: { id: 'annoId1', json: { resources: [{ '@id': 'annoId1' }, { '@id': 'annoId2' }] } },
         },
       },
+      manifests: {
+        mid: {
+          json: {
+            '@context': 'http://iiif.io/api/presentation/2/context.json',
+            '@id':
+             'http://iiif.io/api/presentation/2.1/example/fixtures/19/manifest.json',
+            '@type': 'sc:Manifest',
+            sequences: [
+              {
+                canvases: [
+                  {
+                    '@id': 'cid1',
+                  },
+                ],
+              },
+            ],
+          },
+        },
+      },
       windows: {
-        abc123: { displayAllAnnotations: true },
+        abc123: { canvasIndex: 0, displayAllAnnotations: true, manifestId: 'mid' },
       },
     };
 
     expect(
-      getAllOrSelectedAnnotations(state, 'abc123', ['cid1'], ['annoId1'])[0].resources.length,
+      getAllOrSelectedAnnotationsOnCanvases(state, { windowId: 'abc123' })[0].resources.length,
     ).toBe(2);
   });
 
@@ -215,13 +171,73 @@ describe('getAllOrSelectedAnnotations', () => {
           annoId1: { id: 'annoId1', json: { resources: [{ '@id': 'annoId1' }, { '@id': 'annoId2' }] } },
         },
       },
+      manifests: {
+        mid: {
+          json: {
+            '@context': 'http://iiif.io/api/presentation/2/context.json',
+            '@id':
+             'http://iiif.io/api/presentation/2.1/example/fixtures/19/manifest.json',
+            '@type': 'sc:Manifest',
+            sequences: [
+              {
+                canvases: [
+                  {
+                    '@id': 'cid1',
+                  },
+                ],
+              },
+            ],
+          },
+        },
+      },
+      windows: {
+        abc123: {
+          canvasIndex: 0,
+          displayAllAnnotations: false,
+          manifestId: 'mid',
+          selectedAnnotations: { cid1: ['annoId1'] },
+        },
+      },
+    };
+
+    expect(
+      getAllOrSelectedAnnotationsOnCanvases(state, { windowId: 'abc123' })[0].resources.length,
+    ).toBe(1);
+  });
+
+  it('filters the annotation resources by the selected annotations for the window', () => {
+    const state = {
+      annotations: {
+        cid1: {
+          annoId1: { id: 'annoId1', json: { resources: [{ '@id': 'annoId2' }, { '@id': 'annoId3' }] } },
+        },
+      },
+      manifests: {
+        mid: {
+          json: {
+            '@context': 'http://iiif.io/api/presentation/2/context.json',
+            '@id':
+             'http://iiif.io/api/presentation/2.1/example/fixtures/19/manifest.json',
+            '@type': 'sc:Manifest',
+            sequences: [
+              {
+                canvases: [
+                  {
+                    '@id': 'cid1',
+                  },
+                ],
+              },
+            ],
+          },
+        },
+      },
       windows: {
-        abc123: { displayAllAnnotations: false },
+        abc123: { canvasIndex: 0, manifestId: 'mid', selectedAnnotations: { cid1: ['annoId2'] } },
       },
     };
 
     expect(
-      getAllOrSelectedAnnotations(state, 'abc123', ['cid1'], ['annoId1'])[0].resources.length,
+      getAllOrSelectedAnnotationsOnCanvases(state, { windowId: 'abc123' })[0].resources.length,
     ).toBe(1);
   });
 });
diff --git a/src/components/OpenSeadragonViewer.js b/src/components/OpenSeadragonViewer.js
index 4c1f9533088aa6d75e18b69215982d846135eb9c..2fa636830ef3cb6f297fb21d0e29ce32c74e9f0a 100644
--- a/src/components/OpenSeadragonViewer.js
+++ b/src/components/OpenSeadragonViewer.js
@@ -132,11 +132,11 @@ export class OpenSeadragonViewer extends Component {
    * annotationsToContext - converts anontations to a canvas context
    */
   annotationsToContext(annotations) {
-    const { currentCanvases } = this.props;
+    const { canvasWorld } = this.props;
     const context = this.osdCanvasOverlay.context2d;
     annotations.forEach((annotation) => {
       annotation.resources.forEach((resource) => {
-        const offset = new CanvasWorld(currentCanvases).offsetByCanvas(resource.targetId);
+        const offset = canvasWorld.offsetByCanvas(resource.targetId);
         const fragment = resource.fragmentSelector;
         fragment[0] += offset.x;
         context.strokeStyle = 'yellow';
@@ -149,7 +149,7 @@ export class OpenSeadragonViewer extends Component {
   /**
    */
   addTileSource(tileSource, i = 0) {
-    const { currentCanvases } = this.props;
+    const { canvasWorld } = this.props;
     return new Promise((resolve, reject) => {
       if (!this.viewer) {
         return;
@@ -157,7 +157,7 @@ export class OpenSeadragonViewer extends Component {
       this.viewer.addTiledImage({
         error: event => reject(event),
         fitBounds: new OpenSeadragon.Rect(
-          ...new CanvasWorld(currentCanvases).canvasToWorldCoordinates(i),
+          ...canvasWorld.canvasToWorldCoordinates(i),
         ),
         success: event => resolve(event),
         tileSource,
@@ -197,8 +197,8 @@ export class OpenSeadragonViewer extends Component {
    * zoomToWorld - zooms the viewer to the extent of the canvas world
    */
   zoomToWorld(immediately = true) {
-    const { currentCanvases } = this.props;
-    this.fitBounds(...new CanvasWorld(currentCanvases).worldBounds(), immediately);
+    const { canvasWorld } = this.props;
+    this.fitBounds(...canvasWorld.worldBounds(), immediately);
   }
 
   /**
@@ -262,7 +262,6 @@ OpenSeadragonViewer.defaultProps = {
   annotations: [],
   children: null,
   classes: {},
-  currentCanvases: [],
   label: null,
   tileSources: [],
   viewer: null,
@@ -271,9 +270,9 @@ OpenSeadragonViewer.defaultProps = {
 
 OpenSeadragonViewer.propTypes = {
   annotations: PropTypes.arrayOf(PropTypes.object),
+  canvasWorld: PropTypes.instanceOf(CanvasWorld).isRequired,
   children: PropTypes.element,
   classes: PropTypes.object, // eslint-disable-line react/forbid-prop-types
-  currentCanvases: PropTypes.arrayOf(PropTypes.object),
   label: PropTypes.string,
   t: PropTypes.func.isRequired,
   tileSources: PropTypes.arrayOf(PropTypes.object),
diff --git a/src/components/WindowCanvasNavigationControls.js b/src/components/WindowCanvasNavigationControls.js
index f8315c7070d58e412c5cfddca70c6623ad48c5a9..db6ddaad9cb6fd1562dc5caa4da00ac99376846b 100644
--- a/src/components/WindowCanvasNavigationControls.js
+++ b/src/components/WindowCanvasNavigationControls.js
@@ -12,7 +12,7 @@ export class WindowCanvasNavigationControls extends Component {
   /** */
   render() {
     const {
-      canvases, visible, window, zoomToWorld,
+      visible, window, zoomToWorld,
     } = this.props;
 
     if (!visible) return (<></>);
@@ -20,7 +20,7 @@ export class WindowCanvasNavigationControls extends Component {
     return (
       <div className={ns('canvas-nav')}>
         <ZoomControls windowId={window.id} zoomToWorld={zoomToWorld} />
-        <ViewerNavigation window={window} canvases={canvases} />
+        <ViewerNavigation window={window} />
         <ViewerInfo windowId={window.id} />
       </div>
     );
@@ -29,7 +29,6 @@ export class WindowCanvasNavigationControls extends Component {
 
 
 WindowCanvasNavigationControls.propTypes = {
-  canvases: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
   visible: PropTypes.bool,
   window: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
   zoomToWorld: PropTypes.func.isRequired,
diff --git a/src/components/WindowViewer.js b/src/components/WindowViewer.js
index 87fabc8dc80882e46edc6007e30d8d98a4c8e7bd..65337429efa7c7d97c8c9eb0b4bb76826abdb08b 100644
--- a/src/components/WindowViewer.js
+++ b/src/components/WindowViewer.js
@@ -3,33 +3,21 @@ import PropTypes from 'prop-types';
 import OSDViewer from '../containers/OpenSeadragonViewer';
 import WindowCanvasNavigationControls from '../containers/WindowCanvasNavigationControls';
 import ManifestoCanvas from '../lib/ManifestoCanvas';
-import CanvasGroupings from '../lib/CanvasGroupings';
 
 /**
  * Represents a WindowViewer in the mirador workspace. Responsible for mounting
  * OSD and Navigation
  */
 export class WindowViewer extends Component {
-  /**
-   * @param {Object} props
-   */
-  constructor(props) {
-    super(props);
-
-    const { canvases, window } = this.props;
-    this.canvases = canvases;
-    this.canvasGroupings = new CanvasGroupings(this.canvases, window.view);
-  }
-
   /**
    * componentDidMount - React lifecycle method
    * Request the initial canvas on mount
    */
   componentDidMount() {
-    const { fetchInfoResponse, fetchAnnotation } = this.props;
+    const { currentCanvases, fetchInfoResponse, fetchAnnotation } = this.props;
 
     if (!this.infoResponseIsInStore()) {
-      this.currentCanvases().forEach((canvas) => {
+      currentCanvases.forEach((canvas) => {
         const manifestoCanvas = new ManifestoCanvas(canvas);
         const { imageInformationUri } = manifestoCanvas;
         if (imageInformationUri) {
@@ -47,11 +35,14 @@ export class WindowViewer extends Component {
    * Request a new canvas if it is needed
    */
   componentDidUpdate(prevProps) {
-    const { window, fetchInfoResponse, fetchAnnotation } = this.props;
+    const {
+      currentCanvases, window, fetchInfoResponse, fetchAnnotation,
+    } = this.props;
+
     if (prevProps.window.view !== window.view
       || (prevProps.window.canvasIndex !== window.canvasIndex && !this.infoResponseIsInStore())
     ) {
-      this.currentCanvases().forEach((canvas) => {
+      currentCanvases.forEach((canvas) => {
         const manifestoCanvas = new ManifestoCanvas(canvas);
         const { imageInformationUri } = manifestoCanvas;
         if (imageInformationUri) {
@@ -62,10 +53,6 @@ export class WindowViewer extends Component {
         });
       });
     }
-    // If the view changes, create a new instance
-    if (prevProps.window.view !== window.view) {
-      this.canvasGroupings = new CanvasGroupings(this.canvases, window.view);
-    }
   }
 
   /**
@@ -74,29 +61,22 @@ export class WindowViewer extends Component {
    * @return [Boolean]
    */
   infoResponseIsInStore() {
+    const { currentCanvases } = this.props;
+
     const responses = this.currentInfoResponses();
-    if (responses.length === this.currentCanvases().length) {
+    if (responses.length === currentCanvases.length) {
       return true;
     }
     return false;
   }
 
-  /**
-   * Uses CanvasGroupings to figure out how many and what canvases to present to
-   * a user based off of the view, number of canvases, and canvasIndex.
-   */
-  currentCanvases() {
-    const { window } = this.props;
-    return this.canvasGroupings.getCanvases(window.canvasIndex);
-  }
-
   /**
    * currentInfoResponses - Selects infoResponses that are relevent to existing
    * canvases to be displayed.
    */
   currentInfoResponses() {
-    const { infoResponses } = this.props;
-    const currentCanvases = this.currentCanvases();
+    const { currentCanvases, infoResponses } = this.props;
+
     return currentCanvases.map(canvas => (
       infoResponses[new ManifestoCanvas(canvas).imageInformationUri]
     )).filter(infoResponse => (infoResponse !== undefined
@@ -108,10 +88,12 @@ export class WindowViewer extends Component {
    * Return an image information response from the store for the correct image
    */
   tileInfoFetchedFromStore() {
+    const { currentCanvases } = this.props;
+
     const responses = this.currentInfoResponses()
       .map(infoResponse => infoResponse.json);
     // Only return actual tileSources when all current canvases have completed.
-    if (responses.length === this.currentCanvases().length) {
+    if (responses.length === currentCanvases.length) {
       return responses;
     }
     return [];
@@ -126,10 +108,9 @@ export class WindowViewer extends Component {
       <>
         <OSDViewer
           tileSources={this.tileInfoFetchedFromStore()}
-          currentCanvases={this.currentCanvases()}
           windowId={window.id}
         >
-          <WindowCanvasNavigationControls windowId={window.id} canvases={this.canvases} />
+          <WindowCanvasNavigationControls windowId={window.id} />
         </OSDViewer>
       </>
     );
@@ -137,7 +118,7 @@ export class WindowViewer extends Component {
 }
 
 WindowViewer.propTypes = {
-  canvases: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
+  currentCanvases: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
   fetchAnnotation: PropTypes.func.isRequired,
   fetchInfoResponse: PropTypes.func.isRequired,
   infoResponses: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
diff --git a/src/containers/AnnotationSettings.js b/src/containers/AnnotationSettings.js
index 5fbec40b2e8c34e514c2e1aa4447d81c32f0fb1a..b56ab73ac4eb3e3f76a33766457886098c7f6913 100644
--- a/src/containers/AnnotationSettings.js
+++ b/src/containers/AnnotationSettings.js
@@ -5,8 +5,6 @@ import * as actions from '../state/actions';
 import { withPlugins } from '../extend';
 import {
   getAnnotationResourcesByMotivation,
-  getSelectedTargetAnnotations,
-  getSelectedCanvas,
 } from '../state/selectors';
 import { AnnotationSettings } from '../components/AnnotationSettings';
 
@@ -15,10 +13,7 @@ import { AnnotationSettings } from '../components/AnnotationSettings';
  */
 const mapStateToProps = (state, { windowId }) => ({
   displayAll: state.windows[windowId].displayAllAnnotations,
-  displayAllDisabled: getAnnotationResourcesByMotivation(
-    getSelectedTargetAnnotations(state, (getSelectedCanvas(state, { windowId }) || {}).id),
-    ['oa:commenting', 'sc:painting'],
-  ).length < 2,
+  displayAllDisabled: getAnnotationResourcesByMotivation(state, { motivations: ['oa:commenting', 'sc:painting'], windowId }).length < 2,
 });
 
 /**
diff --git a/src/containers/OpenSeadragonViewer.js b/src/containers/OpenSeadragonViewer.js
index 392f9d6e64aa0aabdca3c3ed2ebe6fab1fabc799..21ac19e3dedd22008a30fe8cb0120dfa40742eb9 100644
--- a/src/containers/OpenSeadragonViewer.js
+++ b/src/containers/OpenSeadragonViewer.js
@@ -6,10 +6,11 @@ import { fade } from '@material-ui/core/styles/colorManipulator';
 import { withPlugins } from '../extend';
 import { OpenSeadragonViewer } from '../components/OpenSeadragonViewer';
 import * as actions from '../state/actions';
+import CanvasWorld from '../lib/CanvasWorld';
 import {
-  getAllOrSelectedAnnotations,
+  getAllOrSelectedAnnotationsOnCanvases,
   getCanvasLabel,
-  getSelectedAnnotationIds,
+  getSelectedCanvases,
 } from '../state/selectors';
 
 /**
@@ -17,23 +18,11 @@ import {
  * @memberof Window
  * @private
  */
-const mapStateToProps = ({
-  viewers, windows, manifests, annotations,
-}, { windowId, currentCanvases }) => ({
-  annotations: getAllOrSelectedAnnotations(
-    { annotations, windows },
-    windowId,
-    currentCanvases.map(c => c.id),
-    getSelectedAnnotationIds({ windows }, windowId, currentCanvases.map(c => c.id)),
-  ),
-  label: getCanvasLabel({
-    manifests,
-    windows,
-  }, {
-    canvasIndex: 'selected',
-    windowId,
-  }),
-  viewer: viewers[windowId],
+const mapStateToProps = (state, { windowId }) => ({
+  annotations: getAllOrSelectedAnnotationsOnCanvases(state, { windowId }),
+  canvasWorld: new CanvasWorld(getSelectedCanvases(state, { windowId })),
+  label: getCanvasLabel(state, { canvasIndex: 'selected', windowId }),
+  viewer: state.viewers[windowId],
 });
 
 /**
diff --git a/src/containers/ViewerNavigation.js b/src/containers/ViewerNavigation.js
index d1edbed061c9122b2d08c78cebf871e955bd42ee..e5e804d7b0aa9163a657bd7f5a2f2af663cc0c76 100644
--- a/src/containers/ViewerNavigation.js
+++ b/src/containers/ViewerNavigation.js
@@ -3,8 +3,14 @@ import { connect } from 'react-redux';
 import { withTranslation } from 'react-i18next';
 import { withPlugins } from '../extend';
 import * as actions from '../state/actions';
+import { getManifestCanvases } from '../state/selectors';
 import { ViewerNavigation } from '../components/ViewerNavigation';
 
+/** */
+const mapStateToProps = (state, { window }) => ({
+  canvases: getManifestCanvases(state, { windowId: window.id }),
+});
+
 /**
  * mapDispatchToProps - used to hook up connect to action creators
  * @memberof ManifestForm
@@ -16,7 +22,7 @@ const mapDispatchToProps = {
 
 const enhance = compose(
   withTranslation(),
-  connect(null, mapDispatchToProps),
+  connect(mapStateToProps, mapDispatchToProps),
   withPlugins('ViewerNavigation'),
   // further HOC go here
 );
diff --git a/src/containers/WindowSideBarAnnotationsPanel.js b/src/containers/WindowSideBarAnnotationsPanel.js
index 12b90c12df81541b918deb5f504cb826a0ae2e6f..53cdb2e4cac0ee9631c5a39aceef12ce462e3b8a 100644
--- a/src/containers/WindowSideBarAnnotationsPanel.js
+++ b/src/containers/WindowSideBarAnnotationsPanel.js
@@ -5,14 +5,23 @@ import { withStyles } from '@material-ui/core/styles';
 import { withPlugins } from '../extend';
 import * as actions from '../state/actions';
 import {
-  getIdAndContentOfResources,
   getSelectedAnnotationIds,
-  getSelectedCanvases,
-  getSelectedTargetsAnnotations,
   getAnnotationResourcesByMotivation,
 } from '../state/selectors';
 import { WindowSideBarAnnotationsPanel } from '../components/WindowSideBarAnnotationsPanel';
 
+/**
+ * @param {Array} resources
+ * @return {Array} [{ id: 'abc123', content: 'Annotation Content' }, ...]
+ */
+function getIdAndContentOfResources(resources) {
+  return resources.map((resource, i) => ({
+    content: resource.chars,
+    id: resource.id,
+    targetId: resource.targetId,
+  }));
+}
+
 /**
  * mapStateToProps - to hook up connect
  * @memberof WindowSideBarAnnotationsPanel
@@ -20,17 +29,9 @@ import { WindowSideBarAnnotationsPanel } from '../components/WindowSideBarAnnota
  */
 const mapStateToProps = (state, { windowId }) => ({
   annotations: getIdAndContentOfResources(
-    getAnnotationResourcesByMotivation(
-      getSelectedTargetsAnnotations(
-        state,
-        getSelectedCanvases(state, { windowId }).map(canvas => canvas.id),
-      ),
-      ['oa:commenting', 'sc:painting'],
-    ),
-  ),
-  selectedAnnotationIds: getSelectedAnnotationIds(
-    state, windowId, getSelectedCanvases(state, { windowId }).map(canvas => canvas.id),
+    getAnnotationResourcesByMotivation(state, { motivations: ['oa:commenting', 'sc:painting'], windowId }),
   ),
+  selectedAnnotationIds: getSelectedAnnotationIds(state, { windowId }),
 });
 
 /**
diff --git a/src/containers/WindowSideBarButtons.js b/src/containers/WindowSideBarButtons.js
index 4e0e8d5b2b74122429fa9c46be31295f89edab5e..f4fc77482ea64b600e61764ebf104c86f3e86f06 100644
--- a/src/containers/WindowSideBarButtons.js
+++ b/src/containers/WindowSideBarButtons.js
@@ -7,8 +7,6 @@ import { withPlugins } from '../extend';
 import * as actions from '../state/actions';
 import {
   getCompanionWindowForPosition,
-  getSelectedCanvas,
-  getSelectedTargetAnnotations,
   getAnnotationResourcesByMotivation,
 } from '../state/selectors';
 import { WindowSideBarButtons } from '../components/WindowSideBarButtons';
@@ -32,14 +30,8 @@ const mapDispatchToProps = (dispatch, { windowId }) => ({
  * @private
  */
 const mapStateToProps = (state, { windowId }) => ({
-  hasAnnotations: getAnnotationResourcesByMotivation(
-    getSelectedTargetAnnotations(state, (getSelectedCanvas(state, { windowId }) || {}).id),
-    ['oa:commenting', 'sc:painting'],
-  ).length > 0,
-  sideBarPanel: (getCompanionWindowForPosition(state, {
-    position: 'left',
-    windowId,
-  }) || {}).content,
+  hasAnnotations: getAnnotationResourcesByMotivation(state, { motivations: ['oa:commenting', 'sc:painting'], windowId }).length > 0,
+  sideBarPanel: (getCompanionWindowForPosition(state, { position: 'left', windowId }) || {}).content,
 });
 
 /** */
diff --git a/src/containers/WindowViewer.js b/src/containers/WindowViewer.js
index 0aa4e8a7cc6cd761465f1be2810bebb7f6db1e71..1f109343d74a4d9a0dd4d78b91f3a1d3723e604e 100644
--- a/src/containers/WindowViewer.js
+++ b/src/containers/WindowViewer.js
@@ -3,7 +3,7 @@ import { connect } from 'react-redux';
 import { withPlugins } from '../extend';
 import * as actions from '../state/actions';
 import { WindowViewer } from '../components/WindowViewer';
-import { getManifestCanvases } from '../state/selectors';
+import { getSelectedCanvases } from '../state/selectors';
 
 /**
  * mapStateToProps - to hook up connect
@@ -12,7 +12,7 @@ import { getManifestCanvases } from '../state/selectors';
  */
 const mapStateToProps = (state, { window }) => (
   {
-    canvases: getManifestCanvases(state, { windowId: window.id }),
+    currentCanvases: getSelectedCanvases(state, { windowId: window.id }),
     infoResponses: state.infoResponses,
   }
 );
diff --git a/src/state/selectors/index.js b/src/state/selectors/index.js
index cc07574c6a8bf1a10fc616351274e308b81d8c29..9b2903a8646f8fa3490c7c0961b12e7e067b35eb 100644
--- a/src/state/selectors/index.js
+++ b/src/state/selectors/index.js
@@ -1,42 +1,37 @@
+import { createSelector } from 'reselect';
 import filter from 'lodash/filter';
 import flatten from 'lodash/flatten';
 import Annotation from '../../lib/Annotation';
+import { getSelectedCanvases } from './canvases';
 
 export * from './canvases';
 export * from './manifests';
 export * from './windows';
 
-/**
-* Return annotations for an array of targets
-* @param {object} state
-* @param {Array} targets
-* @return {Array}
-*/
-export function getSelectedTargetsAnnotations(state, targets) {
-  const annotations = state.annotations
-    && targets.map(target => getSelectedTargetAnnotations(state, target));
-  if (!annotations) return [];
+const getAnnotationsOnSelectedCanvases = createSelector(
+  [
+    getSelectedCanvases,
+    state => state.annotations,
+  ],
+  (canvases, annotations) => {
+    if (!annotations || !canvases) return [];
+    return flatten(
+      canvases.map(c => c.id).map(
+        targetId => annotations[targetId] && Object.values(annotations[targetId]),
+      ),
+    );
+  },
+);
 
-  return flatten(annotations);
-}
-
-/**
-* Return a single target's annotations
-* @param {object} state
-* @param {String} target
-* @return {Array}
-*/
-export function getSelectedTargetAnnotations(state, target) {
-  const annotations = state.annotations && state.annotations[target];
-
-  if (!annotations) return [];
-
-  return filter(
-    Object.keys(annotations).map(id => new Annotation(annotations[id].json, target)),
-    annotation => annotation
-                  && annotation.present(),
-  );
-}
+const getPresentAnnotationsOnSelectedCanvases = createSelector(
+  [
+    getAnnotationsOnSelectedCanvases,
+  ],
+  annotations => filter(
+    Object.values(annotations).map(annotation => annotation && new Annotation(annotation.json)),
+    annotation => annotation && annotation.present(),
+  ),
+);
 
 /**
 * Return an array of annotation resources filtered by the given motivation
@@ -44,25 +39,18 @@ export function getSelectedTargetAnnotations(state, target) {
 * @param {Array} motivations
 * @return {Array}
 */
-export function getAnnotationResourcesByMotivation(annotations, motivations) {
-  const resources = flatten(annotations.map(annotation => annotation.resources));
-
-  return filter(resources, resource => resource.motivations.some(
-    motivation => motivations.includes(motivation),
-  ));
-}
-
-/**
- * @param {Array} resources
- * @return {Array} [{ id: 'abc123', content: 'Annotation Content' }, ...]
- */
-export function getIdAndContentOfResources(resources) {
-  return resources.map((resource, i) => ({
-    content: resource.chars,
-    id: resource.id,
-    targetId: resource.targetId,
-  }));
-}
+export const getAnnotationResourcesByMotivation = createSelector(
+  [
+    getPresentAnnotationsOnSelectedCanvases,
+    (state, { motivations }) => motivations,
+  ],
+  (annotations, motivations) => filter(
+    flatten(annotations.map(annotation => annotation.resources)),
+    resource => resource.motivations.some(
+      motivation => motivations.includes(motivation),
+    ),
+  ),
+);
 
 /**
 * Return languages from config (in state) and indicate which is currently set
@@ -86,39 +74,32 @@ export function getLanguagesFromConfigWithCurrent(state) {
  * @param {Array} targetIds
  * @return {Array}
  */
-export function getSelectedAnnotationIds(state, windowId, targetIds) {
-  return flatten(targetIds.map(targetId => state.windows[windowId].selectedAnnotations
-     && state.windows[windowId].selectedAnnotations[targetId]));
-}
+export const getSelectedAnnotationIds = createSelector(
+  [
+    (state, { windowId }) => state.windows[windowId].selectedAnnotations,
+    getSelectedCanvases,
+  ],
+  (selectedAnnotations, canvases) => (
+    flatten(
+      canvases.map(c => c.id).map(targetId => selectedAnnotations && selectedAnnotations[targetId]),
+    )
+  ),
+);
 
-/**
-* Return the current canvas' (selected in a window) selected annotations
-* @param {object} state
-* @param {Array} targetIds
-* @param {Array} annotationIds
-* @return {Array}
-*/
-export function getSelectedTargetAnnotationResources(state, targetIds, annotationIds) {
-  return getSelectedTargetsAnnotations(state, targetIds)
-    .map(annotation => ({
+export const getAllOrSelectedAnnotationsOnCanvases = createSelector(
+  [
+    getPresentAnnotationsOnSelectedCanvases,
+    getSelectedAnnotationIds,
+    (state, { windowId }) => state.windows[windowId].displayAllAnnotations,
+  ],
+  (canvasAnnotations, selectedAnnotationIds, displayAllAnnotations) => {
+    if (displayAllAnnotations) return canvasAnnotations;
+
+    return canvasAnnotations.map(annotation => ({
       id: (annotation['@id'] || annotation.id),
-      resources: annotation.resources.filter(r => annotationIds && annotationIds.includes(r.id)),
+      resources: annotation.resources.filter(
+        r => selectedAnnotationIds && selectedAnnotationIds.includes(r.id),
+      ),
     }));
-}
-
-/**
-* Return all of the given canvases annotations if the window
-* is set to display all, otherwise only return selected
-* @param {object} state
-* @param {String} windowId
-* @param {Array} targetIds
-* @param {Array} annotationIds
-* @return {Array}
-*/
-export function getAllOrSelectedAnnotations(state, windowId, targetIds, annotationIds) {
-  if (state.windows[windowId].displayAllAnnotations) {
-    return getSelectedTargetsAnnotations(state, targetIds);
-  }
-
-  return getSelectedTargetAnnotationResources(state, targetIds, annotationIds);
-}
+  },
+);