技術日誌

野球関係のサービスを個人開発しています。

react-leaflet事始め

何気なくよく使っているGoogleMap、 googleの公式サイト以外で使おうとすると料金がかかるのを知っていますか? ということで、Google MapsAPIを使わずに地図表示する方法を調べてみました。

Leaflet.jsというオープンソースの地図表示ライブラリがありました。 leafletjs.com こちらはreactコンポーネント化したライブラリ react-leaflet.js.org

react-leafletトラブルシューティング

delete L.Icon.Default.prototype._getIconUrl;
L.Icon.Default.mergeOptions({
  iconRetinaUrl: require("leaflet/dist/images/marker-icon-2x.png"),
  iconUrl: require("leaflet/dist/images/marker-icon.png"),
  shadowUrl: require("leaflet/dist/images/marker-shadow.png")
});
<Map
          center={position}
          zoom={this.state.zoom}
          style={{
            height: 600
          }}
        >
          <TileLayer
            attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
            url="https://{s}.tile.osm.org/{z}/{x}/{y}.png"
          />
          {markers}
        </Map>

完成

  • 日本地図を表示しながら時系列で各地点どのような出来事があったかをアニメーション表示する
  • 各チームの拠点にチームアイコンを表示する という機能を追加しました。 cap-baseball.com

参考

www.ipride.co.jp

パワプロ風画面ジェネレータを作りました。

f:id:ckoshien:20200309103947j:plain

サービスのURL

https://pawapro-gen.netlify.com/

どんなサービスか

下記のようなフォームを埋めると、 f:id:ckoshien:20200309104337j:plain 野球における自分の能力をパワプロ風に表現できるというサービスです。

キャップ野球に対応

一般的な野球と、キャップ野球にも対応しています。 例:「蓋」速など。

その他の機能

  • 左右での変化球の反転に対応
  • 入力内容をブラウザに保存できる

使っている技術

  • ReactJS
  • SASS
  • Netlify

苦労したところ

変化球の変化量をどのようにHTML/CSSで表現するか、 というところが一番の難関だったのですが、 基本的にCSStransform(rotate)だけで何とかなりました。 入力内容に応じて、tdにclassを与えてCSSで色を定義しています。

td.breaking_ball{
    background-color:#6ba5e7ba;
    border: #0e5eb9 2px solid;
    height: 8px;
    margin: 0px;
    padding: 0px;
    min-width: 8px;
    &.change{
      &:nth-child(1){
        background-color: #df6a0bab;
      }
      &:nth-child(2){
        background-color: #e9be4ac2;
      }
      &:nth-child(3){
        background-color: #ffeb3bd6;
      }
      &:nth-child(4){
        background-color: #e9be4ac2;
      }
      &:nth-child(5){
        background-color: #df6a0bab;
      }
      &:nth-child(6){
        background-color: red;
      }
    }
  }

今後の改修予定

  • 最大変化量を7段階にする
  • 第二変化球を設定できるようにする
  • レスポンシブ(モバイル対応)
  • オリジナル変化球の追加ができるようにする
  • オリジナルの特殊能力を追加できるようにする
  • 打者用のジェネレータを作る

情報局リニューアル第2弾

crieitの作ったサービスのレビュー依頼掲示板に投稿して、競技を知っている人、知らない人から見て今のサービスはどう映っているのか率直な意見を募ってみた。

  • スポーツのサイトとしては動きが足りない
  • 全体的に説明が足りない(初見〇し)
  • ユーザに導線を明示する

という意見をいただいたので、その点に気を付けながら再構築してみた。

f:id:ckoshien:20200216221307j:plain

「キャップ野球って?」タブ

動きを出すために動画を一番上に持ってきて、キャップ野球に関する簡単なルールの説明と、「活気が伝わってくる」とのブログ投稿一覧は残して、開催されているリーグや大会を写真で見せることに。
NPBのサイトとか阪神のサイトを参考にメリハリをつけてみた。
以前はテキスト中心だったので思い切って画像を上手く使う方法を模索。

「チームを探す」タブ

f:id:ckoshien:20200216222912j:plain
まだ野暮ったい印象が抜けないデザインではあるけど、
テキスト中心(元々markdownベース)だったのをチームアイコンをふんだんに使って視覚に訴える形に変更した。

練習会カレンダータブ

f:id:ckoshien:20200216223244j:plain

