chart.js 再描画でのエラーを回避


ここで教えて頂きました。

< コード例 >

チャートは4個を同時に表示
chart.jsの描画はクラスで行ってます。
4個のチャートオブジェクトは、staticな配列に入れてます。


static charts = [null, null, null, null] 

   // 前回消去 //
    if (typeof PieChart.charts[this.idx] !== 'undefined' &&  PieChart.charts[this.idx] !== null && PieChart.charts[this.idx]) {
      PieChart.charts[this.idx].destroy();
    }


    // == ドーナツチャート描画 == //
    PieChart.charts[this.idx] = new Chart(context, {
      //type: 'doughnut',
      //type: 'pie',
      type: chartType,
      data: {
        labels: labels,
        datasets: [{
            label: name,
            backgroundColor: ["#fa8072", "#00ff7f", "#00bfff", "#a9a9a9", "#f5f5f5", "#a7d4d4", "#E5E100"],
            data: counts
          }]
      },

      // プラグインをセット //
      plugins: [
        ChartDataLabels
      ],

      //color: "#ffffff",
      options: {
        responsive: false,
        maintainAspectRatio: false, // 2回目以降大きくなってしまうのを防止用

        legend: {
          labels: {
            // グローバルプロパティを上書 ?????? no affect //
            fontColor: 'white'
          }
        },

        // データラベルがアフェクトする用 //
        /*toltip: {
         enabled: false
         },*/

        plugins: {

          // データラベルがアフェクトする用 //
          toltip: {
            enabled: false
          },

          title: {
            display: true,
            text: name
          },

          // == データラベルプラグインの設定 == //
          datalabels: {
            color: '#294829',
            font: {
              weight: 'bold',
              size: 10, // <=== 小さ目
            },
            formatter: (value, ctx) => {

              let label = ctx.chart.data.labels[ctx.dataIndex];
              //return label + '\n' + value + '%';
              return label
            }
          }
          // == データラベルプラグインの設定終わり == //

        }
      }
    })

Tabulator カラムのグループヘッダー

 

 

 

ドキュメントはここ

< コード例 >

グループ化したいタイトルのカラムの配列を入れ子にします。


      // == Sizes == //
      {
        title: "Sizes",
        columns: [
          // GB Size //
          {title: 'Size (GB)', field: 'GB_SIZE', width: 50, hozAlign: 'right',
            formatter: function (cell, formatterParams, onRendered) {
              return numeral(cell.getValue()).format("#,###")
            }
          },

          // GB 前日//
          {title: 'Size (GB) P', field: 'GB_SIZE_BF1', width: 50, hozAlign: 'right',
            formatter: function (cell, formatterParams, onRendered) {
              return numeral(cell.getValue()).format("#,###")
            }
          },

          // MB Diff from 1 day before //
          {title: 'DiffS', field: 'DIFF_MB_SIZE', width: 60, hozAlign: 'right',
            formatter: function (cell, formatterParams, onRendered) {
              return numeral(cell.getValue()).format("#,###")
            }
          },
        ]
      },

      // == Counts == //
      {
        title: "Counts",
        columns: [

          // File count //
          {title: 'Count', field: 'CNT_FILE', width: 70, hozAlign: 'right',
            formatter: function (cell, formatterParams, onRendered) {
              return numeral(cell.getValue()).format("#,###")
            }
          },
          // File count previous //
          {title: 'Count P', field: 'CNT_FILE_BF1', width: 70, hozAlign: 'right',
            formatter: function (cell, formatterParams, onRendered) {
              return numeral(cell.getValue()).format("#,###")
            }
          },

          // File count difference from previous date //
          {title: 'DiffC', field: 'DIFF_CNT_FILE', width: 60, hozAlign: 'right',
            formatter: function (cell, formatterParams, onRendered) {
              return numeral(cell.getValue()).format("#,###")
            }
          }
        ]
      },

< 画面例 >

Leaflet JavaScript OpenRailwayMap

Leafletの地図に鉄道線路、駅を表示する必要があり、探しましたところベースレイヤーでOpenRailwayMapというのが見つかり使ってみました。

 

