2024/11/20

Notes - Excel 連携:#49)凡例の配置

#41)グラフの調整』から開始した ”使える” グラフにするための調整作業のシリーズです。今回は凡例の操作を行います。

前回までで習得した座標系の知識を利用して、凡例の配置を行います。設定の仕様は次の通りです。

  • フォントサイズは基準のフォントサイズとする
  • 幅はプロットエリア右側の空白に合わせる
  • 高さはサブタイトルと同じ(基準フォントサイズ × 1.5)
  • プロットエリアと凡例の底辺をそろえる


凡例の操作

VBA で凡例を操作するには Chart オブジェクトLegend プロパティ を使用して Legend オブジェクト を取得します。

また、グラフ内に凡例があるか判定するには HasLegend プロパティ を使用します。

Legend オブジェクトには、配置を指定する TopLeft、サイズを表す HeightWidth プロパティが存在します。Format プロパティで ChartFormat オブジェクト が取得でき、このオブジェクトに TextFrame2 プロパティがあります。TextFrame2 オブジェクトが取得できれば、テキストボックスと同様の操作でフォントの指定ができますね。


凡例を配置する関数

前回までオブジェクトの配置の方法や座標系について紹介しました。そして、凡例のオブジェクトと操作は上記の通りです。これらを活用して、どのようなコードを書くべきか想像してみましょう。

いかがですか?

仕様通りに凡例を配置する関数 xSetLegend を紹介します。

Function xSetLegend(voShape As Variant)
   Dim oLegend As Variant
   Dim oPlot As Variant

   If voShape.Chart.HasLegend = True Then
      '凡例が存在
      Set oLegend = voShape.Chart.Legend '凡例を取得
      Set oPlot = voShape.Chart.PlotArea 'プロットエリアを取得

      'フォントサイズの設定
      oLegend.Format.TextFrame2.TextRange.Font.Size = xcdFontSize

      '調整前にいったん左上に移動
      oLegend.Left = 0
      oLegend.Top = 0

      '位置とサイズの指定
      oLegend.Left = oPlot.InsideLeft + oPlot.InsideWidth
      oLegend.Width = voShape.Width - oLegend.Left - 4
      oLegend.Height = xcdFontSize * 1.5
      oLegend.Top = oPlot.InsideTop + oPlot.InsideHeight - oLegend.Height
   End If
End Function

凡例の配置を調整する前に位置を左上に移動しています。これは、凡例の現在位置にかかわらず希望のサイズを確実にセットするためです。以前お伝えした通り、グラフのエリアからはみ出ると自動で調整され、希望通り配置されないことがあります。これを抑制するためです。こうしておけば、設定の順序をいちいち意識する必要はないですね(回転はしない前提)。

位置とサイズの指定について補足します。

まず、Left はプロットエリアの右端と同じ位置ですので、プロットエリアの開始位置(InsideLeft)と幅(InsideWidth) を足しているだけです。

凡例の幅は、グラフエリアの幅(voShape.Width)からプロットエリアの幅(=凡例の Left)を引いています。これだけでは左マージンの 4 ポイントが考慮できていないので、それを差し引いています。

凡例の高さ(Top)は、プロットエリアの高さ(oPlot.InsideTop + oPlot.InsideHeight)から凡例エリアの高さを差し引いて決定しています。


まとめ

メインルーチンで、作成した関数をコールすれば今回の作業は完了です。

Sub Initialize
         ・・・
   'Y軸ラベルの生成
   Call xSetLabelY(oShape, "ユーザ数")

   '凡例の設定
   Call xSetLegend(oShape)

   oXls.Visible = True
End Sub

修正後に作成されるグラフは次のようになります。なかなかいい感じになりましたね。

次回は最後の仕上げを行い、グラフを完成させます。


前回 Notes - Excel 連携


2024/11/19

ビューの検索で全角半角が区別できない !?

先日、ノーツアプリでトラブルに遭遇しました。現象がつかめたのでレポートします。なお今回の調査結果はメーカサポートに連絡・確認していません。私の検証結果だけをまとめてております。あくまで私の主観に基づくレポートですので、ご了承ください。


背景のシステム

ノーツとは別の人事システムから部門マスタと社員マスタを連携しており、これら情報を使用してノーツのグループを自動生成しています。連携するデータはその日の全件で、ノーツ内にあるデータ(前日のマスタ)と比較しながら、差分を算出し、新規/変更/削除の処理をしています。

連係インターフェース仕様としては部門名は ”全角” 文字と決まっていたのですが、誤って半角で入力してしまったそうです(パッケージに仕様上禁止できなかった模様)。半角部門名の存在に気づき連絡、全角文字に修正したタイミングで問題が発生しました。


トラブルの内容

発生した問題は、グループ名とメンバーで全角/半角が不一致となったのです。

例えば下表のような感じです。グループ名で使用している部門名(赤字)は半角文字のままで、メンバーとして使用している部門名(青字)は全角となっていました。

グループ名 メンバー
営業部 営業部/営業
営業部/営業
営業部/営業1 A さん
B さん

要は、階層構造が正しくなく、グループとして正しく機能しなくなりました。結果、アクセス障害が発生し大トラブルになったというものでした。


エラーの原因

グループの更新処理において、グループ名は GetDocumentByKey で既存グループを検索していました。この時、全角で検索したにも関わらず、半角のグループがヒットし、半角のグループが削除されず、全角にならずメンバーだけ更新されたため発生していました。

今回はこの問題を単純なプログラムを作成して、検証します。

なお、グループのメンバーに関しては文字列の比較で判定しており、こちらは正常に判断されていたことを補足いたします。


検証プログラム

まず、フィールドが 1 つだけの単純なフォームを作成します。

フォームができたら、プリビューでテストデータを 1 件作成し、”営業部/営業1課” と入力します(数字は半角)。

次にこの項目を表示する 1 列だけのビューを作成します。検索で使用するのでソートを設定しておきます。

最後にテストエージェントを作成します。テストデータを検索するのですが、検索前に StrConv を使用して全角文字列に変換しています。正しく動作すると検索にはヒットしないという算段です。

検索した結果はメッセージで表示します。ヒットした場合は、検索文字列とヒットした文書内のフィールド値を表示しています。

