code: # -*- coding: utf-8 -*-
  
  # This code is part of Qiskit.
  #
  # (C) Copyright IBM 2019.
  #
  # This code is licensed under the Apache License, Version 2.0. You may
  # obtain a copy of this license in the LICENSE.txt file in the root directory
  # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
  #
  # Any modifications or derivative works of this code must retain this
  # copyright notice, and modified files need to carry a notice indicating
  # that they have been altered from the originals.
  
  # pylint: disable=invalid-name
  
  """Matplotlib classes for pulse visualization."""
  
  import collections
  import warnings
  from typing import Dict, List, Tuple, Callable, Union, Any
  
  import numpy as np
  
  try:
      from matplotlib import pyplot as plt, gridspec
      HAS_MATPLOTLIB = True
  except ImportError:
      HAS_MATPLOTLIB = False
  
  from qiskit.visualization.pulse.qcstyle import PulseStyle, SchedStyle
  from qiskit.visualization.pulse.interpolation import step_wise
  from qiskit.pulse.channels import (DriveChannel, ControlChannel,
                                     MeasureChannel, AcquireChannel,
                                     SnapshotChannel, Channel)
  from qiskit.pulse.commands import FrameChangeInstruction
  from qiskit.pulse import (Waveform, SamplePulse, FrameChange, PersistentValue, Snapshot, Play,
                            Acquire, PulseError, ParametricPulse, SetFrequency, ShiftPhase,
                            Instruction, ScheduleComponent, ShiftFrequency, SetPhase)
  
  
  class EventsOutputChannels:
      """Pulse dataset for channel."""
  
      def __init__(self, t0: int, tf: int):
          """Create new channel dataset.
  
          TODO: remove PV
  
          Args:
              t0: starting time of plot
              tf: ending time of plot
          """
          self.pulses = {}
          self.t0 = t0
          self.tf = tf
  
          self._waveform = None
          self._framechanges = None
          self._setphase = None
          self._frequencychanges = None
          self._conditionals = None
          self._snapshots = None
          self._labels = None
          self.enable = False
  
      def add_instruction(self, start_time: int, instruction: Instruction):
          """Add new pulse instruction to channel.
  
          Args:
              start_time: Starting time of instruction
              instruction: Instruction object to be added
          """
          if instruction.command is not None:
              pulse = instruction.command
          elif isinstance(instruction, Play):
              pulse = instruction.pulse
          else:
              pulse = instruction
          if start_time in self.pulses.keys():
              self.pulses[start_time].append(pulse)
          else:
              self.pulses[start_time] = [pulse]
  
      @property
      def waveform(self) -> np.ndarray:
          """Get waveform."""
          if self._waveform is None:
              self._build_waveform()
  
          return self._waveform[self.t0:self.tf]
  
      @property
      def framechanges(self) -> Dict[int, FrameChangeInstruction]:
          """Get frame changes."""
          if self._framechanges is None:
              self._build_waveform()
  
          return self._trim(self._framechanges)
  
      @property
      def setphase(self) -> Dict[int, SetPhase]:
          """Get the SetPhase phase values."""
          if self._setphase is None:
              self._build_waveform()
  
          return self._trim(self._setphase)
  
      @property
      def frequencychanges(self) -> Dict[int, SetFrequency]:
          """Get the frequency changes."""
          if self._frequencychanges is None:
              self._build_waveform()
  
          return self._trim(self._frequencychanges)
  
      @property
      def frequencyshift(self) -> Dict[int, ShiftFrequency]:
          """Set the frequency changes."""
          if self._frequencychanges is None:
              self._build_waveform()
  
          return self._trim(self._frequencychanges)
  
      @property
      def conditionals(self) -> Dict[int, str]:
          """Get conditionals."""
          if self._conditionals is None:
              self._build_waveform()
  
          return self._trim(self._conditionals)
  
      @property
      def snapshots(self) -> Dict[int, Snapshot]:
          """Get snapshots."""
          if self._snapshots is None:
              self._build_waveform()
  
          return self._trim(self._snapshots)
  
      @property
      def labels(self) -> Dict[int, Union[Waveform, Acquire]]:
          """Get labels."""
          if self._labels is None:
              self._build_waveform()
  
          return self._trim(self._labels)
  
      def is_empty(self) -> bool:
          """Return if pulse is empty.
  
          Returns:
              bool: if the channel has nothing to plot
          """
          if (any(self.waveform) or self.framechanges or self.setphase or
                  self.conditionals or self.snapshots):
              return False
  
          return True
  
      def to_table(self, name: str) -> List[Tuple[int, str, str]]:
          """Get table contains.
  
          Args:
              name (str): name of channel
  
          Returns:
              A list of events in the channel
          """
          time_event = []
  
          framechanges = self.framechanges
          setphase = self.setphase
          conditionals = self.conditionals
          snapshots = self.snapshots
          frequencychanges = self.frequencychanges
  
          for key, val in framechanges.items():
              data_str = 'shift phase: %.2f' % val
              time_event.append((key, name, data_str))
          for key, val in setphase.items():
              data_str = 'set phase: %.2f' % val
              time_event.append((key, name, data_str))
          for key, val in conditionals.items():
              data_str = 'conditional, %s' % val
              time_event.append((key, name, data_str))
          for key, val in snapshots.items():
              data_str = 'snapshot: %s' % val
              time_event.append((key, name, data_str))
          for key, val in frequencychanges.items():
              data_str = 'frequency: %.4e' % val
              time_event.append((key, name, data_str))
  
          return time_event
  
      def _build_waveform(self):
          """Create waveform from stored pulses.
          """
          self._framechanges = {}
          self._setphase = {}
          self._frequencychanges = {}
          self._conditionals = {}
          self._snapshots = {}
          self._labels = {}
          fc = 0
          pv = np.zeros(self.tf + 1, dtype=np.complex128)
          wf = np.zeros(self.tf + 1, dtype=np.complex128)
          last_pv = None
          for time, commands in sorted(self.pulses.items()):
              if time > self.tf:
                  break
              tmp_fc = 0
              tmp_set_phase = 0
              tmp_sf = None
              for command in commands:
                  if isinstance(command, (FrameChange, ShiftPhase)):
                      tmp_fc += command.phase
                      pv[time:] = 0
                  elif isinstance(command, SetPhase):
                      tmp_set_phase = command.phase
                      pv[time:] = 0
                  elif isinstance(command, SetFrequency):
                      tmp_sf = command.frequency
                  elif isinstance(command, ShiftFrequency):
                      tmp_sf = command.frequency
                  elif isinstance(command, Snapshot):
                      self._snapshots[time] = command.name
              if tmp_fc != 0:
                  self._framechanges[time] = tmp_fc
                  fc += tmp_fc
              if tmp_set_phase != 0:
                  self._setphase[time] = tmp_set_phase
                  fc = tmp_set_phase
              if tmp_sf is not None:
                  self._frequencychanges[time] = tmp_sf
              for command in commands:
                  if isinstance(command, PersistentValue):
                      pv[time:] = np.exp(1j*fc) * command.value
                      last_pv = (time, command)
                      break
  
              for command in commands:
                  duration = command.duration
                  tf = min(time + duration, self.tf)
                  if isinstance(command, ParametricPulse):
                      command = command.get_sample_pulse()
                  if isinstance(command, (Waveform, SamplePulse)):
                      wf[time:tf] = np.exp(1j*fc) * command.samples[:tf-time]
                      pv[time:] = 0
                      self._labels[time] = (tf, command)
                      if last_pv is not None:
                          pv_cmd = last_pv[1]
                          self._labels[last_pv[0]] = (time, pv_cmd)
                          last_pv = None
  
                  elif isinstance(command, Acquire):
                      wf[time:tf] = np.ones(tf - time)
                      self._labels[time] = (tf, command)
          self._waveform = wf + pv
  
      def _trim(self, events: Dict[int, Any]) -> Dict[int, Any]:
          """Return events during given `time_range`.
  
          Args:
              events: time and operation of events.
  
          Returns:
              Events within the specified time range.
          """
          events_in_time_range = {}
  
          for k, v in events.items():
              if self.t0 <= k <= self.tf:
                  events_in_time_range[k] = v
  
          return events_in_time_range
  
  
  class SamplePulseDrawer:
      """A class to create figure for sample pulse."""
  
      def __init__(self, style: PulseStyle):
          """Create new figure.
  
          Args:
              style: Style sheet for pulse visualization.
          """
          self.style = style or PulseStyle()
  
      def draw(self, pulse: Waveform,
               dt: float = 1.0,
               interp_method: Callable = None,
               scale: float = 1, scaling: float = None):
          """Draw figure.
  
          Args:
              pulse: Waveform to draw.
              dt: time interval.
              interp_method: interpolation function.
              scale: Relative visual scaling of waveform amplitudes.
              scaling: Deprecated, see `scale`.
  
          Returns:
              matplotlib.figure.Figure: A matplotlib figure object of the pulse envelope.
          """
          if scaling is not None:
              warnings.warn('The parameter "scaling" is being replaced by "scale"',
                            DeprecationWarning, 3)
              scale = scaling
          # If these self.style.dpi or self.style.figsize are None, they will
          # revert back to their default rcParam keys.
          figure = plt.figure(dpi=self.style.dpi, figsize=self.style.figsize)
  
          interp_method = interp_method or step_wise
  
          ax = figure.add_subplot(111)
          ax.set_facecolor(self.style.bg_color)
  
          samples = pulse.samples
          time = np.arange(0, len(samples) + 1, dtype=float) * dt
  
          time, re, im = interp_method(time, samples, self.style.num_points)
  
          # plot
          ax.fill_between(x=time, y1=re, y2=np.zeros_like(time),
                          facecolor=self.style.wave_color[0], alpha=0.3,
                          edgecolor=self.style.wave_color[0], linewidth=1.5,
                          label='real part')
          ax.fill_between(x=time, y1=im, y2=np.zeros_like(time),
                          facecolor=self.style.wave_color[1], alpha=0.3,
                          edgecolor=self.style.wave_color[1], linewidth=1.5,
                          label='imaginary part')
  
          ax.set_xlim(0, pulse.duration * dt)
          if scale:
              ax.set_ylim(-1/scale, 1/scale)
          else:
              v_max = max(max(np.abs(re)), max(np.abs(im)))
              ax.set_ylim(-1.2 * v_max, 1.2 * v_max)
  
          bbox = ax.get_position()
  
          # This check is here for backwards compatibility. Before, the check was around
          # the suptitle line, however since the font style can take on a type of None
          # we need to unfortunately check both the type and the value of the object.
          if isinstance(self.style.title_font_size, int) and self.style.title_font_size > 0:
              figure.suptitle(str(pulse.name),
                              fontsize=self.style.title_font_size,
                              y=bbox.y1 + 0.02,
                              va='bottom')
  
          return figure
  
  
  class ScheduleDrawer:
      """A class to create figure for schedule and channel."""
  
      def __init__(self, style: SchedStyle):
          """Create new figure.
  
          Args:
              style: Style sheet for pulse schedule visualization.
          """
          self.style = style or SchedStyle()
  
      def _build_channels(self, schedule: ScheduleComponent,
                          channels: List[Channel],
                          t0: int, tf: int,
                          show_framechange_channels: bool = True
                          ) -> Tuple[Dict[Channel, EventsOutputChannels],
                                     Dict[Channel, EventsOutputChannels],
                                     Dict[Channel, EventsOutputChannels]]:
          """Create event table of each pulse channels in the given schedule.
  
          Args:
              schedule: Schedule object to plot.
              channels: Channels to plot.
              t0: Start time of plot.
              tf: End time of plot.
              show_framechange_channels: Plot channels only with FrameChanges.
  
          Returns:
              channels: All channels.
              output_channels: All (D, M, U, A) channels.
              snapshot_channels: Snapshots.
          """
          # prepare waveform channels
          drive_channels = collections.OrderedDict()
          measure_channels = collections.OrderedDict()
          control_channels = collections.OrderedDict()
          acquire_channels = collections.OrderedDict()
          snapshot_channels = collections.OrderedDict()
          _channels = set()
          if show_framechange_channels:
              _channels.update(schedule.channels)
          # take channels that do not only contain framechanges
          else:
              for start_time, instruction in schedule.instructions:
                  if not isinstance(instruction, (FrameChangeInstruction, ShiftPhase, SetPhase)):
                      _channels.update(instruction.channels)
  
          _channels.update(channels)
          for chan in _channels:
              if isinstance(chan, DriveChannel):
                  try:
                      drive_channels[chan] = EventsOutputChannels(t0, tf)
                  except PulseError:
                      pass
              elif isinstance(chan, MeasureChannel):
                  try:
                      measure_channels[chan] = EventsOutputChannels(t0, tf)
                  except PulseError:
                      pass
              elif isinstance(chan, ControlChannel):
                  try:
                      control_channels[chan] = EventsOutputChannels(t0, tf)
                  except PulseError:
                      pass
              elif isinstance(chan, AcquireChannel):
                  try:
                      acquire_channels[chan] = EventsOutputChannels(t0, tf)
                  except PulseError:
                      pass
              elif isinstance(chan, SnapshotChannel):
                  try:
                      snapshot_channels[chan] = EventsOutputChannels(t0, tf)
                  except PulseError:
                      pass
  
          output_channels = {**drive_channels, **measure_channels,
                             **control_channels, **acquire_channels}
          channels = {**output_channels, **snapshot_channels}
          # sort by index then name to group qubits together.
          output_channels = collections.OrderedDict(sorted(output_channels.items(),
                                                           key=lambda x: (x[0].index, x[0].name)))
          channels = collections.OrderedDict(sorted(channels.items(),
                                                    key=lambda x: (x[0].index, x[0].name)))
  
          for start_time, instruction in schedule.instructions:
              for channel in instruction.channels:
                  if channel in output_channels:
                      output_channels[channel].add_instruction(start_time, instruction)
                  elif channel in snapshot_channels:
                      snapshot_channels[channel].add_instruction(start_time, instruction)
          return channels, output_channels, snapshot_channels
  
      @staticmethod
      def _scale_channels(output_channels: Dict[Channel, EventsOutputChannels],
                          scale: float,
                          channel_scales: Dict[Channel, float] = None,
                          channels: List[Channel] = None,
                          plot_all: bool = False) -> Dict[Channel, float]:
          """Count number of channels that contains any instruction to show
          and find scale factor of that channel.
  
          Args:
              output_channels: Event table of channels to show.
              scale: Global scale factor.
              channel_scales: Channel specific scale factors.
              channels: Specified channels to plot.
              plot_all: Plot empty channel.
  
          Returns:
              scale_dict: Scale factor of each channel.
          """
          # count numbers of valid waveform
          scale_dict = {chan: 0 for chan in output_channels.keys()}
          for channel, events in output_channels.items():
              v_max = 0
              if channels:
                  if channel in channels:
                      waveform = events.waveform
                      v_max = max(v_max,
                                  max(np.abs(np.real(waveform))),
                                  max(np.abs(np.imag(waveform))))
                      events.enable = True
              else:
                  if not events.is_empty() or plot_all:
                      waveform = events.waveform
                      v_max = max(v_max,
                                  max(np.abs(np.real(waveform))),
                                  max(np.abs(np.imag(waveform))))
                      events.enable = True
  
              scale_val = channel_scales.get(channel, scale)
              if not scale_val:
                  # when input schedule is empty or comprises only frame changes,
                  # we need to overwrite maximum amplitude by a value greater than zero,
                  # otherwise auto axis scaling will fail with zero division.
                  v_max = v_max or 1
                  scale_dict[channel] = 1 / v_max
              else:
                  scale_dict[channel] = scale_val
  
          return scale_dict
  
      def _draw_table(self, figure,
                      channels: Dict[Channel, EventsOutputChannels],
                      dt: float):
          """Draw event table if events exist.
  
          Args:
              figure (matpotlib.figure.Figure): Figure object
              channels: Dictionary of channel and event table
              dt: Time interval
  
          Returns:
              Tuple[matplotlib.axes.Axes]: Axis objects for table and canvas of pulses.
          """
          # create table
          table_data = []
          if self.style.use_table:
              for channel, events in channels.items():
                  if events.enable:
                      table_data.extend(events.to_table(channel.name))
              table_data = sorted(table_data, key=lambda x: x[0])
  
          # plot table
          if table_data:
              # table area size
              ncols = self.style.table_columns
              nrows = int(np.ceil(len(table_data)/ncols))
              max_size = self.style.max_table_ratio * figure.get_size_inches()[1]
              max_rows = np.floor(max_size/self.style.fig_unit_h_table/ncols)
              nrows = int(min(nrows, max_rows))
              # don't overflow plot with table data
              table_data = table_data[:int(nrows*ncols)]
              # fig size
              h_table = nrows * self.style.fig_unit_h_table
              h_waves = (figure.get_size_inches()[1] - h_table)
  
              # create subplots
              gs = gridspec.GridSpec(2, 1, height_ratios=[h_table, h_waves], hspace=0)
              tb = plt.subplot(gs[0])
              ax = plt.subplot(gs[1])
  
              # configure each cell
              tb.axis('off')
              cell_value = [['' for _kk in range(ncols * 3)] for _jj in range(nrows)]
              cell_color = [self.style.table_color * ncols for _jj in range(nrows)]
              cell_width = [*([0.2, 0.2, 0.5] * ncols)]
              for ii, data in enumerate(table_data):
                  # pylint: disable=unbalanced-tuple-unpacking
                  r, c = np.unravel_index(ii, (nrows, ncols), order='f')
                  # pylint: enable=unbalanced-tuple-unpacking
                  time, ch_name, data_str = data
                  # item
                  cell_value[r][3 * c + 0] = 't = %s' % time * dt
                  cell_value[r][3 * c + 1] = 'ch %s' % ch_name
                  cell_value[r][3 * c + 2] = data_str
              table = tb.table(cellText=cell_value,
                               cellLoc='left',
                               rowLoc='center',
                               colWidths=cell_width,
                               bbox=[0, 0, 1, 1],
                               cellColours=cell_color)
              table.auto_set_font_size(False)
              table.set_fontsize = self.style.table_font_size
          else:
              tb = None
              ax = figure.add_subplot(111)
  
          return tb, ax
  
      @staticmethod
      def _draw_snapshots(ax,
                          snapshot_channels: Dict[Channel, EventsOutputChannels],
                          y0: float) -> None:
          """Draw snapshots to given mpl axis.
  
          Args:
              ax (matplotlib.axes.Axes): axis object to draw snapshots.
              snapshot_channels: Event table of snapshots.
              y0: vertical position to draw the snapshots.
          """
          for events in snapshot_channels.values():
              snapshots = events.snapshots
              if snapshots:
                  for time in snapshots:
                      ax.annotate(s=u"\u25D8", xy=(time, y0), xytext=(time, y0+0.08),
                                  arrowprops={'arrowstyle': 'wedge'}, ha='center')
  
      def _draw_framechanges(self, ax,
                             fcs: Dict[int, FrameChangeInstruction],
                             y0: float) -> bool:
          """Draw frame change of given channel to given mpl axis.
  
          Args:
              ax (matplotlib.axes.Axes): axis object to draw frame changes.
              fcs: Event table of frame changes.
              y0: vertical position to draw the frame changes.
          """
          for time in fcs.keys():
              ax.text(x=time, y=y0, s=r'$\circlearrowleft$',
                      fontsize=self.style.icon_font_size,
                      ha='center', va='center')
  
      def _draw_frequency_changes(self, ax,
                                  sf: Dict[int, SetFrequency],
                                  y0: float) -> bool:
          """Draw set frequency of given channel to given mpl axis.
  
          Args:
              ax (matplotlib.axes.Axes): axis object to draw frame changes.
              sf: Event table of set frequency.
              y0: vertical position to draw the frame changes.
          """
          for time in sf.keys():
              ax.text(x=time, y=y0, s=r'$\leftrightsquigarrow$',
                      fontsize=self.style.icon_font_size,
                      ha='center', va='center', rotation=90)
  
      def _get_channel_color(self, channel: Channel) -> str:
          """Lookup table for waveform color.
  
          Args:
              channel: Type of channel.
  
          Return:
              Color code or name of color.
          """
          # choose color
          if isinstance(channel, DriveChannel):
              color = self.style.d_ch_color
          elif isinstance(channel, ControlChannel):
              color = self.style.u_ch_color
          elif isinstance(channel, MeasureChannel):
              color = self.style.m_ch_color
          elif isinstance(channel, AcquireChannel):
              color = self.style.a_ch_color
          else:
              color = 'black'
          return color
  
      @staticmethod
      def _prev_label_at_time(prev_labels: List[Dict[int, Union[Waveform, Acquire]]],
                              time: int) -> bool:
          """Check overlap of pulses with pervious channels.
  
          Args:
              prev_labels: List of labels in previous channels.
              time: Start time of current pulse instruction.
  
          Returns:
              `True` if current instruction overlaps with others.
          """
          for labels in prev_labels:
              for t0, (tf, _) in labels.items():
                  if time in (t0, tf):
                      return True
          return False
  
      def _draw_labels(self, ax,
                       labels: Dict[int, Union[Waveform, Acquire]],
                       prev_labels: List[Dict[int, Union[Waveform, Acquire]]],
                       y0: float) -> None:
          """Draw label of pulse instructions on given mpl axis.
  
          Args:
              ax (matplotlib.axes.Axes): axis object to draw labels.
              labels: Pulse labels of channel.
              prev_labels: Pulse labels of previous channels.
              y0: vertical position to draw the labels.
          """
          for t0, (tf, cmd) in labels.items():
              if isinstance(cmd, PersistentValue):
                  name = cmd.name if cmd.name else 'pv'
              elif isinstance(cmd, Acquire):
                  name = cmd.name if cmd.name else 'acquire'
              else:
                  name = cmd.name
  
              ax.annotate(r'%s' % name,
                          xy=((t0+tf)//2, y0),
                          xytext=((t0+tf)//2, y0-0.07),
                          fontsize=self.style.label_font_size,
                          ha='center', va='center')
  
              linestyle = self.style.label_ch_linestyle
              alpha = self.style.label_ch_alpha
              color = self.style.label_ch_color
  
              if not self._prev_label_at_time(prev_labels, t0):
                  ax.axvline(t0, -1, 1, color=color,
                             linestyle=linestyle, alpha=alpha)
              if not (self._prev_label_at_time(prev_labels, tf) or tf in labels):
                  ax.axvline(tf, -1, 1, color=color,
                             linestyle=linestyle, alpha=alpha)
  
      def _draw_channels(self, ax,
                         output_channels: Dict[Channel, EventsOutputChannels],
                         interp_method: Callable,
                         t0: int, tf: int,
                         scale_dict: Dict[Channel, float],
                         label: bool = False,
                         framechange: bool = True,
                         frequencychange: bool = True) -> float:
          """Draw pulse instructions on given mpl axis.
  
          Args:
              ax (matplotlib.axes.Axes): axis object to draw pulses.
              output_channels: Event table of channels.
              interp_method: Callback function for waveform interpolation.
              t0: Start time of schedule.
              tf: End time of schedule.
              scale_dict: Scale factor for each channel.
              label: When set `True` draw labels.
              framechange: When set `True` draw frame change symbols.
              frequencychange: When set `True` draw frequency change symbols.
  
          Return:
              Value of final vertical axis of canvas.
          """
          y0 = 0
          prev_labels = []
          for channel, events in output_channels.items():
              if events.enable:
                  # scaling value of this channel
                  scale = 0.5 * scale_dict.get(channel, 0.5)
                  # plot waveform
                  waveform = events.waveform
                  time = np.arange(t0, tf + 1, dtype=float)
                  if waveform.any():
                      time, re, im = interp_method(time, waveform, self.style.num_points)
                  else:
                      # when input schedule is empty or comprises only frame changes,
                      # we should avoid interpolation due to lack of data points.
                      # instead, it just returns vector of zero.
                      re, im = np.zeros_like(time), np.zeros_like(time)
                  color = self._get_channel_color(channel)
                  # Minimum amplitude scaled
                  amp_min = scale * abs(min(0, np.nanmin(re), np.nanmin(im)))
                  # scaling and offset
                  re = scale * re + y0
                  im = scale * im + y0
                  offset = np.zeros_like(time) + y0
                  # plot
                  ax.fill_between(x=time, y1=re, y2=offset,
                                  facecolor=color[0], alpha=0.3,
                                  edgecolor=color[0], linewidth=1.5,
                                  label='real part')
                  ax.fill_between(x=time, y1=im, y2=offset,
                                  facecolor=color[1], alpha=0.3,
                                  edgecolor=color[1], linewidth=1.5,
                                  label='imaginary part')
                  ax.plot((t0, tf), (y0, y0), color='#000000', linewidth=1.0)
  
                  # plot frame changes
                  fcs = events.framechanges
                  if fcs and framechange:
                      self._draw_framechanges(ax, fcs, y0)
                  # plot frequency changes
                  sf = events.frequencychanges
                  if sf and frequencychange:
                      self._draw_frequency_changes(ax, sf, y0 + scale)
                  # plot labels
                  labels = events.labels
                  if labels and label:
                      self._draw_labels(ax, labels, prev_labels, y0)
                  prev_labels.append(labels)
  
              else:
                  continue
  
              # plot label
              ax.text(x=t0, y=y0, s=channel.name,
                      fontsize=self.style.axis_font_size,
                      ha='right', va='center')
              # show scaling factor
              ax.text(x=t0, y=y0 - 0.1, s='x%.1f' % (2 * scale),
                      fontsize=0.7*self.style.axis_font_size,
                      ha='right', va='top')
  
              # change the y0 offset for removing spacing when a channel has negative values
              if self.style.remove_spacing:
                  y0 -= 0.5 + amp_min
              else:
                  y0 -= 1
          return y0
  
      def draw(self, schedule: ScheduleComponent,
               dt: float, interp_method: Callable,
               plot_range: Tuple[Union[int, float], Union[int, float]],
               scale: float = None,
               channel_scales: Dict[Channel, float] = None,
               plot_all: bool = True, table: bool = False,
               label: bool = False, framechange: bool = True,
               scaling: float = None, channels: List[Channel] = None,
               show_framechange_channels: bool = True):
          """Draw figure.
  
          Args:
              schedule: schedule object to plot.
              dt: Time interval of samples. Pulses are visualized in the unit of
                  cycle time if not provided.
              interp_method: Interpolation function. See example.
                  Interpolation is disabled in default.
                  See `qiskit.visualization.pulse.interpolation` for more information.
              plot_range: A tuple of time range to plot.
              scale: Scaling of waveform amplitude. Pulses are automatically
                  scaled channel by channel if not provided.
              channel_scales: Dictionary of scale factor for specific channels.
                  Scale of channels not specified here is overwritten by `scale`.
              plot_all: When set `True` plot empty channels.
              table: When set `True` draw event table for supported commands.
              label: When set `True` draw label for individual instructions.
              framechange: When set `True` draw framechange indicators.
              scaling: Deprecated, see `scale`.
              channels: A list of channel names to plot.
                  All non-empty channels are shown if not provided.
              show_framechange_channels: When set `True` plot channels
                  with only framechange instructions.
  
          Returns:
              matplotlib.figure.Figure: A matplotlib figure object for the pulse envelope.
  
          Raises:
              VisualizationError: When schedule cannot be drawn
          """
          if scaling is not None:
              warnings.warn('The parameter "scaling" is being replaced by "scale"',
                            DeprecationWarning, 3)
              scale = scaling
          figure = plt.figure(dpi=self.style.dpi, figsize=self.style.figsize)
  
          if channels is None:
              channels = []
          interp_method = interp_method or step_wise
  
          if channel_scales is None:
              channel_scales = {}
  
          # setup plot range
          if plot_range:
              t0 = int(np.floor(plot_range[0]))
              tf = int(np.floor(plot_range[1]))
          else:
              t0 = 0
              # when input schedule is empty or comprises only frame changes,
              # we need to overwrite pulse duration by an integer greater than zero,
              # otherwise waveform returns empty array and matplotlib will be crashed.
              if channels:
                  tf = schedule.ch_duration(*channels)
              else:
                  tf = schedule.stop_time
              tf = tf or 1
  
          # prepare waveform channels
          (schedule_channels, output_channels,
           snapshot_channels) = self._build_channels(schedule, channels, t0, tf,
                                                     show_framechange_channels)
  
          # count numbers of valid waveform
          scale_dict = self._scale_channels(output_channels,
                                            scale=scale,
                                            channel_scales=channel_scales,
                                            channels=channels,
                                            plot_all=plot_all)
  
          if table:
              tb, ax = self._draw_table(figure, schedule_channels, dt)
          else:
              tb = None
              ax = figure.add_subplot(111)
  
          ax.set_facecolor(self.style.bg_color)
  
          y0 = self._draw_channels(ax, output_channels, interp_method,
                                   t0, tf, scale_dict, label=label,
                                   framechange=framechange)
  
          y_ub = 0.5 + self.style.vertical_span
          y_lb = y0 + 0.5 - self.style.vertical_span
  
          self._draw_snapshots(ax, snapshot_channels, y_lb)
  
          ax.set_xlim(t0, tf)
          tick_labels = np.linspace(t0, tf, 5)
          ax.set_xticks(tick_labels)
          ax.set_xticklabels([self.style.axis_formatter % label for label in tick_labels * dt],
                             fontsize=self.style.axis_font_size)
          ax.set_ylim(y_lb, y_ub)
          ax.set_yticklabels([])
  
          if tb is not None:
              bbox = tb.get_position()
          else:
              bbox = ax.get_position()
  
          # This check is here for backwards compatibility. Before, the check was around
          # the suptitle line, however since the font style can take on a type of None
          # we need to unfortunately check both the type and the value of the object.
          if isinstance(self.style.title_font_size, int) and self.style.title_font_size > 0:
              figure.suptitle(str(schedule.name),
                              fontsize=self.style.title_font_size,
                              y=bbox.y1 + 0.02,
                              va='bottom')
  
          return figure
  
apis: numpy.zeros_like numpy.ones numpy.zeros matplotlib.pyplot.figure numpy.floor numpy.abs numpy.exp numpy.nanmin numpy.arange numpy.imag matplotlib.pyplot.subplot matplotlib.gridspec.GridSpec numpy.linspace numpy.real numpy.unravel_index