< コード例 >

レイヤー選択にOpenRailwayMapを加える


{
        label: '鉄道路線図',
        url: 'http://{s}.tiles.openrailwaymap.org/standard/{z}/{x}/{y}.png',
        attr: {
          minZoom: 2,
          maxZoom: 19,
          tileSize: 256,
          attribution: '<a href="https://www.openstreetmap.org/copyright">© OpenStreetMap contributors</a>, Style: <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA 2.0</a> <a href="http://www.openrailwaymap.org/">OpenRailwayMap</a> and OpenStreetMap'
        },
        ldtp: "L"
      },

OpenStreetMapを重ねる必要があります。


   let baseMaps = {};
    let curkey = "";
    let objlayer;
    $.each(leyers, function (j, itm) {
      curkey = itm["label"];
      objlayer = L.tileLayer(itm["url"], itm["attr"]);
      baseMaps[curkey] = objlayer;
    });

    // layersコントロールにbaseMapsオブジェクトを設定して地図に追加
    // コントロール内にプロパティ名が表示される

    L.control.layers(baseMaps).addTo(lmap);
   
    let tgtbase = baseMaps[layerkey];
    tgtbase.addTo(lmap);

    //baseMaps["OpenRailwayMap"].addTo(lmap)

    // 鉄道地図の場合、OpenStreetmapも重ねる必要あるので //
    if (layerkey.includes("OpenRailwayMap")) {
      const addmap = layerkey.includes("satellite") ? "Google satellite" : "Open street map"
      const osbase = baseMaps[addmap]
      try {
        osbase.addTo(lmap);
      }
      catch (e) {
      }

    }
    else {
      $("#legend").hide()
    }

< 画面例 >

Leaflet JavaScript マウスオーバーでポリラインを強調

鉄道路線図が入った地図で、ポリラインのセグメントにマウスが乗った時、その線を強調させてみました。

< コード例 >


    let thickLines

    // -- フィルターされたJSONのfeaturesを走査 -- //  
    const geojson = L.geoJson(json, {
      // 非強調、通常のスタイル //
      style: function (feature) {
        return {color: linecol, weight: 2.0}
      },
      onEachFeature: function (feature, layer) {

        const pp = feature.properties

        // @@ マウスが乗った時 @@ //
        layer.on('mouseover', function (e) {


          // 線を太くする //
          const tgtjson = new RailLines().getComlineJSON(json, pp.NM_COM, pp.NM_LINE)
          //console.log(tgtjson)

          thickLines = L.geoJson(tgtjson, {
            style: {color: linecol, weight: 7}
          }).addTo(lmap)

          const comline = pp.NM_COM + " " + pp.NM_LINE
          $("#raillinespn").text(comline /*+ " " + tgtjson["distKM"] + " Km"*/)


        })

        // @@ 乗ったマウスが外れた時 @@ //
        layer.on('mouseout', function (e) {
          //$("#raillinespn").text("")

          // 線を元に戻す //
          lmap.removeLayer(thickLines)

        })

      },
      pointToLayer: function (feature, latlng) {
      },
    })
    // マップに追加 //
    geojson.addTo(lmap);
    // 消去用配列に追加 //
    RailLines.lines.push(geojson)

  /**
   * 指定路線全線強調用GeoJSON取得
   * @param {type} json 全路線JSON
   * @param {type} com 会社名
   * @param {type} line 路線名
   * @returns {RailLines.getComlineJSON.raillinesAnonym$0} 連想配列
   */
  getComlineJSON(json, com, line) {

    const fts = json["features"]
    const tf = new MyTurf()

    let resfts = []
    let ttldst = 0

    fts.forEach((ft, i) => {
      const pp = ft.properties
      if (pp.NM_COM === com && pp.NM_LINE === line) {
        resfts.push(ft)


        const coords = ft.geometry.coordinates
        coords.forEach((cd, k) => {
          if (k > 0) {
            ttldst += tf.getDistanceM(cd, coords[k - 1])
          }
        })
        
      }
    })
    const distKM = Math.round(ttldst / 1000 * 100) / 100  // ???? 短い

    return {
      type: "FeatureCollection",
      features: resfts,
      distKM: distKM
    }

  }