Option Declare

Sub Initialize
   Dim ns As New NotesSession
   Dim ndb As NotesDatabase
   Dim nv As NotesView
   Dim nd As NotesDocument
   Dim sKey As String

   sKey = "営業部/営業1課"
   sKey = StrConv(sKey, 4)

   Set ndb = ns.CurrentDatabase
   Set nv = ndb.Getview("GetDocumentByKey")

   Set nd = nv.GetDocumentByKey(sKey, True)

   If nd Is Nothing Then
      MsgBox "ヒットしませんでした。"
   Else
      MsgBox sKey & Chr(10) & nd.Keyword(0)
   End If
End Sub


検証結果

手持ちの環境を利用して検証します。サーバは Domino 11 と Domino 14 があったので利用します(クライアントは Notes 11)。その結果、Domino 11 ではヒットし、Domino 14 ではヒットしませんでした。

Domino 11 Domino 14

LotusScript に関連するバージョン依存の障害では、一般にクライアントのバージョンに依存することが多いのですが、今回はサーバのバージョンにより現象が違います。サーバ内のビュー索引に起因するからなのでしょうか?

参考までに、この検証プログラムを Notes 11 と Notes 14 のローカル環境でもテストしてみました。結果はヒットしました(Domino 11 と同じ症状)。同じバージョンでもサーバ実行とクライアント実行で症状が違うことがあるんですね。驚きました。

Domino 14 にすれば問題が起こらないようなので、バージョンアップしましょうというメッセージでこの話は収束できそうです。Domino 12 については検証できていませんが、新しいバージョンの方がいいと言えそうです。


今回は、ノーツのグループを事例にしてご紹介しました。グループに限らず発生しうる問題なので紹介させていただきましたが、『日本語のグループ名はサポート外と再認識した話』で記載しましたが、2バイト文字を使用したグループはそもそもサポート外です。ご注意ください。


2024/11/15

Notes - Excel 連携:#48)オブジェクトの回転と配置

#41)グラフの調整』から開始した ”使える” グラフにするための調整作業です。今回は Y 軸ラベルの設定を行います。

Y 軸ラベルは通常のテキストボックスを 90° 左に回転させ PlotArea の右端に配置します。オブジェクトのサイズは、プロットエリアの Y 軸に合わせ、文字を中央揃えにします。


オブジェクトの回転

テキストボックスをはじめ Shape オブジェクトには Rotation というプロパティがあり、オブジェクトの回転を表します。正の値を設定すると右(時計)回り、負の値で左回りに回転します。

まずはこのプロパティをテストします。100 × 15 ポイントのテキストボックスを座標 (0, 0) に作成し左に 90° 回転してみます。

   Set oText = xCreateTextBox(voShape)
   '配置の設定
   oText.Left = 0
   oText.Top = 0
   oText.Width = 100
   oText.Height = 15

   '左に 90° 回転
   oText.Rotation = -90

結果を確認すると回転処理はできていたのですが、オブジェクトサイズが小さくなったような気がします。そこで、サイズをメッセージボックスで表示させてみました。すると、幅が 61.5 と小さくなっているうえ、座標(Left と Top)が 19.25 と動いています。

まず、回転はオブジェクトの中心の座標を中心に実行されます。今回で言うと (50, 7.5) となります。左に 90° 回転するとオブジェクトがプロットエリアからはみ出ます(紫部分)。この部分が Excel の機能で自動的に調整され、残った部分が 61.5 ということですね。

オブジェクトの座標(Left と Top)はというと、調整されたオブジェクトの回転をもとに戻すとわかります。オブジェクトの長さは 61.5 ポイントだったので中心はその半分、そして、上部に 4 ポイントのマージンがあるので、中心座標は (50, 26.75) となります。回転を戻したオブジェクトの左上が Left と Top になり、Left は幅の半分 30.75、Top は高さの半分 7.5 を中心座標から差し引くと (19.25, 19.25) となります。

このように、はみ出た分の自動調整により中心位置がずれ、それに引きずられてオブジェクトの配置(Left と Top)が動いたということになります。


回転操作の注意点

かなり詳細な説明をしましたが、ここまで座標系や挙動を理解しておくと、今後様々なシーンで迷うことなく操作できるはずです。

回転操作の注意点をまとめると次の通りです。

  • 回転はオブジェクトの中心が起点
  • 回転した結果、はみ出た分は自動調整される
  • 自動調整が発生すると中心位置が変わり、オブジェクトの座標も変わる
  • オブジェクトの位置(Left、Top)やサイズ(Width、Height)は回転しない状態の値

Y 軸ラベルの作成と配置

オブジェクトの回転が理解できたので、グラフに Y 軸ラベルを配置する関数を作成します。

Function xSetLabelY(voShape As Variant, ByVal vsLabel As String)
   Dim oText As Variant
   Dim dW As Double
   Dim dH As Double
   Dim d As Double

   'Y 軸用テキストボックス
   Set oText = xCreateTextBox(voShape)

   With oText.TextFrame2
      'フォーマットの設定
      .TextRange.Font.Size = xcdFontSize 'フォントサイズ
      .TextRange.ParagraphFormat.Alignment = msoAlignCenter '水平方向に中央揃え
      'テキスト
      .TextRange.Text = vsLabel
   End With

   'サイズのセット
   dW = voShape.Chart.PlotArea.InsideHeight 'プロットエリアの高さ
   dH = xcdFontSize * 1.5 'フォントサイズの1.5倍
   oText.Width = dW
   oText.Height = dH

   '回転してもPlotAreaからはみ出ないよう位置を調整
   d = dW / 2
   d = d - dH / 2
'オブジェクトの高さの半分 上へ
   d = d + voShape.Chart.PlotArea.InsideTop '上のマージン分下げる
   oText.Top = d

   '回転
   oText.Rotation = -90

   '左端に移動
   d = dW / 2 - dH / 2 '左への移動量
   d = d + 4 'マージンも詰める
   Call oText.IncrementLeft(-d)
End Function

この関数のポイントは 2 点です。

まず、Y 軸ラベルの幅は、プロットエリアの高さに合わせています。プロットエリアにそろえて配置して、文字を中央揃えするだけなので、計算が比較的簡単となります。

