old_contents: # -*- coding: utf-8 -*-
  
  import itertools
  from collections import namedtuple
  
  
  class SubPlotSpec(namedtuple('SubPlotSpec', ['r', 'c', 'rs', 'cs'])):
      @property
      def row_end(self):
          return self.r + self.rs
  
      @property
      def col_end(self):
          return self.c + self.cs
  
      @property
      def location(self):
          return self.r, self.c
  
      @property
      def r_max(self):
          return self.row_end - 1
  
      @property
      def c_max(self):
          return self.col_end - 1
  
      def area_spec(self):
          area = {}
          for location in itertools.product(range(self.r, self.row_end), range(self.c, self.col_end)):
              area[location] = None
          anchor_spec = {}
          if self.rs > 1:
              anchor_spec['rowspan'] = self.rs
          if self.cs > 1:
              anchor_spec['colspan'] = self.cs
          area[self.location] = anchor_spec
          return area
  
  
  class PlotCanvas(object):
      def __init__(self):
          self.canvas = {}
          self.max_rows = 1
          self.max_cols = 1
  
      def _mark_area(self, area):
          if set(self.canvas.keys()).intersection(set(area.keys())):
              raise RuntimeError('subplot overlap detected')
          self.canvas.update(area)
  
      def _expand(self, r_max, c_max):
          if r_max > self.max_rows:
              self.max_rows = r_max
          if c_max > self.max_cols:
              self.max_cols = c_max
  
      def occupy_area(self, spec):
          self._mark_area(spec.area_spec())
          self._expand(spec.r_max, spec.c_max)
  
      def make_plotly_spec(self):
          plotly_specs = []
          for r in range(1, self.max_rows + 1):
              row_spec = []
              for c in range(1, self.max_cols + 1):
                  row_spec.append(self.canvas.get((r, c), {}))
              plotly_specs.append(row_spec)
          return plotly_specs
  
new_contents: # -*- coding: utf-8 -*-
  
  import itertools
  from collections import namedtuple
  
  
  class SubPlotSpec(namedtuple('SubPlotSpec', ['r', 'c', 'rs', 'cs'])):
      @property
      def row_end(self):
          return self.r + self.rs
  
      @property
      def col_end(self):
          return self.c + self.cs
  
      @property
      def location(self):
          return self.r, self.c
  
      @property
      def r_max(self):
          return self.row_end - 1
  
      @property
      def c_max(self):
          return self.col_end - 1
  
      def area_spec(self):
          area = {}
          for location in itertools.product(range(self.r, self.row_end), range(self.c, self.col_end)):
              area[location] = None
          anchor_spec = {}
          if self.rs > 1:
              anchor_spec['rowspan'] = self.rs
          if self.cs > 1:
              anchor_spec['colspan'] = self.cs
          area[self.location] = anchor_spec
          return area
  
  
  class PlotCanvas(object):
      def __init__(self):
          self.canvas = {}
          self.max_rows = 1
          self.max_cols = 1
  
      def _expand(self, r_max, c_max):
          if r_max > self.max_rows:
              self.max_rows = r_max
          if c_max > self.max_cols:
              self.max_cols = c_max
  
      def occupy_area(self, spec):
          self.canvas.update(spec.area_spec())
          self._expand(spec.r_max, spec.c_max)
  
      def make_plotly_spec(self):
          plotly_specs = []
          for r in range(1, self.max_rows + 1):
              row_spec = []
              for c in range(1, self.max_cols + 1):
                  row_spec.append(self.canvas.get((r, c), {}))
              plotly_specs.append(row_spec)
          return plotly_specs
  
subject: remove _mark_area, overlap subplot should be allowed in order to get more flexibility