< 画面例 >

Leaflet JavaScript マウスオーバーでマーカーを強調

< コード例 >

マウスオーバー用拡大アイコン


 // 通常サイズ用 //
  // 通常 //
        const icn = L.icon({
          iconUrl: "Img/aptower_50_" + iconcol + ".png",
          //shadowUrl: 'leaf-shadow.png',
          iconSize: [sz, sz]
        });
 
 // マウスオーバー用 //
        const icnmon = L.icon({
          iconUrl: "Img/aptower_64_yellow.png",
          //shadowUrl: 'leaf-shadow.png',
          iconSize: [38, 38]
        });

マウスが乗った時にマウスオーバー用拡大アイコンをセット
外れた時は元のサイズに戻す


// @@ マウスが乗った時 @@ //
        marker.on("mouseover", function (e) {
          // 大きい黄色のアイコンに変える //
          marker.setIcon(icnmon)
        })
        // @@ マウスが外れた時 @@ //
        marker.on("mouseout", function (e) {
          // からし色並サイズアイコンに戻す //
          marker.setIcon(icn)

        })

< 画面例 >

通常サイズ

拡大サイズ

Javascript 時刻形式経緯度十進変換関数

気象や航空関係のデータの経緯度の形式が時刻なものを、Leafletの地図で扱う場合のデータ検証用として、
地名入力してジオコーダーを使って移動させるような処理を、入力値を時刻経緯度で行うために作ってみました。

< コード例 >


/**
 * 時刻経緯度十進変換
 * 
 * @param txt 入力文字列 ex. 353220.86N
 *                            1394906.93E
 */
const getDecLocationFromTimeLocation = (txt) => {

  let res = ""

  const pos_comma = txt.indexOf(".")

  const txt_int_part = txt.slice(0, pos_comma)

  let txt_dec_part = txt.slice(pos_comma + 1)
  txt_dec_part = txt_dec_part.replace("N", "").replace("S", "").replace("W", "").replace("E", "")

  let txt_int = ""
  let txt_min = ""

  //alert(txt_int_part + "\n" + txt_int_part.length)

  // 整数部分、分の切り取り //
  switch (txt_int_part.length) {
    // 整数1桁 //
    case 5:
      txt_int = txt_int_part.slice(0, 1)
      txt_min = txt_int_part.slice(1, 3)
      break
      // 整数2桁 //  
    case 6:
      txt_int = txt_int_part.slice(0, 2)
      txt_min = txt_int_part.slice(2, 4)
      break;
      // 整数3桁 //  
    case 7:
      txt_int = txt_int_part.slice(0, 3)
      txt_min = txt_int_part.slice(3, 5)
      break
  }

  // 秒は常に右2文字
  const txt_sec = txt_int_part.slice(-2)

  //alert (txt_int + ":" + txt_min + ":" + txt_sec + "." + txt_dec_part)


  res = Number(txt_int) +
          Number(txt_min / 60) +
          Number(txt_sec / 3600) +
          Number(txt_dec_part / 360000)

  // 南半球の場合 //
  if (txt.slice(-1) === "S" || txt.slice(-1) === "W") {
    res = res * -1
  }

  return res
}
           

作ってから気づいたのですが、整数部分の分秒の変換は、Pythonでの例ですが、


numInt = int(self.txt)
# 整数部
hourPartVal = int(numInt / 10000)
# 分
minutePartVal = int(numInt / 100) % 100
# 秒
secondPartVal = numInt % 100
minuteVal = minutePartVal / 60
secondVal = secondPartVal / 3600
resval = hourPartVal + minuteVal + secondVal

のようにすれば簡潔になります。

< 利用例 >

AiP JAPANに載っている FIXのWaypoint EPSON がどこにあるか調べる

千葉県にあることがわかる

CSSでLeafletの画像マーカーの色を変える

