[{"content":"ソニーのワイヤレスイヤホン WF-C510 のマルチポイント接続を設定したのでメモです。充電ケースのボタンの使い方（ペアリング・リセット・初期化）も調べたのでまとめておきます。\nなお、WF-C510 にはマイクがないです。通話には使えませんが、自分はマイクなしで十分です。 その分なのかマイクのある別のモデルと比べると半分以下ぐらいの値段で、音質に不満もありません。 接続に切り替えにスマホアプリが必要なのがマイナスですが、今のところ許容範囲です。\nマルチポイントとは 2台の機器に同時接続できる機能です。\nたとえば PC で動画を見ていて、スマートフォンで音楽を再生し始めるだけで切り替えられます。スマートフォンに着信があったときは自動でそちらに切り替わります。\n設定に必要なもの スマートフォン専用アプリ「Sony | Sound Connect」（旧：Headphones Connect）が必要です。\nGoogle Play（Android） App Store（iPhone） ※ PC（Mac・Windows）版アプリは公式には提供されていないです。マルチポイントの設定はスマートフォンから行う必要があります。\n設定手順 事前に WF-C510 を接続したい 2台の機器それぞれにペアリングしておく必要があります。\nSony | Sound Connect を起動し、WF-C510 と Bluetooth 接続する 「システム」タブを選択し、「2台の機器と同時に接続」をタップして ON にする 「ステータス」タブを選択し、接続中の機器の \u0026gt; をタップする 「接続機器の管理」画面で「1」と「2」それぞれをタップし、同時接続したい機器を「ペアリング済み」のリストから選択する PC をマルチポイントの接続先に指定することもできます。事前に WF-C510 と PC を Bluetooth ペアリングしておき、手順 4 で PC を選択するだけです。\n充電ケースのボタン操作 ケース背面に「ペアリング／リセット／初期化ボタン」があります。操作はすべてイヤホンをケースにセットし、ふたを開けた状態で行います。\nペアリング 約 5 秒長押しします。\nランプ（青）が 2回ずつ点滅すればペアリングモードになります。接続したい機器の Bluetooth 設定から「WF-C510」を選択します。\n※ 5 分以内にペアリングを完了しなかった場合はモードが解除されます。もう一度操作するといいでしょう。\nWF-C510 は最大 8台までペアリング情報を保存できます。9台目をペアリングすると、接続日時が最も古い情報が上書きされます。\nリセット 約 20 秒以上長押しします。\nランプ（オレンジ）が約 15 秒後から約 5秒間点滅します。ランプが消灯したことを確認してから指を離すとリセット完了です。ペアリング情報やその他の設定は保持されます。\n電源が入らない・操作ができないといった場合に試してみるといいでしょう。\n※ オレンジが点滅している間に指を離すと初期化になってしまいます。消灯してから離すのがポイントです。\n初期化 約 15 秒以上長押しし、ランプ（オレンジ）が点滅し始めたら 5秒以内に指を離します。\n完了するとランプ（緑）が 4回点滅します。音量などの設定が工場出荷時に戻り、ペアリング情報もすべて削除されます。\n※ 初期化後は接続していた機器側からも WF-C510 のペアリング情報を削除してから、再度ペアリングするといいでしょう。\nまとめ 操作 長押し時間 指を離すタイミング ペアリング情報 ペアリング 約 5 秒 ランプ点滅確認後 保持 リセット 約 20 秒以上 ランプ消灯後 保持 初期化 約 15 秒以上 オレンジ点滅中 削除 ペアリングとマルチポイント設定の違い ケースのボタンでのペアリングとマルチポイントの設定は別の操作です。\nケースのボタンはペアリング情報をリストに登録するだけで、同時接続の設定には関与しないです。マルチポイントを有効にするには、別途アプリから「2台の機器と同時に接続」を設定する必要があります。\n流れとしては以下のようになります。\nケースのボタンで接続したい機器をペアリング登録する アプリで「2台の機器と同時に接続」を ON にする 接続機器の管理画面で 1台目・2台目を指定する ケースのボタンはペアリングの登録、アプリはどの 2台を同時接続するかの設定、という役割分担ですね。\nソニー(SONY) 完全ワイヤレスイヤホン WF-C510 (Amazon) ","date":"2026-04-10T00:00:00+09:00","image":"/images/eyecatch/wf-c510.jpg","permalink":"/posts/it/smartphone/6211/","title":"WF-C510：マルチポイント接続を設定するには"},{"content":"タスクマネージャーのディスク使用率が 100% に張り付いているのに、プロセスタブを見ても犯人が見つからないことがありました。リアルタイムで目視するのに限界を感じたので、PowerShell で記録することにしたのでメモです。\nタスクマネージャーで見えない理由 タスクマネージャーの更新間隔はデフォルトで 1 秒ごとですが、0.5 秒以下で終わる処理はほぼ目に入りません。複数のプロセスが交互に動いている場合はさらに特定が難しいです。\nもうひとつ、これは後から気づいたことですが、タスクマネージャーの「ディスク 100%」はビジー率（ディスクが応答待ちになっていた時間の割合）であり、転送量が多いことを意味しないです。転送量が数百 KB/s しかなくても、応答が遅いディスクであれば 100% に張り付きます。\nなので「転送量が大きいプロセスを探せばいい」と思っていたのですが、記録してみると想定と全然違うデータが出てきました。\nGet-Counter を使う理由 PowerShell でプロセスごとの IO 量を取得する方法はいくつかあります。Get-Process でも .IO* プロパティが取れますが、これは起動してからの累計値なので、ある瞬間の転送速度にはなりません。差分を自分で計算する方法もありますが、タイミングを正確に揃えるのが意外と面倒です。\nGet-Counter を使うと、Windows のパフォーマンスカウンターを通じて「1 秒あたりの IO 量」を直接取得できます。OS が計算した瞬間速度なので、自前で差分を取る必要がないです。\nただ、プロセス単位のカウンターだけでは「ディスクが詰まっているのに犯人が見つからない」という状況に対処できないことがわかってきました。実際にこのスクリプトを使った調査でも、ビジー率が 47%・応答時間が 474ms という状況でプロセス単位のアラートは一件も出なかったです。転送量が少なくても応答が遅いディスクは詰まる、というビジー率の話がここでも出てきます。\nなので、スクリプトではプロセス単位とディスク全体の 2 つに分けて監視する構成にしました。\nプロセス単位の IO \\Process(*)\\IO Read Bytes/sec \\Process(*)\\IO Write Bytes/sec \\Process(*)\\ID Process 個々のプロセスが「どれだけ IO を出しているか」を転送量ベースで記録します。SearchIndexer や Windows Update のような特定プロセスの絞り込みに有効です。ただ、このカウンターはディスク IO だけでなくネットワークや名前付きパイプも含む「全 IO」の値です。ディスクだけを厳密に分離したい場合は Process Monitor（Sysinternals）が必要ですが、怪しいプロセスを絞り込む目的であれば全 IO で十分かなと思います。\nディスク全体の状態 \\PhysicalDisk(_Total)\\% Disk Time \\PhysicalDisk(_Total)\\Avg. Disk sec/Transfer \\PhysicalDisk(_Total)\\Avg. Disk Queue Length \\Process(System)\\IO Read Bytes/sec \\Process(System)\\IO Write Bytes/sec \\Memory\\Pages/sec タスクマネージャーと同じビジー率・応答時間・待ち行列に加え、カーネルやドライバー起因の IO（System プロセス）とメモリのページング発生数を同時に取得します。プロセス単位で犯人が見つからないとき、このデータが手がかりになります。\n2 つを組み合わせることで、転送量が少なくてもディスクが詰まる状況や、GPU ドライバーの不安定さに伴うシステム全体の IO 停滞なども記録できるようになりました。なお、これらすべてのカウンターは 1 回の Get-Counter 呼び出しでまとめて取得しています。2 回に分けると内部の待機処理が 2 回走り、ループ周期が余分に延びてしまうためです。\n5 秒間隔にした理由 最初は 1 秒ごとに記録しようとしました。試してみると、Get-Counter は内部で約 1 秒の待機処理を行う仕様になっていて、「1 秒待って取得 → 処理 → また 1 秒待って取得」と繰り返すとループ周期が 1 秒より確実に長くなりました。\n5 秒にすると、内部待機（約 1 秒）と処理時間を差し引いた残りをスリープに使えるので、実際の周期を正味 5 秒に近づけられます。ディスク負荷の調査であれば 5 秒あれば十分な粒度で、ログファイルのサイズも抑えられます。\nスクリプトの構成 CSV の書き込み CSV 保存には注意があります。PowerShell 5.1 の Export-Csv は -Encoding UTF8 を指定しても、BOM（バイトオーダーマーク）付きのファイルを出力することがあります。Excelで開くぶんには問題ないですが、他のツールで読み込む場合に文字化けの原因になります。なので今回は StreamWriter を使って BOM なし UTF-8 で直接書き込んでいます。\nプロセス名の #数字 について Chrome や Edge のように同じ名前のプロセスが複数起動している場合、パフォーマンスカウンター上では chrome、chrome#1、chrome#2 のように番号が振られます。これは単なる識別子で、PID とは直接対応しないです。スクリプト内では \\Process(*)\\ID Process カウンターを使って各インスタンスの PID を取得することで、どのプロセスが実際に動いていたかを特定できるようにしています。\n実行 管理者権限の PowerShell で実行します。\nSet-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass .\\Monitor-DiskUsage.ps1 スクリプト本文は以下のとおりです。\n# ============================================================ # Monitor-DiskUsage.ps1 - プロセスIO・ディスク負荷監視 # # 【重要な制約事項】 # \\Process(*)\\IO *Bytes/sec はディスク・ネットワーク・名前付きパイプを # 含む「全IO」を返します。ディスクIOのみの計測ではありません。 # EstimatedIOPct は推定最大スループットを分母とするため # 100% を超える場合があります。 # プロセス単位でディスクIOのみを厳密に計測したい場合は # Process Monitor (Sysinternals) または ETW を使用してください。 # # 【Get-Counter の負荷について】 # プロセス数が200を超える環境では Get-Counter の wildcard 展開が # 重くなる場合があります。その場合は -IntervalSeconds 10〜30 に # 増やすことで安定します。 # ============================================================ param( [int] $IntervalSeconds = 5, # サンプリング間隔（秒）正味待機時間 [double] $ThresholdPercent = 50.0, # プロセスIO使用率のアラートしきい値（%） [string] $OutputDir = \u0026#34;\u0026#34;, # 出力先フォルダ（省略時はスクリプト隣の IOMonitorLogs） [int] $MaxRunMinutes = 0, # 最大実行時間（分）。0 = 無制限 [int] $DiskCapacityMBs = 0, # ディスク最大スループット手動指定（MB/s）。0 = 自動推定 [int] $ConsoleTopN = 10, # コンソール表示の最大件数（CSV/ログは全件記録） [int] $QuietMinutes = 5, # 「しきい値超えなし」の出力間隔（分）。0 = 毎回出力 [int] $DiskBusyThreshold = 80, # ディスクビジー率のアラートしきい値（%） [double] $LatencyThresholdMs = 50.0 # ディスク平均応答時間のアラートしきい値（ms） ) # ------------------------------------------------------- # $PSScriptRoot フォールバック（対話実行・ドットソース実行対策） # ------------------------------------------------------- $scriptBase = if ($PSScriptRoot -and $PSScriptRoot -ne \u0026#39;\u0026#39;) { $PSScriptRoot } else { (Get-Location).Path } if ($OutputDir -eq \u0026#39;\u0026#39;) { $OutputDir = Join-Path $scriptBase \u0026#34;IOMonitorLogs\u0026#34; } # ------------------------------------------------------- # 初期化 # ------------------------------------------------------- if (-not (Test-Path $OutputDir)) { New-Item -ItemType Directory -Path $OutputDir | Out-Null } $datestamp = Get-Date -Format \u0026#34;yyyyMMdd_HHmmss\u0026#34; $csvFile = Join-Path $OutputDir \u0026#34;IOMonitor_$datestamp.csv\u0026#34; $logFile = Join-Path $OutputDir \u0026#34;IOMonitor_$datestamp.log\u0026#34; if (Test-Path $csvFile) { Remove-Item $csvFile -Force } # ------------------------------------------------------- # ログ出力関数 # ------------------------------------------------------- function Write-Log { param([string]$Message, [string]$Level = \u0026#34;INFO\u0026#34;) $ts = Get-Date -Format \u0026#34;yyyy-MM-dd HH:mm:ss\u0026#34; $line = \u0026#34;[$ts][$Level] $Message\u0026#34; $color = switch ($Level) { \u0026#34;ALERT\u0026#34; { \u0026#34;Red\u0026#34; } \u0026#34;WARN\u0026#34; { \u0026#34;Yellow\u0026#34; } default { \u0026#34;Cyan\u0026#34; } } Write-Host $line -ForegroundColor $color # PS5.1 では Out-File -Encoding UTF8 が BOM 付きになるため StreamWriter で回避 $sw = [System.IO.StreamWriter]::new($logFile, $true, [System.Text.UTF8Encoding]::new($false)) try { $sw.WriteLine($line) } finally { $sw.Close() } } # ------------------------------------------------------- # CSV 追記ヘルパー（BOM なし UTF-8 で StreamWriter 直書き） # PS5.1 の Export-Csv -Append -Encoding UTF8 は毎回 BOM を書き込み # ファイルが壊れる既知バグがあるため StreamWriter で回避。 # 毎回 open/close するが5秒に1回程度のオーバーヘッドは無視できる。 # 常時 open にすると Ctrl+C 割り込み時にバッファが未フラッシュのまま # ファイルが破損するリスクがあるため不採用。 # ------------------------------------------------------- function Append-CsvRow { param([PSCustomObject]$Row, [string]$FilePath) $needHeader = -not (Test-Path $FilePath) -or (Get-Item $FilePath).Length -eq 0 $sw = [System.IO.StreamWriter]::new( $FilePath, $true, [System.Text.UTF8Encoding]::new($false) ) try { if ($needHeader) { $sw.WriteLine(($Row.PSObject.Properties.Name -join \u0026#39;,\u0026#39;)) } $values = $Row.PSObject.Properties.Value | ForEach-Object { $v = \u0026#34;$_\u0026#34; # RFC 4180: カンマ・ダブルクォート・改行を含むフィールドはクォートで囲む if ($v -match \u0026#39;[,\u0026#34;\\r\\n]\u0026#39;) { \u0026#39;\u0026#34;\u0026#39; + $v.Replace(\u0026#39;\u0026#34;\u0026#39;,\u0026#39;\u0026#34;\u0026#34;\u0026#39;) + \u0026#39;\u0026#34;\u0026#39; } else { $v } } $sw.WriteLine($values -join \u0026#39;,\u0026#39;) } finally { $sw.Close() } } # ------------------------------------------------------- # ディスク最大スループット推定（バイト/秒） # 判定優先順位: # 1. 手動指定（-DiskCapacityMBs） # 2. C: ドライブの BusType（MSFT_Disk 経由、複数ディスク混在環境で最も正確） # 3. 全ディスクの BusType（C: 取得失敗時のフォールバック） # 4. Win32_DiskDrive の Model 名（BusType 取得失敗時のフォールバック） # 戻り値: [PSCustomObject]@{ Bytes=[long]; WarnMessage=[string] } # WarnMessage が空でない場合、呼び出し側でログに記録する # ------------------------------------------------------- function Get-DiskMaxThroughput { param([int]$ManualMBs) if ($ManualMBs -gt 0) { return [PSCustomObject]@{ Bytes = [long]($ManualMBs * 1MB); WarnMessage = \u0026#39;\u0026#39; } } # BusType からスループット推定値（バイト/秒）を返すヘルパー # BusType: 17=NVMe, 11=SATA, 10=SAS, 7=USB, 3=SCSI # 仮想環境（BusType=0）または未知の場合は [long]0 を返す function ConvertTo-ThroughputBytes([int]$BusType, [int]$MediaType) { if ($BusType -eq 17) { return [long](3000 * 1MB) } # NVMe PCIe3 if ($BusType -eq 11 -and $MediaType -eq 4) { return [long](500 * 1MB) } # SATA SSD if ($BusType -eq 11) { return [long](150 * 1MB) } # SATA HDD if ($BusType -eq 10) { return [long](300 * 1MB) } # SAS（保守的推定） if ($BusType -eq 7 -and $MediaType -eq 4) { return [long](400 * 1MB) } # USB SSD（USB3.0相当） if ($BusType -eq 7) { return [long](100 * 1MB) } # USB HDD return [long]0 # BusType=0（仮想環境等）または未知 } try { # ステップ1: C: ドライブの物理ディスクを特定して判定 # C: が存在しない環境（Server Core 等）は次のブロックへフォールバック $cPartition = Get-Partition -DriveLetter \u0026#39;C\u0026#39; -ErrorAction Stop $cDisk = $cPartition | Get-Disk -ErrorAction Stop $cDiskInfo = Get-CimInstance -ClassName MSFT_Disk ` -Namespace \u0026#39;root/Microsoft/Windows/Storage\u0026#39; -ErrorAction Stop | Where-Object { $_.Number -eq $cDisk.Number } | Select-Object -First 1 if ($cDiskInfo) { if ($cDiskInfo.BusType -eq 0) { # BusType=0 は仮想環境（Hyper-V/VMware）でよく見られる不定値 # 次のブロックへフォールバック } else { $bytes = ConvertTo-ThroughputBytes -BusType $cDiskInfo.BusType -MediaType $cDiskInfo.MediaType if ($bytes -gt 0) { return [PSCustomObject]@{ Bytes = $bytes; WarnMessage = \u0026#39;\u0026#39; } } } } } catch { \u0026lt;# C: ドライブ取得失敗 → フォールバックへ #\u0026gt; } try { # ステップ2: 全ディスクの BusType で判定（BusType=0 は仮想環境の不定値のため除外） $disks = Get-CimInstance -ClassName MSFT_Disk ` -Namespace \u0026#39;root/Microsoft/Windows/Storage\u0026#39; -ErrorAction Stop $hasNVMe = $disks | Where-Object { $_.BusType -eq 17 } $hasSSD = $disks | Where-Object { $_.BusType -ne 17 -and $_.BusType -ne 0 -and $_.MediaType -eq 4 } if ($hasNVMe) { return [PSCustomObject]@{ Bytes = [long](3000 * 1MB); WarnMessage = \u0026#39;\u0026#39; } } if ($hasSSD) { return [PSCustomObject]@{ Bytes = [long](500 * 1MB); WarnMessage = \u0026#39;\u0026#39; } } # ステップ3: Model 名によるフォールバック判定 $w32disks = Get-CimInstance -ClassName Win32_DiskDrive -ErrorAction Stop if ($w32disks | Where-Object { $_.Model -match \u0026#39;NVMe\u0026#39; }) { return [PSCustomObject]@{ Bytes = [long](3000 * 1MB); WarnMessage = \u0026#39;\u0026#39; } } if ($w32disks | Where-Object { $_.Model -match \u0026#39;SSD\u0026#39; -and $_.Model -notmatch \u0026#39;NVMe\u0026#39; }) { return [PSCustomObject]@{ Bytes = [long](500 * 1MB); WarnMessage = \u0026#39;\u0026#39; } } return [PSCustomObject]@{ Bytes = [long](150 * 1MB); WarnMessage = \u0026#39;\u0026#39; } } catch { $msg = \u0026#34;ディスク種別を判定できませんでした（仮想環境・権限不足の可能性）。\u0026#34; + \u0026#34;500 MB/s をデフォルト使用します。正確な監視には -DiskCapacityMBs で明示指定してください。\u0026#34; return [PSCustomObject]@{ Bytes = [long](500 * 1MB); WarnMessage = $msg } } } # ------------------------------------------------------- # 起動 # ------------------------------------------------------- $startTime = Get-Date $lastQuietLog = [datetime]::MinValue # 「しきい値超えなし」を最後にログした時刻 $diskResult = Get-DiskMaxThroughput -ManualMBs $DiskCapacityMBs $diskCapacity = $diskResult.Bytes $sampleNo = 0 $diskCounterFails = 0 # カウンター取得の連続失敗回数 Write-Log \u0026#34;監視開始 | 間隔=${IntervalSeconds}秒 | しきい値=${ThresholdPercent}% | 出力先=$OutputDir\u0026#34; Write-Log \u0026#34;ディスク最大スループット推定値: $([math]::Round($diskCapacity / 1MB)) MB/s$(if($DiskCapacityMBs -gt 0){\u0026#39; (手動指定)\u0026#39;}else{\u0026#39; (自動推定)\u0026#39;})\u0026#34; Write-Log \u0026#34;※ EstimatedIOPct は全IOを含む推定値のため 100% を超える場合があります\u0026#34; Write-Log \u0026#34;コンソール表示: 上位 $ConsoleTopN 件（CSV/ログは全件記録）\u0026#34; Write-Log \u0026#34;ディスクビジー率しきい値: ${DiskBusyThreshold}% | 応答時間しきい値: ${LatencyThresholdMs}ms\u0026#34; if ($diskResult.WarnMessage -ne \u0026#39;\u0026#39;) { Write-Log $diskResult.WarnMessage \u0026#34;WARN\u0026#34; } Write-Log \u0026#34;CSV : $csvFile\u0026#34; Write-Log \u0026#34;LOG : $logFile\u0026#34; Write-Log \u0026#34;停止するには Ctrl+C を押してください\u0026#34; Write-Host \u0026#34;\u0026#34; # ------------------------------------------------------- # メインループ # ------------------------------------------------------- try { while ($true) { if ($MaxRunMinutes -gt 0) { if (((Get-Date) - $startTime).TotalMinutes -ge $MaxRunMinutes) { Write-Log \u0026#34;最大実行時間 ${MaxRunMinutes} 分に達したため終了します。\u0026#34; break } } $sampleNo++ $loopStart = Get-Date $timestamp = $loopStart.ToString(\u0026#34;yyyy-MM-dd HH:mm:ss\u0026#34;) # --- 全カウンターを1回の Get-Counter で取得（2回呼び出すと周期が約2秒延びるため統合）--- # 取得するカウンター: # プロセス単位 IO・PID : InstanceName と PID を 1:1 対応づけ、犯人プロセスを特定 # PhysicalDisk : ビジー率・応答時間・待ち行列（タスクマネージャーと同じ指標） # Process(System) IO : ドライバ/カーネル起因の高IO検出 # Memory Pages/sec : メモリ不足によるページングの検出 # # -SampleInterval 1 : 内部で1回だけ約1秒待機してレート値を算出 # -ErrorAction SilentlyContinue : プロセスの起動・終了タイミングの無効サンプルを許容し # 後段で Status -ne 0 のサンプルを個別スキップする $diskBusyPct = 0.0 # ディスクビジー率（%） $diskLatencyMs = 0.0 # 平均応答時間（ms） $diskQueueLen = 0.0 # 待ち行列の長さ（2以上で深刻） $systemIOKBs = 0.0 # System プロセスの総IO（KB/s）＝ドライバ/カーネル起因IO $pagesPerSec = 0.0 # メモリページング発生数（100以上でメモリ不足の疑い） try { $allCounters = Get-Counter -Counter @( \u0026#39;\\PhysicalDisk(_Total)\\% Disk Time\u0026#39;, \u0026#39;\\PhysicalDisk(_Total)\\Avg. Disk sec/Transfer\u0026#39;, \u0026#39;\\PhysicalDisk(_Total)\\Avg. Disk Queue Length\u0026#39;, \u0026#39;\\Process(System)\\IO Read Bytes/sec\u0026#39;, \u0026#39;\\Process(System)\\IO Write Bytes/sec\u0026#39;, \u0026#39;\\Memory\\Pages/sec\u0026#39;, \u0026#39;\\Process(*)\\IO Read Bytes/sec\u0026#39;, \u0026#39;\\Process(*)\\IO Write Bytes/sec\u0026#39;, \u0026#39;\\Process(*)\\ID Process\u0026#39; ) -SampleInterval 1 -ErrorAction SilentlyContinue $diskCounterFails = 0 # 成功したらリセット } catch { $diskCounterFails++ Write-Log \u0026#34;カウンター取得失敗（スキップ、連続 ${diskCounterFails} 回目）: $_\u0026#34; \u0026#34;WARN\u0026#34; $failElapsed = ((Get-Date) - $loopStart).TotalSeconds $failWait = [math]::Max(0, $IntervalSeconds - $failElapsed) if ($failWait -gt 0) { Start-Sleep -Milliseconds ([int]($failWait * 1000)) } continue } if (-not $allCounters) { Write-Log \u0026#34;カウンターオブジェクトが空です（スキップ）\u0026#34; \u0026#34;WARN\u0026#34; $failElapsed = ((Get-Date) - $loopStart).TotalSeconds $failWait = [math]::Max(0, $IntervalSeconds - $failElapsed) if ($failWait -gt 0) { Start-Sleep -Milliseconds ([int]($failWait * 1000)) } continue } # --- 全サンプルを1回のループで振り分け（ディスク全体・プロセス単位を同時処理）--- # ① ディスク全体カウンター（PhysicalDisk・Memory）→ 専用変数へ # ② プロセス単位カウンター（Process(*)）→ readMap/writeMap/pidMap/nameMap へ # # 重要: Process(System) は $systemIOKBs に集計するが # readMap/writeMap には含めない（プロセス一覧への二重出力を防ぐ） $pathByPid = @{} $readMap = @{} $writeMap = @{} $pidMap = @{} # lowercase InstanceName → PID $nameMap = @{} # lowercase InstanceName → 元ケース InstanceName（表示用） foreach ($sample in $allCounters.CounterSamples) { if ($sample.Status -ne 0) { continue } $pl = $sample.Path.ToLowerInvariant() # ── ディスク全体・システム要因カウンター ── if ($pl -like \u0026#39;*% disk time*\u0026#39;) { $diskBusyPct = [math]::Round($sample.CookedValue, 1); continue } elseif ($pl -like \u0026#39;*avg. disk sec/transfer*\u0026#39;) { $diskLatencyMs = [math]::Round($sample.CookedValue * 1000, 1); continue } elseif ($pl -like \u0026#39;*avg. disk queue length*\u0026#39;) { $diskQueueLen = [math]::Round($sample.CookedValue, 2); continue } elseif ($pl -like \u0026#39;*process(system)*io*\u0026#39;) { $systemIOKBs += [math]::Round($sample.CookedValue / 1KB, 1); continue } elseif ($pl -like \u0026#39;*pages/sec*\u0026#39;) { $pagesPerSec = [math]::Round($sample.CookedValue, 1); continue } # ── プロセス単位カウンター ── # InstanceName が null のサンプルをスキップ（Status=0 でも稀に発生する） if ($null -eq $sample.InstanceName) { continue } $instRaw = $sample.InstanceName $inst = $instRaw.ToLowerInvariant() # _total・idle は二重計上防止のため除外 # system は上段で集計済みのためプロセス一覧には含めない if ($inst -eq \u0026#39;_total\u0026#39; -or $inst -eq \u0026#39;idle\u0026#39; -or $inst -eq \u0026#39;system\u0026#39;) { continue } $nameMap[$inst] = $instRaw # 表示用に元ケースを保存 if ($pl -like \u0026#39;*io read*\u0026#39;) { $readMap[$inst] = $sample.CookedValue } elseif ($pl -like \u0026#39;*io write*\u0026#39;) { $writeMap[$inst] = $sample.CookedValue } elseif ($pl -like \u0026#39;*id process*\u0026#39;) { $pidMap[$inst] = [int]$sample.CookedValue } } # readMap と writeMap の Union（書き込みのみのプロセスの漏れを防ぐ） $allInsts = [System.Collections.Generic.HashSet[string]]::new() $readMap.Keys | ForEach-Object { [void]$allInsts.Add($_) } $writeMap.Keys | ForEach-Object { [void]$allInsts.Add($_) } # --- しきい値を超えるプロセスを抽出 --- $alerts = @() foreach ($inst in $allInsts) { $read = if ($readMap.ContainsKey($inst)) { $readMap[$inst] } else { 0.0 } $write = if ($writeMap.ContainsKey($inst)) { $writeMap[$inst] } else { 0.0 } $total = $read + $write $pct = [math]::Round(($total / $diskCapacity) * 100, 2) if ($pct -ge $ThresholdPercent) { $procId = if ($pidMap.ContainsKey($inst)) { $pidMap[$inst] } else { 0 } # nameMap から元ケースの InstanceName を復元 # （前段ループの $instRaw は最後のサンプルの値が残るため直接使用しない） $instRaw = if ($nameMap.ContainsKey($inst)) { $nameMap[$inst] } else { $inst } $baseName = ($instRaw -replace \u0026#39;#\\d+$\u0026#39;, \u0026#39;\u0026#39;) $alerts += [PSCustomObject]@{ Timestamp = $timestamp InstanceName = $instRaw # Chrome#3 等、インスタンスを一意に特定できる名前 ProcessName = $baseName # Chrome 等、人間が読みやすいベース名 PID = $procId EstimatedIOPct = $pct ReadKBPerSec = [math]::Round($read / 1KB, 1) WriteKBPerSec = [math]::Round($write / 1KB, 1) TotalIOKBPerSec = [math]::Round($total / 1KB, 1) ExePath = \u0026#39;\u0026#39; # アラート確定後に後付け（下段で取得） } } } # --- アラート対象の PID のみ EXEパスを取得（全件取得より効率的）--- # PID は \\Process(*)\\ID Process カウンター（pidMap）から取得済みのため # Get-Process は ExePath の取得にのみ使用する。 if ($alerts.Count -gt 0) { $neededPids = $alerts | Where-Object { $_.PID -gt 0 } | Select-Object -ExpandProperty PID -Unique foreach ($procId in $neededPids) { try { $p = Get-Process -Id $procId -ErrorAction Stop $pathByPid[$procId] = if ($p.Path) { $p.Path } else { \u0026#39;\u0026#39; } } catch { $pathByPid[$procId] = \u0026#39;\u0026#39; } } foreach ($a in $alerts) { $a.ExePath = if ($pathByPid.ContainsKey($a.PID)) { $pathByPid[$a.PID] } else { \u0026#39;\u0026#39; } } } # --- ディスク全体のビジー率・応答時間アラート（タスクマネージャーと同じ指標）--- if ($diskBusyPct -ge $DiskBusyThreshold -or $diskLatencyMs -ge $LatencyThresholdMs) { Write-Log \u0026#34;【ディスク高負荷】ビジー率: ${diskBusyPct}% | 応答時間: ${diskLatencyMs}ms | 待ち行列: ${diskQueueLen}\u0026#34; \u0026#34;ALERT\u0026#34; # システム要因の自動判別（複数該当する場合はすべて出力） if ($pagesPerSec -ge 100) { Write-Log \u0026#34; └ 原因候補: メモリ不足によるページング（Pages/sec: ${pagesPerSec}）- メモリ使用量の確認またはRAM増設を推奨\u0026#34; \u0026#34;ALERT\u0026#34; } if ($systemIOKBs -ge 1024) { Write-Log \u0026#34; └ 原因候補: System プロセス（ドライバ/カーネル）の高IO（${systemIOKBs} KB/s）- Windows Update・ウイルス対策・SysMain を確認\u0026#34; \u0026#34;ALERT\u0026#34; } if ($diskQueueLen -ge 0.5 -and $pagesPerSec -lt 100 -and $systemIOKBs -lt 1024) { Write-Log \u0026#34; └ 原因候補: ディスク自体の応答遅延（待ち行列: ${diskQueueLen}）- CrystalDiskInfo でディスク健全性を確認\u0026#34; \u0026#34;ALERT\u0026#34; } if ($diskQueueLen -lt 0.5 -and $pagesPerSec -lt 100 -and $systemIOKBs -lt 1024) { Write-Log \u0026#34; └ 原因候補: 短時間の高負荷（瞬間的な書き込みスパイク等）- 継続的に高い場合はディスク健全性を確認\u0026#34; \u0026#34;ALERT\u0026#34; } } # --- プロセス単位の結果出力 --- if ($alerts.Count -gt 0) { $alerts = $alerts | Sort-Object EstimatedIOPct -Descending Write-Log \u0026#34;【サンプル #$sampleNo】しきい値超えプロセス: $($alerts.Count) 件\u0026#34; \u0026#34;ALERT\u0026#34; $lastQuietLog = [datetime]::MinValue # アラート後の「収束」を必ずログに残すためリセット # コンソールは上位 N 件のみ表示（CSV/ログは全件記録） $displayAlerts = $alerts | Select-Object -First $ConsoleTopN foreach ($a in $displayAlerts) { $pathInfo = if ($a.ExePath) { \u0026#34; | $($a.ExePath)\u0026#34; } else { \u0026#39;\u0026#39; } $msg = \u0026#34; $($a.InstanceName) (PID:$($a.PID)) | \u0026#34; + \u0026#34;IO使用率: $($a.EstimatedIOPct)% | \u0026#34; + \u0026#34;R: $($a.ReadKBPerSec) KB/s W: $($a.WriteKBPerSec) KB/s$pathInfo\u0026#34; Write-Log $msg \u0026#34;ALERT\u0026#34; } if ($alerts.Count -gt $ConsoleTopN) { Write-Log \u0026#34; ...他 $($alerts.Count - $ConsoleTopN) 件（CSV/ログを参照）\u0026#34; \u0026#34;ALERT\u0026#34; } # CSV は全件記録（ロック競合時はリトライ） foreach ($a in $alerts) { for ($retry = 1; $retry -le 3; $retry++) { try { Append-CsvRow -Row $a -FilePath $csvFile break } catch { if ($retry -lt 3) { Start-Sleep -Milliseconds 200 } else { Write-Log \u0026#34;CSV 書込失敗（3回リトライ後あきらめ）: $_\u0026#34; \u0026#34;WARN\u0026#34; } } } } } else { # 「しきい値超えなし」は QuietMinutes 分に1回だけログに出す（連続出力を抑制） # アラート発生後は必ず1回出力して「収束した」ことをログに残す $now = Get-Date $quietElapsed = ($now - $lastQuietLog).TotalMinutes if ($QuietMinutes -eq 0 -or $quietElapsed -ge $QuietMinutes) { $quietSuffix = if ($QuietMinutes -eq 0) { \u0026#34;（抑制なし・毎回出力）\u0026#34; } else { \u0026#34;（次回出力まで最大 ${QuietMinutes} 分抑制）\u0026#34; } Write-Log \u0026#34;サンプル #$sampleNo : プロセス単位のIOはしきい値以下 | ビジー率: ${diskBusyPct}% 応答時間: ${diskLatencyMs}ms 待ち行列: ${diskQueueLen} System-IO: ${systemIOKBs}KB/s Pages/sec: ${pagesPerSec}${quietSuffix}\u0026#34; $lastQuietLog = $now } } # 正味 IntervalSeconds 秒待機（Get-Counter の約1秒待機と処理時間を差し引く） $elapsed = ((Get-Date) - $loopStart).TotalSeconds $wait = [math]::Max(0, $IntervalSeconds - $elapsed) if ($wait -gt 0) { Start-Sleep -Milliseconds ([int]($wait * 1000)) } } } finally { $totalMin = [math]::Round(((Get-Date) - $startTime).TotalMinutes, 1) Write-Log \u0026#34;監視終了 | 総サンプル数: $sampleNo | 経過時間: ${totalMin} 分\u0026#34; Write-Host \u0026#34;\u0026#34; Write-Host \u0026#34;ログ保存先 : $logFile\u0026#34; -ForegroundColor Green Write-Host \u0026#34;CSV保存先 : $csvFile\u0026#34; -ForegroundColor Green } 停止は Ctrl+C で、終了時に CSV とログの保存先が表示されます。\n実際に動かしてわかったこと スクリプトを動かしてしばらくすると、こんなログが出ました。\n[ALERT] 【ディスク高負荷】ビジー率: 47.1% | 応答時間: 474ms | 待ち行列: 0.94 [INFO] サンプル #N : プロセス単位のIOはしきい値以下 ディスクは明らかに詰まっているのに、プロセス単位のアラートは一件も出なかったです。応答時間 474ms は正常な SSD では通常 1〜5ms 程度なので、この瞬間にかなり悪化していたことになります。\nこれをきっかけに Windows のイベントログを確認しました。\nGet-WinEvent -LogName \u0026#34;System\u0026#34; | Where-Object { $_.ProviderName -eq \u0026#34;nvlddmkm\u0026#34; } | Select-Object TimeCreated, Id, Message | Format-List nvlddmkm（NVIDIA のグラフィックスドライバー）のイベント ID 153 が 2 件記録されていました。GPU が応答しなくなりドライバーがリセットした記録です。GPU ドライバーがクラッシュすると、その回復処理でシステム全体が一時的に重くなり、ディスクへのアクセスが詰まることがあるようです。\nドライバーのバージョンを確認すると約 2 ヶ月古いものが入っていたので、最新版に更新しました。インストール時は「カスタム」を選んでグラフィックスドライバーのみにチェックを絞り、余計なソフトウェアは入れないようにしました。\nただ、これで解決したかはまだわからないです。。引き続きスクリプトを改善しながら様子を見ています。\nよくある犯人 自分の場合は GPU ドライバーが原因だったかもですが、一般的にディスク高負荷の原因としてよく挙がるプロセスは以下のようなものです。\nSearchIndexer：Windows 検索のインデックス作成。短時間だけ現れて消えるパターンが多い Windows Update（wuauclt、WaaSMedicSvc）：バックグラウンドで動いていることに気づきにくい ウイルス対策ソフトのスキャン：特にフルスキャン時 SysMain（SuperFetch）：SSD の環境では無効にすると改善する場合がある これらは 5 秒間隔の記録でも十分に捕捉できます。一瞬で終わる処理でも、繰り返し発生していれば記録に残るので、なんとなく特定できると良いなと思っています。\n注意点 実行時にいくつか注意点があります。\n管理者権限がないとパフォーマンスカウンターの一部が取得できない プロセス数が 200 を超える環境では -IntervalSeconds 10 程度に増やすと安定する CSV は Excel で開いたままにしていると書き込みがロックされるため、確認するときはいったん閉じるかコピーして使う しきい値（デフォルト 50%）は環境によって調整する しきい値をカスタマイズするには、実行時にパラメーターで指定します。\n.\\Monitor-DiskUsage.ps1 -ThresholdPercent 10 -DiskBusyThreshold 70 -LatencyThresholdMs 30 文字コードについて スクリプトには日本語のコメントや出力メッセージが含まれているため、そのままでは実行時に文字化けエラーが出ることがあります。\n自分の場合はこんなエラーが出ました。\n発生場所 Monitor-DiskUsage.ps1:16 文字:14 + $scriptBase = if ($PSScriptRoot -and $PSScriptRoot -ne \u0026#39;\u0026#39;) { + ~ \u0026#39;=\u0026#39; の後に式が存在しません。 式またはステートメントのトークン \u0026#39;xxx\u0026#39; を使用できません。 コードそのものは正しいのに、日本語部分が文字化けして PowerShell が構文エラーと誤認するパターンです。\n原因はファイルの文字コードにあります。PowerShell 5.1（Windows 標準）はデフォルトで Shift-JIS を想定していますが、多くのエディタは UTF-8 で保存します。この食い違いが文字化けを引き起こします。\nVSCode で文字コードを変換するといいでしょう。\nVSCode でスクリプトを開く 画面右下の文字コード表示（UTF-8 等）をクリック 「Encoding で保存」を選択 「UTF-8 with BOM」を選んで保存 BOM（Byte Order Mark）はファイルの先頭に付く識別子で、PowerShell 5.1 はこれを手がかりに UTF-8 ファイルを正しく認識します。BOM なしの UTF-8 では認識できないため、必ず「with BOM」を選ぶ必要があります。\n保存し直すと冒頭の構文エラーが消え、日本語のログメッセージも正しく表示されるようになりました。\n補足 長時間記録する場合、ログファイルがそれなりのサイズになります。C ドライブ上に保存し続けると、その C ドライブへの書き込み自体がわずかに影響することもあります。-OutputDir パラメーターで別のドライブに保存先を変えるといいでしょう。\nWindows PowerShell 実践システム管理ガイド 第3版 (Amazon) Windows セキュリティインターナル ―PowerShell で理解する Windows の認証、認可、監査の仕組み (Amazon)\n","date":"2026-04-04T00:00:00+09:00","image":"/images/eyecatch/PSMonitor.jpg","permalink":"/posts/it/pc/6209/","title":"Windows：ディスク 100% の原因を PowerShell で記録するには"},{"content":"2026年度（令和8年度）長野市のゴミ収集日を Google カレンダーに表示させる為の CSV ファイルを作成しました。 Google カレンダーに CSV ファイルを読み込ませる方法はこちらを参照ください。\nGoogle ドライブにも置いています。\nNo. 地区別 CSV ファイル 01 第一 02 第二 03 第三 04 第四 05 第五 06 芹田 07 古牧 08 三輪 09 吉田 10 古里、朝陽 11 柳原、若穂 12 浅川 13 大豆島 14 若槻 15 長沼 16 安茂里 17 小田切 18 芋井 19 篠ノ井塩崎、篠ノ井共和、篠ノ井川柳、篠ノ井信里 20 篠ノ井東福寺、篠ノ井西寺尾 21 篠ノ井中央 22 松代 23 川中島 24 更北 25 七二会 26 信更 27 戸隠中社、戸隠宝光社、戸隠上楠川 28 戸隠北部、戸隠中央、戸隠東部、戸隠南部、戸隠川手、戸隠志垣 29 戸隠西部、戸隠平、戸隠西条、戸隠追通、戸隠上祖山、戸隠下祖山 30 鬼無里上里、鬼無里中央1 31 鬼無里中央2、鬼無里両京 32 大岡甲（川口を除く）、大岡中牧、大岡弘崎、大岡聖ヶ岡 33 大岡乙、大岡丙（聖ヶ岡を除く）、大岡川口 34 豊野南郷、豊野石、豊野西町、豊野上組、豊野立町、豊野南町、豊野中尾1・2 35 豊野横町、豊野伊豆毛、豊野上田中、豊野神代町、豊野本町1・2、豊野中尾1・2（一部）、豊野小瀬1、豊野泉平、豊野上神代、豊野豊陽台、豊野沖1・2、豊野中央組、豊野向原 36 豊野本町3・4・5、豊野東町、豊野小瀬2、豊野ゆたかの、豊野豊南町、豊野下田中 37 豊野浅野、豊野蟹沢、豊野入、豊野小日向、豊野上堰、豊野鳥居団地、豊野大方、豊野橋場、豊野上原、豊野蟻ケ崎、豊野城山、豊野川谷 38 信州新町旭町、信州新町仲町、信州新町上町、信州新町西上町、信州新町常磐町、信州新町鹿島東、信州新町鹿島西、信州新町大原東、信州新町大原西、信州新町下市場、信州新町牧野島（伊切を除く）、信州新町鹿道、信州新町鹿道団地 39 信州新町久保、信州新町本町、信州新町境町、信州新町千原田、信州新町平、信州新町和平団地、信州新町藤池団地、信州新町下川西平、信州新町太田笠子（石畑を除く）、信州新町穂刈下、信州新町穂刈中、信州新町穂刈上、信州新町穂刈北、信州新町穂刈団地、信州新町陽のあたる丘、信州新町大門、信州新町LR、信州新町原、信州新町道祖神 40 信州新町津和中央、信州新町山秋、信州新町中福、信州新町栃久保、信州新町中尾、信州新町菅沼、信州新町細尾、信州新町津上、信州新町外味藤、信州新町豊和、信州新町津南、信州新町中組、信州新町味藤、信州新町橋場、信州新町安用、信州新町風越、信州新町追沢、信州新町神田、信州新町花倉、信州新町二丁田、信州新町穴平、信州新町寺尾、信州新町矢ノ尻、信州新町峰組、信州新町枌ノ木、信州新町赤柴、信州新町石畑、信州新町尾崎、信州新町上古、信州新町芦沢、信州新町本村、信州新町大河、信州新町西日時 41 信州新町塩本、信州新町伊切、信州新町牧田中一、信州新町牧田中二、信州新町中牧一、信州新町中牧二、信州新町南牧住平、信州新町一倉田和、信州新町下中山、信州新町日名、信州新町和田吐唄、信州新町置原、信州新町橋木、信州新町左右、信州新町岩下、信州新町信級中央、信州新町高見、信州新町岩本、信州新町柳高、信州新町川名 42 中条 ※ 内容は保証しません。利用はご自身の判断でお願い致します。\n元となったデータは以下から取得しました。\nごみ収集カレンダー（オープンデータ） - 長野市公式ホームページ\n","date":"2026-03-31T06:40:11+09:00","image":"/images/2022/04/gomi.jpg","permalink":"/posts/it/pc/6206/","title":"Google カレンダー：2026年度長野市ごみ収集日"},{"content":"「2026年度長野市ごみカレンダー」を公開しました。 2025年度（令和7年度）長野市のゴミ収集日を Google カレンダーに表示させる為の CSV ファイルを作成しました。 Google カレンダーに CSV ファイルを読み込ませる方法はこちらを参照ください。\nNo. 地区別 CSV ファイル 01 第一 02 第二 03 第三 04 第四 05 第五 06 芹田 07 古牧 08 三輪 09 吉田 10 古里、朝陽 11 柳原、若穂 12 浅川 13 大豆島 14 若槻 15 長沼 16 安茂里 17 小田切 18 芋井 19 篠ノ井塩崎、篠ノ井共和、篠ノ井川柳、篠ノ井信里 20 篠ノ井東福寺、篠ノ井西寺尾 21 篠ノ井中央 22 松代 23 川中島 24 更北 25 七二会 26 信更 27 戸隠中社、戸隠宝光社、戸隠上楠川 28 戸隠北部、戸隠中央、戸隠東部、戸隠南部、戸隠川手、戸隠志垣 29 戸隠西部、戸隠平、戸隠西条、戸隠追通、戸隠上祖山、戸隠下祖山 30 鬼無里上里、鬼無里中央1 31 鬼無里中央2、鬼無里両京 32 大岡甲（川口を除く）、大岡中牧、大岡弘崎、大岡聖ヶ岡 33 大岡乙、大岡丙（聖ヶ岡を除く）、大岡川口 34 豊野南郷、豊野石、豊野西町、豊野上組、豊野立町、豊野南町、豊野中尾1・2 35 豊野横町、豊野伊豆毛、豊野上田中、豊野神代町、豊野本町1・2、豊野中尾1・2（一部）、豊野小瀬1、豊野泉平、豊野上神代、豊野豊陽台、豊野沖1・2、豊野中央組、豊野向原 36 豊野本町3・4・5、豊野東町、豊野小瀬2、豊野ゆたかの、豊野豊南町、豊野下田中 37 豊野浅野、豊野蟹沢、豊野入、豊野小日向、豊野上堰、豊野鳥居団地、豊野大方、豊野橋場、豊野上原、豊野蟻ケ崎、豊野城山、豊野川谷 38 信州新町旭町、信州新町仲町、信州新町上町、信州新町西上町、信州新町常磐町、信州新町鹿島東、信州新町鹿島西、信州新町大原東、信州新町大原西、信州新町下市場、信州新町牧野島（伊切を除く）、信州新町鹿道、信州新町鹿道団地 39 信州新町久保、信州新町本町、信州新町境町、信州新町千原田、信州新町平、信州新町和平団地、信州新町藤池団地、信州新町下川西平、信州新町太田笠子（石畑を除く）、信州新町穂刈下、信州新町穂刈中、信州新町穂刈上、信州新町穂刈北、信州新町穂刈団地、信州新町陽のあたる丘、信州新町大門、信州新町LR、信州新町原、信州新町道祖神 40 信州新町津和中央、信州新町山秋、信州新町中福、信州新町栃久保、信州新町中尾、信州新町菅沼、信州新町細尾、信州新町津上、信州新町外味藤、信州新町豊和、信州新町津南、信州新町中組、信州新町味藤、信州新町橋場、信州新町安用、信州新町風越、信州新町追沢、信州新町神田、信州新町花倉、信州新町二丁田、信州新町穴平、信州新町寺尾、信州新町矢ノ尻、信州新町峰組、信州新町枌ノ木、信州新町赤柴、信州新町石畑、信州新町尾崎、信州新町上古、信州新町芦沢、信州新町本村、信州新町大河、信州新町西日時 41 信州新町塩本、信州新町伊切、信州新町牧田中一、信州新町牧田中二、信州新町中牧一、信州新町中牧二、信州新町南牧住平、信州新町一倉田和、信州新町下中山、信州新町日名、信州新町和田吐唄、信州新町置原、信州新町橋木、信州新町左右、信州新町岩下、信州新町信級中央、信州新町高見、信州新町岩本、信州新町柳高、信州新町川名 42 中条 ※ 内容は保証しません。利用はご自身の判断でお願い致します。\n元となったデータは以下から取得しました。\nごみ収集カレンダー（オープンデータ） - 長野市公式ホームページ\n","date":"2025-03-29T07:49:49+09:00","image":"/images/2022/04/gomi.jpg","permalink":"/posts/it/pc/6184/","title":"Google カレンダー：2025年度長野市ごみ収集日"},{"content":"スタイルを設定できる部分がコンポーネントによってらしい。\nCollapse は padding と背景色が変えられるようなので試してみた。\nConfigProvider の theme で設定する。\nimport React from \u0026#34;react\u0026#34;; import { ConfigProvider } from \u0026#34;antd\u0026#34;; import type { CollapseProps } from \u0026#34;antd\u0026#34;; import { Collapse } from \u0026#34;antd\u0026#34;; const items: CollapseProps[\u0026#34;items\u0026#34;] = [ { key: \u0026#34;1\u0026#34;, label: \u0026#34;Test Text\u0026#34;, children: \u0026#34;Content Text\u0026#34;, }, ]; const ExampleCollapse: React.FC = () =\u0026gt; ( \u0026lt;\u0026gt; \u0026lt;h1\u0026gt;デフォルト\u0026lt;/h1\u0026gt; \u0026lt;Collapse defaultActiveKey={[\u0026#34;1\u0026#34;]} items={items} /\u0026gt; \u0026lt;br /\u0026gt; \u0026lt;h1\u0026gt;padding なし、背景色変更\u0026lt;/h1\u0026gt; \u0026lt;ConfigProvider theme={{ components: { Collapse: { headerPadding: \u0026#34;0px\u0026#34;, headerBg: \u0026#34;#fa8072\u0026#34;, contentPadding: \u0026#34;0px\u0026#34;, contentBg: \u0026#34;#87cefa\u0026#34;, }, }, }} \u0026gt; \u0026lt;Collapse defaultActiveKey={[\u0026#34;1\u0026#34;]} items={items} /\u0026gt; \u0026lt;/ConfigProvider\u0026gt; \u0026lt;/\u0026gt; ); 何が変えられるかは、定義を辿れば分かりそう。\nCollapse の場合は以下だった。\n","date":"2024-07-13T09:53:00+09:00","image":"/images/2024/07/col00.jpg","permalink":"/posts/it/pc/6160/","title":"Ant Design：Collapse のヘッダーとコンテンツの padding と背景色を変更する"},{"content":"react-markdown と remark-gfm を使ったコンポーネントのテストコードを jest で実行する設定のメモです。要点を挙げるなら、\nJSDOM で未実装のメソッドはモックを用意しておく transformIgnorePatterns にトランスコンパイルさせるものを設定しておく といった辺りです。\nテンプレ作成 create-next-app@14.2.4 で以下を指定。\nreact-markdown と remark-gfm を追加。\nnpm install \\ react-markdown \\ remark-gfm npm install -D @tailwindcss/typography jest 関連のパッケージを追加。\nnpm install -D \\ @testing-library/jest-dom \\ @testing-library/react \\ @types/jest \\ jest \\ jest-environment-jsdom packages.json の scripts に test を追加。\n{ \u0026#34;name\u0026#34;: \u0026#34;my-app\u0026#34;, \u0026#34;version\u0026#34;: \u0026#34;0.1.0 \u0026#34;private\u0026#34;: true, \u0026#34;scripts\u0026#34;: { \u0026#34;dev\u0026#34;: \u0026#34;next dev\u0026#34;, \u0026#34;build\u0026#34;: \u0026#34;next build\u0026#34;, \u0026#34;start\u0026#34;: \u0026#34;next start\u0026#34;, \u0026#34;lint\u0026#34;: \u0026#34;next lint\u0026#34;, \u0026#34;test\u0026#34;: \u0026#34;jest\u0026#34; }, \u0026#34;dependencies\u0026#34;: { \u0026#34;next\u0026#34;: \u0026#34;14.2.4\u0026#34;, \u0026#34;react\u0026#34;: \u0026#34;^18\u0026#34;, \u0026#34;react-dom\u0026#34;: \u0026#34;^18\u0026#34;, \u0026#34;react-markdown\u0026#34;: \u0026#34;^9.0.1\u0026#34;, \u0026#34;remark-gfm\u0026#34;: \u0026#34;^4.0.0\u0026#34; }, \u0026#34;devDependencies\u0026#34;: { \u0026#34;@tailwindcss/typography\u0026#34;: \u0026#34;^0.5.13\u0026#34;, \u0026#34;@testing-library/jest-dom\u0026#34;: \u0026#34;^6.4.6\u0026#34;, \u0026#34;@testing-library/react\u0026#34;: \u0026#34;^16.0.0\u0026#34;, \u0026#34;@types/jest\u0026#34;: \u0026#34;^29.5.12\u0026#34;, \u0026#34;@types/node\u0026#34;: \u0026#34;^20\u0026#34;, \u0026#34;@types/react\u0026#34;: \u0026#34;^18\u0026#34;, \u0026#34;@types/react-dom\u0026#34;: \u0026#34;^18\u0026#34;, \u0026#34;eslint\u0026#34;: \u0026#34;^8\u0026#34;, \u0026#34;eslint-config-next\u0026#34;: \u0026#34;14.2.4\u0026#34;, \u0026#34;jest\u0026#34;: \u0026#34;^29.7.0\u0026#34;, \u0026#34;jest-environment-jsdom\u0026#34;: \u0026#34;^29.7.0\u0026#34;, \u0026#34;postcss\u0026#34;: \u0026#34;^8\u0026#34;, \u0026#34;tailwindcss\u0026#34;: \u0026#34;^3.4.1\u0026#34;, \u0026#34;typescript\u0026#34;: \u0026#34;^5\u0026#34; } } 設定の追加 tsconfig.json の types に @testing-library/jest-dom を追加。\nしないとテストの関数の型などが解決されません。\n{ \u0026#34;compilerOptions\u0026#34;: { \u0026#34;lib\u0026#34;: [\u0026#34;dom\u0026#34;, \u0026#34;dom.iterable\u0026#34;, \u0026#34;esnext\u0026#34;], \u0026#34;allowJs\u0026#34;: true, \u0026#34;skipLibCheck\u0026#34;: true, \u0026#34;strict\u0026#34;: true, \u0026#34;noEmit\u0026#34;: true, \u0026#34;esModuleInterop\u0026#34;: true,tsconfig.json \u0026#34;module\u0026#34;: \u0026#34;esnext\u0026#34;, \u0026#34;moduleResolution\u0026#34;: \u0026#34;bundler\u0026#34;, \u0026#34;resolveJsonModule\u0026#34;: true, \u0026#34;isolatedModules\u0026#34;: true, \u0026#34;jsx\u0026#34;: \u0026#34;preserve\u0026#34;, \u0026#34;incremental\u0026#34;: true, \u0026#34;plugins\u0026#34;: [ { \u0026#34;name\u0026#34;: \u0026#34;next\u0026#34; } ], \u0026#34;paths\u0026#34;: { \u0026#34;@/*\u0026#34;: [\u0026#34;./*\u0026#34;] }, \u0026#34;types\u0026#34;: [ \u0026#34;@testing-library/jest-dom\u0026#34; ], }, \u0026#34;include\u0026#34;: [\u0026#34;next-env.d.ts\u0026#34;, \u0026#34;**/*.ts\u0026#34;, \u0026#34;**/*.tsx\u0026#34;, \u0026#34;.next/types/**/*.ts\u0026#34;], \u0026#34;exclude\u0026#34;: [\u0026#34;node_modules\u0026#34;] } jest の設定は JavaScript にしました。\nTypeScript だとその設定も必要の様なので妥協しました。\npackages.json と同じディレクトリに次のファイルを用意します。\n・jest.setup.js\n公式にある通り、jest で使用される DOM で未実装のメソッドはモックにしておきます。\nimport \u0026#34;@testing-library/jest-dom\u0026#34;; // https://jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom Object.defineProperty(window, \u0026#34;matchMedia\u0026#34;, { writable: true, value: jest.fn().mockImplementation((query) =\u0026gt; ({ matches: false, media: query, onchange: null, addListener: jest.fn(), // deprecated removeListener: jest.fn(), // deprecated addEventListener: jest.fn(), removeEventListener: jest.fn(), dispatchEvent: jest.fn(), })), }); ・jest.config.js\nreact-markdown と remark-gfm を足した後は、node_modules 配下に依存パッケージも含め、エラーになるものが結構出ました。\n個々に設定するのが辛かったので、node_modules 配下は全部トランスコンパイルするように transformIgnorePatterns に設定しました。不都合がでたら見直すつもり。\nconst nextJest = require(\u0026#34;next/jest\u0026#34;); const createJestConfig = nextJest({ // Provide the path to your Next.js app to load next.config.js and .env files in your test environment dir: \u0026#34;./\u0026#34;, }); // Add any custom config to be passed to Jest const customJestConfig = { setupFilesAfterEnv: [\u0026#34;\u0026lt;rootDir\u0026gt;/jest.setup.js\u0026#34;], testEnvironment: \u0026#34;jsdom\u0026#34;, moduleNameMapper: { \u0026#34;@/(.*)$\u0026#34;: \u0026#34;\u0026lt;rootDir\u0026gt;/$1\u0026#34;, }, }; module.exports = async () =\u0026gt; ({ ...(await createJestConfig(customJestConfig)()), transformIgnorePatterns: [ `node_modules/(?!.*)/`, ], }); ページとテストコード 以前のページで試してみます。\nimport ReactMarkdown from \u0026#34;react-markdown\u0026#34;; import remarkGfm from \u0026#34;remark-gfm\u0026#34;; const md = ` # GFM ## Autolink literals www.example.com, https://example.com, and contact@example.com. ## Footnote A note[^1] [^1]: Big note. ## Strikethrough ~one~ or ~~two~~ tildes. ## Table | a | b | c | d | | - | :- | -: | :-: | ## Tasklist * [ ] to do * [x] done `; const Home = () =\u0026gt; { return \u0026lt;ReactMarkdown remarkPlugins={[remarkGfm]} className=\u0026#34;prose\u0026#34;\u0026gt;{md}\u0026lt;/ReactMarkdown\u0026gt;; }; export default Home; テストコードは以下で。\nimport \u0026#34;@testing-library/jest-dom\u0026#34;; import { render, screen } from \u0026#34;@testing-library/react\u0026#34;; import Page from \u0026#34;./page\u0026#34;; describe(\u0026#34;Page\u0026#34;, () =\u0026gt; { it(\u0026#34;renders a heading\u0026#34;, () =\u0026gt; { render(\u0026lt;Page /\u0026gt;); const headers = screen.getAllByRole(\u0026#34;heading\u0026#34;); expect(headers.length).toEqual(7); }); }); テストの実行 npm run test で実行します。\n% npm run test \u0026gt; my-app@0.1.0 test \u0026gt; jest (node:17255) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead. (Use `node --trace-deprecation ...` to show where the warning was created) PASS app/page.test.tsx Page ✓ renders a heading (51 ms) Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total Snapshots: 0 total Time: 2.002 s Ran all test suites. ","date":"2024-07-06T18:22:20+09:00","image":"/images/2022/02/backup.jpg","permalink":"/posts/it/pc/6115/","title":"Next.js：jest でテストする"},{"content":"以前やった URL.createObjectURL で良いと思いますが、直接作っちゃっても行けたのでメモ。\n\u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html lang=\u0026#34;ja\u0026#34;\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026#34;UTF-8\u0026#34;\u0026gt; \u0026lt;title\u0026gt;テキストのデータURL例\u0026lt;/title\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;span id=\u0026#34;dataUrlContainer\u0026#34;\u0026gt;\u0026lt;/span\u0026gt; \u0026lt;script\u0026gt; function convertToDataURL(text) { const encodedText = encodeURIComponent(text); return `data:text/plain;charset=utf-8,${encodedText}`; } // 使用例 const plainText = \u0026#39;Hello World!\u0026#39;; const dataURL = convertToDataURL(plainText); // spanの中にリンクを作成 const container = document.getElementById(\u0026#39;dataUrlContainer\u0026#39;); const link = document.createElement(\u0026#39;a\u0026#39;); link.href = dataURL; link.textContent = \u0026#39;テキストをダウンロード\u0026#39;; // ダウンロード時のファイル名 link.download = \u0026#39;message.txt\u0026#39;; container.appendChild(link); \u0026lt;/script\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; ","date":"2024-06-22T19:30:51+09:00","image":"/images/2022/08/rmd00.jpg","permalink":"/posts/it/pc/6107/","title":"JavaScript：テキストをデータ URL にしてダウンロード"},{"content":"マークダウンの表示に react-markdown と remark-gfm を使った際、tailwind css が影響してスタイルされなかったときの対処です。\n・使用したマークダウン （remark-gfm のリポジトリから）\n# GFM ## Autolink literals www.example.com, https://example.com, and contact@example.com. ## Footnote A note[^1] [^1]: Big note. ## Strikethrough ~one~ or ~~two~~ tildes. ## Table | a | b | c | d | | - | :- | -: | :-: | ## Tasklist * [ ] to do * [x] done ・対処前の表示結果\n・対処後の表示結果\n環境 create-next-app@14.2.4 で雛形を作り、react-markdown と remark-gfm をインストールしました。\nnpm i react-markdown remark-gfm サンプルのページは次の通りです。\nimport ReactMarkdown from \u0026#34;react-markdown\u0026#34;; import remarkGfm from \u0026#34;remark-gfm\u0026#34;; const md = ` # GFM ## Autolink literals www.example.com, https://example.com, and contact@example.com. ## Footnote A note[^1] [^1]: Big note. ## Strikethrough ~one~ or ~~two~~ tildes. ## Table | a | b | c | d | | - | :- | -: | :-: | ## Tasklist * [ ] to do * [x] done `; const Home = () =\u0026gt; { return ( \u0026lt;ReactMarkdown remarkPlugins={[remarkGfm]}\u0026gt;{md}\u0026lt;/ReactMarkdown\u0026gt; ); }; export default Home; 対処方法 @tailwindcss/typography をインストールします。\nnpm i --save-dev @tailwindcss/typography tailwind.config.ts に plugins を追加します。\nimport type { Config } from \u0026#34;tailwindcss\u0026#34;; const config: Config = { content: [ \u0026#34;./pages/**/*.{js,ts,jsx,tsx,mdx}\u0026#34;, \u0026#34;./components/**/*.{js,ts,jsx,tsx,mdx}\u0026#34;, \u0026#34;./app/**/*.{js,ts,jsx,tsx,mdx}\u0026#34;, ], theme: { }, plugins: [ require(\u0026#39;@tailwindcss/typography\u0026#39;), ], }; export default config; マークダウンを表示する要素のクラスに prose を追加します。\n直接付けるか親要素に付けるかはお好みで。\nconst Home = () =\u0026gt; { return ( \u0026lt;ReactMarkdown remarkPlugins={[remarkGfm]} className=\u0026#34;prose\u0026#34;\u0026gt;{md}\u0026lt;/ReactMarkdown\u0026gt; ); }; const Home = () =\u0026gt; { return ( \u0026lt;div className=\u0026#34;prose\u0026#34;\u0026gt; \u0026lt;ReactMarkdown remarkPlugins={[remarkGfm]}\u0026gt;{md}\u0026lt;/ReactMarkdown\u0026gt; \u0026lt;/div\u0026gt; ); }; ","date":"2024-06-15T12:33:34+09:00","image":"/images/2021/07/restapi.jpg","permalink":"/posts/it/pc/6081/","title":"Next.js：react-markdown と tailwind css を共存させる"},{"content":"Next.js で Ant Design を使った際に、TextArea にフォーカスを移すためのメモです。\n環境 Next.js v14.2.3 Ant Design v5.18.0 作例 以下のように、カーソルの位置を指定できます。\n先頭 末尾 テキストを全選択 \u0026#34;use client\u0026#34; import { useRef, useState } from \u0026#34;react\u0026#34;; import TextArea from \u0026#34;antd/es/input/TextArea\u0026#34;; import type { TextAreaRef } from \u0026#34;antd/es/input/TextArea\u0026#34;; export default function Home() { const textAreaRef = useRef\u0026lt;TextAreaRef\u0026gt;(null); const [value, setValue] = useState(\u0026#34;\u0026#34;); const setFocus = (cursor: \u0026#34;start\u0026#34; | \u0026#34;end\u0026#34; | \u0026#34;all\u0026#34;) =\u0026gt; { textAreaRef.current?.focus({ cursor }); }; return ( \u0026lt;div\u0026gt; \u0026lt;TextArea ref={textAreaRef} value={value} onInput={(e: React.ChangeEvent\u0026lt;HTMLTextAreaElement\u0026gt;) =\u0026gt; setValue(e.target.value) } \u0026gt;\u0026lt;/TextArea\u0026gt; \u0026lt;div\u0026gt; \u0026lt;button onClick={() =\u0026gt; setFocus(\u0026#34;start\u0026#34;)}\u0026gt;start\u0026lt;/button\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;div\u0026gt; \u0026lt;button onClick={() =\u0026gt; setFocus(\u0026#34;end\u0026#34;)}\u0026gt;end\u0026lt;/button\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;div\u0026gt; \u0026lt;button onClick={() =\u0026gt; setFocus(\u0026#34;all\u0026#34;)}\u0026gt;all\u0026lt;/button\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; ); } ","date":"2024-06-08T07:00:00+09:00","image":"/images/2022/06/crystal.jpg","permalink":"/posts/it/pc/6071/","title":"Ant Design：TextArea にフォーカスを移すには"},{"content":"以下の様に、テキストファイルの内容の取り出しを import で済ませたい場合です。\n極端に大きなファイルで無ければ、起動時間に与える影響は少ないと思います。\nHello, world. This is an example text file. import example from \u0026#34;./example.txt\u0026#34;; export default async function Home() { return \u0026lt;pre\u0026gt;{example}\u0026lt;/pre\u0026gt;; } 設定方法 next.config.mjs に webpack の設定を追加します。\n/** @type {import(\u0026#39;next\u0026#39;).NextConfig} */ const nextConfig = { webpack(config, options) { config.module.rules.push({ test: /\\.(txt)$/, type: \u0026#34;asset/source\u0026#34;, }); return config; }, }; export default nextConfig; 新規に .d.ts ファイルを作成し、テキストファイルの型宣言をしておきます。\ndeclare module \u0026#34;*.txt\u0026#34; webpack 5 より前はローダーの設定を追加するのが一般的だった様ですが、現在は Asset Modules が使用できて便利な様です。\n参考：webpack - Asset Modules\n","date":"2024-06-01T12:49:24+09:00","image":"/images/2022/03/javascript.jpg","permalink":"/posts/it/pc/6049/","title":"Next.js：テキストファイルを import して使う"},{"content":"こんな感じで開いて行った後、まとめて閉じるときの操作です。\n⌘ + A で全選択\n⌘ + ← で選択対象が閉じます。\n逆に、開きたいときは ⌘ + → です。\nMac Fan 2026年5月号 (Amazon) ","date":"2024-05-25T14:05:08+09:00","image":"/images/2022/10/repo.jpg","permalink":"/posts/it/pc/6037/","title":"Mac：リスト表示している Finder でフォルダをまとめて閉じる"},{"content":"以前 React で書いたものの Next.js の場合になります。\nNext.js は API Routes を通すこともできますが、バックエンドを単純に呼びたいだけなら rewrites が便利です。\n設定方法 next.config.mjs に rewrites の設定を追加します。\n例として、バックエンドが http://example.com:3100 の /v1 に API がある場合は以下のようにします。\n/** @type {import(\u0026#39;next\u0026#39;).NextConfig} */ const nextConfig = { async rewrites() { return [ { source: \u0026#34;/api/:path*\u0026#34;, destination: \u0026#34;http://example.com:3100/v1/:path*\u0026#34;, }, ]; }, }; export default nextConfig; この場合、フロントエンドで /api/users に GET を要求した場合、\nバックエンドの http://example.com:3100/v1/users に GET を要求して応答が得られます。\n","date":"2024-05-18T12:40:45+09:00","image":"/images/2023/01/vs00.jpg","permalink":"/posts/it/pc/6022/","title":"Next.js：ローカルで実行してもサーバーの API を利用するには"},{"content":"String.prototype.trim() は対象が空白文字ですが、他の文字を指定したかったときのサンプルです。\n/** * 文字列の先頭と末尾から指定した文字を削除 * @param {string} str - 対象の文字列 * @param {string} charToTrim - 削除する文字 * @returns {string} 削除後の文字列 */ function trimSpecificChars(str, charToTrim) { const regex = new RegExp(`^[${charToTrim}]+|[${charToTrim}]+$`, \u0026#39;g\u0026#39;); return str.replace(regex, \u0026#39;\u0026#39;); } // 使用例 console.log(trimSpecificChars(\u0026#39;!!!Hello, World!!!\u0026#39;, \u0026#39;!\u0026#39;)); // 出力: \u0026#34;Hello, World\u0026#34; console.log(trimSpecificChars(\u0026#39;???$$$Hello$$$???\u0026#39;, \u0026#39;?$\u0026#39;)); // 出力: \u0026#34;Hello\u0026#34; console.log(trimSpecificChars(\u0026#39;abcabcabc\u0026#39;, \u0026#39;abc\u0026#39;)); // 出力: \u0026#34;\u0026#34; ","date":"2024-05-11T13:58:14+09:00","image":"/images/2022/02/deepcopy.jpg","permalink":"/posts/it/pc/6013/","title":"JavaScript：文字列の先頭と末尾にある指定の文字を削除する"},{"content":"長野市ごみ収集日の CSV ファイルを公開していますが、使うには少しハードルがありますよね。\nカレンダーの共有リンクで自分のカレンダーに反映できたら楽だろうなと思っていたのですが、40 以上のカレンダーを手動で作って公開までする気にはならず、コードでできないか調べつつ書きました。\n作成したカレンダーの共有リンクは先のページに反映しました。\nCalendar API を使う Calendar API のガイドに従い、以下のように必要なパッケージをインストールしておきます。\npip install --upgrade google-api-python-client google-auth-httplib2 google-auth-oauthlib カレンダーを作成して共有リンクを得る 作成したコードの主な処理内容は以下の通り。\n・長野市の地区毎に１つのカレンダーを作成\n・そのカレンダーに、ごみ収集日を登録\n・カレンダーを読み取り専用で一般公開\n・カレンダーの共有リンクを取得\n・公開ページで使う HTML の一部を作成\nこれも手入力で作るのは辛い部分の HTML を作成しています。\n認証は「OAuth クライアント ID」で行っており、作成した認証の json ファイルはダウンロードして credentials.json にリネームしています。\nカレンダーの共有リンクを取得する方法が API リファレンスなどでは見つからず、コードの作成は無理かと思いましたが Stack Overflow で見つかったので、そのまま流用しました。\n収集日を１つずつ登録していくので結構時間が掛かります。\nまた、一度に全て実行すると無料枠を超えそうなので実際は地区を分け、日を変えて登録しました。\nimport base64 import csv import os.path from google.auth.transport.requests import Request from google.oauth2.credentials import Credentials from google_auth_oauthlib.flow import InstalledAppFlow from googleapiclient.discovery import build from googleapiclient.errors import HttpError # Scopesを変更する場合は、token.jsonファイルを削除してください。 SCOPES = [\u0026#34;https://www.googleapis.com/auth/calendar\u0026#34;] def getCredentials(): creds = None # token.jsonファイルには、ユーザーのアクセストークンとリフレッシュトークンが保存されており、 # 認証フローが初めて完了したときに自動的に作成されます。 if os.path.exists(\u0026#34;token.json\u0026#34;): creds = Credentials.from_authorized_user_file(\u0026#34;token.json\u0026#34;, SCOPES) # 有効な資格情報がない場合は、ユーザーにログインしてもらいます。 if not creds or not creds.valid: if creds and creds.expired and creds.refresh_token: creds.refresh(Request()) else: flow = InstalledAppFlow.from_client_secrets_file( \u0026#34;credentials.json\u0026#34;, SCOPES ) creds = flow.run_local_server(port=0) # 次回の実行のために資格情報を保存します with open(\u0026#34;token.json\u0026#34;, \u0026#34;w\u0026#34;) as token: token.write(creds.to_json()) return creds # 共有リンクのcidを取得 # https://stackoverflow.com/questions/55150173/google-calendar-get-shareable-link-via-api-get-cid-value-for-a-calendar def getCalenderCID(calendarId): calendarId_bytes = calendarId.encode(\u0026#34;utf-8\u0026#34;) cid_base64 = base64.b64encode(calendarId_bytes) cid = cid_base64.decode().rstrip(\u0026#34;=\u0026#34;) return cid # 新しいカレンダーを作成 def createCalendar(service, calendarName, description): # 新しいカレンダーを作成 calendar = { \u0026#34;summary\u0026#34;: calendarName, \u0026#34;description\u0026#34;: description, \u0026#34;timeZone\u0026#34;: \u0026#34;Asia/Tokyo\u0026#34;, } request = service.calendars().insert(body=calendar).execute() # カレンダーIDを取得 calendarId = request[\u0026#34;id\u0026#34;] return calendarId # CSVファイルからイベントをインポート def importEvents(service, calendarId, csvFile): with open(csvFile, \u0026#34;r\u0026#34;, encoding=\u0026#34;utf-8\u0026#34;) as f: reader = csv.reader(f) next(reader) # ヘッダー行を読み飛ばす for row in reader: # CSVファイルから予定データを取得 subject, start_date = row is_all_day = True # 終日イベント # イベントを作成 event = { \u0026#34;summary\u0026#34;: subject, \u0026#34;location\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;start\u0026#34;: { \u0026#34;date\u0026#34;: start_date, \u0026#34;timeZone\u0026#34;: \u0026#34;Asia/Tokyo\u0026#34;, }, \u0026#34;end\u0026#34;: { \u0026#34;date\u0026#34;: start_date, \u0026#34;timeZone\u0026#34;: \u0026#34;Asia/Tokyo\u0026#34;, }, \u0026#34;allDay\u0026#34;: is_all_day, } service.events().insert( calendarId=calendarId, body=event ).execute() print(f\u0026#34;イベントが作成されました: {subject}\u0026#34;) def main(): credentials = getCredentials() service = build(\u0026#34;calendar\u0026#34;, \u0026#34;v3\u0026#34;, credentials=credentials) trs = [] with open(\u0026#34;./2024/2024nagano_area.csv\u0026#34;, \u0026#34;r\u0026#34;, encoding=\u0026#34;utf-8\u0026#34;) as f: reader = csv.reader(f) next(reader) # ヘッダー行を読み飛ばす for row in reader: no, area = row no = no.zfill(2) print(f\u0026#34;{no}:{area}\u0026#34;) csvFile = f\u0026#34;./2024/2024nagano_area{no}.csv\u0026#34; try: calendarName = f\u0026#34;長野市{no.zfill(2)}_ごみ収集日\u0026#34; calendarId = createCalendar(service, calendarName, area) print(f\u0026#34;カレンダーが作成されました: {calendarName}\u0026#34;) importEvents(service, calendarId, csvFile) rule = { \u0026#34;scope\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;default\u0026#34;, }, \u0026#34;role\u0026#34;: \u0026#34;reader\u0026#34;, } created_rule = ( service.acl() .insert(calendarId=calendarId, body=rule) .execute() ) print(created_rule) service.acl().list(calendarId=calendarId).execute() cid = getCalenderCID(calendarId) print(f\u0026#34;cid={cid}\u0026#34;) sharedLink = f\u0026#34;https://calendar.google.com/calendar?cid={cid}\u0026#34; tr = ( f\u0026#34;\u0026lt;tr\u0026gt;\u0026#34; f\u0026#34;\u0026lt;td\u0026gt;{no}\u0026lt;/td\u0026gt;\u0026#34; f\u0026#34;\u0026lt;td\u0026gt;\u0026lt;a href=\u0026#34; f\u0026#39;\u0026#34;/images/2024/03/2024nagano_area{no}.csv\u0026#34; \u0026#39; f\u0026#39;download=\u0026#34;\u0026#34;\u0026gt;{area}\u0026lt;/a\u0026gt;\u0026lt;/td\u0026gt;\u0026#39; f\u0026#39;\u0026lt;td\u0026gt;\u0026lt;a href=\u0026#34;{sharedLink}\u0026#34; \u0026#39; f\u0026#39;target=\u0026#34;_blank\u0026#34; rel=\u0026#34;noopener noreferrer\u0026#34;\u0026gt;\u0026#39; f\u0026#34;カレンダーに追加\u0026lt;/a\u0026gt;\u0026lt;/td\u0026gt;\u0026#34; f\u0026#34;\u0026lt;/tr\u0026gt;\u0026#34; ) trs.append(tr) except HttpError as error: print(f\u0026#34;エラーが発生しました: {error}\u0026#34;) # 共有リンクを公開する際に使うHTMLを保存 with open(\u0026#34;./tbody.html\u0026#34;, \u0026#34;w\u0026#34;, encoding=\u0026#34;utf-8\u0026#34;) as f: f.write(\u0026#34;\\n\u0026#34;.join(trs)) if __name__ == \u0026#34;__main__\u0026#34;: main() 子ディレクトリの 2024 に置いたファイルですが、\n・2024nagano_areaXX.csv は公開している CSV ファイル\n（但し収集対象の名称を短くする為に一部変更している）\n・2024nagano_area.csv は以下の内容\nです。\n\u0026#34;no\u0026#34;,\u0026#34;area\u0026#34; 1,\u0026#34;第一\u0026#34; 2,\u0026#34;第二\u0026#34; 3,\u0026#34;第三\u0026#34; 4,\u0026#34;第四\u0026#34; 5,\u0026#34;第五\u0026#34; 6,\u0026#34;芹田\u0026#34; 7,\u0026#34;古牧\u0026#34; 8,\u0026#34;三輪\u0026#34; 9,\u0026#34;吉田\u0026#34; 10,\u0026#34;古里、朝陽\u0026#34; 11,\u0026#34;柳原、若穂\u0026#34; 12,\u0026#34;浅川\u0026#34; 13,\u0026#34;大豆島\u0026#34; 14,\u0026#34;若槻\u0026#34; 15,\u0026#34;長沼\u0026#34; 16,\u0026#34;安茂里\u0026#34; 17,\u0026#34;小田切\u0026#34; 18,\u0026#34;芋井\u0026#34; 19,\u0026#34;篠ノ井塩崎、篠ノ井共和、篠ノ井川柳、篠ノ井信里\u0026#34; 20,\u0026#34;篠ノ井東福寺、篠ノ井西寺尾\u0026#34; 21,\u0026#34;篠ノ井中央\u0026#34; 22,\u0026#34;松代\u0026#34; 23,\u0026#34;川中島\u0026#34; 24,\u0026#34;更北\u0026#34; 25,\u0026#34;七二会\u0026#34; 26,\u0026#34;信更\u0026#34; 27,\u0026#34;戸隠中社、戸隠宝光社、戸隠上楠川\u0026#34; 28,\u0026#34;戸隠北部、戸隠中央、戸隠東部、戸隠南部、戸隠川手、戸隠志垣\u0026#34; 29,\u0026#34;戸隠西部、戸隠平、戸隠西条、戸隠追通、戸隠上祖山、戸隠下祖山\u0026#34; 30,\u0026#34;鬼無里上里、鬼無里中央1\u0026#34; 31,\u0026#34;鬼無里中央2、鬼無里両京\u0026#34; 32,\u0026#34;大岡甲（川口を除く）、大岡中牧、大岡弘崎、大岡聖ヶ岡\u0026#34; 33,\u0026#34;大岡乙、大岡丙（聖ヶ岡を除く）、大岡川口\u0026#34; 34,\u0026#34;豊野南郷、豊野石、豊野西町、豊野上組、豊野立町、豊野南町、豊野中尾1・2\u0026#34; 35,\u0026#34;豊野横町、豊野伊豆毛、豊野上田中、豊野神代町、豊野本町1・2、豊野中尾1・2（一部）、豊野小瀬1、豊野泉平、豊野上神代、豊野豊陽台、豊野沖1・2、豊野中央組、豊野向原\u0026#34; 36,\u0026#34;豊野本町3・4・5、豊野東町、豊野小瀬2、豊野ゆたかの、豊野豊南町、豊野下田中\u0026#34; 37,\u0026#34;豊野浅野、豊野蟹沢、豊野入、豊野小日向、豊野上堰、豊野鳥居団地、豊野大方、豊野橋場、豊野上原、豊野蟻ケ崎、豊野城山、豊野川谷\u0026#34; 38,\u0026#34;信州新町旭町、信州新町仲町、信州新町上町、信州新町西上町、信州新町常磐町、信州新町鹿島東、信州新町鹿島西、信州新町大原東、信州新町大原西、信州新町下市場、信州新町牧野島（伊切を除く）、信州新町鹿道、信州新町鹿道団地\u0026#34; 39,\u0026#34;信州新町久保、信州新町本町、信州新町境町、信州新町千原田、信州新町平、信州新町和平団地、信州新町藤池団地、信州新町下川西平、信州新町太田笠子（石畑を除く）、信州新町穂刈下、信州新町穂刈中、信州新町穂刈上、信州新町穂刈北、信州新町穂刈団地、信州新町陽のあたる丘、信州新町大門、信州新町LR、信州新町原、信州新町道祖神\u0026#34; 40,\u0026#34;信州新町津和中央、信州新町山秋、信州新町中福、信州新町栃久保、信州新町中尾、信州新町菅沼、信州新町細尾、信州新町津上、信州新町外味藤、信州新町豊和、信州新町津南、信州新町中組、信州新町味藤、信州新町橋場、信州新町安用、信州新町風越、信州新町追沢、信州新町神田、信州新町花倉、信州新町二丁田、信州新町穴平、信州新町寺尾、信州新町矢ノ尻、信州新町峰組、信州新町枌ノ木、信州新町赤柴、信州新町石畑、信州新町尾崎、信州新町上古、信州新町芦沢、信州新町本村、信州新町大河、信州新町西日時\u0026#34; 41,\u0026#34;信州新町塩本、信州新町伊切、信州新町牧田中一、信州新町牧田中二、信州新町中牧一、信州新町中牧二、信州新町南牧住平、信州新町一倉田和、信州新町下中山、信州新町日名、信州新町和田吐唄、信州新町置原、信州新町橋木、信州新町左右、信州新町岩下、信州新町信級中央、信州新町高見、信州新町岩本、信州新町柳高、信州新町川名\u0026#34; 42,\u0026#34;中条\u0026#34; ","date":"2024-05-04T16:53:18+09:00","image":"/images/2022/04/ggcalen.jpg","permalink":"/posts/it/pc/5945/","title":"Python：Google Calendar API を使う"},{"content":"DevTools でコマンドを実行して取得していましたが、ショートカットが割り当てられることを知ったので設定しました。\nDevTools を開き、歯車アイコンをクリック\nサイドメニューの「ショートカット」を選び、\n「フルサイズのスクリーンショットをキャプチャ」を探します。\nデフォルトは未設定なので、好みのショートカットを設定します。\nDevTools をアクティブにした状態で設定したショートカットキーを押すと、スクリーンショットがキャプチャできます。\nChrome Developer Tools 入門 (Amazon) ","date":"2024-04-27T15:15:13+09:00","image":"/images/2022/08/shortcut00.jpg","permalink":"/posts/it/pc/5937/","title":"Google Chrome：ショートカットキーでスクリーンショットを撮る"},{"content":"例えばシェルスクリプトが test.sh で環境変数 TEST_ENV を設定していたとき、\ntest.sh の実行が終わると環境変数 TEST_ENV は消えている。\n実行後も残したい場合は、\n. test.sh\nまたは\nsource test.sh\nと実行すれば良い。\nまた、.bashrc や .bash_profile を修正したときは再ログインしなくても、\n. .bashrc や . .bash_profile で反映できる。\n","date":"2024-04-20T17:09:17+09:00","image":"/images/2023/06/tfdf00.jpg","permalink":"/posts/it/pc/5927/","title":"Linux：シェルスクリプトで設定した環境変数が残らないとき"},{"content":"仮想環境（venv）で明示的にインストールしたパッケージの一覧を取得したく、pip freeze を使ったのですが、依存でインストールされたパッケージまで含まれているもので欲しい一覧になりませんでした。\n調べた所、pip-chill パッケージで欲しかった一覧が取れたのでメモしておきます。\n※ 但し、手元の環境で試した限りは意図しない一覧になる場合もあったので注意が必要そうです。\n→ 明示的にインストールしたものが依存先になっているパッケージをインストールしたとか。\n(例) numpy インストール後に pandas をインストールすると numpy が出なくなる。\nこれぐらいなら良いんですが、別パターンで私には意図が読めない結果の場合もありました。\npip-chill のインストールは以下でOK。\npip install pip-chill この後 pip-chill を実行すると以下のエラーが発生したので、pip install setuptools を実行しました。\nModuleNotFoundError: No module named \u0026#39;pkg_resources\u0026#39; そして、pip install pandas した後の pip freeze と pip-chill の違いは以下の通り。\n$ pip freeze numpy==1.26.4 pandas==2.2.2 pip-chill==1.0.3 python-dateutil==2.9.0.post0 pytz==2024.1 setuptools==69.2.0 six==1.16.0 tzdata==2024.1 $ pip-chill pandas==2.2.2 pip-chill==1.0.3 ","date":"2024-04-13T07:41:15+09:00","image":"/images/2023/11/pydecarg00.jpg","permalink":"/posts/it/pc/5908/","title":"Python：明示的にインストールしたパッケージの一覧を取得する"},{"content":"ワークスペースのフォルダ直下に .env ファイルを作成して環境変数を設定するメモ。\nプロキシ環境下かつルート証明書の設定を定義した例。\nプロキシの設定 http_proxy, https_proxy が無いと外部に繋がらない。\n→ プロキシの値が https://proxy.example.com:8080 を例にした。\nプロキシを使わない接続先を no_proxy に設定。\nルート証明書の設定が無いと https が繋がらない。\n→ 例としてルート証明書のファイルをワークスペース直下に置いた。\nまた、REQUESTS_CA_BUNDLE で requests パッケージにルート証明書を示すことで検証してアクセスされる。\n→ 引数 verify=False で検証を止める必要が無い。\n定義済みの環境変数は ${\u0026hellip;} で参照できるので、同じ値のものは参照で設定する。\nhttp_proxy=https://proxy.example.com:8080 https_proxy=${http_proxy} no_proxy=\u0026#34;localhost, 127.0.0.1, ::1\u0026#34; CA_BUNDLE=${workspaceFolder}/cacert.pem REQUESTS_CA_BUNDLE=${CA_BUNDLE} ./.vscode/settings.json に .env ファイルを定義する。\n{ \u0026#34;python.envFile\u0026#34;: \u0026#34;${workspaceFolder}/.env\u0026#34; } ","date":"2024-04-06T07:15:34+09:00","image":"/images/2023/04/siteallow00.jpg","permalink":"/posts/it/pc/5885/","title":"VSCode：Python の環境変数を .env ファイルに設定する"},{"content":"「2026年度長野市ごみカレンダー」を公開しました。 2024年度（令和6年度）長野市のゴミ収集日を Google カレンダーに表示させる為の CSV ファイルを作成しました。 Google カレンダーに CSV ファイルを読み込ませる方法はこちらを参照ください。\n2024/05/04 追記\nカレンダーの共有リンクを追加しました。\nご自身の地区の「カレンダーに追加」リンクから Google カレンダーに収集日を追加できます。\nPC または Android からはデフォルトで利用できるようですが、iPhone は Google カレンダー アプリ がインストールされている必要がありました。\n収集日に使われる色は好みで変更ください。\n地区別 CSV ファイル / カレンダー共有リンク 長野市がクリエイティブ・コモンズ・ライセンス表示4.0国際（CC-BY4.0）ライセンスの下に公開しているデータを以前のソースコードを流用して加工しました。\n※ 内容は保証しません。利用は、ご自身の判断でお願い致します。\n下記リンクからファイルのダウンロードができない場合、\nGoogle ドライブの共有ファイルからダウンロードをお試しください。\nその際は、欲しい CSV の下記 No. を元に対象のファイルをご利用ください。\nNo. 地区別 CSV ファイル 共有リンク 01 第一 カレンダーに追加 02 第二 カレンダーに追加 03 第三 カレンダーに追加 04 第四 カレンダーに追加 05 第五 カレンダーに追加 06 芹田 カレンダーに追加 07 古牧 カレンダーに追加 08 三輪 カレンダーに追加 09 吉田 カレンダーに追加 10 古里、朝陽 カレンダーに追加 11 柳原、若穂 カレンダーに追加 12 浅川 カレンダーに追加 13 大豆島 カレンダーに追加 14 若槻 カレンダーに追加 15 長沼 カレンダーに追加 16 安茂里 カレンダーに追加 17 小田切 カレンダーに追加 18 芋井 カレンダーに追加 19 篠ノ井塩崎、篠ノ井共和、篠ノ井川柳、篠ノ井信里 カレンダーに追加 20 篠ノ井東福寺、篠ノ井西寺尾 カレンダーに追加 21 篠ノ井中央 カレンダーに追加 22 松代 カレンダーに追加 23 川中島 カレンダーに追加 24 更北 カレンダーに追加 25 七二会 カレンダーに追加 26 信更 カレンダーに追加 27 戸隠中社、戸隠宝光社、戸隠上楠川 カレンダーに追加 28 戸隠北部、戸隠中央、戸隠東部、戸隠南部、戸隠川手、戸隠志垣 カレンダーに追加 29 戸隠西部、戸隠平、戸隠西条、戸隠追通、戸隠上祖山、戸隠下祖山 カレンダーに追加 30 鬼無里上里、鬼無里中央1 カレンダーに追加 31 鬼無里中央2、鬼無里両京 カレンダーに追加 32 大岡甲（川口を除く）、大岡中牧、大岡弘崎、大岡聖ヶ岡 カレンダーに追加 33 大岡乙、大岡丙（聖ヶ岡を除く）、大岡川口 カレンダーに追加 34 豊野南郷、豊野石、豊野西町、豊野上組、豊野立町、豊野南町、豊野中尾1・2 カレンダーに追加 35 豊野横町、豊野伊豆毛、豊野上田中、豊野神代町、豊野本町1・2、豊野中尾1・2（一部）、豊野小瀬1、豊野泉平、豊野上神代、豊野豊陽台、豊野沖1・2、豊野中央組、豊野向原 カレンダーに追加 36 豊野本町3・4・5、豊野東町、豊野小瀬2、豊野ゆたかの、豊野豊南町、豊野下田中 カレンダーに追加 37 豊野浅野、豊野蟹沢、豊野入、豊野小日向、豊野上堰、豊野鳥居団地、豊野大方、豊野橋場、豊野上原、豊野蟻ケ崎、豊野城山、豊野川谷 カレンダーに追加 38 信州新町旭町、信州新町仲町、信州新町上町、信州新町西上町、信州新町常磐町、信州新町鹿島東、信州新町鹿島西、信州新町大原東、信州新町大原西、信州新町下市場、信州新町牧野島（伊切を除く）、信州新町鹿道、信州新町鹿道団地 カレンダーに追加 39 信州新町久保、信州新町本町、信州新町境町、信州新町千原田、信州新町平、信州新町和平団地、信州新町藤池団地、信州新町下川西平、信州新町太田笠子（石畑を除く）、信州新町穂刈下、信州新町穂刈中、信州新町穂刈上、信州新町穂刈北、信州新町穂刈団地、信州新町陽のあたる丘、信州新町大門、信州新町LR、信州新町原、信州新町道祖神 カレンダーに追加 40 信州新町津和中央、信州新町山秋、信州新町中福、信州新町栃久保、信州新町中尾、信州新町菅沼、信州新町細尾、信州新町津上、信州新町外味藤、信州新町豊和、信州新町津南、信州新町中組、信州新町味藤、信州新町橋場、信州新町安用、信州新町風越、信州新町追沢、信州新町神田、信州新町花倉、信州新町二丁田、信州新町穴平、信州新町寺尾、信州新町矢ノ尻、信州新町峰組、信州新町枌ノ木、信州新町赤柴、信州新町石畑、信州新町尾崎、信州新町上古、信州新町芦沢、信州新町本村、信州新町大河、信州新町西日時 カレンダーに追加 41 信州新町塩本、信州新町伊切、信州新町牧田中一、信州新町牧田中二、信州新町中牧一、信州新町中牧二、信州新町南牧住平、信州新町一倉田和、信州新町下中山、信州新町日名、信州新町和田吐唄、信州新町置原、信州新町橋木、信州新町左右、信州新町岩下、信州新町信級中央、信州新町高見、信州新町岩本、信州新町柳高、信州新町川名 カレンダーに追加 42 中条 カレンダーに追加 情報元 上記 CSV ファイルの元データや参考にしたソースコードです。\n長野市オープンデータ 令和６年度 ごみ収集カレンダー （「最後に更新した日」は「2024年3月25日」でした。） Rへのロード用API ","date":"2024-03-30T07:15:00+09:00","image":"/images/2022/04/gomi.jpg","permalink":"/posts/it/pc/5797/","title":"Google カレンダー：2024年度長野市ごみ収集日"},{"content":"@pytest.fixture デコレータで前処理/後処理が書けるので、テストで共通するログイン処理を前処理として作成した例です。\nimport requests import pytest @pytest.fixture def login_session(): with requests.Session() as session: res = session.post( \u0026#34;ログイン先のURL\u0026#34;, data=( { \u0026#34;id\u0026#34;: \u0026#34;kuma\u0026#34;, \u0026#34;pass\u0026#34;: \u0026#34;emon\u0026#34;, } ), ) if res.status_code != 200: raise Exception(res.text) yield session def test_resource1(login_session): # login_session を使ってテスト res = login_session.get(\u0026#34;リソースのURL\u0026#34;) assert res.status_code == 200 with を使用したので後処理の記述が無いですが、以下のパターンで定義すれば良いです。\n@pytest.fixture def setup_teardown(): # テストごとの事前処理 # 例えば、データベースの接続を確立するなど yield # 例えば、データベースの接続を返すなど # テストごとの事後処理 # 例えば、データベースの接続を閉じるなど def test_hoge(setup_teardown): # setup_teardown からの値を使ってテスト ","date":"2024-03-23T18:46:48+09:00","image":"/images/2023/12/pylgst00.jpg","permalink":"/posts/it/pc/5866/","title":"pytest：共通の前処理/後処理を作成する"},{"content":"日本取引所グループで公開されていた\u0026ldquo;構成銘柄別ウエイト一覧\u0026quot;に、配当利回りの列を付与してみました。\n銘柄コードの終わりに \u0026ldquo;.T\u0026rdquo; を追加。それで良いのか未検証。 配当利回り等の値は Yahoo Finance から取得。 Yahoo ファイナンス JAPAN で見られる株式ランキングの値と異なっていたので、終値と一株配当も付与してみました。\n※ 作成したソースコード及びその結果の一覧は保証しません。\n配当利回りなどの列を追加した一覧をダウンロード（2024/08/20 作成）\nimport pandas as pd import yfinance as yf from datetime import datetime from zoneinfo import ZoneInfo # TOPIX 構成銘柄別ウエイト一覧 csv_url = ( \u0026#34;https://www.jpx.co.jp/automation/markets/indices/\u0026#34; \u0026#34;topix/files/topixweight_j.csv\u0026#34; ) df = pd.read_csv(csv_url, encoding=\u0026#34;shift_jis\u0026#34;) # 不要な列を削除 df = df.drop(columns=[\u0026#34;日付\u0026#34;]) df = df.drop(columns=[\u0026#34;ニューインデックス区分\u0026#34;]) # \u0026#34;コード\u0026#34;列が数字で始まっている行のみにし、末尾に\u0026#34;.T”を追加 df = df[df[\u0026#34;コード\u0026#34;].astype(str).str.match(\u0026#34;^\\\\d\u0026#34;)] df[\u0026#34;コード\u0026#34;] = df[\u0026#34;コード\u0026#34;].astype(str).str.replace(\u0026#34;$\u0026#34;, \u0026#34;.T\u0026#34;, regex=True) # 銘柄コード毎に次の値を格納 # - 配当利回り # - 終値 # - 一株配当 dividend_yields = [] close_prices = [] dividends_per_share = [] for code in df[\u0026#34;コード\u0026#34;]: stock_info = yf.Ticker(code) dividend_yield = stock_info.info.get(\u0026#34;dividendYield\u0026#34;, None) dividend_yields.append(dividend_yield) # 上場廃止銘柄は終値が取れずにエラーになったので対策 try: close_price = stock_info.history(period=\u0026#34;1d\u0026#34;).iloc[-1][\u0026#34;Close\u0026#34;] except IndexError: close_price = None close_prices.append(close_price) dividend_per_share = stock_info.info.get(\u0026#34;dividendRate\u0026#34;, None) dividends_per_share.append(dividend_per_share) df[\u0026#34;配当利回り\u0026#34;] = dividend_yields df[\u0026#34;終値\u0026#34;] = close_prices df[\u0026#34;一株配当\u0026#34;] = dividends_per_share # CSVファイルに出力 now = datetime.now(ZoneInfo(\u0026#34;Asia/Tokyo\u0026#34;)) ts = now.strftime(\u0026#34;%Y%m%d\u0026#34;) output_csv = f\u0026#34;topixweight_j_with_some_value_{ts}.csv\u0026#34; df.to_csv(output_csv, index=False) 以前のコード 2024/03/16 頃に使用したコードです。\n2024/08/20 現在は実行してもエラーになりますが残しておきます。\n銘柄コードの終わりが \u0026ldquo;.0\u0026rdquo; だったので、\u0026quot;.T\u0026rdquo; に置換。それで良いのか未検証。 配当利回り等の値は Yahoo Finance から取得。 配当利回りなどの列を追加した一覧をダウンロード（2024/03/16 作成）\nimport pandas as pd import yfinance as yf # TOPIX 構成銘柄別ウエイト一覧（1月末現在） csv_url = ( \u0026#34;https://www.jpx.co.jp/markets/indices/topix/\u0026#34; \u0026#34;tvdivq00000030ne-att/topixweight_j.csv\u0026#34; ) df = pd.read_csv(csv_url, encoding=\u0026#34;shift_jis\u0026#34;) # 不要な列を削除 df = df.drop(columns=[\u0026#34;日付\u0026#34;]) df = df.drop(columns=[\u0026#34;ニューインデックス区分\u0026#34;]) # \u0026#34;コード\u0026#34;列が数字で始まっている行のみにし、\u0026#34;.0\u0026#34;を\u0026#34;.T\u0026#34;に置換 df = df[df[\u0026#34;コード\u0026#34;].astype(str).str.match(\u0026#34;^\\\\d\u0026#34;)] df[\u0026#34;コード\u0026#34;] = df[\u0026#34;コード\u0026#34;].astype(str).str.replace(\u0026#34;\\\\.0$\u0026#34;, \u0026#34;.T\u0026#34;, regex=True) # 銘柄コード毎に次の値を格納 # - 配当利回り # - 終値 # - 一株配当 dividend_yields = [] close_prices = [] dividends_per_share = [] for code in df[\u0026#34;コード\u0026#34;]: stock_info = yf.Ticker(code) dividend_yield = stock_info.info.get(\u0026#34;dividendYield\u0026#34;, None) dividend_yields.append(dividend_yield) close_price = stock_info.history(period=\u0026#34;1d\u0026#34;).iloc[-1][\u0026#34;Close\u0026#34;] close_prices.append(close_price) dividend_per_share = stock_info.info.get(\u0026#34;dividendRate\u0026#34;, None) dividends_per_share.append(dividend_per_share) df[\u0026#34;配当利回り\u0026#34;] = dividend_yields df[\u0026#34;終値\u0026#34;] = close_prices df[\u0026#34;一株配当\u0026#34;] = dividends_per_share # CSVファイルに出力 output_csv = \u0026#34;topixweight_j_with_some_value.csv\u0026#34; df.to_csv(output_csv, index=False) Pythonでできる！ 株価データ分析 (Amazon) Docker Desktop for Windows/Macでつくるクリーンな開発環境構築入門(Python版) (Amazon) ファイナンス機械学習 (Amazon) アセットマネージャーのためのファイナンス機械学習 (Amazon)\nコメント TaC · 2024年8月16日\n初めまして。コードありがとうございます。 株式データを取得をしたく､新しくpythonを始めた者です。とても参考になりました。 早速試してみたのですが、下記エラーが発生しました。 エラーの内容を見ると恐らくcsvのURLが見つからないようです。 もし宜しければ､お手すきの際に解決法を教えて頂けないでしょうか？\ncsv_urlのみ下記､最新アドレスに変更しており､他はそのままコピーペーストしています。 \u0026ldquo;https://www.jpx.co.jp/markets/indices/topix/files/topixweight_j.csv\"\nIDLE Shell で Run Moduleしています。\nエラー内容 ------------------------------------- Traceback (most recent call last): File \u0026#34;C:\\Users\\xxx\\AppData\\Local\\Programs\\Python\\Python312\\TestPythonTopix.py\u0026#34;, line 8, in df = pd.read_csv(csv_url, encoding=\u0026#34;shift_jis\u0026#34;) File \u0026#34;C:\\Users\\xxx\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\pandas\\io\\parsers\\readers.py\u0026#34;, line 1026, in read_csv return _read(filepath_or_buffer, kwds) File \u0026#34;C:\\Users\\xxx\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\pandas\\io\\parsers\\readers.py\u0026#34;, line 620, in _read parser = TextFileReader(filepath_or_buffer, **kwds) File \u0026#34;C:\\Users\\xxx\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\pandas\\io\\parsers\\readers.py\u0026#34;, line 1620, in __init__ self._engine = self._make_engine(f, self.engine) File \u0026#34;C:\\Users\\xxx\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\pandas\\io\\parsers\\readers.py\u0026#34;, line 1880, in _make_engine self.handles = get_handle( File \u0026#34;C:\\Users\\xxx\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\pandas\\io\\common.py\u0026#34;, line 728, in get_handle ioargs = _get_filepath_or_buffer( File \u0026#34;C:\\Users\\xxx\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\pandas\\io\\common.py\u0026#34;, line 384, in _get_filepath_or_buffer with urlopen(req_info) as req: File \u0026#34;C:\\Users\\xxx\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\pandas\\io\\common.py\u0026#34;, line 289, in urlopen return urllib.request.urlopen(*args, **kwargs) File \u0026#34;C:\\Users\\xxx\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\urllib\\request.py\u0026#34;, line 215, in urlopen return opener.open(url, data, timeout) File \u0026#34;C:\\Users\\xxx\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\urllib\\request.py\u0026#34;, line 521, in open response = meth(req, response) File \u0026#34;C:\\Users\\xxx\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\urllib\\request.py\u0026#34;, line 630, in http_response response = self.parent.error( File \u0026#34;C:\\Users\\xxx\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\urllib\\request.py\u0026#34;, line 559, in error return self._call_chain(*args) File \u0026#34;C:\\Users\\xxx\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\urllib\\request.py\u0026#34;, line 492, in _call_chain result = func(*args) File \u0026#34;C:\\Users\\xxx\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\urllib\\request.py\u0026#34;, line 639, in http_error_default raise HTTPError(req.full_url, code, msg, hdrs, fp) urllib.error.HTTPError: HTTP Error 404: Not Found 熊右衛門 · 2024年8月20日\n修正頂いた csv_url が違っておりエラーになったようです。 現在は \u0026ldquo;https://www.jpx.co.jp/automation/markets/indices/topix/files/topixweight_j.csv\" であり、\u0026quot;/automation\u0026rdquo; が抜けていると見受けます。 上記含め、いくつかの修正をして投稿のコード及びデータを更新しましたので参照ください。\nTaC · 2024年8月21日\n先日は長文失礼しました。 ご返信また､コードの更新ありがとうございます。大変助かりました。 お陰様で無事に実行・完了することができました。\n途中､401 Client Error: Unauthorized for url が出ましたが､ yfinanceをアップデートすることで最後まで完了できました。 次はサーバーでスケジュール実行をしようと思っています。ありがとうございました！\n","date":"2024-03-16T17:41:54+09:00","image":"/images/2021/08/stock.jpg","permalink":"/posts/it/pc/5836/","title":"Python：TOPIX に含まれる銘柄の配当利回りを取得する"},{"content":"関数をモックに差し替えるけど、引数の値によっては元々の関数で処理したい、という場合のメモです。\nまずは、モックに差し替える関数の例。\ndef myfunc(cond): return f\u0026#34;original: {cond}\u0026#34; pytest で実行するテストコード。\nモックに差し替える前に、元の関数を org_myfunc に保持し、差し替えたモック内で引数を判定して処理を分岐させます。\nimport mdl import pytest def test_myfunc(mocker): org_myfunc = mdl.myfunc def mock_myfunc(cond): if cond: return f\u0026#34;mock: {cond}\u0026#34; else: return org_myfunc(cond) mocker.patch(\u0026#34;mdl.myfunc\u0026#34;, side_effect=mock_myfunc) assert mdl.myfunc(True) == \u0026#34;mock: True\u0026#34; assert mdl.myfunc(False) == \u0026#34;original: False\u0026#34; ","date":"2024-03-09T15:48:35+09:00","image":"/images/2021/06/mock.jpg","permalink":"/posts/it/pc/5827/","title":"pytest：モックの処理内容を引数で変える"},{"content":"自分が調べた限り、現時点では以下の拡張機能をインストール、設定すれば良さそうに感じました。\nBlack Formatter Flake8 isort 設定は以下で初めようと思います。\nまずは black と flask8 で1行の長さの初期値が違うようなので設定しました。\n必要に応じてカスタマイズかな。\n{ \u0026#34;[python]\u0026#34;: { \u0026#34;editor.defaultFormatter\u0026#34;: \u0026#34;ms-python.black-formatter\u0026#34;, \u0026#34;editor.formatOnSave\u0026#34;: true, \u0026#34;editor.codeActionsOnSave\u0026#34;: { \u0026#34;source.organizeImports\u0026#34;: \u0026#34;explicit\u0026#34; }, }, \u0026#34;black-formatter.args\u0026#34;: [ \u0026#34;--line-length\u0026#34;, \u0026#34;79\u0026#34; ], \u0026#34;flake8.args\u0026#34;: [ \u0026#34;--max-line-length\u0026#34;, \u0026#34;79\u0026#34; ], \u0026#34;isort.args\u0026#34;:[\u0026#34;--profile\u0026#34;, \u0026#34;black\u0026#34;] } ルールに違反した適当なコードで試してみます。\n次のコードが保存前。\n次のコードが保存後。\n手動による修正が必要な所が残りましたが、まずは良さそうかなと思います。\n参考：Existing Python Tools Extensions\n","date":"2024-03-02T14:48:03+09:00","image":"/images/2022/01/ir.jpg","permalink":"/posts/it/pc/5820/","title":"VSCode：Python のフォーマッターとリンターを設定する"},{"content":"長い文字列、例えば「心地よい陽光、爽やかな風、穏やかな心。自然と笑顔が溢れ、幸せが全身を包みます。感謝と平和の日。」を変数に代入するとき、複数行で書く際の例です。\nバックスラッシュを使う value = \u0026#34;心地よい陽光、爽やかな風、穏やかな心。\u0026#34; \\ \u0026#34;自然と笑顔が溢れ、幸せが全身を包みます。\u0026#34; \\ \u0026#34;感謝と平和の日。\u0026#34; 括弧を使う value = ( \u0026#34;心地よい陽光、爽やかな風、穏やかな心。\u0026#34; \u0026#34;自然と笑顔が溢れ、幸せが全身を包みます。\u0026#34; \u0026#34;感謝と平和の日。\u0026#34; ) ","date":"2024-02-24T07:17:00+09:00","image":"/images/2024/02/stva00.jpg","permalink":"/posts/it/pc/5814/","title":"Python：変数に代入する長い文字列を複数行で書く"},{"content":"requests から session を得てアクセスしていけば OK です。\nログインの有無を Cookie で管理されている場合などに便利です。\nimport requests with requests.Session() as s: res = s.post( \u0026#34;ログイン先のURL\u0026#34;, data = ({ \u0026#34;id\u0026#34;: \u0026#34;kuma\u0026#34;, \u0026#34;pass\u0026#34;: \u0026#34;emon\u0026#34; }) ) res = s.get(\u0026#34;何等かの情報を取得\u0026#34;) 使わない場合は Cookie を受け渡して行けば良いですが手間ですね。\nimport requests res = requests.post( \u0026#34;ログイン先のURL\u0026#34;, data = ({ \u0026#34;id\u0026#34;: \u0026#34;kuma\u0026#34;, \u0026#34;pass\u0026#34;: \u0026#34;emon\u0026#34; }) ) res = requests.get(\u0026#34;何等かの情報を取得\u0026#34;, cookies = res.cookies) ","date":"2024-02-17T09:49:21+09:00","image":"/images/2023/07/pywi00.jpg","permalink":"/posts/it/pc/5807/","title":"Python：requests で Cookie を引き継いでアクセスする"},{"content":"VSCode の Docker 拡張機能で、コンテナ内のファイルを開けて編集も可能だったのでメモです。\n今までは、コンテナ内のファイルを確認したい場合は -it で入っていましたが、VSCode から開けるなら手間いらずです。\nまた、修正が残らないとは言えコンテナ内のファイルを編集できるのも、ちょっとしたときに便利です。\nVisual Studio Code実践ガイド (Amazon) Visual Studio Codeデバッグ技術 (Amazon)\n","date":"2024-02-10T19:37:09+09:00","image":"/images/2023/04/siteallow00.jpg","permalink":"/posts/it/pc/5791/","title":"VSCode：Docker コンテナ内のファイルを開く"},{"content":"ライブラリに pycryptodome を使用するので、予めインストールしておきます。\npip install pycryptodome 暗号化と復号化は別のマシンで実行されるものですが、例ということで。\nfrom Crypto.PublicKey import RSA from Crypto.Cipher import PKCS1_OAEP # 暗号化する文字列 message = \u0026#34;kuma\u0026#34; # 公開鍵/秘密鍵を生成する key = RSA.generate(2048) private_key = key.export_key() public_key = key.publickey().export_key() print(f\u0026#34;公開鍵: {public_key.decode(\u0026#39;utf-8\u0026#39;)}\u0026#34;) # 公開鍵で文字列を暗号化する public_key_obj = RSA.import_key(public_key) cipher = PKCS1_OAEP.new(public_key_obj) encrypted_message = cipher.encrypt(message.encode(\u0026#34;utf-8\u0026#34;)) # 暗号化した結果を秘密鍵で復号化する private_key_obj = RSA.import_key(private_key) decipher = PKCS1_OAEP.new(private_key_obj) decrypted_message = decipher.decrypt(encrypted_message) print(f\u0026#34;元の文字列: {message}\u0026#34;) print(f\u0026#34;暗号化後: {encrypted_message.hex()}\u0026#34;) print(f\u0026#34;復号化後: {decrypted_message.decode(\u0026#39;utf-8\u0026#39;)}\u0026#34;) 公開鍵: -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvYcDGJ3IDGQBDAxtyrto NG50KZex+NVyFHf9qOt/FKT6HYyMwB3XrdTQiyw6TZE9IlfKsnzQeNDuNUr9d9kE JVR1CpeOtDGu+Yi/7Fzz1jm5/Oy4FgsESv4J0ENB5ICcMTtagy8E8hUrAXzkAOZ0 PPwIQ+c4QGb4Exg7to7AD024+VVsjiW2obAXRechzGbE38uNFLSMI4pqBVlKfh/0 WMSk6pQj13CsTwk5NSUm+i9D4/LYGU2n6jQBebTe/aITlHSYQX/BanZbmEGcGmv9 s6b0mfzV+Wp4YBmUIoAVBfb1gTjLVbTEA8ieE20p48N6wAGxCmoTkByoqLgwkBEv jQIDAQAB -----END PUBLIC KEY----- 元の文字列: kuma 暗号化後: 84452db925a820614abb1b21b5716cc7cb14a7163b37aa3c4533683b282dcad9f65a052bbfcc8281beddea9ef08e02027a9afcd391f2b238eb10f42d0b279338487837de02c02cc92281b7f7ac0750ebad69ac365e04300730a65ffd09e76bddf535bd58051a4b2a8db08dcdbf02a9d0ce0b9bcd1385e4274f18d84df47277520039665a4420a4a5467836289e7f9c10a00eb3a70484c97d1f7185c716fd9c07da601666937e70698005ab8ef2a68b92001aa8eb4a330516a57eefd044e177ccd4163fd425218c6eda94b73d405b2211cc5157815375150a6973ac666e32f053b687e9c99c408f599e81a3ffc5e4f77de45bc6dae36ec9faa60332909d541c4c 復号化後: kuma ","date":"2024-02-03T17:02:43+09:00","image":"/images/2022/01/crypt.jpg","permalink":"/posts/it/pc/5778/","title":"Python：RSA 公開鍵/秘密鍵で文字列を暗号化/復号化する"},{"content":"プロキシの設定を環境変数に定義したかった時のメモです。\nroot ユーザーでも一般ユーザーでも同じなので、一箇所で設定したかったんです。\n1. 環境変数を設定するスクリプトを作成する /etc/profile.d/ ディレクトリに環境変数を設定するスクリプトを作成します。\n今回はプロキシの設定なので、proxy.sh で作成しました。\n2. 環境変数の設定を記載する proxy.sh を以下の内容で作成します。\nプロキシのURLやポート番号、必要であれば認証の設定も記載します。\nexport http_proxy=\u0026lt;プロキシのURL\u0026gt;:\u0026lt;ポート番号\u0026gt; export https_proxy=\u0026lt;プロキシのURL\u0026gt;:\u0026lt;ポート番号\u0026gt; ファイルに実行権限を付けなくても Ubuntu では効きましたが、他のディストリビューションも同様かは知りません。\n同じディレクトリにある既存のスクリプトファイルの権限を確認し、合わせておけば良いと思われます。\n補足：個別にプロキシの設定が必要なものがある docker には上記環境変数が効かなかったので、別途設定しました。\n外部ネットワークにアクセスするようなものは、まずは上記設定を見て、無ければ個別の設定を見る、といった作りになっていると有り難いんですが、何か事情があるんでしょうか。\n","date":"2024-01-27T03:28:20+09:00","image":"/images/2023/10/vovla00.jpg","permalink":"/posts/it/pc/5768/","title":"Linux：全ユーザーで共通する環境変数を設定する"},{"content":"VSCode のページでショートカットの一覧を見つけ、覚えてみようかなと思っています。\n自分の場合は IDE の以前と以後で、エディタで使うショートカットキーが減りましたね。\nまた、触るマシンも変わったりしてカスタマイズした設定を適用しづらくなった事も、減った原因かもしれません。\n全部使わなくても、頻度が高く効率が上がるものは早く覚えられますよね。\nカスタマイズは便利なんですが、標準に慣れておくと、どこでも使える方が便利かなと思います。\nVSCode 上でもショートカットの一覧を確認できるので、ちょっとした時間に見直せて便利ですね。\nVSCode 上から確認する場合は、\n・Windows と Linux の場合： CTRL+K, CTRL+S\n・Mac の場合：Command+K, Command+S\nと２ストロークの入力で確認できます。\nVisual Studio Code実践ガイド (Amazon) Visual Studio Codeデバッグ技術 (Amazon) Docker Desktop for Windows/Mac と VSCode でつくるクリーンな開発環境構築入門 (Amazon)\n","date":"2024-01-20T13:35:05+09:00","image":"/images/2023/09/kbs00.jpg","permalink":"/posts/it/pc/5759/","title":"VSCode：ショートカットキーで操作する"},{"content":"ミュージックのアプリから高い頻度で聴くアルバムを再生するのが面倒になったので、ホーム画面にそれ用のアイコンを配置しました。\n以下のような感じでアイコンが作成でき、タップすると再生が始まります。\n気に入った曲を集めたプレイリストでも作成できるので、そのアイコンを置くのも良いでしょう。\nアイコンにする画像を用意する この辺りの説明は簡単に行きます。\n写真アプリにアイコンに使用する画像を保存しておきます。\n無くても良いんですが、あった方がホーム画面上で分かりやすく見栄えもします。\n自分は 128x128 のサイズで用意しましたが、適切なサイズかは確認していません。\nアルバムを再生するショートカットを作成する ショートカットのアプリを開きます。\n「+」をタップして新しいショートカットを作成します。\n「新規ショートカット」をタップし、メニューの「名称変更」をタップ後、名称を入力します。\nアイコンの下に表示される名称になります。後でも変更できます。\n「アクションを追加」をタップします。\n「アプリ」の「ミュージック」をタップします。\n「ミュージックを再生」をタップします。\n「ミュージック」をタップします。\n対象のアルバムやプレイリストを選択します。\nアルバムの全曲を対象にするので、上にある「＋」をタップします。\nアルバムを繰り返し再生し続ける設定をします。\n矢印マークをタップしてメニューを開き、リピートの「選択」から「すべて」を選びます。\n「完了」でショートカットの作成を完了します。\nショートカットが作成できました。\nアイコンをホーム画面に置く ショートカットを長押しし、メニューの「共有」をタップします。\n「ホーム画面に追加」をタップします。\nアイコンをタップして「写真を選択」から用意した写真に変更します。\n「追加」のタップでホーム画面に追加されます。\n後はそのアイコンを好きな場所に配置します。\nMac Fan 2026年5月号 (Amazon) ","date":"2024-01-13T11:57:11+09:00","image":"/images/2024/01/musicicon00.jpg","permalink":"/posts/it/smartphone/5710/","title":"iPhone：ホーム画面にアルバムを再生するアイコンを置く"},{"content":"国土地理院のサイトで出来たんですが、見慣れた Google Map の画像にしたい。でも、緯度経度を決めてのスクショは厳しい。\nということで、Maps Static API を使用して画像ファイルを取得してみました。\nGoogle Colab がお手軽なので Python にしましたが、別の言語でも良いと思います。\nMaps Static API で地図の画像を保存 API キーの取得と、Maps Static API を許可する説明は省略します。\nコードは以下のようにしました。\nimport requests # 自分のAPIキーに置き換える api_key = \u0026#34;自分のAPIキー\u0026#34; def save_map_image(api_key, filename, locations, maptype, zoom, width, height): base_url = \u0026#34;https://maps.googleapis.com/maps/api/staticmap?\u0026#34; api_key_param = f\u0026#34;key={api_key}\u0026#34; maptype_param = f\u0026#34;maptype={maptype}\u0026#34; zoom_param = f\u0026#34;zoom={zoom}\u0026#34; size_param = f\u0026#34;size={width}x{height}\u0026#34; markers = \u0026#34;\u0026amp;\u0026#34;.join([f\u0026#34;markers=size:small%7C{loc}\u0026#34; for loc in locations]) request_url = base_url + \u0026#34;\u0026amp;\u0026#34;.join([ api_key_param, maptype_param, zoom_param, size_param, markers, \u0026#34;scale=2\u0026#34;, \u0026#34;language=ja_JP\u0026#34;, ]) response = requests.get(request_url) if response.status_code == 200: with open(filename, \u0026#34;wb\u0026#34;) as f: f.write(response.content) else: raise Exception(f\u0026#34;Error: {response.status_code} - {response.text}\u0026#34;) save_map_image( api_key, \u0026#34;test.png\u0026#34;, [\u0026#34;36.103993,140.084481\u0026#34;, \u0026#34;36.105103,140.085855\u0026#34;], \u0026#34;roadmap\u0026#34;, 17, 256, 256 ) 保存できたファイルは次の通りです。\nzoom の値を何度か調整して、経度と緯度の位置がマーカーで分かるようにしました。\n少し面倒ですが、トリミングすれば良いかなと。\n国土地理院のサイトで地図の画像を保存 参考までに国土地理院のサイトを使った場合です。\nツールバーの「共有」から、山っぽいアイコンを選びます。\n「範囲を固定」を選択すると、緯度経度を指定できます。\n「OK」で進めていくと、画像ファイルに保存できます。\n","date":"2024-01-06T15:23:26+09:00","image":"/images/2021/05/navi.jpg","permalink":"/posts/it/pc/5688/","title":"Python：緯度経度を指定して地図の画像ファイルを保存する"},{"content":"登録は新たな環境を構築する場合や、開発メンバーで揃えるとき、\n削除はその前段階として一旦全部消す場合に使えると思います。\nインストールしている拡張機能の一覧を取得 一括で登録または削除する為に、まずは拡張機能の一覧を取得します。\nファイルに取得せずに行うことも可能ですが、取っておいた方が安心だと思います。\nシェルから次のコマンドを実行します。\nWindows でも同様に PowerShell から実行します。\ncode --list-extensions \u0026gt; extensions.txt これで、extensions.txt に拡張機能の一覧が書き出されます。\n一括アンインストール xargs が使えるとき\ncat extensions.txt | xargs -n 1 code --uninstall-extension 使えないとき\nwhile read -r extension; do code --uninstall-extension \u0026#34;$extension\u0026#34; done \u0026lt; extensions.txt Windows のとき\ncat extensions.txt | ForEach-Object { code --uninstall-extension $_ } 一括インストール オプションが --uninstall-extension から --install-extension に変わるだけです。\nxargs が使えるとき\ncat extensions.txt | xargs -n 1 code --install-extension 使えないとき\nwhile read -r extension; do code --install-extension \u0026#34;$extension\u0026#34; done \u0026lt; extensions.txt Windows のとき\ncat extensions.txt | ForEach-Object { code --install-extension $_ } Visual Studio Code実践ガイド (Amazon) Visual Studio Codeデバッグ技術 (Amazon)\n","date":"2023-12-30T13:06:04+09:00","image":"/images/2022/05/amzn2.jpg","permalink":"/posts/it/pc/5670/","title":"VSCode：拡張機能を一括でインストールまたはアンインストールする"},{"content":"pull 済みイメージの新しいものがあるとき、個々にアップデートするのは大変です。\nなので、一括で取得するコマンドを用意しました。\nfor image in $(docker images --format \u0026#34;{{.Repository}}:{{.Tag}}\u0026#34;); do docker pull $image; done foreach ($image in $(docker images --format \u0026#34;{{.Repository}}:{{.Tag}}\u0026#34;)) { docker pull $image } 更新後、未使用になったイメージを次のコマンドで削除しておきます。\ndocker rmi $(docker images -f \u0026#34;dangling=true\u0026#34; -q) docker images -f \u0026#34;dangling=true\u0026#34; -q | ForEach-Object { docker rmi $_ } Docker Desktop for Windows/Mac と VSCode でつくるクリーンな開発環境構築入門 (Amazon) Docker Desktop for Windows/Macでつくるクリーンな開発環境構築入門(Python版) (Amazon)\n","date":"2023-12-23T12:49:41+09:00","image":"/images/2023/04/siteallow00.jpg","permalink":"/posts/it/pc/5659/","title":"Docker：pull 済みイメージの最新を一括で取得する"},{"content":"コードは Mac のシェル（zsh）から実行しています。\n$?: 直前に実行されたコマンドの終了コード % date 2023年 12月16日 土曜日 15時36分19秒 JST % echo $? 0 $$: 現在のシェルプロセスのプロセスID % ps 68374 ttys001 0:00.05 /bin/zsh -il 96238 ttys002 0:00.06 -zsh % echo $$ 96238 $0: 現在のスクリプトの名前 % echo $0 -zsh $1, $2, …, $n: スクリプトや関数に渡された引数の値 echo $1 echo $2 echo $3 % ./test.sh a bc def ghij a bc def $#: 渡された引数の数 echo $# % ./test.sh a bc def ghij 4 $@: 全ての引数をクォートした状態で取得 echo $@ % ./test.sh a bc def ghij a bc def ghij クォートされているらしいんですが、表示させる方法が思いつかない。\nなので、$* と見た目の結果ですね。。\n$*: 全ての引数をクォートせずに取得 echo $* % ./test.sh a bc def ghij a bc def ghij $!: 直前にバックグラウンドで実行されたプロセスのPID % sleep 3 \u0026amp; echo $! [1] 96786 96786 シェルスクリプト基本リファレンス (Amazon) ","date":"2023-12-16T16:09:12+09:00","image":"/images/2021/06/mock.jpg","permalink":"/posts/it/pc/5631/","title":"シェルスクリプト：組み込み変数のメモ"},{"content":"JavaScript のスプレッド演算子の様なことをしたかったので調べました。\ndict1 = {\u0026#39;a\u0026#39;: 1, \u0026#39;b\u0026#39;: 2} dict2 = {\u0026#39;c\u0026#39;: 3, \u0026#39;d\u0026#39;: 4} dict3 = {**dict1, **dict2} # dict3 は {\u0026#39;a\u0026#39;: 1, \u0026#39;b\u0026#39;: 2, \u0026#39;c\u0026#39;: 3, \u0026#39;d\u0026#39;: 4} になる 同じキーを後ろに持っていくことで、上書き可能です。\ndict1 = {\u0026#39;a\u0026#39;: 1, \u0026#39;b\u0026#39;: 2} dict2 = {\u0026#39;b\u0026#39;: 3, \u0026#39;c\u0026#39;: 4} dict3 = {**dict1, **dict2} # dict3 は {\u0026#39;a\u0026#39;: 1, \u0026#39;b\u0026#39;: 3, \u0026#39;c\u0026#39;: 4} になる dict4 = {**dict1, \u0026#39;b\u0026#39;: 9} # dict4 は {\u0026#39;a\u0026#39;: 1, \u0026#39;b\u0026#39;: 9, \u0026#39;c\u0026#39;: 4} になる ディクショナリの update メソッドを使う手もありましたが、元のディクショナリが変更されるので、そちらの場合はそのつもりで。\nDocker Desktop for Windows/Macでつくるクリーンな開発環境構築入門(Python版) (Amazon) ","date":"2023-12-09T09:37:43+09:00","image":"/images/2023/12/pydct00.jpg","permalink":"/posts/it/pc/5622/","title":"Python：ディクショナリ同士を結合する"},{"content":"スタックトレースを出力するには exc_info を True にするが、毎回引数に指定はしたくない。\nそんなときの対応方法のメモです。\nスタックトレースが欲しいのは error と critical かと思いますが、その辺りはお好みで。\nfunctools.partial を使ってデフォルトの値を変えて使います。\nimport logging from functools import partial logging.basicConfig(level=logging.DEBUG) logger = logging.getLogger(\u0026#34;hoge\u0026#34;) logger.error = partial(logger.error, exc_info=True) logger.critical = partial(logger.critical, exc_info=True) try: raise Exception(\u0026#34;例外発生\u0026#34;) except Exception as e: logger.error(\u0026#34;エラーメッセージ\u0026#34;) Docker Desktop for Windows/Macでつくるクリーンな開発環境構築入門(Python版) (Amazon) ","date":"2023-12-02T14:45:12+09:00","image":"/images/2023/12/pylgst00.jpg","permalink":"/posts/it/pc/5604/","title":"Python：logging で exc_info をデフォルトで True にする"},{"content":"Python の logger モジュールでは、getLogger関数を使用してロガーを取得しますが、getLogger に未定義の名前を渡した場合、その名前のロガーが新しく作成されます。\n新しいロガーを作成したときは設定を行いたいことがあったので、その際の判定方法です。\nlogging.Logger.manager.loggerDict に含まれているかどうかで、存在するロガーか確認できます。\nimport logging logger_name = \u0026#34;kuma\u0026#34; if logger_name in logging.Logger.manager.loggerDict: # 既存のロガーを取得 logger = logging.getLogger(logger_name) else: # 新たなロガーを取得 logger = logging.getLogger(logger_name) # ここでロガーの設定などを行う Docker Desktop for Windows/Macでつくるクリーンな開発環境構築入門(Python版) (Amazon) ","date":"2023-11-25T18:11:15+09:00","image":"/images/2023/11/pylg00.jpg","permalink":"/posts/it/pc/5591/","title":"Python：指定した名前のロガーがあるか確認する"},{"content":"copy.deepcopy を使えば良い。\nimport copy import json value = { \u0026#34;name\u0026#34;: \u0026#34;Yamada\u0026#34;, \u0026#34;gender\u0026#34;: \u0026#34;male\u0026#34;, \u0026#34;address\u0026#34;: { \u0026#34;country\u0026#34;: \u0026#34;Japan\u0026#34;, \u0026#34;Prefecture\u0026#34;: \u0026#34;Saitama\u0026#34;, } } dup1 = copy.deepcopy(value) dup1[\u0026#34;name\u0026#34;] = \u0026#34;Suzuki\u0026#34; dup1[\u0026#34;address\u0026#34;][\u0026#34;Prefecture\u0026#34;] = \u0026#34;Tokyo\u0026#34; print(json.dumps(dup1, indent=2)) print(json.dumps(value, indent=2)) { \u0026#34;name\u0026#34;: \u0026#34;Suzuki\u0026#34;, \u0026#34;gender\u0026#34;: \u0026#34;male\u0026#34;, \u0026#34;address\u0026#34;: { \u0026#34;country\u0026#34;: \u0026#34;Japan\u0026#34;, \u0026#34;Prefecture\u0026#34;: \u0026#34;Tokyo\u0026#34; } } { \u0026#34;name\u0026#34;: \u0026#34;Yamada\u0026#34;, \u0026#34;gender\u0026#34;: \u0026#34;male\u0026#34;, \u0026#34;address\u0026#34;: { \u0026#34;country\u0026#34;: \u0026#34;Japan\u0026#34;, \u0026#34;Prefecture\u0026#34;: \u0026#34;Saitama\u0026#34; } } Docker Desktop for Windows/Macでつくるクリーンな開発環境構築入門(Python版) (Amazon) ","date":"2023-11-18T16:06:16+09:00","image":"/images/2023/11/pydp00.jpg","permalink":"/posts/it/pc/5582/","title":"Python：値をデープコピーしたい"},{"content":"用途によってはクラスを作るより contextmanager の方がスッキリ書けると知ったのでメモです。\n例えば、リソースを使う際に \u0026ldquo;開く/閉じる\u0026rdquo; や \u0026ldquo;接続/切断\u0026rdquo;、\u0026ldquo;確保/解放\u0026rdquo; などの処理を書くとき try/finally で囲みますが、そういったときに try/finally を contextmanager デコレータを付けた関数内で行い、使う側は with 文で書きます。\n試すにあたって Google Colab を使いました。\nそこにあった README.md を読み込んで表示してみました。\nファイルを例にサンプルを書きましたが、ファイルは次のように既に with 文で使えますので念のため。\nwith open(\u0026#39;./sample_data/README.md\u0026#39;, \u0026#39;r\u0026#39;) as f: print(f.read()) ということで、contextmanager を使って自作した場合は\nfrom contextlib import contextmanager @contextmanager def sample_read(filename): file = open(filename, \u0026#39;r\u0026#39;) try: yield file finally: file.close() with sample_read(\u0026#34;./sample_data/README.md\u0026#34;) as file: print(file.read()) contextmanager デコレータを付けた関数内で try/finally し、finally でファイルを閉じるようにしています。\nファイルのように with 文に対応しているものは良いですが、自前で解放が必要な場合は contextmanager でスッキリ書けそうです。\nDocker Desktop for Windows/Macでつくるクリーンな開発環境構築入門(Python版) (Amazon) ","date":"2023-11-11T17:52:56+09:00","image":"/images/2023/11/pyctmgr00.jpg","permalink":"/posts/it/pc/5569/","title":"Python：with 文と contextmanager で try/finally を除外する"},{"content":"ということで、付けると引数の値を出力するデコレータのコードを例として書き留めておきます。\nfrom functools import wraps def print_params(func): @wraps(func) def wrapper(*args, **kwargs): for v in args: print(v) for k, v in kwargs.items(): print(k,\u0026#39;=\u0026#39;,v) func(*args, **kwargs) return wrapper @print_params def hoge(a, b, c, d = \u0026#39;test\u0026#39;): print(\u0026#39;hoge hoge\u0026#39;) hoge(1, [2, 3], { \u0026#39;first name\u0026#39;: \u0026#39;Taroh\u0026#39;, \u0026#39;last name\u0026#39;: \u0026#39;Yamada\u0026#39; }, d = \u0026#39;abcd\u0026#39;) 結果\n1 [2, 3] {\u0026#39;first name\u0026#39;: \u0026#39;Taroh\u0026#39;, \u0026#39;last name\u0026#39;: \u0026#39;Yamada\u0026#39;} d = abcd hoge hoge functools.wraps は、対象の関数の __name__ や __doc__ が失われないように付けます。\nDocker Desktop for Windows/Macでつくるクリーンな開発環境構築入門(Python版) (Amazon) ","date":"2023-11-04T06:48:45+09:00","image":"/images/2023/11/pydecarg00.jpg","permalink":"/posts/it/pc/5559/","title":"Python：デコレータと *args, **kwargs の使い方メモ"},{"content":"いわゆる format ですが、何パターンか見かけたのでメモです。\n場面によりますが、個人的には最後の書き方をすることが最近は多いです。\nfirst_name = \u0026#39;Taroh\u0026#39; last_name = \u0026#39;Yamada\u0026#39; print(\u0026#34;name: {} {}\u0026#34;.format(first_name, last_name)) print(\u0026#34;name: {1} {0}\u0026#34;.format(first_name, last_name)) print(\u0026#34;name: %s %s\u0026#34; % (first_name, last_name)) print(f\u0026#34;name: {first_name} {last_name}\u0026#34;) name: Taroh Yamada name: Yamada Taroh name: Taroh Yamada name: Taroh Yamada Docker Desktop for Windows/Macでつくるクリーンな開発環境構築入門(Python版) (Amazon) ","date":"2023-10-28T12:09:05+09:00","image":"/images/2021/09/package.jpg","permalink":"/posts/it/pc/5554/","title":"Python：文字列に変数の内容を展開する"},{"content":"デバックで要求を console.log 出力させてみたときのメモです。\n個々の URL で処理するのでは無く、一箇所で対応します。\nimport express from \u0026#39;express\u0026#39;; const app = express(); app.use(express.json()); app.use((req, res, next) =\u0026gt; { console.log(req.method, req.url, req.query, req.body); next(); }) PUT http://localhost:8080/test/1024?a=鈴木\u0026amp;b=花子 content-type: application/json { \u0026#34;name\u0026#34;: \u0026#34;山田太郎\u0026#34;, \u0026#34;time\u0026#34;: \u0026#34;2023-08-05T00:00:00Z\u0026#34; } のような要求が来たら、コンソールには\nPUT /test/1024?a=%E9%88%B4%E6%9C%A8\u0026amp;b=%E8%8A%B1%E5%AD%90 { a: \u0026#39;鈴木\u0026#39;, b: \u0026#39;花子\u0026#39; } { name: \u0026#39;山田太郎\u0026#39;, time: \u0026#39;2023-08-05T00:00:00Z\u0026#39; } と出力されます。\n","date":"2023-10-21T20:02:56+09:00","image":"/images/2022/01/serial.jpg","permalink":"/posts/it/pc/5551/","title":"express：要求をコンソールに出力する"},{"content":"素の table タグに設定しているのと変わらないので、他にも流用できると思います。\nv-table は props に先頭行・末尾行を固定にする設定（fixed-header、fixed-footer）があるので、それで十分なら props で設定した方が簡単です。\n\u0026lt;template\u0026gt; \u0026lt;div\u0026gt; \u0026lt;v-table\u0026gt; \u0026lt;thead\u0026gt; \u0026lt;tr\u0026gt; \u0026lt;th v-for=\u0026#34;col in columns\u0026#34;\u0026gt;{{ col }}\u0026lt;/th\u0026gt; \u0026lt;/tr\u0026gt; \u0026lt;/thead\u0026gt; \u0026lt;tbody\u0026gt; \u0026lt;tr v-for=\u0026#34;row in cells\u0026#34;\u0026gt; \u0026lt;td v-for=\u0026#34;col in row\u0026#34;\u0026gt;{{ col }}\u0026lt;/td\u0026gt; \u0026lt;/tr\u0026gt; \u0026lt;/tbody\u0026gt; \u0026lt;/v-table\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script lang=\u0026#34;ts\u0026#34;\u0026gt; export default { data() { const columns = Array .from({ length: 100 }) .map((_, i) =\u0026gt; i); const cells = Array .from({ length: 100 }) .map((_, i) =\u0026gt; Array .from({ length: 100 }) .map((_, i) =\u0026gt; i) ); return ({ columns, cells, }); }, } \u0026lt;/script\u0026gt; \u0026lt;style scoped\u0026gt; div { width: 100vw; height: 100vh; } thead { z-index: 2 !important; position: sticky; top: 0; } th { width: 40px; background-color: white; } th:nth-child(1) { position: sticky; left: 0; z-index: 3 !important; } td { position: sticky; left: 0; width: 40px; background-color: white; } td:nth-child(1) { z-index: 1; } \u0026lt;/style\u0026gt; ","date":"2023-10-14T10:16:25+09:00","image":"/images/2023/10/tblscr00.jpg","permalink":"/posts/it/pc/5544/","title":"Vuetify 3：v-table で先頭行と先頭列を固定してスクロール"},{"content":"Vuetify 2 は props にありましたが、3 には無いので css で変更します。\n対象のコンポーネントでのみ透明度を変更する場合の例です。\n\u0026lt;template\u0026gt; \u0026lt;slot /\u0026gt; \u0026lt;v-overlay class=\u0026#34;hoge\u0026#34; \u0026gt; \u0026lt;v-btn\u0026gt;test\u0026lt;/v-btn\u0026gt; \u0026lt;/overlay\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;style scoped\u0026gt; .hoge :deep(.v-overlay__scrim) { opacity: 0; } \u0026lt;/style\u0026gt; コンポーネント内のクラスである hoge 配下にある v-overlay__scrim クラスの透明度を 0 に変更することで、透明なオーバーレイになります。\n他の所の v-overlay は透明度 0.32（デフォルト）で使えます。\n","date":"2023-10-07T10:43:00+09:00","image":"/images/2023/10/vovla00.jpg","permalink":"/posts/it/pc/5529/","title":"Vuetify 3：v-overlay の透明度を変更するには"},{"content":"今回もインストールできるUSBも作成しました。\n以下の画面が表示されずにアップデートが済んでしまったので、再度ダウロードしました。\nUSBメモリをマウントして、ターミナルから次のコマンドを実行すると、インストーラのUSB起動ディスクが作成できます。\n自分は \u0026ldquo;usb\u0026rdquo; というボリューム名で USB メモリを用意しました。\nsudo /Applications/Install\\ macOS\\ Sonoma.app/Contents/Resources/createinstallmedia --volume /Volumes/usb アクセスの許可が求められたので OK します。\nUSB には 15GB ぐらい容量が必要の様です。\nバッファロー SSD 外付け 1.0TB USB3.2 (Amazon) Mac mini (Amazon) MacBook Pro 13インチ (Amazon) MacBook Air (Amazon) Mac Fan 2026年5月号 (Amazon)\n","date":"2023-09-30T08:03:00+09:00","image":"/images/2022/10/usbinst00.jpg","permalink":"/posts/it/pc/5514/","title":"USB メモリに macOS Sonoma のインストーラを作成する"},{"content":"GUIから実行できるので、コマンド入力が減りました。\n使用頻度の高いもののメモです。\n環境 VSCode v1.82.2 Docker 機能拡張 v1.26.1 Docker イメージのビルド Docker ファイルの右クリックメニューから「Build Image\u0026hellip;」を選択し、イメージ名を入力します。\n次回からは入力したイメージ名がデフォルトで入っています。\ndocker compose の操作 docker-compose.yaml ファイルの右クリックメニューから「Compose Up」で起動できます。\n起動したコンテナの操作 構成するコンテナ群の右クリックメニューからは\nCompose Logs Compose Restart Compose Down が便利です。\nコンテナの右クリックメニューからは\nView Logs Attach Shell Restart が便利です。\n「Attach Shell」は結構助かってます。\nVisual Studio Code実践ガイド (Amazon) Visual Studio Codeデバッグ技術 (Amazon) Docker Desktop for Windows/Mac と VSCode でつくるクリーンな開発環境構築入門 (Amazon)\n","date":"2023-09-23T12:10:37+09:00","image":"/images/2023/09/dke00.jpg","permalink":"/posts/it/pc/5494/","title":"VSCode：Docker 機能拡張でコマンド入力する機会が減った"},{"content":"以前に自前で算出しましたが、便利なライブラリがあったのでメモです。\n日付に関する多くの機能があるので、自作する機会は無くなるのかもしれません。\n日数の加算・減算 import { add, sub } from \u0026#39;date-fns\u0026#39;; const currentDate = new Date(); // 明日 const tomorrow = add(currentDate, { days: 1 }); console.log(tomorrow); // 昨日 const yesterday = sub(currentDate, { days: 1 }); console.log(yesterday); // 来週 const nextWeek = add(currentDate, { weeks: 1 }); console.log(nextWeek); // 先週 const lastWeek = sub(currentDate, { weeks: 1 }); console.log(lastWeek); その他 使用率が高そうな関数のメモ。\n日付のフォーマット import { format } from \u0026#39;date-fns\u0026#39;; const formattedDate = format(currentDate, \u0026#39;yyyy/MM/dd\u0026#39;); // \u0026#34;2023/09/16\u0026#34; 2つの日付の日数差 import { differenceInDays } from \u0026#39;date-fns\u0026#39;; console.log(differenceInDays(tomorrow, lastWeek)); // 8 月の終わり import { endOfMonth } from \u0026#39;date-fns\u0026#39;; console.log(endOfMonth(currentDate)); // 2023-09-30T14:59:59.999Z // currentDate は 2023-09-16 // 時刻は日本時間23:59:59が返ってきた ","date":"2023-09-16T22:42:55+09:00","image":"/images/2022/04/ggcalen.jpg","permalink":"/posts/it/pc/5482/","title":"JavaScript：日付に加算・減算して未来や過去の日付にする【date-fns ライブラリ】"},{"content":"create-next-app@13.4.19 で初期コードを生成後、実現したいフックを書いてみました。\n保存のショートカットキーで、指定した処理を実行するフック Mac なら Command + S、Mac でなければ CTRL + S で指定した処理を実行します。\n\u0026#34;use client\u0026#34; import { useEffect } from \u0026#39;react\u0026#39;; export default function useSaveKey(saveFunc: ()=\u0026gt; void) { useEffect(() =\u0026gt; { function onKeyDown(e: KeyboardEvent) { const isMac = window.navigator.userAgent.indexOf(\u0026#34;Mac\u0026#34;) !== -1; const isCtrl = isMac ? e.metaKey : e.ctrlKey; if (e.key === \u0026#39;s\u0026#39; \u0026amp;\u0026amp; isCtrl) { e.preventDefault(); saveFunc(); } } document.addEventListener(\u0026#39;keydown\u0026#39;, onKeyDown); return () =\u0026gt; document.removeEventListener(\u0026#34;keydown\u0026#34;, onKeyDown) }, [saveFunc]) } 使用例 初期で生成された page.tsx に追加してみた。\n\u0026#34;use client\u0026#34; import Image from \u0026#39;next/image\u0026#39; import useSaveKey from \u0026#39;./useSaveKey\u0026#39;; export default function Home() { useSaveKey(() =\u0026gt; console.log(\u0026#39;save.\u0026#39;)); return ( (以下略) アップデートに期待するライブラリ useKeyPress というフックがあったので試しましたが、動作しませんでした。\n動作環境によって Command か CTRL かを変えられるかも不明ですが。\n","date":"2023-09-09T18:56:24+09:00","image":"/images/2023/09/kbs00.jpg","permalink":"/posts/it/pc/5463/","title":"React：CTRL + S や Command + S で保存処理を行うには"},{"content":"ある日付の数日前、数日後といった日付を得たいことがあったので、そのメモです。\n※ 便利な date-fns ライブラリがあったので、そちらについては別で投稿しました。\n日数の加算・減算 getDate の結果に加算・減算して、setDate します。\n// 明日 var currentDate = new Date(); currentDate.setDate(currentDate.getDate() + 1); console.log(currentDate); // 昨日 var currentDate = new Date(); currentDate.setDate(currentDate.getDate() - 1); console.log(currentDate); 月数の加算・減算 getMonth の結果に加算・減算して、setMonth します。\n// 来月 var currentDate = new Date(); currentDate.setMonth(currentDate.getMonth() + 1); console.log(currentDate); // 先月 var currentDate = new Date(); currentDate.setMonth(currentDate.getMonth() - 1); console.log(currentDate); 月末の日付を使う場合は、用途に合っているか注意した方が良さそうです。\nvar currentDate = new Date(\u0026#39;2023-01-31\u0026#39;); currentDate.setMonth(currentDate.getMonth() + 1); console.log(currentDate); // 結果： Fri Mar 03 2023 09:00:00 GMT+0900 (日本標準時) 月末の日付を得るなら、月初め ー 1日 で計算するなどでしょうか。\n","date":"2023-09-02T14:58:04+09:00","image":"/images/2022/04/ggcalen.jpg","permalink":"/posts/it/pc/5454/","title":"JavaScript：日付に加算・減算して未来や過去の日付にする"},{"content":"以前紹介した Chrome 機能拡張の Safari 版が出たのでインストールしてみました。\nY!News Excluder for Safari macOS と iOS のどちらも App Store からインストールできます。\nmacOS 用 インストール後、設定画面を開くように促されたので従いました。\n「Webサイトを編集\u0026hellip;」を選びます。\nYahooのサイトを「許可」にします。\nSafari を開き、追加された機能拡張のアイコンをタップし、除外するワードを追加します。\niOS 用 インストール後、アイコンをタップします。\n「設定画面」＞「Safari」＞「機能拡張」を開きます。\n下記の「設定画面へ」をタップして開く画面とは別なのが、ちょっと紛らわしいですね。。\n機能拡張をオンにし、サイトを許可にします。\nSafari で Yahoo! サイトを開き、URL 欄の機能拡張ボタンをタップし、「Y!News Excluder」をタップします。\n除外するワードを追加します。\nChrome版で寄付していたら、プロモーションコードを頂いた Safari版の拡張機能配布には、費用が掛かるので有料になったようです。\nChrome版の機能拡張は無料ですが、自分は寄付していました。\nそうした所、プロモーションコードを作者の方からメール頂きました。\nプロモーションコードは、ギフトカードのコードを入力を使う方法と同じです。\nApp Store からアプリをインストールする手順の途中で入力する訳では無いので、分かりづらい気がします。\nプロモーションコードを入れると、アプリがダウンロードされました。\n","date":"2023-08-26T11:18:16+09:00","image":"/images/2022/11/yne00.jpg","permalink":"/posts/it/pc/5420/","title":"Safari：Yahoo! News で見たくない記事を除外する"},{"content":"拡張機能の Markdown PDF でできます。\n日本語の説明も用意されています。\nVSCode で Markdown ファイルを開いて、右クリックメニューの「Markdown PDF: Export (pdf)」を選択すれば OK です。\nVisual Studio Code実践ガイド (Amazon) Visual Studio Codeデバッグ技術 (Amazon) Docker Desktop for Windows/Mac と VSCode でつくるクリーンな開発環境構築入門 (Amazon)\n","date":"2023-08-19T14:19:56+09:00","image":"/images/2022/07/gsinf00.jpg","permalink":"/posts/it/pc/5413/","title":"VSCode：Markdown ファイルを PDF にする"},{"content":"Apple Watch のバンドをレザーバンドに変えました。\n土屋鞄製造所のレザーウォッチバンド 画像：土屋鞄製造所のサイトから\n購入したレザーウォッチバンドAppleWatch用（オイルヌメ革）は、職人さんによる手作りで販売本数に限りがあるそうで、商品を知って見た時には在庫切れでした。\n特に急ぎでもないので「入庫お知らせ登録」をして気長にメールを待ちました。\n購入するかは別にして、気になった場合は登録だけでもしておくと良いかもしれません。\nしばらく経って再販の連絡をメールを受け取り、日付と時間をスケジュールにメモ。\nほぼ販売開始時刻に注文してゲットできました。\n商品の見た目や質感は、販売ページの写真から感じる通りで非常に満足しています。\n時計としても使いますが活動量や睡眠の計測もしたいので、バンド交換後も充電時以外はほぼ付けて過ごしています。\nしいて要望を挙げるなら、少し長さに余裕が欲しいぐらいでしょうか。\n40mm の Watch 用のためか、自分の場合は２個目の穴が丁度良いのであまり余裕がありません。\nこのサイズ向けは、女性を想定しているんですかね。\n前のバンドはスポーツループでした 長らく使っていたスポーツループは、だいぶ汚れて洗ってもあまり綺麗になりません。\nで、次もスポーツループが良いと思っていて購入済みでした。\n付け心地も良く装着も楽だし、ベストな長さに調節できるし。\nしかしレザーバンドにしたので保管ですね。\nバンドを交換しながら使うのは、面倒なのでしないと思いますが、いつスポーツループに戻っても問題ないです。レザーバンドも大切に使いますが。\n","date":"2023-08-12T10:43:50+09:00","image":"/images/2023/07/aw000.jpg","permalink":"/posts/it/smartphone/5385/","title":"Apple Watch：土屋鞄製造所のレザーウォッチバンドに変えてみた"},{"content":"受け取る際のメモです。\nボディで渡すパラメータは JSON 形式を想定します。\n環境 express：4.18.2\ntypescript：5.1.6\n例 express 側のコード\nimport express from \u0026#39;express\u0026#39;; const app = express(); app.use(express.json()); app.put(\u0026#39;/test/:id\u0026#39;, async (req, res) =\u0026gt; { const { a, b, } = req.query; const { name, time, } = req.body; res.send({ id: req.params.id, name, time, a, b, }); }); app.listen(8080, () =\u0026gt; { console.log(\u0026#39;listening on port 8080\u0026#39;); }); 呼ぶ側で VSCode の機能拡張 REST Client を使った場合\nPUT http://localhost:8080/test/1024?a=鈴木\u0026amp;b=花子 content-type: application/json { \u0026#34;name\u0026#34;: \u0026#34;山田太郎\u0026#34;, \u0026#34;time\u0026#34;: \u0026#34;2023-08-05T00:00:00Z\u0026#34; } 実行結果\nHTTP/1.1 200 OK X-Powered-By: Express Content-Type: application/json; charset=utf-8 Content-Length: 91 ETag: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx Date: Fri, 05 Aug 2023 00:00:00 GMT Connection: close { \u0026#34;id\u0026#34;: \u0026#34;1024\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;山田太郎\u0026#34;, \u0026#34;time\u0026#34;: \u0026#34;2023-08-05T00:00:00Z\u0026#34;, \u0026#34;a\u0026#34;: \u0026#34;鈴木\u0026#34;, \u0026#34;b\u0026#34;: \u0026#34;花子\u0026#34; } ","date":"2023-08-05T08:06:00+09:00","image":"/images/2022/01/serial.jpg","permalink":"/posts/it/pc/5379/","title":"express：クエリ、パス、ボディでデータを受け取る"},{"content":"GUI 操作便利なんですが、一般ユーザーに許しているデータベースだけ表示したかったのでメモです。\nMongoDB も mongo-express も docker イメージを使います。\nユーザーの作成 一般ユーザーを作成します。\nuse test db.createUser( { user: \u0026#34;john\u0026#34;, pwd: \u0026#34;wayne\u0026#34;, roles: [ { role: \u0026#34;readWrite\u0026#34;, db: \u0026#34;kuma\u0026#34; }, { role: \u0026#34;readWrite\u0026#34;, db: \u0026#34;emon\u0026#34; } ] } ) docker-compose.yaml version: \u0026#39;3.1\u0026#39; services: mongo: image: mongo restart: always environment: MONGO_INITDB_ROOT_USERNAME: root MONGO_INITDB_ROOT_PASSWORD: example volumes: - ./db:/data/db mongo-express: image: mongo-express restart: always ports: - 8081:8081 environment: # ME_CONFIG_MONGODB_ADMINUSERNAME: root # ME_CONFIG_MONGODB_ADMINPASSWORD: example ME_CONFIG_MONGODB_AUTH_DATABASE: test ME_CONFIG_MONGODB_AUTH_USERNAME: john ME_CONFIG_MONGODB_AUTH_PASSWORD: wayne ME_CONFIG_MONGODB_SERVER: mongo mongo-express 許可したデータベースだけ表示されています。\nMongoDB 解体新書 (Amazon) ","date":"2023-07-29T20:26:14+09:00","image":"/images/2023/07/mngex00.jpg","permalink":"/posts/it/pc/5363/","title":"mongo-express：表示するデータベースをユーザーに許しているものに限定する"},{"content":"作成したオブジェクトの後処理を手軽かつ確実にするには with 文が便利です。\nそのクラスの作成と、使用する際のメモです。\nwith 文で使うクラスの作成 class prc: param = \u0026#34;unknown\u0026#34; def __init__(self, *args, **kwargs): self.param = kwargs.get(\u0026#34;param\u0026#34;) print(f\u0026#34;prc init {self.param}\u0026#34;) def __enter__(self): print(f\u0026#34;prc enter {self.param}\u0026#34;) return self def __exit__(self, exception_type, exception_value, traceback): print(f\u0026#34;prc exit {self.param}\u0026#34;) def something(self): print(f\u0026#34;prc something {self.param}\u0026#34;) 関数でオブジェクトを生成したい コンストラクタのパラメータを決まった値にしたいので、関数でオブジェクトを返して使うとき。\ndef get_prc(): return prc(param=\u0026#34;A\u0026#34;) print(\u0026#34;---1---\u0026#34;) with get_prc() as t: print(\u0026#34;---2---\u0026#34;) t.something() print(\u0026#34;---3---\u0026#34;) ---1--- prc init A prc enter A ---2--- prc something A ---3--- prc exit A 関数をクラスのメソッドにしたい スタティックメソッドにしたかったので @staticmethod を付けています。\nclass wrapB: @staticmethod def get_prc(): return prc(param=\u0026#34;B\u0026#34;) print(\u0026#34;---1---\u0026#34;) with wrapB.get_prc() as t: print(\u0026#34;---2---\u0026#34;) t.something() print(\u0026#34;---3---\u0026#34;) ---1--- prc init B prc enter B ---2--- prc something B ---3--- prc exit B 別クラスでラップしたい class wrapC: instance = None def __init__(self): print(\u0026#34;wrap init\u0026#34;) self.instance = prc(param=\u0026#34;C\u0026#34;) def __enter__(self): print(\u0026#34;wrap enter\u0026#34;) self.instance.__enter__() return self def __exit__(self, exception_type, exception_value, traceback): print(\u0026#34;wrap exit\u0026#34;) return self.instance.__exit__(exception_type, exception_value, traceback) def something(self): print(\u0026#34;wrap something\u0026#34;) self.instance.something() print(\u0026#34;---1---\u0026#34;) with wrapC() as t: print(\u0026#34;---2---\u0026#34;) t.something() print(\u0026#34;---3---\u0026#34;) ---1--- wrap init prc init C wrap enter prc enter C ---2--- wrap something prc something C ---3--- wrap exit prc exit C ","date":"2023-07-22T11:03:09+09:00","image":"/images/2023/07/pywi00.jpg","permalink":"/posts/it/pc/5352/","title":"Python：with 文で使うクラスを作る"},{"content":"iPhone の時計アプリでアラームを設定しておく。なのですが、以下の様にしたかった際のメモです。\n指定時刻になったら Apple Watch がバイブレーションで通知して欲しい。 iPhone は音やバイブレーションしないで欲しい。 iOS は 16.5.1(c)、watchOS は 9.5.2 で試しました。\niPhone の時計アプリでアラームの編集で、「サウンド」で「バイブレーション」を「なし」、「着信音」を「なし」にすれば OK です。\nただし、iPhone を使っているとそちらに通知されて Apple Watch は反応しないので、iPhone はロック状態にしておきます。\nApple Watch SE(第2世代) GPSモデル (Amazon) ","date":"2023-07-15T17:05:29+09:00","image":"/images/2023/07/aw000.jpg","permalink":"/posts/it/smartphone/5342/","title":"Apple Watch を指定時刻にバイブレーションさせる"},{"content":"ウォーキングを含め、運動の開始や停止で確認の通知が来るのがうっとおしいので止めました。\n自動で検出した運動を記録していてくれれば十分です。検出が間違っていても特に気にならないんで。\niPhone で Watch アプリを開く マイウォッチの「アクティビティ」 「通知オフ」 「スタンドリマインダー」オフ Apple Watch SE(第2世代) GPSモデル (Amazon) ","date":"2023-07-08T18:21:35+09:00","image":"/images/2023/07/aw000.jpg","permalink":"/posts/it/smartphone/5331/","title":"Apple Watch で運動系の通知や確認を止める"},{"content":"そのまま出力すると、以下のように長くて見づらいです。\ndic = { \u0026#34;a\u0026#34;: 1, \u0026#34;b\u0026#34;: { \u0026#34;c\u0026#34;: 2, \u0026#34;d\u0026#34;: { \u0026#34;e\u0026#34;: 3}}, \u0026#34;f\u0026#34;: 4 } print(f\u0026#34;dic = {dic}\u0026#34;) dic = {\u0026#39;a\u0026#39;: 1, \u0026#39;b\u0026#39;: {\u0026#39;c\u0026#39;: 2, \u0026#39;d\u0026#39;: {\u0026#39;e\u0026#39;: 3}}, \u0026#39;f\u0026#39;: 4} json.dumps 関数で indent を指定して整形すると見やすく出力できます。\nimport json print(f\u0026#34;dic = {json.dumps(dic, indent=2)}\u0026#34;) dic = { \u0026#34;a\u0026#34;: 1, \u0026#34;b\u0026#34;: { \u0026#34;c\u0026#34;: 2, \u0026#34;d\u0026#34;: { \u0026#34;e\u0026#34;: 3 } }, \u0026#34;f\u0026#34;: 4 } ","date":"2023-07-01T09:48:00+09:00","image":"/images/2022/11/yne00.jpg","permalink":"/posts/it/pc/5324/","title":"Python : ディクショナリ変数を JSON 形式で整形して出力する"},{"content":"公開鍵の形式が RSA か ECC か、ビット数はいくつかを確認するメモです。\ncryptography ライブラリを使用します。\npip install cryptography from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.asymmetric import rsa, ec from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePublicKey # RSA公開鍵 def get_rsa_public_key(): private_key = rsa.generate_private_key( public_exponent=65537, key_size=2048, backend=default_backend() ) public_key = private_key.public_key() return public_key # ECC公開鍵 def get_ecc_public_key(): private_key = ec.generate_private_key(ec.SECP256R1()) public_key = private_key.public_key() return public_key # 公開鍵の確認 def show_key_info(public_key): key_type = \u0026#34;形式不明な公開鍵\u0026#34; if isinstance(public_key, RSAPublicKey): key_type = \u0026#34;RSA公開鍵\u0026#34; elif isinstance(public_key, EllipticCurvePublicKey): key_type = \u0026#34;ECC公開鍵\u0026#34; print(f\u0026#34;{key_type} {public_key.key_size}bit\u0026#34;) show_key_info(get_rsa_public_key()) show_key_info(get_ecc_public_key()) 実行結果\nRSA公開鍵 2048bit ECC公開鍵 256bit ","date":"2023-06-24T08:08:00+09:00","image":"/images/2022/01/crypt.jpg","permalink":"/posts/it/pc/5315/","title":"Python : 公開鍵の形式とビット数を得る"},{"content":"カーナビタイムは交通情報やナビゲーション機能を提供するアプリで、ルート案内、最適な交通手段、渋滞情報などを提供してくれるので便利です。\nただ、デフォルトだと\n音声案内の音量が大きいと感じる 曲を再生してた場合、音声案内の際に曲の音量が下がる\n→ 案内を優先して聞きたいときは助かるんですが、そうでない時もあるんですよね。 設定の変更で対応できるようなので変えてみます。\n確認したカーナビタイムのバージョンは 4.11.7 です。\n音声案内の音量などを設定する 「MENU」→「設定/ヘルプ」→「設定」→「ナビゲーション」を開く\n発話音量\n音声案内の音量を変更できるので、少し下げます。 オーディオダッキング\nチェックを外して、再生している曲の音量が変わらないようにします。 ","date":"2023-06-17T19:23:44+09:00","image":"/images/2021/05/navi.jpg","permalink":"/posts/it/smartphone/5303/","title":"カーナビタイムの音声案内の音量などを変える"},{"content":"単純にテキストファイルを比較するなら diff コマンドですが、行頭の数桁を比較対象から除外するには、cut コマンドで除外してから比較します。\n行頭 10 桁を比較対象外にしたいときは以下の通り。\ncut -c 11- file1.txt | diff - \u0026lt;(cut -c 11- file2.txt) シェルスクリプト基本リファレンス (Amazon) ","date":"2023-06-10T04:19:00+09:00","image":"/images/2023/06/tfdf00.jpg","permalink":"/posts/it/pc/5294/","title":"シェルスクリプト：テキストファイルの行頭数桁を除いて比較する"},{"content":"bash を想定しています。\nファイルのリストアップ ls コマンドでフルパスを得る方法が無いようなので、find コマンドを使います。\nfind /home/ec2-user -type f 配列に格納 ループで処理する為に、ファイルのリストを mapfile コマンドで配列に格納します。\nmapfile -t files_array \u0026lt; \u0026lt;(find /home/ec2-user -type f) ループで処理する \u0026ldquo;${files_array[@]}\u0026rdquo; で配列の要素を展開してループします。\n処理はファイルの先頭5行を出力するものにしてみました。\nfor file in \u0026#34;${files_array[@]}\u0026#34;; do head -t 5 \u0026#34;$file\u0026#34; done 完成したスクリプト #!/bin/bash # findコマンドの結果を配列に格納する mapfile -t files_array \u0026lt; \u0026lt;(find /home/ec2-user -type f) # ファイルの先頭5行を表示する for file in \u0026#34;${files_array[@]}\u0026#34;; do head -t 5 \u0026#34;$file\u0026#34; done シェルスクリプト基本リファレンス (Amazon) ","date":"2023-06-03T00:03:00+09:00","image":"/images/2021/10/log.jpg","permalink":"/posts/it/pc/5279/","title":"シェルスクリプト：ファイルのリストを得てループで処理する"},{"content":"確認する方法を探した所、Dividend Investor で調べることができました。\n例えばコカ・コーラなら、stock symbol を KO で検索します。\n60年ですね。\n一括で調べるソースコードの作成 １つずつ手動で確認するのは辛いのでコードを書いてみました。\n相手のサイトを高負荷にしない為に間を置きながら取得します。\n使用パッケージ typescript 5.0.4\nts-node 10.9.1\naxios 1.4.0\nソースコード import axios from \u0026#39;axios\u0026#39;; // 調べたい銘柄 const symbols = [ \u0026#39;AWR\u0026#39;, \u0026#39;DOV\u0026#39;, \u0026#39;GPC\u0026#39;, \u0026#39;NWN\u0026#39;, \u0026#39;PG\u0026#39;, \u0026#39;AAPL\u0026#39;, \u0026#39;MSFT\u0026#39;, \u0026#39;GOOGL\u0026#39;, ]; const main = async(symbol: string) =\u0026gt; { const res = await axios.get(`https://www.dividendinvestor.com/ajax/?action=quote_ajax\u0026amp;symbol=${symbol}`) .then(value =\u0026gt; value); const html = res.data.html; const start = \u0026#39;Consecutive Dividend Increases\u0026lt;/a\u0026gt;\u0026lt;/span\u0026gt;\u0026lt;span class=\u0026#34;data\u0026#34;\u0026gt;\u0026#39;; const from = html.indexOf(start) + start.length; const to = html.indexOf(\u0026#39;\u0026lt;/span\u0026gt;\u0026#39;, from); const y = html.slice(from, to); console.log(`${symbol} : ${y}`); } let i = 0; const intervalID = setInterval(() =\u0026gt; { main(symbols[i]); i += 1; if (i \u0026gt;= symbols.length) { clearInterval(intervalID); } }, 5000); コード簡略化の為に結果を console.log で表示しましたが、実際はファイルやDBに書き出して使い回し、頻繁に取得するのは避けた方が良いでしょう。\n私の場合は console.log の出力をテキストファイルにコピペ後、加工してから使いまわそうと思っています。\n※ サイトが新しくなるなど、変更が行われた場合は結果が得られないと思います。\n実行 $ ts-node src/index AWR : 69 Years DOV : 67 Years GPC : 66 Years NWN : 67 Years PG : 69 Years AAPL : 10 Years MSFT : 17 Years GOOGL : 0 Years ちなみに、日本個別銘柄の連続増配を調べられるサイトは見つけられませんでした。残念。\n無いんですかね。もしくは有料ならあったりするんでしょうか。\nPythonでできる！ 株価データ分析 (Amazon) Docker Desktop for Windows/Macでつくるクリーンな開発環境構築入門(Python版) (Amazon) ファイナンス機械学習 (Amazon) アセットマネージャーのためのファイナンス機械学習 (Amazon)\n","date":"2023-05-27T07:31:21+09:00","image":"/images/2021/08/stock.jpg","permalink":"/posts/it/pc/5223/","title":"TypeScript：アメリカ個別銘柄の連続増配を調べる"},{"content":"スタートメニューの「設定」から「アプリ」-「スタートアップ」を開くと、開始するアプリのオン・オフを切り替えられます。\nWindows 11 Pro 22H2 で試しました。\n","date":"2023-05-20T09:49:52+09:00","image":"/images/2023/05/stup00.jpg","permalink":"/posts/it/pc/5215/","title":"Windows：ログインしたときに開始するアプリを減らす"},{"content":"誤ってキーを押したときも電源が入ってしまうので無効にしました。\n環境 Windows 11 Pro 22H2 キーボードは PS/2接続 設定方法 ・スタートメニューの右クリックメニューから「デバイス マネージャー」を選択\n・「キーボード」-「標準 PS/2 キーボード」の右クリックメニューから「プロパティ」を選択\n・「電源の管理」タブの「このデバイスで、コンピューターのスタンバイ状態を解除できるようにする」のチェックを外す\nBIOS の設定 BIOS の設定項目に、この手の設定があったので無効にしましたが、これだけだと電源 ON が効くので意味不明でした。BIOS をアップデートしても駄目だったし。\nWindows の設定も必要なんですね。というか、もしかして BIOS の設定は不要？\nPS/2接続のキーボードはこうなんでしょうか？\n","date":"2023-05-13T09:47:51+09:00","image":"/images/2023/05/kbp00.jpg","permalink":"/posts/it/pc/5197/","title":"Windows：キーボードによる電源ONを無効にする"},{"content":"試作のワークスペースを作る度に信頼するか聞かれるのが厄介なので、信頼するフォルダーを決め、そのサブフォルダーに試作物を作成することで聞かれてこないようにしました。\nVSCode はバージョン 1.78.0 です。\nワークスペースの信頼の設定 コマンドパレット（Windows なら Shift + Ctrl + P、Mac なら Shift + Command + P）から「Workspace: Manage Workspace Trust」を選択します。\n「フォルダーの追加」で、信頼するフォルダーを追加します。\n追加したフォルダーのサブフォルダーは信頼されます。\nVisual Studio Code実践ガイド (Amazon) Visual Studio Codeデバッグ技術 (Amazon) Docker Desktop for Windows/Mac と VSCode でつくるクリーンな開発環境構築入門 (Amazon)\n","date":"2023-05-06T11:43:11+09:00","image":"/images/2021/06/sql.jpg","permalink":"/posts/it/pc/5168/","title":"VSCode：ワークスペースの信頼を毎回せずに済ませるように設定する"},{"content":"次の様なスタートメニューにしています。ピン留めするアプリを厳選できてませんが。\n「おすすめ」自体も消したいんですができるのか不明でした。。\nWindows 11 Pro 22H2 で、以下の設定をしています。\n設定から、「個人用設定」→「スタート」\n「フォルダー」で以下をオン\nDOS/V POWER REPORT 2024年冬号 (Amazon) ","date":"2023-04-29T20:21:11+09:00","image":"/images/2022/10/wipb00.jpg","permalink":"/posts/it/pc/5153/","title":"Windows：スタートメニューに最近使ったアプリやファイルを表示しない"},{"content":"エクスプローラーから対象のファイルを右クリックして、メニューを選択すれば OK です。\n確認した VSCode のバージョンは 1.77.3 です。\nまず、比較元のファイルを右クリックして「比較対象の選択」をクリック\n次に比較先のファイルを右クリックして「選択項目と比較」をクリック\nで、差分が確認できます。\nVisual Studio Code実践ガイド (Amazon) Visual Studio Codeデバッグ技術 (Amazon) Docker Desktop for Windows/Mac と VSCode でつくるクリーンな開発環境構築入門 (Amazon)\n","date":"2023-04-22T08:05:00+09:00","image":"/images/2022/03/javascript.jpg","permalink":"/posts/it/pc/5141/","title":"VSCode：ファイルを比較して差分を確認するには"},{"content":"設定を度々見失うので、そのメモです。\nメニューの「並び替え」-「グループ化」-「(なし)」\nを選択すれば OK です。\n","date":"2023-04-15T17:27:33+09:00","image":"/images/2022/04/ggcalen.jpg","permalink":"/posts/it/pc/5135/","title":"Windows：ダウンロードフォルダーの表示を期間で分けない"},{"content":"サイトの通知を許可するか初回に確認してくれるので、「ブロック」なり「許可する」を選択しています。\n「許可する」にしたものの、後になって止めたいときのメモです。\n設定方法 メニューから、\n設定 → プライバシーとセキュリティ → サイトの設定 → 通知\nを選ぶ。 「通知の送信を許可するサイト」にある止めたいサイトを選ぶ 「通知」の設定を「許可する」→「ブロック」に変更する ","date":"2023-04-08T18:45:47+09:00","image":"/images/2023/04/siteallow00.jpg","permalink":"/posts/it/pc/5126/","title":"Google Chrome：サイトからの通知を止める"},{"content":"本サイトのソースコードなどのシンタックスハイライトは Prism.js を使用しています。\n言語とプラグインを選択してダウンロードしているのですが、ダウンロードしたファイルの先頭に、コメントで URL が書かれており、その URL を開けば同様の選択されて便利なので、そのメモです。\nhttps://prismjs.com/download.html#themes=prism\u0026amp;languages=markup+css+clike+javascript+bash+batch+csharp+docker+git+java+json+powershell+python+r+jsx+tsx+scss+sql+typescript+typoscript\u0026amp;plugins=line-highlight+line-numbers\n","date":"2023-04-01T17:30:37+09:00","image":"/images/2023/04/prismcust0.jpg","permalink":"/posts/it/pc/5108/","title":"Prism.js で選択した言語とプラグイン"},{"content":"ワークスペース内のソースコードにまとめて \u0026ldquo;ドキュメントのフォーマット\u0026rdquo; かける方法を探した所、以下の拡張機能が良さそうだったのでメモです。\nFormat Files Format in context menus そして、今の所 \u0026ldquo;Format Files\u0026rdquo; を使うことにしました。\nFormat Files 拡張機能 予め、設定 \u0026gt; 拡張機能 \u0026gt; Format Files で\nExcluded Folders に除外するディレクトリ、\nExtensions To Include に対象にするファイルの拡張子、\nで対象にするファイルを設定しておきます。\nあとはコマンドパレットから、\u0026ldquo;Start Format Files: Workspace\u0026rdquo; -\u0026gt; \u0026ldquo;Do it!\u0026rdquo; で実行されます。\n各ファイルを、開く、フォーマット、保存、するので数が多いと時間は掛かりますね。\nFormat in context menus 拡張機能 エクスプローラーからフォーマットを掛けたいファイルを選択後、右クリックのメニューから \u0026ldquo;Format\u0026rdquo; を選ぶと実行されます。\n以上、２つを試してみました。\nVisual Studio Code実践ガイド (Amazon) Visual Studio Codeデバッグ技術 (Amazon) Docker Desktop for Windows/Mac と VSCode でつくるクリーンな開発環境構築入門 (Amazon)\n","date":"2023-03-25T07:35:03+09:00","image":"/images/2022/03/javascript.jpg","permalink":"/posts/it/pc/5075/","title":"VSCode：\"ドキュメントのフォーマット\" を一括で実行するには"},{"content":"以前の試作では、表示が即切り替わりました。\nCytoscape.js に animate があるので、じんわり更新する試作をしてみました。\nimport React, { FC, useRef, useEffect } from \u0026#39;react\u0026#39;; import cytoscape, { Core, ElementDefinition, Stylesheet, } from \u0026#39;cytoscape\u0026#39;; const Child: FC\u0026lt;{ Elements?: ElementDefinition[], Styles?: Stylesheet[], }\u0026gt; = ({ Elements = undefined, Styles = undefined, }) =\u0026gt; { const refEl = useRef(null); const refCy = useRef\u0026lt;Core | null\u0026gt;(null); useEffect(() =\u0026gt; { const container = refEl.current! as HTMLDivElement; refCy.current = cytoscape({ container: container, elements: Elements, style: Styles, }); const cy = refCy.current; return (() =\u0026gt; { cy.destroy(); }) }, []); useEffect(() =\u0026gt; { const cy = refCy.current; cy?.json({ elements: Elements, styles: Styles }); cy?.nodes(\u0026#39;[type]\u0026#39;).style({ opacity: 0.2 }); cy?.nodes(\u0026#39;[type]\u0026#39;).animate({ style: { opacity: 1 }, duration: 1000 }); }, [Elements, Styles]); return ( \u0026lt;div ref={refEl} style={{ width: \u0026#39;300px\u0026#39;, height: \u0026#39;200px\u0026#39; }}\u0026gt;\u0026lt;/div\u0026gt; ); } export default Child; Child.tsx に 30、31行を追加しただけで、他は以前のままです。\n30行目：不透明度を即下げる\n31行目：1秒掛けて不透明にする\nで、じんわり更新しているような効果にしてみました。\n前回\n今回\n","date":"2023-03-18T17:01:29+09:00","image":"/images/2022/09/rvec.jpg","permalink":"/posts/it/pc/5063/","title":"React：Cytoscape.js の図をじんわり更新する"},{"content":"以前、クラスライブラリを使用して距離を取得しましたが、そのときのライブラリが今では使いづらそうなので、算出するコードを作成してみました。\nChatGPT で生成して、少し直した程度です。凄い時代になりましたね。\nVincenty 法で求めると精度が高いようなので、それで生成させました。\nusing System; var location1 = new Location(35.6895, 139.6917); // 東京 var location2 = new Location(34.6937, 135.5022); // 大阪 var distance = Vincenty.Distance(location1, location2); Console.WriteLine($\u0026#34;Distance between Tokyo and Osaka: {distance} m\u0026#34;); public class Location { public double Latitude { get; set; } public double Longitude { get; set; } public Location(double latitude, double longitude) { Latitude = latitude; Longitude = longitude; } } public static class Vincenty { private const double Flattening = 1 / 298.257223563; // WGS84楕円体の扁平率 private const double EquatorialRadius = 6378137.0; // WGS84楕円体の赤道半径 public static double Distance(Location loc1, Location loc2) { var f = Flattening; var a = EquatorialRadius; var b = a * (1 - f); var latRad1 = loc1.Latitude * Math.PI / 180.0; var lonRad1 = loc1.Longitude * Math.PI / 180.0; var latRad2 = loc2.Latitude * Math.PI / 180.0; var lonRad2 = loc2.Longitude * Math.PI / 180.0; var L = lonRad2 - lonRad1; var tanU1 = (1 - f) * Math.Tan(latRad1); var cosU1 = 1 / Math.Sqrt((1 + tanU1 * tanU1)); var sinU1 = tanU1 * cosU1; var tanU2 = (1 - f) * Math.Tan(latRad2); var cosU2 = 1 / Math.Sqrt((1 + tanU2 * tanU2)); var sinU2 = tanU2 * cosU2; double lambda = L; double lambdaP = 2 * Math.PI; double sinLambda = 0; double cosLambda = 0; double sinSigma = 0; double cosSigma = 0; double sigma = 0; double sinAlpha = 0; double cosSqAlpha = 0; double cos2SigmaM = 0; double C = 0; while (Math.Abs(lambda - lambdaP) \u0026gt; 1e-12) { sinLambda = Math.Sin(lambda); cosLambda = Math.Cos(lambda); sinSigma = Math.Sqrt((cosU2 * sinLambda) * (cosU2 * sinLambda) + (cosU1 * sinU2 - sinU1 * cosU2 * cosLambda) * (cosU1 * sinU2 - sinU1 * cosU2 * cosLambda)); if (sinSigma == 0) { return 0; // 2点間の距離が0の場合 } cosSigma = sinU1 * sinU2 + cosU1 * cosU2 * cosLambda; sigma = Math.Atan2(sinSigma, cosSigma); sinAlpha = cosU1 * cosU2 * sinLambda / sinSigma; cosSqAlpha = 1 - sinAlpha * sinAlpha; if (cosSqAlpha == 0) { cos2SigmaM = 0; } else { cos2SigmaM = cosSigma - 2 * sinU1 * sinU2 / cosSqAlpha; } C = f / 16 * cosSqAlpha * (4 + f * (4 - 3 * cosSqAlpha)); lambdaP = lambda; lambda = L + (1 - C) * f * sinAlpha * (sigma + C * sinSigma * (Math.Cos(2 * sigma) + C * cosSigma * (-1 + 2 * Math.Cos(2 * sigma)))); } var uSq = cosSqAlpha * (a * a - b * b) / (b * b); var A = 1 + uSq / 16384 * (4096 + uSq * (-768 + uSq * (320 - 175 * uSq))); var B = uSq / 1024 * (256 + uSq * (-128 + uSq * (74 - 47 * uSq))); var deltaSigma = B * sinSigma * (cos2SigmaM + B / 4 * (cosSigma * (-1 + 2 * cos2SigmaM * cos2SigmaM) - B / 6 * cos2SigmaM * (-3 + 4 * sinSigma * sinSigma) * (-3 + 4 * cos2SigmaM * cos2SigmaM))); var s = b * A * (sigma - deltaSigma); return s; } } Distance between Tokyo and Osaka : 397192.2878195703 m ","date":"2023-03-11T14:17:35+09:00","image":"/images/2021/12/map.jpg","permalink":"/posts/it/pc/5048/","title":"C#：経度緯度の２点間の距離を求めるには"},{"content":"Google TypeScript Style Guide に従う場合、 gts が便利そうなので使ってみます。\nインストール お試しなので、空のディレクトリで次のコマンドを実行しました。\n既存のプロジェクトに追加する場合も同様のコマンドですが、その際は設定に問題がないように気をつける必要がありそうなので、今回はパスしました。\nnpx gts init lint の実行 お試し用のソースコード src/index.ts が生成されるので、次のコマンドで lint を実行してみます。\nyarn lint 3:7 warning \u0026#39;longString\u0026#39; is assigned a value but never used @typescript-eslint/no-unused-vars 6:7 warning \u0026#39;trailing\u0026#39; is assigned a value but never used @typescript-eslint/no-unused-vars 8:7 warning \u0026#39;why\u0026#39; is assigned a value but never used @typescript-eslint/no-unused-vars ✖ 3 problems (0 errors, 3 warnings) 検出されてますね。\nソースコードのフォーマットと問題の修正 上記で検出される指摘は自動で修正できない問題なので、修正できる問題を src/index.ts 加えました。\n文字列の囲みをシングルクォーテーションからダブルクォーテーションに変更 変な所で改行 その後、次のコマンドを実行。\nyarn fix すると、ダブルクォーテーションがシングルクォーテーションになったり、変な改行が整形されました。\nソースコードを保存したときに自動修正 VSCode でソースコードの保存時に自動修正されるように .vscode ディレクトリの setting.json に設定しました。\n{ \u0026#34;editor.codeActionsOnSave\u0026#34;: { \u0026#34;source.fixAll\u0026#34;: true } } これで、ソースコードのフォーマットや、修正可能な問題は保存時に自動でされるので便利です。\nESLint 拡張機能を既にインストール済みな環境で試しています。\n必要か未確認なのですが、自動で修正されない場合はそれが原因かもしれません。\n既存プロジェクトに gts を追加するのは辛いとき ESLint を追加して、ルールは https://github.com/google/gts/blob/main/.eslintrc.json を参考に作成すると良さそうです。\n","date":"2023-03-04T12:10:47+09:00","image":"/images/2022/03/javascript.jpg","permalink":"/posts/it/pc/5013/","title":"TypeScript：Google のスタイルガイドに従ってみる"},{"content":"「2026年度長野市ごみカレンダー」を公開しました。 2023年度（令和5年度）長野市のゴミ収集日を Google カレンダーに表示させる為の CSV ファイルを作成しました。 Google カレンダーに CSV ファイルを読み込ませる方法はこちらを参照ください。\n地区別 CSV ファイル 長野市がクリエイティブ・コモンズ・ライセンス表示4.0国際（CC-BY4.0）ライセンスの下に公開しているデータを昨年度のソースコードを流用して加工しました。\n※ 内容は保証しません。利用は、ご自身の判断でお願い致します。\n下記リンクからファイルのダウンロードができない場合、\nGoogle ドライブの共有ファイルからダウンロードをお試しください。\nその際は、欲しい CSV の下記 No. を元に対象のファイルをご利用ください。\nNo. 地区別 CSV ファイルダウンロード 1 第一 2 第二 3 第三 4 第四 5 第五 6 芹田 7 古牧 8 三輪 9 吉田 10 古里、朝陽 11 柳原、若穂 12 浅川 13 大豆島 14 若槻 15 長沼 16 安茂里 17 小田切 18 芋井 19 篠ノ井塩崎、篠ノ井共和、篠ノ井川柳、篠ノ井信里 20 篠ノ井東福寺、篠ノ井西寺尾 21 篠ノ井中央 22 松代 23 川中島 24 更北 25 七二会 26 信更 27 戸隠中社、戸隠宝光社、戸隠上楠川 28 戸隠北部、戸隠中央、戸隠東部、戸隠南部、戸隠川手、戸隠志垣 29 戸隠西部、戸隠平、戸隠西条、戸隠追通、戸隠上祖山、戸隠下祖山 30 鬼無里上里、鬼無里中央1 31 鬼無里中央2、鬼無里両京 32 大岡甲（川口を除く）、大岡中牧、大岡弘崎、大岡聖ヶ岡 33 大岡乙、大岡丙（聖ヶ岡を除く）、大岡川口 34 豊野南郷、豊野石、豊野西町、豊野上組、豊野立町、豊野南町、豊野中尾1・2 35 豊野横町、豊野伊豆毛、豊野上田中、豊野神代町、豊野本町1・2、豊野中尾1・2（一部）、豊野小瀬1、豊野泉平、豊野上神代、豊野豊陽台、豊野沖1・2、豊野中央組、豊野向原 36 豊野本町3・4・5、豊野東町、豊野小瀬2、豊野ゆたかの、豊野豊南町、豊野下田中 37 豊野浅野、豊野蟹沢、豊野入、豊野小日向、豊野上堰、豊野鳥居団地、豊野大方、豊野橋場、豊野上原、豊野蟻ケ崎、豊野城山、豊野川谷 38 信州新町旭町、信州新町仲町、信州新町上町、信州新町西上町、信州新町常磐町、信州新町鹿島東、信州新町鹿島西、信州新町大原東、信州新町大原西、信州新町下市場、信州新町牧野島（伊切を除く）、信州新町鹿道、信州新町鹿道団地 39 信州新町久保、信州新町本町、信州新町境町、信州新町千原田、信州新町平、信州新町和平団地、信州新町藤池団地、信州新町下川西平、信州新町太田笠子（石畑を除く）、信州新町穂刈下、信州新町穂刈中、信州新町穂刈上、信州新町穂刈北、信州新町穂刈団地、信州新町陽のあたる丘、信州新町大門、信州新町LR、信州新町原、信州新町道祖神 40 信州新町津和中央、信州新町山秋、信州新町中福、信州新町栃久保、信州新町中尾、信州新町菅沼、信州新町細尾、信州新町津上、信州新町外味藤、信州新町豊和、信州新町津南、信州新町中組、信州新町味藤、信州新町橋場、信州新町安用、信州新町風越、信州新町追沢、信州新町神田、信州新町花倉、信州新町二丁田、信州新町穴平、信州新町寺尾、信州新町矢ノ尻、信州新町峰組、信州新町枌ノ木、信州新町赤柴、信州新町石畑、信州新町尾崎、信州新町上古、信州新町芦沢、信州新町本村、信州新町大河、信州新町西日時 41 信州新町塩本、信州新町伊切、信州新町牧田中一、信州新町牧田中二、信州新町中牧一、信州新町中牧二、信州新町南牧住平、信州新町一倉田和、信州新町下中山、信州新町日名、信州新町和田吐唄、信州新町置原、信州新町橋木、信州新町左右、信州新町岩下、信州新町信級中央、信州新町高見、信州新町岩本、信州新町柳高、信州新町川名 42 中条 情報元 上記 CSV ファイルの元データや参考にしたソースコードです。\n長野市オープンデータ 令和５年度 ごみ収集カレンダー （「最後に更新した日」は「2023年2月17日」でした。） Rへのロード用API ","date":"2023-02-25T09:15:40+09:00","image":"/images/2022/04/gomi.jpg","permalink":"/posts/it/pc/4995/","title":"Google カレンダー：2023年度長野市ごみ収集日"},{"content":"作業しているワークスペースでのみ効いて欲しい環境変数を設定したときのメモです。\n以下の設定をワークスペースのファイルに記載しました。\nOpenSSL の互換性の都合で、環境変数 NODE_OPTIONS を設定した。 npm または yarn で追加したパッケージをターミナルから実行したいが、パスが通っていないので追加した。 Windows、Mac、Linux それぞれの設定を記載しましたが、使う環境だけ書いておけば OK だと思います。\n{ \u0026#34;folders\u0026#34;: [ { \u0026#34;path\u0026#34;: \u0026#34;.\u0026#34; } ], \u0026#34;settings\u0026#34;: { \u0026#34;terminal.integrated.env.windows\u0026#34;: { \u0026#34;NODE_OPTIONS\u0026#34;: \u0026#34;--openssl-legacy-provider\u0026#34;, \u0026#34;PATH\u0026#34;: \u0026#34;${env:PATH};${workspaceFolder}\\\\node_modules\\\\.bin\u0026#34; }, \u0026#34;terminal.integrated.env.osx\u0026#34;: { \u0026#34;NODE_OPTIONS\u0026#34;: \u0026#34;--openssl-legacy-provider\u0026#34;, \u0026#34;PATH\u0026#34;: \u0026#34;${env:PATH}:${workspaceFolder}/node_modules/.bin\u0026#34; }, \u0026#34;terminal.integrated.env.linux\u0026#34;: { \u0026#34;NODE_OPTIONS\u0026#34;: \u0026#34;--openssl-legacy-provider\u0026#34;, \u0026#34;PATH\u0026#34;: \u0026#34;${env:PATH}:${workspaceFolder}/node_modules/.bin\u0026#34; } } } ${\u0026hellip;} は VSCode で定義済みの変数です。\n参考：VSCode の定義済み変数\nhttps://code.visualstudio.com/docs/editor/variables-reference\nVisual Studio Code実践ガイド (Amazon) Visual Studio Codeデバッグ技術 (Amazon) Docker Desktop for Windows/Mac と VSCode でつくるクリーンな開発環境構築入門 (Amazon)\n","date":"2023-02-18T17:56:52+09:00","image":"/images/2022/08/shortcut00.jpg","permalink":"/posts/it/pc/4970/","title":"VSCode：ワークスペースでのみ有効な環境変数を設定する"},{"content":"試作内容 \u0026lt;Child Elements={el} Styles={sy} /\u0026gt; 上記のように、props で cytoscape へ設定するデータを渡して更新する Child コンポーネントの作成を目指します。\nテンプレ作成 ベースとなるアプリは以下のコマンドで用意しました。\nnpx create-react-app my-app --template=typescript cd my-app yarn add cytoscape yarn add -D @types/cytoscape 試作したコンポーネント import React, { FC, useRef, useEffect } from \u0026#39;react\u0026#39;; import cytoscape, { Core, ElementDefinition, Stylesheet } from \u0026#39;cytoscape\u0026#39;; const Child: FC\u0026lt;{ Elements?: ElementDefinition[], Styles?: Stylesheet[], }\u0026gt; = ({ Elements = undefined, Styles = undefined, }) =\u0026gt; { const refEl = useRef(null); const refCy = useRef\u0026lt;Core | null\u0026gt;(null); useEffect(() =\u0026gt; { const container = refEl.current! as HTMLDivElement; refCy.current = cytoscape({ container: container, elements: Elements, style: Styles, }); const cy = refCy.current; return (() =\u0026gt; { cy.destroy(); }) }, []); useEffect(() =\u0026gt; { const cy = refCy.current; cy?.json({ elements: Elements, styles: Styles }); }, [Elements, Styles]); return ( \u0026lt;div ref={refEl} style={{ width: \u0026#39;300px\u0026#39;, height: \u0026#39;200px\u0026#39; }}\u0026gt;\u0026lt;/div\u0026gt; ); } export default Child; 主なポイントは、useEffect を以下の２つに分けて処理するところでしょうか。\n初期に描画する処理（14行目以降） 更新された props のデータで描画する処理（27行目以降） 14行目の useEffect の第２引数に Elements と Styles を入れても更新は可能でしたが、はじめからの描画になるので、図の変更（ノードの移動など）が戻ってしまいました。\nその為、27行目の useEffect で props の更新に反応させ、\nsrc/Child.tsx Line 25:8: React Hook useEffect has missing dependencies: \u0026#39;Elements\u0026#39; and \u0026#39;Styles\u0026#39;. Either include them or remove the dependency array react-hooks/exhaustive-deps と言う指摘は無視しています。\nやはりこの手のライブラリ使用は useEffect でハマりそうな気がしますね。\nあと、cytoscape の要素の初期の高さをどうするか悩みそうでした。\n何もしないと高さがゼロになって何も描かれないんですよね。\n仕方がないので今回は固定値を設定しました。\n作成したコンポーネントを使用したスクリーンショット ボタンクリックで、Node1 の色が変わります。\nblank：黒 A：赤 B：青 その他のファイル Child.tsx の他に、修正または追加したファイルは以下の通りです。\nimport React, { useState } from \u0026#39;react\u0026#39;; import Child from \u0026#39;./Child\u0026#39;; import Elements from \u0026#39;./cy-element\u0026#39;; import Styles from \u0026#39;./cy-style\u0026#39;; function App() { const [el, setElements] = useState(Elements); const [sy, setStyles] = useState(Styles); const onClick = (type: string) =\u0026gt; { const node = el.find(e =\u0026gt; e.data.id === \u0026#34;n01\u0026#34;); node!.data.type = type; setElements([...el]); }; return ( \u0026lt;div\u0026gt; \u0026lt;Child Elements={el} Styles={sy} /\u0026gt; \u0026lt;button onClick={() =\u0026gt; onClick(\u0026#39;\u0026#39;)}\u0026gt;blank\u0026lt;/button\u0026gt; \u0026lt;button onClick={() =\u0026gt; onClick(\u0026#39;A\u0026#39;)}\u0026gt;A\u0026lt;/button\u0026gt; \u0026lt;button onClick={() =\u0026gt; onClick(\u0026#39;B\u0026#39;)}\u0026gt;B\u0026lt;/button\u0026gt; \u0026lt;/div\u0026gt; ); } export default App; import { ElementDefinition } from \u0026#39;cytoscape\u0026#39;; export default [{ \u0026#34;data\u0026#34;: { \u0026#34;id\u0026#34;: \u0026#34;n01\u0026#34;, \u0026#34;label\u0026#34;: \u0026#34;Node1\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;A\u0026#34; } }, { \u0026#34;data\u0026#34;: { \u0026#34;id\u0026#34;: \u0026#34;n02\u0026#34;, \u0026#34;label\u0026#34;: \u0026#34;Node2\u0026#34; } }, { \u0026#34;data\u0026#34;: { \u0026#34;source\u0026#34;: \u0026#34;n01\u0026#34;, \u0026#34;target\u0026#34;: \u0026#34;n02\u0026#34; } }] as ElementDefinition[]; import { Stylesheet } from \u0026#39;cytoscape\u0026#39;; export default [ { \u0026#34;selector\u0026#34;: \u0026#34;node\u0026#34;, \u0026#34;style\u0026#34;: { \u0026#39;label\u0026#39;: \u0026#39;data(label)\u0026#39;, \u0026#34;background-color\u0026#34;: \u0026#34;black\u0026#34; } }, { \u0026#34;selector\u0026#34;: \u0026#34;node[type=\u0026#39;A\u0026#39;]\u0026#34;, \u0026#34;style\u0026#34;: { \u0026#34;background-color\u0026#34;: \u0026#34;red\u0026#34; } }, { \u0026#34;selector\u0026#34;: \u0026#34;node[type=\u0026#39;B\u0026#39;]\u0026#34;, \u0026#34;style\u0026#34;: { \u0026#34;background-color\u0026#34;: \u0026#34;blue\u0026#34; } } ] as Stylesheet[]; プロを目指す人のためのTypeScript入門 (Amazon) ","date":"2023-02-11T20:00:24+09:00","image":"/images/2022/09/rvec.jpg","permalink":"/posts/it/pc/4933/","title":"React：props で Cytoscape.js の図を更新する試作"},{"content":"Cytoscape.js を使った試作に、要素の大きさに図のサイズが追随するようなコードを追記しました。\n以前と差分がある行を強調表示にしています。（分かりづらいですが。）\nimport React, { useRef, useEffect } from \u0026#39;react\u0026#39;; import \u0026#39;./App.css\u0026#39;; import cytoscape, { Core, GridLayoutOptions } from \u0026#39;cytoscape\u0026#39;; import cyStyle from \u0026#39;./cy-style.json\u0026#39;; import data from \u0026#39;./data.json\u0026#39;; function App() { const refEl = useRef(null); const refCy = useRef\u0026lt;Core | null\u0026gt;(null); useEffect(() =\u0026gt; { const container = refEl.current! as HTMLDivElement; refCy.current = cytoscape({ container: container, layout: { name: \u0026#39;grid\u0026#39;, columns: 4 } as GridLayoutOptions, style: cyStyle as unknown as cytoscape.Stylesheet[], elements: data as unknown as cytoscape.ElementsDefinition }); const cy = refCy.current; cy.ready(function(){ var n13 = cy.$(\u0026#39;#n13\u0026#39;); var n11 = cy.$(\u0026#39;#n11\u0026#39;); var n12 = cy.$(\u0026#39;#n12\u0026#39;); var p11 = n11.position(); var p12 = n12.position(); var d = (p12.x - p11.x)/4; n13.position({ x: (p11.x + p12.x)/2, y: p11.y - d }); n11.add(n12).position({ x: p11.x, y: p11.y + d }); }); return(() =\u0026gt; { cy.destroy(); }) }, []) // リサイズ対応 useEffect(() =\u0026gt;{ const el = refEl.current! as HTMLDivElement; const resizeObserver = new ResizeObserver(() =\u0026gt;{ const cy = refCy.current; cy?.resize(); cy?.fit(); }); resizeObserver.observe(el); return () =\u0026gt; resizeObserver.unobserve(el); }, []) return ( \u0026lt;div ref={refEl} style={{ width: \u0026#34;100vw\u0026#34;, height: \u0026#34;100vh\u0026#34; }} /\u0026gt; ); } export default App; ResizeObserver でリサイズを監視して、cy.resize、cy.fit で図のサイズを合わせています。\nブラウザのサイズを変更すると、要素のサイズも変わるので図の大きさも変わるようになります。\nプロを目指す人のためのTypeScript入門 (Amazon) ","date":"2023-02-04T10:54:59+09:00","image":"/images/2023/01/cs_et_00.jpg","permalink":"/posts/it/pc/4904/","title":"React：Cytoscape.js の図を要素に合わせてリサイズする"},{"content":"Hyper-V で Amazon Linux 2 を試しましたが、数日後に使ったら外部ネットワークに繋がらない。\n確認してみると、仮想スイッチで選択した Default Switch の IP アドレスが変わっていました。\n調べてみると Default Switch の IP アドレスは自動で振られて変わるんですね。\nなので、専用の仮想スイッチを用意することにしました。\n仮想スイッチの新規追加 「Hyper-V マネージャ」の「操作」から「仮想スイッチ マネージャー」を選択\n「新しい仮想ネットワーク スイッチ」で「内部」を選択して「仮想スイッチの作成」\n分かりやすい名前に変更します。\n追加した仮想スイッチがネットワーク接続に表示されるので、IP アドレスを設定します。\nIPv4 アドレス （192.168.244.1） サブネット マスク （225.225.240.0） 作成済みのゲスト OS の都合に合わせましたが、そういった事情が無ければ全く別のセグメントのアドレスを使用した方が無難だと思われます。（自動割り当てされた過去があるアドレスを取って大丈夫か未確認の為。）\nNAT の設定 追加した仮想スイッチは内部ネットワークなので、このままでは外部に繋がりません。\n内部ネットワークアドレスを外部ネットワークアドレスに変換する NAT の設定をします。\nPowerShell から次のコマンドで設定します。\nNew-NetNat -Name \u0026#34;amz2nat\u0026#34; -InternalIPInterfaceAddressPrefix 192.168.244.0/20 ちなみに、間違えたときなどで削除したいときは次のコマンド。\nRemove-NetNat -Name \u0026#34;amz2nat\u0026#34; 仮想スイッチの再選択 ゲスト OS の設定で、仮想スイッチを変更します。\nゲスト OS の ネームサーバーの設定 ゲスト OS が使用する ネームサーバーの IP アドレスを、ホスト OS が使用しているものと同じにします。\n今回のホスト OS の DNS サーバー は 192.168.0.1 なので、ゲスト OS（Amazon Linux 2）の /etc/resolv.conf を修正しました。\nnameserver 192.168.0.1 これで外部に繋がるようになりました。\nひと目でわかるHyper-V Windows Server 2022版 (Amazon) コンテナセキュリティ コンテナ化されたアプリケーションを保護する要素技術 (Amazon)\n","date":"2023-01-28T10:57:54+09:00","image":"/images/2023/01/vs00.jpg","permalink":"/posts/it/pc/4773/","title":"Hyper-V：Default Switch は IP アドレスが変わるので、新規に固定 IP の仮想スイッチを作る"},{"content":"R の gglot2 を使用して人口のグラフを作成してみました。\n使用したデータは、長野市がクリエイティブ・コモンズ・ライセンス表示4.0国際（CC-BY4.0）ライセンスの下に公開している「長野市　令和５年地区別年齢別人口」（2023/01/13 更新）です。\n全体 長野市　2023年（令和5年）年齢別人口\n地区別 長野市　2023年（令和5年）地区別年齢別人口\nソースコード 説明は、昨年の記事を参考にしてください。\nlibrary(tidyverse) source(\u0026#34;http://linkdata.org/api/1/rdf1s9823i/R\u0026#34;) data \u0026lt;- chikubetsu_nenreibetsu_202301[, -1] area_levels = data[[\u0026#34;地区名\u0026#34;]] gender \u0026lt;- c(\u0026#34;男\u0026#34;,\u0026#34;女\u0026#34;) cols \u0026lt;- data %\u0026gt;% colnames() %\u0026gt;% as.array() ages \u0026lt;- cols[str_detect(cols, pattern=\u0026#34;^X\u0026#34;)] %\u0026gt;% str_extract(pattern=\u0026#34;[0-9]+.+\u0026#34;) %\u0026gt;% str_replace(pattern=\u0026#34;(男|女)\u0026#34;, \u0026#34;\u0026#34;) %\u0026gt;% unique() data2 \u0026lt;- data.frame() for (r in 1:nrow(data)) { for (g in 1:length(gender)) { for (a in 1:length(ages)) { col \u0026lt;- sprintf(\u0026#34;X%s%s\u0026#34;, ages[a], gender[g]) age \u0026lt;- as.numeric(str_replace(ages[a], \u0026#34;歳.*\u0026#34;, \u0026#34;\u0026#34;)) row \u0026lt;- c( data[r, \u0026#34;地区名\u0026#34;], gender[g], age, data[r, col] ) data2 \u0026lt;- rbind(data2, row) } } } colnames(data2) \u0026lt;- c(\u0026#34;Area\u0026#34;, \u0026#34;Gender\u0026#34;, \u0026#34;Age\u0026#34;, \u0026#34;Population\u0026#34;) data2$Age \u0026lt;- as.numeric(data2$Age) data2$Population \u0026lt;- as.numeric(data2$Population) data2 \u0026lt;- data2 %\u0026gt;% mutate(Area = factor(Area, levels = area_levels)) theme_update(text=element_text(family=\u0026#34;Noto Sans Mono CJK JP\u0026#34;)) options(repr.plot.width=24, repr.plot.height=9) ggplot(data2, aes(x = as.factor(Age), y = Population, fill = Gender)) + geom_col(position = \u0026#34;dodge\u0026#34;) + labs(title = \u0026#34;長野市　2023年(令和5年)年齢別人口\u0026#34;, x = \u0026#34;歳\u0026#34;, y = \u0026#34;人口\u0026#34;) ggsave(\u0026#34;2023_nagano-shi_Population_All.png\u0026#34;, width = 24, height = 9, dpi = 96) options(repr.plot.width = 48, repr.plot.height = 32) ggplot(data2, aes(x = Age, y = Population, fill = Gender)) + geom_col(position = \u0026#34;dodge\u0026#34;) + facet_wrap(~Area, ncol = 4) + labs(title = \u0026#34;長野市　2023年(令和5年)地区別年齢別人口\u0026#34;, x = \u0026#34;歳\u0026#34;, y = \u0026#34;人口\u0026#34;) ggsave(\u0026#34;2023_nagano-shi_Population_Area.png\u0026#34;, width = 48, height = 32, dpi = 96) データ分析のためのデータ可視化入門 (Amazon) ","date":"2023-01-21T06:38:37+09:00","image":"/images/2022/05/Population.jpg","permalink":"/posts/it/pc/4748/","title":"2023年(令和5年) 長野市の人口を gglot2 でグラフにしてみた"},{"content":"前回に引き続き、ライブラリを物色しています。\n環境 yarn create react-app my-app --template typescript cd my-app yarn add cytoscape yarn add -D @types/cytoscape で作成しました。\nReact は 18.2.0\ncytoscape は 3.23.0\nでした。\nサンプル Demo にある Edge-types を組み込んでみました。\ncy-style.json と data.json はデモのリポジトリから拝借し、App.tsx ファイルを修正しました。\nimport React, { useRef, useEffect } from \u0026#39;react\u0026#39;; import \u0026#39;./App.css\u0026#39;; import cytoscape, { GridLayoutOptions } from \u0026#39;cytoscape\u0026#39;; import cyStyle from \u0026#39;./cy-style.json\u0026#39;; import data from \u0026#39;./data.json\u0026#39;; function App() { const el = useRef(null); useEffect(() =\u0026gt; { const container = el.current! as HTMLDivElement; var cy = cytoscape({ container: container, layout: { name: \u0026#39;grid\u0026#39;, columns: 4 } as GridLayoutOptions, style: cyStyle as unknown as cytoscape.Stylesheet[], elements: data as unknown as cytoscape.ElementsDefinition }); cy.ready(function(){ var n13 = cy.$(\u0026#39;#n13\u0026#39;); var n11 = cy.$(\u0026#39;#n11\u0026#39;); var n12 = cy.$(\u0026#39;#n12\u0026#39;); var p11 = n11.position(); var p12 = n12.position(); var d = (p12.x - p11.x)/4; n13.position({ x: (p11.x + p12.x)/2, y: p11.y - d }); n11.add(n12).position({ x: p11.x, y: p11.y + d }); }); return(() =\u0026gt; { cy.destroy(); }) }, []) return ( \u0026lt;div ref={el} style={{ width: \u0026#34;100vw\u0026#34;, height: \u0026#34;100vh\u0026#34; }} /\u0026gt; ); } export default App; プロを目指す人のためのTypeScript入門 (Amazon) ","date":"2023-01-14T21:46:12+09:00","image":"/images/2023/01/cs_et_00.jpg","permalink":"/posts/it/pc/4734/","title":"React：Cytoscape.js を試した"},{"content":"構成図や階層図に使えそうなライブラリを物色しています。\n現状、React でこの手のグラフを描画するライブラリの多くは相性が良くないと感じています。\nmaxGraph を試した結果を残しますが、やはり React と相性が良くなさそうでした。\n別々に作って組み合わせた方が良いんでしょうか。\n環境 yarn create react-app my-app --template typescript cd my-app yarn add @maxgraph/core で作成しました。\nReact は 18.2.0\nmaxGraph は 0.1.0\nでした。\nサンプル README.md にある例を組み込んでみました。\nApp.tsx ファイルを修正しました。\nimport React, { useRef, useEffect } from \u0026#39;react\u0026#39;; import { Graph, InternalEvent } from \u0026#39;@maxgraph/core\u0026#39;; function App() { const el = useRef(null); useEffect(() =\u0026gt; { const container = el.current!; InternalEvent.disableContextMenu(container); const c = container as HTMLDivElement; const g = c.appendChild(document.createElement(\u0026#34;div\u0026#34;)); const graph = new Graph(g); graph.setPanning(true); const parent = graph.getDefaultParent(); graph.batchUpdate(() =\u0026gt; { const vertex01 = graph.insertVertex(parent, null, \u0026#39;a regular rectangle\u0026#39;, 10, 10, 100, 100); const vertex02 = graph.insertVertex(parent, null, \u0026#39;a regular ellipse\u0026#39;, 350, 90, 50, 50, {shape: \u0026#39;ellipse\u0026#39;, fillColor: \u0026#39;orange\u0026#39;}); graph.insertEdge(parent, null, \u0026#39;a regular edge\u0026#39;, vertex01, vertex02); }); return(() =\u0026gt; { g.remove(); }) }, []); return ( \u0026lt;div ref={el} /\u0026gt; ); } export default App; 表示はできましたが、useEffect の挙動を考えるとハマりそうとしか思えません。。\nプロを目指す人のためのTypeScript入門 (Amazon) ","date":"2023-01-07T20:05:29+09:00","image":"/images/2023/01/mg00.jpg","permalink":"/posts/it/pc/4714/","title":"React：maxGraph を試した"},{"content":"前回はカテゴリ値だったので、数値もやっておきます。\nlibrary(\u0026#34;dplyr\u0026#34;) which( sapply( colnames(iris), function(c) { iris[[c]] %\u0026gt;% na.omit() %\u0026gt;% is.numeric() } ) ) %\u0026gt;% names() \u0026#39;Sepal.Length\u0026#39;\u0026#39;Sepal.Width\u0026#39;\u0026#39;Petal.Length\u0026#39;\u0026#39;Petal.Width\u0026#39; ","date":"2022-12-31T18:32:03+09:00","image":"/images/2022/12/num.jpg","permalink":"/posts/it/pc/4708/","title":"R：データフレームから数値の列を選別する"},{"content":"データフレームのどの列がカテゴリ値なのか、summary で見るなどして予め確認しておくべきなのかもですが、ある程度の条件でそれらしい列を抽出しようという試みです。\nデータは iris を使う カテゴリ値は、列ごとに 2 〜 5 種類存在すると想定 とした場合、以下のような形が使えるかもしれません。\nlibrary(\u0026#34;dplyr\u0026#34;) which( sapply( colnames(iris), function(c) { iris[[c]] %\u0026gt;% na.omit() %\u0026gt;% unique() %\u0026gt;% length() %\u0026gt;% between(2, 5) } ) ) %\u0026gt;% names() \u0026#39;Species\u0026#39; データ量が少ないと誤認識しそうですが、参考になれば。\n","date":"2022-12-24T16:16:05+09:00","image":"/images/2022/05/amzn2.jpg","permalink":"/posts/it/pc/4701/","title":"R：データフレームからカテゴリ値らしい列を選別する"},{"content":"Warning message: “`aes_string()` was deprecated in ggplot2 3.0.0. ℹ Please use tidy evaluation ideoms with `aes()`” といったメッセージで対応したときのメモです。\n対応前 変数に入れた列名など、文字列で列名を指定したいときに使っていました。\nlibrary(ggplot2) x_val \u0026lt;- \u0026#34;Sepal.Length\u0026#34; y_val \u0026lt;- \u0026#34;Sepal.Width\u0026#34; c_val \u0026lt;- \u0026#34;Species\u0026#34; # aes_string ggplot( data = iris, mapping = aes_string( x = x_val, y = y_val, color = c_val ) ) + geom_point() # aes_ ggplot( data = iris, mapping = aes_( x = as.name(x_val), y = as.name(y_val), color = as.name(c_val) ) ) + geom_point() 対応後 library(ggplot2) x_val \u0026lt;- \u0026#34;Sepal.Length\u0026#34; y_val \u0026lt;- \u0026#34;Sepal.Width\u0026#34; c_val \u0026lt;- \u0026#34;Species\u0026#34; ggplot( data = iris, mapping = aes( x = .data[[x_val]], y = .data[[y_val]], color = .data[[c_val]] ) ) + geom_point() データ分析のためのデータ可視化入門 (Amazon) ","date":"2022-12-17T16:16:14+09:00","image":"/images/2022/09/plotcolor00.jpg","permalink":"/posts/it/pc/4689/","title":"ggplot2：aes_ や aes_string が非推奨なので置き換える"},{"content":"GPS はアメリカの衛星測位システムで、日本が運用するのは QZSS だそうですが、大抵 GPS って言っちゃいますよね。\nその衛星の位置が確認できるスマホアプリ「GNSS View」のメモです。\n（GNSS：Global Navigation Satellite System）\n起動すると、以下のように「Position Radar」か「AR Display」を選択する画面になります。\nPosition Radar 表記国名称GPSアメリカGlobal Positioning SystemGLOロシアGLONASSグロナスGAL欧州GalileoガリレオSBS-Satellite-based augmentation systemsQZS日本Quasi-Zenith Satellite SystemみちびきBDS中国BeiDou Navigation Satellite System北斗 「Select Satellite」から、表示する衛星を選択できます。\n日本の QZSS だけにしてみます。\n番号衛星名194みちびき2号機QZS02199みちびき3号機QZS03195みちびき4号機QZS04196みちびき初号機後継機QZS1R AR Display スマホのカメラ越しに、衛星が居るであろう位置が確認できます。\n外壁越しなこの位置では、表示されている衛星からの電波は多分入らないんでしょう。\n","date":"2022-12-10T09:47:00+09:00","image":"/images/2021/12/map.jpg","permalink":"/posts/it/smartphone/4663/","title":"スマホで GPS 衛星の位置を確認する"},{"content":"与えられたデータを２つのグループに分けるとき、２値の列を使ったりします。\nただ、列が多いときは、どの列が2値か得られると楽なので、その場合のメモです。\n元データ 丁度良いサンプルデータが見つからなかったので、iris へテスト用に NA を含む2値の列を追加しました。\nSepal.Length.bool\nTRUE または FALSE または NA Petal.Width.ab\n\u0026ldquo;A\u0026rdquo; または \u0026ldquo;B\u0026rdquo; または NA library(dplyr) data \u0026lt;- iris %\u0026gt;% mutate( Sepal.Length.bool = ifelse(Sepal.Length \u0026gt; 7, NA, trunc(Sepal.Length) %% 2 == 0), Petal.Width.ab = ifelse(Petal.Width \u0026gt; 2, NA, ifelse(trunc(Petal.Width) %% 2 == 0, \u0026#34;A\u0026#34;, \u0026#34;B\u0026#34;)) ) 結果はこんな感じ。\ndata %\u0026gt;% tail(10) 2値の列を得る cols \u0026lt;- colnames(data) twoGroupCols \u0026lt;- cols[which(sapply(cols, function(col) { data[[col]] %\u0026gt;% na.omit() %\u0026gt;% unique() %\u0026gt;% length() == 2 }))] で、２値の列が得られます。\n","date":"2022-12-03T11:04:00+09:00","image":"/images/2022/12/twoGrp00.jpg","permalink":"/posts/it/pc/4640/","title":"R：データから2値が入っている列名を得る"},{"content":"Windows 10 から 11 になって、一時ファイルや古い Windows Update ファイルを削除する機能を見失ったのですが、分かったのでメモです。\n不要なファイルを削除することで、ディスクの空き容量を増やせるハズです。\n１．スタートメニューから設定をクリックし、「システム」の「記憶域」を選択\n２．「一時ファイル」を選択\n３．削除したい項目にチェックを入れて「ファイルの削除」をクリック\n自分の場合は、「ダウンロード」フォルダのファイルは残したいので未チェックとし、\nその他は不要なので選択して削除しました。\n","date":"2022-11-26T09:32:00+09:00","image":"/images/2022/11/wt00.jpg","permalink":"/posts/it/pc/4622/","title":"Windows 11：ディスクの空き容量を増やす"},{"content":"「Y!News Excluder」を Google Chrome に追加することで実現できるので紹介します。\nSafari 版も出ました。別投稿で紹介しています。\n自分の場合は、\n切り取りや炎上系などで頻出する人名 注目度が高い事件/事故/事象などで一時的に急増する単語 を設定して、対象の記事が表示されないようにしました。\n拡張機能の追加 Google Chrome で Chrome ウェブストアにある「Y!News Excluder」拡張機能のページを開き、「Chrome に追加」をクリック\n「拡張機能を追加」をクリックして、拡張機能を追加します。\n除外する記事の単語設定 Chrome の右上にある「拡張機能」から「Y!News Excluder」をクリックします。\n除外したい記事に含まれる単語を入力して「更新」をクリックします。\n下記で入力している単語は例です。実際は人名や通称などを設定しています。\n設定後、Yahoo! JAPAN を開いて除外できているか確認します。\n自分は気に入ったのでコーヒー代を寄付してみました。\n","date":"2022-11-19T12:31:38+09:00","image":"/images/2022/11/yne00.jpg","permalink":"/posts/it/pc/4597/","title":"Yahoo!ニュース：指定の単語がタイトルに含まれる記事を除外する【Google Chrome 拡張機能】"},{"content":"sf を使う為に install.packages(\u0026ldquo;sf\u0026rdquo;) した所、以下のエラーでインストールできなかったので、解消した際のメモです。\nERROR: dependencies ‘s2’, ‘units’ are not available for package ‘sf’ 環境 Amazon Linux release 2 (Karoo) R version 4.0.2 また、以下のコマンドで EPEL レポジトリを有効化済みです。\nsudo yum install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm 必要なパッケージのインストール GDAL 2.0.1 以上が必要なのですが、yum で入れられるのが 1.11.4 なので、3.5.3 のソースをコンパイルしてインストールします。\nまた、その際に PROJ 6 が必要になるのですが、こちらも yum で入れられるのが 4.8.0 なので、先に 6.1.1 のソースをコンパイルしてインストールします。\nyum でインストール まずは、yum で行けるパッケージから。\nsudo yum install -y openssl-devel geos-devel sqlite-devel udunits2-devel cmake3 ちなみに、以下のパッケージは既にインストール済みだったので含めませんでした。\ngcc-c++ cpp libtiff PROJ のインストール ソースコードをコンパイルしてインストール。\ncd /tmp wget https://download.osgeo.org/proj/proj-6.1.1.tar.gz tar -xvf proj-6.1.1.tar.gz cd proj-6.1.1 ./configure sudo make sudo make install GDAL のインストール ソースコードをコンパイルしてインストール。\ncd /tmp wget https://github.com/OSGeo/gdal/releases/download/v3.5.3/gdal-3.5.3.tar.gz tar -xvf gdal-3.5.3.tar.gz cd gdal-3.5.3 ./configure --with-proj=/usr/local sudo make sudo make install 共有ライブラリの反映 コンパイルしたライブラリが /usr/local/lib にインストールされます。\n既に共有ライブラリとして設定済みの場合は良いのですが、自分の環境ではできていなかったので設定しました。\nsudo bash -c \u0026#34;echo /usr/local/lib\u0026gt;/etc/ld.so.conf.d/usr.local.lib.conf\u0026#34; /etc/ld.so.conf.d/usr.local.lib.conf ファイルを新規作成してパスを設定しました。\n既に usr.local.lib.conf ファイルが存在することは無いと思いますが、あるなら追記するなり別のファイル名で作成します。\n設定を反映にするために、次のコマンドを実行します。\nsudo ldconfig sf パッケージのインストール 後は通常通り R から install.packages(\u0026ldquo;sf\u0026rdquo;) でインストールできました。\n問題なければ、コンパイルで使ったファイルを削除しておきます。\nsudo rm -rf /tmp/proj-6.1.1.* sudo rm -rf /tmp/gdal-3.5.3.* ","date":"2022-11-12T06:52:41+09:00","image":"/images/2022/09/rvec.jpg","permalink":"/posts/it/pc/4535/","title":"Amazon Linux 2 で R に sf パッケージをインストールする"},{"content":"開発サーバー内に共有リポジトリを作成するときのメモです。\n別サーバーにリポジトリを用意する程では無いときの参考になれば。\n状況 ホームディレクトリ配下でソースコードを書いて git で管理していた そのソースコードを、同一サーバーの他ユーザーと更新したくなった 別サーバーに git のリポジトリを用意する程では無い ということで、サーバー内に共有リポジトリを用意することにしました。\n共有前のソースコード ソースコードを作成したユーザーは kuma ホームディレクトリの example フォルダでソースコードを書いており、git を使っている ブランチは master と develop がある $ pwd /home/kuma/example $ git branch * develop master グループを作成してユーザーを所属させる 共有リポジトリを使用するユーザーのグループ名を developer で作成します。\nユーザーの kuma と、コードを共有するユーザー hachi を所属させます。\n$ sudo groupadd developer $ sudo usermod -aG developer kuma $ sudo usermod -aG developer hachi できているか確認します。\n$ getent group developer developer:x:1008:kuma,hachi 共有リポジトリの作成 共有リポジトリの作成先は /usr/local/git/example.git/ とします。\n共有リポジトリの所有グループを developer に変更します。\n$ sudo git init --bare --shared /usr/local/git/example.git/ $ sudo chgrp -R developer /usr/local/git/example.git/ 作成していたソースコードを、共有リポジトリに反映します。\n$ pwd /home/kuma/example $ git remote add origin /usr/local/git/example.git/ $ git push --set-upstream origin master $ git push --set-upstream origin develop 共有リポジトリを使う ソースコードの更新に加わる hachi ユーザーは\nメールアドレスと名前を設定 ソースコードを自身のホームディレクトリ配下に clone $ pwd /home/hachi $ git config --global user.email \u0026#34;hachi@example.com\u0026#34; $ git config --global user.name \u0026#34;hachi\u0026#34; $ git clone /usr/local/git/example.git/ とすることで、hachi は /home/hachi/example/ で作業でき、git で kuma とソースコードを更新していく感じです。\n","date":"2022-11-05T12:46:00+09:00","image":"/images/2022/10/repo.jpg","permalink":"/posts/it/pc/4466/","title":"git：ローカルサーバー内で共有リポジトリを使う"},{"content":"macOS のアップデートをする際、インストールできるUSBも作成するようにしています。\nアップデートが終わるとインストーラを作成に必要なファイルが消えるので、以下の画面の「続ける」をする前に実施します。\nUSBメモリをマウントして、ターミナルから次のコマンドを実行すると、インストーラのUSB起動ディスクが作成できます。\n自分は \u0026ldquo;usb\u0026rdquo; というボリューム名で USB メモリを用意しました。\nsudo /Applications/Install\\ macOS\\ Ventura.app/Contents/Resources/createinstallmedia --volume /Volumes/usb USB には 14GB ぐらい容量が必要の様です。\n※ 最初は、ターミナルで以下のエラーがでました。\nIA app name cookie write failed The bless of the installer disk failed. なので、ターミナルにフルディスクアクセスを許可しました。\nアップルメニューから、システム環境設定 - セキュリティとプライバシー - プライバシー を選ぶ ガギをクリック 「フルディスクアクセス」を選択 「ターミナル」にチェックを入れる\n無ければ「＋」をクリックして「ターミナル」を追加する USB にインストーラを作成できたら、チェックを戻しておきました。\nバッファロー SSD 外付け 1.0TB USB3.2 (Amazon) Mac mini (Amazon) MacBook Pro 13インチ (Amazon) MacBook Air (Amazon) Mac Fan 2026年5月号 (Amazon)\n","date":"2022-10-29T10:13:23+09:00","image":"/images/2022/10/usbinst00.jpg","permalink":"/posts/it/pc/4512/","title":"USB メモリに macOS Ventura のインストーラを作成する"},{"content":"簡易マニュアルや掲示物、公的な通知など、\n手軽に参照したい いつまで保管が必要か分からない といった１枚紙は、電子データとして撮っておけば気が楽ですよね。\nその場合、標準アプリのメモを使うと、\n書類のスキャン 文字のスキャン iCloud に保存 ができるので便利です。\n環境 iOS 16.0.3 で確認しています。\nメモの起動 標準のアプリなので、以下のアイコンがあるはずです。\n起動したら、新規メモを追加します。\n書類のスキャン 「カメラ」アイコンをタップして、「書類をスキャン」をタップします。\n保存したい用紙をカメラに収めます。\n検出した用紙が黄色で示され、シャッターは自動で切られました。\n書類のスキャン 写真のみでは後で探しづらいので、その紙をスキャンして文字も残しておきます。\n「カメラ」アイコンをタップして、「テキストをスキャン」をタップします。\n読み取らせたい場所をスキャンします。\nタイトル的な文や、検索しそうな単語を含めるのが良いと思います。\n良ければ「完了」をタップして、メモの入力を終わらせます。\nといった感じで保存することができました。\n","date":"2022-10-22T13:10:17+09:00","image":"/images/2022/10/memo00.jpg","permalink":"/posts/it/smartphone/4428/","title":"iPhone：1枚紙をメモに保存する"},{"content":"%in% で NA の判定ができるようなので、そのメモです。\n確認するテストデータの作成 iris の偶数行の Sepal.Length が NA なデータを作成\nlibrary(dplyr) i \u0026lt;- seq_len(trunc(nrow(iris) / 2)) * 2 df \u0026lt;- iris df[i, \u0026#34;Sepal.Length\u0026#34;] \u0026lt;- NA 偶数の Speal.Length が NA になってます。\n%in% で Speal.Length が NA の行を得る df %\u0026gt;% filter(Sepal.Length %in% NA) %\u0026gt;% head(10) NA の行が得られました。\nis.na() 使えば？ 単に NA だけ判定したいならその通りで、\ndf %\u0026gt;% filter(is.na(Sepal.Length)) %\u0026gt;% head(10) の方が分かりやすいです。\n%in% にしたのは、変数と比較してフィルターしたい場合に融通が効きそうだったからです。\n例えば、\ntarget_value \u0026lt;- 5.1 df %\u0026gt;% filter(Sepal.Length %in% target_value) %\u0026gt;% head(10) として、得たい行の条件が target_value 次第だったとき、\ntarget_value \u0026lt;- NA だった場合も、同じ filter で対応できるなと。\n以上、そんなお話でした。\n","date":"2022-10-15T11:40:13+09:00","image":"/images/2022/10/flt00.jpg","permalink":"/posts/it/pc/4415/","title":"R ：dplyr::filter で NA の判定"},{"content":"PCの内蔵ストレージにバックアップしていると、容量を圧迫するので外部ストレージにするようにしました。\nMac で行う場合は、こちらを参照ください。\n要点 デフォルトのバックアップ先は\nC:¥Users¥ユーザー名¥Apple¥MobileSync¥Backup バックアップ先をシンボリックリンクで外部ストレージを指定して作成する。 環境 Windows 11 Pro バージョン 22H2\n設定 スタートメニューを右クリックして、メニューから「ターミナル（管理者）」を選ぶ 外部ストレージにバックアップ先のフォルダを作成する\n自分の場合、外部ストレージが D ドライブなので、「D:¥MobileSync¥Backup」にしました。 mkdir D:¥MobileSync¥Backup シンボリックリンクを作成する\n既にバックアップしたことがある場合、「C:¥Users¥ユーザー名¥Apple¥MobileSync¥Backup」を移動するか削除しておく New-Item -ItemType SymbolicLink -Path C:¥Users¥ユーザー名¥Apple¥MobileSync¥Backup -Value D:¥MobileSync¥Backup バックアップ iTunes で iPhone を選択し、\n「このコンピュータ」 「今すぐバックアップ」 でバックアップします。\n","date":"2022-10-08T15:13:45+09:00","image":"/images/2022/10/wipb00.jpg","permalink":"/posts/it/pc/4399/","title":"Windows：iPhone を外部ストレージにバックアップする"},{"content":"Mac 本体にバックアップしていると、容量を圧迫するので外部ストレージにするようにしました。\nWindows で行う場合は、こちらを参照ください。\n要点 デフォルトのバックアップ先は\n/Users/ユーザー名/Library/Application\\ Support/MobileSync/Backup /Users/ユーザー名/Library が隠しフォルダなので、Finder からは\nCommand + Shift + . で表示させないと確認できない。 バックアップ先をシンボリックリンクで外部ストレージを指定して作成する。\nただし、ターミナルからシンボリックリンクを作成する際、予めアクセス許可の設定をしないと、「Operation not permitted」となってしまう。 環境 macOS Monterey バージョン 12.6\n設定 アップルメニューから、システム環境設定 - セキュリティとプライバシー - プライバシー を選ぶ ガギをクリック 「フルディスクアクセス」を選択 「ターミナル」にチェックを入れる\n無ければ「＋」をクリックして「ターミナル」を追加する 外部ストレージにバックアップ先のフォルダを作成する\n自分の場合、外部ストレージ名 「SSD」の 「MobileSync/Backup」にしました。 mkdir /Volumes/SSD/MobileSync/Backup シンボリックリンクを作成する\n既にバックアップしたことがある場合、/Users/ユーザー名/Library/Application\\ Support/MobileSync/Backup を移動するか削除しておく ln -s /Volumes/SSD/MobileSync/Backup /Users/ユーザー名/Library/Application\\ Support/MobileSync/Backup アップルメニューから、システム環境設定 - セキュリティとプライバシー - プライバシー を選び、フルディスクアクセスのターミナルのチェックを外す バックアップ Finder で iPhone を選択し、\n「iPhone 内のすべてのデータをこの Mac にバックアップ」 「今すぐバックアップ」 でバックアップします。\n外部ストレージ バッファロー SSD 外付け 1.0TB を使いました。USB メモリっぽい SSD が出ているんですね。\nType-C 変換アダプタ付きなのが有り難い。\n保護フィルム？が剥ぎにくかったのがイマイチだったぐらいでしょうか。\nバッファロー SSD 外付け 1.0TB USB3.2 (Amazon) Mac mini (Amazon) MacBook Pro 13インチ (Amazon) MacBook Air (Amazon) Mac Fan 2026年5月号 (Amazon)\n","date":"2022-10-01T13:57:32+09:00","image":"/images/2021/05/wf.jpg","permalink":"/posts/it/pc/4365/","title":"Mac：iPhone を外部ストレージにバックアップする"},{"content":"ふるさと納税のワンストップ特例制度で、寄付先の自治体に申請書等の必要書類を返送するんですが、須坂市にふるさと納税した所、スマホ＋マイナンバーカードで完結して楽でした。\n郵送だと、本人確認書類をコピーするとか、一連の作業が地味に面倒臭いんですよね。。\nオンラインで済む自治体は 100 以上ある様なんですが、個別に見つかる感じで、一覧では見つかりませんでした。増えるといいなぁ。\n2022/12/17追記\n「さとふる」で、ワンストップ特例制度の申請手続きがオンラインで完結する「さとふるアプリdeワンストップ申請」サービスが開始しています。\nマイナンバーカードと、スマホアプリのインストールが必要の様です。\nそちらも検討すると良いと思います。\nスマホからの手続き 公的個人認証アプリ「IAM」をダウンロード\niPhone、Android どちらでもOK。 自治体から送られてきた書類にある QR コードの読み込み\n申請のサイトが開くので、\n「個人番号カードをお持ちの方」-「IAM を利用した申請を行う」 寄付情報の確認と修正 IAM アプリでマイナンバーカードを読み取って、電子署名\nマイナンバーカードのパスワードは、数字４桁と、長いパスワードの両方が必要でした。 といった感じでした。\n来年も、オンラインで済む所にしたいな。\n","date":"2022-09-24T16:23:47+09:00","image":"/images/2022/09/furu00.jpg","permalink":"/posts/it/smartphone/4348/","title":"ふるさと納税：ワンストップ特例制度で申請書の返送はスマホで完結"},{"content":"デフォルトの色で良ければ、そのままの方が無難なんですが、カテゴリ値を色で表したい場合があったので、その際のメモです。\nデータに色の列を足して scale_color_identity を使う 例としてデータは iris を使い、iris$Species をカテゴリ値とします。\niris$Speciesは、以下の様な factor です。\nそれぞれのカテゴリ値を、以下の色でプロットするとします。\n実際のデータでは NA があることを想定して、「その他」の色も決めています。\nsetosa : 赤 versicolor : 黄 virginica : 青 その他 : 緑 ということで、対応する factor を定義します。\nfactor にしているのは、凡例を決まった並びにしたい為です。\npalette \u0026lt;- factor( c(\u0026#34;red\u0026#34;, \u0026#34;yellow\u0026#34;, \u0026#34;blue\u0026#34;, \u0026#34;green\u0026#34;), levels = c(\u0026#34;red\u0026#34;, \u0026#34;yellow\u0026#34;, \u0026#34;blue\u0026#34;, \u0026#34;green\u0026#34;) ) データに色の列を追加します。\niris$Species の \u0026ldquo;setosa\u0026rdquo; は NA に置換しました。\n定義はされていても、データには存在しないカテゴリ値という想定です。\n列 \u0026ldquo;iro\u0026rdquo; に、そのプロットの色を設定します。\niris_with_color \u0026lt;- iris %\u0026gt;% mutate(Species = as.character(Species)) %\u0026gt;% mutate(Species = if_else(Species == \u0026#34;setosa\u0026#34;, as.character(NA), Species)) %\u0026gt;% mutate(Species = factor(Species, levels = c(\u0026#34;setosa\u0026#34;, \u0026#34;versicolor\u0026#34;, \u0026#34;virginica\u0026#34;))) %\u0026gt;% mutate(iro = case_when( Species == \u0026#34;setosa\u0026#34; ~ palette[1], Species == \u0026#34;versicolor\u0026#34; ~ palette[2], Species == \u0026#34;virginica\u0026#34; ~ palette[3], T ~ palette[4] )) プロットしてみます。\nggplot( iris_with_color, aes( x = Sepal.Length, y = Sepal.Width, colour = iro )) + geom_point() + scale_color_identity( name = \u0026#34;Species\u0026#34;, labels = c( \u0026#34;red\u0026#34; = \u0026#34;setosa\u0026#34;, \u0026#34;yellow\u0026#34; = \u0026#34;versicolor\u0026#34;, \u0026#34;blue\u0026#34; = \u0026#34;virginica\u0026#34;, \u0026#34;green\u0026#34; = \u0026#34;unknown\u0026#34; ), guide = \u0026#34;legend\u0026#34; ) データが無いので、凡例に \u0026ldquo;setosa\u0026rdquo; がありません。\nデータが無くても、凡例には表記が欲しかったので、他のプロットに上書きされるデータとして混ぜることで実現してみました。\n本来なら Species の種類分のダミーのデータを作るべきですが、以下では \u0026ldquo;setosa\u0026rdquo; だけ足しています。\nもっと他に良い方法はあるんでしょうか。。\nggplot( iris_with_color[1,] %\u0026gt;% mutate( Species = \u0026#34;setosa\u0026#34;, iro = palette[1] ) %\u0026gt;% rbind(iris_with_color), aes( x = Sepal.Length, y = Sepal.Width, colour = iro )) + geom_point() + scale_color_identity( name = \u0026#34;Species\u0026#34;, labels = c( \u0026#34;red\u0026#34; = \u0026#34;setosa\u0026#34;, \u0026#34;yellow\u0026#34; = \u0026#34;versicolor\u0026#34;, \u0026#34;blue\u0026#34; = \u0026#34;virginica\u0026#34;, \u0026#34;green\u0026#34; = \u0026#34;unknown\u0026#34; ), guide = \u0026#34;legend\u0026#34; ) 最終的なコードは以下の通りです。\nlibrary(ggplot2) library(dplyr) palette \u0026lt;- factor( c(\u0026#34;red\u0026#34;, \u0026#34;yellow\u0026#34;, \u0026#34;blue\u0026#34;, \u0026#34;green\u0026#34;), levels = c(\u0026#34;red\u0026#34;, \u0026#34;yellow\u0026#34;, \u0026#34;blue\u0026#34;, \u0026#34;green\u0026#34;) ) iris_with_color \u0026lt;- iris %\u0026gt;% mutate(Species = as.character(Species)) %\u0026gt;% mutate(Species = if_else(Species == \u0026#34;setosa\u0026#34;, as.character(NA), Species)) %\u0026gt;% mutate(Species = factor(Species, levels = c(\u0026#34;setosa\u0026#34;, \u0026#34;versicolor\u0026#34;, \u0026#34;virginica\u0026#34;))) %\u0026gt;% mutate(iro = case_when( Species == \u0026#34;setosa\u0026#34; ~ palette[1], Species == \u0026#34;versicolor\u0026#34; ~ palette[2], Species == \u0026#34;virginica\u0026#34; ~ palette[3], T ~ palette[4] )) ggplot( iris_with_color[1,] %\u0026gt;% mutate( Species = \u0026#34;setosa\u0026#34;, iro = palette[1] ) %\u0026gt;% rbind(iris_with_color), aes( x = Sepal.Length, y = Sepal.Width, colour = iro )) + geom_point() + scale_color_identity( name = \u0026#34;Species\u0026#34;, labels = c( \u0026#34;red\u0026#34; = \u0026#34;setosa\u0026#34;, \u0026#34;yellow\u0026#34; = \u0026#34;versicolor\u0026#34;, \u0026#34;blue\u0026#34; = \u0026#34;virginica\u0026#34;, \u0026#34;green\u0026#34; = \u0026#34;unknown\u0026#34; ), guide = \u0026#34;legend\u0026#34; ) データ分析のためのデータ可視化入門 (Amazon) ","date":"2022-09-17T13:53:19+09:00","image":"/images/2022/09/plotcolor00.jpg","permalink":"/posts/it/pc/4299/","title":"R：ggplot2 でカテゴリ値のプロット色を設定する"},{"content":"自分では思ってもいなかった挙動だったので、ちょっとした衝撃でした。\nやっていたことは、\n引数に文字列の vector を受け取る関数を作成した。 その後、同様の処理内容で引数に文字列を受け取る関数が欲しかった。\nなので、以下を調べ始めた。 引数の型を判定して分岐するか？ ジェネリック関数にするべきなのか？ 結果としては、そのまま使えるんかい、と。\n該当のコードを簡略化すると、以下の様なものでした。\ntest \u0026lt;- function (names) { print(typeof(names)) sapply(seq(names), function (i) { paste0(i, \u0026#34;=\u0026#34;, names[i]) }) } test(c(\u0026#34;hoge\u0026#34;, \u0026#34;foo\u0026#34;, \u0026#34;bar\u0026#34;)) Google Colab で実行した結果は以下の通り。\nnames の型は character と言われますね。\n[1] \u0026#34;character\u0026#34; \u0026#39;1=hoge\u0026#39;\u0026#39;2=foo\u0026#39;\u0026#39;3=bar\u0026#39; 次は文字列を渡してみます。\ntest(\u0026#34;piyo\u0026#34;) 結果は、\n[1] \u0026#34;character\u0026#34; \u0026#39;1=piyo\u0026#39; と、期待通りです。\n疑問に思った部分を実行してみました。\nseq(\u0026#34;piyo\u0026#34;) \u0026#34;piyo\u0026#34;[1] 1 \u0026#39;piyo\u0026#39; 大丈夫なんですね。\n今まで自分が経験してきた言語とは別物で中々興味深い。\n","date":"2022-09-10T17:53:30+09:00","image":"/images/2022/09/rvec.jpg","permalink":"/posts/it/pc/4272/","title":"R：引数に vector を渡しても、型を確認すると vector じゃなかった件"},{"content":"緑内障の早期発見に寄与できるという触れ込みのスマホゲームアプリが公開されていたので試してみました。\n「目の健康状態を判定するゲームアプリ METEOR BLASTER」\n緑内障の心配はまだ先だと思っていますが、視野の状態は少し気になりますね。\n普段の生活で、活発に眼球を動かせている自信はないので。\nゲームを開始する アプリのインストールは不要で、ブラウザ上で動作していました。\n先に挙げたリンク先のページにある QR コードを読み取って飛ぶのが無難だと思いますが、ゲームのページの URL は現時点では以下でした。\nMETEOR BLASTER\nスマホと謳われていましたが、iPad でやってみました。\nちなみに、Mac の Safari と Chrome からはできませんでした。\nゲーム内でタップする場所が、クリックでは反応してくれない感じです。\nおそらく Windows でも同様でしょうか。\nゲームをプレイする 指示に従って、左→右→左→右 と片目ずつ進めて計５分程度でした。\nプレイ内容はシンプルです。\n隕石が画面中央に入ったら右下の「SHOOT」をタップ 光（白い点）が画面内に現れたら左下の「CAPTURE」をタップ １巡目の左右をプレイしているときは平気でしたが、２巡目は見づらく感じました。\n疲れが出てきているのか、表示が少し変わったのかどうか。\n白い点にイマイチ反応できてない気がします。。\n結果表示 自信は右の方があったんですが、結果はそうでもない模様。\n後半になる程、集中できていないかもしれません。\n緑内障が疑われるときは、どういう結果になるんでしょうね。\n","date":"2022-09-03T15:26:58+09:00","image":"/images/2022/09/mb00.jpg","permalink":"/posts/it/smartphone/4232/","title":"目の健康：視野の状態を簡易判定してみた"},{"content":"以前、体重と体脂肪率を入力するショートカットを作成しましたが、今回は血圧です。\nショートカットを作成するのは、標準アプリの「ヘルスケア」から入力するのが手間だからです。\nオムロン 上腕式血圧計 プレミアム19シリーズ HCR-7601T (Amazon) 記録がどのような感じになるか 例を動画にしてみました。\nhttps://youtube.com/shorts/a6BzhMvz6zY\nショートカットを作成した動画 作成手順を動画にしました。\nhttps://youtu.be/f5-8aUjhAcw\n環境 iOS 15.6\n血圧を記録する「ショートカット」の作成手順 最後に操作した動画を載せていますので、良く分からない場合は参考にしてください。\n１．「ショートカット」アプリを起動する。\n２．「＋」をタップする。\n３．ショートカットに名前を付け、「アクションを追加」をタップする。\n４．「スクリプティング」をタップする。\n５．「入力を要求」をタップする。\n６．「プロンプト」に「最高血圧は？」、「テキスト」を「数字」にして、小数点不可、マイナス不可にする。\n７．「変数を設定」をタップする。\n８．「変数名」に「最高血圧」、「指定入力」はそのまま。\n９．「入力を要求」をタップする。\n１０．「プロンプト」に「最低血圧は？」、「テキスト」を「数字」にして、小数点不可、マイナス不可にする。\n１１．「変数を設定」をタップする。\n１２．「変数名」に「最高血圧」、「指定入力」はそのまま。\n１３．「ヘルスケア」をタップする。\n１４．「ヘルスケアサンプルを記録」をタップする。\n１５．「血圧」をタップする。\n１６．アクセス権が無いと言われた場合は許可する。\n１７．「最高血圧」「最低血圧」の書き込みを許可する。\n１８．血圧の最高と最低、単位を設定し、日付を「現在の日付」にする。\n１９．以上でショートカットの作成は完了です。\nMac Fan 2026年5月号 (Amazon) ","date":"2022-08-27T16:04:47+09:00","image":"/images/2022/08/recbp.jpg","permalink":"/posts/it/smartphone/4158/","title":"iPhone に血圧を記録する"},{"content":"スマホの通信・通話代を安く済ませる為に、私は格安SIMの「OCN モバイル ONE」を使用しています。\nほとんど電話を掛けないのでオプションは付けていませんが、知人のスマホ購入をお手伝いした際は「OCN モバイル ONE」にして暫く使ってもらい、利用状況から「10 分かけ放題」を付けました。\n「OCNでんわ かけ放題オプション」を付ける目安のメモを残します。\n毎月の通話料を確認する OCN のサイト、または「OCN アプリ」をスマホにインストールして通話料を確認します。\n自分の場合、OCN アプリで確認してみました。\n料金内訳が「OCN電話通話料（国内）」の所です。はい、全然掛けて無いですね。\n何ヶ月分か遡って確認して、毎月大体いくらぐらい使っているか見ておきます。\nかけ放題オプションを検討する 毎月、通話料が 850 円未満で済んでいる。\n→ オプションの追加不要。\n通話料が 30 秒 で 10 円（税込み 11 円）なので、 毎月の通話が 170 分以内（毎日約 5 分掛ける程度）なら、オプション無しでしょう。\n毎月、通話料に 850 円以上掛けている。\n1回の通話は 10 分以内に終わる。\n→「10 分かけ放題」\n電話を掛ける先が3つ以内に収まる。\n→「トップ 3 かけ放題」\nどちらにするかはその人次第ですが、1回の通話が短い人は迷わず「10 分かけ放題」で済むでしょう。\n10 分以内に収まるか微妙な場合、通話時間を知らせてくれるアプリを使うのも手でしょうか。（スマホ、通話、タイマー 辺りでググると、その手のアプリが見つかりますね。）\nオプションを付けて使ってみて 1,300 円以上になってしまうようなら、オプション無しの方が良いかもしれません。\n毎月、通話料に 1,300 円以上掛けている。\n「10 分かけ放題」か「トップ 3 かけ放題」を検討する。\nそれでも 1,300 円以上掛かるなら、「完全かけ放題」\nOCN モバイル ONE より安く運用できそうな格安SIM会社もあるので、特別おすすめな感じでもないのですが、もし契約する場合はエントリーパッケージを利用すると、初期手数料の 3,300 円が不要（エントリーパッケージは 300 円なので、3,000 円分お得。）になります。\nOCN モバイル ONE エントリーパッケージ (Amazon) ","date":"2022-08-20T12:52:15+09:00","image":"/images/2021/04/smartphone.jpg","permalink":"/posts/it/smartphone/4094/","title":"OCN モバイル ONE で、かけ放題オプションを検討する"},{"content":"チャンクに書いたコードから、Markdown を出力したい場合、\nチャックのオプションで results=\u0026lsquo;asis\u0026rsquo; を設定する knitr::asis_output を使う で行うことができます。\nresults=\u0026lsquo;asis\u0026rsquo; した場合 例として、見出し（行頭を # で始める）を出力してみます。\n```{r results=\u0026#39;asis\u0026#39;} cat(\u0026#34;## top level : chunk\\n\u0026#34;) ``` ```{r results=\u0026#39;asis\u0026#39;} for (i in 1:3) { cat(\u0026#34;## loop : chunk\u0026#34;, i, \u0026#34;\\n\u0026#34;) } ``` 結果は、\nknitr::asis_output した場合 この場合、ループ内では動作しないことを認識しておく必要があります。\n```{r} knitr::asis_output(\u0026#34;## top level : asis_output\\n\u0026#34;) ``` ```{r} for (i in 1:3) { knitr::asis_output(paste0(\u0026#34;## loop : asis_output\u0026#34;, i, \u0026#34;\\n\u0026#34;)) } ``` 結果は、\nループ内の出力は、そのまま出力されますね。\n","date":"2022-08-13T18:07:12+09:00","image":"/images/2022/08/rmd00.jpg","permalink":"/posts/it/pc/4080/","title":"R Markdown：コードから Markdown を出力するには"},{"content":"コマンドパレットからコマンドを選択して使う場合、頻繁に使う機能にはショートカットキーを割り当てた方が作業が捗ります。\nショートカットキーの設定を表示する 左下の「管理」のアイコンをクリックして「キーボード ショートカット」を選択します。\nコマンドに割り当てられているショートカットキーが表示されます。\nコマンドにショートカットキーを割り当てる 以前紹介した「Paste JSON as Code」にキーを割り当てたいと思います。\nまずは対象のコマンドを探す為に絞り込みます。\n・・・既に割り当てられていました。この拡張機能の既定値なんでしょうか。\nペンのアイコンをクリックすると、割り当てたいキーが設定できます。\n使用頻度の高いコマンドは、ショートカットが既に割り当てられていないか確認して、あれば使う、無ければ設定すると良さそうです。\n","date":"2022-08-06T16:02:16+09:00","image":"/images/2022/08/shortcut00.jpg","permalink":"/posts/it/pc/4054/","title":"VSCode：コマンドパレットから頻繁に使うものにはショートカットキーを割り当てる"},{"content":"iOS 5.6 と iPadOS 5.6 がリリースされていますね。\n手持ちの iPhone や iPad をアップデートしましたが、その中の iPhone 6s で初めて見る画面が表示されてちょっとビビりました。（本投稿のアイキャッチ画像）\nこの iPhone 自体、中身が消えても困らないような用途でしか使っていなかったので、バックアップもしてないんですよね。バッテリーもヘタってますし、少し前のバージョンぐらいから突然再起動することもあったので、そろそろ終了かなと思ったりもしました。\nまぁ、初体験な状況なので、表示された URL を参考に復元してみました。\nPC に接続して iPhone を再起動 Mac に繋いで iPhone を再起動（サイドボタンとホームボタンを同時に押し続ける）しましたが、状況が変わらなかったので Windows で試してみました。\nPC 側で iTunes を起動してから iPhone を再起動してみると、「復元が必要」な旨のメッセージが表示されたので、指示に従って進めれば復元が完了しました。\nMac でも iTunes の役割をするものを起動しておけば良かったのかな？と思いましたが、何になるんですかね。\n最新の macOS だと iTunes は無くて Finder に統合されている認識だったんですが、どうなんでしょうか。\niPhone の再設定 バージョンアップが正常に終わらなかった iOS 5.6 に復元したとして、起動できるのか疑問でしたが無事起動しましたし、後は指示に従って設定を進めれば OK でした。\n少しヒヤヒヤしたのは「Apple ID」ですね。\nこの iPhone 用の Apple ID を割り当てていたのですが、そのメモに自信がありません（汗）\n暗証番号や指紋認証で進められるステップもありましたが、最終的には Apple ID とそのパスワードが分からないと使えるような状態にはできない感じでした。\nというか、できてしまったら問題ですね。\n","date":"2022-07-30T13:11:59+09:00","image":"/images/2022/07/iprs01.jpg","permalink":"/posts/it/smartphone/3995/","title":"iOS のアップデートが正常に終わらず復元する羽目になった件"},{"content":"グラフを ggplot2 で作成して、基準値などの特定な値に線を引くときに geom_segment を使いました。\n軸が数値の場合は良いのですが、日付のときに躓いて解消したときのメモです。\n準備 ライブラリの読み込みと、サンプル用のデータを生成しておきます。\nlibrary(tidyverse) set.seed(1234) data \u0026lt;- data.frame( date = seq( as.Date(\u0026#34;2021-01-01\u0026#34;), length = 12, by = \u0026#34;months\u0026#34; ), y = rnorm(12, 20, 5) ) 日付の軸方向に線を引く 日付の軸に -Inf、Inf を直接指定すると\nError: Invalid input: date_trans works with objects of class Date only となるので、次の様に structure で Date クラスにして使います。\nggplot(data = data) + geom_line( aes( x = date, y = y ) ) + geom_segment( aes( x = structure(-Inf, class = \u0026#34;Date\u0026#34;), xend = structure(Inf, class = \u0026#34;Date\u0026#34;), y = 18, yend = 18 ), color = \u0026#34;red\u0026#34; ) 追記：geom_hline を使う 今回の様な用途なら、横線は geom_hline 、縦線は geom_vline の方が良さそうです。\n以下のようにしても、同様のグラフになります。\nggplot(data = data) + geom_line( aes( x = date, y = y ) ) + geom_hline( aes( yintercept = 18 ), color = \u0026#34;red\u0026#34; ) データ分析のためのデータ可視化入門 (Amazon) ","date":"2022-07-23T13:41:36+09:00","image":"/images/2022/07/gsinf00.jpg","permalink":"/posts/it/pc/3972/","title":"R : geom_segment でグラフの端まで線を引くには"},{"content":"結論としては、dplyr::pull または unlist を使うことで vector にできました。\nやりたいこと 例えば次のように１列取得すると、\nret \u0026lt;- head(iris) %\u0026gt;% dplyr::select(Sepal.Length) ret には次のように１列の data.frame が返されます。\n欲しいのは、以下を実行したときの様に vector だったのですが、パイプ演算子で完結させたい。という話です。\ntmp \u0026lt;- head(iris) %\u0026gt;% dplyr::select(Sepal.Length) ret \u0026lt;- as.vector(tmp$Sepal.Length) # ret の中身は # c(5.1, 4.9, 4.7, 4.6, 5.0, 5.4) dplyr::pull を使う ret \u0026lt;- head(iris) %\u0026gt;% dplyr::select(Sepal.Length) %\u0026gt;% dplyr::pull() dplyr にやりたいことそのものな pull がありました。\nunlist を使う ret \u0026lt;- head(iris) %\u0026gt;% dplyr::select(Sepal.Length) %\u0026gt;% unlist(use.names = FALSE) base の unlist も使えました。\n実際に使うなら、dplyr::pull の方でしょうか。\n","date":"2022-07-16T12:32:22+09:00","image":"/images/2022/07/pipe.jpg","permalink":"/posts/it/pc/3942/","title":"R : dplyr で結果が１列のとき vector で返すには"},{"content":"相関係数を算出する為に cor.test を使っていた所、「not enough finite observations」が発生しました。\nデータの数が少ないことが原因の様ですが対処の為に調べてみました。\n結果として必要なデータ数は method によって次の通りでした。\npearson は 3 つ以上 kendall か spearman は 2 つ以上 NA 除外後、最低でも上記のデータ数が残るもので算出すれば良さそうです。\n除外の例 R のサンプルデータ iris で試します。\niris 自体はデータが揃っているので、エラーになるダミーのデータを追加してみます。\niris_dummy \u0026lt;- iris iris_dummy$Species \u0026lt;- as.character(iris_dummy$Species) iris_dummy \u0026lt;- rbind(iris_dummy, list(1, 1, 1, 1, \u0026#34;japonica\u0026#34;)) エラーになることを確認します。\nlibrary(tidyverse) iris_dummy %\u0026gt;% dplyr::group_by(Species) %\u0026gt;% dplyr::summarise(cor = cor.test(Sepal.Length, Petal.Length)$estimate) Error in `dplyr::summarise()`: ! Problem while computing `cor = cor.test(Sepal.Length, Petal.Length)$estimate`. ℹ The error occurred in group 1: Species = \u0026#34;japonica\u0026#34;. Caused by error in `cor.test.default()`: ! not enough finite observations 除外してみます。\niris_dummy %\u0026gt;% na.omit() %\u0026gt;% dplyr::group_by(Species) %\u0026gt;% dplyr::summarise(count = n()) %\u0026gt;% dplyr::filter(count \u0026gt;= 2) %\u0026gt;% dplyr::inner_join(iris_dummy, by = c(\u0026#34;Species\u0026#34;)) %\u0026gt;% dplyr::group_by(Species) %\u0026gt;% dplyr::summarise(cor = cor.test(Sepal.Length, Petal.Length)$estimate) A tibble: 3 × 2 Species\tcor \u0026lt;chr\u0026gt;\t\u0026lt;dbl\u0026gt; setosa\t0.2671758 versicolor\t0.7540490 virginica\t0.8642247 データ数が足りない japonica が除外できました。\n参考：必要データ数の確認 必要なデータ数は、cor.test のソースコードからのものです。\nif(method == \u0026#34;pearson\u0026#34;) { if(n \u0026lt; 3L) stop(\u0026#34;not enough finite observations\u0026#34;) （中略） else { if(n \u0026lt; 2) stop(\u0026#34;not enough finite observations\u0026#34;) ","date":"2022-07-09T13:40:27+09:00","image":"/images/2022/07/calc.jpg","permalink":"/posts/it/pc/3915/","title":"R : cor.test で \"not enough finite observations\" となったときの対処"},{"content":"Mac に Visual Studio Code （以下、VSCode）をインストール後、ターミナルから code コマンドを実行しても、\nzsh: command not found: code となってしまう。\n解消するには VSCode でコマンドパレットを開いて（Shift + Command + P）、\n「Shell Command: Install \u0026lsquo;code\u0026rsquo; command in PATH」\nを選択すれば良い。\n何が行われるのか確認したところ、シェルスクリプトが作成されていました。\n$ which code /usr/local/bin/code $ cat /usr/local/bin/code #!/usr/bin/env bash # # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. （以下略） また、コマンドで開く利点として、ターミナルのカレントディレクトリを VSCode 開きたい場合、\ncode . で開くことができ、VSCode を開いてからフォルダーを選ぶよりは少し楽だと思います。\nVisual Studio Code実践ガイド (Amazon) Visual Studio Codeデバッグ技術 (Amazon) Docker Desktop for Windows/Mac と VSCode でつくるクリーンな開発環境構築入門 (Amazon)\n","date":"2022-07-02T16:02:41+09:00","image":"/images/2021/08/json.jpg","permalink":"/posts/it/pc/3897/","title":"Mac：code コマンドで VSCode を開くには"},{"content":"Git との連携に欠かせない GitLens 。個人的にはソースコード上に色々表示され過ぎに感じるんですが、非表示にできるようなので設定を見直しました。\n設定項目が 218 個（v12.1.1時点）もあって個々に見るのは辛いので、「GitLens Setting editor」から設定するのが良さそうです。\nGitLens Setting editor を開く 「GitLens Setting editor」を開くには、コマンドパレットから「GitLens：Open Settings」を選択します。コマンドパレットは、\nWindows なら Shift + Ctrl + P Mac なら Shift + Command + P で表示されるので、「GitLens：Open Settings」を選択します。\n何文字か入力してから探すと良いです。\n設定を変更する 後は自分の好みで変更するだけです。以降は自分の設定を紹介します。\n設定を反映する範囲を「User」か「Workspace」か選択できますが、自分は「User」で自身が使う全体の設定にしています。\nまず、開いたソースコード上には色々表示されたく無いので、Blame などは非表示にしています。\nBlame あり\nBlame なし\nなので、以下のチェックボックスは Off にしています。\nCurrent Line Blame Git CodeLens Hovers 後は日時の書式を、分かりやすい表記に変えています。\n「6 Years ago」→「2016/11/13 05:41:00」\n「7 25th, 2018 7:18 夜」→「2018/07/25 19:18:00」\nとかですね。\nCommits view Commit description format : ${author, }${date} Stashes view Stash description format : ${stashOnRef, }${date} File Blame Annotation format : ${message|50?} ${date} Status Bar Blame Annotation format: ${author}, ${date}${\u0026rsquo; via \u0026lsquo;pullRequest} Dates \u0026amp; Times Date format : YYYY/MM/DD HH:mm:ss Time format : HH:mm:ss editor なら個々の設定項目を追うより大分楽ですね。\nVisual Studio Code実践ガイド (Amazon) Visual Studio Codeデバッグ技術 (Amazon) Docker Desktop for Windows/Mac と VSCode でつくるクリーンな開発環境構築入門 (Amazon)\n","date":"2022-06-25T08:33:45+09:00","image":"/images/2022/06/gl00.jpg","permalink":"/posts/it/pc/3832/","title":"GitLens を好みの設定に変更する"},{"content":"インストールの install.packages(\u0026quot;geojsonio\u0026quot;) で次のように失敗しました。\nInstalling package into ‘/usr/local/lib/R/site-library’ (as ‘lib’ is unspecified) also installing the dependencies ‘proxy’, ‘e1071’, ‘wk’, ‘triebeard’, ‘classInt’, ‘Rcpp’, ‘s2’, ‘units’, ‘geometries’, ‘jsonify’, ‘rapidjsonr’, ‘sfheaders’, ‘urltools’, ‘httpcode’, ‘protolite’, ‘lazyeval’, ‘sp’, ‘sf’, ‘geojsonsf’, ‘rgeos’, ‘crul’, ‘maptools’, ‘V8’, ‘geojson’, ‘jqr’ Warning message in install.packages(\u0026#34;geojsonio\u0026#34;): “installation of package ‘units’ had non-zero exit status” Warning message in install.packages(\u0026#34;geojsonio\u0026#34;): “installation of package ‘protolite’ had non-zero exit status” Warning message in install.packages(\u0026#34;geojsonio\u0026#34;): “installation of package ‘jqr’ had non-zero exit status” Warning message in install.packages(\u0026#34;geojsonio\u0026#34;): “installation of package ‘geojson’ had non-zero exit status” Warning message in install.packages(\u0026#34;geojsonio\u0026#34;): “installation of package ‘sf’ had non-zero exit status” Warning message in install.packages(\u0026#34;geojsonio\u0026#34;): “installation of package ‘geojsonio’ had non-zero exit status” 必要なパッケージを先にインストールする 依存パッケージが必要としているライブラリをインストールします。\nsf、protolite、jqr パッケージが必要としているライブラリが Google Colab に無いようです。\n# https://github.com/r-spatial/sf/issues/1572#issuecomment-758858154 system(\u0026#39;sudo add-apt-repository ppa:ubuntugis/ubuntugis-unstable\u0026#39;) system(\u0026#39;sudo apt-get update\u0026#39;) system(\u0026#39;sudo apt-get install libudunits2-dev libgdal-dev libgeos-dev libproj-dev\u0026#39;) # protolite system(\u0026#39;sudo apt-get install libprotobuf-dev protobuf-compiler\u0026#39;) # jqr system(\u0026#39;sudo apt-get install libjq-dev\u0026#39;) その後、install.packages(\u0026quot;geojsonio\u0026quot;) すれば、無事にインストールできました。\nこれで、sf で読み込んだデータを何かしたときに表示される ERROR while rich displaying an object: Error in loadNamespace(x): there is no package called ‘geojsonio’ が解消されました。\n","date":"2022-06-18T16:02:12+09:00","image":"/images/2022/06/crystal.jpg","permalink":"/posts/it/pc/3789/","title":"Google Colab に geojsonio をインストールするには"},{"content":"\ne-Stat 2015年国勢調査 小地域から長野県を抽出\nソースコードと解説 実行した環境は Google Colaboratory です。\nsystem(\u0026#34;apt-get install -y fonts-noto-cjk\u0026#34;) 日本語フォントをインストールします。\nインストール後、「ランタイム」-「ランタイムを再起動」を行わないと有効にならないっぽいです。\nlibrary(tidyverse) theme_update(text=element_text(family=\u0026#34;Noto Sans Mono CJK JP\u0026#34;)) ggplot2 等のライブラリの読み込み。\nggplot2 で使うデフォルトのフォントを Noto Sans Japanese に設定。\n# トップページ 地図で見る統計(統計GIS) 境界データダウンロード # 小地域 / 国勢調査 / 2015年 / 小地域（町丁・字等別）（JGD2000） / 世界測地系緯度経度・Shapefile / 長野県 # 20000 長野県全域 2018-05-14 system(\u0026#39;curl -o A002005212015DDSWC20.zip -JLO \u0026#34;https://www.e-stat.go.jp/gis/statmap-search/data?dlserveyId=A002005212015\u0026amp;code=20\u0026amp;coordSys=1\u0026amp;format=shape\u0026amp;downloadType=5\u0026amp;datum=2000\u0026#34;\u0026#39;) system(\u0026#34;unzip A002005212015DDSWC20.zip\u0026#34;) e-Stat（日本の統計が閲覧できる政府統計ポータルサイト）からデータをダウンロードして解凍します。\n前回は Google Drive を使いましたが、今回は直接ダウンロードしてみました。\ncurl の -o でファイル名を指定しているのは、-JLO で実行しても URL の名前になってしまった為です。\nレスポンスヘッダーの Content-Disposition には\n「attachment; filename*=UTF-8\u0026rsquo;\u0026lsquo;A002005212015DDSWC20.zip」\nとあるんですけどね。\n# https://github.com/r-spatial/sf/issues/1572#issuecomment-758858154 system(\u0026#39;sudo add-apt-repository ppa:ubuntugis/ubuntugis-unstable\u0026#39;) system(\u0026#39;sudo apt-get update\u0026#39;) system(\u0026#39;sudo apt-get install libudunits2-dev libgdal-dev libgeos-dev libproj-dev\u0026#39;) install.packages(\u0026#39;sf\u0026#39;) library(sf) sf パッケージをインストールして読み込みます。少し時間が掛かります。\nnagano_pref \u0026lt;- read_sf(\u0026#34;/content/h27ka20.shp\u0026#34;) ggplot(nagano_pref) + geom_sf() Shapefile を読み込んで、どんな図なのか見てみます。\nはい。長野県ですね。\nggplot(nagano_pref) + geom_sf(aes(fill = JINKO)) 人口もありますね。もう少し加工してみます。\nggplot(nagano_pref) + geom_sf(aes(fill = JINKO)) + geom_sf_label(aes(label = S_NAME)) + scale_fill_gradient(low = \u0026#34;#ffeeee\u0026#34;, high = \u0026#34;#ff6e6e\u0026#34;) + labs(x = \u0026#34;経度\u0026#34;, y = \u0026#34;緯度\u0026#34;, fill = \u0026#34;人口\u0026#34;) + theme_minimal() 町丁・字等の表示は無謀でした。\n市町村ぐらいにしてみます。\nsf::sf_use_s2(FALSE) nagano_pref2 \u0026lt;- nagano_pref %\u0026gt;% dplyr::group_by(CITY_NAME) %\u0026gt;% dplyr::summarise(JINKO = sum(JINKO), .groups = \u0026#34;drop\u0026#34;) nagano_pref2$CITY_NAME \u0026lt;- as.factor(nagano_pref2$CITY_NAME) sf::sf_use_s2(FALSE) は、エラー「Error in s2_geography_from_wkb(x, oriented = oriented, check = check): Evaluation error: Found 2 features with invalid spherical geometry.」を回避する為に指定しています。\nggplot(data = nagano_pref2) + geom_sf(aes(fill = JINKO)) + geom_sf_label(aes(label = CITY_NAME)) + scale_fill_gradient(low = \u0026#34;#ffeeee\u0026#34;, high = \u0026#34;#ff6e6e\u0026#34;) + labs(x = \u0026#34;経度\u0026#34;, y = \u0026#34;緯度\u0026#34;, fill = \u0026#39;人口\u0026#39;) + theme_minimal() 名称は市町村ぐらいが良さそうです。\nggplot() + geom_sf(data = nagano_pref, aes(fill = JINKO)) + scale_fill_gradient(low = \u0026#34;#ffeeee\u0026#34;, high = \u0026#34;#ff6e6e\u0026#34;) + geom_sf(data = nagano_pref2, alpha = 0) + geom_sf_label(data = nagano_pref2, aes(label = CITY_NAME)) + labs(x = \u0026#34;経度\u0026#34;, y = \u0026#34;緯度\u0026#34;, fill = \u0026#39;人口\u0026#39;) + theme_minimal() 町丁・字等の人口の上に、市町村の名前を乗せてみました。\n良さそうなので、色々微調整してみます。\n以下で、本投稿にある最初のマップになります。\noptions(repr.plot.width = 32, repr.plot.height = 32) ggplot() + geom_sf(data = nagano_pref, aes(fill = JINKO), size = 0.07) + scale_fill_gradient(low = \u0026#34;#ffeeee\u0026#34;, high = \u0026#34;#ff6e6e\u0026#34;) + geom_sf(data = nagano_pref2, alpha = 0, color = \u0026#34;#333333\u0026#34;, size = 0.2) + geom_sf_label(data = nagano_pref2, aes(label = CITY_NAME)) + labs(x = \u0026#34;経度\u0026#34;, y = \u0026#34;緯度\u0026#34;, fill = \u0026#39;人口\u0026#39;) + theme_minimal() データ分析のためのデータ可視化入門 (Amazon) ","date":"2022-06-11T12:47:43+09:00","image":"/images/2022/05/Population.jpg","permalink":"/posts/it/pc/3739/","title":"2015年長野県の人口を ggplot2 でマップにしてみた"},{"content":"\n2022年5月長野市地区別人口\n使用したデータは以下になります。\n長野市 行政地図情報 で公開されていた「統計マップ」（令和３年４月１日） 「長野市　令和４年地区別年齢別人口」（2022/05/20 更新） ソースコード 実行した環境は Google Colab です。\nGoogle Drive からファイル取得が可能と知ったので、統計マップの 365179.zip は予め格納しています。\nlibrary(tidyverse) # https://github.com/r-spatial/sf/issues/1572#issuecomment-758858154 system(\u0026#39;sudo add-apt-repository ppa:ubuntugis/ubuntugis-unstable\u0026#39;) system(\u0026#39;sudo apt-get update\u0026#39;) system(\u0026#39;sudo apt-get install libudunits2-dev libgdal-dev libgeos-dev libproj-dev\u0026#39;) install.packages(\u0026#39;sf\u0026#39;) library(sf) 必要なパッケージの読み込みです。\nsf パッケージを使う為に、issue を参考に apt-get でパッケージをインストールしています。\nsystem(\u0026#34;apt-get install -y fonts-noto-cjk\u0026#34;) systemfonts::system_fonts() theme_update(text=element_text(family=\u0026#34;Noto Sans Mono CJK JP\u0026#34;)) 日本語フォントを取得します。グラフの文字化けを回避する為です。\ninstall.packages(\u0026#34;R.utils\u0026#34;) library(R.utils) library(httr) library(googledrive) reassignInPackage(\u0026#34;is_interactive\u0026#34;, pkgName = \u0026#34;httr\u0026#34;, function() { return(TRUE) }) options(rlang_interactive=TRUE) drive_auth(use_oob = TRUE, cache = FALSE) Google Drive を使う為に認証します。\n以下のように指示されるので、URL を開いて認証コードを取得し、そのコードを入力します。\ndrive_ls(\u0026#34;Colab Notebooks\u0026#34;) %\u0026gt;% select(name) Google Drive が使えるか、試しに「Colab Notebooks」フォルダのファイル一覧を取得してみます。\n# https://www.city.nagano.nagano.jp/soshiki/jouhou/114514.html drive_get(\u0026#34;Colab Notebooks/365179.zip\u0026#34;) %\u0026gt;% drive_download(overwrite = TRUE) Google Drive に保存した統計マップの zip ファイルを Google Colab にダウンロードします。\nsystem(\u0026#34;unzip -O sjis 365179.zip\u0026#34;) nagano_city \u0026lt;- read_sf(\u0026#34;/content/支所所管区域（参考図）/R03支所所管区域.shp\u0026#34;) zip ファイルを解凍し、シェイプファイルを読み込みます。\n取得したデータで以下のように描画できます。\noptions(repr.plot.width = 16, repr.plot.height = 16) ggplot(nagano_city) + geom_sf() + geom_sf_label(aes(label = 地区名)) + labs(x = \u0026#34;経度\u0026#34;, y = \u0026#34;緯度\u0026#34;) + theme_minimal() + theme(text=element_text(family=\u0026#34;Noto Sans CJK JP\u0026#34;)) 人口のデータも含まれていたのですが、昨年の数なので更新します。\ndata3 \u0026lt;- data2 %\u0026gt;% dplyr::group_by(Area) %\u0026gt;% dplyr::summarise(Population=sum(Population)) %\u0026gt;% dplyr::select(Area, Population) data3$Area \u0026lt;- as.character(data3$Area) data2 は「長野市　令和４年地区別年齢別人口」が入っています。\n取得方法は前回と同様です。\ndata3 に地区別の人口を集計します。\nnagano_city202205 \u0026lt;- data3 %\u0026gt;% dplyr::inner_join(nagano_city, by = c(\u0026#34;Area\u0026#34; = \u0026#34;地区名\u0026#34;)) %\u0026gt;% dplyr::select(Area, Population, geometry) %\u0026gt;% st_as_sf() 人口とマップのデータを結合します。\noptions(repr.plot.width = 16, repr.plot.height = 16) ggplot(nagano_city202205) + geom_sf(aes(fill = Population)) + geom_sf_label(aes(label = Area)) + scale_fill_gradient(low = \u0026#34;#ffeeee\u0026#34;, high = \u0026#34;#ff6e6e\u0026#34;) + labs(x = \u0026#34;経度\u0026#34;, y = \u0026#34;緯度\u0026#34;) + theme_minimal() + theme(text=element_text(family=\u0026#34;Noto Sans Mono CJK JP\u0026#34;)) ggsave(\u0026#34;2022_nagano-shi_Population_Map.png\u0026#34;, width = 16, height = 16, dpi = 96) 本投稿にある最初のマップになります。\n更に、昨年との差でマップにしてみます。\nnagano_city2205_2104 \u0026lt;- data3 %\u0026gt;% dplyr::inner_join(nagano_city, by = c(\u0026#34;Area\u0026#34; = \u0026#34;地区名\u0026#34;)) %\u0026gt;% dplyr::mutate(diff = Population - 人口_計) %\u0026gt;% dplyr::select(Area, diff, geometry) %\u0026gt;% st_as_sf() options(repr.plot.width = 16, repr.plot.height = 16) ggplot(nagano_city2205_2104) + geom_sf(aes(fill = diff)) + geom_sf_label(aes(label = Area)) + scale_fill_gradient2(low = \u0026#34;#6e6eff\u0026#34;, mid = \u0026#34;#ffffff\u0026#34;, high = \u0026#34;#ff6e6e\u0026#34;) + labs(x = \u0026#34;経度\u0026#34;, y = \u0026#34;緯度\u0026#34;) + theme_minimal() + theme(text=element_text(family=\u0026#34;Noto Sans CJK JP\u0026#34;)) ggsave(\u0026#34;2022_nagano-shi_Population_Map1.png\u0026#34;, width = 16, height = 16, dpi = 96) 長野市地区別人口増減（2021年4月〜2022年5月）\nデータ分析のためのデータ可視化入門 (Amazon) ","date":"2022-06-04T18:06:34+09:00","image":"/images/2022/05/Population.jpg","permalink":"/posts/it/pc/3686/","title":"2022年度長野市の人口を gglot2 でマップにしてみた"},{"content":"R の gglot2 を使用して人口のグラフを作成してみました。\n使用したデータは、長野市がクリエイティブ・コモンズ・ライセンス表示4.0国際（CC-BY4.0）ライセンスの下に公開している「長野市　令和４年地区別年齢別人口」（2022/05/20 更新）です。\nサイト上だと画像が小さくて見づらいです。。\n画像を保存するか、ブラウザの別タブで開くなどをお試しください。\n全体 地区別 ソースコード 実行した環境は Google Colab です。\nlibrary(tidyverse) chikubetsu_nenreibetsu_202205 \u0026lt;- read.delim(f \u0026lt;- file(\u0026#34;http://linkdata.org/api/1/rdf1s9117i/chikubetsu_nenreibetsu_202205_R.txt\u0026#34;, open=\u0026#34;r\u0026#34;, encoding=\u0026#34;UTF-8\u0026#34;), header=T) close(f) data \u0026lt;- chikubetsu_nenreibetsu_202205[, -1] 必要なライブラリと、人口データの読み込みです。\nデータの1列目は、以下の通り不要なので除外しています。\narea_levels = data[[\u0026#34;地区名\u0026#34;]] gender \u0026lt;- c(\u0026#34;男\u0026#34;,\u0026#34;女\u0026#34;) cols \u0026lt;- data %\u0026gt;% colnames() %\u0026gt;% as.array() ages \u0026lt;- cols[str_detect(cols, pattern=\u0026#34;^X\u0026#34;)] %\u0026gt;% str_extract(pattern=\u0026#34;[0-9]+.+\u0026#34;) %\u0026gt;% str_replace(pattern=\u0026#34;(男|女)\u0026#34;, \u0026#34;\u0026#34;) %\u0026gt;% unique() data2 \u0026lt;- data.frame() for (r in 1:nrow(data)) { for (g in 1:length(gender)) { for (a in 1:length(ages)) { col \u0026lt;- sprintf(\u0026#34;X%s%s\u0026#34;, ages[a], gender[g]) age \u0026lt;- as.numeric(str_replace(ages[a], \u0026#34;歳.*\u0026#34;, \u0026#34;\u0026#34;)) row \u0026lt;- c( data[r, \u0026#34;地区名\u0026#34;], gender[g], age, data[r, col] ) data2 \u0026lt;- rbind(data2, row) } } } colnames(data2) \u0026lt;- c(\u0026#34;Area\u0026#34;, \u0026#34;Gender\u0026#34;, \u0026#34;Age\u0026#34;, \u0026#34;Population\u0026#34;) data2$Age \u0026lt;- as.numeric(data2$Age) data2$Population \u0026lt;- as.numeric(data2$Population) data2 \u0026lt;- data2 %\u0026gt;% mutate(Area = factor(Area, levels = area_levels)) データの形式を、以下のように変換します。グラフのデータとして扱いやすくする為です。\nsystem(\u0026#34;apt-get install -y fonts-noto-cjk\u0026#34;) # systemfonts::system_fonts() # theme_update(text=element_text(family=\u0026#34;Noto Sans Mono CJK JP\u0026#34;)) グラフの日本語が化けないように、フォントをインストールします。\nsystemfonts::system_fonts() で使えるフォントが分かります。\ntheme_update でデフォルトのフォントを設定したのですが、効いたり効かなかったりしたので、グラフの所で指定するようにしました。\noptions(repr.plot.width=24, repr.plot.height=9) ggplot(data2, aes(x = as.factor(Age), y = Population, fill = Gender)) + geom_col(position = \u0026#34;dodge\u0026#34;) + labs(title = \u0026#34;長野市　令和４年年齢別人口\u0026#34;, x = \u0026#34;歳\u0026#34;, y = \u0026#34;人口\u0026#34;) + theme(text=element_text(family=\u0026#34;Noto Sans CJK JP\u0026#34;)) ggsave(\u0026#34;2022_nagano-shi_Population_All.png\u0026#34;, width = 24, height = 9, dpi = 96) 全体のグラフを描画してファイルに保存します。\noptions(repr.plot.width = 48, repr.plot.height = 32) ggplot(data2, aes(x = Age, y = Population, fill = Gender)) + geom_col(position = \u0026#34;dodge\u0026#34;) + facet_wrap(~Area, ncol = 4) + labs(title = \u0026#34;長野市　令和４年地区別年齢別人口\u0026#34;) + theme(text=element_text(family=\u0026#34;Noto Sans CJK JP\u0026#34;)) ggsave(\u0026#34;2022_nagano-shi_Population_Area.png\u0026#34;, width = 48, height = 32, dpi = 96) 地区別のグラフを描画してファイルに保存します。\nデータ分析のためのデータ可視化入門 (Amazon) ","date":"2022-05-28T17:24:19+09:00","image":"/images/2022/05/Population.jpg","permalink":"/posts/it/pc/3639/","title":"2022年度長野市の人口を gglot2 でグラフにしてみた"},{"content":"「チェックポイント」で、現時点の状態を保持することができ、後続で問題があっても保持した時点からやり直すことができるので便利です。\n自分が「チェックポイント」を使ったことで便利だった場面は、主に以下の点でしょうか。\n環境構築の試行錯誤で、「あの設定をしたとき」に戻ってやり直したい事が多々ある。 手順書の作成で手順に不備がないか、実際の確認及びやり直し。 例えば、前回作成した仮想マシンで環境構築を試すとき、以下のように主要な時点を保持することで、試行錯誤で発生する「再設定」する手間が減ります。\n上記の様に分岐もできるので、別パターンで進むようなことも可能です。\n（但し、ディスクの空き容量に注意）\n仮想マシンのチェックポイントの設定 使っている Hyper-V マネージャー は バージョン 10.0.22000.1 です。\nチェックポイントの設定は、デフォルトで以下になっていると思います。\n自分の場合「自動チェックポイントを使用する」を外しています。\nチェックポイントは意識して作成しているので、自動は不要かなと。\nチェックポイントの作成 チェックポイントは、仮想マシンをシャットダウンした状態で作成するようにしています。\n仮想マシン接続のメニューから、「操作」-「チェックポイント」を選択し、チェックポイント名を入力すれば OK です。\n作成したチェックポイントの時点に戻る 「チェックポイント」内で対象を選択すると、赤枠内のことが可能です。\nなので、戻りたい時点のチェックポイントを選択し、「適用」することで戻ることができます。\nチェックポイントの削除 チェックポイントは、どう消したいかで選べば OK です。\nチェックポイントの削除\n親 - 子 - 孫 となるチェックポイントの「子」を消した場合、「子」の作業内容が「孫」に入る。 チェックポイントのサブツリーを削除\n親 - 子 - 孫 となるチェックポイントの「子」を消した場合、「子」と「孫」が消える。 なので、「孫」の削除ならどちらも同じです。\nひと目でわかるHyper-V Windows Server 2022版 (Amazon) ","date":"2022-05-21T21:36:09+09:00","image":"/images/2022/02/backup.jpg","permalink":"/posts/it/pc/3589/","title":"Hyper-V 上の仮想マシンで環境構築を試すなら「チェックポイント」が便利"},{"content":"ローカル PC の Windows で Hyper-V の Amazon Linux 2 を作成したので残します。\nAmazon Linux 2 の学習や、環境構築のリハーサルなど、費用を掛けずに試せるのは便利だと思います。\n[toc]\nひと目でわかるHyper-V Windows Server 2022版 (Amazon) 環境 Windows 11 Pro 21H2\nHyper-V はインストール済み。Hyper-V マネージャー バージョン 10.0.22000.1 Amazon Linux 2 LTS 2.0.20220426.0 x86_64 seed.iso の作成 公式にある通りですが、実際にやった内容を残します。\n作成にあたって、ネットワーク設定の為に予め自身の Windows 環境を確認します。\nPowerShell やコマンドプロンプトから以下のコマンドを実行します。\nipconfig /all Hyper-V で作成する仮想マシンのネットワークに \u0026ldquo;Default Switch\u0026rdquo; を選択するので、その記載がある箇所を見つけます。自分の場合は次の内容でした。（一部改変あり）\nイーサネット アダプター vEthernet (Default Switch): 接続固有の DNS サフィックス . . . . .: 説明. . . . . . . . . . . . . . . . .: Hyper-V Virtual Ethernet Adapter 物理アドレス. . . . . . . . . . . . .: 00-00-00-00-00-00 DHCP 有効 . . . . . . . . . . . . . .: いいえ 自動構成有効. . . . . . . . . . . . .: はい リンクローカル IPv6 アドレス. . . . .: 0000::0000:0000:0000:0000%00(優先) IPv4 アドレス . . . . . . . . . . . .: 192.168.224.1(優先) サブネット マスク . . . . . . . . . .: 255.255.240.0 デフォルト ゲートウェイ . . . . . . .: DHCPv6 IAID . . . . . . . . . . . . .: 000000000 DHCPv6 クライアント DUID. . . . . . .: 00-00-00-00-00-00-00-00-00-00-00-00-00-00 NetBIOS over TCP/IP . . . . . . . . .: 有効 必要になる部分は、\nIPv4 アドレス （192.168.244.1） サブネット マスク （225.225.240.0） です。\nmeta-data ファイルの作成 公式の例の必要な箇所を書き換え、以下の文字コード UTF-8 のテキストファイルを作成しました。\nlocal-hostname: kumasvr # eth0 is the default network interface enabled in the image. You can configure static network settings with an entry like the following. network-interfaces: | auto eth0 iface eth0 inet static address 192.168.224.11 network 192.168.224.0 netmask 255.255.240.0 broadcast 192.168.255.255 gateway 192.168.224.1 local-hostname：ホスト名を適当に kumasvr と設定 address：192.168.244.1 と同じセグメントのアドレスとして適当に 192.168.244.11 を設定 network：上記の「IPv4 アドレス」に「サブネット マスク」でマスクした値の 192.168.224.0 を設定 netmask：上記の「サブネット マスク」の値を設定 broadcast：network の値の下位ビットを立てて 192.168.255.255 を設定 gateway：上記の「IPv4 アドレス」の 192.168.224.1 を設定 user-data ファイルの作成 公式の例から ec2-user のパスワードを書き換えたぐらいです。\nブート起動時の cloud-init によるネットワーク設定の適用を無効にし、最初の起動時のネットワーク設定を保持する為の記述も使っています。\n#cloud-config #vim:syntax=yaml users: # A user by the name `ec2-user` is created in the image by default. - default chpasswd: list: | ec2-user:hogehoge # In the above line, do not add any spaces after \u0026#39;ec2-user:\u0026#39;. # NOTE: Cloud-init applies network settings on every boot by default. To retain network settings from first boot, add following ‘write_files’ section: write_files: - path: /etc/cloud/cloud.cfg.d/80_disable_network_after_firstboot.cfg content: | # Disable network configuration after first boot network: config: disabled WSL2 で seed.iso の作成 iso イメージを作成する為に、WSL2 で Ubuntu を使いました。\n公式にある「Linux の場合は、genisoimage などのツールを〜」で iso イメージを作成する為です。\nWSL2 は 管理者権限のある PowerShell からコマンドでインストールしました。\n管理者権限のある PowerShell は、Windows アイコンの右クリックから「Windows ターミナル（管理者）」で起動します。\nPowerShell が起動したら、以下のコマンドを実行します。\nwsl --install WSL2 がインストールされたら、指示に従ってユーザーアカウントの作成とそのパスワードを入力します。\n自分の環境では、Ubuntu 20.04 LTS (GNU/Linux 5.10.16.3-microsoft-standard-WSL2 x86_64) が動くようになりました。\nUbuntu のターミナルから以下のコマンドで genisoimage をインストールします。\nsudo apt install genisoimage Ubuntu 上に meta-data ファイルと user-data ファイルを置きます。\nエクスプローラー で Ubuntu のユーザーディレクトリ（/home/kuma）にコピーしました。\nUbuntu のターミナルから、公式通り以下のコマンドを実行して seed.iso を作成します。\ngenisoimage -output seed.iso -volid cidata -joliet -rock user-data meta-data 作成した seed.iso ファイルがエクスプローラーからも確認できます。\n見当たらない場合は、表示を最新にする為に F5 キーを押します。\n仮想マシンの作成と起動 Amazon Linux 2 VM イメージのダウンロード 公式のリンクから Microsoft Hyper-V の Amazon Linux 2 VM イメージをダウンロードしました。\nhttps://cdn.amazonlinux.com/os-images/2.0.20220426.0/hyperv/ の amzn2-hyperv-2.0.20220426.0-x86_64.xfs.gpt.vhdx.zip でした。\nHyper-V で Amazon Linux 2 VM を作成して起動 C:¥VM に以下のファイルを置きました。\nAmazon Linux 2 VM イメージの zip ファイルを解凍して取り出した VM イメージ 作成した seed.iso ファイル 「Hyper-V マネージャー」を起動し、「操作」パネルの「新規」-「仮想マシン」をクリックし、「仮想マシンの新規作成ウィザード」を開きます。\nウィザードは、以下の通り進めました。\n仮想マシンの名前を入力。適当に「kumasvr」と設定。\nデフォルトのまま「第１世代」。\nメモリの割り当てもデフォルトのまま。\nネットワークの構成では「Default Switch」を選択。\n※ 2023/01/28 追記 ゲスト OS を固定 IP アドレスにする今回の様な場合、 \u0026ldquo;Default Switch\u0026rdquo; は不向きでした。 仮想スイッチを作成した投稿の方を採用してください。 \u0026ldquo;Default Switch\u0026rdquo; は IP アドレスが変わるので、 ホスト OS の再起動などで外部ネットワークに繋がらなくなります。\n仮想ハードディスクの接続は「既存の仮想ハードディスクを使用する」にし、C:¥VM に配置した VM イメージを指定。\n完了。\n作成した仮想マシンの DVD ドライブに seed.iso を設定します。\n「Hyper-V マネージャー」で作成した kumasvr を選択し、「操作」パネルの kumasvr の「設定」を選択します。\n「IDE コントローラー 1」-「DVD ドライブ」の「メディア」で「イメージファイル」を選択し、seed.iso を指定したら「OK」で閉じます。\n仮想マシンが作成できたので、起動します。\n「Hyper-V マネージャー」で kumasvr を選択し、「操作」パネルの kumasvr の「接続」を選択します。\n仮想マシンの接続のウインドウが開くので「起動」をクリックします。\nAmazon Linux 2 の初期設定 Amazon Linux 2 が起動したら、ec2-user でログインして幾つか設定します。\nロケールとタイムゾーンの設定 デフォルトだと、キーボード配列が英語で入力が辛いので、日本語配列にしました。\nsudo localectl set-keymap jp106 sudo localectl set-locale LANG=ja_JP.utf8 sudo timedatectl set-timezone Asia/Tokyo ネームサーバーの設定 /etc/resolv.conf にネームサーバーを追加します。\nIP アドレスは \u0026ldquo;Default Switch\u0026rdquo; の IPv4 アドレスを指定します。\nnameserver 192.168.224.1 パッケージのアップデート 以下のコマンドを実行してパッケージをアップデートしました。\nプロキシ環境下で上手く行かない場合は、後述の補足を参照ください。\nsudo yum update Windows から Amazon Linux 2 に公開鍵認証で ssh 接続 秘密鍵と公開鍵の作成 PowerShell で以下のコマンドを実行します。\nパスフレーズは無しで作成しました。\nssh-keygen -t ecdsa 鍵ファイルは C:¥Users¥（ユーザー名）¥.ssh ディレクトリに作成される以下の２つです。\n・id_ecdsa\n・id_ecdsa.pub\n公開鍵を Amazon Linux 2 に格納 一旦、パスワード認証による SSH 接続を有効にし、公開鍵を格納したらパスワード認証を無効にします。\nHyper-V で接続したウインドウからはクリップボードが使えず作業が辛い為、SSH 接続したい為です。\nパスワード認証による SSH 接続の有効化 Amazon Linux 2 の /etc/ssh/sshd_config ファイルを編集してパスワード認証を有効にします。\n以下のように vi を起動して編集しました。\nsudo vi /etc/ssh/sshd_config PasswordAuthentication の no を yes に変更して保存し、vi を終了します。\nPasswordAuthentication yes sshd を再起動して設定を反映します。\nsudo systemctl restart sshd 公開鍵の格納 Windows から Amazon Linux 2 に scp で公開鍵をコピーします。\nPowerShell で以下を実行します。パスワードを聞かれるので入力します。\nscp C:¥Users¥（ユーザー名）¥.ssh¥id_ecdsa.pub ec2-user@192.168.224.11:~/ Amazon Linux 2 に SSH 接続し、パスワードを入力します。\nssh ec2-user@192.168.224.11 公開鍵を /home/ec2-user/.ssh/authorized_keys ファイルに反映します。\n中身はテキストなので、作成した公開鍵の id_ecdsa.pub ファイルの内容が追記できれば OK です。\ncat id_ecdsa.pub\u0026gt;\u0026gt;~/.ssh/authorized_keys パスワード認証による SSH 接続の無効化 有効化で実施した内容を戻します。\nsshd_config の PasswordAuthentication を no に戻して sshd を再起動します。\n以上です。後はあれこれ Amazon Linux 2 を試してみましょう。\n補足：プロキシ環境下のPCで動かすときの設定 プロキシ環境下の Windows PC で、仮想マシンの Amazon Linux 2 側が外部のネットワークに接続できない場合、幾つか設定を行います。\n設定するのは仮想マシンの Amazon Linux 2 側です。\nプロキシの設定 環境変数 http_proxy、https_proxy にプロキシを設定します。\nプロキシが http://proxy.example.com のポート 8080 だとしたら、\nexport http_proxy=http://proxy.example.com:8080 export https_proxy=http://proxy.example.com:8080 を実行します。\nただ、毎回実行するのは面倒だし、どのユーザーにも反映されるように /etc/profile の末尾に追記しました。\nまた、sudo でも設定が効くように、visudo で設定ファイルを編集します。\nsudo visudo \u0026ldquo;Defaults env_keep += \u0026quot; が複数行記載されている箇所があるので、続けて次を追記します。\nDefaults env_keep += \u0026#34;http_proxy https_proxy\u0026#34; /etc/profile と visudo を設定後、Amazon Linux 2 を再起動し、\nsudo printenv で対象の環境変数が設定できていることを確認します。\n外部のコンテンツが取得できるか確認 プロキシとネームサーバーを設定したら、外部のコンテンツが取得できるか確認します。\n試しに curl で google を指定してみます。\ncurl https://google.com 成功なら sudo yum update も行けるか確認します。\nエラーで \u0026ldquo;SSL certificate problem: unable to get local issuer certificate\u0026rdquo; なら、次を行います。\nSSL 証明書の問題を解消 Windows 側でローカル発行者証明書を「信頼されたルート証明機関」に登録済みでも、Amazon Linux 2 側は分からないので追加します。\nローカル発行者証明書のファイルが無くても、必要なローカル発行者証明書が分かるなら Windows 側で管理している「信頼されたルート証明書」の中から、エクスポートして得ることができます。\nが、ここではローカル発行者証明書のファイルは既にあるものとして進めます。\n場合によっては後述の SSL の確認をしない設定を参考にしてください。\nローカル発行者証明書を追加する 追加できる証明書の形式は DER か PEM です。\n手元の証明書が ProxyRootCertificate.cer（Base 64 のテキストファイル）なので、Amazon Linux 2 にコピーし、変換してから追加します。\nopenssl x509 -in ProxyRootCertificate.cer -out ProxyRootCertificate.pem -outform pem sudo cp ProxyRootCertificate.pem /etc/pki/ca-trust/source/anchors/ sudo update-ca-trust 追加できたら再度 curl で外部コンテンツを取得できるか確認し、良ければ sudo yum update します。\nSSL の確認をしない yum エラーが解消できない場合、お勧めはしませんが SSL の確認を止めることも可能です。\n/etc/yum.repos.d/amzn2-core.repo に、SSL の確認をさせない為に sslverify=0 を追加します。\n[amzn2-core] （省略） sslverify=0 [amzn2-core-source] （省略） sslverify=0 [amzn2-core-debuginfo] （省略） sslverify=0 これで yum は通ると思いますが、他のツールでは同様のエラーが発生し、個別に対応が必要で面倒かもしれません。\n","date":"2022-05-14T17:25:07+09:00","image":"/images/2022/05/amzn2.jpg","permalink":"/posts/it/pc/3290/","title":"Hyper-V で Amazon Linux 2 の仮想マシンを作る"},{"content":"kindle unlimited を利用しており、その対象だったので読んでみました。\n知らなかった機能の存在が知れて良かったので紹介します。\nChrome Developer Tools 入門 (Amazon) 普段 DevTools で使う機能がほぼ決まっていましたが、新たに使えそう、使ってみたい、と思った幾つかを挙げてみます。\n色のCSSプロパティー値のフォーマットを変更する\n#FFFFFF が rgb(255 255 255) など、別の表記に切り替えられる。\n自分は基本的に16進で記載しており、何らかのソフトで選んだ色が10進で表された場合は変換してました。rgb で書いて切り替えれば、変換が少し楽かなと。\nそのまま rgb で書けば良い気もするけど、表記が混在するのは統一感が無いので。 Console に XHR リクエストを表示する\nConsole パネルに居ながらリクエストを見たいときに良いかも。\n必要なら Network パネルの該当リクエストにも飛べる。 minify（圧縮）されたコードを読みやすく展開する\n圧縮されている時点で読むのを諦めるので、（大抵は読みたくは無いのだが）展開できるなら読むかも。 Audits パネル\nサイトのパフォーマンス、アクセシビリティ、ユーザーエクスペリエンスを評価するものということだが、試してみたい。\n現在の最新 Chrome では Lighthouse パネルの様でした。 Layers パネル\n要素をレイヤーで表示してくれる。z-index も表現してくれる。\nこちらも試したいところ。\n既存サイトのメンテや、参考にするサイトの構造を確認するときに便利そう。\nRendering パネルも試してみたい。 ","date":"2022-05-07T14:06:11+09:00","image":"/images/2021/09/package.jpg","permalink":"/posts/it/pc/3250/","title":"電子書籍「Chrome Developer Tools 入門」を読んだ"},{"content":"Chrome で新規タブや新規ウインドウを開くと同時に、DevTools も開いて欲しいことがあります。\n例えば、開いた直後からのネットワークをモニタしたいとか。\nその場合、DevTools の「Setting」-「Preferences」にある Global の「Auto-open DevTools for popups」にチェックを入れれば OK です。\n日本語にしている場合は「DevTools をポップアップで自動オープン」\n","date":"2022-04-30T16:52:32+09:00","image":"/images/2021/05/wf.jpg","permalink":"/posts/it/pc/3238/","title":"Google Chrome で新規タブと一緒に DevTools を開くには"},{"content":"ローカルに環境を準備しなくても Python が実行できて便利な Google Colaboratory ですが、R も実行できるようなので試してみました。\nR が実行できるノートブックを開く 以下の URL から、R が実行できるノートブックを作成することができました。\nhttps://colab.research.google.com/notebook#create=true\u0026amp;language=r\nR を実行してみる 以前作成したコードを実行してみました。\nサイドバーの\u0026quot;「ファイル」クリックで、コードで作成しているファイルは直ぐに見つかりました。\nダウンロードできるので OK です。\n作成した Untitled1.ipynb も、Google ドライブの 「Colab Notebooks」フォルダに保存されていました。\nその他の R の実行方法 rpy2 を使うことで、Python と R のどちらも実行できるノートブックにできます。\nrpy2.ipython を %load_ext または %reload_ext で読み込む。 R のブロックは先頭に %%R を入れる。 で実行できました。\nコードに入る波線が気になるので、Python と混在させたい事が無ければ、使う頻度は少なそうです。\nDocker Desktop for Windows/Macでつくるクリーンな開発環境構築入門(Python版) (Amazon) ","date":"2022-04-23T19:04:42+09:00","image":"/images/2022/04/gcr00.jpg","permalink":"/posts/it/pc/3210/","title":"Google Colab で R を使う"},{"content":"以下のように、ホーム画面にカレンダーのウィジェットがあると便利なので、その紹介です。\nちなみに、予定を記載した CSV ファイルを Google カレンダーにインポートする手順の紹介はこちら。\nGoogle カレンダーをインストール １．App Store から「Google カレンダー」を入手\n２．Google カレンダーを起動し、「Google アカウントを追加する」をタップ\n３．Google にログイン\n４．「始める」をタップ\n５．登録してある予定が表示される\n以下の表示は、メニューから \u0026ldquo;月\u0026rdquo; を選択した状態\nウィジェットの追加 １．ホーム画面を長押しし、編集できる状態の右上の「＋」をタップ\n２．「Google カレンダー」をタップ\n３．「ウィジェットを追加」をタップ\nで、ホーム画面にウィジェットが追加されるので、好みの場所に配置します。\nMac Fan 2026年5月号 (Amazon) ","date":"2022-04-16T16:39:34+09:00","image":"/images/2022/04/ggcalen.jpg","permalink":"/posts/it/smartphone/3185/","title":"iPhone に Google カレンダー のウィジェットを追加する"},{"content":"ゴミ収集日の CSV ファイルを作成したので、Google カレンダーに読み込ませます。\n１．「設定」をクリック\n２．「カレンダーを追加」-「新しいカレンダーを作成」をクリック\n「名前」を入力\n「カレンダーを作成」をクリック\n※ CSV の読み込み先のカレンダーを、予め作成します。\n既存のカレンダーに読み込ませると、他の予定と混ざってメンテナンスしづらくなる為です。\n３．「インポート / エクスポート」-「インポート」をクリック\n「インポート」で読み込ませる CSV ファイルを設定\n「カレンダーに追加」で追加先のカレンダーを選択\n「インポート」をクリック\n４．カレンダーに読み込まれます。\n「マイカレンダー」に追加したカレンダー「2022年度ゴミ収集日」があります。\nチェックボックスによって、予定の表示/非表示を切り替えることができます。\n※ 削除したい場合\n「設定」から対象カレンダーの「カレンダーの削除」をクリック\n「削除」をクリック\n","date":"2022-04-09T18:38:25+09:00","image":"/images/2022/04/ggcalen.jpg","permalink":"/posts/it/pc/3136/","title":"Google カレンダーに CSV ファイルを読み込ませるには"},{"content":"「2026年度長野市ごみカレンダー」を公開しました。 2022年度（令和４年度）長野市のゴミ収集日を Google カレンダーに表示させる為の CSV ファイルを作成してみました。\nGoogle カレンダーに CSV ファイルを読み込ませる方法はこちらを参照ください。\n地区別 CSV ファイル 長野市がクリエイティブ・コモンズ・ライセンス表示4.0国際（CC-BY4.0）ライセンスの下に公開しているデータを加工して作成しました。\n※ 内容は保証しません。利用は、ご自身の判断でお願い致します。\nまた、Google カレンダーにインポートする前にファイルの内容を確認し、お好みで修正してください。\n例えば、\n「プラスチック製容器包装」は長いので全て「プラ」に置換する。\n置換したものにしました。（2022/04/09更新）\nが、ご自身が分かりやすい名称があれば置換する。 「ペットボトル」は破棄する場所があり、カレンダーに含めたくないので行を削除する。 など、自身に合ったものにした方が便利でしょう。\n※ 2022/12/22 注記\n下記リンクからファイルのダウンロードができない場合、\nGoogle ドライブの共有ファイルからダウンロードをお試しください。\nその際は、欲しい CSV の下記 No. を元に対象のファイルをご利用ください。\nNo. 地区別 CSV ファイルダウンロード 1 第一 2 第二 3 第三 4 第四 5 第五 6 芹田 7 古牧 8 三輪 9 吉田 10 古里、朝陽 11 柳原、若穂 12 浅川 13 大豆島 14 若槻 15 長沼 16 安茂里 17 小田切 18 芋井 19 篠ノ井塩崎、篠ノ井共和、篠ノ井川柳、篠ノ井信里 20 篠ノ井東福寺、篠ノ井西寺尾 21 篠ノ井中央 22 松代 23 川中島 24 更北 25 七二会 26 信更 27 戸隠中社、戸隠宝光社、戸隠上楠川 28 戸隠北部、戸隠中央、戸隠東部、戸隠南部、戸隠川手、戸隠志垣 29 戸隠西部、戸隠平、戸隠西条、戸隠追通、戸隠上祖山、戸隠下祖山 30 鬼無里上里、鬼無里中央1 31 鬼無里中央2、鬼無里両京 32 大岡甲（川口を除く）、大岡中牧、大岡弘崎、大岡聖ヶ岡 33 大岡乙、大岡丙（聖ヶ岡を除く）、大岡川口 34 豊野南郷、豊野石、豊野西町、豊野上組、豊野立町、豊野南町、豊野中尾1・2 35 豊野横町、豊野伊豆毛、豊野上田中、豊野神代町、豊野本町1・2、豊野中尾1・2（一部）、豊野小瀬1、豊野泉平、豊野上神代、豊野豊陽台、豊野沖1・2、豊野中央組、豊野向原 36 豊野本町3・4・5、豊野東町、豊野小瀬2、豊野ゆたかの、豊野豊南町、豊野下田中 37 豊野浅野、豊野蟹沢、豊野入、豊野小日向、豊野上堰、豊野鳥居団地、豊野大方、豊野橋場、豊野上原、豊野蟻ケ崎、豊野城山、豊野川谷 38 信州新町旭町、信州新町仲町、信州新町上町、信州新町西上町、信州新町常磐町、信州新町鹿島東、信州新町鹿島西、信州新町大原東、信州新町大原西、信州新町下市場、信州新町牧野島（伊切を除く）、信州新町鹿道、信州新町鹿道団地 39 信州新町久保、信州新町本町、信州新町境町、信州新町千原田、信州新町平、信州新町和平団地、信州新町藤池団地、信州新町下川西平、信州新町太田笠子（石畑を除く）、信州新町穂刈下、信州新町穂刈中、信州新町穂刈上、信州新町穂刈北、信州新町穂刈団地、信州新町陽のあたる丘、信州新町大門、信州新町LR、信州新町原、信州新町道祖神 40 信州新町津和中央、信州新町山秋、信州新町中福、信州新町栃久保、信州新町中尾、信州新町菅沼、信州新町細尾、信州新町津上、信州新町外味藤、信州新町豊和、信州新町津南、信州新町中組、信州新町味藤、信州新町橋場、信州新町安用、信州新町風越、信州新町追沢、信州新町神田、信州新町花倉、信州新町二丁田、信州新町穴平、信州新町寺尾、信州新町矢ノ尻、信州新町峰組、信州新町枌ノ木、信州新町赤柴、信州新町石畑、信州新町尾崎、信州新町上古、信州新町芦沢、信州新町本村、信州新町大河、信州新町西日時 41 信州新町塩本、信州新町伊切、信州新町牧田中一、信州新町牧田中二、信州新町中牧一、信州新町中牧二、信州新町南牧住平、信州新町一倉田和、信州新町下中山、信州新町日名、信州新町和田吐唄、信州新町置原、信州新町橋木、信州新町左右、信州新町岩下、信州新町信級中央、信州新町高見、信州新町岩本、信州新町柳高、信州新町川名 42 中条 情報元 上記 CSV ファイルの元データや参考にしたソースコードです。\n長野市オープンデータサイト 生活環境 令和４年度 ごみ収集カレンダー （「最後に更新した日」は「2022年3月22日」でした。） Rへのロード用API ソースコード 公開データを加工したソースコードは以下の通りです。\n実行した環境は R version 4.1.3 です。\n「Rへのロード用API」をそのまま使ってみましたが、自分の所ではタイムアウトして駄目でした。\nデータを提供している LinkData のサイトが重い感じがしますが、作成した下記コードを実行した際はそれほど重いと感じませんでした。謎。\nf \u0026lt;- file(\u0026#34;http://linkdata.org/api/1/rdf1s9118i/gomi_calendar_chiku_R.txt\u0026#34;, open = \u0026#34;r\u0026#34;, encoding = \u0026#34;UTF-8\u0026#34;) chiku \u0026lt;- read.delim(f, header = T) close(f) area \u0026lt;- Filter(function(col) charmatch(\u0026#34;対象地区\u0026#34;, col), colnames(chiku)) df_area \u0026lt;- NULL for (n in 1:nrow(chiku)) { url \u0026lt;- sprintf(\u0026#34;http://linkdata.org/api/1/rdf1s9118i/gomi_calendar_2022_%02d_R.txt\u0026#34;, n) # print(url) data \u0026lt;- read.delim(f \u0026lt;- file(url, open = \u0026#34;r\u0026#34;, encoding = \u0026#34;UTF-8\u0026#34;), header = T) close(f) areas \u0026lt;- Filter(function(v) v != \u0026#34;\u0026#34;, as.character(mapply(function(col) chiku[n, col], area))) area_name \u0026lt;- paste0(areas, collapse = \u0026#34;、\u0026#34;) df_area \u0026lt;- rbind(df_area, data.frame(no = n, area = area_name)) days \u0026lt;- Filter(function(col) charmatch(\u0026#34;X00\u0026#34;, col), colnames(data)) df \u0026lt;- NULL for (i in 1:length(data$資源物.ごみ)) { target \u0026lt;- switch( data$資源物.ごみ[i], \u0026#34;可燃ごみ\u0026#34; = \u0026#34;可燃\u0026#34;, \u0026#34;不燃ごみ\u0026#34; = \u0026#34;不燃\u0026#34;, \u0026#34;プラスチック製容器包装\u0026#34; = \u0026#34;プラ\u0026#34;, \u0026#34;缶、スプレー缶、カセットボンベ缶\u0026#34; = \u0026#34;缶\u0026#34;, \u0026#34;ペットボトル\u0026#34; = \u0026#34;ペット\u0026#34;, \u0026#34;ビン、乾電池、灰\u0026#34; = \u0026#34;ビン電\u0026#34;, \u0026#34;剪定枝葉等\u0026#34; = \u0026#34;枝葉\u0026#34;, data$資源物.ごみ[i] ) marks \u0026lt;- as.character(mapply(function(col) { data[[col]][i] }, days)) dates \u0026lt;- as.Date(\u0026#34;2022-03-31\u0026#34;) + which(marks == \u0026#34;○\u0026#34;) subjects \u0026lt;- rep(target, length(dates)) df \u0026lt;- rbind(df, data.frame(Subject = subjects, \u0026#34;Start Date\u0026#34; = dates, check.names = F)) } write.csv(df, file = sprintf(\u0026#34;2022nagano_area%02d.csv\u0026#34;, n), fileEncoding = \u0026#34;UTF-8\u0026#34;, row.names = F) } write.csv(df_area, file = \u0026#34;2022nagano_area.csv\u0026#34;, fileEncoding = \u0026#34;UTF-8\u0026#34;, row.names = F) ","date":"2022-04-04T20:59:16+09:00","image":"/images/2022/04/gomi.jpg","permalink":"/posts/it/smartphone/3069/","title":"2022年度長野市ごみ収集日を CSV ファイルにして Google カレンダーで使う"},{"content":"スマホでラジオを聴きながら寝るとき、ある程度の時間経過したら再生を停止して欲しいですよね。\nradiko、NHK ラジオ、どちらもアプリ内で設定できるので、その方法を紹介します。\nソニー ポータブルラジオ ICF-B300:手回しラジオ 防災ラジオ FM/AM LEDライト 携帯電話充電 太陽光発電 手回し充電 (Amazon) radiko の場合 iPhone の radiko (Version 7.4.11) で確認しましたが、Android も同様でした。\n１．「マイページ」の「オフタイマー」を選択\n２．「オフタイマー設定」で、何分後に停止するか選択\nAndroid には「オンタイマー」の設定があるので、目覚ましにも良いかもしれません。\nらじる★らじる の場合 iPhone の NHKラジオ (バージョン 6.0.0) で確認しましたが、Android も同様でした。\n以下の赤枠で囲った箇所を追ってもらえば、大丈夫だと思います。\n１．再生中の番組の赤枠で示す箇所を、上にスワイプして広げる\n２．タイマーのアイコンをタップ\n３．時間をタップ\n４．選択した時間と、オフするまでの時間が表示される\n","date":"2022-04-02T19:24:26+09:00","image":"/images/2022/04/radio.jpg","permalink":"/posts/it/smartphone/3058/","title":"radiko(ラジコ) や NHK ラジオ の再生をタイマーで停止するには"},{"content":"残念ながら、iPhone に登録済みの Wi-Fi のパスワードを、iPhone 単独で確認する術は標準では提供されていないようです。→ されていましたので更新しました。Mac から確認する方法も残しています。（2023/06/04 更新）\niPhone で確認する iOS 16.5 で確認しました。少し前のバージョンでも同じ様です。\n「設定」-「Wi-Fi」を開き、対象の Wi-Fi の赤枠で示したアイコンをタップします。\nパスワードが伏せ字になっていますが、タップして認証が通れば表示されます。\nMac から確認する 同じ Apple ID で Mac を使用していれば、Mac のキーチェーンアクセスで確認できます。\n開くと保存されているパスワードの一覧が表示されます。\nWi-Fi のパスワードは種類が「AirMac ネットワークのパスワード」の項目になります。\n確認したい Wi-Fi を「名前」で見つけて開きます。\n「パスワードを表示」にチェックを入れると、キーチェーンのパスワード入力を求められます。\n入力すると、登録されているパスワードが表示されます。\nMac Fan 2026年5月号 (Amazon) ","date":"2022-03-26T18:20:06+09:00","image":"/images/2022/03/wifi.jpg","permalink":"/posts/it/smartphone/3039/","title":"iPhone に設定した Wi-Fi のパスワードを確認するには"},{"content":"「PC の画面をテレビに映して動画を観ることはできないの？」的な質問を年配の友人から聞かれました。\n動画は YouTube で、テレビも YouTube を観られるものだったので、「PC とテレビを接続しなくても、動画を検索すれば観られますよ」と答えたんですが、連携して簡単にできると知ったので、何かの機会に伝えられれば、ということでそのメモです。\n私が所持しているテレビは YouTube に対応していないので、PS4 で試してみました。\n環境 iOS 15.4 / YouTube アプリ 7.10.2 Android 12 / YouTube アプリ 17.09.33 macOS Monterey 12.3 / Google Chrome 99.0.4844.74 PS4 / YouTube アプリ web_20220316_10_RC00 接続の手順 １．テレビで YouTube を開きます。\n２．スマホや PC で YouTube を開き、次のアイコンをタップします。\nスマホの場合は YouTube アプリの上段に、PC の場合は動画内の右下にあると思います。\n３．対象のテレビが見つかったら、それを選択すれば OK です。\n見つからなかった場合は、次の「テレビコードでリンク」で接続します。\nテレビが自動で見つからないなら「テレビコードでリンク」する テレビの YouTube で、\n１．「設定」を選択する。\n２．「テレビコードでリンク」を選択すると12桁の数値が表示される。\n３．スマホや PC の YouTube で「テレビコードでリンク」を選択し、12桁の数値を入力する。\n動画を視聴する 後はスマホや PC から動画を再生すれば、テレビで視聴できました。\nPS4 の YouTube アプリを事前に起動しておく必要があるのが、ちょっと手間ですね。\nテレビの YouTube の場合も同様なのか、友人に説明する際にでも確認してみたいと思います。\n","date":"2022-03-19T21:16:01+09:00","image":"/images/2022/03/ytcast.jpg","permalink":"/posts/it/smartphone/3017/","title":"スマホや PC で開いた YouTube をテレビで再生するには"},{"content":"Chrome の DevTools でデバッグなどをしているとき、頻繁に実行するコードがある場合はスニペットが便利です。\n環境 Google Chrome バージョン: 99.0.4844.51\n使用例 次の様にデータをファイルにダウンロードできるようにします。\nconst data = ({ id: 1, name: \u0026#34;kuma\u0026#34;, site: \u0026#34;kuma-emon.com\u0026#34; }); console.save(data, \u0026#34;data.json\u0026#34;); 次のファイルがダウンロードされます。\n保存したいデータが小さければコピペが楽ですが、大きくなると辛いですよね。\n{ \u0026#34;id\u0026#34;: 1, \u0026#34;name\u0026#34;: \u0026#34;kuma\u0026#34;, \u0026#34;site\u0026#34;: \u0026#34;kuma-emon.com\u0026#34; } console.save というメソッドはデフォルトでは存在しません。\n事前にスニペットに登録したコードを実行して使えるようにしています。\nコードは devtools-snippets の console-save から拝借しました。\nスニペットにコードを保存する DevTools で次のようにします。\n「ソース」を選択 「スニペット」を選択（ない場合は [\u0026raquo;] をクリックした中にあるはず） 「新しいスニペット」をクリック スニペットの名前を入力 コードを入力 「Ctrl」+ S（Windows）または「Command」+ S（Mac）で保存 スニペットを実行する DevTools で「Ctrl」+ O（Windows）または「Command」+ O（Mac）でコマンドメニューを開き、「!」の後にスニペット名を入力します。\n実行後から、console.save が使えるようになります。\n","date":"2022-03-12T18:09:47+09:00","image":"/images/2022/03/javascript.jpg","permalink":"/posts/it/pc/2975/","title":"JavaScript：デバッグで良く使うコードを使い回すには"},{"content":"キーボードショートカットまたは標準で含まれている「スクリーンショット」アプリで撮れます。\n確認した環境 macOS Monterey バージョン 12.2.1\nキーボードショートカット 以下の操作で、スクリーンショットのファイルがディスクトップに保存されます。\n撮りたいもの キーボードショートカット 画面全体 Shift + Command + 3 選択した範囲 Shift + Command + 4 の後、撮りたい範囲をドラッグ ウインドウ または メニュー Shift + Command + 4 、スペースキーの後、 撮りたいウインドウまたはメニューの上に マウスカーソルを置いてクリック 「スクリーンショット」アプリを開く Shift + Command + 5 Touch Bar Shift + Command + 6 ウインドウやメニューの影を除くには 「Option」キーを押しながら撮ることで、ウインドウやメニューの影を無くせます。\n影あり\n影なし\nターミナルからコマンドを実行することで「Option」キーを押さなくても、常に影なしにできます。\ndefaults write com.apple.screencapture disable-shadow -boolean true killall SystemUIServer 戻すには次のコマンドを実行します。\ndefaults delete com.apple.screencapture disable-shadow killall SystemUIServer スクリーンショットの保存先を変更するには ディスクトップではなく、別の場所に保存されるようにするには「スクリーンショット」アプリの「オプション」で設定します。\n「スクリーンショット」アプリを起動するには Shift + Command + 5 キーです。\n「スクリーンショット」アプリ\n「オプション」メニューの先頭にある「保存先」で指定すれば OK です。\n「オプション」メニュー\nアプリの方では、スクリーンの動画も撮れるようですが、また別のお話ということで。。\n","date":"2022-03-05T16:53:56+09:00","image":"/images/2022/03/scrnsht.jpg","permalink":"/posts/it/pc/2935/","title":"Mac でスクリーンショットを撮るには"},{"content":"通常、Time Machine でのバックアップは時間が掛かります。\n予めターミナルからコマンドで設定を変更することで、バックアップの時間を短縮できます。\nバックアップ前に実行\nsudo sysctl debug.lowpri_throttle_enabled=0 バックアップ後は戻しておく\nsudo sysctl debug.lowpri_throttle_enabled=1 アイ・オー・データ 外付けHDD HDPX-UTSC1K (Amazon) Mac mini (Amazon) MacBook Pro 13インチ (Amazon) MacBook Air (Amazon)\n","date":"2022-02-26T20:16:26+09:00","image":"/images/2022/02/backup.jpg","permalink":"/posts/it/pc/2930/","title":"Mac のバックアップ時間を短縮するには"},{"content":"標準アプリの「ヘルスケア」に測定値を入力するのは手間なので、「ショートカット」を使って少しでも楽してみました。\nBluetooth の体重計を使っている方は自動で記録されると思うので不要かもしれませんが、手動で記録している計測値がある場合は参考になるかもしれません。\n例えば、血圧計は Bluetooth で無いとか。\n血圧を記録するショートカットはこちらに作成手順を記載しています。\nタニタ 体組成計 スマートフォン通信対応エントリーモデル BC-768-BK (Amazon) 記録がどのような感じになるか https://youtube.com/shorts/DqHUFK4M2D0\nショートカットを作成した動画 作成手順を動画にしました。\nhttps://youtu.be/JsBSGCVq3E4\n環境 iOS 15.2 で確認していますが、15.3.1 でも同じだと思います。\n体重と体脂肪率を記録する「ショートカット」の作成手順 １．「ショートカット」アプリを起動する。\n２．「＋」をタップする。\n３．ショートカットに名前を付け、「アクションを追加」をタップする。\n４．「スクリプティング」をタップする。\n５．「入力を要求」をタップする。\n６．「プロンプト」に「体重は？」、「テキスト」を「数字」にする。\n７．「App」の「ヘルスケア」をタップする。\n８．「ヘルスケアサンプルを記録」をタップする。\n９．「体重」をタップする。\n「アクセス権が無い」と言われた場合は、書き込みを許可する。\n１０．「値」を「指定入力」「kg」、「日付」を「現在の日付」にする。\n体脂肪率も同様の手順で追加します。\n動画の方ではショートカットの色とアイコンも変更しています。\nMac Fan 2026年5月号 (Amazon) ","date":"2022-02-19T21:44:13+09:00","image":"/images/2022/02/scale.jpg","permalink":"/posts/it/smartphone/2870/","title":"iPhone に体重や体脂肪率を記録する"},{"content":"iPad をダークモードで使っていて、Kindle も黒ページになっていたのですが、白ページに変更しました。\n図がある本のときに図だけ白背景だったりすると、見づらいんですよね。\n環境 主に iPad で Kindle を使用していますが、iPhone、Android でも設定方法は同じでした。\niPad 用の Kindle バージョン 6.51 iPhone 用の Kindle バージョン 6.51 Kindle for Android 8.51.0.100 設定方法 １．Kindle で本を開いた状態でタップし、画面上にバーを表示させる。\n２．バーに表示されている「Aa」をタップする。\n３．「レイアウト」をタップする。\nページの色を変更できない本もあり、その場合は「レイアウト」自体表示されませんでした。\nその場合は、小説など文字が主な本で試すと良いと思います。\n４．「ページの色」を選択する。\n","date":"2022-02-12T16:22:53+09:00","image":"/images/2022/02/kindle.jpg","permalink":"/posts/it/smartphone/2851/","title":"Kindle でページの色を変えるには"},{"content":"”データ（オブジェクト）”としているのは、\nオブジェクトにすると話が広がる（ function や getter / setter 等も含むよね） そういったオブジェクトをディープコピーする機会に会ったことがない からです。で、どうするかですが、\nstructuredClone を使う (2022/04/12追記)\n使える環境でサポート対象の型のデータなら、これが良さそうです。\n以下の話は、使えなかったときの参考になれば、ぐらいしょうか。。 ライブラリが使える環境であればそれを頼る JSON.parse、JSON.stringify でコピーしても問題ないデータであるなら使う かなと思います。そして避けるのは、\nObjects.assign を使う でしょうか。説明を読むと勘違いしそうですが、シャローコピーなので駄目でしょう。\nということで、それぞれどんな感じか見ていきたいと思います。\nライブラリは以下を試してみます。\njQuery 3.6.0 lodash 4.17.21 angular 1.8.2 コピー元のデータ 以下のデータを例に試します。\nconst data = { n: 1, s: \u0026#39;abc\u0026#39;, d: new Date(\u0026#39;2022-01-01T00:00:00Z\u0026#39;), u: undefined, o: { n: 2, s: \u0026#39;def\u0026#39;, d: new Date(\u0026#39;2022-01-02T00:00:00Z\u0026#39;), u: undefined, } }; jQuery ライブラリを使う import $ from \u0026#39;jquery\u0026#39;; const j = $.extend(true, {}, data); console.log(j); コンソールに出力される結果は、\nということで、undefined だった data.u、data.o.u が消失していますね。\n使用するなら許容できるか検討が必要ですが、私自身が使う範囲では問題にならないと思いました。\nlodash ライブラリを使う import _ from \u0026#39;lodash\u0026#39;; const l = _.cloneDeep(data); console.log(l); コンソールに出力される結果は、\n同じですね。\nangular ライブラリを使う import angular from \u0026#39;angular\u0026#39;; const a = angular.copy(data); console.log(a); コンソールに出力された結果は、lodash と同じだったので割愛します。\nJSON.parse、JSON.stringify を使う const s = JSON.parse(JSON.stringify(data)); console.log(s); コンソールに出力される結果は、\n日付が文字列になった（s.d、s.o.d） undefined が消失した（s.u、s.o.u） ということで、こちらも使う場合は許容できるか検討が必要でしょう。\n数値や文字列でない型が含まれるデータで使うとハマるかもしれません。\nObjects.assign を使う シャローコピーなことを後で確認する為に、Objects.assign も使ってみます。\nconst o = Object.assign({}, data); console.log(o); コンソールに出力される結果は、\nと一見良さそうですが、コピー先は以下と同様でディープコピーになっていません。\nconst o = { ...data }; ディープコピーされているか コピー元のデータを更新した後、コピー先のデータをそれぞれ出力してみます。\n結果はコメントに記載しました。\ndata.o.n = 9; console.log(j); // j.o.n = 2 : jquery console.log(l); // l.o.n = 2 : lodash console.log(a); // a.o.n = 2 : angular console.log(s); // s.o.n = 2 : JSON.stringify, JSON.parse console.log(o); // o.o.n = 9 : Objects.assign ということで、Objects.assign 以外は大丈夫ですね。\nObjects.assign の場合、コピー先を変えたつもりがコピー元も変わるので、勘違いして使っているとハマるかもしれません。\no.o.n = 7; console.log(data); // data.o.n = 7 ということで、どれを選択するにしても、コピー対象のデータで問題ないか検討してからの方が良いでしょう。\n","date":"2022-02-05T20:00:48+09:00","image":"/images/2022/02/deepcopy.jpg","permalink":"/posts/it/pc/2767/","title":"JavaScript：データ（オブジェクト）をディープコピーする"},{"content":"リストを操作して結果を得るときに、メソッドを繋げて処理する話です。\n例えば以下の様に filter して map するといった事です。\n分かっている方はそっと閉じてください。。\nconst data = [ { id: 1, name: \u0026#39;佐藤\u0026#39;, scores: { a: 10, b: 20, c: 30 } }, { id: 2, name: \u0026#39;鈴木\u0026#39;, scores: { a: 11, b: 21, c: 31 } }, { id: 3, name: \u0026#39;高橋\u0026#39;, scores: { a: 12, b: 22, c: 32 } }, { id: 4, name: \u0026#39;田中\u0026#39;, scores: { a: 13, b: 23, c: 33 } }, { id: 5, name: \u0026#39;渡辺\u0026#39;, scores: { a: 14, b: 24, c: 34 } }, ]; // b のスコアが偶数の人の名前と b のスコアを得る const ret = data .filter((item) =\u0026gt; item.scores.b % 2 === 0) .map((item) =\u0026gt; ({ name: item.name, b: item.scores.b })); // ret に入る結果 // [ // { name: \u0026#39;佐藤\u0026#39;, b: 20 }, // { name: \u0026#39;高橋\u0026#39;, b: 22 }, // { name: \u0026#39;渡辺\u0026#39;, b: 24 }, // ] filter と map メソッドを使わなかったら この場合、以下の感じでしょうか。\nconst ret = []; for (let item of data) { if (item.scores.b % 2 === 0) { ret.push({ name: item.name, b: item.scores.b }); } } filter と map メソッドを別にしたら const temp = data.filter((item) =\u0026gt; item.scores.b % 2 === 0); const ret = temp.map((item) =\u0026gt; ({ name: item.name, b: item.scores.b })); filter で得た temp を他で使うことが無ければ、分ける理由が無いですね。\n連続で使ってソートもしてみる 結果の並びを、b のスコアが高い順にしてみます。\nconst ret = data .filter((item) =\u0026gt; item.scores.b % 2 === 0) .map((item) =\u0026gt; ({ name: item.name, b: item.scores.b })) .sort((item1, item2) =\u0026gt; item2.b - item1.b); // ret に入る結果 // [ // { name: \u0026#39;渡辺\u0026#39;, b: 24 }, // { name: \u0026#39;高橋\u0026#39;, b: 22 }, // { name: \u0026#39;佐藤\u0026#39;, b: 20 }, // ] 1行で書けますが、メソッド毎に改行すると読みやすいです。\n慣れないうちは記述しづらいと思いますが、慣れれば楽に処理できて読みやすいと感じると思います。\n","date":"2022-01-29T11:27:19+09:00","image":"/images/2022/01/serial.jpg","permalink":"/posts/it/pc/2730/","title":"JavaScript：Array のメソッドを連続で使う"},{"content":"MacBook を閉じても、スリープされたくない時があります。\n音楽を再生しているとき、動画を再生（音だけ聴きたい）しているときなどですね。\nその方法になります。\n環境 MacBook Pro (13-inch, M1, 2020) macOS Monterey\nスリープしないようにコマンドで設定する ターミナルから以下のコマンドを実行するとスリープの機能を無効にできます。\n管理者パスワードの入力が必要です。\nただ、全くスリープ無しだとバッテリーが勿体ないので、後述のユーティリティの方が便利です。\nsudo pmset -a disablesleep 1 実行すると、アップルメニューの「スリープ」が使えなくなりますし、MacBook を閉じてもスリープしません。\n戻すには、最後のパラメータを 1 → 0 にしてコマンドを実行します。\nsudo pmset -a disablesleep 0 Fermata ユーティリティを使う Fermata を使うと、\n手動でスリープを無効化 指定したアプリが起動している時はスリープを無効化 指定したアプリがアイドルスリープを防いでいる時はスリープを無効化\n（具体的なアプリは不明ですが、音楽再生アプリに多いらしい？） することができて便利です。\n使うには、\nリリースのページから最新版をダウンロード\n現時点では2019/12/12リリースの 1.2 、Fermata-136.zip が最新版でした。 zip を解凍してできる Fermata.app をアプリケーションフォルダに移動 Fermata を起動 します。\nメニューバーの右側に Fermata のアイコンが現れるので、設定などはここから行います。\n手動でスリープを無効化 Fermata のアイコンをクリックして、メニューの「Postpone Lid Close Sleep」を選択します。\n選択すると、メニューにチェックマークが付きます。\n指定したアプリが起動している時はスリープを無効化 Fermata のアイコンをクリックして、メニューの「Preferences\u0026hellip;」を選択します。\n自分の場合は、以下のように「Music」と「QuickTime Player」を設定しています。\n「Add Application\u0026hellip;」でアプリを追加\n「Remove Application」でアプリを削除\nすれば OK です。デフォルトは「Embrace」が設定されていましたが、使っていないので削除しました。特にそのままでも問題ないと思います。\nMac mini (Amazon) MacBook Pro 13インチ (Amazon) MacBook Air (Amazon)\n","date":"2022-01-22T14:53:57+09:00","image":"/images/2021/05/mb.jpg","permalink":"/posts/it/pc/2687/","title":"Mac のスリープを防止するには"},{"content":"結論から言うと「AirPlayを使う」です。これで分かった方は以降を読まなくても大丈夫です。\n環境 Mac：macOS Monterey 12.1\niPhone：iOS 15.2.1\nワイヤレスヘッドホン：SONY WH-1000XM3\n自分の場合、Mac で動画や音楽を再生しながら使う方が多いので、ヘッドホンは Mac とペアリングしています。\niPhone は別のワイヤレスイヤホンにペアリングしており、外出時などはそちらを使っています。\nちなみに、後継のヘッドホン WH-1000XM4 は音楽再生のマルチポイント接続に対応している様なので、その機能で切り替えられそうです。（未確認）\nMac / iPhone を切り替える iPhone \bで再生する音楽をヘッドホンで聴くには、iPhone のコントロールセンターから以下のように操作します。\n１．右上の赤枠にあるマークをタップします。\n２．「スピーカーとテレビ」の下に Mac（Macのコンピュータ名）が出るのでタップします。\n３．iPhone で再生した音楽をヘッドホンから聴くことができます。\n他の音楽再生のマルチペアリングには対応していないヘッドホンやイヤホンでも、同様な操作で行けるんじゃないかと思います。\nソニー ワイヤレスノイズキャンセリングステレオヘッドホン WH-1000XM5 (Amazon) Mac mini (Amazon) MacBook Pro 13インチ (Amazon) MacBook Air (Amazon)\n","date":"2022-01-14T14:17:41+09:00","image":"/images/2022/01/wirelessHeadphone.jpg","permalink":"/posts/it/smartphone/2661/","title":"1台のワイヤレスヘッドホンで Mac / iPhone を切り替えて音楽を聴くには"},{"content":"バックアップ先にクラウドを使用する場合、そのままバックアップするのは少し心配なファイルがあったので、仮想ディスクファイルを BitLocker で暗号化し、その仮想ディスクファイルをクラウドに保存しました。\n環境 Windows 11 Pro バージョン 21H2\n仮想ディスクファイルの作成 「コンピューターの管理」で仮想ディスクファイルを作成します。\n次のどちらかで「コンピューターの管理」を起動します。\nエクスプローラで「PC」の右クリックメニューから「その他のオプションを表示」-「管理」を選択 Windows メニューの「すべてのアプリ」から「Windows ツール」から「コンピューターの管理」を開く (1) 「記憶域」-「ディスクの管理」を選択し、メニューから「操作」-「VHD の作成」を選択します。\n(2) 作成する仮想ディスクを設定します。\n場所 - 仮想ディスクを作成するファイルを設定 仮想ハードディスクフォーマット - 「VHDX」を選択 仮想ハードディスクの種類 - 「可変容量」を選択 BitLocker で暗号化するには最小でも64MBは必要の様です。\nかといって、大きすぎるとクラウドへのアップロード/ダウンロードが厄介です。\n(3) 作成したディスクの右クリックメニューから「ディスクの初期化」を選択します。\n(4) ディスクと「GPT」を選択します。\n(5) ウィザードに従って進めます。\n(6) 仮想ディスクのサイズですが、変えずに進めます。\n(7) ドライブ文字を割り当てます。\n(8) フォーマットの設定をします。\n(9) ウィザードを完了します。\n仮想ディスクを暗号化する 作成した仮想ディスクを BitLocker で暗号化します。\n(1) エクスプローラから仮想ディスクの右クリックメニューから「BitLocker を有効にする」を選択します。\n(2) ロック解除方法をパスワードにして、そのパスワードを設定します。\n(3) 回復キーをバックアップ方法を選択します。自分は「回復キーを印刷する」にしました。\n(4) 暗号化する範囲で「使用済みの領域のみ暗号化する」を選択します。\n(5) 「新しい暗号化モード」を選択します。\n(6) 暗号化を開始します。\n暗号化した仮想ディスクにバックアップするファイルを保存する (1) 通常のディスクと同様に見えるので、バックアップしたいファイルをコピーします。\n(2) 仮想ディスクを右クリックメニューから「取り出し」を選択します。\n(3) 仮想ディスクも普通のファイルと同様なので、バックアップします。\n(4) 仮想ディスクのファイルをマウントするには、ファイルをダブルクリックします。\nマウントされたディスクを選択すると、パスワードを聞かれるので入力します。\nアイ・オー・データ 外付けHDD HDPX-UTSC1K (Amazon) ","date":"2022-01-08T15:34:10+09:00","image":"/images/2022/01/crypt.jpg","permalink":"/posts/it/pc/2610/","title":"バックアップに暗号化した仮想ディスクファイルを使う"},{"content":"JupyterLab または Jupyter Notebook で上場企業の要約を取得してみます。\nJupyterLab または Jupyter Notebook の用意はこちらを参照ください。\n要約を取得する 取得には、yahooquery 2.2.15 を使います。\nfrom yahooquery import Ticker toyota = Ticker(\u0026#39;7203.T\u0026#39;) sd = toyota.summary_detail ３行目： Ticker で、取得したい銘柄のコードを指定します。\n日本の銘柄は ‘7203.T’ の様にコード（7203=トヨタ自動車）と取引所（T=東京証券取引所）をドットで区切って設定します。 ４行目： 要約のデータを取得します。 sd に dict 型オブジェクトでデータを取得できます。\n{\u0026#39;7203.T\u0026#39;: {\u0026#39;maxAge\u0026#39;: 1, \u0026#39;priceHint\u0026#39;: 2, \u0026#39;previousClose\u0026#39;: 2110.5, \u0026#39;open\u0026#39;: 2104.0, \u0026#39;dayLow\u0026#39;: 2091.0, \u0026#39;dayHigh\u0026#39;: 2116.0, \u0026#39;regularMarketPreviousClose\u0026#39;: 2110.5, \u0026#39;regularMarketOpen\u0026#39;: 2104.0, \u0026#39;regularMarketDayLow\u0026#39;: 2091.0, \u0026#39;regularMarketDayHigh\u0026#39;: 2116.0, \u0026#39;dividendRate\u0026#39;: 51.0, \u0026#39;dividendYield\u0026#39;: 0.0242, \u0026#39;exDividendDate\u0026#39;: \u0026#39;2021-09-29 09:00:00\u0026#39;, \u0026#39;payoutRatio\u0026#39;: 0.2282, \u0026#39;fiveYearAvgDividendYield\u0026#39;: 3.07, \u0026#39;beta\u0026#39;: 0.648588, \u0026#39;trailingPE\u0026#39;: 9.420413, \u0026#39;forwardPE\u0026#39;: 9.330822, \u0026#39;volume\u0026#39;: 14009600, \u0026#39;regularMarketVolume\u0026#39;: 14009600, \u0026#39;averageVolume\u0026#39;: 21168583, \u0026#39;averageVolume10days\u0026#39;: 18734770, \u0026#39;averageDailyVolume10Day\u0026#39;: 18734770, \u0026#39;bid\u0026#39;: 2105.0, \u0026#39;ask\u0026#39;: 2108.0, \u0026#39;bidSize\u0026#39;: 0, \u0026#39;askSize\u0026#39;: 0, \u0026#39;marketCap\u0026#39;: 29175282925568, \u0026#39;fiftyTwoWeekLow\u0026#39;: 1443.2, \u0026#39;fiftyTwoWeekHigh\u0026#39;: 2188.0, \u0026#39;priceToSalesTrailing12Months\u0026#39;: 0.9315025, \u0026#39;fiftyDayAverage\u0026#39;: 2063.05, \u0026#39;twoHundredDayAverage\u0026#39;: 1924.4655, \u0026#39;trailingAnnualDividendRate\u0026#39;: 51.0, \u0026#39;trailingAnnualDividendYield\u0026#39;: 0.024164889, \u0026#39;currency\u0026#39;: \u0026#39;JPY\u0026#39;, \u0026#39;fromCurrency\u0026#39;: None, \u0026#39;toCurrency\u0026#39;: None, \u0026#39;lastMarket\u0026#39;: None, \u0026#39;algorithm\u0026#39;: None, \u0026#39;tradeable\u0026#39;: False}} 特定の項目、例えば dividendYield を取り出したければ、\nsd.get(\u0026#39;7203.T\u0026#39;).get(\u0026#39;dividendYield\u0026#39;) とし、ループで項目を処理したければ、\nv = sd.get(\u0026#39;7203.T\u0026#39;) for k in v: print(k, v.get(k)) のようにすれば、各項目を以下のように表示できます。\nmaxAge 1 priceHint 2 previousClose 2110.5 open 2104.0 dayLow 2091.0 dayHigh 2116.0 regularMarketPreviousClose 2110.5 regularMarketOpen 2104.0 regularMarketDayLow 2091.0 regularMarketDayHigh 2116.0 dividendRate 51.0 dividendYield 0.0242 exDividendDate 2021-09-29 09:00:00 payoutRatio 0.2282 fiveYearAvgDividendYield 3.07 beta 0.648588 trailingPE 9.420413 forwardPE 9.330822 volume 14009600 regularMarketVolume 14009600 averageVolume 21168583 averageVolume10days 18734770 averageDailyVolume10Day 18734770 bid 2105.0 ask 2108.0 bidSize 0 askSize 0 marketCap 29175282925568 fiftyTwoWeekLow 1443.2 fiftyTwoWeekHigh 2188.0 priceToSalesTrailing12Months 0.9315025 fiftyDayAverage 2063.05 twoHundredDayAverage 1924.4655 trailingAnnualDividendRate 51.0 trailingAnnualDividendYield 0.024164889 currency JPY fromCurrency None toCurrency None lastMarket None algorithm None tradeable False Pythonでできる！ 株価データ分析 (Amazon) Docker Desktop for Windows/Macでつくるクリーンな開発環境構築入門(Python版) (Amazon) ファイナンス機械学習 (Amazon) アセットマネージャーのためのファイナンス機械学習 (Amazon)\n","date":"2022-01-01T13:17:13+09:00","image":"/images/2022/01/ir.jpg","permalink":"/posts/it/pc/2587/","title":"Python で上場企業の要約を取得する"},{"content":"JupyterLab または Jupyter Notebook で上場企業の損益計算書を取得してみます。\nJupyterLab または Jupyter Notebook の用意はこちらを参照ください。\n損益計算書を取得する 取得には、yahooquery 2.2.15 を使います。\nfrom yahooquery import Ticker toyota = Ticker(\u0026#39;7203.T\u0026#39;) icst = toyota.income_statement() ３行目： Ticker で、取得したい銘柄のコードを指定します。\n日本の銘柄は ‘7203.T’ の様にコード（7203=トヨタ自動車）と取引所（T=東京証券取引所）をドットで区切って設定します。 ４行目： 損益計算書のデータを取得します。 icst に以下の様にデータを取得できます。\n列が多くて表示しきれません。\nなので、転置行列にする .T をつけてみます。\nicst.T 多いので見たい項目だけに絞ってみます。が、英語でどれに当たるか良く分かってません。。\n取り敢えず、\n売上総利益 (GrossProfit) 純利益 (NetIncome) 営業利益 (OperatingIncome) 総収入 (TotalRevenue) にしてみます。\nicst.T.query(\u0026#39;index in (\u0026#34;asOfDate\u0026#34;, \u0026#34;GrossProfit\u0026#34;, \u0026#34;NetIncome\u0026#34;, \u0026#34;OperatingIncome\u0026#34;, \u0026#34;TotalRevenue\u0026#34;)\u0026#39;) 省略表示されない程度に絞れるなら、転置行列しなくても良いかもしれません。\nicst[[\u0026#34;asOfDate\u0026#34;, \u0026#34;GrossProfit\u0026#34;, \u0026#34;NetIncome\u0026#34;, \u0026#34;OperatingIncome\u0026#34;, \u0026#34;TotalRevenue\u0026#34;]] 指数で表示されちゃいますね。微妙でしょうか。\nPythonでできる！ 株価データ分析 (Amazon) Docker Desktop for Windows/Macでつくるクリーンな開発環境構築入門(Python版) (Amazon) ファイナンス機械学習 (Amazon) アセットマネージャーのためのファイナンス機械学習 (Amazon)\n","date":"2021-12-25T20:20:00+09:00","image":"/images/2021/08/stock.jpg","permalink":"/posts/it/pc/2562/","title":"Python で上場企業の損益計算書を取得する"},{"content":"以前 RTK Query を使ってみましたが、定周期で実行することができるようなので試しました。\nテンプレートの作成 create-react-app でテンプレートを作成します。\ncreate-react-app のバージョンを指定しているのはエラーを回避する為です。\nv5.0.0が最近(2021/12/14)出たばかりだからかと思います。\nnpx create-react-app@5.0.0 my-app --template redux-typescript APIの準備 定期的に取得して結果に違いが出そうな API として、Yahoo! JAPAN Webサービスの気象情報API を利用します。\n利用には固有のアプリケーションIDを取得する必要があります。\nRTK Query のコードを追加 テンプレートに追加/修正するファイルは以下の４ファイルです。\nsrc/app/services/weather/index.ts src/app/services/weather/type.ts src/app/store.ts src/App.tsx 気象情報APIを実行するコードを以下のように作成しました。\n渡した座標（Coordinates）の天気（Weather）を返します。\nimport { createApi, fetchBaseQuery } from \u0026#39;@reduxjs/toolkit/query/react\u0026#39;; import { Coordinates, Weather } from \u0026#39;./type\u0026#39;; const appId = \u0026#39;(取得した固有のアプリケーションID)\u0026#39;; export const weatherApi = createApi({ reducerPath: \u0026#39;weatherApi\u0026#39;, baseQuery: fetchBaseQuery({ baseUrl: \u0026#39;https://map.yahooapis.jp/weather/V1/\u0026#39; }), endpoints: (builder) =\u0026gt; ({ getWeather: builder.query\u0026lt;Weather, Coordinates\u0026gt;({ query: (coordinates: Coordinates) =\u0026gt; { const { latitude, longitude, } = coordinates; return `place?output=json\u0026amp;past=1\u0026amp;coordinates=${longitude},${latitude}\u0026amp;appid=${appId}`; } , }), }), }); export const { useGetWeatherQuery } = weatherApi; Weather の定義は以前紹介した「Paste JSON as Code」を利用し、Coodinates を含め以下のようにしました。\nexport interface Coordinates { latitude: number; longitude: number; } export interface Weather { ResultInfo: ResultInfo; Feature: Feature[]; } export interface Feature { Id: string; Name: string; Geometry: Geometry; Property: Property; } export interface Geometry { Type: string; Coordinates: string; } export interface Property { WeatherAreaCode: number; WeatherList: WeatherList; } export interface WeatherList { Weather: Weather[]; } export interface Weather { Type: string; Date: string; Rainfall: number; } export interface ResultInfo { Count: number; Total: number; Start: number; Status: number; Latency: number; Description: string; Copyright: string; } そして、作成した Reducer を既存の store に加えました。\nimport { configureStore, ThunkAction, Action } from \u0026#39;@reduxjs/toolkit\u0026#39;; import counterReducer from \u0026#39;../features/counter/counterSlice\u0026#39;; import { weatherApi } from \u0026#39;./services/weather\u0026#39;; export const store = configureStore({ reducer: { counter: counterReducer, [weatherApi.reducerPath]: weatherApi.reducer, }, middleware: (getDefaultMiddleware) =\u0026gt; getDefaultMiddleware().concat(weatherApi.middleware), }); export type AppDispatch = typeof store.dispatch; export type RootState = ReturnType\u0026lt;typeof store.getState\u0026gt;; export type AppThunk\u0026lt;ReturnType = void\u0026gt; = ThunkAction\u0026lt; ReturnType, RootState, unknown, Action\u0026lt;string\u0026gt; \u0026gt;; 表示部分の作成 既存の App.tsx を以下のように変更しました。\nimport React, { useState } from \u0026#39;react\u0026#39;; import { useGetWeatherQuery } from \u0026#39;./app/services/weather\u0026#39;; const intervalOptions = [ { label: \u0026#39;Off\u0026#39;, value: 0 }, { label: \u0026#39;1分\u0026#39;, value: 1 * 60 * 1000 }, { label: \u0026#39;10分\u0026#39;, value: 10 * 60 * 1000 }, { label: \u0026#39;30分\u0026#39;, value: 30 * 60 * 1000 }, ]; const Types: { [key: string]: string } = { observation: \u0026#39;実測値\u0026#39;, forecast: \u0026#39;予測値\u0026#39;, }; export function App() { const [latitude, setLatitude] = useState(35.663613); const [longitude, setLongitude] = useState(139.732293); const [pollingInterval, setPollingInterval] = useState(60000); const { data, error, isLoading } = useGetWeatherQuery({ latitude, longitude, }, { pollingInterval }); const now = new Date(); return ( \u0026lt;\u0026gt; \u0026lt;div\u0026gt; \u0026lt;label\u0026gt;更新間隔\u0026lt;/label\u0026gt; \u0026lt;select value={pollingInterval} onChange={({ target: { value } }) =\u0026gt; setPollingInterval(Number(value)) } \u0026gt; {intervalOptions.map(({ label, value }) =\u0026gt; ( \u0026lt;option key={value} value={value}\u0026gt; {label} \u0026lt;/option\u0026gt; ))} \u0026lt;/select\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;div\u0026gt; \u0026lt;label\u0026gt;経度\u0026lt;/label\u0026gt;\u0026lt;input type=\u0026#34;text\u0026#34; value={longitude} onChange={(e) =\u0026gt; setLongitude(Number(e.target.value))} /\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;div\u0026gt; \u0026lt;label\u0026gt;緯度\u0026lt;/label\u0026gt;\u0026lt;input type=\u0026#34;text\u0026#34; value={latitude} onChange={(e) =\u0026gt; setLatitude(Number(e.target.value))} /\u0026gt; \u0026lt;/div\u0026gt; { error ? \u0026#39;Error.\u0026#39; : isLoading ? \u0026#39;Loading...\u0026#39; : data \u0026amp;\u0026amp; data.Feature .map(feature =\u0026gt; \u0026lt;\u0026gt; \u0026lt;div\u0026gt;{feature.Name}\u0026lt;/div\u0026gt; \u0026lt;table\u0026gt; \u0026lt;caption\u0026gt;{now.toLocaleDateString()} {now.toLocaleTimeString()} 更新\u0026lt;/caption\u0026gt; \u0026lt;thead\u0026gt; \u0026lt;tr\u0026gt; \u0026lt;th\u0026gt;区分\u0026lt;/th\u0026gt; \u0026lt;th\u0026gt;日時\u0026lt;/th\u0026gt; \u0026lt;th\u0026gt;降水強度（mm/h）\u0026lt;/th\u0026gt; \u0026lt;/tr\u0026gt; \u0026lt;/thead\u0026gt; \u0026lt;tbody\u0026gt; {feature.Property.WeatherList.Weather .map(w =\u0026gt; \u0026lt;tr key={w.Date}\u0026gt; \u0026lt;td\u0026gt;{Types[w.Type]}\u0026lt;/td\u0026gt; \u0026lt;td\u0026gt;{w.Date}\u0026lt;/td\u0026gt; \u0026lt;td align=\u0026#39;right\u0026#39;\u0026gt;{w.Rainfall}\u0026lt;/td\u0026gt; \u0026lt;/tr\u0026gt; )} \u0026lt;/tbody\u0026gt; \u0026lt;/table\u0026gt; \u0026lt;/\u0026gt;) } \u0026lt;/\u0026gt; ); } export default App; API を実行する周期を useGetWeatherQuery のオプションで pollingInterval に指定すれば OK です。\nこのソースコード自身の注意としては、入力中の座標の天気も取得に行っちゃう作りになっている所ですが妥協しました。\n実行結果 「降水強度」が 0 のみだと寂しいので、降りそうな新潟駅辺りを指定してみました。\nデータの取得と表示も定周期で動きました。\n定周期のタイミングで、座標変更前のデータ取得も１度発生しますね。\n特に使われることが無いので問題にはならなそうです。\n","date":"2021-12-18T17:19:33+09:00","image":"/images/2021/07/restapi.jpg","permalink":"/posts/it/pc/2521/","title":"Redux Toolkit の RTK Query を定周期に実行する"},{"content":"Excel から 国内株式、 先物・オプション の売買ができる「マーケットスピード II RSS」を使ってみました。といっても売買はしておらず、株価の参照までです。\nこの「RSS」は「リアルタイムスプレッドシート」の略称で、RSSフィードのRSS（RDF Site Summary / Rich Site Summary / Really Simple Syndication）とは無関係でした。\n使用環境 自分は以下の環境で試しました。\nWindows 11 Pro 21H2（64 ビット オペレーティング システム） Microsoft® Excel® for Microsoft 365 MSO 64 ビット また、「マーケットスピード II RSS」 を使用するには以下が必要です。\n楽天証券の口座 「マーケットスピード II」のインストール 「マーケットスピード II RSS」 のアドインをExcelに登録 「マーケットスピード II」のインストール 1.「マーケットスピード II」のインストーラーをダウンロード\nサイトからインストーラーをダウンロードします。　2. ダウンロードしたインストーラー（MarketSpeed2Installkits_0001.exe）を実行します。\nウィザードに従って進めば OK です。\n「マーケットスピード II RSS」 のアドインをExcelに登録 「マーケットスピード II」をインストールすると、アドインのファイルが以下に置かれます。\nC:\\ユーザー\\(ユーザー名)\\AppData\\Local\\MarketSpeed2\\Bin\\rss\nファイルをExcelにアドイン登録します。\n1. Excel の「オプション」を選択します。\n2. 「Excel のオプション」の「アドイン」で「設定」を選択します。\n3. 「参照」を選択します。\n4. アドインのファイルを選択します。\nExcel が\n64ビットなら「MarketSpeed2_RSS_64bit.xll」\n32ビットなら「MarketSpeed2_RSS_32bit.xll」\nを選択します。\n5. 再度「参照」を選択します。\n6. 「MarketSpeed2_RSS_VBA.xlam」を選択します。\n7. 「OK」でアドインのダイアログを閉じます。\n8. Excel に「マーケットスピード II」のタブが増えれば完了です。\nExcel で株価の読み込みを試してみる サンプルシートが公開されているので、「TOPIX100構成銘柄サンプルシート」で試してみます。\n1. 先に「マーケットスピード II」にログインします。\n2. ダウンロードしたサンプルシート「topix_samplesheet.xlsx」を開きます。\n3. 「マーケットスピード II」タブで「未接続」を選択します。\n4. RSS の使用に同意していなかったので楽天証券のサイトが開きました。\n同意して進みます。\n5. 株価がシートに反映されました。\nファイナンス機械学習 (Amazon) アセットマネージャーのためのファイナンス機械学習 (Amazon)\n","date":"2021-12-11T20:27:00+09:00","image":"/images/2021/08/stock.jpg","permalink":"/posts/it/pc/2412/","title":"マーケットスピード II RSS を使ってみた"},{"content":".NET Framework の GeoCoordinate クラス を使う 2023/03/11：別投稿を作成しました。\nC# など、.NET Framework が使える場合は、GeoCoordinate クラス の GetDistanceTo メソッドが便利です。\nGeoCoordinate が System.Device.Location のクラスなので NuGet で追加しましたが、複数見つかるんですね。\n使ったのはこれなんですが、良かったのかどうか。。\nusing System; using System.Device.Location; namespace Haversine { class Program { static void Main(string[] args) { var latTokyo = 35.6809591; var lonTokyo = 139.7673068; var latOsaka = 34.7055051; var lonOsaka = 135.4983028; var distance = new GeoCoordinate(latTokyo, lonTokyo).GetDistanceTo(new GeoCoordinate(latOsaka, lonOsaka)); // 403110.29750609986m Console.WriteLine($\u0026#34;{distance}m\u0026#34;); } } } MSDN の注釈には以下の記載がありました。違和感があるのは機械翻訳からでしょうか。\nHaversine 式は、距離を計算するために使用されます。 Haversine 式は地球の惑星を表しますが、楕円体ではなく球面地球を想定しています。 距離が長い場合、Haversine 式では 0.1% 未満のエラーが発生します。\n高度は距離の計算には使用されません。\nhttps://docs.microsoft.com/ja-jp/dotnet/api/system.device.location.geocoordinate.getdistanceto?view=netframework-4.8#System_Device_Location_GeoCoordinate_GetDistanceTo_System_Device_Location_GeoCoordinate_\nHaversine 式なるもので計算できるようですね。\nhaversine-distance パッケージを使う Haversine で探すと JavaScript で使える haversine-distance パッケージがありました。\nconst haversine = require(\u0026#39;haversine-distance\u0026#39;); const tokyo = { latitude: 35.6809591, longitude: 139.7673068 }; const osaka = { latitude: 34.7055051, longitude: 135.4983028 }; // 403213.7855570708 console.log(haversine(tokyo, osaka)); 結果の差は計算途中の誤差でしょうか。\n","date":"2021-12-04T18:25:01+09:00","image":"/images/2021/12/map.jpg","permalink":"/posts/it/pc/2382/","title":"経度緯度の２点間の距離を求めるには"},{"content":"RESTful API のテストをしたいときなど、VSCode で REST Client 拡張機能を使うと便利です。\nVSCode に REST Client 拡張機能を追加する 環境\nVisual Studio Code バージョン: 1.62.3 REST Client バージョン: 0.24.5 インストール手順\nクリックして「拡張機能」を選択 “rest-client” で検索 「インストール」をクリック RESTful API を実行する NHK番組API を例に実行してみます。\n@area = 130 @service = g1 @date = 2021-11-27 @apikey = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx @genre = 0000 ### GET https://api.nhk.or.jp/v2/pg/list/{{area}}/{{service}}/{{date}}.json?key={{apikey}} HTTP/1.1 ### GET https://api.nhk.or.jp/v2/pg/genre/{{area}}/{{service}}/{{genre}}/{{date}}.json?key={{apikey}} HTTP/1.1 @マークで変数が定義でき、使うときは括弧を二重にして記載します。\n@apikey の値はアカウント登録して取得してください。\n要求を右クリックしてメニューから「Send Request」を選択するか、\nWindows なら Ctrl + Alt + R Mac なら ⌥ ⌘ R キーで実行できます。\n右側に結果が表示されます。\n試す環境が用意できず確認できていませんが、POST 要求でボディを送ることや認証の設定もできるので、テストの際は便利だと思います。\n要求のコードを生成する 要求を右クリックしてメニューから「Generate Code Snippet」で、以下のコードを生成することができます。\nC Clojure C# Go HTTP Java JavaScript Kotlin JavaScript → fetch を選択した場合は、以下のように右側に生成したコードが表示されました。\nfetch の他は jQuery、XMLHttpRequest、axios を選択することができます。\n使い所はパッと思いつきませんでしたが、興味を引く機能ですね。\n","date":"2021-11-27T20:29:00+09:00","image":"/images/2021/06/mock.jpg","permalink":"/posts/it/pc/2320/","title":"RESTful API を VSCode から実行するには"},{"content":"ソースコードを git で管理しているとき、差分を確認するには VSCode 上で GitLens 拡張機能を使うと便利です。\nVSCode に GitLens 拡張機能を追加する 環境\nVisual Studio Code バージョン: 1.62.3 GitLens バージョン: 11.7.0 インストール手順\nクリックして「拡張機能」を選択 \u0026ldquo;gitlens\u0026rdquo; で検索 「インストール」をクリック コミットの変更を表示する 「axios」を例にするので、ローカルにクローンしました。\n「ソース管理」をクリック 「COMMITS」を開く コミットを開くと変更したファイルが分かるので、見たいファイルを選択 で確認することができます。\nコミット間の差分を表示する 比較元のコミットを右クリックしてメニューから選択すれば OK です。\nCompare with HEAD → HEAD と比較 Compare with Working Tree → 現在作業中のブランチと比較 Select for Compare → 比較元として選択 「Select for Compare」を選択した場合、比較先のコミットを右クリックしたとき「Compare with Selected」があるので選択します。\n「SEARCH \u0026amp; COMPARE」で、コミット間のファイルとその内容の差分が確認できます。\n比較を消したいときは、「Clear Results」アイコンをクリックします。\n１つのファイルを過去と比較するには 対象のファイルを開いてから「ソース管理」を選択後、「FILE HISTORY」の方で同様に確認できます。\n複数コミットに渡ったレビューするときに、レビューする側される側どちらになった場合でも、知っておくと便利だと思います。\nVisual Studio Code実践ガイド (Amazon) Visual Studio Codeデバッグ技術 (Amazon) Docker Desktop for Windows/Mac と VSCode でつくるクリーンな開発環境構築入門 (Amazon) Software Design (ソフトウェアデザイン) 2021年6月号 (Amazon)\n","date":"2021-11-20T20:27:11+09:00","image":"/images/2021/06/sql.jpg","permalink":"/posts/it/pc/2255/","title":"ソースコードの差分を GitLens で確認するには"},{"content":"TypeScript なのにざっくり定義するのは如何なものか、というのは承知の上でやってみました。\nどれでも当てはまる様にしてみる 以下の定義が簡単ですね。\ntype AnyObj = { [prop: string]: string | number }; プロパティが文字と数値の他にもあるなら、| (OR) で追加します。\n（流石に any は止めておく。）\n以下の様に、どのデータでも当てはまる感じになります。\nfunction example1(data: AnyObj) { // 何らかの処理 } example1({ name: \u0026#39;kuma\u0026#39;, date: \u0026#39;20211113\u0026#39;, }); example1({ id: 9, file: \u0026#39;image.png\u0026#39;, }); 特定のプロパティは型を決める プロパティによって決まった型に制限してみます。\n// 必須項目のプロパティ type RequiredProps = { id: number; title: string; }; // 任意の文字列プロパティ type StringPropNames = \u0026#39;author\u0026#39; | \u0026#39;publisher\u0026#39;; type StringProps = { [prop in StringPropNames]?: string; }; // 任意の数値プロパティ type NumberPropNames = \u0026#39;price\u0026#39; | \u0026#39;stock\u0026#39;; type NumberProps = { [prop in NumberPropNames]?: number; }; // その他何でも type OtherProps = { [prop: string]: string | number; }; // データの型 type BookData = RequiredProps \u0026amp; StringProps \u0026amp; NumberProps \u0026amp; OtherProps; プロパティを、一旦「必須」「文字列（プロパティが無くてもOK）」「数値（プロパティが無くてもOK）」と分けてから結合しましたが、要は\ntype BookData = { id: number; title: string; author?: string; publisher?: string; price?: number; stock?: number; } \u0026amp; { [prop: string]: string | number; }; という宣言と一緒です。\nfunction example2(data: BookData) { // 何らかの処理 } example2({ id: 1, // 必須 title: \u0026#39;kumakuma\u0026#39;, // 必須 author: \u0026#39;kuma\u0026#39;, // publisher は無くてもOK price: 100, // stock は無くてもOK translator: \u0026#39;kuma\u0026#39;, // 未定義の文字列プロパティがあってもOK }); というように、特定のプロパティは型を制限して他は何でも、という定義にできました。\n","date":"2021-11-13T20:45:00+09:00","image":"/images/2021/09/package.jpg","permalink":"/posts/it/pc/2216/","title":"TypeScript：データの型をざっくり定義してみた"},{"content":"qs 使えば RFC3986 の予約文字がエンコードされるのは確認できたのですが、今どきのブラウザなら URLSearchParams が使えるらしい。\nパッケージ無く行けるなら楽ですよね。試しに大丈夫なのか DevTools のコンソール（Chrome 95.0.4638.69）で試してみました。\n・・・アスターリスク(*)だけそのままですね。\nどうやら application/x-www-form-urlencoded のエンコードになるようですが、\n[URLSearchParams](https://url.spec.whatwg.org/#urlsearchparams) objects will percent-encode anything in the application/x-www-form-urlencoded percent-encode set, and will encode U+0020 SPACE as U+002B (+).\nhttps://url.spec.whatwg.org/#interface-urlsearchparams\nThe application/x-www-form-urlencoded percent-encode set contains all code points, except the ASCII alphanumeric, U+002A (*), U+002D (-), U+002E (.), and U+005F (_).\nhttps://url.spec.whatwg.org/#application-x-www-form-urlencoded-percent-encode-set\nの様に記載があるので、特に準拠するRFCは無いんでしょうか？\n確かにエクスクラメーションマーク(!) などはエンコードされたので RFC2396 とは別っぽいんですよね。\nただ、MDN に encodeURIComponent を使って対応する例がありました。\n// https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent function fixedEncodeURIComponent(str) { return encodeURIComponent(str).replace(/[!\u0026#39;()*]/g, function(c) { return \u0026#39;%\u0026#39; + c.charCodeAt(0).toString(16); }); } URLSearchParams なら以下の感じでしょうか。\nアスターリスクだけで良いのか確証が持てませんが。\nfunction fixedURLSearchParams(obj) { return new URLSearchParams(obj) .toString() .replace(/[*]/g, function(c) { return \u0026#39;%\u0026#39; + c.charCodeAt(0).toString(16); }); } 今の所、qs にしておいた方が良さそうですね。\n","date":"2021-11-06T20:44:00+09:00","image":"/images/2021/10/log.jpg","permalink":"/posts/it/pc/2178/","title":"URLSearchParams なら RFC3986 に準拠すると思ったら微妙だった"},{"content":"RESTful API のパラメータを変えて実行したいとき、\ncurl コマンドでゴリゴリ書いて実行する REST Client 拡張機能で VS Code から実行する といったものがありますね。特に REST Client 拡張機能は便利です。\nただ、ログインしていないと実行できない API の場合は、Cookie などで認証済みであることを示す必要があって少し厄介です。\nChrome の DevTools なら、その場合でも比較的簡単に実行できるので紹介します。\nDevTools を開く Chrome のメニューから「その他のツール」-「デベロッパー ツール」で開くか、キー入力で\nWindows なら Ctrl + Shift + I または F12 Mac なら ⌥⌘I または F12 すると DevTools が開くので、「ネットワーク」タブを選択します。\n該当の API を実行して、そのソースコードを取得する ブラウザを操作して、対象の API が実行されるまで進めます。\n今回は例として「POST /api/v1/save/」という架空の API を例にします。\n実行した API を右クリックして「コピー」-「fetch としてコピー」を選択すると、実行する API をコードで取得できます。\nAPI を実行する コードをコピーしたら、後は「コンソール」タブで貼り付けて実行するだけです。\nCookie 等の記述はありませんが、リクエストには出力されます。\nパラメータを変更して実行してみる コードが得られれば、後は好きなように変更してみます。\n今回は以下の前提と変更方針でしてみます。\n前提：/api/v1/save のレスポンスは json で { stationId: \u0026lsquo;xxx\u0026rsquo; } といった応答を返す。 変更方針：API が返した stationId を、次に実行するときのパラメータに使う。 async function execApi(stationId) { const nextStationId = await fetch(\u0026#34;http://localhost:3000/api/v1/save/\u0026#34;, { \u0026#34;headers\u0026#34;: { \u0026#34;accept\u0026#34;: \u0026#34;application/json, text/plain, */*\u0026#34;, \u0026#34;accept-language\u0026#34;: \u0026#34;ja,en-US;q=0.9,en;q=0.8\u0026#34;, \u0026#34;content-type\u0026#34;: \u0026#34;application/json\u0026#34;, \u0026#34;sec-ch-ua\u0026#34;: \u0026#34;\\\u0026#34;Google Chrome\\\u0026#34;;v=\\\u0026#34;95\\\u0026#34;, \\\u0026#34;Chromium\\\u0026#34;;v=\\\u0026#34;95\\\u0026#34;, \\\u0026#34;;Not A Brand\\\u0026#34;;v=\\\u0026#34;99\\\u0026#34;\u0026#34;, \u0026#34;sec-ch-ua-mobile\u0026#34;: \u0026#34;?0\u0026#34;, \u0026#34;sec-ch-ua-platform\u0026#34;: \u0026#34;\\\u0026#34;macOS\\\u0026#34;\u0026#34;, \u0026#34;sec-fetch-dest\u0026#34;: \u0026#34;empty\u0026#34;, \u0026#34;sec-fetch-mode\u0026#34;: \u0026#34;cors\u0026#34;, \u0026#34;sec-fetch-site\u0026#34;: \u0026#34;same-origin\u0026#34; }, \u0026#34;referrer\u0026#34;: \u0026#34;http://localhost:3000/\u0026#34;, \u0026#34;referrerPolicy\u0026#34;: \u0026#34;strict-origin-when-cross-origin\u0026#34;, \u0026#34;body\u0026#34;: `{\\\u0026#34;data\\\u0026#34;:[{\\\u0026#34;stationId\\\u0026#34;:\\\u0026#34;${stationId}\\\u0026#34;]}`, \u0026#34;method\u0026#34;: \u0026#34;POST\u0026#34;, \u0026#34;mode\u0026#34;: \u0026#34;cors\u0026#34;, \u0026#34;credentials\u0026#34;: \u0026#34;include\u0026#34; }).then(async res =\u0026gt; await res.json().then(data =\u0026gt; data.stationId)); return nextStationId; } async function main() { const nextStationId = await execApi(\u0026#39;001\u0026#39;); await execApi(nextStationId); } main(); というようにコードで書けるので、API の結果をパラメータに使って再実行したいといったことも簡単です。\n他にも、POST や PUT でデータ登録できる API があるとき、ダミーの連番データを一括登録する、といった場合も便利そうです。\n","date":"2021-10-30T20:33:00+09:00","image":"/images/2021/06/props.jpg","permalink":"/posts/it/pc/2095/","title":"RESTful API のパラメータを変えての実行にも DevTools が便利でした"},{"content":"なぜエンコード処理を変更するのか？\n→ RFC3986の予約文字が、いくつかエンコードされないから。\nということで、実際に axios で試してみた要求がこちら。\naxios.get(\u0026#39;/test\u0026#39;, { params: { a: \u0026#34;:/?#[]@!$\u0026amp;\u0026#39;()*+,;=\u0026#34; } }); 結果がこちら。\n/test?a=:%2F%3F%23[]%40!$%26%27()*%2B,%3B%3D エンコードされていない文字（:[]!$()*,）がありますね。\nこうなっている背景が確認できていませんが、対策しないまま受け付けてくれないサーバーがあったとしても、基本的には違反しているクライアント側（axiosを使っている側）が直すことになるんじゃないかと思います。\nそして、axios側はエンコード処理を設定できるようにしてくれているので、その部分を設定すれば OK です。\nということで、axios 0.23.0 で試してみました。\n要求毎に設定するとメンテナンス性が下がるので、ログの場合と同様に共通化で対応します。\nqs を使う qs 6.10.1 で試してみました。\nデフォルトは RFC3986 に従い、オプションで RFC1738 にできます。\nimport axios from \u0026#39;axios\u0026#39;; import qs from \u0026#39;qs\u0026#39;; function paramsSerializer(params: any) { return qs.stringify(params); // return qs.stringify(params, { format: \u0026#39;RFC1738\u0026#39; }); } axios.interceptors.request.use( config =\u0026gt; ({ ...config, paramsSerializer, }), ); 結果は以下の通り、エンコードされました。\n/test?a=%3A%2F%3F%23%5B%5D%40%21%24%26%27%28%29%2A%2B%2C%3B%3D jQuery を使う ついでに jQuery 3.6.0 でも試してみました。\n想定する用途が違うからか、何文字かエンコードされなかったので、qs にしておけば良さそうです。\nimport $ from \u0026#39;jquery\u0026#39;; function paramsSerializer(params: any) { return $.param(params); } axios.interceptors.request.use( config =\u0026gt; ({ ...config, paramsSerializer, }), ); 結果は\n/test?a=%3A%2F%3F%23%5B%5D%40!%24%26%27()*%2B%2C%3B%3D ","date":"2021-10-23T20:16:00+09:00","image":"/images/2021/10/log.jpg","permalink":"/posts/it/pc/2051/","title":"axios でURLパラメータのエンコード処理を変更するには"},{"content":"予め axios.interceptors.request.use、axios.interceptors.response.use を設定すれば OK です。\nimport axios from \u0026#39;axios\u0026#39;; axios.interceptors.request.use( config =\u0026gt; { const { method, url, } = config; const { params = config.params, data, } = config.data || {}; console.log(method, url, params, data); return config; }); axios.interceptors.response.use( res =\u0026gt; res, err =\u0026gt; { const { config, response, } = err; const { method, url, } = config || {}; if (response) { const { status, statusText, data, } = response; console.log(method, url, status, statusText, data); } else { console.log(method, url, err.toString()); } return Promise.reject(err); }); 要求のログ出力（4〜13行目）、失敗のログ出力（20〜38行目）をしています。\n正常なレスポンスならログ出力も不要かと思うので、何もしていません。（18行目）\n","date":"2021-10-16T19:19:03+09:00","image":"/images/2021/10/log.jpg","permalink":"/posts/it/pc/2042/","title":"axios で要求/応答のログ出力を共通化するには"},{"content":"複数の非同期処理を並列で実行して、全て終わったらその結果を処理する場合の書き方になります。\n【Promise.all】全て成功で終わったら処理する async function main() { // 非同期処理を5個作成 const procs = Array .from({ length: 5 }) .map((_, i) =\u0026gt; new Promise\u0026lt;number\u0026gt;((resolve) =\u0026gt; { const seq = i + 1; if (seq % 2 === 1) { // 奇数個番目の処理なら、何番目か返す resolve(seq); } else { // 偶数個番目の処理なら、10倍で返す resolve(seq * 10); } })); const results = await Promise.all(procs); console.log(results); } main(); 非同期処理が返した値が results に配列で渡されるので、コンソール出力は\n[ 1, 20, 3, 40, 5 ] 【Promse.allSettled】全て終わったら処理する Promise.all では、実行した非同期処理のいずれかでエラーがあったとき処理できないので、成功した結果と失敗した結果をそれぞれ処理したい場合は Promise.allSettled を使います。\nasync function main() { // 非同期処理を5個作成 const procs = Array .from({ length: 5 }) .map((_, i) =\u0026gt; new Promise\u0026lt;number\u0026gt;((resolve, reject) =\u0026gt; { const seq = i + 1; if (seq % 2 === 1) { // 奇数個番目の処理なら、何番目か返す resolve(seq); } else { // 偶数個番目の処理なら、エラーを返す reject(new Error(`${seq} is even`)); } })); const results = await Promise.allSettled(procs); console.log(\u0026#39;成功\u0026#39;, results .filter((result) =\u0026gt; result.status === \u0026#39;fulfilled\u0026#39;) .map((result) =\u0026gt; (result as PromiseFulfilledResult\u0026lt;number\u0026gt;).value) ); console.log(\u0026#39;失敗\u0026#39;, results .filter((result) =\u0026gt; result.status === \u0026#39;rejected\u0026#39;) .map((result) =\u0026gt; (result as PromiseRejectedResult).reason.toString()) ); } main(); 非同期処理の結果が results に配列で渡されます。\nstauts が \u0026lsquo;fulfilled\u0026rsquo; なら成功で value に返された値が入り、 \u0026lsquo;rejected\u0026rsquo; なら失敗で reason が存在しますので、コンソール出力は\n成功 [ 1, 3, 5 ] 失敗 [ \u0026#39;Error: 2 is even\u0026#39;, \u0026#39;Error: 4 is even\u0026#39; ] プロを目指す人のためのTypeScript入門 (Amazon) ","date":"2021-10-09T20:31:00+09:00","image":"/images/2021/06/sql.jpg","permalink":"/posts/it/pc/2014/","title":"TypeScript で複数の非同期処理の結果を後続の処理で使うには"},{"content":"「らじる★らじる」は NHK のラジオ放送をインターネットで提供しているサービスです。\nその中に、聴き逃し番組を聴取できるものがあり、検索することもできます。\nただ、個人的には探したい単語を毎回検索するのが面倒なので、番組のデータを取得することができないか探してみました。\nNHKが提供するAPI NHK番組表APIというものが提供されていました。\n今後放送される番組を探すのに良さそうですが、聞き逃しの対象になっている番組なのか不明なので、今回はパスしました。\nらじる★らじる の聞き逃し番組を触ってAPIを探す 聞き逃し番組のページを触って分かった API が以下でした。\n今後も使えるかは分かりませんが、現時点(2021/10/02)として残しておきます。\n・聞き逃し番組の一覧\nhttps://www.nhk.or.jp/radioondemand/json/index_v3/index.json\n項目名 データから想像される内容 site_id 番組固有の4桁数字？ program_name 番組名 program_name_kana 番組名の平仮名 media_code メディアコード？ corner_id コーナーID？ corner_name コーナー名？ thumbnail_p サムネイル画像のリンク（親？） thumbnail_c サムネイル画像のリンク（子？） open_time 聞き逃し配信開始日時？ close_time 聞き逃し配信終了日時？ onair_date 番組放送日？ link_url リンクURL？　空のみ start_time 番組放送日時？ update_time 更新日時？ dev 本API実行日時？ detail_json 番組の詳細へのリンク？ ・detail_json リンクの内容\n項目名 データから想像される内容 main site_id 番組固有の4桁数字？ program_name 番組名 mode モード？ media_type メディアタイプ？ media_code メディアコード？ media_name メディア名？ site_detail 番組紹介文？ thumbnail_p サムネイル画像のリンク（親？） thumbnail_c サムネイル画像のリンク（子？） schedule 放送日時 official_url 番組公式ページのURL？ share_url この番組の聞き逃しページのURL？ corner_id コーナーID？ corner_name コーナー名？ corner_detail コーナー詳細？ program_index true / false ？ cast キャスト？ dev 本API実行日時？ detail_list 聞き逃し配信中のリスト？内容省略 ","date":"2021-10-02T20:13:57+09:00","image":"/images/2021/08/json.jpg","permalink":"/posts/it/pc/1979/","title":"らじる★らじる の聞き逃し番組のデータを取得するには"},{"content":"結果、package.json に以下の様な proxy を設定すれば OK という話なのですが、やりたいことから順に説明していきます。\n\u0026#34;proxy\u0026#34;: \u0026#34;http://192.168.0.10/\u0026#34; API のポートが 80 で無く、例えば 8080 なら、それも指定します。\n\u0026#34;proxy\u0026#34;: \u0026#34;http://192.168.0.10:8080/\u0026#34; やりたいこと 開発サーバー (192.168.0.10)\nバックエンドの API のサービスが用意できている 開発したフロントエンドのアプリもビルドして配置する 開発するPC (192.168.0.20)\nフロントエンドのアプリを create-react-app で生成されるものを元に開発する ローカルで実行する http://localhost:3000/ からサーバーの API を呼びたい という太字の部分が「やりたいこと」になります。\n何故なら単純に開発するPC側から、\nfetch(\u0026#39;http://192.168.0.10/v1/item\u0026#39;) しても、\nAccess to fetch at \u0026#39;http://192.168.0.10/v1/item\u0026#39; from origin \u0026#39;http://localhost:3000\u0026#39; has been blocked by CORS policy: No \u0026#39;Access-Control-Allow-Origin\u0026#39; header is present on the requested resource. If an opaque response serves your needs, set the request\u0026#39;s mode to \u0026#39;no-cors\u0026#39; to fetch the resource with CORS disabled. というエラーでアクセスはできません。\n試してみる：バックエンドの作成 簡単なものを express で作ってみます。\nconst express = require(\u0026#39;express\u0026#39;); const app = express(); const port = 80; app.get(\u0026#39;/v1/item\u0026#39;, (req, res) =\u0026gt; { const resObj = { timestamp: new Date().toString(), message: \u0026#39;Hello World!\u0026#39; }; res.send(resObj); }); app.listen(port, () =\u0026gt; { console.log(`listening:${port}`) }); サーバー側で次のコマンドで実行\nnode app.js 試しにブラウザからアクセスしてみて、\n{\u0026#34;timestamp\u0026#34;:\u0026#34;Sat Sep 25 2021 21:28:05 GMT+0900 (日本標準時)\u0026#34;,\u0026#34;message\u0026#34;:\u0026#34;Hello World!\u0026#34;} の様な文字列が表示されれば OK です。\n試してみる：フロントエンドの作成 ベースは create-react-app で生成します。\nnpx create-react-app front-example --template typescript 修正するのは、App.tsx と package.json になります。\nimport React, {useEffect, useState} from \u0026#39;react\u0026#39;; function App() { const [value, setValue] = useState\u0026lt;string\u0026gt;(); useEffect(() =\u0026gt; { fetch(\u0026#39;/v1/item\u0026#39;) .then(async res =\u0026gt; setValue(JSON.stringify(await res.json(), null, 2))); }, []); return ( \u0026lt;pre\u0026gt;{value}\u0026lt;/pre\u0026gt; ); } export default App; { \u0026#34;proxy\u0026#34;: \u0026#34;http://192.168.0.10/\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;front-example\u0026#34;, \u0026#34;version\u0026#34;: \u0026#34;0.1.0\u0026#34;, \u0026#34;private\u0026#34;: true, (以下省略) ローカルで実行します。\nyarn start ブラウザの http://localhost:3000/ に\n{ \u0026#34;timestamp\u0026#34;: \u0026#34;Sat Sep 25 2021 21:40:27 GMT+0900 (日本標準時)\u0026#34;, \u0026#34;message\u0026#34;: \u0026#34;Hello World!\u0026#34; } の様な文字が表示されれば OK です。\n","date":"2021-09-25T21:52:50+09:00","image":"/images/2021/05/js2.jpg","permalink":"/posts/it/pc/1945/","title":"ローカルで実行した React アプリでもサーバーの API を利用するには"},{"content":"jQuery を使う jQuery が使える環境であれば簡単です。\n$(`\u0026lt;div\u0026gt;${\u0026#34;タグを含む文字列\u0026#34;}\u0026lt;/div\u0026gt;`).text() とすればタグを取り除けます。\nなので、\nimport $ from \u0026#39;jquery\u0026#39;; const html = ` \u0026lt;div\u0026gt; \u0026lt;span\u0026gt;テスト\u0026lt;/span\u0026gt; \u0026lt;div\u0026gt; \u0026lt;span\u0026gt;何をしているか\u0026lt;a href=\u0026#34;/posts/it/pc/1931/\u0026#34; target=\u0026#34;_blank\u0026#34;\u0026gt;詳細はこちら\u0026lt;/a\u0026gt;\u0026lt;/span\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; `; console.log($(`\u0026lt;div\u0026gt;${html}\u0026lt;/div\u0026gt;`).text()); としたときのコンソール出力は\nテスト 何をしているか詳細はこちら となります。\n※ img など、更にリソースを取得するタグの場合は、そのリソースの読み込みはされるので、先に別途対処した方が良いかも。\nサーバーサイドでやりたいときは サーバーサイドの場合、jsdom でDOMを用意します。\nimport { JSDOM } from \u0026#39;jsdom\u0026#39;; import jquery from \u0026#39;jquery\u0026#39;; const dom = new JSDOM(\u0026#39;\u0026lt;html\u0026gt;\u0026lt;body\u0026gt;\u0026lt;/body\u0026gt;\u0026lt;/html\u0026gt;\u0026#39;); const $ = jquery(dom.window); const html = ` \u0026lt;div\u0026gt; \u0026lt;span\u0026gt;テスト\u0026lt;/span\u0026gt; \u0026lt;div\u0026gt; \u0026lt;span\u0026gt;何をしているか\u0026lt;a href=\u0026#34;/posts/it/pc/1931/\u0026#34; target=\u0026#34;_blank\u0026#34;\u0026gt;詳細はこちら\u0026lt;/a\u0026gt;\u0026lt;/span\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; `; console.log($(`\u0026lt;div\u0026gt;${html}\u0026lt;/div\u0026gt;`).text()); ","date":"2021-09-18T20:34:00+09:00","image":"/images/2021/09/html.jpg","permalink":"/posts/it/pc/1931/","title":"JavaScript で HTML タグを取り除くには"},{"content":"作成した React コンポーネントの確認に、Storybook が便利です。\n更に、Redux Toolkit を導入している場合はどうするか、を確認してみました。\nRedux Toolkit のプロジェクトを作成する 公式で案内されている Typescript の例をそのまま使ってサクっと作成します。\n# Redux + TypeScript template npx create-react-app my-app --template redux-typescript プロジェクトに Storybook を追加する こちらも公式で案内されている sb init でサクっと導入します。\ncd my-app npx sb init Storybook を導入したことで、元プロジェクトをローカルで実行する yarn start がコケてしまうので、以下の .env ファイルを作成してエラーになってしまうのを回避します。\nSKIP_PREFLIGHT_CHECK=true プロジェクトのコンポーネントで Story を作成する 元プロジェクトにある「Counter.tsx」コンポーネントを Story に追加する為に、以下のように「Counter.stories.tsx」を作成しました。\nimport { Provider } from \u0026#39;react-redux\u0026#39;; import { ComponentStory, ComponentMeta } from \u0026#39;@storybook/react\u0026#39;; import { store } from \u0026#39;../../app/store\u0026#39;; import { Counter } from \u0026#39;./Counter\u0026#39;; export default { title: \u0026#39;Example/Counter\u0026#39;, component: Counter, argTypes: { }, decorators: [ (Story) =\u0026gt; ( \u0026lt;Provider store={store}\u0026gt; \u0026lt;Story /\u0026gt; \u0026lt;/Provider\u0026gt; ), ], } as ComponentMeta\u0026lt;typeof Counter\u0026gt;; const Template: ComponentStory\u0026lt;typeof Counter\u0026gt; = () =\u0026gt; \u0026lt;Counter /\u0026gt;; export const Default = Template.bind({}); Redux を使っている事で変わる点は、Provider で包む必要がある所です。（13〜15行目）\nこれをしない場合、以下のエラーになりました。\nError could not find react-redux context value; please ensure the component is wrapped in a \u0026lt;Provider\u0026gt; Storybook を実行する yarn storybook で実行します。\nと表示され、Counter コンポーネントの表示や動作を確認することができます。\nCounter コンポーネントに props は無いので、Storybook の Controls タブで設定を変えて表示の変化を確認することはできません。\nが、ボタンのクリックも処理されてストア及び表示に反映されますので、ストアを使っていることで挙動を確認し易くなる場合もあるのかな、とも思います。\nこの辺りはコンポーネントの設計次第でしょうか。\n","date":"2021-09-11T20:41:00+09:00","image":"/images/2021/09/localhost_6006__path_story_example-counter-default-2.png","permalink":"/posts/it/pc/1895/","title":"React + Redux Toolkit + Storybook のプロジェクトを作成する"},{"content":"package.json があるディレクトリで下記コマンドを実行\nyarn upgrade 対話式なら\nyarn upgrade-interactive ただ、yarn.lock は更新されるが package.json が更新されないので、\nnpx syncyarnlock -s -k -s で package.json を更新 -k で ^ のバージョン範囲も残す で、package.json も更新できる。\n","date":"2021-09-04T12:55:12+09:00","image":"/images/2021/09/package.jpg","permalink":"/posts/it/pc/1884/","title":"package.json のパッケージを更新する"},{"content":"Dockerイメージが continuumio/anaconda3 で公開されているので利用はとても簡単です。\nJupyterLab（Jupyer Notebook の後継）を使いたいなら\ndocker run -it -p 8888:8888 continuumio/anaconda3 /bin/bash -c \u0026#34;\\ conda install jupyter -y --quiet \u0026amp;\u0026amp; \\ mkdir -p /opt/notebooks \u0026amp;\u0026amp; \\ jupyter lab \\ --notebook-dir=/opt/notebooks --ip=\u0026#39;*\u0026#39; --port=8888 \\ --no-browser --allow-root \\ --NotebookApp.token=\u0026#39;\u0026#39;\u0026#34; Jupyter Notebook の方なら\ndocker run -it -p 8888:8888 continuumio/anaconda3 /bin/bash -c \u0026#34;\\ conda install jupyter -y --quiet \u0026amp;\u0026amp; \\ mkdir -p /opt/notebooks \u0026amp;\u0026amp; \\ jupyter notebook \\ --notebook-dir=/opt/notebooks --ip=\u0026#39;*\u0026#39; --port=8888 \\ --no-browser --allow-root \\ --NotebookApp.token=\u0026#39;\u0026#39;\u0026#34; で実行後、ブラウザから http://localhost:8888/ にアクセスすれば OK です。\nroot ユーザーの許可、トークン無しのログインを許すオプションを付けているので、閉じた環境のみで使うなどの注意はしてください。\n必要なパッケージを毎回追加するのは面倒 → Dockerイメージをカスタマイズ\n前回の様な事をする場合、毎回パッケージをインストールするのは面倒なので、追加済みの Docker イメージを作っておくと便利です。\nということで以下のような Dockerfile を作成して、パッケージを追加済みのイメージを作りました。今回追加したパッケージは、\nsqlite3 scikit-learn mplfinance yahooquery です。\nFROM continuumio/anaconda3 RUN apt-get update \\ \u0026amp;\u0026amp; apt-get install --no-install-recommends -y \\ sqlite3 \\ \u0026amp;\u0026amp; apt-get clean \\ \u0026amp;\u0026amp; rm -r /var/lib/apt/lists/* \\ \u0026amp;\u0026amp; ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime RUN pip install --upgrade pip \u0026amp;\u0026amp; \\ pip install scikit-learn \u0026amp;\u0026amp; \\ pip install mplfinance \u0026amp;\u0026amp; \\ pip install yahooquery RUN conda install jupyter -y --quiet \u0026amp;\u0026amp; \\ mkdir -p /opt/notebooks WORKDIR /opt/notebooks EXPOSE 8888 ENTRYPOINT [\u0026#34;jupyter\u0026#34;, \u0026#34;lab\u0026#34;, \u0026#34;--ip=\u0026#39;*\u0026#39;\u0026#34;, \u0026#34;--port=8888\u0026#34;, \u0026#34;--no-browser\u0026#34;, \u0026#34;--allow-root\u0026#34;, \u0026#34;--NotebookApp.token=\u0026#39;\u0026#39;\u0026#34;, \u0026#34;--notebook-dir=/opt/notebooks\u0026#34;] で、この Dockerfile のあるディレクトリで\ndocker image build -t myjn -f Dockerfile . とすれば、イメージが作成できます。イメージの名前は myjn にしてみました。\n実行する場合は、\ndocker run -it \\ -p 8888:8888 \\ -v $PWD/notebooks:/opt/notebooks \\ --rm \\ myjn で OK です。Docker 停止後も JupterLab で作成したファイルが残るように、ホストPC の ./notebooks ディレクトリを使うようにしています。\n今週、ちょっと株価戻ったんですね。\n2023/12/08 追記\n起動時の \u0026ldquo;Would you like to receive official Jupyter news?\u0026rdquo; という通知を止めるには、次のコマンドを先に実行しておきます。\njupyter labextension disable \u0026#34;@jupyterlab/apputils-extension:announcements\u0026#34; 例えば以下の様になります。\ndocker run -it -p 8888:8888 continuumio/anaconda3 /bin/bash -c \u0026#34;\\ conda install jupyter -y --quiet \u0026amp;\u0026amp; \\ mkdir -p /opt/notebooks \u0026amp;\u0026amp; \\ jupyter labextension disable \u0026#34;@jupyterlab/apputils-extension:announcements\u0026#34; \u0026amp;\u0026amp; \\ jupyter lab \\ --notebook-dir=/opt/notebooks --ip=\u0026#39;*\u0026#39; --port=8888 \\ --no-browser --allow-root \\ --NotebookApp.token=\u0026#39;\u0026#39;\u0026#34; Docker Desktop for Windows/Macでつくるクリーンな開発環境構築入門(Python版) (Amazon) ","date":"2021-08-28T17:50:01+09:00","image":"/images/2021/08/python.jpg","permalink":"/posts/it/pc/1842/","title":"Docker で JupyterLab または Jupyter Notebook 使う"},{"content":"JupyterLab または Jupyter Notebook で株価の取得とグラフ化をしてみます。\nJupyterLab または Jupyter Notebook の用意はこちらを参照ください。\n株価を取得する 株価の取得には、yahooquery 2.2.15 を使います。\nfrom yahooquery import Ticker ticker = Ticker(\u0026#39;7203.T\u0026#39;) df = ticker.history(period=\u0026#39;1mo\u0026#39;, interval=\u0026#39;1d\u0026#39;) ３行目： Ticker で、取得したい銘柄のコードを指定します。\n日本の銘柄は \u0026lsquo;7203.T\u0026rsquo; の様にコード（7203=トヨタ自動車）と取引所（T=東京証券取引所）をドットで区切って設定します。 ４行目： history でデータを取得します。\nperiod で期間を指定します。\u0026lsquo;1mo\u0026rsquo; は１ヶ月分になります。\ninterval で間隔を指定します。\u0026lsquo;1d\u0026rsquo; で１日毎になります。 df に以下の様にデータを取得できます。\nラインチャートにする ラインチャートは matplotlib を使って書いてみます。\nimport matplotlib.pyplot as plt fig, ax = plt.subplots() ax.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, loc: \u0026#34;{:,}\u0026#34;.format(int(x)))) plt.xticks(rotation=45) plt.plot(df.loc[\u0026#39;7203.T\u0026#39;].close) plt.show() ３−４行目：Y軸の数値を３桁区切りで表示させる ５行目：横軸のラベルを45°に傾ける ６行目：グラフ描画\n引数のデータの指定が df.loc[\u0026lsquo;7203.T\u0026rsquo;].close と分かりづらいですが、中身は以下のようになっています。 グラフにした結果は、\nローソク足チャートにする ローソク足チャートは mplfinance を使って書いてみます。\nimport pandas as pd import mplfinance as mpf dg = df.loc[\u0026#39;7203.T\u0026#39;].copy() dh = dg.reset_index() dh.rename(columns=str.capitalize, inplace=True) dh[\u0026#39;Date\u0026#39;] = pd.to_datetime(dh[\u0026#39;Date\u0026#39;]) di = dh.reset_index() di.set_index(\u0026#39;Date\u0026#39;, inplace=True) di.drop(\u0026#39;Adjclose\u0026#39;, axis=1, inplace=True) mpf.plot(di, type=\u0026#39;candle\u0026#39;) mpf.show() ４−１２行目：１４行目で使う為に、データの変換 １４行目：グラフ描画 ほとんどデータ変換に費やしています。変換の前と変換後で見ると、次の通りです。\n変換前\n変換後\n７行目：列名の頭文字を大文字に変換（ date → Date ） ８行目：日付の列を datetime64 型に変換 １０−１１行目：インデックスを Date に設定 １２行目：不要な列（Adjclose）を削除 グラフにした結果は、\nPythonでできる！ 株価データ分析 (Amazon) Docker Desktop for Windows/Macでつくるクリーンな開発環境構築入門(Python版) (Amazon) ファイナンス機械学習 (Amazon) アセットマネージャーのためのファイナンス機械学習 (Amazon)\n","date":"2021-08-21T20:49:00+09:00","image":"/images/2021/08/stock.jpg","permalink":"/posts/it/pc/1803/","title":"Python で株価を取得してグラフにする"},{"content":"いきなりタイトルに反しますが、大抵 ESLint の no-await-in-loop や no-restricted-syntax の対象になるので、並列に処理して問題無い場合は Promise.all を使い、ループ内の await を避けるリファクタリングをしましょう。\nforEach では await が使えない まず、Array.prototype.forEach によるループで await は諦めましょう。\nMDN の解説にあるコード例の通り、await しても意図した動作になりません。\n実行する非同期処理 挙動を確認する非同期処理は次の通り。\n非同期処理の開始時にコンソール出力 非同期処理が完了後（指定時間経過後）にコンソール出力 非同期処理の結果として、現在待った時間と、次に待つ時間を返す という、順次実行する必要がある（並列に処理できない）場合を考えてみます。\nconst asyncSub = (n) =\u0026gt; new Promise((resolve) =\u0026gt; { console.log(`${n} start.`); setTimeout(() =\u0026gt; { console.log(`${n} resolve.`); resolve({ current: n, next: n - 3 }); }, n); }); 期待する挙動 まずは期待する処理をループ無しで実行してみます。\nconst exec = async () =\u0026gt; { const r1 = await asyncSub(8); console.log(`${r1.current} end.`); const r2 = await asyncSub(r1.next); console.log(`${r2.current} end.`); const r3 = await asyncSub(r2.next); console.log(`${r3.current} end.`); }; exec(); 結果は当然期待どおり、順番に処理されます。\n8 start. 8 resolve. 8 end. 5 start. 5 resolve. 5 end. 2 start. 2 resolve. 2 end. then で処理する いきなりループ内でも await でも無いんかい。\nまぁ、処理がループしてますので。\nawait は\u0026hellip;できてません。\n結果自体は期待通りですが、読みやすさは今ひとつ。\nconst exec = (n) =\u0026gt; { asyncSub(n) .then((r) =\u0026gt; { console.log(`${r.current} end.`); if (r.next \u0026gt; 0) { exec(r.next); } }); }; exec(8); while で処理する 結果は期待通りですが、ESLint で no-await-in-loop の対象になるので、コメントで指摘を除外します。\nESLint のドキュメント上も、このように順序が関係するループなら除外してOKの様です。\nWhen Not To Use It\nIn many cases the iterations of a loop are not actually independent of each-other. For example, the output of one iteration might be used as the input to another. Or, loops may be used to retry asynchronous operations that were unsuccessful. Or, loops may be used to prevent your code from sending an excessive amount of requests in parallel. In such cases it makes sense to use await within a loop and it is recommended to disable the rule via a standard ESLint disable comment.\n(多くの場合、ループの反復は実際には互いに独立していません。たとえば、ある反復の出力が別の反復への入力として使用される場合があります。または、ループを使用して、失敗した非同期操作を再試行することもできます。または、ループを使用して、コードが過剰な量のリクエストを並行して送信するのを防ぐことができます。このような場合await、ループ内で使用するのが理にかなっており、標準のESLint無効化コメントを使用してルールを無効にすることをお勧めします。by Google翻訳)\nhttps://eslint.org/docs/rules/no-await-in-loop\nconst exec = async () =\u0026gt; { let s = ({ next: 8 }); while (s.next \u0026gt; 0) { // eslint-disable-next-line no-await-in-loop s = await asyncSub(s.next); console.log(`${s.current} end.`); } }; exec(); for で処理する while と変わりません。\nconst exec = async () =\u0026gt; { for (let s = ({ next: 8 }); s.next \u0026gt; 0;) { // eslint-disable-next-line no-await-in-loop s = await asyncSub(s.next); console.log(`${s.current} end.`); } }; exec(); for await で処理する(1) 結果は期待通りですが、イテレータを作る労力や、前の非同期処理の結果を引き継ぐ部分も微妙ですし、ESLint に no-restricted-syntax の対象になります。\nfor で処理した方が分かりやすいですね。for await の良い使い所って何なんだろうか。\nconst exec = async () =\u0026gt; { let n = 8; const asyncIterable = { [Symbol.asyncIterator]() { return { next() { if (n \u0026gt; 0) { return asyncSub(n) .then((r) =\u0026gt; ({ value: r, done: false })); } return Promise.resolve({ done: true }); }, }; }, }; for await (const r of asyncIterable) { n = r.next; console.log(`${r.current} end.`); } }; exec(); Promise.all で処理する 基本的にはコレに置き換えられないか考えることになると思いますが、\n前の非同期処理の結果に関係なく用意できて 並列に実行されても良く 全非同期処理の完了後に結果が処理できれば良い 必要があるので、今回例にした非同期処理では無理です。\nconst exec = async () =\u0026gt; { const procs = [8, 5, 2] .map((n) =\u0026gt; asyncSub(n)); const ret = await Promise.all(procs); ret.map(r =\u0026gt; console.log(`${r.current} end.`)); }; exec(); 結果は次の通り、並列に処理されてますね。\n8 start. 5 start. 2 start. 2 resolve. 5 resolve. 8 resolve. 8 end. 5 end. 2 end. 処理が済んだものから結果を返して終わってます。\n結果の処理は、全ての非同期処理結果が終わってからになっています。\nfor await で処理する(2) 次のようにした for wait は Promise.all と「結果は同じ」でした。\nESLint で no-restricted-syntax を指摘されるので Promise.all が良いでしょう。\nというか「結果が同じ」になることが分かりづらいので、Promise.all にした方が良いでしょう。\nどの辺りを分かりづらく感じるかというと\n非同期処理が並列に実行される\n→ 非同期処理を配列に用意している時点から非同期処理は始まっている 全非同期処理が済んでから結果が処理される（ループ内の処理がされる） 所です。\nconst exec = async () =\u0026gt; { const procs = [8, 5, 2] .map((n) =\u0026gt; asyncSub(n)); for await (const r of procs) { console.log(`${r.current} end.`); } }; exec(); 非同期処理を await で待つとき、ループなどで勘違いを起こさないようにしたいですね。\nプロを目指す人のためのTypeScript入門 (Amazon) ","date":"2021-08-14T20:14:00+09:00","image":"/images/2021/08/loop.jpg","permalink":"/posts/it/pc/1643/","title":"JavaScript のループ内で await したい"},{"content":"既にある JSON データを TypeScript で扱うとき、型定義を自分で書くのは億劫ですよね。\nなので、やってくれるツールを Visual Studio Code の機能拡張で探しました。\nダウンロード数の多さを参考に、それなりに使われていそうなものを２つ試してみました。\n扱う JSON データを用意する 使うデータは、radiko の Web API が返してくるデータにしました。\nただ、返されるデータ形式は XML なので、JSON にする必要があります。\n１つ目は、地域の放送局を返す API\nhttps://radiko.jp/v3/station/region/full.xml\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34; ?\u0026gt; \u0026lt;region\u0026gt; \u0026lt;stations ascii_name=\u0026#34;HOKKAIDO TOHOKU\u0026#34; region_id=\u0026#34;hokkaido-tohoku\u0026#34; region_name=\u0026#34;北海道・東北\u0026#34;\u0026gt; \u0026lt;station\u0026gt;\u0026lt;id\u0026gt;HBC\u0026lt;/id\u0026gt; \u0026lt;name\u0026gt;ＨＢＣラジオ\u0026lt;/name\u0026gt; \u0026lt;ascii_name\u0026gt;HBC RADIO\u0026lt;/ascii_name\u0026gt; \u0026lt;ruby\u0026gt;えいちびーしーらじお\u0026lt;/ruby\u0026gt; \u0026lt;areafree\u0026gt;1\u0026lt;/areafree\u0026gt; \u0026lt;timefree\u0026gt;1\u0026lt;/timefree\u0026gt; 〜以下略〜 ２つ目は、ニッポン放送の週間番組表を返す API\nhttps://radiko.jp/v3/program/station/weekly/LFR.xml\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;radiko\u0026gt; \u0026lt;ttl\u0026gt;1800\u0026lt;/ttl\u0026gt; \u0026lt;srvtime\u0026gt;1628297768\u0026lt;/srvtime\u0026gt; \u0026lt;stations\u0026gt; \u0026lt;station id=\u0026#34;LFR\u0026#34;\u0026gt; \u0026lt;name\u0026gt;ニッポン放送\u0026lt;/name\u0026gt; \u0026lt;progs\u0026gt; \u0026lt;date\u0026gt;20210731\u0026lt;/date\u0026gt; \u0026lt;prog id=\u0026#34;9580196668\u0026#34; master_id=\u0026#34;\u0026#34; ft=\u0026#34;20210731050000\u0026#34; to=\u0026#34;20210731074000\u0026#34; ftl=\u0026#34;0500\u0026#34; tol=\u0026#34;0740\u0026#34; dur=\u0026#34;9600\u0026#34;\u0026gt; \u0026lt;title\u0026gt;徳光和夫　とくモリ！歌謡サタデー\u0026lt;/title\u0026gt; \u0026lt;url\u0026gt;https://www.1242.com/toku/\u0026lt;/url\u0026gt; \u0026lt;url_link\u0026gt;\u0026lt;/url_link\u0026gt; \u0026lt;failed_record\u0026gt;0\u0026lt;/failed_record\u0026gt; \u0026lt;ts_in_ng\u0026gt;0\u0026lt;/ts_in_ng\u0026gt; \u0026lt;ts_out_ng\u0026gt;0\u0026lt;/ts_out_ng\u0026gt; 〜以下略〜 を取得し、XML から JSON への変換には、xml-js (https://github.com/nashwaan/xml-js)を使用しました。\n変換に使ったコードは以下になります。\nconst xml = \u0026#39;Web API が返した XML\u0026#39;; const json = xmljs.xml2json( xml, { compact: true, spaces: 2, }, ); 結果はそれぞれ以下の通り。\n{ \u0026#34;_declaration\u0026#34;: { \u0026#34;_attributes\u0026#34;: { \u0026#34;version\u0026#34;: \u0026#34;1.0\u0026#34;, \u0026#34;encoding\u0026#34;: \u0026#34;UTF-8\u0026#34; } }, \u0026#34;region\u0026#34;: { \u0026#34;stations\u0026#34;: [ { \u0026#34;_attributes\u0026#34;: { \u0026#34;ascii_name\u0026#34;: \u0026#34;HOKKAIDO TOHOKU\u0026#34;, \u0026#34;region_id\u0026#34;: \u0026#34;hokkaido-tohoku\u0026#34;, \u0026#34;region_name\u0026#34;: \u0026#34;北海道・東北\u0026#34; }, \u0026#34;station\u0026#34;: [ { \u0026#34;id\u0026#34;: { \u0026#34;_text\u0026#34;: \u0026#34;HBC\u0026#34; }, \u0026#34;name\u0026#34;: { \u0026#34;_text\u0026#34;: \u0026#34;ＨＢＣラジオ\u0026#34; }, \u0026#34;ascii_name\u0026#34;: { \u0026#34;_text\u0026#34;: \u0026#34;HBC RADIO\u0026#34; }, \u0026#34;ruby\u0026#34;: { \u0026#34;_text\u0026#34;: \u0026#34;えいちびーしーらじお\u0026#34; }, \u0026#34;areafree\u0026#34;: { \u0026#34;_text\u0026#34;: \u0026#34;1\u0026#34; }, \u0026#34;timefree\u0026#34;: { \u0026#34;_text\u0026#34;: \u0026#34;1\u0026#34; }, 〜以下略〜 { \u0026#34;_declaration\u0026#34;: { \u0026#34;_attributes\u0026#34;: { \u0026#34;version\u0026#34;: \u0026#34;1.0\u0026#34;, \u0026#34;encoding\u0026#34;: \u0026#34;UTF-8\u0026#34; } }, \u0026#34;radiko\u0026#34;: { \u0026#34;ttl\u0026#34;: { \u0026#34;_text\u0026#34;: \u0026#34;1800\u0026#34; }, \u0026#34;srvtime\u0026#34;: { \u0026#34;_text\u0026#34;: \u0026#34;1628299563\u0026#34; }, \u0026#34;stations\u0026#34;: { \u0026#34;station\u0026#34;: { \u0026#34;_attributes\u0026#34;: { \u0026#34;id\u0026#34;: \u0026#34;LFR\u0026#34; }, \u0026#34;name\u0026#34;: { \u0026#34;_text\u0026#34;: \u0026#34;ニッポン放送\u0026#34; }, \u0026#34;progs\u0026#34;: [ { \u0026#34;date\u0026#34;: { \u0026#34;_text\u0026#34;: \u0026#34;20210731\u0026#34; }, \u0026#34;prog\u0026#34;: [ { \u0026#34;_attributes\u0026#34;: { \u0026#34;id\u0026#34;: \u0026#34;9580196668\u0026#34;, \u0026#34;master_id\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;ft\u0026#34;: \u0026#34;20210731050000\u0026#34;, \u0026#34;to\u0026#34;: \u0026#34;20210731074000\u0026#34;, \u0026#34;ftl\u0026#34;: \u0026#34;0500\u0026#34;, \u0026#34;tol\u0026#34;: \u0026#34;0740\u0026#34;, \u0026#34;dur\u0026#34;: \u0026#34;9600\u0026#34; }, \u0026#34;title\u0026#34;: { \u0026#34;_text\u0026#34;: \u0026#34;徳光和夫　とくモリ！歌謡サタデー\u0026#34; }, \u0026#34;url\u0026#34;: { \u0026#34;_text\u0026#34;: \u0026#34;https://www.1242.com/toku/\u0026#34; }, \u0026#34;url_link\u0026#34;: {}, \u0026#34;failed_record\u0026#34;: { \u0026#34;_text\u0026#34;: \u0026#34;0\u0026#34; }, \u0026#34;ts_in_ng\u0026#34;: { \u0026#34;_text\u0026#34;: \u0026#34;0\u0026#34; }, \u0026#34;ts_out_ng\u0026#34;: { \u0026#34;_text\u0026#34;: \u0026#34;0\u0026#34; }, 〜以下略〜 JSON としては無駄が目に付きますが、XML からの変換を考えると仕方ないですね。\n機能拡張「JSON to TS」で変換してみた １つ目は「JSON to TS」です。\n変換するには、変換元となる JSON をコピーしてクリップボードにある状態から、Windows なら Shift + Ctrl + Alt + V、Mac なら ⌃⇧⌥V することで、TypeScript のコードが生成されました。\ninterface RootObject { _declaration: Declaration; region: Region; } interface Region { stations: Station2[]; } interface Station2 { _attributes: Attributes2; station: Station[]; } 〜以下略〜 LFR.json の方は、残念ながら「spawnSync /bin/sh ENOBUFS」となり変換できませんでした。\n大きいデータは無理っぽいです。\n機能拡張「Paste JSON as Code」で変換してみた ２つ目は「Paste JSON as Code」です。\nTypeScript の他に、変換先に Python / Go / Ruby / C# / Java / Swift / Rust / Kotlin / C++ / Flow / Objective-C / JavaScript / Elm / JSON Schema が選べます。\n変換するには、変換元となる JSON をコピーしてクリップボードにある状態からコマンドパレット(Windows は Ctrl + Shift + P、Mac は ⇧⌘P)で、\n「Paste JSON as Code」を選択 変換先に「TypeScript」を選択 最上位の型名を入力 という手順になります。LFR.json をコピーした状態から、以下の様な感じです。こちらは大きくても変換できました。\n変換した結果は、それぞれ以下の通り。\n// Generated by https://quicktype.io export interface Full { _declaration: Declaration; region: Region; } export interface Declaration { _attributes: DeclarationAttributes; } export interface DeclarationAttributes { version: string; encoding: string; } 〜以下略〜 // Generated by https://quicktype.io export interface Lfr { _declaration: Declaration; radiko: Radiko; } export interface Declaration { _attributes: DeclarationAttributes; } export interface DeclarationAttributes { version: string; encoding: string; } 〜以下略〜 ということで今の所、私は「Paste JSON as Code」の方を使おうと思います。\nプロを目指す人のためのTypeScript入門 (Amazon) ","date":"2021-08-07T20:12:00+09:00","image":"/images/2021/08/json.jpg","permalink":"/posts/it/pc/1580/","title":"JSON データから TypeScript のコードを生成するには"},{"content":"前回作成したコンポーネントのテストコードを書いてみた所、ResizeObserver と Element.getBoundingClientRect をモックにする必要がありました。\nどの様にしたかを記述します。\nモック化したテストコード まず、実際にモックにしたテストコードは以下の通りです。\nimport React from \u0026#39;react\u0026#39;; import { act } from \u0026#39;react-dom/test-utils\u0026#39;; import { render, screen } from \u0026#39;@testing-library/react\u0026#39;; import { Text } from \u0026#39;../Text\u0026#39;; let instanceResize: ResizeObserver | null = null; let callbackResize: ResizeObserverCallback | null = null; global.ResizeObserver = class mockResizeObjerver { constructor(callback: ResizeObserverCallback) { instanceResize = this; callbackResize = callback; } disconnect(){ } observe(target: Element, options?: ResizeObserverOptions){ } unobserve(target: Element){ } } describe(\u0026#39;Textのテスト\u0026#39;, () =\u0026gt; { test(\u0026#39;省略表示されること\u0026#39;, () =\u0026gt; { jest.spyOn(Element.prototype, \u0026#39;getBoundingClientRect\u0026#39;) // rectOuter .mockImplementationOnce(jest.fn(() =\u0026gt; ({x:0, y:10, width: 50, height: 50}) as DOMRect)) // rectInner .mockImplementationOnce(jest.fn(() =\u0026gt; ({x:0, y:10, width: 100, height: 50}) as DOMRect)); const result = render(\u0026lt;Text\u0026gt;abcdefghijklmnopqrstuvwxyz\u0026lt;/Text\u0026gt;); act(() =\u0026gt; { callbackResize!([], instanceResize!); }); // console.log(\u0026#39;省略表示されること\u0026#39;, screen.debug()); const outer = result.container.querySelectorAll(\u0026#39;span[title]\u0026#39;); expect(outer.length).toBe(1); }); test(\u0026#39;省略表示されないこと\u0026#39;, () =\u0026gt; { const result = render(\u0026lt;Text\u0026gt;abcdefghijklmnopqrstuvwxyz\u0026lt;/Text\u0026gt;); act(() =\u0026gt; { callbackResize!([], instanceResize!); }); // console.log(\u0026#39;省略表示されないこと\u0026#39;, screen.debug()); const outer = result.container.querySelectorAll(\u0026#39;span[title]\u0026#39;); expect(outer.length).toBe(0); }); }); ResizeObserver をモックしないとどうなるか 以下の通り、「ResizeObserver は未定義」でコケます。\n● Textのテスト › 省略表示されること ReferenceError: ResizeObserver is not defined 13 | // リサイズ監視 14 | useEffect(() =\u0026gt;　{ \u0026gt; 15 | const resizeObserver = new ResizeObserver(() =\u0026gt;　{ | ^ 16 | const rectOuter = refOuter.current?.getBoundingClientRect(); 17 | const rectInner = refInner.current?.getBoundingClientRect(); 18 | // 外枠 \u0026lt; 内枠 なら省略表記 ということで、以下の様にモックにします。\nlet instanceResize: ResizeObserver | null = null; let callbackResize: ResizeObserverCallback | null = null; global.ResizeObserver = class mockResizeObjerver { constructor(callback: ResizeObserverCallback) { instanceResize = this; callbackResize = callback; } disconnect(){ } observe(target: Element, options?: ResizeObserverOptions){ } unobserve(target: Element){ } } コンストラクタに渡すコールバック関数が呼ばれないとリサイズの処理がされないので、callbackResize を外出ししてテストコード中に呼んでいます。\nコールバックに渡す引数に ResizeObserver のインスタンスが必要なので、こちらも instanceResize に外出ししています。\nElement.getBoundingClientRect をモックしないとどうなるか 「省略表示されること」のテストでコケます。\n● Textのテスト › 省略表示されること expect(received).toBe(expected) // Object.is equality Expected: 1 Received: 0 20 | const result = render(\u0026lt;Text\u0026gt;abcdefghijklmnopqrstuvwxyz\u0026lt;/Text\u0026gt;); 21 | const outer = result.container.querySelectorAll(\u0026#39;span[title]\u0026#39;); \u0026gt; 22 | expect(outer.length).toBe(1); | ^ 23 | }); 24 | test(\u0026#39;省略表示されないこと\u0026#39;, () =\u0026gt; { 25 | const result = render(\u0026lt;Text\u0026gt;abcdefghijklmnopqrstuvwxyz\u0026lt;/Text\u0026gt;); 描画内容を確認する為、screen.debug() を console.log してみると、\n\u0026lt;body\u0026gt; \u0026lt;div\u0026gt; \u0026lt;span class=\u0026#34;text\u0026#34; \u0026gt; \u0026lt;span\u0026gt; abcdefghijklmnopqrstuvwxyz \u0026lt;/span\u0026gt; \u0026lt;/span\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/body\u0026gt; の通り、title 属性がありません。確認してみると、getBoundingClientRect が返してくる値が、\n{ bottom: 0, height: 0, left: 0, right: 0, top: 0, width: 0 } と、全部ゼロ。試しに幅を指定した要素の子要素にText コンポーネントを入れても変わりませんでした。ということで、\njest.spyOn(Element.prototype, \u0026#39;getBoundingClientRect\u0026#39;) // rectOuter .mockImplementationOnce(jest.fn(() =\u0026gt; ({x:0, y:10, width: 50, height: 50}) as DOMRect)) // rectInner .mockImplementationOnce(jest.fn(() =\u0026gt; ({x:0, y:10, width: 100, height: 50}) as DOMRect)); のように、呼び出された１回目は幅50、２回目は幅100、を返すモックにして省略表示が必要となる状況にしました。\nテストしている内容 省略表示した場合は、\n\u0026lt;body\u0026gt; \u0026lt;div\u0026gt; \u0026lt;span class=\u0026#34;text\u0026#34; \u0026gt; \u0026lt;span title=\u0026#34;abcdefghijklmnopqrstuvwxyz\u0026#34; \u0026gt; abcdefghijklmnopqrstuvwxyz \u0026lt;/span\u0026gt; \u0026lt;/span\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/body\u0026gt; 省略表示しない場合は、\n\u0026lt;body\u0026gt; \u0026lt;div\u0026gt; \u0026lt;span class=\u0026#34;text\u0026#34; \u0026gt; \u0026lt;span\u0026gt; abcdefghijklmnopqrstuvwxyz \u0026lt;/span\u0026gt; \u0026lt;/span\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/body\u0026gt; という描画結果なので、title 属性がある span の有無をテストしています。\n","date":"2021-07-31T19:59:57+09:00","image":"/images/2021/06/mock.jpg","permalink":"/posts/it/pc/1539/","title":"jest で ResizeObserver、Element.getBoundingClientRect をモックにするには"},{"content":"どんなことをしたいかというと、\n文字列が長くて表示しきれないときは省略表記する マウスホバーで文字列全体をツールチップ表示する というコンポーネントを作成してみます。\n動作は Chrome バージョン 92.0.4515.107 で確認しています。\nこの表示をしたコンポーネントのコードは以下の通りです。\nexport const Simple = () =\u0026gt; \u0026lt;span style={{ display: \u0026#39;block\u0026#39;, textOverflow: \u0026#39;ellipsis\u0026#39;, whiteSpace: \u0026#39;nowrap\u0026#39;, overflow: \u0026#39;hidden\u0026#39;, }} title=\u0026#34;abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\u0026#34; \u0026gt;abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\u0026lt;/span\u0026gt; style に、文字列が表示に収まらなかったら省略表記するように指定 title に、ツールチップで表示する文字列を設定 ということをしています。\nただ、このままだと省略表記が不要な文字長でもツールチップが表示されます。省略表記していないときはツールチップがでないようにしたいと思います。\n作成したコード .text { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } import React, { useRef, useState, useEffect } from \u0026#39;react\u0026#39;; import \u0026#39;./text.css\u0026#39;; type TextProps = Omit\u0026lt;JSX.IntrinsicElements[\u0026#39;span\u0026#39;], \u0026#39;children\u0026#39;\u0026gt; \u0026amp; { children: string }; export const Text = (props: TextProps) =\u0026gt; { const refOuter = useRef\u0026lt;HTMLSpanElement\u0026gt;(null); const refInner = useRef\u0026lt;HTMLSpanElement\u0026gt;(null); // 省略表記のとき true const [isEllipsis, setIsEllipsis] = useState\u0026lt;boolean\u0026gt;(false); // リサイズ監視 useEffect(() =\u0026gt;{ const resizeObserver = new ResizeObserver(() =\u0026gt;{ const rectOuter = refOuter.current?.getBoundingClientRect(); const rectInner = refInner.current?.getBoundingClientRect(); // 外枠 \u0026lt; 内枠 なら省略表記 setIsEllipsis((rectOuter?.width ?? 0) \u0026lt; (rectInner?.width ?? 0)); }); const el = refOuter.current as Element; resizeObserver.observe(el); return () =\u0026gt; resizeObserver.unobserve(el); }); const { children, ...etc } = props; // 省略表記になるとき title を設定 const title = isEllipsis \u0026amp;\u0026amp; { title: children }; return ( \u0026lt;span ref={refOuter} className=\u0026#34;text\u0026#34; {...etc} \u0026gt; \u0026lt;span ref={refInner} {...title} \u0026gt;{children}\u0026lt;/span\u0026gt; \u0026lt;/span\u0026gt; ); }; という Text コンポーネントにすると、\n\u0026lt;Text\u0026gt;abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\u0026lt;Text\u0026gt; の様に使えます。\n解説 省略表記している → title の props を設定 省略表記していない → title の props は無し ということをする為に色々していきます。\n\u0026lt;span ref={refOuter} className=\u0026#34;text\u0026#34; {...etc} \u0026gt; \u0026lt;span ref={refInner} {...title} \u0026gt;{children}\u0026lt;/span\u0026gt; \u0026lt;/span\u0026gt; span を親子な状態にしています。\n親\ndisplay: block; でブロック要素にして親要素の幅に沿わせる。 文字列が収まらなかったときに省略表記するスタイルの設定。 要素のリサイズを監視し、親と子の幅を比較して、ツールチップが必要かどうかを isEllipsis に反映。\n親は overflow: hidden; にしているので、\n親の幅 \u0026lt; 子の幅 のときは省略表記されている、と判定します。 子\n文字列の表示 文字列が\n省略表記されたときはツールチップで全文表示\n省略表記されていなければツールチップは無し という役割をしています。\n// リサイズ監視 useEffect(() =\u0026gt;{ const resizeObserver = new ResizeObserver(() =\u0026gt;{ const rectOuter = refOuter.current?.getBoundingClientRect(); const rectInner = refInner.current?.getBoundingClientRect(); // 外枠 \u0026lt; 内枠 なら省略表記 setIsEllipsis((rectOuter?.width ?? 0) \u0026lt; (rectInner?.width ?? 0)); }); const el = refOuter.current as Element; resizeObserver.observe(el); return () =\u0026gt; resizeObserver.unobserve(el); }); useEffect で親spanのリサイズ監視/監視解除をします。\nリサイズの監視には ResizeObserver を使用します。\nリサイズが発生したときは、親と子の span の幅を比較して、省略表記されているか判定した結果を isEllipsis に反映します。\nここまでするぐらいなら、もう常に title ありでも良いんじゃないか？という気も湧いてきますが、何かの参考になりましたら幸いです。\nプロを目指す人のためのTypeScript入門 (Amazon) ","date":"2021-07-24T20:12:00+09:00","image":"/images/2021/06/sql.jpg","permalink":"/posts/it/pc/1488/","title":"React で長い文字列を省略表記にして、ツールチップに全て表示するには"},{"content":"20秒全力、10秒休憩、を８回繰り返すタバタ式トレーニングをジムでするときに、計測が割と厄介だったので良いアプリが無いか探していました。要望は、\nインターバルの計測と通知ができるもの 通知は控えめか無音にできるもの できれば Apple Watch で使えて、通知は振動のもの でした。ピッタリのアプリ「Seconds Pro」が見つかったので紹介します。\n無料版もありましたが有料版でも¥610ですし、レビューを読んだ感じ最初から有料版にしておいた方が良さそうに見受けられました。\nスマホ側の設定 実現したいのは「20秒、10秒、を繰り返し８回、Apple Watch で振動で通知」です。\nその他の機能は、気になったら見るかも？ぐらい。\nアプリを起動したら、自分が使いたいインターバルを追加します。\n以下は自分用に「HIIT」を既に足した後ですが、\n「マイ・タイマー」の右上にある「＋」をタップ\n「HIIT タイマー」をタップ\n「名前」を入力\n「セット数」を「8」\n「高強度」を「0:20」\n「低強度」を「0:10」\nすぐに始まると慌ただしいので、\n「ウォームアップ」を「0:10」\n次は Apple Watch の設定で、\n「Apple Watch」をタップ\n「アラート」をオフ\n「触覚」をオン　（振動の通知をオン/オフ）\nApple Watch で使用 アプリを起動\n「私の間隔」をタップ\n作成したインターバル（自分の場合は「HIIT」）をタップ\n最初の時間の「ウォームアップ」をタップ\nで、運動の準備を整える。\n振動で開始のカウントダウンが分かるので、トレーニング開始。\n使い始めてから、ジムでもこのトレーニングする頻度が上がって良い感じです。\n１日４分　世界標準の科学的トレーニング　今日から始める「タバタトレーニング」(Amazon) トレーニングのプロが本気で考えた 効果絶大自重筋トレ (Amazon)\n","date":"2021-07-17T20:42:00+09:00","image":"/images/2021/04/wt.jpg","permalink":"/posts/it/smartphone/1454/","title":"インターバルトレーニングするなら「Seconds Pro」アプリが便利"},{"content":"前回は createAsyncThunk を使いましたが、チュートリアルを読み進んでみると、元々データフェッチの機能が用意されていましたので、今回はそちらの方法で REST API を使ってみました。\ncreate-react-app でテンプレートを用意する所は前回と同じなので、その後の修正部分を書いていきます。\nREST API を使用するコードを追加 公式の例を参考に createApi、fetchBaseQuery を使って書きます。\nデータを取得したい側は、useGetCityQuery フックを使うようになります。\nimport { createApi, fetchBaseQuery } from \u0026#39;@reduxjs/toolkit/query/react\u0026#39; export type city = { status: string; data: { id: string, name: string, }[]; }; export const cityApi = createApi({ reducerPath: \u0026#39;cityApi\u0026#39;, baseQuery: fetchBaseQuery({ baseUrl: \u0026#39;https://www.land.mlit.go.jp/webland/api/\u0026#39; }), endpoints: (builder) =\u0026gt; ({ getCity: builder.query\u0026lt;city, string\u0026gt;({ query: (code) =\u0026gt; `CitySearch?area=${code}`, }), }), }) export const { useGetCityQuery } = cityApi 表示部分を次のように追加しました。\nimport React, { useState } from \u0026#39;react\u0026#39;; import { useGetCityQuery } from \u0026#39;../../app/services/city\u0026#39;; export function City() { const [inputCode, setInputCode] = useState(\u0026#39;13\u0026#39;); const [code, setCode] = useState(\u0026#39;\u0026#39;); const { data, error, isLoading } = useGetCityQuery(code, { skip: code === \u0026#39;\u0026#39;}); const cities = (code === \u0026#39;\u0026#39;) ? undefined : data?.data; return ( \u0026lt;div\u0026gt; \u0026lt;input type=\u0026#34;text\u0026#34; value={inputCode} onChange={(e) =\u0026gt; setInputCode(e.target.value)} /\u0026gt; \u0026lt;button onClick={() =\u0026gt; setCode(inputCode)}\u0026gt;get\u0026lt;/button\u0026gt; \u0026lt;button onClick={() =\u0026gt; { setCode(\u0026#39;\u0026#39;); setInputCode(\u0026#39;\u0026#39;); }}\u0026gt;clear\u0026lt;/button\u0026gt; { error ? \u0026#39;Error.\u0026#39; : isLoading ? \u0026#39;Loading...\u0026#39; : \u0026lt;table\u0026gt; \u0026lt;thead\u0026gt; \u0026lt;tr\u0026gt; \u0026lt;th\u0026gt;ID\u0026lt;/th\u0026gt; \u0026lt;th\u0026gt;name\u0026lt;/th\u0026gt; \u0026lt;/tr\u0026gt; \u0026lt;/thead\u0026gt; \u0026lt;tbody\u0026gt; { cities?.map(c =\u0026gt; \u0026lt;tr key={c.id}\u0026gt; \u0026lt;td\u0026gt;{c.id}\u0026lt;/td\u0026gt; \u0026lt;td\u0026gt;{c.name}\u0026lt;/td\u0026gt; \u0026lt;/tr\u0026gt; ) } \u0026lt;/tbody\u0026gt; \u0026lt;/table\u0026gt; } \u0026lt;/div\u0026gt; ); } useGetCityQuery フックには、[get]クリック時の都道府県コードを渡す必要があるので、\ninputCode：入力中の都道府県コード code：[get]クリック時に inputCode を設定 としています。\ncode が空のときはクエリを発行したくないので、フックに skip: True を渡して発行を止めています。\nデータは現状の値が返されるので、cities は undefined にしています。\nREST API の実行中は \u0026lsquo;Loading\u0026hellip;\u0026rsquo; の表示にしてみましたが、REST API が軽くて目視では確認できませんでした。\nまた、同じクエリは RTK Query がキャッシュから返してくれる為、同じ都道府県コードのときは REST API は発行されません。\nそして、store に作成した Reducer を既存の store に加えました。\nimport { configureStore, ThunkAction, Action } from \u0026#39;@reduxjs/toolkit\u0026#39;; import counterReducer from \u0026#39;../features/counter/counterSlice\u0026#39;; import { cityApi } from \u0026#39;./services/city\u0026#39;; export const store = configureStore({ reducer: { counter: counterReducer, [cityApi.reducerPath]: cityApi.reducer, }, middleware: (getDefaultMiddleware) =\u0026gt; getDefaultMiddleware().concat(cityApi.middleware), }); export type AppDispatch = typeof store.dispatch; export type RootState = ReturnType\u0026lt;typeof store.getState\u0026gt;; export type AppThunk\u0026lt;ReturnType = void\u0026gt; = ThunkAction\u0026lt; ReturnType, RootState, unknown, Action\u0026lt;string\u0026gt; \u0026gt;; 前回のコードよりもスッキリしましたね。ファイルの構成も次の通りになりました。\ncreateAsyncThunk と RTK Query 、どちらも使い易くできていますね。\n","date":"2021-07-10T20:17:00+09:00","image":"/images/2021/07/restapi.jpg","permalink":"/posts/it/pc/1429/","title":"Redux Toolkit の RTK Query で REST API を使う"},{"content":"React Redux で面倒に感じる部分が、かなり良い感じに改善しているらしい Redux Toolkit 。\n非同期の部分も加わっている様なので、REST API を試してみました。\n基本はテンプレートを使用 Redux Toolkit のイントロダクションでも説明されている通り、次のコマンドでテンプレートを用意しました。\nnpx create-react-app my-app --template redux-typescript REST API は「国土交通省 総合情報システム」が公開している「都道府県内市区町村一覧取得API」で試しました。\nテンプレートを参考に REST API を使用するコードを追加 テンプレートが良い感じに参考なります。\nなので、同じ構成でコードを追加していきました。\nまずは counterAPI.ts を参考に次のコードを追加。\nexport type city = { status: string; data: { id: string, name: string, }[]; }; export function fetchCity(code: string) { return new Promise\u0026lt;city\u0026gt;((resolve, reject) =\u0026gt; { fetch(`https://www.land.mlit.go.jp/webland/api/CitySearch?area=${code}`) .then(response =\u0026gt; resolve(response.json())) .then(data =\u0026gt; reject(data)); }); } REST API を呼んで非同期に実行させる部分になります。\n次に counterSlice.ts を参考に次のコードを追加。\nimport { createAsyncThunk, createSlice } from \u0026#39;@reduxjs/toolkit\u0026#39;; import { RootState } from \u0026#39;../../app/store\u0026#39;; import { city, fetchCity, } from \u0026#39;./cityAPI\u0026#39;; export interface CityState { data?: city; status: \u0026#39;idle\u0026#39; | \u0026#39;loading\u0026#39; | \u0026#39;failed\u0026#39;; } const initialState: CityState = { data: undefined, status: \u0026#39;idle\u0026#39;, }; export const cityAsync = createAsyncThunk( \u0026#39;city/fetch\u0026#39;, async (code: string) =\u0026gt; { try { const response = await fetchCity(code); return { status: \u0026#39;idle\u0026#39;, data: response.data, }; } catch (e) { return { status: \u0026#39;failed\u0026#39;, data: [], }; } }, ); export const citySlice = createSlice({ name: \u0026#39;city\u0026#39;, initialState, reducers: { clear: (state) =\u0026gt; { state.data = { status: \u0026#39;idle\u0026#39;, data: [] }; }, }, extraReducers: (builder) =\u0026gt; { builder .addCase(cityAsync.pending, (state) =\u0026gt; { state.status = \u0026#39;loading\u0026#39;; }) .addCase(cityAsync.fulfilled, (state, action) =\u0026gt; { state.status = \u0026#39;idle\u0026#39;; state.data = action.payload; }); }, }); export const { clear } = citySlice.actions; export const selectCity = (state: RootState) =\u0026gt; state.city.data; export default citySlice.reducer; createAsyncThunk 関数で非同期のアクションを生成し、extraReducers で非同期の結果を反映する感じですね。\ncityAsync.rejected に対する処理は省略しましたが。\nそして表示部分となる Counter.tsx を参考に次のコードを追加。\nimport React, { useState } from \u0026#39;react\u0026#39;; import { useAppSelector, useAppDispatch } from \u0026#39;../../app/hooks\u0026#39;; import { clear, cityAsync, selectCity, } from \u0026#39;./citySlice\u0026#39;; export function City() { const [code, setCode] = useState(\u0026#39;13\u0026#39;); const cities = useAppSelector(selectCity); const dispatch = useAppDispatch(); return ( \u0026lt;div\u0026gt; \u0026lt;input type=\u0026#34;text\u0026#34; value={code} onChange={(e) =\u0026gt; setCode(e.target.value)} /\u0026gt; \u0026lt;button onClick={() =\u0026gt; dispatch(cityAsync(code))}\u0026gt;get\u0026lt;/button\u0026gt; \u0026lt;button onClick={() =\u0026gt; dispatch(clear())}\u0026gt;clear\u0026lt;/button\u0026gt; \u0026lt;table\u0026gt; \u0026lt;thead\u0026gt; \u0026lt;tr\u0026gt; \u0026lt;th\u0026gt;ID\u0026lt;/th\u0026gt; \u0026lt;th\u0026gt;name\u0026lt;/th\u0026gt; \u0026lt;/tr\u0026gt; \u0026lt;/thead\u0026gt; \u0026lt;tbody\u0026gt; { cities?.data?.map(c =\u0026gt; \u0026lt;tr key={c.id}\u0026gt; \u0026lt;td\u0026gt;{c.id}\u0026lt;/td\u0026gt; \u0026lt;td\u0026gt;{c.name}\u0026lt;/td\u0026gt; \u0026lt;/tr\u0026gt; ) } \u0026lt;/tbody\u0026gt; \u0026lt;/table\u0026gt; \u0026lt;/div\u0026gt; ); } 実行時には次のような形になります。\nそして、作成した Reducer を既存の store に加えました。\nimport { configureStore, ThunkAction, Action } from \u0026#39;@reduxjs/toolkit\u0026#39;; import counterReducer from \u0026#39;../features/counter/counterSlice\u0026#39;; import cityReducer from \u0026#39;../features/city/citySlice\u0026#39;; export const store = configureStore({ reducer: { counter: counterReducer, city: cityReducer, }, }); export type AppDispatch = typeof store.dispatch; export type RootState = ReturnType\u0026lt;typeof store.getState\u0026gt;; export type AppThunk\u0026lt;ReturnType = void\u0026gt; = ThunkAction\u0026lt; ReturnType, RootState, unknown, Action\u0026lt;string\u0026gt; \u0026gt;; これで最終的なファイルの構成は以下の通り。\nホント使い勝手が良くなってますね。比較的小さい規模でも導入しやすいと思いました。\n","date":"2021-07-03T18:56:53+09:00","image":"/images/2021/07/restapi.jpg","permalink":"/posts/it/pc/1385/","title":"React + Redux Toolkit で REST API を使う"},{"content":"ネット接続が光でギガな昨今でも、自宅は VDSL なので最速でも100Mです。。\nそれでも大丈夫なんですが、度々遅いので改善できないかググってみた所、OCN は2020年6月から IPoE 方式のインターネット接続提供を始めていて、改善が期待出来そうな感じでした。\nそして実際に変えてみて、自分の場合は改善しました。\n現状の接続状況を確認する 確認できるサイトが OCN にあります。\nIPoE接続環境確認サイト\nOCNのお客様のためのサイトです。\n確認ボタンを押してください。\n接続環境とIPアドレスが確認できます。\nhttps://v6test.ocn.ne.jp/\n自分の場合、IPv4 は PPPoE、IPv6 は IPoE でした。\nIPv4 も IPoE になれば、改善が期待できそうです。\nIPoE 接続を利用する 案内されている利用方法は、\n市販のルーターを使用する ホームゲートウェイを利用する OCN v6アルファ（レンタルルーター）を利用する の３通りです。\n自分の場合は 1. がお手軽に感じたので、OCN対応おすすめルーターに紹介されていた IOデータのルーター（WN-DAX1800GR/E）をポチりました。\nアイ・オー・データ WiFi 無線LAN ルーター WN-DAX1800GR/E (Amazon) ルーターを交換する 交換前は NEC Aterm WG1200HP3 を使用していました。\n交換前（左）の WG1200HP3\n交換後（右）の WN-DAX1800GR\n新しいルーターに交換して設定を済ませてネット接続できたので、先のページで IPv4 の接続状況を確認しました。が、なぜか PPPoE のまま。\n調べてみたところ、今までの感覚で「PPPoE 認証」で接続していたのが原因でした。\n「IPv4 over IPv6 (MAP-E)」に変更したら、IPv4 も IPoE 接続になりました。\n「PPPoE 接続」から、\n「IPv4 over IPv6 (MAP-E)」に変更。\n手動で接続設定せずに、自動判別に頼れば良かったらしいです。\n今どきはID、パスワードが無くて接続できるんですね。。\n変更前後の速度 フレッツ速度測定サイト で計測した結果、改善されていました。\n変更前の PPPoE 接続\n変更後の IPoE 接続\n時間帯によっては IPv4 が 10 未満で IPv6 が 50 未満ぐらいの事があったので、それぐらいになる場合も、マシになっているんじゃないかと期待できそうです。\nただ、ギガの環境なら４桁行くと思うと、ちょっと哀しい現実ですね。。\n変更前のルーターでも接続の設定で行けたのでは？→駄目でした PPPoE 認証で接続していた事が原因で IPv4 が PPPoE 接続だったのなら、「前のルーターでも設定で出来たんじゃないのか？」という疑問が出てきます。\nということで、変更前の WG1200HP3 のマニュアルを確認しつつ進めてみましたが、\nv6プラスモード\n日本ネットワークイネイブラー株式会社仕様のMAP-EプロトコルでIPv4 over IPv6通信する場合 IPv6オプションモード\nビッグローブ株式会社仕様のMAP-EプロトコルでIPv4 over IPv6通信する場合 のどちらも IPv4 では接続できなかったので、ルーター購入は余計な出費とならずホッとしました。\n","date":"2021-06-26T20:29:00+09:00","image":"/images/2021/06/LAN.jpg","permalink":"/posts/it/pc/1338/","title":"OCN で IPv4 も IPoE 接続する"},{"content":"props の型を定義するとき、\n組み込み要素の props を引き継ぐ 不適切な props 値を設定できないようにする 際に参考になりそうな事を記載します。\n組み込み要素の props を自身のコンポーネントに加える htmlの要素が持つ pops を自身が作成するコンポーネントでも使うとき、ひとつひとつ定義すると大変なことになりますよね。\nそういうときは、JSX.IntrinsicElements が便利です。\n例えば、自身が作成するコンポーネントで button を使っていて、button が持つ props を受け取れることを定義したいときは、\ntype MyButtonProps = JSX.IntrinsicElements[\u0026#39;button\u0026#39;]; とすればOKです。自身のコンポーネント用に expanded: boolean という props があるなら、\ntype MyButtonProps = { expanded: boolean; } \u0026amp; JSX.IntrinsicElements[\u0026#39;button\u0026#39;]; という形で定義できます。\nprops に設定が間違っている事が分かるようにする props の定義で、組み合わせできない props の値が設定されても、エラーで気が付けるようにできます。\n少し長いですが以下の様な形です。\nimport React from \u0026#39;react\u0026#39;; type BaseProps = { label: string; } type OrderedProps = { listType: \u0026#39;ordered\u0026#39;; listStyle: \u0026#39;decimal\u0026#39; | \u0026#39;cjk-decimal\u0026#39; | \u0026#39;upper-roman\u0026#39;; } \u0026amp; BaseProps \u0026amp; JSX.IntrinsicElements[\u0026#39;ol\u0026#39;]; type UnorderedProps = { listType: \u0026#39;unordered\u0026#39;; listStyle: \u0026#39;none\u0026#39; | \u0026#39;disc\u0026#39; | \u0026#39;circle\u0026#39; | \u0026#39;square\u0026#39;; } \u0026amp; BaseProps \u0026amp; JSX.IntrinsicElements[\u0026#39;ul\u0026#39;]; type Props = OrderedProps | UnorderedProps; export default function MyList(props: Props) { const { label, listStyle, children, ...otherProps } = props; let listEl = \u0026lt;\u0026gt;\u0026lt;/\u0026gt;; if (props.listType === \u0026#39;ordered\u0026#39;) { listEl = \u0026lt;ol style={{ listStyleType: listStyle }} {...(otherProps as JSX.IntrinsicElements[\u0026#39;ol\u0026#39;])} \u0026gt;{children}\u0026lt;/ol\u0026gt;; } if (props.listType === \u0026#39;unordered\u0026#39;) { listEl = \u0026lt;ul style={{ listStyleType: listStyle }} {...(otherProps as JSX.IntrinsicElements[\u0026#39;ul\u0026#39;])} \u0026gt;{children}\u0026lt;/ul\u0026gt;; } return ( \u0026lt;div\u0026gt; \u0026lt;div\u0026gt;{label}\u0026lt;/div\u0026gt; {listEl} \u0026lt;/div\u0026gt; ); } listType が\n\u0026lsquo;ordered\u0026rsquo; なら、listStyle に使えるのは \u0026lsquo;decimal\u0026rsquo;, \u0026lsquo;cjk-decimal\u0026rsquo;, \u0026lsquo;upper-roman\u0026rsquo; \u0026lsquo;unordered\u0026rsquo; なら、listStyle に使えるのは \u0026rsquo;none\u0026rsquo;, \u0026lsquo;disc\u0026rsquo;, \u0026lsquo;circle\u0026rsquo;, \u0026lsquo;square\u0026rsquo; となるので、違っている場合は指摘されます。\nVisual Studio Code 上なら、\nlistType が \u0026lsquo;ordered\u0026rsquo; なら、\nlistStyle に \u0026rsquo;none\u0026rsquo; は使えないので、\nMyList に波線が付いて、間違っていることが分かります。\ntype として OrderedProps と UnorderedProps を定義して Props はそのどちらかとすることで、できない組み合わせを設定されたときでも間違いに気づけます。\ntype Props = OrderedProps | UnorderedProps; VSCode で props のサジェストも追随してくれると更に楽なんですが、そこまで判断はされませんでした。\n","date":"2021-06-19T20:38:00+09:00","image":"/images/2021/06/props.jpg","permalink":"/posts/it/pc/1317/","title":"propsの型で楽する"},{"content":"SQL で値をバインドするとき、よく見るかける例ではプレースホルダーを ? にしていますが、名前付きパラメーターで記述した方が可読性も良く、その後のメンテナンスも楽です。\n環境 SQLite Version 3.32.3 Node.js Version 16.3.0 sqlite3 Version 5.0.2 名前付きパラメーターを使ったとき SQL の記述で、パラメーターで渡したい値に $ で始まる変数名を使い、その名前で値をバインドします。\nconst sqlite3 = require(\u0026#39;sqlite3\u0026#39;).verbose(); const db = new sqlite3.Database(\u0026#39;:memory:\u0026#39;); const sql = ` INSERT INTO users(id, name, email, address) VALUES($id, $name, $email, $address) ON CONFLICT(id) DO UPDATE SET name = $name, email = $email, address = $address ;`; db.serialize(function () { const stmt = db.prepare(sql); const params = { $id: \u0026#39;kuma\u0026#39;, $name: \u0026#39;kuma emon\u0026#39;, $email: \u0026#39;master@kuma-emon.com\u0026#39;, $address: \u0026#39;kuma 1-2-3\u0026#39;, }; stmt.run(params); stmt.finalize(); }); db.close(); ＄ と : と @ を使うことができますが、公式は $ を勧めています。\nNamed parameters can be prefixed with :name, @name and $name. We recommend using $name since JavaScript allows using the dollar sign as a variable name without having to escape it. https://github.com/mapbox/node-sqlite3/wiki/API#databaserunsql-param--callback\n名前付きパラメーターを使わなかったとき プレースホルダーを ? で記述したときは、次のようになります。\nconst sqlite3 = require(\u0026#39;sqlite3\u0026#39;).verbose(); const db = new sqlite3.Database(\u0026#39;:memory:\u0026#39;); const sql = ` INSERT INTO users(id, name, email, address) VALUES(?, ?, ?, ?) ON CONFLICT(id) DO UPDATE SET name = ?, email = ?, address = ? ;`; db.serialize(function () { const stmt = db.prepare(sql); const params = [ \u0026#39;kuma\u0026#39;, \u0026#39;kuma emon\u0026#39;, \u0026#39;master@kuma-emon.com\u0026#39;, \u0026#39;kuma 1-2-3\u0026#39;, \u0026#39;kuma emon\u0026#39;, \u0026#39;master@kuma-emon.com\u0026#39;, \u0026#39;kuma 1-2-3\u0026#39;, ]; stmt.run(params); stmt.finalize(); }); db.close(); 名前付きパラメーターの良さが分かる SQL文と渡すパラメーターを比較すると、名前付きパラメーターの良さが分かります。\n// 名前付きパラメーター // INSERT INTO users(id, name, email, address) // VALUES($id, $name, $email, $address) // ON CONFLICT(id) // DO UPDATE SET // name = $name, // email = $email, // address = $address; const params = { $id: \u0026#39;kuma\u0026#39;, $name: \u0026#39;kuma emon\u0026#39;, $email: \u0026#39;master@kuma-emon.com\u0026#39;, $address: \u0026#39;kuma 1-2-3\u0026#39;, }; // 名前付きパラメーターでない // INSERT INTO users(id, name, email, address) // VALUES(?, ?, ?, ?) // ON CONFLICT(id) // DO UPDATE SET // name = ?, // email = ?, // address = ?; const params = [ \u0026#39;kuma\u0026#39;, \u0026#39;kuma emon\u0026#39;, \u0026#39;master@kuma-emon.com\u0026#39;, \u0026#39;kuma 1-2-3\u0026#39;, \u0026#39;kuma emon\u0026#39;, \u0026#39;master@kuma-emon.com\u0026#39;, \u0026#39;kuma 1-2-3\u0026#39;, ]; 名前付きパラメーターを使わなかった場合、プレースホルダーの順に値を渡すようになり、\n順番通りに値を渡す必要がある\n→ パラメーターが増減するような変更が SQL にあったときに注意が必要 同じ変数も、現れる順番に渡す必要がある\n→ INSERT と UPDATE で同じ変数なのに、それぞれで指定が必要 というように名前付きパラメーターの方が、可読性とメンテナンスの面で優れています。\n","date":"2021-06-11T20:02:00+09:00","image":"/images/2021/06/sql.jpg","permalink":"/posts/it/pc/1272/","title":"sqlite3 の SQL で名前付きパラメーターを使うには"},{"content":"jest で Blob をモックにしてテストしたかったのですが、Node.js に Blob は定義されていませんでした。\n未定義なので定義することで対応してみました。\n作成したコード まずはテスト対象のコードです。前回のコードを typescript で別ファイルにしました。\nexport default function downloadText(fileName: string, text: string) { const blob = new Blob([text], { type: \u0026#39;text/plain\u0026#39; }); const aTag = document.createElement(\u0026#39;a\u0026#39;); aTag.href = URL.createObjectURL(blob); aTag.target = \u0026#39;_blank\u0026#39;; aTag.download = fileName; aTag.click(); URL.revokeObjectURL(aTag.href); } そして、テストコードは以下です。\nここでは意図した値で Blob を作成したか？を確認し、Aタグについては割愛しています。\nimport downloadText from \u0026#39;./downloadText\u0026#39;; class BlobMock { constructor( content: Array\u0026lt;ArrayBuffer | ArrayBufferView | Blob | String\u0026gt; | undefined, options: BlobPropertyBag | undefined, ) { this.content = content; this.options = options; } public content: Array\u0026lt;ArrayBuffer | ArrayBufferView | Blob | String\u0026gt; | undefined; public options: BlobPropertyBag | undefined; } test(\u0026#39;downloadText\u0026#39;, () =\u0026gt; { (global as any).Blob = BlobMock; const backup1 = window.URL.createObjectURL; const backup2 = window.URL.revokeObjectURL; window.URL.createObjectURL = jest.fn(); window.URL.revokeObjectURL = jest.fn(); downloadText(\u0026#39;sample.txt\u0026#39;, \u0026#39;一富士二鷹三茄子\u0026#39;); expect(window.URL.createObjectURL).toBeCalledWith({ content: [\u0026#39;一富士二鷹三茄子\u0026#39;], options: { type: \u0026#39;text/plain\u0026#39; }, }); window.URL.createObjectURL = backup1; window.URL.revokeObjectURL = backup2; }); 悩んだポイント１：どう Blob を定義するか 未定義だからといって、\nglobal.Blob = BlobMock; にすると、\u0026ldquo;A file-like object of immutable, raw data. Blobs represent data that isn\u0026rsquo;t necessarily in a JavaScript-native format. The File interface is based on Blob, inheriting blob functionality and expanding it to support files on the user\u0026rsquo;s system.\u0026rdquo; と言われてしまうので、\n(global as any).Blob = BlobMock; の通り、global を一旦 any にして Blob を設定しています。\n悩んだポイント２：URL.createObjectURL をモックにできない モックにできないというか、\njest.spyOn(window.URL, \u0026#39;createObjectURL\u0026#39;).mockImplementation(jest.fn()); だと、\nCannot spy the createObjectURL property because it is not a function; undefined given instead となってしまうので、関数を定義することで乗り越えました。\n","date":"2021-06-05T19:22:24+09:00","image":"/images/2021/06/mock.jpg","permalink":"/posts/it/pc/1247/","title":"jest で Blob をモックにしてテストするには"},{"content":"サーバーから受け取ったデータを、テキストやCSVにしてローカルにダウンロードしたいときのコードになります。\nサンプル \u0026lt;html\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026#34;utf-8\u0026#34; /\u0026gt; \u0026lt;script\u0026gt; function downloadText(fileName, text) { const blob = new Blob([text], { type: \u0026#39;text/plain\u0026#39; }); const aTag = document.createElement(\u0026#39;a\u0026#39;); aTag.href = URL.createObjectURL(blob); aTag.target = \u0026#39;_blank\u0026#39;; aTag.download = fileName; aTag.click(); URL.revokeObjectURL(aTag.href); } \u0026lt;/script\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;button onClick=\u0026#39;downloadText(\u0026#34;sample.txt\u0026#34;, \u0026#34;一富士二鷹三茄子\u0026#34;)\u0026#39;\u0026gt;text download\u0026lt;/button\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;html\u0026gt; 動作は Chrome 91、Safari 14.1.1 で確認しました。\n解説 \u0026lt;button onClick=\u0026#39;downloadText(\u0026#34;sample.txt\u0026#34;, \u0026#34;一富士二鷹三茄子\u0026#34;)\u0026#39;\u0026gt;text download\u0026lt;/button\u0026gt; ボタンをクリックしたとき、\u0026ldquo;一富士二鷹三茄子\u0026quot;というテキストを、「sample.txt」ファイルでダウンロードするように、downloadText 関数を呼び出します。\nfunction downloadText(fileName, text) { const blob = new Blob([text], { type: \u0026#39;text/plain\u0026#39; }); const aTag = document.createElement(\u0026#39;a\u0026#39;); aTag.href = URL.createObjectURL(blob); aTag.target = \u0026#39;_blank\u0026#39;; aTag.download = fileName; aTag.click(); URL.revokeObjectURL(aTag.href); } downloadText の各行でやっていることは、\nconst blob = new Blob([text], { type: \u0026#39;text/plain\u0026#39; }); ダウンロードするデータを生成します。\nテキストを書き出すので、2つ目の引数は { type: \u0026rsquo;text/plain\u0026rsquo; } にしています。\nCSV を書き出すときは、{ type: \u0026rsquo;text/csv\u0026rsquo; } にするなど、対象の MIME を指定します。\nconst aTag = document.createElement(\u0026#39;a\u0026#39;); ダウンロードは、A タグのクリックをコード上ですることで発火させます。\n作成した A タグは create したまま、DOMに追加しないので、ユーザーからは見えません。\naTag.href = URL.createObjectURL(blob); A タグが指すリンク先として、ダウンロードするデータを設定します。\naTag.target = \u0026#39;_blank\u0026#39;; リンク先を新しいタブで開くようにします。\nファイルのダウンロードなので、開いたタブは残りません。\naTag.download = fileName; ダウンロードするときのファイル名を設定します。\naTag.click(); A タグをクリックしたことにして、ファイルのダウンロードを行います。\nURL.revokeObjectURL(aTag.href); createObjectURL で作成したオブジェクトを解放します。\nおわりに javascript 上で生成したちょっとしたテキストデータを、ローカルにダウンロードしたいときは、このような形で簡単に行うことができます。\nプロを目指す人のためのTypeScript入門 (Amazon) ","date":"2021-06-02T20:11:12+09:00","image":"/images/2021/06/file.jpg","permalink":"/posts/it/pc/1228/","title":"javascript でテキストをファイルにダウンロードするには"},{"content":"WordPress のバックアップを UpdraftPlus で取っていますが、復元できるか確認します。\n復元先にはローカルの Docker を使います。\n復元先の WordPress をDocker で用意する 公式のサンプル等を参考に以下の docker-compose.yml を用意しました。\nversion: \u0026#39;3\u0026#39; services: db: image: mysql@sha256:68b207d01891915410db3b5bc1f69963e3dc8f23813fd01e61e6d7e7e3a46680 volumes: - ./mysql:/var/lib/mysql restart: always environment: MYSQL_ROOT_PASSWORD: somewordpress MYSQL_DATABASE: wordpress MYSQL_USER: wordpress MYSQL_PASSWORD: wordpress wordpress: depends_on: - db image: wordpress:latest volumes: - ./html:/var/www/html ports: - \u0026#34;80:80\u0026#34; restart: always environment: WORDPRESS_DB_HOST: db:3306 WORDPRESS_DB_USER: wordpress WORDPRESS_DB_PASSWORD: wordpress mysql のイメージが latest で無いのは Apple silicon に対応してない為で、回避策として latest のダイジェストを指定しています。\nDocker コンテナを起動して WordPress を初期化する 下記コマンドで起動します。\ndocker compose up -d --remove-orphans しばらく待つと、http://localhost/ で WordPress の初期設定を行うページが開けるので、そのまま初期設定を行います。\n言語を選択 必要情報を入力（仮の値でOK。復元が済むまでの一時的なもの。） WordPress をインストール インストールが完了したら、ログイン画面になるのでログインしておきます。\nUpdraftPlus プラグインを追加する WordPress の左サイドのメニューから「プラグイン」し、「新規追加」をクリック。\nプラグインを検索するテキストボックスで「UpdraftPlus」を検索し、見つかった「UpdraftPlus WordPress Backup Plugin」の「今すぐインストール」をクリック。\n「UpdraftPlus」の「有効化」をクリック。\nバックアップしたファイルを取得する 自分の場合は Google ドライブにバックアップしているので、直近のバックアップである下記ファイルをダウンロードしました。（ファイル名を一部変更しています。）\nbackup_2021-05-28-1546_kumaemoncom_6fc7e15e487e-db.gz backup_2021-05-28-1546_kumaemoncom_6fc7e15e487e-others.zip backup_2021-05-28-1546_kumaemoncom_6fc7e15e487e-plugins.zip backup_2021-05-28-1546_kumaemoncom_6fc7e15e487e-themes.zip backup_2021-05-28-1546_kumaemoncom_6fc7e15e487e-uploads.zip バックアップした一部のファイルを書き換える \u0026ldquo;backup_2021-05-28-1546_kumaemoncom_6fc7e15e487e-db.gz\u0026rdquo; を解凍したファイルをテキストエディタで開き、一括置換します。\n\u0026ldquo;https://kuma-emon.com\u0026rdquo; → \u0026ldquo;http://localhost\u0026rdquo; \u0026ldquo;https%3A%2F%2Fkuma-emon.com\u0026rdquo; → \u0026ldquo;http%3A%2F%2Flocalhost\u0026rdquo; 他にも \u0026ldquo;kuma-emon.com\u0026rdquo; が見つかりましたが、投稿を救い出す分には問題なさそうなので、そのままにしました。\nUpdraftPlus にバックアップファイルをアップロードして復元する 「設定」の「UpdraftPlus バックアップ」にある「バックアップファイルをアップロード」をクリックし、バックアップファイルをアップロードします。\n※ backup_2021-05-28-1546_kumaemoncom_6fc7e15e487e-db.gz はアップロードせず、書き換えたテキストファイルの方をアップロードします。\nアップロードしたバックアップの「復元」をクリック。\n「復元するコンポーネントを選択」を全て選択して「次」で進めていきます。\n「UpdraftPlus 設定に戻る」をクリックするとログインになります。\nログインに使うアカウントは、復元したサイトで使っているものを入力します。\n復元の状態を確認する ざっと見た感じでは、テーマのスキンが未選択になった程度で選択し直せば大丈夫でした。\n自分の場合は投稿と画像が復元できれば十分と思っているのでこれでOKですが、ご自身が必要と思うものが復元されているか確認しましょう。\n","date":"2021-05-29T19:59:00+09:00","image":"/images/2021/05/res.jpg","permalink":"/posts/it/pc/1186/","title":"UpdraftPlus によるバックアップを Docker に復元するには"},{"content":"javascript には、java や C# などにある format の様に書式化するものがありません。\n数値や文字列、日付の書式化に対応するのは辛いですが、文字列に変数を展開するものであれば比較的簡単なので作ってみます。\nformat っぽいことをする関数 javascript なら、\nfunction fmt(template, values) { return !values ? template : new Function(...Object.keys(values), `return \\`${template}\\`;`)(...Object.values(values).map(value =\u0026gt; value ?? \u0026#39;\u0026#39;)); } typescript なら型の定義を追加して、\nfunction fmt(template: string, values?: { [key: string]: string | number | null | undefined }): string { return !values ? template : new Function(...Object.keys(values), `return \\`${template}\\`;`)(...Object.values(values).map(value =\u0026gt; value ?? \u0026#39;\u0026#39;)); } という関数で実現できます。\n使い方 １つ目の引数 template に、テンプレートリテラルのような文字列\n２つ目の引数 values に、展開する値をプロパティに持つオブジェクト\nを指定します。\n戻り値に展開後の文字列が返されます。\n以下のように使います。\nfmt(\u0026#39;${name}による${codeTitle}メモ。${name}は${morning}時起床。${night}時就寝。\u0026#39;, { name: \u0026#39;熊右衛門\u0026#39;, codeTitle: \u0026#39;フォーマットの\u0026#39;, morning: 5, night: 21 }); // 返される文字列 \u0026#34;熊右衛門によるフォーマットのメモ。熊右衛門は5時起床。21時就寝。\u0026#34; 解説 色付した部分を解説して行きます。\n理解のしやすさを優先しますので、実際に Object.keys と Object.values が返してくる順が違う可能性がありますが、対ではあるので問題ありません。\nreturn !values ? template : new Function(...Object.keys(values), `return \\`${template}\\`;`)(...Object.values(values).map(value =\u0026gt; value ?? \u0026#39;\u0026#39;)); values が undefined なら template をそのまま返します。\nvalues を必須にするなら不要な判定です。\nreturn !values ? template : new Function(...Object.keys(values), `return \\`${template}\\`;`)(...Object.values(values).map(value =\u0026gt; value ?? \u0026#39;\u0026#39;)); ファンクションのコンストラクタに、例にした引数を与えた場合は以下のように展開されるイメージです。\nnew Function(\u0026#39;name\u0026#39;, \u0026#39;codeTitle\u0026#39;, \u0026#39;morning\u0026#39;, \u0026#39;night\u0026#39;, \u0026#39;return `${name}による${codeTitle}メモ。${name}は${morning}時起床。${night}時就寝。`;\u0026#39;) コードに展開されるので、template と values に与える値に注意しましょう。\n以下のような関数が作られます。関数名は仮に kuma としました。\nfunction kuma(name, codeTitle, morning, night) { return `${name}による${codeTitle}メモ。${name}は${morning}時起床。${night}時就寝。`; } return !values ? template : new Function(...Object.keys(values), `return \\`${template}\\`;`)(...Object.values(values).map(value =\u0026gt; value ?? \u0026#39;\u0026#39;)); オブジェクトが持つ値を並べます。\nnull や undefined は空文字にしています。\n(\u0026#39;熊右衛門\u0026#39;, \u0026#39;フォーマットの\u0026#39;, 5, 21) と展開されるイメージです。\nなので、\nkuma(\u0026#39;熊右衛門\u0026#39;, \u0026#39;フォーマットの\u0026#39;, 5, 21) という形になり、展開された文字列を戻り値として得られます。\nプロを目指す人のためのTypeScript入門 (Amazon) ","date":"2021-05-24T20:18:00+09:00","image":"/images/2021/05/js2.jpg","permalink":"/posts/it/pc/1025/","title":"typescript や javascript で format っぽいことをするには"},{"content":"前回は直交表を作成しましたが、今回はペアワイズ法で作ってみたいと思います。\nペアワイズテストとは ペアワイズテストは、ほとんどのバグが最大で2つの要因の相互作用によって引き起こされるという観察に基づいた、効果的なテストケース生成技術です。ペアワイズテストでは、2つの要因のすべての組み合わせをカバーしているため、網羅的なテストよりもはるかに小さくなりますが、欠陥を発見するには非常に効果的です。\nhttps://jaccz.github.io/pairwise/　から意訳\nということで、こちらでもテストパターンを削減できます。\nペアワイズ法でテストパターンを作成 前回の状態があるという前提で進みます。\n１．PictMaster.xlsm を開き、「環境設定」ボタンをクリックする\n２．「生成方法」で「ペアワイズ法」を選択し、「OK」をクリックする\n４．「実行」をクリックする\n※「パラメータ」と「値の並び」は前回と同じものを使いました。\n５．結果として56パターンが生成される\nテストできないパターンを除外する ペアワイズ法を選択した場合、「パラメータ」と「値」で除外する組み合わせを設定できます。\n例えば、\n・圧縮を On にできるのは、\nファイルシステムに NTFS を選択し、\nクラスタサイズに4096以上を選択したとき\nといった条件や、FAT や FAT32 なら設定できるサイズに上限がある、が考えられます。\nここでは圧縮の条件を入れて、設定できないパターンを除外してみます。\n１．「環境設定」ボタンをクリックし、「制約表を使用」をチェックして「OK」をクリックする\n２．制約を入力するセルが表示される\nパラメータは自動的に設定される\n３．制約を入力する\n制約１： FileSystem が FAT か FAT32 なら Compression は Off のみ\n制約２： ClusterSize が 512 か 1024 か 2048 なら Compression は Off のみ\n※条件のセルは背景色を設定（例えば黄色に）する\n４．「実行」をクリックする\n５．結果として56パターンが生成される\n※ パターン数は変わりませんでしたが、制約に入れた組み合わせはありません。\n「Compression」を「On」で絞ると、対象のパターンが無いことが分かります。\n制約なし\n制約あり\nテストできないパターンを制約にするのはOKですが、テスト結果が成功になるパターンで制約をした場合は、テスト結果が失敗するパターンのテストは考えましょう。\n今回はファイルシステムとして作成できないパターンを除外しましたが、もし指定されたときはエラーメッセージが表示されるか、ファイルシステムに影響が無いか、の確認が必要かどうかでしょうか。 制約の詳しい説明は、PictMaster に付属する「PictMasterユーザーズマニュアル.pdf」ファイルに記載があります。 PictMaster で入力するのが難しい制約でも、直接 pict コマンドへなら簡単に済む場合がありそうなので、確認することをお勧めします。 直交表とペアワイズテストのどちらを使うか 個人的には、\n直交表は、なんらかの実験をするときに有効なパラメータと組み合わせを求めたい ペアワイズテストは、ソフトウエアテストを効率的に行いたい という感触だったので、ソフトウエアのテストならペアワイズテストを使います。\nソフトウェア品質を高める開発者テスト アジャイル時代の実践的・効率的なテストのやり方 (Amazon) ","date":"2021-05-22T20:25:00+09:00","image":"/images/2021/05/ts.jpg","permalink":"/posts/it/pc/932/","title":"PictMasterでプログラムのテストパターンをペアワイズ法で作る"},{"content":"テストパターンを直交表で作成する機会があり、PictMaster が便利でしたのでご紹介します。\n実際に作成した直交表は書けませんので、例で作成してみたいと思います。\nプログラムのテストパターンとは プログラムが想定通りに動作するかテストするとき、「どういう条件」で「何を」したとき「どうなるべきか」を考えますが、それを書き出していくとテストパターンになると思えば良いと思います。\n例えば、PCのディスクを初期化するプログラムをテストしたいとき、\n・「何を」→ 初期化を実行\n・「どうなるべきか」→ 指定の設定で初期化される／初期化できない\nになりますが、「どういう条件」は対応した設定に従って複数の組み合わせでテストが必要になります。\nType: Single, Span, Stripe, Mirror, RAID-5\nSize: 10, 100, 500, 1000, 5000, 10000, 40000\nFormat method: Quick, Slow\nFile system: FAT, FAT32, NTFS\nCluster size: 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536\nCompression: On, Off\nhttps://github.com/microsoft/pict\n上記の設定に対応した場合なら、5 x 7 x 2 x 3 x 8 x 2 = 3,360 通りのパターンがあることになります。\n直交表で効率的にテストパターンを削減 3,360通りのテストをするには、どれぐらい時間が掛かるんだろう？というぐらい非現実的ですが、直交表を作成した場合は64通りに下がりました。\nそれでも多いと感じますが、まだ現実的な量ではないでしょうか。\n環境 ということで、下記の環境で作成しました。\n・Windows 10 Pro（64 ビット）\n・Microsoft Excel for Microsoft 365 （32 ビット）\n・PictMaster Japanese version 7.0.4J\n・pict Version 3.7.2\nインストール １．ダウンロードした PictMaster の zip ファイルを解凍し、C:\\Program Files (x86)\\PICT に配置\n２．ダウンロードした pict.exe を C:\\Program Files (x86)\\PICT に配置\n３．C:\\Program Files (x86)\\PICT 内の全ファイルのプロパティでセキュリティを許可\n直交表を作成 １．PictMaster.xlsm を開く\nマクロが無効にされていることがツールバー下に「セキュリティの警告」で表示されたときは「コンテンツの有効化」をクリックしてマクロを有効にする\n２．「環境設定」ボタンをクリックする\n３．「生成方法」で「直交表」を選択し、「OK」をクリックする\n４．シートに「パラメータ」と「値の並び」を入力する\n例として、pict.exe の README.md に記載されたものを入れてみます\n５．「実行」をクリックする\n※ 「TripleDESによる復号化に失敗しました」というメッセージが表示された場合は、「Windows 機能の有効化または無効化」で「.NET Framework 3.5」を有効化する。\n一旦 Excel を終了し、再度 PictMaster.xlsm を開く\n６．直交表で64パターンが生成される\n直交表に想定結果を追記 そのパターンで実行した結果の想定（成功/失敗）は自身で記入すれば完成です。\nソフトウェア品質を高める開発者テスト アジャイル時代の実践的・効率的なテストのやり方 (Amazon) ","date":"2021-05-15T20:20:00+09:00","image":"/images/2021/05/ts.jpg","permalink":"/posts/it/pc/774/","title":"プログラムのテストパターンに使う直交表をPictMasterで作る"},{"content":"docomo ユーザーである知人の Android スマホで、ショートメールのアプリを変更したときの手順です。\n変更した理由は “＋メッセージ” は不要で “メッセージ” 使えば良いんじゃないのか？ の通りです。\nGoogle Play から 「メッセージ」アプリをインストールする Google Play を開き、「メッセージ」アプリを検索します。\n開発元は Google LLC となっています。\nイントール後、ホーム画面にアイコンが無いようであれば配置しておきます。\nホーム画面にある「＋メッセージ」のアイコンは削除します。（念の為アンインストールはしない。）\nSMS のデフォルトアプリを「メッセージ」に変更する 「設定」アイコンから、「アプリと通知」-「デフォルトのアプリ」-「SMS アプリ」を開き、\n「メッセージ」を選択すれば完了です。\n","date":"2021-05-12T20:25:00+09:00","image":"/images/2021/05/mes2.png","permalink":"/posts/it/smartphone/706/","title":"\"＋メッセージ\" をやめて \"メッセージ\" を使う方法"},{"content":"以前使用していた MacBook Pro (Early 2013) を Apple Trade In で下取りに出す準備をしました。\n手順は公式にありますが、内容が少し古いようなので、迷ったときにでも今回自分の残した作業が参考になれば幸いです。\nバックアップを作成する 自分は必要なファイルを外付けHDDにコピーして新しい Mac へ移行しましたが、王道は\n・TimeMachine\n・iCloud\nだと思います。\n使わなかった理由はクリーンインストールしたかったからです。\n新しい Mac を数日使って、旧マシンを手放しても問題ない状態だと確認できました。\n動かないものもありましたが、代替えの手段が確認できたので大丈夫です。\nまた、旧マシンに OS を再インストールする起動ディスクも念の為作成しました。\niTunes からサインアウトする iTunes となっていますが、今は「ミュージック」ですね。\n認証解除の手順は変わらないのでサクッと完了です。\niCloud からサインアウトする これも手順通りにサクッと完了です。\n連絡先は「残す」か「キャンセル」だったので「残す」にしました。削除で良いんですが「キャンセル」は何か違うような気がするので。\nキーチェーンは「消す」にしました。\niMessage からサインアウトする iMessage となっていますが、今は「FaceTime」ですね。\nこれも手順は変わらないので問題なし。\nNVRAM をリセットする 手順どおりで問題なし。\n起動音の後、再度起動音がしてからログイン画面が表示されました。\nBluetooth デバイスのペアリングを解除 既に削除して空だったので、やることはありませんでした。\nハードドライブを消去して macOS を再インストールする 起動直後の状態が公式の説明と違うのでちょっと焦りました。\n自分の場合は再起動後の command + R で待った後、言語選択画面になり、選択して進めたら「macOS復旧」に変わってユーザー選択する状態になりました。\nディスクを消去後、再インストール起動ディスクからOSを入れました。\nディスクが復元できないように消去したいと思いましたが、「セキュリティオプション」が見当たりません。どうやらSSDの場合は選択できないようです。\n以上で終わりですが 感謝を込めて筐体を拭きました。約８年間どうもありがとう。\n後は集荷を待つだけです。\n","date":"2021-05-11T20:37:00+09:00","image":"/images/2021/05/macini.jpg","permalink":"/posts/it/pc/636/","title":"Mac を下取りに出す際にやるべきこと７つ"},{"content":"WordPress のバックアップに使用している「BackWPup」ですが、バックアップ先を Google Drive にしたいと思って調べていた所、UpdraftPlus の方が使いやすそうだったので試してみました。\n良い感じだったので乗り換えて、BackWPup は削除しました。\n今回行った UpdraftPlus の設定手順を残しますので、よろしければ参考まで。\nUpdraftPlus のプラグインを追加 管理画面の「プラグイン」-「新規追加」から「UpdraftPlus」を検索して、「今すぐインストール」し、有効化する。\nバックアップの設定 １．管理画面で「設定」-「UpdraftPlus バックアップ」をクリックする。\n２．UpdraftPlus の「設定」タブを選択する。\n３．保存先に「Google Drive」を選択して下スクロールしていき「変更を保存」する。\n４．「このリンクを〜」をクリックする。\n５．Google の認証を進み「許可」する。\n６．「Complete setup」をクリックする。\nバックアップの実行 １．UpdraftPlus の「バックアップ / 復元」タブで「今すぐバックアップ」をクリックする。\n２．「今すぐバックアップ」をクリックする。\n３．しばらく待つと、バックアップが完了しました。復元も簡単っぽい。\nGoogle ドライブにバックアップされていることを確認 「UpdraftPlus」にバックアップされていました。\n自動バックアップを設定する 無料版では時刻は指定できない様ですが、周期は指定できるので「毎日」にしてしばらく様子を見てみます。\nこれでもしもサーバー側で何かあっても Google ドライブにバックアップが残るので安心かな。\n","date":"2021-05-10T20:23:00+09:00","image":"/images/2021/05/res2.jpg","permalink":"/posts/it/pc/506/","title":"BackWPup より UpdraftPlus の方がバックアップに良いのでは？"},{"content":"本日５月９日、３年前に「＋メッセージ」が始まったんですね。\nこれで思い出した、**\u0026quot;＋メッセージ\u0026quot; をやめて \u0026ldquo;メッセージ\u0026rdquo; を使った方が幸せなんじゃないか？**という話です。\nきっかけは、年配の知人が\n・docomo ユーザーで、らくらくホンから Galaxy に変えた\n・SMS でやり取りするガラケーの友人がいる\nことからでした。\n「＋メッセージ」で困ること Wi-Fi を Off にするように警告される\nOff にしないと警告が消えませんでした。\n自宅で Wi-Fi があるなら On にするのが自然と思うのですが、どういう思想なんでしょうか。\n思いつく想像としては、送る前から「失敗しそうな設定だから警告」しておくことでサポートの手間が軽減掛からない、データ通信が増えることで料金プランをそっちの誘導に役立ちそう、あたりでしょうか。 スマホの文字サイズを大きくすると、上記警告中は利用できる表示領域が狭すぎる\n年配の方なので、文字サイズを大きく設定していました。\n警告が表示されるせいで、メッセージが表示/入力できる範囲が画面サイズの半分以下になりました。\nかといって、都度 Wi-Fi を On/Off するのは使い勝手が悪すぎます。 SMS の標準アプリになっている\nこの状態で標準アプリにするのは Wi-Fi 使うな、という事なんでしょうか。 「メッセージ」が幸せなこと Wi-Fi が On のままでも警告されない\nこれによって、スマホの文字サイズが大きくても問題なく使えます。\n何らかの原因でメッセージが送れなかったとしても、結果として表示から読み取れます。 シンプルな SMS のまま\n文字のみでやり取りするなら十分です。 必要に応じてMMS にしてくれる\n長文や写真などを送るときは、キャリアメールを使ってくれます。 結果：「メッセージ」を標準アプリに変更しました この知人の Galaxy に「メッセージ」をインストールして、ショートメールの標準アプリに設定しました。ガラケーの友人とのやりとりもOKでした。\n以上、あのアプリは不要な代物だなと感じた思い出でした。\n","date":"2021-05-09T21:36:55+09:00","image":"/images/2021/05/mes2.png","permalink":"/posts/it/smartphone/552/","title":"\"＋メッセージ\" は不要で \"メッセージ\" 使えば良いんじゃないのか？"},{"content":"WordPress のバックアッププラグイン「BackWPup」で自動バックアップをしていますが、自分で戻せるのか試してみました。BackWPup Pro では無いので手動です。\nサービス側も自動バックアップしている このブログは CohoHa WING を利用しているので、サーバー側で過去１４日分の自動バックアップが取られています。\n操作の間違いや失敗があって戻したい場合はそれを利用する方が簡単・確実です。\n復元先に Docker を使用 お試しサーバーは Docker で立ち上げるのがお手軽です。\nなので、公式のサンプル等を参考に以下の docker-compose.yml を用意しました。\nまた、docker-compose.yml を置いたディレクトリに「mysql」「html」ディレクトリは手動で作成しておきます。\nversion: \u0026#39;3\u0026#39; services: db: image: mysql@sha256:bbeff35b63bf28aeb024de309aab2d501f8aa30e94664d3840d55b36c8db53c8 volumes: - ./mysql:/var/lib/mysql restart: always environment: MYSQL_ROOT_PASSWORD: somewordpress MYSQL_DATABASE: wordpress MYSQL_USER: wordpress MYSQL_PASSWORD: wordpress wordpress: depends_on: - db image: wordpress:latest volumes: - ./html:/var/www/html ports: - \u0026#34;80:80\u0026#34; restart: always environment: WORDPRESS_DB_HOST: db:3306 WORDPRESS_DB_USER: wordpress WORDPRESS_DB_PASSWORD: wordpress mysql のイメージが latest で無いのは Apple silicon に対応してない為で、回避策として latest（お試し時の最新） のダイジェストを指定しています。\n起動してしばらく待つと、http://localhost/ で WordPress の初期設定ページ（言語選択）が開けました。\n初期状態が確認できたので、一旦Dockerコンテナーは停止してバックアップから復元してみます。\nバックアップのダウンロード バックアップ対象の WordPress のメニューから、BackWPup → バックアップ を選択して、バックアップしたファイルをダウンロードします。\n自分は .tar.gz にしているのでダウンロード後に解凍します。できたファイルとディレクトリは以下でした。（一部のファイル名は伏せ字）\nbackwpup_readme.txt xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.pluginlist.2021-05-03.txt error index.php license.txt manifest.json xxxxxxxxxxxxxx.sql readme.html wp-activate.php wp-admin wp-blog-header.php wp-comments-post.php wp-config-sample.php wp-config.php wp-content wp-cron.php wp-includes wp-links-opml.php wp-load.php wp-login.php wp-mail.php wp-settings.php wp-signup.php wp-trackback.php xmlrpc.php バックアップから復元 解凍してできた、\n・ xxxxxxxxxxxxxx.sql は mysql ディレクトリにコピー\n・その他のファイルは html ディレクトリに上書きコピー\n但し、コピー先の wp-config.php は予め退避しておきます。後で内容のコピーに使います。\nwp-config.php の修正 バックアップから戻した wp-config.php ファイルに、退避した wp-config.php の先頭から DB_COLLATE の定義がある範囲を上書きします。\n\u0026lt;?php /** * The base configuration for WordPress * * The wp-config.php creation script uses this file during the * installation. You don\u0026#39;t have to use the web site, you can * copy this file to \u0026#34;wp-config.php\u0026#34; and fill in the values. * * This file contains the following configurations: * * * MySQL settings * * Secret keys * * Database table prefix * * ABSPATH * * @link https://codex.wordpress.org/Editing_wp-config.php * * @package WordPress */ // a helper function to lookup \u0026#34;env_FILE\u0026#34;, \u0026#34;env\u0026#34;, then fallback if (!function_exists(\u0026#39;getenv_docker\u0026#39;)) { // https://github.com/docker-library/wordpress/issues/588 (WP-CLI will load this file 2x) function getenv_docker($env, $default) { if ($fileEnv = getenv($env . \u0026#39;_FILE\u0026#39;)) { return rtrim(file_get_contents($fileEnv), \u0026#34;\\r\\n\u0026#34;); } else if (($val = getenv($env)) !== false) { return $val; } else { return $default; } } } // ** MySQL settings - You can get this info from your web host ** // /** The name of the database for WordPress */ define( \u0026#39;DB_NAME\u0026#39;, getenv_docker(\u0026#39;WORDPRESS_DB_NAME\u0026#39;, \u0026#39;wordpress\u0026#39;) ); /** MySQL database username */ define( \u0026#39;DB_USER\u0026#39;, getenv_docker(\u0026#39;WORDPRESS_DB_USER\u0026#39;, \u0026#39;example username\u0026#39;) ); /** MySQL database password */ define( \u0026#39;DB_PASSWORD\u0026#39;, getenv_docker(\u0026#39;WORDPRESS_DB_PASSWORD\u0026#39;, \u0026#39;example password\u0026#39;) ); /** * Docker image fallback values above are sourced from the official WordPress installation wizard: * https://github.com/WordPress/WordPress/blob/f9cc35ebad82753e9c86de322ea5c76a9001c7e2/wp-admin/setup-config.php#L216-L230 * (However, using \u0026#34;example username\u0026#34; and \u0026#34;example password\u0026#34; in your database is strongly discouraged. Please use strong, random credentials!) */ /** MySQL hostname */ define( \u0026#39;DB_HOST\u0026#39;, getenv_docker(\u0026#39;WORDPRESS_DB_HOST\u0026#39;, \u0026#39;mysql\u0026#39;) ); /** Database Charset to use in creating database tables. */ define( \u0026#39;DB_CHARSET\u0026#39;, getenv_docker(\u0026#39;WORDPRESS_DB_CHARSET\u0026#39;, \u0026#39;utf8\u0026#39;) ); /** The Database Collate type. Don\u0026#39;t change this if in doubt. */ define( \u0026#39;DB_COLLATE\u0026#39;, getenv_docker(\u0026#39;WORDPRESS_DB_COLLATE\u0026#39;, \u0026#39;\u0026#39;) ); データベースの復元 xxxxxxxxxxxxxx.sql に URL が書かれている所があるので変更します。\nhttps://kuma-emon.com を http://localhost に置換しました。\nDockerコンテナーを起動し、mysqlのコンテナーに接続してファイルから復元します。\nmysql -u wordpress -D wordpress -p \u0026lt; /var/lib/mysql/xxxxxxxxxxxxxx.sql それなりに復元できたが 利用できないプラグインが（BackWPup）ありました。\nデザインも初期値になったものがあり、管理画面から再設定が必要でした。\n記事や画像は復元できたので、もしもの際に使えそうです。\n","date":"2021-05-08T20:57:00+09:00","image":"/images/2021/05/res.jpg","permalink":"/posts/it/pc/413/","title":"WordPress のバックアップを Docker に復元してみた"},{"content":"知人に誘われて温泉に行ってきました。\nインドア派で日頃の行動範囲が狭いので、少しでも遠くになるとナビられないと無理です。\nこういう場合、以前はMapFanのアプリを使用していましたが、Web版に変わってしまいましたので、今回探して良さそうだった「カーナビタイム」をインストールして行ってみました。\nとても優秀だったので紹介します。ただ、自分はナビアプリを使うのは数年ぶりなので、感覚は古いかもしれません。\nオフラインで使える 格安SIM利用なこともあり、オンラインなナビは不安なので、オフラインで使えるものを選んでいます。\nカーナビタイムをオフラインで使う場合、予めデータをダウンロードしておく必要があります。\n有償ですが初回は１ヶ月無料とあります。\n１ヶ月経つ前にお試しを終了すれば無料らしいのですが、月額コースの領収書メールが届きました。\n自分は以前試した事がある気がしますので、そのせいでしょうか。\n利用頻度が低いので、月額の継続は停止して必要なときだけ課金しようと思います。\n方向音痴な自分には、道に迷う心配＋実際なってしまったら、を考えると十分に価値があります。\nオフラインデータは自宅Wi-Fiでダウンロードして、１時間は掛かってないと思います。\n音声案内のタイミングが丁度いい 次に曲がるポイントまでの距離と、そのとき曲がる方向を案内してくれるので、\n・そのポイントに到着する前に走行しておく車線\n・どちらに曲がるか\nという準備が出来て助かります。\n車線の案内も分かりやすく表示されました。\n曲がったら次の案内が流れるのもGOODです。\n曲がる指示のタイミングも丁度良いです。昔は割と手前で言われて微妙だった気がします。\n位置情報の精度が上がっていないと難しいと思うので、その辺りの進化も感じます。\nサクッとリルートが効く 目的地付近で、「この道は入ったらマズイんでは？」と通り過ぎたのですが、サクッとリルートしてくれて助かりました。\n「通り過ぎた」や「戻れ」みたいな事をしつこく言われるような案内ではなくてGOODです。\n到着予定時刻もほぼその通りでした。１時間程度の渋滞の無い所だった事もあるかもしれませんが。\nというか、その程度の場所でもナビが必要なぐらい、長く住んでいても土地勘がありません。。\nおわりに - ナビアプリの選択 カーナビタイム、多機能っぽいですが試せていません。今の所、普通にナビって貰えれば十分なので。\nそもそも普段からナビが不要な道でアプリを使って比較しておけば良いんじゃね？ですかね。\nそれなら駄目であっても問題ないので、オンライン版を試す障壁も低いですし、アプリの使い心地を比較しやすいですよね。\nただ、ほとんど運転しない生活になってしまったので、その機会も少ないんですが。。\nということで、他と使い心地の比較は出来てないんですが、カーナビタイムはオススメできます。\n","date":"2021-05-06T20:56:00+09:00","image":"/images/2021/05/navi.jpg","permalink":"/posts/it/smartphone/342/","title":"ドライブのナビに「カーナビタイム」アプリを押す３つの理由"},{"content":"新コースの 3GB/月 を利用していましたが、先月に 1GB/月 にしました。\nコロナ渦で在宅ワークになって以来、毎月 1GB すら使わないまま１年ぐらい経過したので、月200円下がる程度ではありますが切り替えました。\nなので、今月の容量は繰越分も足すと 2GB かな？と思っていましたが実際は 4GB になっていました。想定より多かったのでちょっと嬉しいんですが何で？\nで、公式ページを確認すると、繰越は「翌月末まで」とあったので、\n（４月分の）3GB +（5月分の）1GB = 4GB\nということでしょうか。まぁ、今月も 1GB は使わないでしょう。来月からは 2GB でスタートかな。\nそんなことは起きないと思いますが、容量不足で耐えられない状況になったら容量追加オプションで対応するつもりです。\nアプリ経由で申し込んだ場合、550円/GB で利用期限が３ヶ月なので自分には十分です。\nOCN モバイル ONE エントリーパッケージ (Amazon) ","date":"2021-05-05T20:12:21+09:00","image":"/images/2021/04/smartphone.jpg","permalink":"/posts/it/smartphone/315/","title":"OCN モバイル ONE で 月 3GB → 1GB にしたときの繰越は？"},{"content":"「最近の項目」にあるものを消すと、ファイル本体も消えます。\nファイル本体は消さずに「最近の項目」から消す方法と、サイドバーから「最近の項目」を消す方法について記載します。\n「最近の項目」にファイルを載せない Spotlight の対象外にすればOKです。\n１．アップルメニューから「システム環境設定」-「Spotlight」を開く\n2. 「+」をクリックしてHDを追加、特定のフォルダだけ載せたくなければフォルダを追加すればOKです。\n「最近の項目」に載せたくなったら「-」で消せば、また載ってきます。\nサイドバーから「最近の項目」を消す Finder のメニューから「Finder」-「環境設定」を開く 「サイドバー」を選択し、「最近の項目」チェックを外す 初期表示するフォルダを変える 初期表示するフォルダはデフォルトだと「最近の項目」の為、開く度に空から始まって使いづらいので、使う頻度が多いフォルダなどに変えた方が便利です。\nFinder のメニューから「Finder」-「環境設定」を開く 「一般」を選択し、「新規Finderウインドウで次を表示」を設定する\n下記は「デスクトップ」にした場合です Mac Fan 2026年5月号 (Amazon) Mac mini (Amazon) MacBook Pro 13インチ (Amazon) MacBook Air (Amazon)\n","date":"2021-05-01T19:04:13+09:00","image":"/images/2021/05/spotlight-hd.jpg","permalink":"/posts/it/pc/230/","title":"Mac の Finder で「最近の項目」を消す"}]