もう一点は、回転後 PlotArea からはみ出ないよう、事前にに位置を調整している点です。また、この操作と同時に、回転後の高さがプロットエリアにぴったり合うよう調整しています。プロットエリアの高さ(= テキストラベルの幅)とマージン(InsideTop)を利用、回転の中心は Top 座標よりオブジェクトの高さ(Height)の半分下にずれることも考慮してオブジェクトを配置しています。

回転後、オブジェクトを左端に寄せています。移動量は、オブジェクトの幅や高さ、マージンの 4 ポイントを使って厳密に計算しています。Excel の自動調整を活用するのであれば、厳密な計算をせず、大きめの値をセットするだけでもかまいません(意図が見えなくなるので推奨しませんが...)。


最後にメインルーチンから作成した関数をコールすれば Y 軸ラベルの作業は完了です。

Sub Initialize
         ・・・
   'タイトルエリアの生成
   Call xSetTitle(oShape, "ユーザ数の推移", "Server01/Domino")

   'Y軸ラベルの生成
   Call xSetLabelY(oShape, "ユーザ数")

   oXls.Visible = True
End Sub


前回 Notes - Excel 連携 次回


2024/11/12

編集権限を持つ文書の編集を止める方法

先日、既存ノーツ DB のメンテナンスをしていてバグを発見しました。LotusScript を覚えたての方がよくやるミスだったので、ご紹介します。

問題の DB は『漢字アドレス帳』です。懐かしいですね(笑)

Notes 4.6 時代に Lotus 社が提供した First Step Kit 内に同梱されていた製品(?)の一部です。ノーツユーザを漢字表記するための DB です(Notes R5 以降は DJX が同等の機能を担当)。非常に短命だったのですが、当時ノーツを新規導入した企業が多かったことから爆発的に利用されました。もしかしたらお使いのサーバ内にも残骸があるかもしれませんね。


問題の機能

今回漢字アドレス帳の文書を修正しようと管理者権限のユーザで文書を編集しようとしました。すると、『この文書は編集できません』とエラーが表示されました。

漢字アドレス帳への登録は『申請データベース』経由で行う仕様だから、当然です。そこで、フォームの設計を確認すると次のようになっていました。

QueryModeChange のイベントは、文書のモードが変化(読込⇔編集)する前に発生します。上記コードでは EditMode が True でないとき(= 読込モード)エラーを表示しています。そして、引数の Continue に False を設定することで、モードの変更をキャンセルさせています。

なるほど、これで編集を抑制(= 編集権限があっても文書を編集させなく)しているんですね。今回『この文書は編集できません』とエラーが表示されたので、うまく機能しています。


バグとは?

さて、この機能、何が問題か気がつきましたか?


バグは QueryModeChange イベントではありません。QueryOpen イベントにコードがないことが問題なんです。例えば、ビューで文書を選択し、Ctrl + E を押して編集モードで文書を開くと、難なく編集できてしまいます。

これでは、編集を抑制したい機能は満足できていませんね。


対策

QueryOpen は文書を開く前に発生するイベントです。イベントの引数は 4 つです。


Source 開く文書
Mode 編集モードで開く場合 1
読込モードで開く場合 0
IsNewDoc 開く文書が新規文書の場合 True
Continue 文書を開かせない場合 False をセット


この引数を利用して、いきなり編集モードで開く操作に制限を掛けます。

Sub QueryOpen(Source As Notesuidocument, Mode As Integer, IsNewDoc As Variant, Continue As Variant)
   If IsNewDoc = False Then
      If Mode = 1 Then
         Msgbox "この文書は編集できません",16,"漢字メール"
         Continue = False
      End If
   End If
End Sub

Mode = 1 の場合、エラーメッセージを表示して、開く動作をキャンセルしています。

また、このフォームで文書を新規作成する機能があるかは知りませんが、万が一、新規作成をした場合に備えて、開くようにしています。


まとめ

今回はフォームの編集を抑制する機能について紹介しました。

編集を止めるのは QueryModeChange イベントだけでなく QueryOpen イベントにも配慮が必要というお話でした。LotusScript に不慣れであったり、急いで作るとついつい忘れがちです。

約 30 年前とはいえ、ノーツのメーカが提供した DB で発見しました。プロでもやっちゃうありがちなバグということですね。注意しましょう。


2024/11/07

Notes - Excel 連携:#47)グラフタイトルの作成と配置

前回はグラフオブジェクト内の座標系についてまとめました。仕様が明確になったので、いよいよタイトルを表示するコーディングを始めます。

#45)グラフタイトルとテキストボックスの違い』で記載したように、標準のグラフタイトルは使用せず、”テキスト ボックス” でタイトルとサブタイトルを作成します。


テキストボックスの配置

今回、テキストボックスの幅はグラフエリアいっぱいにしておきます。文字を右寄せにするのでできるだけ大きくしておけば、文字の折り返しなど、細かなことは気にしなくて済むという判断です。基準のフォントを 10 ポイントとし、目立たせたいタイトルだけ 1.5 倍のフォントサイズで太字とします。

テキストボックスの高さはフォントサイズの 1.5 倍とし、文字の垂直揃えを真ん中に設定します。

これらの設定を行う関数は次の通りとなります。関数の引数は、グラフ全体を表す Shape オブジェクトとタイトル / サブタイトルに設定する文字列です。

Function xSetTitle(voShape As Variant, ByVal vsTitle As String, ByVal vsSubTitle As String)
   Dim oText As Variant

   'タイトル
   Set oText = xCreateTextBox(voShape)
   'オブジェクトの配置
   oText.Width = voShape.Width
   oText.Height = xcdFontSize * 1.5 * 1.5 ' フォントサイズ×マージンの 1.5
   Call oText.IncrementTop(-4) '上マージンをなくす
   With oText.TextFrame2
      'フォーマットの設定
      .TextRange.Font.Size = xcdFontSize * 1.5 'フォントサイズ(標準の1.5)
      .TextRange.ParagraphFormat.Alignment = msoAlignRight '水平方向に右揃え
      'テキスト
      .TextRange.Text = vsTitle
   End With

   'サブタイトル
   Set oText = xCreateTextBox(voShape)
   'オブジェクトの配置
   oText.Width = voShape.Width
   oText.Top = xcdFontSize * 1.5 * 1.5  'タイトルの高さ分下に下げる
   oText.Height = xcdFontSize * 1.5
   With oText.TextFrame2
      'フォーマットの設定
      .TextRange.Font.Size = xcdFontSize 'フォントサイズ
      .TextRange.ParagraphFormat.Alignment = msoAlignRight '水平方向に右揃え
      'テキスト
      .TextRange.Text = vsSubTitle
   End With
