class_docstr: Class for textflint bar chart Report.
  Code from https://github.com/robustness-gym/robustness-gym
class_docstr_tok: ["Class","for","textflint","bar","chart","Report",".","Code","from","https",":","\/\/github.com\/robustness-gym\/robustness-gym"]
code: def figure(self, show_title=False):
      # Verify that rows are grouped by category
      row_categories = self.data[0].tolist()
      # Previous category groupings already encountered
      save_cat_groups = set()
      prev_cat = None
      # Loop through each row and see
      # if a category is encountered outside of first
      # identified group for that category
      for cat in row_categories:
          if cat != prev_cat:  # category changes
              # if new category previously encountered
              if cat in save_cat_groups:
                  raise ValueError("Rows must be grouped by category.")
              prev_cat = cat
              save_cat_groups.add(cat)
  
      categories = []
      category_sizes = []  # Num rows in each category
      # column 0 is category
      for category, group in itertools.groupby(self.data[0]):
          categories.append(category)
          category_sizes.append(len(list(group)))
      n_rows = sum(category_sizes)
      height = (
          n_rows * self.config["row_height"]
          + len(categories) * self.config["category_padding"]
          + self.config["header_padding"]
      )
      col_widths = []
      for col in self.columns:
          if isinstance(col, ScoreColumn):
              col_width = self.config["score_col_width"]
          else:
              col_width = self.config["numeric_col_width"]
          col_widths.append(col_width)
  
      fig = make_subplots(
          rows=len(categories),
          row_titles=categories,
          cols=len(self.columns),
          shared_yaxes=True,
          subplot_titles=[col.title for col in self.columns],
          horizontal_spacing=self.config["col_spacing"],
          vertical_spacing=self.config["category_padding"] / height,
          row_width=list(reversed(category_sizes)),
          column_width=col_widths,
      )
  
      hms = []
      coords = []
      category_ndx = 1
      # Group data by category
      for category, category_data in self.data.groupby(0, sort=False):
          score_col_ndx = 0
          slice_names = category_data[1]
          slice_names = [s + " " * 3 for s in slice_names]
          for col_ndx, col in enumerate(self.columns):
              df_col_ndx = col_ndx + 2
              # Dataframe has two leading columns with category, slice
              fig_col_ndx = col_ndx + 1  # figure columns are 1-indexed
              x = category_data[df_col_ndx].tolist()
              if isinstance(col, ScoreColumn):
                  if col.is_0_to_1:
                      x = [100 * x_i for x_i in x]
                  col_max = col.max_val
                  if col.is_0_to_1:
                      col_max = 100 * col.max_val
                  fig.add_trace(
                      go.Bar(
                          x=x,
                          y=slice_names,
                          orientation="h",
                          marker=dict(
                              color=self.get_color(score_col_ndx)
                          ),
                          showlegend=False,
                          text=[f"{x_i:.1f}" for x_i in x],
                          textposition="inside",
                          width=0.95,
                          textfont=dict(color="white"),
                      ),
                      row=category_ndx,
                      col=fig_col_ndx,
                  )
                  # Add marker for gray fill
                  fig.add_trace(
                      go.Bar(
                          x=[col_max - x_i for x_i in x],
                          y=slice_names,
                          orientation="h",
                          marker=dict(
                              color=self.config[
                                  "score_color_complement"
                              ]
                          ),
                          showlegend=False,
                          width=0.9,
                      ),
                      row=category_ndx,
                      col=fig_col_ndx,
                  )
                  score_col_ndx += 1
              elif isinstance(col, NumericColumn):
                  # Repurpose bar chart as text field.
                  fig.add_trace(
                      go.Bar(
                          x=[1] * len(x),
                          y=slice_names,
                          orientation="h",
                          marker=dict(
                              color=self.config["text_fill_color"],
                              line=dict(
                                  width=0,
                                  color=self.config[
                                      "text_border_color"
                                  ],
                              ),
                          ),
                          showlegend=False,
                          text=[human_format(x_i) for x_i in x],
                          textposition="inside",
                          insidetextanchor="middle",
                          width=0.9,
                      ),
                      row=category_ndx,
                      col=fig_col_ndx,
                  )
              else:
                  raise ValueError("Invalid col type")
          category_ndx += 1
  
      for category_ndx in range(1, len(categories) + 1):
          if category_ndx == len(categories):
              show_x_axis = True
          else:
              show_x_axis = False
          for col_ndx, col in enumerate(self.columns):
              # plotly cols are 1-indexed
              fig_col_ndx = col_ndx + 1
              fig.update_yaxes(autorange="reversed", automargin=True)
              if isinstance(col, ScoreColumn):
                  if col.is_0_to_1:
                      col_min, col_max = (
                          100 * col.min_val,
                          100 * col.max_val,
                      )
                  else:
                      col_min, col_max = col.min_val, col.max_val
  
                  fig.update_xaxes(
                      range=[col_min, col_max],
                      row=category_ndx,
                      col=fig_col_ndx,
                      tickvals=[col_min, col_max],
                      showticklabels=show_x_axis,
                  )
              elif isinstance(col, NumericColumn):
                  fig.update_xaxes(
                      range=[0, 1],
                      row=category_ndx,
                      col=fig_col_ndx,
                      showticklabels=False,
                  )
  
      fig.update_layout(
          height=height,
          width=self.config["layout_width"],
          barmode="stack",
          plot_bgcolor="rgba(0, 0, 0, 0)",
          paper_bgcolor="rgba(0, 0, 0, 0)",
          font=dict(size=self.config["font_size_data"]),
          yaxis={"autorange": "reversed"},
          margin=go.layout.Margin(
              # right margin  # bottom margin  # top margin
              r=0,
              b=0,
              t=20,
          ),
      )
  
      # Use low-level plotly interface to update padding / font size
      for a in fig["layout"]["annotations"]:
          # If label for group
          if a["text"] in categories:
              a["x"] = 0.99  # Add padding
              a["font"] = dict(size=self.config["font_size_category"])
          else:
              a["font"] = dict(
                  size=self.config["font_size_heading"]
              )  # Adjust font size for non-category labels
  
      # Due to a quirk in plotly, need to do some special low-level coding
      # Code from https://community.plotly.com/t/how-to-create-
      # annotated-heatmaps-in-subplots/36686/25
      newfont = [
          go.layout.Annotation(
              font_size=self.config["font_size_heading"]
          )
      ] * len(fig.layout.annotations)
      fig_annots = [newfont] + [hm.layout.annotations for hm in hms]
      for col_ndx in range(1, len(fig_annots)):
          for k in range(len(fig_annots[col_ndx])):
              coord = coords[col_ndx - 1]
              fig_annots[col_ndx][k]["xref"] = f"x{coord}"
              fig_annots[col_ndx][k]["yref"] = f"y{coord}"
              fig_annots[col_ndx][k]["font_size"] = self.config[
                  "font_size_dist"
              ]
  
      def recursive_extend(mylist, nr):
          # mylist is a list of lists
          result = []
          if nr == 1:
              result.extend(mylist[nr - 1])
          else:
              result.extend(mylist[nr - 1])
              result.extend(recursive_extend(mylist, nr - 1))
          return result
  
      new_annotations = recursive_extend(
          fig_annots[::-1], len(fig_annots)
      )
      fig.update_layout(annotations=new_annotations)
  
      if show_title:
          title = {
              "text": f"{self.dataset_name or ''} {self.model_name or ''} "
              f"Robustness Report",
              "x": 0.5,
              "xanchor": "center",
          }
      else:
          title = None
      fig.update_layout(
          title=title,
          margin=go.layout.Margin(
              r=0,
              b=0,
              t=80,  # right margin  # bottom margin  # top margin
          ),
      )
  
      return fig
  
imports: from __future__ import annotations
  import itertools
  from functools import partial
  from typing import Dict, List
  import dill
  import numpy
  import plotly.graph_objects
  from plotly.subplots import make_subplots
  import plotly.express
  from ..analyzer.analyzer import *
label_desc: Use image node_id 12 for calling the BarChart obj's underlying member method code with example usage: obj.figure(show_title) and returns: fig, result