Leafletでマーカーに画像ファイルを使ってる場合、同じ形のマーカーで動的に色を変えたい場合、以前は色を違えた別の画像ファイルを用意して、アイコンのURLをセットする方法を行ってましたが、色数が多くなったり中間色を使う場合、別のファイルを用意するのも大変なので何とかならないかと調べてましたところ、
CSSのhue-rotateで色相環の回転位置を使って同じ画像ファイルで色を変えることがわかり試してみました。

ここで教えていただきました。

 

 

 

 

< 使った画像 >

黄色


< コード例 >

CSS


  /* 色相環回転色 */
  .acmarker_aqua {
    filter: hue-rotate(150deg);   /* 黄色から150度 */
  }
  .acmarker_orange {
    filter: hue-rotate(-45deg);   /* 黄色から-45度 */
  }
  .acmarker_aquamarine {
    filter: hue-rotate(120deg);   /* 黄色から120度 */
  }
  
  .acmarker_red {
    filter: hue-rotate(-90deg);   /* 黄色から-90度 */
  }
  .acmarker_fuchsia {
    filter: hue-rotate(-135deg);  /* 黄色から-135度 */ 
  }
  .acmarker_yellow {
    filter: hue-rotate(0deg);   
  }
  .acmarker_lightgreen {
    filter: hue-rotate(45deg);   /* 黄色から-45度 */ 
  }
  .acmarker_blue {
    filter: hue-rotate(150deg);  /* 黄色から-150度 */ 
  }
  
  .acmarker_blue_red {
    filter: hue-rotate(150deg);   /* 青から150度 */ 
  }
  
  .acmarker_blue_green {
    filter: hue-rotate(250deg);   /* 青から250度 */ 
  }

javaScript


      // 色相環回転色適用CSSクラス //
      let markerClass = isAquaChange ? "acmarker_aqua" : ""

      if (!isByAltMarker) {
        markerClass = isOrangeChange ? "acmarker_orange" : markerClass
        markerClass = isAquaMarineChange ? "acmarker_aquamarine" : markerClass
        markerClass = isPinkChange ? "acmarker_red" : markerClass
        markerClass = isRedChange ? "acmarker_blue_red" : markerClass
        markerClass = isGreenChange ? "acmarker_blue_green" : markerClass
      }

      // 高度別マーカーの場合 //
      if (isByAltMarker) {

        // 色名ディレクトリ、クラス名取得 //
        const altclass = new Altitude()
        const map = altclass.getIconColorDirClassName(lineitm.altidx)

        const coldir = map["dir"]
        const iconfnm = ICON_URL_OTHEROPE.split("/")[3]
        ICON_URL_OTHEROPE = `Img/apbytype/${coldir}/${iconfnm}`
        markerClass = map["class"]

        // Img/acbytype/yellow/ac_787.png
      }

      // 対象オペレータ飛行中 //
      const acicon = L.icon({
        iconUrl: ICON_URL,
        iconRetinaUrl: ICON_URL,
        iconSize: [szInt, szInt],
        iconAnchor: [szIntHalf, szInt],
        popupAnchor: [0, ppAnchorY],

        className: markerClass
      })
      // 他オペレータ飛行中 //
      const acicon_other = L.icon({
        iconUrl: ICON_URL_OTHEROPE,
        iconRetinaUrl: ICON_URL,
        iconSize: [szInt, szInt],
        iconAnchor: [szIntHalf, szInt],
        popupAnchor: [0, ppAnchorY],

        className: markerClass
      })