End Function

テキストボックスを作成する処理はサブ関数  xCreateTextBox は #45 で紹介しています。

なお、上記プログラムで未定義の定数を 2 つ使用しています。1 つ目は文字のフォントサイズである  です。このエージェントの (Declarations) に記載します。

Private Const xcdFontSize = 10  'ベースフォントサイズ

もう一つは文字列の水平揃えに関する定数です。今回は右寄せだけを使用していますが、ついでに左寄せも記述しておきます。なお、こちらの記述は lsXls ライブラリへの追加となるので注意してください。

'MsoParagraphAlignment 列挙 (Office)
Public Const msoAlignLeft = 1  '左寄せ
Public Const msoAlignCenter = 2  '中央揃え
Public Const msoAlignRight = 3  '右寄せ


メインプログラムの修正

関数ができあがったら、メインルーチンからコールします。

Sub Initialize
         ・・・
   'プロット領域の設定
   oChart.PlotArea.InsideWidth = 350
   oChart.PlotArea.InsideLeft = 50
   oChart.PlotArea.InsideTop = 7
   oChart.PlotArea.InsideHeight = 170

   'タイトルエリアの生成
   Call xSetTitle(oShape, "ユーザ数の推移", "Server01/Domino")

   oXls.Visible = True
End Sub


実行結果とまとめ

実行すると次のようにタイトル、サブタイトルが表示されます。また、グラフエリアの上から順に隙間なく配置されています。


ところで、テキストボックスの横幅にはグラフエリアの幅をセットしています。

   oText.Width = voShape.Width

以前まとめたように Excel は範囲外の値をセットしてもエラーを出さずうまく調整してくれます。今回はこの機能を利用して、以下の記述を省略しています。

    Call oText.IncrementLeft(-4)


前回 Notes - Excel 連携 次回


2024/11/06

Notes - Excel 連携:#46)テキストボックスの配置

前回紹介したように、グラフ上にテキストボックスを追加すると、座標 (0, 0) で作成したにも関わらず、左と上に少し隙間が空いてしまいます。

Set oText = voShape.Chart.Shapes.AddLabel(msoTextOrientationHorizontal, 0, 0, 50, 10)

今後、より詳細にオブジェクトの位置決めをするときに混乱しないよう挙動を確認します。


隙間のサイズ

まず、Excel のマクロの記録しながらテキストボックスに左上に移動してみました。すると、次のスクリプトが出力されました。

Sub Macro1()
   Selection.ShapeRange.IncrementLeft -4
   Selection.ShapeRange.IncrementTop -4
End Sub

IncrementLeft は、図形を指定したポイント数だけ水平方向に移動するメソッドです。マイナスの値を指定すると左、プラスの値で右に移動します。IncrementTop も同様の機能で、こちらは垂直方向に移動するメソッドです。Left や Top プロパティで位置を絶対的に設定するだけでなく、現在位置から相対的に移動できるんですね。

この結果より、左と上の隙間の幅は 4 ポイントであることがわかりました。


左上に配置するには

隙間の大きさがわかったので、左上ぎりぎりに配置する方法を確認します。

まず、テキストボックス作成時にマイナスの座標をセットしてみます。結果は、変わらず隙間が空いた状態となりました。

・・・   .Shapes.AddLabel(msoTextOrientationHorizontal, -4, -4, 50, 10)


次は、Left と Top プロパティに負の値を入れてみます。実行はしているようですが、結果の位置は変わりませんでした。


では先ほどの Increment* メソッドをテストします。座標 (0, 0) で作成したオブジェクトをそれぞれ -4 してみます。これで無事、左隅に移動することができました。

   'タイトルエリアの生成
   Dim oText As Variant
   Set oText = xCreateTextBox(oShape)
   oText.TextFrame2.TextRange.Text = "Test"

   Call oText.IncrementLeft(-4)
   Call oText.IncrementTop(-4)


座標系

上記のように、右上に寄せた状態で、オブジェクトの位置を確認すると -4 となっています。

   Call oText.IncrementLeft(-4)
   Call oText.IncrementTop(-4)

   Dim s As String
   s = "Left = " & CStr(oText.Left)
   s = s & Chr(10) & "Top = " & CStr(oText.Top)
   MsgBox s

グラフオブジェクトの幅は 500 ポイントでした。試しに座標 (0, 0) で作成し、幅を 492 ポイントに設定してみます。

   'タイトルエリアの生成
   Dim oText As Variant
   Set oText = xCreateTextBox(oShape)
   oText.TextFrame2.TextRange.Text = "Test"

   ' Call oText.IncrementLeft(-4)
   ' Call oText.IncrementTop(-4)


   oText.Width = 492

結果は以下のように、左右の隙間が同じになりました。


上記の結果より、Shape オブジェクトとその上に配置するテキストオブジェクトの関係は次の通りとなります。


まとめ

今回は、グラフオブジェクトの座標系に関して調査しました。Excel としてはオブジェクトのマージンとして 4 ポイント分確保し、そこを座標の起点としていることがわかりました。

Notes - Excel 連携:#42)PlotArea とサイズ』の「位置合わせの謎」のセクションで、PlotArea を左から 50 ポイント(約 66 ピクセル)にしても、54 ポイント(72 ポイント)になる原因が不明だと書きました。原因がわかりましたね。今回の検証で理解した 4 ポイントのマージン(約 5 ピクセル)ということだったのです。

上記は PlotArea の操作で、今回の検証はテキストボックスでしたが、位置の指定は同じです。Shape オブジェクト内の座標系はこれで統一されているようですね。

オブジェクトの配置やサイズの操作のポイントは 2 つです。

