今日の重力の影響力ウィジェット
設定難易度

今いる場所の「現在の重力」を表示するScriptableウィジェットです。

地球上の重力は常にまったく同じではなく、緯度や標高に加えて、月や太陽の位置による潮汐の影響でわずかに変化しています。
このウィジェットでは、設定した地点の緯度・経度・標高から基準となる重力を計算し、さらに月と太陽による影響を加えて、現在の重力を m/s² で表示します。
昨日の同じ時刻との差や、その日の最大・最小も確認できます。
さらに毎日の重力データを端末内に保存するため、使い続けることで「観測史上MAX / MIN」や過去30日の変化も確認できるようになります。

背景には宇宙から見た地球を配置し、ホーム画面で現在の重力を眺められるウィジェットにしました。

ウィジェットの表示内容

観測地点・標高・体重などから重力を近似計算し、実行時の値を履歴へ保存します。

サイズごとの表示

  • 小:重力の値と変化、自分への影響の目安を表示します。
  • 中:当日の最大・最小なども確認できます。
  • 大:保存した履歴やグラフ、観測開始からの情報を表示します。

設定手順

上から順に進めてください。コードの保存と設定のコピーに必要な操作欄は、それぞれの手順の中にあります。

  1. Scriptableを用意する

    App StoreでScriptableを開き、iPhoneにインストールします。すでに入っている場合は次へ進んでください。

  2. コードをコピーして保存する

    下のコード欄の「コピー」を押します。Scriptableへ移動し、「+」から新しいスクリプトを作ってコード全文を貼り付け、現在の重力ウィジェットなどの名前で保存してください。

    保存したらこのページに戻り、次の手順へ進みます。

    // Variables used by Scriptable.
    // These must be at the very top of the file. Do not edit.
    // icon-color: deep-blue; icon-glyph: globe;
    // Version: 1.0.0
    
    // =====================================================
    // 現在の重力 - Scriptable Widget
    // =====================================================
    //
    // Parameter:
    //   緯度,経度,標高(m),体重(kg)
    //
    // 例:
    //   34.7000,135.5000,100,67
    //
    // ・APIキー不要
    // ・背景画像のみ初回通信
    // ・背景画像はローカルキャッシュ
    // ・緯度 / 標高から基準重力を計算
    // ・月 / 太陽による潮汐重力を近似計算
    // ・現在の重力
    // ・昨日との差
    // ・今日の最大 / 最小
    // ・あなたへの影響
    // ・1日ごとの履歴保存
    // ・観測開始以来の最高 / 最低
    // ・大サイズでは過去30日のグラフ
    //
    // ※理論計算による近似値です。
    // =====================================================
    
    
    // =====================================================
    // Parameter
    // =====================================================
    
    const rawParam = (args.widgetParameter || "").trim();
    
    const parts = rawParam
      .split(",")
      .map(v => v.trim());
    
    let LAT = parseFloat(parts[0]);
    let LON = parseFloat(parts[1]);
    let ALT = parseFloat(parts[2]);
    let BODY_WEIGHT = parseFloat(parts[3]);
    
    if (!Number.isFinite(LAT)) LAT = 35.0;
    if (!Number.isFinite(LON)) LON = 135.0;
    if (!Number.isFinite(ALT)) ALT = 0;
    if (!Number.isFinite(BODY_WEIGHT)) BODY_WEIGHT = 60;
    
    LAT = Math.max(-90, Math.min(90, LAT));
    LON = Math.max(-180, Math.min(180, LON));
    ALT = Math.max(-500, Math.min(10000, ALT));
    BODY_WEIGHT = Math.max(1, Math.min(500, BODY_WEIGHT));
    
    
    // =====================================================
    // Constants
    // =====================================================
    
    const G = 6.67430e-11;
    
    const MASS_MOON = 7.342e22;
    const MASS_SUN = 1.98847e30;
    
    const AU = 149597870700;
    const EARTH_RADIUS = 6371008.8;
    
    const DEG = Math.PI / 180;
    
    
    // =====================================================
    // FileManager
    // =====================================================
    
    const fm = FileManager.local();
    
    const dataDir = fm.joinPath(
      fm.documentsDirectory(),
      "GravityWidget"
    );
    
    if (!fm.fileExists(dataDir)) {
      fm.createDirectory(dataDir, true);
    }
    
    
    // =====================================================
    // Background
    // =====================================================
    
    const BACKGROUND_URL =
      "https://geboku.com/up/earth.png";
    
    const backgroundPath =
      fm.joinPath(
        dataDir,
        "earth.png"
      );
    
    
    async function loadBackground() {
    
      // キャッシュを優先
      if (fm.fileExists(backgroundPath)) {
    
        try {
          return fm.readImage(backgroundPath);
        } catch (e) {
    
          try {
            fm.remove(backgroundPath);
          } catch (_) {}
    
        }
    
      }
    
    
      // 初回のみ取得
      try {
    
        const req = new Request(BACKGROUND_URL);
    
        const image = await req.loadImage();
    
        fm.writeImage(
          backgroundPath,
          image
        );
    
        return image;
    
      } catch (e) {
    
        return null;
    
      }
    
    }
    
    
    // =====================================================
    // 背景画像を暗くする
    // =====================================================
    
    function darkenImage(image, opacity = 0.48) {
    
      const ctx = new DrawContext();
    
      ctx.size = image.size;
      ctx.opaque = false;
      ctx.respectScreenScale = true;
    
      ctx.drawImageAtPoint(
        image,
        new Point(0, 0)
      );
    
      ctx.setFillColor(
        new Color(
          "#000000",
          opacity
        )
      );
    
      ctx.fillRect(
        new Rect(
          0,
          0,
          image.size.width,
          image.size.height
        )
      );
    
      return ctx.getImage();
    
    }
    
    
    // =====================================================
    // Date helpers
    // =====================================================
    
    function pad2(n) {
      return String(n).padStart(2, "0");
    }
    
    
    function dateKey(date) {
    
      return (
        date.getFullYear() +
        "-" +
        pad2(date.getMonth() + 1) +
        "-" +
        pad2(date.getDate())
      );
    
    }
    
    
    function shortDate(dateString) {
    
      const p = dateString.split("-");
    
      if (p.length !== 3) return dateString;
    
      return `${Number(p[1])}/${Number(p[2])}`;
    
    }
    
    
    function formatStartDate(dateString) {
    
      const p = dateString.split("-");
    
      if (p.length !== 3) return dateString;
    
      return `${p[0]}.${pad2(p[1])}.${pad2(p[2])}`;
    
    }
    
    
    // =====================================================
    // Julian Date
    // =====================================================
    
    function julianDate(date) {
    
      return (
        date.getTime() / 86400000 +
        2440587.5
      );
    
    }
    
    
    // =====================================================
    // Angle helpers
    // =====================================================
    
    function normalize360(v) {
    
      v %= 360;
    
      if (v < 0) v += 360;
    
      return v;
    
    }
    
    
    function sinD(v) {
      return Math.sin(v * DEG);
    }
    
    
    function cosD(v) {
      return Math.cos(v * DEG);
    }
    
    
    // =====================================================
    // 基準重力
    // WGS84 Somigliana + free-air correction
    // =====================================================
    
    function normalGravity(latDeg, altitudeM) {
    
      const lat = latDeg * DEG;
    
      const sinLat = Math.sin(lat);
      const sin2 = sinLat * sinLat;
    
      const gamma =
        9.7803253359 *
        (
          1 +
          0.00193185265241 * sin2
        ) /
        Math.sqrt(
          1 -
          0.00669437999013 * sin2
        );
    
      return (
        gamma -
        3.086e-6 * altitudeM
      );
    
    }
    
    
    const BASE_GRAVITY =
      normalGravity(
        LAT,
        ALT
      );
    
    
    // =====================================================
    // 太陽位置
    // =====================================================
    
    function sunPosition(date) {
    
      const jd = julianDate(date);
    
      const n =
        jd - 2451545.0;
    
      const L =
        normalize360(
          280.460 +
          0.9856474 * n
        );
    
      const g =
        normalize360(
          357.528 +
          0.9856003 * n
        );
    
      const lambda =
        normalize360(
          L +
          1.915 * sinD(g) +
          0.020 * sinD(2 * g)
        );
    
      const epsilon =
        23.439 -
        0.0000004 * n;
    
      const lambdaRad =
        lambda * DEG;
    
      const epsilonRad =
        epsilon * DEG;
    
      const ra =
        Math.atan2(
          Math.cos(epsilonRad) *
          Math.sin(lambdaRad),
          Math.cos(lambdaRad)
        );
    
      const dec =
        Math.asin(
          Math.sin(epsilonRad) *
          Math.sin(lambdaRad)
        );
    
      const distanceAU =
        1.00014 -
        0.01671 * cosD(g) -
        0.00014 * cosD(2 * g);
    
      return {
    
        ra:
          normalize360(
            ra / DEG
          ),
    
        dec:
          dec / DEG,
    
        distance:
          distanceAU * AU
    
      };
    
    }
    
    
    // =====================================================
    // 月位置
    // =====================================================
    
    function moonPosition(date) {
    
      const jd = julianDate(date);
    
      const d =
        jd - 2451543.5;
    
      const N =
        normalize360(
          125.1228 -
          0.0529538083 * d
        );
    
      const i = 5.1454;
    
      const w =
        normalize360(
          318.0634 +
          0.1643573223 * d
        );
    
      const a = 60.2666;
      const e = 0.054900;
    
      const M =
        normalize360(
          115.3654 +
          13.0649929509 * d
        );
    
    
      let E =
        M +
        (180 / Math.PI) *
        e *
        sinD(M) *
        (
          1 +
          e * cosD(M)
        );
    
    
      const xv =
        a *
        (
          cosD(E) -
          e
        );
    
    
      const yv =
        a *
        (
          Math.sqrt(
            1 - e * e
          ) *
          sinD(E)
        );
    
    
      const v =
        Math.atan2(
          yv,
          xv
        ) / DEG;
    
    
      const r =
        Math.sqrt(
          xv * xv +
          yv * yv
        );
    
    
      const xh =
        r *
        (
          cosD(N) *
          cosD(v + w) -
          sinD(N) *
          sinD(v + w) *
          cosD(i)
        );
    
    
      const yh =
        r *
        (
          sinD(N) *
          cosD(v + w) +
          cosD(N) *
          sinD(v + w) *
          cosD(i)
        );
    
    
      const zh =
        r *
        (
          sinD(v + w) *
          sinD(i)
        );
    
    
      const lon =
        Math.atan2(
          yh,
          xh
        );
    
    
      const lat =
        Math.atan2(
          zh,
          Math.sqrt(
            xh * xh +
            yh * yh
          )
        );
    
    
      const epsilon =
        (
          23.4393 -
          3.563e-7 * d
        ) *
        DEG;
    
    
      const xe =
        Math.cos(lon) *
        Math.cos(lat);
    
    
      const ye =
        Math.sin(lon) *
        Math.cos(lat) *
        Math.cos(epsilon) -
        Math.sin(lat) *
        Math.sin(epsilon);
    
    
      const ze =
        Math.sin(lon) *
        Math.cos(lat) *
        Math.sin(epsilon) +
        Math.sin(lat) *
        Math.cos(epsilon);
    
    
      const ra =
        Math.atan2(
          ye,
          xe
        );
    
    
      const dec =
        Math.atan2(
          ze,
          Math.sqrt(
            xe * xe +
            ye * ye
          )
        );
    
    
      return {
    
        ra:
          normalize360(
            ra / DEG
          ),
    
        dec:
          dec / DEG,
    
        distance:
          r * EARTH_RADIUS
    
      };
    
    }
    
    
    // =====================================================
    // Greenwich Sidereal Time
    // =====================================================
    
    function gmst(date) {
    
      const jd = julianDate(date);
    
      const T =
        (
          jd -
          2451545.0
        ) /
        36525;
    
    
      const theta =
        280.46061837 +
        360.98564736629 *
        (
          jd -
          2451545.0
        ) +
        0.000387933 *
        T *
        T -
        T *
        T *
        T /
        38710000;
    
    
      return normalize360(theta);
    
    }
    
    
    // =====================================================
    // 天頂角
    // =====================================================
    
    function zenithCos(date, body) {
    
      const lst =
        normalize360(
          gmst(date) +
          LON
        );
    
    
      let hourAngle =
        normalize360(
          lst -
          body.ra
        );
    
    
      if (hourAngle > 180) {
        hourAngle -= 360;
      }
    
    
      const lat =
        LAT * DEG;
    
      const dec =
        body.dec * DEG;
    
      const H =
        hourAngle * DEG;
    
    
      return (
        Math.sin(lat) *
        Math.sin(dec) +
        Math.cos(lat) *
        Math.cos(dec) *
        Math.cos(H)
      );
    
    }
    
    
    // =====================================================
    // 潮汐重力
    // =====================================================
    
    function tidalGravity(
      date,
      body,
      mass
    ) {
    
      const cosZ =
        zenithCos(
          date,
          body
        );
    
    
      const factor =
        (
          G *
          mass *
          EARTH_RADIUS
        ) /
        Math.pow(
          body.distance,
          3
        );
    
    
      return (
        factor *
        (
          3 *
          cosZ *
          cosZ -
          1
        )
      );
    
    }
    
    
    // =====================================================
    // 重力計算
    // =====================================================
    
    function gravityAt(date) {
    
      const sun =
        sunPosition(date);
    
      const moon =
        moonPosition(date);
    
    
      const moonTide =
        tidalGravity(
          date,
          moon,
          MASS_MOON
        );
    
    
      const sunTide =
        tidalGravity(
          date,
          sun,
          MASS_SUN
        );
    
    
      return {
    
        gravity:
          BASE_GRAVITY +
          moonTide +
          sunTide,
    
        moon:
          moonTide,
    
        sun:
          sunTide
    
      };
    
    }
    
    
    // =====================================================
    // 現在値
    // =====================================================
    
    const now = new Date();
    
    const current =
      gravityAt(now);
    
    
    // =====================================================
    // 昨日同時刻
    // =====================================================
    
    const yesterday =
      new Date(
        now.getTime() -
        86400000
      );
    
    
    const yesterdayValue =
      gravityAt(yesterday);
    
    
    const yesterdayDiff =
      current.gravity -
      yesterdayValue.gravity;
    
    
    // =====================================================
    // 今日の最大 / 最小 / 平均
    // 10分間隔
    // =====================================================
    
    function dailyRange(date) {
    
      const start =
        new Date(
          date.getFullYear(),
          date.getMonth(),
          date.getDate(),
          0,
          0,
          0
        );
    
    
      let min = Infinity;
      let max = -Infinity;
      let sum = 0;
      let count = 0;
    
    
      for (
        let m = 0;
        m < 1440;
        m += 10
      ) {
    
        const t =
          new Date(
            start.getTime() +
            m * 60000
          );
    
    
        const g =
          gravityAt(t).gravity;
    
    
        min =
          Math.min(
            min,
            g
          );
    
    
        max =
          Math.max(
            max,
            g
          );
    
    
        sum += g;
    
        count++;
    
      }
    
    
      return {
    
        min,
    
        max,
    
        average:
          sum / count
    
      };
    
    }
    
    
    const todayRange =
      dailyRange(now);
    
    
    // =====================================================
    // あなたへの影響
    //
    // 基準重力との差を
    // 体重計上の相当差に換算
    // =====================================================
    
    const weightEffectKg =
      BODY_WEIGHT *
      (
        current.gravity /
        BASE_GRAVITY -
        1
      );
    
    
    const weightEffectG =
      weightEffectKg * 1000;
    
    
    // =====================================================
    // History
    // =====================================================
    
    const locationID =
      `${LAT.toFixed(4)}_${LON.toFixed(4)}_${Math.round(ALT)}`;
    
    
    const historyPath =
      fm.joinPath(
        dataDir,
        `history_${locationID}.json`
      );
    
    
    let history = [];
    
    
    if (
      fm.fileExists(
        historyPath
      )
    ) {
    
      try {
    
        history =
          JSON.parse(
            fm.readString(
              historyPath
            )
          );
    
    
        if (
          !Array.isArray(history)
        ) {
    
          history = [];
    
        }
    
      } catch (e) {
    
        history = [];
    
      }
    
    }
    
    
    // =====================================================
    // 今日の履歴
    // =====================================================
    
    const todayKey =
      dateKey(now);
    
    
    const todayRecord = {
    
      date:
        todayKey,
    
      min:
        todayRange.min,
    
      max:
        todayRange.max,
    
      average:
        todayRange.average
    
    };
    
    
    const existingIndex =
      history.findIndex(
        item =>
          item.date === todayKey
      );
    
    
    if (
      existingIndex >= 0
    ) {
    
      history[existingIndex] =
        todayRecord;
    
    } else {
    
      history.push(
        todayRecord
      );
    
    }
    
    
    history.sort(
      (a, b) =>
        a.date.localeCompare(
          b.date
        )
    );
    
    
    // =====================================================
    // 履歴保存
    // =====================================================
    
    try {
    
      fm.writeString(
        historyPath,
        JSON.stringify(history)
      );
    
    } catch (e) {}
    
    
    // =====================================================
    // 観測史上 MAX / MIN
    // =====================================================
    
    let recordMax =
      todayRecord;
    
    
    let recordMin =
      todayRecord;
    
    
    for (
      const item of history
    ) {
    
      if (
        item.max >
        recordMax.max
      ) {
    
        recordMax =
          item;
    
      }
    
    
      if (
        item.min <
        recordMin.min
      ) {
    
        recordMin =
          item;
    
      }
    
    }
    
    
    const firstRecord =
      history.length
        ? history[0]
        : todayRecord;
    
    
    // =====================================================
    // Formatting
    // =====================================================
    
    function gravityText(value) {
    
      return value.toFixed(7);
    
    }
    
    
    function signedGravity(value) {
    
      const sign =
        value > 0
          ? "+"
          : value < 0
            ? "−"
            : "±";
    
    
      return (
        sign +
        Math.abs(value).toFixed(7)
      );
    
    }
    
    
    function weightText(value) {
    
      if (
        Math.abs(value) <
        0.005
      ) {
    
        return "±0.00 g";
    
      }
    
    
      const sign =
        value > 0
          ? "+"
          : "−";
    
    
      return (
        sign +
        Math.abs(value).toFixed(2) +
        " g"
      );
    
    }
    
    
    // =====================================================
    // Widget
    // =====================================================
    
    const widget =
      new ListWidget();
    
    
    widget.setPadding(
      14,
      14,
      14,
      14
    );
    
    
    // =====================================================
    // 背景設定
    // =====================================================
    
    const backgroundImage =
      await loadBackground();
    
    
    if (backgroundImage) {
    
      widget.backgroundImage =
        darkenImage(
          backgroundImage,
          0.48
        );
    
    } else {
    
      widget.backgroundColor =
        new Color("#10141d");
    
    }
    
    
    // =====================================================
    // Text helpers
    // =====================================================
    
    function addText(
      stack,
      text,
      size,
      opacity = 1,
      bold = false
    ) {
    
      const t =
        stack.addText(text);
    
    
      t.font =
        bold
          ? Font.boldSystemFont(size)
          : Font.systemFont(size);
    
    
      t.textColor =
        new Color(
          "#ffffff",
          opacity
        );
    
    
      t.shadowColor =
        new Color(
          "#000000",
          0.65
        );
    
    
      t.shadowRadius = 2;
    
    
      t.shadowOffset =
        new Point(
          0,
          1
        );
    
    
      return t;
    
    }
    
    
    function addLabel(
      stack,
      text
    ) {
    
      return addText(
        stack,
        text,
        10,
        0.65,
        false
      );
    
    }
    
    
    // =====================================================
    // Header
    // =====================================================
    
    function addHeader() {
    
      const row =
        widget.addStack();
    
    
      row.centerAlignContent();
    
    
      addText(
        row,
        "🌍",
        15
      );
    
    
      row.addSpacer(5);
    
    
      addText(
        row,
        "現在の重力",
        12,
        0.8,
        true
      );
    
    }
    
    
    // =====================================================
    // Graph
    // =====================================================
    
    function createGraph(
      records,
      width = 620,
      height = 125
    ) {
    
      const draw =
        new DrawContext();
    
    
      draw.size =
        new Size(
          width,
          height
        );
    
    
      draw.opaque =
        false;
    
    
      draw.respectScreenScale =
        true;
    
    
      if (
        records.length < 2
      ) {
    
        return draw.getImage();
    
      }
    
    
      const values =
        records.map(
          r =>
            r.average
        );
    
    
      let min =
        Math.min(
          ...values
        );
    
    
      let max =
        Math.max(
          ...values
        );
    
    
      if (
        max === min
      ) {
    
        max += 0.000001;
        min -= 0.000001;
    
      }
    
    
      const pad = 8;
    
      const usableW =
        width -
        pad * 2;
    
      const usableH =
        height -
        pad * 2;
    
    
      const path =
        new Path();
    
    
      values.forEach(
        (v, i) => {
    
          const x =
            pad +
            (
              i /
              (
                values.length -
                1
              )
            ) *
            usableW;
    
    
          const y =
            pad +
            (
              1 -
              (
                v -
                min
              ) /
              (
                max -
                min
              )
            ) *
            usableH;
    
    
          const p =
            new Point(
              x,
              y
            );
    
    
          if (
            i === 0
          ) {
    
            path.move(p);
    
          } else {
    
            path.addLine(p);
    
          }
    
        }
      );
    
    
      draw.addPath(path);
    
    
      // 黒い縁取り
      draw.setStrokeColor(
        new Color(
          "#000000",
          0.5
        )
      );
    
    
      draw.setLineWidth(7);
    
      draw.strokePath();
    
    
      draw.addPath(path);
    
    
      // 白線
      draw.setStrokeColor(
        new Color(
          "#ffffff",
          0.95
        )
      );
    
    
      draw.setLineWidth(3);
    
      draw.strokePath();
    
    
      return draw.getImage();
    
    }
    
    
    // =====================================================
    // SMALL
    // =====================================================
    
    function renderSmall() {
    
      addHeader();
    
    
      widget.addSpacer(8);
    
    
      addText(
        widget,
        gravityText(
          current.gravity
        ),
        19,
        1,
        true
      );
    
    
      addText(
        widget,
        "m/s²",
        10,
        0.55
      );
    
    
      widget.addSpacer();
    
    
      addLabel(
        widget,
        "昨日との差"
      );
    
    
      addText(
        widget,
        signedGravity(
          yesterdayDiff
        ),
        11,
        0.9,
        true
      );
    
    
      widget.addSpacer(7);
    
    
      addLabel(
        widget,
        "あなたへの影響"
      );
    
    
      addText(
        widget,
        weightText(
          weightEffectG
        ),
        14,
        1,
        true
      );
    
    }
    
    
    // =====================================================
    // MEDIUM
    // =====================================================
    
    function renderMedium() {
    
      addHeader();
    
    
      widget.addSpacer(5);
    
    
      const main =
        widget.addStack();
    
    
      main.centerAlignContent();
    
    
      addText(
        main,
        gravityText(
          current.gravity
        ),
        22,
        1,
        true
      );
    
    
      main.addSpacer(5);
    
    
      addText(
        main,
        "m/s²",
        10,
        0.55
      );
    
    
      widget.addSpacer(8);
    
    
      const cols =
        widget.addStack();
    
    
      // LEFT
      const left =
        cols.addStack();
    
    
      left.layoutVertically();
    
    
      addLabel(
        left,
        "昨日との差"
      );
    
    
      addText(
        left,
        signedGravity(
          yesterdayDiff
        ),
        11,
        0.95,
        true
      );
    
    
      left.addSpacer(7);
    
    
      addLabel(
        left,
        "あなたへの影響"
      );
    
    
      addText(
        left,
        weightText(
          weightEffectG
        ),
        14,
        1,
        true
      );
    
    
      cols.addSpacer();
    
    
      // RIGHT
      const right =
        cols.addStack();
    
    
      right.layoutVertically();
    
    
      addLabel(
        right,
        "今日の MAX"
      );
    
    
      addText(
        right,
        gravityText(
          todayRange.max
        ),
        10,
        0.95,
        true
      );
    
    
      right.addSpacer(7);
    
    
      addLabel(
        right,
        "今日の MIN"
      );
    
    
      addText(
        right,
        gravityText(
          todayRange.min
        ),
        10,
        0.95,
        true
      );
    
    }
    
    
    // =====================================================
    // LARGE
    // =====================================================
    
    function renderLarge() {
    
      addHeader();
    
    
      widget.addSpacer(6);
    
    
      const main =
        widget.addStack();
    
    
      main.centerAlignContent();
    
    
      addText(
        main,
        gravityText(
          current.gravity
        ),
        25,
        1,
        true
      );
    
    
      main.addSpacer(6);
    
    
      addText(
        main,
        "m/s²",
        11,
        0.55
      );
    
    
      widget.addSpacer(4);
    
    
      addText(
        widget,
        `昨日との差 ${signedGravity(yesterdayDiff)}`,
        10,
        0.7
      );
    
    
      widget.addSpacer(10);
    
    
      // あなたへの影響
      addLabel(
        widget,
        "あなたへの影響"
      );
    
    
      addText(
        widget,
        weightText(
          weightEffectG
        ),
        18,
        1,
        true
      );
    
    
      widget.addSpacer(10);
    
    
      // 今日
      const today =
        widget.addStack();
    
    
      const todayLeft =
        today.addStack();
    
    
      todayLeft.layoutVertically();
    
    
      addLabel(
        todayLeft,
        "今日の MAX"
      );
    
    
      addText(
        todayLeft,
        gravityText(
          todayRange.max
        ),
        11,
        0.95,
        true
      );
    
    
      today.addSpacer();
    
    
      const todayRight =
        today.addStack();
    
    
      todayRight.layoutVertically();
    
    
      addLabel(
        todayRight,
        "今日の MIN"
      );
    
    
      addText(
        todayRight,
        gravityText(
          todayRange.min
        ),
        11,
        0.95,
        true
      );
    
    
      widget.addSpacer(11);
    
    
      // 観測史上
      const records =
        widget.addStack();
    
    
      const maxCol =
        records.addStack();
    
    
      maxCol.layoutVertically();
    
    
      addLabel(
        maxCol,
        "観測史上 MAX"
      );
    
    
      addText(
        maxCol,
        gravityText(
          recordMax.max
        ),
        11,
        0.95,
        true
      );
    
    
      addText(
        maxCol,
        shortDate(
          recordMax.date
        ),
        9,
        0.55
      );
    
    
      records.addSpacer();
    
    
      const minCol =
        records.addStack();
    
    
      minCol.layoutVertically();
    
    
      addLabel(
        minCol,
        "観測史上 MIN"
      );
    
    
      addText(
        minCol,
        gravityText(
          recordMin.min
        ),
        11,
        0.95,
        true
      );
    
    
      addText(
        minCol,
        shortDate(
          recordMin.date
        ),
        9,
        0.55
      );
    
    
      widget.addSpacer(11);
    
    
      // Graph
      addLabel(
        widget,
        "過去30日の平均重力"
      );
    
    
      widget.addSpacer(4);
    
    
      const last30 =
        history.slice(-30);
    
    
      if (
        last30.length >= 2
      ) {
    
        const graph =
          widget.addImage(
            createGraph(
              last30
            )
          );
    
    
        graph.imageSize =
          new Size(
            280,
            55
          );
    
      } else {
    
        addText(
          widget,
          "履歴を記録中…",
          10,
          0.55
        );
    
      }
    
    
      widget.addSpacer();
    
    
      addText(
        widget,
        `観測開始 ${formatStartDate(firstRecord.date)}`,
        9,
        0.5
      );
    
    }
    
    
    // =====================================================
    // Render
    // =====================================================
    
    const family =
      config.widgetFamily ||
      "medium";
    
    
    if (
      family === "small"
    ) {
    
      renderSmall();
    
    } else if (
      family === "large"
    ) {
    
      renderLarge();
    
    } else {
    
      renderMedium();
    
    }
    
    
    // =====================================================
    // Refresh
    // =====================================================
    
    widget.refreshAfterDate =
      new Date(
        Date.now() +
        30 *
        60 *
        1000
      );
    
    
    // =====================================================
    // Finish
    // =====================================================
    
    if (
      config.runsInWidget
    ) {
    
      Script.setWidget(widget);
    
    } else {
    
      if (
        family === "small"
      ) {
    
        await widget.presentSmall();
    
      } else if (
        family === "large"
      ) {
    
        await widget.presentLarge();
    
      } else {
    
        await widget.presentMedium();
    
      }
    
    }
    
    
    Script.complete();
  3. ウィジェットの設定を作る

    下の入力欄で自分用の設定を作り、生成されたパラメーターをコピーします。詳しい設定仕様は、設定手順の下でも確認できます。

    重力ウィジェット パラメーター作成

    より正確に表示したい場合は、観測地点と体重を設定してください。
    すべて未入力でもウィジェットを使用できます。

    観測地点

    任意

    現在地に合わせた重力をより正確に計算するために使用します。



    未入力の場合は標準値で計算します。緯度・経度・標高はGoogleマップや地理院地図などで確認できます。

    あなたへの影響

    任意

    重力の変化による「あなたへの影響」の計算に使用します。

    入力した体重がウィジェット上に表示されることはありません。未入力の場合は60kgとして計算します。

    生成されたパラメーター


    この値をScriptableウィジェットの「Parameter」に貼り付けてください。

    コピーできたら、次の手順でホーム画面のウィジェットへ貼り付けます。

  4. ホーム画面に追加して設定する

    iPhoneのホーム画面を長押しし、ウィジェット追加画面からScriptableを選びます。この記事の対応サイズの説明に合わせてサイズを選んで追加してください。

    追加したウィジェットを長押しして「ウィジェットを編集」を開き、「Script」に保存したスクリプトを選びます。

    「Parameter」に直前の手順でコピーした値を貼り付けます。設定を変えるときも、同じ入力欄で作り直して貼り替えてください。

    編集画面を閉じて表示を確認します。ホーム画面への反映には少し時間がかかる場合があります。