timetreeなどのUIからよいなと思ったところ(日付の数字を強調するなど)を吸収させてもらっている。

試合結果タブ

f:id:ckoshien:20200216223454j:plain

チームを(データ登録されている)試合数で並び替える機能と、試合結果一覧を閲覧できる機能を統合。1行に押し込んでいて窮屈だったUIを改行することで余裕をもたせている。

pixela APIを使ってアクセス数を可視化する

f:id:ckoshien:20200202000311j:plain

作っているサービス

cap-baseball.com

pixelaという草を生やすAPIチームページのアクセスカウンタに使ってみます。

pixelaとは

pixe.la

pixelaのAPIドキュメント

docs.pixe.la

開発者(a-knowさん)

blog.a-know.me

実装に使う技術

API

言語など:NodeJS

pixelaの3rd partyライブラリを使います。 github.com

実装

createGraphincrementPixelというメソッドを作って、各チームページにアクセスされるたびにcreateGraphを呼びます。 重複している場合はAPIから400エラーが返ってくるのでハンドリングしつつ、 カウントアップするメソッドを実行します。

export class pixelaLogic {
  public createGraph(team) {
      return new Promise(async(resolve,reject)=>{
          try {
            let response = await client.createGraph({
                id: "team"+team[0].team_id.toString(),
                name: team[0].team_name.toString(),
                unit: "access",
                type: "int",
                color: "shibafu"
              });
            //console.log(response.status);
            resolve(response.status);                  
          } catch (error) {
              resolve(500)
          }
      })
  }

  public incrementPixel(team):Promise<number>{
      return new Promise(async(resolve,reject)=>{
        let response;
        try {
            response = await client.incrementPixel('team' + team[0].team_id)
            console.log(response.status)
            return resolve(response.status)                
        } catch (error) {
            console.error(error);
            return resolve(500); 
        }
      })

  }
}

生成されたグラフのリスト

ckoshien Graph List | Pixela

UI側

ReactJS

グラフの埋め込みを行います。

<iframe src={'https://pixe.la/v1/users/ckoshien/graphs/team'+ store.getState().team.team_id +'.html?mode=simple'} height="150" width="600" frameborder="0" />

a-knowさんとmottox2さんは運営者ギルドのギルドメンバーでもあります。 (私も) qiita.com

D3.jsで日本地図に地点をプロットしてツールチップを表示してみる

作ったもの

キャップ野球チームの全国分布図を作成。

  • チームカラー
  • (あれば)チーム旗

f:id:ckoshien:20200127000421j:plain https://cap-baseball.com/map

動作イメージ

地図データの準備

地図データはshapeファイルで提供されているものをGeoJSON→topojson化する。

npm i topojson -g
$ wget  http://www.naturalearthdata.com/http//www.naturalearthdata.com/download/10m/cultural/ne_10m_admin_1_states_provinces.zip
# zip解凍
$ unzip ne_10m_admin_1_states_provinces.zip
# ogr2ogrが含まれているgdalをインストールする
$ brew install gdal
# GeoJSONから日本のデータのみ抽出してtopoJson化する
$ ogr2ogr -f GeoJSON -where "adm0_a3 = 'JPN'" ne_pref_japan_geo.json ne_10m_admin_1_states_provinces.shp

CRAでd3を扱う

準備

# d3とtopojson-clientをインストールする
$ npm i --save d3 topojson-client

reactでzoom機能を扱う

コンストラクタでzoom機能を定義する

this.zoom = d3
      .zoom()
      .scaleExtent([-5, Infinity])
      .translateExtent([
        [-100, -100],
        [200, 300]
      ])
      .extent([
        [0, 0],
        [0, 0]
      ])
      .on("zoom", this.zoomed.bind(this));

zoomしたときに再描画を発火させるのがミソ。

zoomed() {
    this.setState({
      zoomTransform: d3.event.transform
    });
  }

点をプロットする

プロットする点のデータ構造

{
        name:"学習院大",
        y:35.71,
        x:139.7,
        teamId: 73,
        color:"lightblue"
    },

データから円を書く

svg
      .append("g")
      .attr("id", "circle-g")
      .selectAll("circle")
      .data(teamInfo)
      .enter()
      .append("circle");

円を塗りつぶす