まず、4 ポイント分のマージンは利用できないわけではなく、 Increment* メソッドを使えば利用できます。ただし、Left や Top プロパティで、直接的にマイナスの値は指定できないようなので、注意が必要です。

もう一点は、今回の例でいうと Left と Top に -4 を指定しても、0 となったように、範囲外の値を指定してもエラーが発生しないことです。こちらは、 #42 でも触れましたが、プログラムを実行しても希望した通りの配置とならない場合、バグなのか、Excel が調整したのか、それがいつ(どの操作で)発生したのか判定しずらい点に注意が必要です。


前回 Notes - Excel 連携 次回


2024/11/05

Notes - Excel 連携:#45)グラフタイトルとテキストボックスの違い

#41)グラフの調整』から開始した ”使える” グラフにするための調整作業です。今回はグラフ右上に配置するグラフタイトルエリアです。通常のタイトルのほかに補足情報を表示できるサブタイトル欄を用意します。

両タイトルとも右寄せで配置します。タイトルが長い場合、グラフエリアにはみ出して表示し、文字の折り返しはない前提とします。


グラフタイトルは特殊?

まずは Excel でグラフタイトルの設定を確認します。フォントの設定や右寄せなど一般的な操作は可能です。ただ、[グラフ タイトルの書式設定]-[タイトルのオプション]-[サイズとプロパティ]を表示すると、オブジェクトのサイズ設定がありません。

ところが、図形メニューから追加した ”テキスト ボックス” では、次の通り、サイズの設定が可能です。このようにグラフタイトルでは、設定できないプロパティが部分的に存在するようです。


グラフタイトルの削除

将来細かな操作が必要となった場合に備え、今回はグラフタイトルは使用せず、”テキスト ボックス” でタイトルとサブタイトルを表現することとします。

標準のグラフタイトルの削除は、HasTitle プロパティに False を設定するだけです。

Sub Initialize
         ・・・
   'タイトルのセット
   'oChart.ChartTitle.Text = "ユーザ数の推移"

   oChart.HasTitle = False
         ・・・


テキストボックスの配置

グラフ内にテキストボックスを配置する方法は、『#39)名前アイコン生成 ③』で紹介しました。Shapes オブジェクトの AddLabel メソッドを使用するんでしたね。

今回は、タイトル、ダブタイトル、Y 軸ラベルでテキストボックスを使用します。そこで、オブジェクトを作成する汎用的な関数 xCreateTextBox を作成します。

引数はグラフを表す Shape オブジェクトです。テキストボックスの作成以外に共通の設定である垂直方向の中央揃えと文字色を設定しています。

Function xCreateTextBox(voShape As Variant) As Variant
   Dim oText As Variant

   Set oText = voShape.Chart.Shapes.AddLabel(msoTextOrientationHorizontal, 0, 0, 50, 10)
   With oText.TextFrame2
      '垂直方向に中央揃え
      .VerticalAnchor = msoAnchorMiddle
      '色の設定
      .TextRange.Font.Fill.ForeColor.RGB = RGB(32, 32, 32)
   End With

   Set xCreateTextBox = oText
End Function

なお、オブジェクトの配置や文字サイズなどはテキストボックス毎にバラバラになります。そのため、この関数の外側で設定する前提としています。ですので、オブジェクト作成時に初期設定しているオブジェクトの位置とサイズは適当な値で、意味はありません。


次回の予告

上記関数のテストを行います。次の通り、テキストボックスの作成して、文字をセットします。

Sub Initialize
          ・・・
   'プロット領域の設定
   oChart.PlotArea.InsideWidth = 350
   oChart.PlotArea.InsideLeft = 50
   oChart.PlotArea.InsideTop = 7
   oChart.PlotArea.InsideHeight = 170

   '関数テスト(テキストボックスの作成)
   Dim oText As Variant
   Set oText = xCreateTextBox(oShape)
   oText.TextFrame2.TextRange.Text = "Test"


   oXls.Visible = True
End Sub

※ 上記コードはテスト目的であり、以降の作業では不要です。


実行すると左と上に隙間が空きます。次回はこの挙動について整理したいと思います。


前回 Notes - Excel 連携 次回


2024/10/31

ノーツ・しこく・フェスタ 2024:AIとNotes/Domino 禁断のコラボ ~ 生成 AI 実装事例集

2024年 10月 25日にうどん(香川)県 高松市で開催された『ノーツ・しこく・フェスタ 2024』のレポートです。イベント本体はもちろん有意義なものでしたが、前後のエクストラツアーも充実しており、全方位で魅力的なイベントでした(結果的に 4 日間もうどん県に滞在...)。

イベントの全体像については、以下のブログを参照ください。一緒に参加した仲間がすでに公開してくれています。

ノーツコンソーシアムブログ:ノーツ・しこく・フェスタ2024 開催

Domino Lab:NSF 2024 エクストラツアー


私はこのイベントで、ミニセッションを担当しました。『AIとNotes/Domino 禁断のコラボ ~ 生成 AI 実装事例集』というタイトルで ”一席” 設けさせていただきました。

生成 AI 実装事例ということで、OpenAI の gpt-4o の API と連動したノーツアプリを 4 本を一気に紹介させていただきました。

どんなアプリなのか、ざっくり紹介させていただきます。


1. AI じゃダメなんですか? - AI 文書仕分

文書作成者がタイトル、カテゴリ、要約を記入していると、その人の知識や経験、好みや文章力で統一感がなくなるという、文書管理のお悩みあるあるを AI で解決しようというサンプルです。

Notes/Domino に関する HCLSoftware Blog をコピペしてノーツ文書として保存し、勝手に事例にさせていただきました。もとの文書を AI に送信し、タイトル、カテゴリ、要約を生成させています。

ビューで比較すると効果は一目瞭然です。AI ってすごいですね。

カテゴリは、ドキュメントタイプ、テーマ/トピック、キーワード(タグ的なもの)の 3 種類生成しています。これらカテゴリを複数の人間が的確に行うのは不可能に近いと思います。

ビューのカテゴリ分けはノーツの特徴的な機能で大変便利です。それを活かすためにもカテゴリの適切な設定、平準化は重要ですよね。


