座標計算に四苦八苦した。

ブツ

eyecatch.png

たぶんこの図でイメージが伝わると思う。自分を中心に、取引したことのある相手を並べたい。どれだけの人と関わったか可視化できる。その人のサイトに遊びに行ける。そんな図を作りたい。できれば名前検索や一覧、投げモナもできればいいのだが、実現できるかわからない。これはSVGであり、SVGでそんなことができるかどうか知らない。

以下のような想定である。

  • 最大256人表示する
  • 中央ほど大きく外側ほど小さい
  • 中央は自分。外側は取引相手
  • 金額が大きい順に中央の真上から時計回りに配置する

なお、実際はまだ取引データと連動していない。あくまで座標計算をして表示できるか試しただけである。

私は算数や数学が大の苦手なので三角関数という言葉だけで鳥肌が立つ。そこをグーグル先生と気合いで切り抜けた。

課題

  • 隣の画像が重なり合わない最大表示件数が算出できない(画像サイズと表示アイコン数から算出したいのに)
  • 実際の取引データと連動させたい

微妙に隣の画像と重なってしまっている部分がある。かと思えば、微妙に離れているところもある。ここをぴっちりかっちり計算したい。でも、どうすればいいかわからない。

とりあえず今はサイズと件数を固定値で指定している。

追記

配置件数を計算できないか試みる

コメント頂いたので計算してみました。私も円周、半径、中点からの直線距離がわかれば計算できるはずだとは思っていました。

結果からいうと外側1つ目だけは成功しましたが、それ以降は隙間が空きすぎに見えます。失敗。

試行 結果
3.html 円の直径を画像1個サイズで割ったら、外1はいい感じだが、他は隙間が空いた
4.html 円サイズを小さくしたら、さらに隙間が空いてしまった

3.png
4.png

計算は次のようにしました。

画像サイズは以下を元に計算ました。今は個数を以下のように決め打ちしていますが、これを動的に算出したいです。隣と重ならない最大数がほしいです。

位置 個数 画像サイズ
中心 1 96px
外側1 8 72px
外側2 16 64px
外側3 32 48px
外側4 48 32px
外側5 64 24px
外側6 88 20px

現状、外側1に8個並べているのですが、少しだけ重なっています。もし正しく重ならない配置数が計算できたなら7個になると予想しています。

外側1にN個並べたときにできる大きな円の直径を、1個あたりの画像サイズ72pxで割れば、重ならない配置数が計算できるはずです。たぶんコメントしてくださった方はそうおっしゃったのだと思っています。実際に計算してみました。

外1全体の直径
=中心+外1画像サイズ×2
=96+(72*2)
=240
外1全体の円周
=直径×円周率
=240*3.14
=753.6
外1の配置数
=外1全体の円周÷直径
=753.6 / 72
=10.46個

10個……。8個より大きな数になってしまいました。もっと重なってしまいます。

外1全体の直径が大きすぎるのでしょう。でも値は正しいはずです。2倍しているところが気になりますが、これは左右や上下など反対の方向にも配置されるため、2倍しています。求めるのが半径でなく直径なら、これで合っていると思うのですが。

もし「外1全体の直径」を算出するとき外側でなく中点位置が正しいのだとすれば半分になるため、2倍を打ち消すことになります。

でも画像の配置位置x,y座標は左上が始点になるはずだから、中点で計算するのは間違っているような気もするのですが。とにかくやってみます。今より直径が小さくなる計算をテキトーにやってみます。

外1全体の直径
=中心+外1画像サイズ
=96+72
=168
外1全体の円周
=直径×円周率
=168*3.14
=527.52
外1の配置数
=外1全体の円周÷直径
=527.52 / 72
=7.326個

小数点を切り捨てれば「7個」です。おや、予想どおりの重ならない最大数になりました。この計算式でいけるのかもしれません。

これをコードに書いてみたのが3.htmlです。

外1だけはうまくいっています。でも外2以降はスペースが空きすぎです。明らかにもっとたくさん画像を配置できるでしょう。どうしてこうなった……。

重ならない配置数を計算するコードの要点は以下。

this.userIconSizes = [96,72,64,48,32,24,20]
#calcIconNum(i) { // i:0,1,2,...  外周位置におけるアイコン同士が重ならない最大配置数を返す
    // 外i+1全体の直径から外i+1画像の中点までの距離
    const outerCircleSize = this.userIconSizes.slice(0,i+2).reduce((sum,v)=>sum+v)
    // 外i+1全体の円周=直径×円周率
    const circumference = outerCircleSize * Math.PI
    // 重ならない最大配置数=外i+1全体の円周÷直径
    return parseInt(circumference / this.userIconSizes[i+1])
}

隙間があるということは、外周円のサイズが大きすぎるということでしょう。どうにかして、もう少し小さくしてみます。外周位置iのところの画像サイズだけ半分にしてみました。

それをコードにしたのが4.htmlです。

結果をみてみると、さらに隙間が空いてしまいました。あるぇ? どゆこと?

コード配下です。

#calcIconNum(i) { // i:0,1,2,...  外周位置におけるアイコン同士が重ならない最大配置数を返す
    const middles = this.userIconSizes.slice(1,i+1)
    const outerCircleSize = ((this.userIconSizes[0]+this.userIconSizes[i+1])/2) + ((0==middles.length) ? 0 : middles.reduce((sum,v)=>sum+v))
    const circumference = outerCircleSize * Math.PI // 外1全体の円周=直径×円周率
    return parseInt(circumference / this.userIconSizes[i+1])
}

お手上げです。これ以上はわかりません。仕方ないので表示数も決め打ちでいきます。少し重なってますが、目をつぶります。

追記2

再びコメントいただきました。ありがとうございます。おかげさまで、ついにできました!

中心の直径+(n-1個目のサイズ)*2+n個目のサイズ
2円めですが14.915になるとおもいます

これをコードに反映させると、それっぽくなりました!

5.html

5.png

#calcIconNum(i) {
    const middles = this.userIconSizes.slice(1,i+1)
    const outerCircleSize = ((this.userIconSizes[0]+this.userIconSizes[i+1])) + ((0==middles.length) ? 0 : middles.reduce((sum,v)=>sum+v)*2)
    const circumference = outerCircleSize * Math.PI // 外1全体の円周=直径×円周率
    return parseInt(circumference / this.userIconSizes[i+1])
}
  • 外周円は最大で6つ(最も外側の画像サイズが20pxでこれ以上小さくしたくない)
  • 最大261人表示する
  • 外周円ごとの表示人数は内側順に[7, 14, 27, 48, 72, 93]

以上。