パラメータ・カスタマイズ

パラメーター設定

パラメーターは入力しなくてもウィジェットを使用できます。
より正確に表示したい場合は、観測地点の「緯度・経度・標高」と「体重」を設定してください。

緯度・経度・標高
現在地に合わせた重力をより正確に計算するために使用します。未入力の場合は標準値で計算します。

体重
「あなたへの影響」の計算に使用します。入力した体重がウィジェット上に表示されることはありません。

すべて未入力でも、そのままウィジェットを使用できます。

緯度・経度・標高を調べるには

緯度・経度が分からない場合は、Googleマップなどで現在地を長押しすると確認できます。
標高は国土地理院の地理院地図などで確認できます。
おおよその値でもウィジェットは使用できますが、より正確な値を設定すると、その地点に合わせた重力を計算できます。

とはいえ、違いはものすごく、ごくごくわずかなので、こだわらなくても大丈夫かと思います(笑)

「あなたへの影響」とは?

重力が変化すると、同じ人でも重力によって受ける力がほんのわずかに変化します。
「あなたへの影響」では、設定した体重をもとに、現在の重力によって基準時と比べてどのくらい重く・軽くなるかを g(グラム)相当で表示します。
設定した体重そのものがウィジェットに表示されることはありません。

※一般的な体重計で確認できるほど大きな変化ではありません。