2. AI が導く、次世代の学習体験 - AI Learning

eLearning のコンテンツって作成時間がかかる割に利用が一瞬だったり、さまざまなレベルの利用者を前提したコンテンツが難しかったりと、作成作業は非常に非効率ですよね。これを AI に助けてもらおうというサンプルです。

次のように学習のテーマと目標を入力し、学習をスタートするだけで、コンテンツは AI が生成してくれるというアプリケーションです。

生成されたコンテンツは、次の通り、体裁を整えたノーツ文書として提供されます。キーワードはリンクになっていて、クリックするその単語をより詳しく教えてくれます。

これを繰り返し、どんどん調査(学習)を繰り返すという仕組みです。生成されたコンテンツはノーツの返答文書として階層管理されます。返答文書のツリーをビューで確認すると学習の進捗がわかります。クリックしたキーワード別のビューでは用語集になり、全ユーザ分をキーワードでカテゴライズすると同じ悩み(学習課題)を持つ仲間を探したりできます。

当日のセッションでは解説しなかった技術的な補足を少しだけします。

コンテンツはすべて AI に生成させています。各セクションのタイトルや文章以外にリンクにすべきキーワードも AI 任せです。ノーツの文章として表現する(プログラムで利用する)ためにこれらが的確に判定できる必要があります。これを実現するために、gpt-4o で使用できる Structured Outputs という機能を利用しています。この機能は、AI の返答をあらかじめ指定したスキーマに従わせることができます。要は返答がこちらが指定する JSON フォーマットになるので、どの部分がタイトルで、キーワードは何かが後続のプログラムで簡単に判別できます。

ちなみに、コンテンツの生成は DXL を使用しています。フォントサイズや行間の設定はもちろん、リンクも生成しています。LotusScript で記述されたホットスポットのアクションで、その段落内の文章とともにクリックしたキーワードについて AI に問い合わせしており、連続性のあるコンテンツが生成されるようにしています。


3. 添付画像の説明を生成 - PicInsight

古いノーツで添付した文書はサムネイルが表示されません。また、XPages や LotusScript で添付した文書も同様です。こういった画像ファイルを AI に食べさせ画像に何が写っているか説明させようというものです。

この結果をビューに表示すると、文書や画像を開くことなく、どのような画像か判別できます。より詳細に確認したい画像だけ開けばいいので、便利ですね。


4. AI はとびきりの愛なんだよ! - 忖度メール AI(アイ)

こちらは AI Learning と同様、文章生成系のアプリです。メールの宛先となる方の性格や趣味・趣向と要件を入力すると、メールの文面を考えてくれるアプリです。

メールの作成条件を入力してから、文面作成を依頼します。

すると、次のように素晴らしい文章が生成されます。新しいバイクが欲しいという欲求をダイレクトに伝えることなく、買ってもいいかな?買ったほうがいいかな?と思わる文章になっていますね。私には書けない文章です...

文面の作成は何度も実行できますので、気に入った文書が出るまで繰り返すことができます。実用においては、相手との関係性などを考慮して、言葉尻や事実関係などの調整が必要かもしれませんが、話の流れや構成はそのまま利用できると思います。


まとめ

これまで、AI との連携はつながるかどうかの検証など、技術的な側面からだけ見ていました。今回のセッションはサンプルアプリの紹介がテーマだったので、利用シーンなどを考えながらアプリ開発を行い、AI 利用の可能性を大きく感じました。

AI は膨大な知識を持っています。API を利用するとその知識や情報量を利用したアプリが作成できます。知らないことはもちろんですが、自分にない発想で回答してくれます。新たな気づきが得られることに感動しました。

AI への問い合わせは、標準化された方法で問い合わせします(連載『つないでみよう』参照)。パターンが決まっているということは、応用が簡単ということです。今回紹介した 4 アプリとボツとなった 2 本を合わせて、6 DBを作成しました。9月 19日 の DominoHub Osaka が終わって本格的に作成したので、期間は約 1 ヶ月間でした。Notes/Domino が備える基本機能の高さもありますが、開発効率が高いと感じました。スピード感をもって対応できますね。

AI × Notes/Domino。これまでとは違う新しい世界、そしてその可能性を感じました。今後の展開が楽しみです!


最後に、今回のセッションはノーツコンソーシアム 大阪研でともに活動している 林哲司さん と共同で行いました。前半は私の事例紹介で、後半は林さんの『Notesアプリと生成AIを融合 生成AI実装のポイントと課題』でした。

AI 連係の実装については林さんのブログ『Notes開発者のためのXPagesデザインレシピ』も参考にしてください。

AI 関連の記事はこちら


2024/10/23

Notes - Excel 連携:#44)軸のフォントサイズ設定と PlotArea の調整

#41)グラフの調整』から開始した ”使える” グラフにするための調整作業です。いよいよプロット領域を調整するコーディングを始めます。


軸のフォント調整の必要性

#42 で PlotArea の位置調整で無理な値を設定した場合、エラーもなく適当な値に設定されることを紹介しました。そして、PlotArea の Left や Width が指すのは、軸エリアを含んでいました。ということは、PlotArea の調整の前に軸エリアのサイズを把握しておく必要があります。

例えば軸エリアの幅は桁数だけでなく、フォントサイズにも依存します。そこで、Excel が自動で設定するフォントサイズではなく、明示的に指定することで、見た目の統一感と軸エリアのサイズを固定化を狙います。


軸のフォントサイズ設定

エージェントに以下のコードを追加して軸のフォントサイズを明示的に設定します。

Sub Initialize
         ・・・
   'Y 軸補助目盛線の設定
   oChart.Axes(xlValue).MinorUnit = 250
  
   '軸フォントサイズの指定
   oChart.Axes(xlValue).TickLabels.Font.Size = 10
   oChart.Axes(xlCategory).TickLabels.Font.Size = 10

  
   oXls.Visible = True
End Sub

軸のアクセスは #16#17 で紹介しました。Chart オブジェクトの Axes メソッド でアクセスするんでしたね。引数に xlValue を指定すると縦軸、xlCategory を指定すると横軸となります。

