ExcelでIE記法のER図を書くときに、鳥の足 (Crow’s Foot) をどう描くか。(え? ER図書くならもっと便利なツールを使う? そりゃ恵まれてますね)

今までは、線と円のシェイプを組み合わせて、それをグループ化した部品を作って描いていた。「0..1」とか「1..n」とかのパーツを用意しておいて、それをコネクタで結べば、エンティティの移動とか整列とかには割と耐えられる。けど、グループ化を解除しすぎちゃったり、手が滑って線を1本だけ動かしちゃったりすると、形が崩れてしまう。それと、Excelってやつはズームの倍率を変えると微妙にシェイプの位置やサイズが変わるので、編集をくり返しているうちに、なーんか形が歪んできたりもする。丸が潰れて真円じゃなくなったりとか。これなんとかならんじゃろか。

ふと、SVG画像の鳥足パーツがあれば、型崩れの心配もなく、拡大・縮小・回転・コネクタ接続などもできて便利なのではと思った。最近のExcelならSVGもそのまま取り込めたはず。SVGならフォアグランドカラーも好きに変えられたはず。こんなもん絶対もう誰かが作ってるはずだけど、検索しても意外に手頃なものがなかったので、自作してみた。最初は1方向だけ作って回転すればいいやと思ったけど、画像のハンドルがバラバラの方向を向いて使いづらいので、4方向分作ってみた。使うとき極力軽くなるようにということで、SVGのrotate()とかは使っていない。

さすがに量産するのが面倒くさくなってきたので、SVG生成用のbashスクリプトも作った。それもついでに載せておく。bashとawkが使える環境があるなら、SVGをダウンロードするよりこのスクリプトで再生成した方が早い。サイズとか色とか太さとか丸の大きさとか変えたいときも使えそうだし。

場合によっては、古いExcelしか使えずSVGが取り込めないこともありそうなので、いちおう透過PNGにも変換したものも置いておく。(ImageMagickでconvert -background noneしただけのもの)

SVG版

カーディナリティ 西
1 img   img  
n img img img img
1..1 img   img  
1..n img img img img
0..1 img img img img
0..n img img img img

PNG版

カーディナリティ 西
1 img   img  
n img img img img
1..1 img   img  
1..n img img img img
0..1 img img img img
0..n img img img img

SVG生成スクリプト

#!/bin/bash

main() {
  base_filename=crows-foot
  canvas_size=20
  foot_width=$(calc $canvas_size / 2)
  circle_radius=$(calc $canvas_size / 5)

  for rotation in "east" "south"; do
    generate "none" "one" $rotation
    generate "one" "one" $rotation
  done

  for rotation in "east" "west" "south" "north"; do
    generate "none" "many" $rotation
    generate "zero" "one" $rotation
    generate "zero" "many" $rotation
    generate "one" "many" $rotation
  done
}

generate() {
  local parent=$1
  local child=$2
  local rotation=$3

  exec > ${base_filename}-${parent}-${child}-${rotation}.svg

  echo '<?xml version="1.0"?>'
  echo '<svg width="'$canvas_size'" height="'$canvas_size'" version="1.1" xmlns="http://www.w3.org/2000/svg">'
  echo '  <g stroke="black">'

  local grid=$(calc $canvas_size / 5)
  local x_left_end=$(calc $canvas_size / -2)
  local x_center=0
  local x_right_end=$(calc $canvas_size / 2)
  local y_center=0

  if [[ $parent = "zero" ]]; then
    # stem and circle
    local x_circle_center=$(calc $grid / -2)
    line $x_left_end $y_center $(calc $x_circle_center - $circle_radius) $y_center $rotation
    circle $x_circle_center $y_center $rotation
    line $(calc $x_circle_center + $circle_radius) $y_center $x_right_end $y_center $rotation
  else
    # stem
    line $x_left_end $y_center $x_right_end $y_center $rotation
  fi

  if [[ $parent = "one" ]]; then
    # vertical bar
    line $(calc $grid / 2) $(calc $foot_width / 2) $(calc $grid / 2) $(calc $foot_width / -2) $rotation
  fi

  if [[ $child = "one" ]]; then
    # another vertical bar
    case $parent in
      "none") line $x_center $(calc $foot_width / 2) $x_center $(calc $foot_width / -2) $rotation;;
      "zero") line $(calc $x_right_end - $grid) $(calc $foot_width / 2) $(calc $x_right_end - $grid) $(calc $foot_width / -2) $rotation;;
      "one") line $(calc $grid / -2) $(calc $foot_width / 2) $(calc $grid / -2) $(calc $foot_width / -2) $rotation;;
    esac
  else
    # crow's foot
    line $(calc $grid / 2) $y_center $(calc $canvas_size / 2) $(calc $foot_width / 2) $rotation
    line $(calc $grid / 2) $y_center $(calc $canvas_size / 2) $(calc $foot_width / -2) $rotation
  fi

  echo '  </g>'
  echo '</svg>'
}

line() {
  local x1=$1
  local y1=$2
  local x2=$3
  local y2=$4
  local rotation=$5

  rotate $x1 $y1 $rotation
  x1=$new_x
  y1=$new_y

  rotate $x2 $y2 $rotation
  x2=$new_x
  y2=$new_y

  local offset=$(calc $canvas_size / 2)
  x1=$(calc $x1 + $offset)
  y1=$(calc - $y1 + $offset)
  x2=$(calc $x2 + $offset)
  y2=$(calc - $y2 + $offset)
  echo '    <line x1="'$x1'" y1="'$y1'" x2="'$x2'" y2="'$y2'" />'
}

circle() {
  local cx=$1
  local cy=$2
  local rotation=$3

  rotate $cx $cy $rotation
  cx=$new_x
  cy=$new_y

  local offset=$(calc $canvas_size / 2)
  cx=$(calc $cx + $offset)
  cy=$(calc - $cy + $offset)
  echo '    <circle cx="'$cx'" cy="'$cy'" r="'$circle_radius'" fill="none" />'
}

rotate() {
  local x=$1
  local y=$2
  local rotation=$3

  case $rotation in
    "east")
      new_x=$x
      new_y=$y
      ;;
    "north")
      new_x=$(calc - $y)
      new_y=$x
      ;;
    "west")
      new_x=$(calc - $x)
      new_y=$(calc - $y)
      ;;
    "south")
      new_x=$(calc - $y)
      new_y=$(calc - $x)
      ;;
  esac
}

calc() {
  awk "BEGIN{print $*}"
}

main

※更新履歴

  • 2024-10-19 4方向化、生成スクリプト追加。