重力の履歴について

このウィジェットは、使用開始日から毎日の最大・最小・平均重力をScriptable内に保存します。
そのため、使い始めた直後は「観測史上MAX / MIN」が当日の値になります。
使い続けることでデータが蓄積され、大サイズでは過去30日の平均重力の変化をグラフで確認できます。
履歴データは非常に小さいため、長期間使用しても大きなストレージ容量を必要としません。

重力の値について

表示される重力は精密重力計による実測値ではありません。
緯度・標高から求めた基準重力と、日時・緯度・経度から計算した月・太陽による潮汐重力を組み合わせた理論上の近似値です。
地質や地下水、大気、地球潮汐による地面そのものの変形など、実際の重力に影響するすべての要素を再現するものではありません。
「今日、重力はどのくらい変化しているんだろう?」という変化を楽しむウィジェットとして使ってください。

動作確認・使用結果

確認した端末 iPhone 17 ProMAX
iOS iOS27
Scriptable 1.7.19
確認したサイズ 小・中・大
バージョン 1.0.0
動作確認日 2026-09-26

表示・使用時のポイント

観測地点・標高・体重などから重力を近似計算し、実行時の値を履歴へ保存します。履歴は使い始めた日から蓄積します。

更新履歴

1.0.0

  • 初回リリース

よくあるエラーと対処

確認すること
表示されない場合は、ウィジェット編集画面の「Script」に保存したスクリプトが選ばれているかも確認してください。コードを貼り直すときは全文を置き換えます。
履歴が少ない・グラフが短い
まだ十分な記録がたまっていないためです。継続して使うと履歴が増えます。
体重への影響が違う
設定した体重と観測地点の値を確認してください。
背景が出ない
初回の背景画像取得には通信が必要です。通信を確認して再実行してください。

注意事項・制限

  • センサーで測定した重力ではなく、モデルによる計算値です。
  • 「あなたへの影響」は計算上の目安で、体重計の測定値や体調を表すものではありません。
  • ホーム画面の更新タイミングはiOSが管理するため、操作やデータの変更がすぐに反映されない場合があります。

データ提供・参考資料

計算方式:コード内の地球の重力モデルと時間変化の近似式を使用しています。背景画像の取得と、重力の計算は別の処理です。

SHARE

この記事をシェア