この Axes メソッド はヘルプでの説明が不明瞭です。戻り値には Object としか書いていません。Microsoft Learn の Excel 内を調査したところ軸に関する 2 つのオブジェクトが見つかりました。

Axes 指定したグラフ内のすべての Axis オブジェクトのコレクション
Axis グラフの 1 つの軸を表します

この結果より Chart オブジェクトの Axes メソッド で xlValue や xlCategory を指定することにより、Axis オブジェクトを返すと想定できます。Axis オブジェクトには、軸に関するさまざまなプロパティが存在し、TickLabels プロパティから取得する同名のオブジェクトの Font プロパティを利用して、フォントサイズがセットできます。

Axis.TickLabels プロパティ (Excel)

TickLabels.Font プロパティ (Excel)


PlotArea の調整

今回、作成しているグラフのサイズは 500 x 200 ポイントでした。

   'グラフの作成
   Set oShape = oSheet.Shapes.AddChart2(, 4, 200, 1, 500, 200)

このサイズに合わせて、グラフの配置を決定します。

横幅の設定は縦軸エリアを 50 ポイント、グラフエリアを 350 ポイント、残りをグラフタイトルや凡例エリアとします。

続いて高さについてです。軸ラベルの高さは文字サイズの 2 倍程度の幅が必要そうです。また、縦軸を見ると、一番上の縦軸ラベルが上にはみ出ています。文字がセンタリングされているので、軸のフォントサイズ 10 ポイントの半分です。これを考慮して、7 ポイント下に下げ、少し余裕を持たせることにします。以上の結果からプロット領域の高さは 70 ポイントとすればよさそうです。

これらの設定を行うプログラムは下記の通りです。いたってシンプルなコードなのですが、正常に動作させるために重要なポイントがあります。

一部は #42 で触れていますが、わかりますか? 

Sub Initialize
         ・・・
   '軸フォントサイズの指定
   oChart.Axes(xlValue).TickLabels.Font.Size = 10
   oChart.Axes(xlCategory).TickLabels.Font.Size = 10
  
   'プロット領域の設定
   oChart.PlotArea.InsideWidth = 350
   oChart.PlotArea.InsideLeft = 50
   oChart.PlotArea.InsideTop = 7
   oChart.PlotArea.InsideHeight = 170


   oXls.Visible = True
End Sub

正解はプロパティを設定する順番です。

デフォルトで作成されるグラフは、横幅がエリア全体を使っていますが、高さはグラフタイトルと凡例が配置され、プロット領域は狭くなっています。

今回のように、プロット領域を右にずらそうとすると、先に幅を縮める必要があります。逆に高さは広げることになるので、先に上に移動しておかないと必要な高さに変更できないということですね。


実行結果

ここまでの修正で作成されるグラフは次のようになります。

プロット領域の配置はこれで OK ですね。次回は、ラベルの調整を行います。


前回 Notes - Excel 連携 次回


2024/10/22

Notes - Excel 連携:#43)作業前のコードの確認

前回は PlotArea のプロパティについて調査しましたので、今回からはその設定を行うコーディングを始めます。

今回以降の作業は『#18)グラフの目盛線』のプログラムをベースに行います。ただ、その記事から期間が空いていること、連載が長くなっていることから、いったん全体のコードを掲載します。

今回は、作業のスタート地点の確認だけの記事です。ご了承ください。


設計要素としては、エージェントとスクリプトライブラリとなります。


エージェント

内容は #18 と同じですが、#25 でスクリプトライブラリ名を lsXls に変更したので、その点だけ修正しています。

Option Declare
Use "lsXls"

Sub Initialize
   Dim oXls As Variant
   Dim oSheet As Variant
   Dim oShape As Variant
   Dim oChart As Variant

   'Excel の準備
   Set oXls = CreateObject("Excel.Application")
   Call oXls.Workbooks.Add
   Set oSheet = oXls.Workbooks(1).WorkSheets(1)

   'サンプルデータのセット
   Call xSetSampleData(oSheet)

   'グラフの作成
   Set oShape = oSheet.Shapes.AddChart2(, 4, 200, 1, 500, 200)
   Set oChart = oShape.Chart

   'データソースの指定
   Call oChart.SetSourceData(oSheet.Range("A:A, B:B"))

   'タイトルのセット
   oChart.ChartTitle.Text = "ユーザ数の推移"

   'Y 軸の設定
   oChart.Axes(xlValue).MinimumScale = 500
   oChart.Axes(xlValue).MaximumScale = 2000
   oChart.Axes(xlValue).MajorUnit = 500

   'X 軸の設定
   oChart.Axes(xlCategory).CategoryType = xlTimeScale
   oChart.Axes(xlCategory).MajorUnit = 7
   oChart.Axes(xlCategory).TickLabels.NumberFormatLocal = "m/d;@"

   '目盛線の追加
   Call oChart.SetElement(msoCategoryGridLinesMajor)
   Call oChart.SetElement(msoValueGridLinesMinorMajor)
   Call oChart.SetElement(msoCategoryGridLinesMinorMajor)

   'Y 軸補助目盛線の設定
   oChart.Axes(xlValue).MinorUnit = 250
  
   oXls.Visible = True
End Sub

Function xSetSampleData(voSheet As Variant)
   Dim i As Integer
   Dim vDT As Variant
   Dim iUsr As Integer
   Dim iVal As Integer
   Dim iMax As Integer

   '列フォーマットの設定
   voSheet.Columns(1).NumberFormatLocal = "yyyy/m/d"
   voSheet.Columns(2).NumberFormatLocal = "#,##0_ "
   voSheet.Columns(3).NumberFormatLocal = "#,##0_ "

   'ヘッダ行のフォーマット設定
   voSheet.Rows(1).NumberFormatLocal = "@"

   'カラム名称の設定(ヘッダ行)
   voSheet.Cells(1, 1).Value = "日付"
   voSheet.Cells(1, 2).Value = "ユーザ数"
   voSheet.Cells(1, 3).Value = "増減"

   'サンプルデータの設定
   Randomize
   iMax = 30
   vDT = Today - iMax
   iUsr = 1000

   For i = 2 To iMax + 1
      vDT = vDT + 1
      iVal = Int(Rnd()*100) - 30
      iUsr = iUsr + iVal
      voSheet.Cells(i, 1).Value = vDT
      voSheet.Cells(i, 2).Value = iUsr
      voSheet.Cells(i, 3).Value = iVal
   Next