データで定義した色で塗りつぶします。 マウスオーバーしたときにツールチップに表示する情報や、 表示の切り替えなどもここで記述しています。

    svg
      .selectAll("#circle-g circle")
      .attr("fill", (d)=>{
        return (d.color !== undefined ? d.color :'gray')
      })
      //.attr("opacity", 0.5)
      .attr("transform", function(d) {
        var coord = projection([d.x, d.y]);
        return "translate(" + coord.join(",") + ")";
      })
      .on("mouseover", function(d) {
          //console.log(d.name);
          tooltip
          .style("display", "block")
          .html(
              `<div>
              <h1>${d.name}</h1>
              <div
                style={{
                    display: flex,
                    margin: 10px
                }}
              >
              <img src="/images/${d.teamId}.jpg" width="100" />
              
              </div>
              </div>`
              )
        
      })
      .attr("r", 5 / (d3.event !== null ? d3.event.transform.k : 1));

参考

ReactでRSSを取得する

f:id:ckoshien:20200119004756j:plain

右側の余白が頭痛の種

キャップ野球情報局ではレスポンシブ対応を行っています。
ただ、2カラムレイアウトにするとどうしても右側に余白ができてしまう問題がありました。
そこで意見を募った結果、新着ブログ記事を表示してほしいなどの意見があったので早速実装しました。

rss-parserを使う

再び立ちはだかるCORS

ブラウザでは、セキュリティの観点から、あるサイトで異なるサイトのデータを読み込むことに制限を設けています。それがCORS (Cross-Origin Resource Sharing)です。

解決策

基本的にCORSはサーバ側で'Access-Control-Allow-Origin'ヘッダを付加することによって解決できますが、amebaとかnoteのサーバの設定を変えてくださいとはお願いできないので、 プロキシサーバーを経由することが一般的です。

ありがたいことに、herokuを使ったプロキシサーバが用意されています。


これでブログのRSSが取得できます。

実装編

import Parser from 'rss-parser';
import moment from 'moment';

componentDidMount(){
        (async () => {
            const blogs = [
                'https://note.com/mpre/rss',
                'http://rssblog.ameba.jp/capfuta/rss20.xml',
                'https://note.com/kumagae_capthrow/rss',
                'http://rssblog.ameba.jp/tohru-cap/rss20.xml'
            ]
            let parser = new Parser();
            let feeds = [];
            for(let i = 0; i < blogs.length; i++){
                let feed = await parser.parseURL('https://cors-anywhere.herokuapp.com/'+blogs[i]);
                //console.log(feed.items);
                feeds = feeds.concat(feed.items);
            }
            feeds.sort((a,b)=>{
                if(moment(a.pubDate) > moment(b.pubDate)){
                    return -1;
                }else{
                    return 1;
                }
            })
            this.setState({
                blogs:feeds
            })
           
          })()
          .catch(err=>{
              console.error(err);
          });
    }

参考

CRAにSCSSを導入しました

CRA(create-react-app)にSCSSを導入した話。

node-sassをnpmインストールする

npm i -D node-sass

.cssファイルを.scssにリネーム

scssで書いてみる

このような3色の四角の囲みを使っているのですが、

f:id:ckoshien:20200118194824j:plain

div.topic{
    position: relative;
    top: 0px;
    padding: 5px;
    border-radius: 5px;
    margin: 0px 5px 5px 5px;
    overflow-x: scroll;
    max-width: 400px;
    font-size: 14px;
    &.border_green{
      border:1px solid #386537;
    }
    &.border_pink{
      border:1px solid #BF7A87;
    }
    &.border_blue{
      border:1px solid #1F72A6;
      //トップページyoutube用style
      iframe#widget2{
        max-width: 400px;
        max-height: 300px;
      }
    }

ベースのスタイルを書いて色だけ分岐させるのはこう書けるようです。

埋め込みyoutubeのスタイル指定にメディアクエリを使います。 SCSSの場合は変数を使うこともできますが、ベーシックな書き方を使ってみます。

iframe{
  //youtube
  &#widget2{
    @media (max-width: 400px) {
      width: 100%;
      height: 300px;
    }
    @media (min-width: 400px) { 
      max-width: 600px;
    }
  }
}

上記画像のサービス

キャップ野球とは

ペットボトルキャップとウィッフルバットで使う野球の縮小型競技。


20191229 【キャップ野球】交流会練習試合 第二試合 A 10-2 B ダイジェスト【MUCtC主催】

ウィッフルバット

グリップテープもおすすめ

参考