% Karnaugh diagram
% Author: Uwe Zimmermann
\documentclass[x11names,border=10pt,tikz]{standalone}
\usepackage{xstring}    % needed for string manipulations
\usepackage{fmtcount}   % needed for some binary printing
\usetikzlibrary{calc,math}
\newif\ifKaddress
\newif\ifKInd
\pgfkeys{
  /K/.is family, /K,
  x bits/.estore in              = \KXvars,     % bits along the horizontal axis
  y bits/.estore in              = \KYvars,     % bits along the vertical axis
  variable names/.estore in      = \KVars,      % comma-separated variable names
  label/.estore in               = \KLabel,     % label for the diagram
  label scale/.estore in         = \KLabelscale,% scale factor for values
  value scale/.estore in         = \KValscale,  % scale factor for values
  variable scale/.estore in      = \KVarscale,  % scale factor for variables
  variable sep/.estore in        = \KVarsep,    % inner sep for variables
  address scale/.estore in       = \KAddrscale, % scale factor for adresses
  address sep/.estore in         = \KAddrsep,   % inner sep for adresses
  group distance/.estore in      = \KGdist,     % distance for grouping lines
  group color/.estore in         = \KGcolor,    % grouping color
  group linewidth/.estore in     = \KGwidth,    % grouping line width
  group rounded/.estore in       = \KGrounded,  % grouping rounded corner radius
  group opacity/.estore in       = \KGopacity,  % grouping field opacity
  indicator distance/.estore in  = \KInddist,   % variable indicator distance
  indicator linewidth/.estore in = \KIndwidth,  % line width for indicators
  indicator scale/.estore in     = \KIndscale,  % scale factor for indicators
  indicator sep/.estore in       = \KIndsep,    % inner sep for indicators
  plot addresses/.is if=Kaddress,               % plot address fields
  plot indicators/.is if=KInd,                  % plot variable indicators
}
\pgfkeys{
  /K,
  default/.style = { plot addresses      = true, 
                     plot indicators     = true,
                     x bits              = 2, 
                     y bits              = 2,
                     address scale       = 0.5,
                     address sep         = 1pt,
                     variable names      = {$A$,$B$,$C$,$D$,$E$,$F$,$G$}, 
                     label               = $X$,
                     label scale         = 1.4, 
                     variable scale      = 0.7,
                     variable sep        = 2pt,
                     value scale         = 1.5,
                     group color         = red,
                     group rounded       = 2pt,
                     group linewidth     = .3mm,
                     group opacity       = 0.10,
                     group distance      = 0.4,
                     indicator distance  = 0.45,
                     indicator linewidth = .4mm,
                     indicator scale     = 1.0,
                     indicator sep       = 2pt,
                    }
}
\tikzset{
  Kcorners/.style = { rounded corners = \KGrounded },
     Kline/.style = { Kcorners, draw = \KGcolor, line width = \KGwidth },
      KInd/.style = { draw = black, line width = \KIndwidth },
     Kfill/.style = { Kcorners, fill = \KGcolor, opacity = \KGopacity },
     Krect/.style = { Kcorners, Kfill },
     right/.style = { black, anchor = west }, % nodes at right side
      left/.style = { black, anchor = east }, % nodes at left side
}
\let\ol\overline% just to abbreviate