End Function


スクリプトライブラリ

スクリプトライブラリ lsXls は #18 より後に掲載した内容も含め、現時点での最新の状態としています。少し長くなりますが、下記の通りです。

Option Declare

'XlAxisType 列挙 (Excel)
Public Const xlCategory = 1
Public Const xlValue = 2

'XlCategoryType 列挙 (Excel)
Public Const xlAutomaticScale = -4105
Public Const xlCategoryScale = 2
Public Const xlTimeScale = 3

'MsoChartElementType 列挙 (Excel)
Public Const msoCategoryGridLinesMajor = 334
Public Const msoValueGridLinesMinorMajor = 331
Public Const msoCategoryGridLinesMinorMajor = 335

'XlBordersIndex 列挙 (Excel)
Public Const xlEdgeLeft = 7 '範囲の左側の罫線
Public Const xlEdgeTop = 8 '範囲の上側の罫線
Public Const xlEdgeBottom = 9 '範囲の下側の罫線
Public Const xlEdgeRight = 10 '範囲の右側の罫線
Public Const xlInsideVertical = 11 '範囲の外側を除くすべての垂直罫線
Public Const xlInsideHorizontal = 12 '範囲の外側を除くすべての水平罫線

'XlBorderWeight 列挙 (Excel)
Public Const xlHairline = 1 '細線 (最も細い罫線)
Public Const xlMedium = -4138 '普通
Public Const xlThin = 2 '極細
Public Const xlThick = 4 '太線 (最も太い罫線)

'XlLineStyle 列挙 (Excel)
Public Const xlContinuous = 1 '実線
Public Const xlDouble = -4119 '2 本線
Public Const xlLineStyleNone = -4142 'なし
Public Const xlDash = -4115 '破線

'XlHAlign 列挙 (Excel)
Public Const xlHAlignLeft = -4131 '左揃え
Public Const xlHAlignCenter = -4108 '中央揃え
Public Const xlHAlignRight = -4152 '右揃え
Public Const xlHAlignGeneral = 1 'データの種類に従って揃える

'XlPaperSize 列挙 (Excel)
Public Const xlPaperA3 = 8
Public Const xlPaperA4 = 9
Public Const xlPaperB4 = 12
Public Const xlPaperB5 = 13

'XlPageOrientation 列挙 (Excel)
Public Const xlPortrait = 1 '縦モード
Public Const xlLandscape = 2 '横モード

'XlFixedFormatType 列挙
Public Const xlTypePDF = 0
Public Const xlTypeXPS = 1

'MsoAutoShapeType 列挙 (Office)
Public Const msoShapeOval = 9 '楕円

'MsoTextOrientation 列挙 (Office)
Public Const msoTextOrientationHorizontal = 1 '横方向

'MsoVerticalAnchor 列挙 (Office)
Public Const msoAnchorMiddle = 3 '垂直方向に中央揃え

'MsoParagraphAlignment 列挙 (Office)
Public Const msoAlignCenter = 2 '中央揃え

Public Function GetRangeString(_
         ByVal viRowFm As Integer, ByVal viColFm As Integer, _
         ByVal viRowTo As Integer, ByVal viColTo As Integer) As String
   Dim s As String

   s = RCToA1(viRowFm, viColFm)
   s = s & ":" & RCToA1(viRowTo, viColTo)

   GetRangeString = s
End Function

Public Function PointToCM(ByVal vdPoint As Single) As Single
   PointToCM = InchToCM(PointToInch(vdPoint))
End Function

Public Function PointToPixcel(ByVal vdPoint As Single) As Integer
   PointToPixcel = CInt(vdPoint / .75)
End Function

Public Function InchToPixcel(ByVal vdInch As Single) As Integer
   InchToPixcel = CInt(vdInch * 96)
End Function

Public Function CMToInch(ByVal vdCM As Double) As Single
   CMToInch = vdCM / 2.54
End Function

Public Function RGB(ByVal vbyR As Byte, ByVal vbyG As Byte, ByVal vbyB As Byte) As Long
   RGB = vbyR + CLng(vbyG) * 256 + CLng(vbyB) * 256 ^ 2
End Function

Public Function InchToPoint(ByVal vdInch As Single) As Single
   InchToPoint = vdInch * 72
End Function

Public Function PixcelToInch(ByVal viPixcel As Integer) As Single
   PixcelToInch = viPixcel / 96
End Function

Public Function PixcelToPoint(ByVal viPixcel As Integer) As Single
   PixcelToPoint = viPixcel * .75
End Function

Function x9ToA(ByVal viColNumber As Integer) As String
   Dim i As Integer
   Dim i1 As Integer
   Dim i2 As Integer
   Dim s As String

   i = viColNumber - 1
   i1 = (i Mod 26) + 1
   i2 = Int(i / 26)

   If i2 > 0 Then s = Chr(64 + i2)
   s = s & Chr(64 + i1)

   x9ToA = s
End Function

Public Function CCToAA(ByVal viColFm As Integer, ByVal viColTo As Integer) As String
   CCToAA = x9ToA(viColFm) & ":" & x9ToA(viColTo)
End Function

Public Function CMToPixcel(ByVal vdCM As Single) As Single
   CMToPixcel = InchToPixcel(CMToInch(vdCM))
End Function

Public Function RCToA1(ByVal viRow As Integer, ByVal viCol As Integer) As String
   RCToA1 = x9ToA(viCol) & CStr(viRow)
End Function

Public Function InchToCM(ByVal vdInch As Single) As Single
   InchToCM = vdInch * 2.54
End Function

Public Function PointToInch(ByVal vdPoint As Single) As Single
   PointToInch = vdPoint / 72
End Function

Public Function PixcelToCM(ByVal viPixcel As Integer) As Single
   PixcelToCM = InchToCM(PixcelToInch(viPixcel))
End Function

Public Function CMToPoint(ByVal vdCM As Single) As Single
   CMToPoint = InchToPoint(CMToInch(vdCM))
End Function


次回
前回 Notes - Excel 連携