JavaScript 画像ディレクトリ、クラス名関数


  /**
   * 高度レベル別飛行機マーカーディレクトリ名クラス名
   * @param {type} level 高度レベル
   * @returns {Altitude.getIconColorDirClassName.map}
   */
  getIconColorDirClassName(level) {

    let map = {"dir": "white", "class": ""}


    if (level === 0) {
      map = {"dir": "blue", "class": "acmarker_blue_green"}
    }
    else if (level === 1) {
      map = {"dir": "blue", "class": "acmarker_blue_red"}
    }
    else if (level === 2) {
      map = {"dir": "fuchsia", "class": ""}  // Fucisia
    }
    else if (level === 3) {
      map = {"dir": "yellow", "class": ""}    // Yellow
    }
    else if (level === 4) {
      map = {"dir": "lightgreen", "class": ""}     // LightGreen
    }
    else if (level === 5) {
      map = {"dir": "blue", "class": ""}     // Blue
    }
    else if (level === 6) {
      map = {"dir": "yellow", "class": "acmarker_aqua"}    // Aqua
    }
    else if (level === 7) {
      map = {"dir": "white", "class": ""}     // white
    }

    return map
  }

< 画面例 >

3AFB Aviaton

飛行機の高度でアイコンの色を変えています。

画像ファイルにない色を使う。

Javascript Timer の停止再開インターバル変更など

データベースのデータを読み込んで、飛行機の航跡地図を表示する処理で、録画した航跡の再現速度を速くしたり、遅くしたり、録画位置の変更、一時停止などする必要があるため作ってみました。
調べましたところ、タイマーを解除するのはclearIntervalで出来るのがわかりましたが、一時停止するメソッドがないようなので、タイマーは動かしたままにして、タイマーの処理内でフラグを見て判別してます。
再生速度変更も同じようにして、clearIntervalで解除してインターバルを変えて再開しています。

< コード例 >

HTML
Bootstrap4のモーダルダイアログのタイトル

        <div class="pull-left mt-0 histrangeclass">

          <!-- ポーズ再開ボタン -->
          <button id="histpause" type="button" class="btn btn-sm btn-primary tippyspan histshow pauseresume"
                    title="Pause live"><i class="fa fa-pause"></i></button>
          <button id="histresume" type="button" class="btn btn-sm btn-primary tippyspan histshow pauseresume dsp_none"
                    title="Resume live"><i class="fa fa-play"></i></button>          

          <!-- ヒストリースライダー -->
          <input id="histrange" type="range" class="histshow dsp_none curpo" min="1" max="200" step="1">  
          <span id="range_pos" class="histshow dsp_none">126/200</span>

          <!-- インターバルボタンズ --> 
          <span id="hist_int_buttons">

            <button id="histint_2" type="button" class="btn btn-sm btn-primary tippyspan histshow intbtns"
                    title="Change to 2 sec interval">2</button>
            <button id="histint_3" type="button" class="btn btn-sm btn-primary tippyspan histshow intbtns"
                    title="Change to 3 sec interval">3</button>

            <button id="histint_7" type="button" class="btn btn-sm btn-primary tippyspan histshow intbtns"
                    title="Change to 7 sec interval">7</button>

            <button id="histint_15" type="button" class="btn btn-sm btn-primary tippyspan histshow intbtns"
                    title="Change to 15 sec interval">15</button>

            <button id="histint_22" type="button" class="btn btn-sm btn-primary tippyspan histshow intbtns"
                    title="Change to 22 sec interval">22</button>

            <button id="histint_33" type="button" class="btn btn-sm btn-primary tippyspan histshow intbtns"
                    title="Change to 33 sec interval">33</button>

            <button id="histint_45" type="button" class="btn btn-sm btn-primary tippyspan histshow intbtns"
                    title="Change to 45 sec interval">45</button>

          </span>  

        </div>