%==============================================================================
% K[options]{table}
%
% takes the address,value pairs from the comma-separated list {table} and
% draws up the corresponding K diagram.
% the table does not need to be sorted, nor to be complete
%
\newcommand{\K}[2][]
{%
  \pgfkeys{/K, default, #1}%
  \def\newstr{#2}
  \tikzmath
  {
    function fromgray(\gcode,\bits) %
    {
      int \x, \pos, \leftbit, \twop;
      \leftbit = 0;
      \pos = 0;
      for \x in {0,...,\bits-1}%
      {
        \twop = 2^(\bits - \x -1);
        if \gcode >= \twop then %
        { % gray code is 1
          if \leftbit == 0 then %
          {
            \pos = \pos + \twop;
            \leftbit = 1;
          } else
          {
            \leftbit = 0;
          };
          \gcode = \gcode - \twop;
        } else
        { % gray code is 0
          if \leftbit == 0 then %
          {
            \pos = \pos;
          } else
          {
            \pos = \pos + \twop;
            \leftbit = 1;
          };
        };
      };
      return \pos;
    };
    %
    int \xbits, \ybits, \fields, \vars;
    \xbits  = \KXvars;
    \ybits  = \KYvars;
    \vars   = \xbits+\ybits;
    \fields = 2^(\vars);
  }
  \StrCut[\xbits]{\KVars}{,}{\xlabels}{\ylabels}
  \StrBefore[\ybits]{\ylabels}{,}[\ylabels]
  \StrSubstitute[0]{\xlabels}{,}{}[\xlabels]
  \StrSubstitute[0]{\ylabels}{,}{}[\ylabels]
  \path ($(0,0.5)+\KVarscale*(0,2ex)$) node[anchor = south,
    inner sep = \KVarsep, scale = \KVarscale] {\ttfamily\xlabels};
  \path (-0.5,0.5) node[anchor = north east,
    inner sep = \KVarsep, scale = \KVarscale] {\ttfamily\ylabels};
  \foreach [remember = \newstr as \workstr (initially \newstr)]
    \i in {1,...,\fields} {%
    \StrCut[2]{\workstr}{,}{\nowstr}{\newstr}
    \StrLen{\newstr}[\len]
    \ifthenelse{\equal{\len}{0}}{\def\newstr{,,}}{}%
    \StrLen{\nowstr}[\len]
    \ifthenelse{\equal{\len}{1}}{}%
    {%
      \StrCut[1]{\nowstr}{,}{\addrstr}{\valuestr}
      \tikzmath{
        int \xpos, \ypos, \xaddr, \yaddr, \addr;
        \addr = 0b\addrstr;
        \xaddr = int(\addr / 2^\ybits);
        \yaddr = \addr - \xaddr * 2^\ybits;
        \xpos = fromgray(\xaddr,\xbits);
        \ypos = fromgray(\yaddr,\ybits);
      } % end tikzmath
      % place field value
      \path ($(\xpos,-\ypos)$) node[anchor=center,
        inner sep = 0pt, scale = \KValscale](F\addrstr){\valuestr};
    }%
  }
  \tikzmath
  {
    int \x, \y, \xmax, \ymax, \value;
    \xmax = 2^\xbits - 1;
    \ymax = 2^\ybits - 1;
    { \draw ($(-0.5,0.5)$) -- ($(-0.5,-\ymax-0.5)$); };
    for \x in {0,...,\xmax}%
    {
      \xpos = fromgray(\x,\xbits);
      {
        \path ($(\xpos,0.5)$) node[anchor = south, black, inner sep = 2pt,
          scale = \KVarscale]{\ttfamily\padzeroes[\xbits]\binarynum{\x}};
        \draw ($(\xpos+0.5,0.5)$) -- ($(\xpos+0.5,-\ymax-0.5)$);
      };
    };
    { \draw ($(-0.5,+0.5)$) -- ($(\xmax+0.5,+0.5)$); };
    for \y in {0,...,\ymax}%
    {
      \ypos = fromgray(\y,\ybits);
      {
        \path ($(-0.5,-\ypos)$) node[anchor=east, black, inner sep = 2pt,
          scale = \KVarscale] {\ttfamily\padzeroes[\ybits]\binarynum{\y}};
        \draw ($(-0.5,-\ypos-0.5)$) -- ($(\xmax+0.5,-\ypos-0.5)$);
      };
    };
  }
  \ifKaddress
  \tikzmath
  {
    int \x, \y, \xmax, \ymax, \value;
    \xmax = 2^\xbits - 1;
    \ymax = 2^\ybits - 1;
    for \x in {0,...,\xmax}%
    {
      \xpos = fromgray(\x,\xbits);
      for \y in {0,...,\ymax}%
      {
        \ypos = fromgray(\y,\ybits);
        { %
            \path ($(\xpos,-\ypos)+(0.5,-0.5)$) node[anchor = south east,
              inner sep = \KAddrsep, blue, scale = \KAddrscale]
              {\ttfamily\padzeroes[\xbits]%
               \binarynum{\x}\padzeroes[\ybits]\binarynum{\y}};
        };
      };
    };
  }
  \fi
  \ifKInd
  \tikzmath%
  {%
    int \i, \il, \j, \n;
    real \x,\xmax,\y,\ymax;
    for \i in {1,...,\xbits}%
    {
      \n = 2^(\i-2);
      \il = \i+1;  
      if \n < 1 then {\n=1;};
      \y = 0.5 + \KInddist*(\i);
      for \j in {1,...,\n}%
      {
        \x = -0.5 + 2^(\xbits-\i) + (\j-1)*2^(\xbits-\i+2);
        \xmax = \x + 2^(\xbits-\i+1);
        if \xmax > (2^(\xbits)-0.5) then {\xmax=2^(\xbits)-0.5;};
        {
          \StrBetween[\i,\numexpr\i+1]{,\KVars,}{,}{,}[\tlabel] 
          \path [KInd] (\x,\y) -- (\xmax,\y) node[anchor = south east,
               inner sep = \KIndsep, scale = \KIndscale] {\tlabel};
          \path [KInd] (\x,\y)++(0,-0.1)    -- ++(0,+0.2);
          \path [KInd] (\xmax,\y)++(0,-0.1) -- ++(0,+0.2);
        };
      };
    };
    for \i in {1,...,\ybits}%
    {
      \n = 2^(\i-2);
      if \n < 1 then {\n=1;};
      \x = -0.5 - \KInddist*(\i-1);
      for \j in {1,...,\n}%
      {
        \y = -0.5 + 2^(\ybits-\i) + (\j-1)*2^(\ybits-\i+2);
        \ymax = \y + 2^(\ybits-\i+1);
        if \ymax > (2^(\ybits)-0.5) then {\ymax=2^(\ybits)-0.5;};
        {
          \StrBetween[\numexpr\i+\xbits,\numexpr\i+\xbits+1]
            {,\KVars,}{,}{,}[\tlabel]
          \path [KInd] ($(\x,-\y)-\ybits*(1.4ex,0)$)
            -- ($(\x,-\ymax)-\ybits*(1.4ex,0)$) node[anchor=south west, 
                rotate=90, inner sep=\KIndsep, scale=\KIndscale]{\tlabel};
          \path [KInd] ($(\x,-\y)-\ybits*(1.4ex,0)$)++(-0.1,0) -- ++(+0.2,0);
          \path [KInd] ($(\x,-\ymax)-\ybits*(1.4ex,0)$)++(-0.1,0) -- ++(+0.2,0);
        };
      };
    };
  }
  \fi
  \path[draw = black] (-0.5,0.5) -- ++(-0.5,0.5) node[anchor = south east,
    inner sep = 2pt, scale = \KLabelscale] {\KLabel};
}

%==============================================================================
% KG[options]{from}{to}
%
% groups together the fields between {from} and {to} by drawing a frame around 
% these fields.
% if the {from} field is above or right of the {to} field, the frame is
% extended around the edge of the diagram.
%
\newcommand{\KG}[3][] % options from to
{%
  \pgfkeys{/K, default, #1}%
  \tikzmath
  {
    int \xbits, \ybits, \fields, \vars;
    \xbits = \KXvars;
    \ybits = \KYvars;
    \vars  = \xbits+\ybits;
    \fields = 2^(\vars);
  }
  \tikzmath{
    int \xfrompos, \yfrompos, \xfrom, \yfrom, \fromaddr;
    int \xtopos, \ytopos, \xto, \yto, \toaddr;
    \fromaddr = 0b#2;
    \toaddr   = 0b#3;
    \xfrom    = int(\fromaddr / 2^\ybits);
    \yfrom    = \fromaddr - \xfrom * 2^\ybits;
    \xto      = int(\toaddr / 2^\ybits);
    \yto      = \toaddr - \xto * 2^\ybits;
    \xfrompos = fromgray(\xfrom,\xbits);
    \yfrompos = fromgray(\yfrom,\ybits);
    \xtopos   = fromgray(\xto,\xbits);
    \ytopos   = fromgray(\yto,\ybits);
    \xmax     = 2^\xbits-1;
    \ymax     = 2^\ybits-1;
  } % end tikzmath
  \tikzmath%
  {
    if (\xfrompos <= \xtopos) && (\yfrompos >= \ytopos) then %
    {
      { 
        \path[Kfill] ($(\xfrompos,-\yfrompos)-(\KGdist,\KGdist)$)
          rectangle ($(\xtopos,-\ytopos)+(\KGdist,\KGdist)$);
        \path[Kline] ($(\xfrompos,-\yfrompos)-(\KGdist,\KGdist)$)
          rectangle ($(\xtopos,-\ytopos)+(\KGdist,\KGdist)$);
      };
    };
    if (\xfrompos <= \xtopos) && (\yfrompos < \ytopos) then %
    {
      {
        \path[Krect] ($(\xfrompos,0)+(-\KGdist,+0.5)$)
          -- ($(\xfrompos,-\yfrompos)+(-\KGdist,-\KGdist)$)
          -- ($(\xtopos,-\yfrompos)+(\KGdist,-\KGdist)$)
          -- ($(\xtopos,0)+(\KGdist,+0.5)$) -- cycle;
        \path[Krect] ($(\xfrompos,-\ymax)+(-\KGdist,-0.5)$)
          -- ($(\xfrompos,-\ytopos)+(-\KGdist,+\KGdist)$)
          -- ($(\xtopos,-\ytopos)+(\KGdist,+\KGdist)$)
          -- ($(\xtopos,-\ymax)+(\KGdist,-0.5)$) -- cycle;
        \path[Kline] ($(\xfrompos,0)+(-\KGdist,+0.5)$)
          -- ($(\xfrompos,-\yfrompos)+(-\KGdist,-\KGdist)$)
          -- ($(\xtopos,-\yfrompos)+(\KGdist,-\KGdist)$)
          -- ($(\xtopos,0)+(\KGdist,+0.5)$);
        \path[Kline] ($(\xfrompos,-\ymax)+(-\KGdist,-0.5)$)
        -- ($(\xfrompos,-\ytopos)+(-\KGdist,+\KGdist)$)
        -- ($(\xtopos,-\ytopos)+(\KGdist,+\KGdist)$)
        -- ($(\xtopos,-\ymax)+(\KGdist,-0.5)$);
      };
    };
    if (\xfrompos > \xtopos) && (\yfrompos >= \ytopos) then %
    {
      {
        \path[Krect] ($(0,-\yfrompos)+(-0.5,-\KGdist)$)
          -- ($(\xtopos,-\yfrompos)+(+\KGdist,-\KGdist)$)
          -- ($(\xtopos,-\ytopos)+(\KGdist,+\KGdist)$)
          -- ($(0,-\ytopos)+(-0.5,+\KGdist)$) -- cycle;
        \path[Krect] ($(\xmax,-\yfrompos)+(+0.5,-\KGdist)$)
          -- ($(\xfrompos,-\yfrompos)+(-\KGdist,-\KGdist)$)
          -- ($(\xfrompos,-\ytopos)+(-\KGdist,+\KGdist)$)
          -- ($(\xmax,-\ytopos)+(+0.5,+\KGdist)$) -- cycle;
        \path[Kline] ($(0,-\yfrompos)+(-0.5,-\KGdist)$)
          -- ($(\xtopos,-\yfrompos)+(+\KGdist,-\KGdist)$)
          -- ($(\xtopos,-\ytopos)+(\KGdist,+\KGdist)$)
          -- ($(0,-\ytopos)+(-0.5,+\KGdist)$);
        \path[Kline] ($(\xmax,-\yfrompos)+(+0.5,-\KGdist)$)
          -- ($(\xfrompos,-\yfrompos)+(-\KGdist,-\KGdist)$)
          -- ($(\xfrompos,-\ytopos)+(-\KGdist,+\KGdist)$)
          -- ($(\xmax,-\ytopos)+(+0.5,+\KGdist)$);
      };
    };
    if (\xfrompos > \xtopos) && (\yfrompos < \ytopos) then %
    {
      {
        \path[Kfill] {[Kcorners] ($(0,-\yfrompos)+(-0.5,-\KGdist)$) 
          -- ($(\xtopos,-\yfrompos)+(+\KGdist,-\KGdist)$)
          -- ($(\xtopos,0)+(\KGdist,+0.5)$)}
          -- ($(0,0)+(-0.5,+0.5)$) -- cycle;
        \path[Kfill] {[Kcorners] ($(0,-\ytopos)+(-0.5,+\KGdist)$)
          -- ($(\xtopos,-\ytopos)+(+\KGdist,+\KGdist)$)
          -- ($(\xtopos,-\ymax)+(\KGdist,-0.5)$)}
          -- ($(0,-\ymax)+(-0.5,-0.5)$) -- cycle;
        \path[Kfill] {[Kcorners] ($(\xmax,-\yfrompos)+(+0.5,-\KGdist)$)
          -- ($(\xfrompos,-\yfrompos)+(-\KGdist,-\KGdist)$)
          -- ($(\xfrompos,0)+(-\KGdist,+0.5)$)}
          -- ($(\xmax,0)+(+0.5,+0.5)$) -- cycle;
        \path[Kfill] {[Kcorners] ($(\xmax,-\ytopos)+(+0.5,+\KGdist)$)
          -- ($(\xfrompos,-\ytopos)+(-\KGdist,+\KGdist)$)
          -- ($(\xfrompos,-\ymax)+(-\KGdist,-0.5)$)}
          -- ($(\xmax,-\ymax)+(+0.5,-0.5)$) -- cycle;
        \path[Kline] ($(0,-\yfrompos)+(-0.5,-\KGdist)$)
          -- ($(\xtopos,-\yfrompos)+(+\KGdist,-\KGdist)$)
          -- ($(\xtopos,0)+(\KGdist,+0.5)$);
        \path[Kline] ($(0,-\ytopos)+(-0.5,+\KGdist)$)
          -- ($(\xtopos,-\ytopos)+(+\KGdist,+\KGdist)$)
          -- ($(\xtopos,-\ymax)+(\KGdist,-0.5)$);
        \path[Kline] ($(\xmax,-\yfrompos)+(+0.5,-\KGdist)$)
          -- ($(\xfrompos,-\yfrompos)+(-\KGdist,-\KGdist)$)
          -- ($(\xfrompos,0)+(-\KGdist,+0.5)$);
        \path[Kline] ($(\xmax,-\ytopos)+(+0.5,+\KGdist)$)
          -- ($(\xfrompos,-\ytopos)+(-\KGdist,+\KGdist)$)
          -- ($(\xfrompos,-\ymax)+(-\KGdist,-0.5)$);
      };
    };
  }
  
}

\newcommand*\ul[2]{\tikz[baseline = (char.base)]{
  \node[inner sep = 2pt] (char) {#2};
  \draw[#1, line width = 2pt](char.south west) -- (char.south east);}}

\begin{document}
  \begin{tikzpicture}[x=1cm,y=1cm]
    \K[x bits = 3, y bits = 3,
       variable names = {$A_2$,$A_1$,$A_0$,$B_1$,$B_0$,$D$,}]
    { % some random truth table...
      000000,1,    010000,1,     100000,1,    110000,0,
      000001,0,    010001,0,     100001,0,    110001,0,
      000010,1,    010010,1,     100010,1,    110010,0,
      000011,1,    010011,0,     100011,1,    110011,0,
      000100,1,    010100,1,     100100,1,    110100,0,
      000101,0,    010101,0,     100101,0,    110101,0,
      000110,1,    010110,0,     100110,0,    110110,0,
      000111,1,    010111,?,     100111,0,    110111,0,
      001000,0,    011000,1,     101000,0,    111000,0,
      001001,X,    011001,0,     101001,0,    111001,0,
      001010,X,    011010,0,     101010,1,    111010,0,
      001011,0,    011011,0,     101011,1,    111011,0,
      001100,0,    011100,1,     101100,1,    111100,0,
      001101,0,    011101,0,     101101,1,    111101,0,
      001110,1,    011110,1,     101110,1,    111110,1,
      001111,1,    011111,1,     101111,1,    111111,1,
    }
    \newcommand*{\myKG}[4][0.1]{\KG[x bits = 3,y bits = 3,group opacity = #1,
                  #2]{#3}{#4}}
    \myKG     {group color = green,  group distance=0.45}{100000}{000100}
    \myKG     {group color = red,    group distance=0.45}{000111}{001110}
    \myKG     {group color = blue,   group distance=0.40}{000110}{001010}
    \myKG     {group color = olive,  group distance=0.35}{001111}{011110}
    \myKG     {group color = olive,  group distance=0.35}{111111}{101110}
    \myKG     {group color = brown,  group distance=0.35}{100010}{000011}
    \myKG     {group color = black,  group distance=0.45}{011000}{010100}
    \myKG     {group color = cyan,   group distance=0.45}{010010}{010010}
    \myKG     {group color = teal,   group distance=0.45}{101100}{101110}
    \myKG[0.0]{group color = yellow, group distance=0.40}{101111}{101011}
    \myKG[0.0]{group color = yellow, group distance=0.45}{101010}{100011}
    %=====================================================================
    % in picture comments
    %=====================================================================
    \begin{scope}[latex-,red]
      \draw (6.7,-1.55)  -- ++(2,1)  node[right] {grouping, opacity = 0};
      \draw (6.5,-6.0)   -- ++(2,1)  node[right] {grouping, opacity = 0.1};
      \draw (7.5,+0.95)  -- ++(2,1)  node[right] {variable \emph{indicator}};
      \draw (7.3,-4.35)  -- ++(2,1)  node[right] {field \emph{address}};
      \draw (7.25,+0.6)  -- ++(2,1)  node[right] {variable values};
      \draw (-1.5,+1.3)  -- ++(-2,1) node[left]  {diagram \emph{label}};
      \draw (-1.4,+0.35) -- ++(-2,1) node[left]  {variable names};
      \draw (-0.15,-1.0) -- ++(-2,1) node[left]  {field \emph{value}};
      \draw (5.05,1.2)   -- ++(2,1)  node[right] {indicator \emph{distance}};
    \end{scope}
    \draw [latex-latex,blue] (5,0.95) -- ++(0,0.45);
    \path (3,-8) node[anchor = north, align = left] (eq1){%
    $X = 
       \ul{red}{$\ol{A_2}\,\ol{A_1}\,B_1\,B_0$}
      +\ul{blue}{$\ol{A_2}\,\ol{A_1}\,B_0\,\ol{D}$}
      +\ul{green}{$\ol{A_1}\,\ol{A_0}\,\ol{B_0}\,\ol{D}$}
      +\ul{black}{$\ol{A_2}\,A_1\,\ol{B_0}\,\ol{D}$}
      +\ul{olive}{$A_0\,{B_1}\,{B_0}$}
      +\ul{brown}{$\ol{A_1}\,\ol{A_0}\,\ol{B_1}\,{B_0}$}
      +\ul{yellow}{${A_2}\,\ol{A_1}\,{A_0}\,{B_0}$}
      +\ul{cyan}{$\ol{A_2}\,{A_1}\,\ol{A_0}\,\ol{B_1}\,{B_0}\,\ol{D}$}
    $};
    \path(eq1.south) node[anchor = north] (t1) {or};
    \path (t1.south) node[anchor = north, align = left] (eq2){%
    $X =
       \ul{red}{$\ol{A_2}\,\ol{A_1}\,B_1\,B_0$}
      +\ul{blue}{$\ol{A_2}\,\ol{A_1}\,B_0\,\ol{D}$}
      +\ul{green}{$\ol{A_1}\,\ol{A_0}\,\ol{B_0}\,\ol{D}$}
      +\ul{black}{$\ol{A_2}\,A_1\,\ol{B_0}\,\ol{D}$}
      +\ul{olive}{$A_0\,{B_1}\,{B_0}$}
      +\ul{brown}{$\ol{A_1}\,\ol{A_0}\,\ol{B_1}\,{B_0}$}
      +\ul{yellow}{${A_2}\,\ol{A_1}\,\ol{B_1}\,{B_0}$}
      +\ul{cyan}{$\ol{A_2}\,{A_1}\,\ol{A_0}\,\ol{B_1}\,{B_0}\,\ol{D}$}
    $};
  \end{tikzpicture}
\end{document}