タイマー関数


    /**
     * 飛行機マーカー描画タイマー関数
     * @param {type} interval タイマーインターバル (ms)
     * @returns {undefined}
     */
    const timerFunc = (interval) => {

      $("#timerint").removeClass("dsp_none")
      $("#timerint").show()
      $("#timerint").text("T:" + parseInt(interval / 1000))

      HistoryMap.histTimer = setInterval(() => {

        if (!isPaused) {

          const slidepos = eval(recpos) + eval(1)

          // スライダーを動かす //
          $("#histrange").val(recpos)
          $("#range_pos").text(`${slidepos}/${recs.length}`)

          console.log("TimerPos:" + recpos)
          const tmpdata = JSON.parse(recs[recpos]["TXT_JSON"])

          // 時計更新 //
          const currtmtxt = recs[recpos]["TM_ADD"].slice(5, 16).replace("-", "/") + "(JST)"
          const currtmtxtUTC = recs[recpos]["TM_ADD_UTC"].slice(5, 16).replace("-", "/") + "(UTC)"
          const postxt = `${eval(recpos) + eval(1)}/${recs.length}`
          console.log(currtmtxt)

          $("#historymap_time").text(currtmtxt + "  " + currtmtxtUTC + "  " + postxt)

          // 中心、ズーム //
          const clat = recs[recpos]["CLAT"]
          const clon = recs[recpos]["CLON"]
          const zoom = recs[recpos]["ZOOM"]
          //lmap.setView([clat, clon], zoom)
          lmap.flyTo([clat, clon], zoom)
          //lmap.panTo([clat, clon], zoom)


          // 飛行機マーカー描画 //
          const mmp = new MoveMarkerParent("", this.userId, false,
                  false, "", "")
          mmp.setTypeAndVoice("HISTORY", recs[recpos]["MP3_MSG"])
          const mkres = mmp.drawAircraftMarkers(lmap, tmpdata, false, false)
          console.log(mkres)

          recpos++;

          if (recpos === recs.length) {
           
            recpos = 0
          }

        }

      }, interval)

    }

初回読込


    let isPaused = false    // 停止用フラグ
    let recpos = 1          // 再生位置
 
    let intvl = Math.floor(this.firstInterval * 3 / 4 * 1000)

    // 初期インターバルボタン強調 //
    $("#histint_" + parseInt(intvl / 1000)).removeClass("btn-primary")
    $("#histint_" + parseInt(intvl / 1000)).addClass("btn-dark")

    // ライブ受信ランプ 受信コストを常に消す //
    $("#liveramp, #t2scost, #livecost").addClass("dsp_none")
    $("#liveramp, #t2scost, #livecost").hide()

    //alert(intvl)
    // 飛行機マーカー描画タイマー関数 //
    timerFunc(intvl)

停止、再開ボタンのリスナー


    // @@ 停止再開ボタンを押した時 @@ //
    $(".pauseresume").on("click", function (e) {

      const id = $(this).attr("id")
      isPaused = id === "histpause"
      $(this).addClass("dsp_none")
      $(this).hide()

      const opobtnid = isPaused ? "#histresume" : "#histpause"
      $(opobtnid).removeClass("dsp_none")
      $(opobtnid).show()

    })

スライダーでの位置変更


    $("#histrange").off("input")

    // @@ レンジスライダーの値が変わる時 @@ //
    // ユーザーが変えた場合発生、タイマーからは発生しない //
    $("#histrange").on("input", function (e) {

      const val = $(this).val()
      console.log("# input # slider pos is " + val)

      $("#range_pos").text(`${val}/${recs.length}`)

      isPaused = true

    })

再生インターバル変更ボタンのリスナー


    $(".intbtns").off("click")

    // @@ インターバル変更ボタンを押した時 @@ //
    $(".intbtns").on("click", function (e) {

      const val = $(this).text()
      //alert(val)

      $(".intbtns").removeClass("btn-dark")
      $(".intbtns").addClass("btn-primary")
      $(this).addClass("btn-dark")

      // タイマー再セットインターバル変更 //
      try {
        clearInterval(HistoryMap.histTimer)
      }
      catch (e) {
      }

      // 飛行機マーカー描画タイマー関数 //
      timerFunc(val * 1000)


    })

< 画面例 >
ボタンのテキストはインターバルの秒数です。
スライダーはHTMLの標準のを使ってスタイルはここを真似ました。

[商品価格に関しましては、リンクが作成された時点と現時点で情報が変更されている場合がございます。]

航空情報 2023年 4月号 [雑誌]
価格:1425円(税込、送料無料) (2023/3/3時点)

[商品価格に関しましては、リンクが作成された時点と現時点で情報が変更されている場合がございます。]

航空ファン 2023年 4月号 [雑誌]
価格:1361円(税込、送料無料) (2023/3/3時点)