今回作るもの
コードからインポートしたモデルから図を自動生成すると、関連や依存が絡み合った複雑な図となってしまいます。自動整列機能もあるものの、なかなか思ったように整列してくれません。結局手動で整列させていくのですが、関連を一つずつ辿りながら並び変えるのは手間です。
というわけで、この作業をプラグイン化してみましょう。
コード
前回作成したプロジェクトを開き、TemplateAction.ktを編集していきます。
class TemplateAction : IPluginActionDelegate {
override fun run(window: IWindow): Any? {
val diagramViewManager = AstahAPI.getAstahAPI().viewManager.diagramViewManager
val targetNodes = diagramViewManager.selectedPresentations
.filterIsInstance<INodePresentation>()
.flatMap { node ->
node.links.filter { it.source == node }.map { it.target }
}.toTypedArray()
diagramViewManager.select(targetNodes)
return null
}
}
ビルドして実行してみましょう。
以下のようなクラス図で、真ん中の要素が選択された状況で説明していきます。
コード説明
val diagramViewManager = AstahAPI.getAstahAPI().viewManager.diagramViewManager
は、APIからViewManager、DiagramViewManagerを取得しています。DiagramViewManagerは現在開いている図に関するAPIを提供してくれます。
diagramViewManager.selectedPresentations
は、現在選択されている図要素を取得します。
astahでは、モデル(Model)と図要素(Presentation)は別物として扱われています。例えばクラス図にクラスを置くと、モデルとしてIClassのインスタンスが、図要素としてINodePresentationのインスタンスが生成されます。
上記の図では「関連線、クラス0、クラス7」が選択されており、selectedPresentationsでは「ILinkPresentation, INodePresentation, INodePresentation」が取得できます。
図要素は、箱(INodePresentation)と線(ILinkPresentation)の二種類です。今回は線は対象外としてINodePresentationだけ扱うようにしています。
.filterIsInstance<INodePresentation>()
では、selectedPresentationsの内、INodePresentationのものだけ通すフィルターで、ついでに型変換も行ってくれます。
selectedPresentationsは、NodeもLinkも混じったIPresentationの配列ですが、このメソッドを通すことで、INodePresentationの配列(厳密にはStream<INodePresentation>)に変換してくれます。
次の.flatMap { node -> node.links.filter { it.source == node }.map { it.target } }
は少々複雑です。分解して見ていきましょう。
flatMap
は一旦置いておいて、node -> node.links
から見ていきます。仮変数nodeには、先程INodePresentationにフィルタされた要素が一つずつ渡ってきます。今回だと「クラス0」のINodePresentationと「クラス7」のINodePresentationです。
node.linksでは、Nodeに繋がっている線の配列を返します。
.filter { it.source == node }
「クラス0」Nodeのlinksでは「1,2,3」が、「クラス7」のlinksでは「4」のILinkPresentationが返ってきます。今回は矢印の先の要素が欲しいので、自身から生えていないものは除外します。
(※ 実際にはILinkPresentationのsource, targetは矢印の方向ではなく、作成時の作図順によるものです。厳密に矢印の方向でフィルタする場合はもう少し複雑な処理が必要ですが今回は割愛します。)
.map { it.target }
また選択したいのは線ではなく、その先に繋がっている要素なので、linkからその先(target)に繋がってる要素に変換(map)します。
node -> node.links.filter { it.source == node }.map { it.target }
で、[クラス2, クラス1][クラス8]のINodePresentationとなりますが、2つの要素群となっているため、flatMap{}
で一つに纏めて[クラス2, クラス1, クラス8]にします。
ここまではKotlinのStreamなので、APIに渡すために.toTypedArray()
でJavaの配列に変換します。
これでval targetNodes
には、選択された要素から繋がってる要素のINodePresentation[]が入ります。
最後にDiagramViewManager.select(nodes:INodePresentation[])に、この配列を渡して選択状態にします。
各関係ごとの修正
継承関係でも試してみましょう。
継承関係では、親クラスが選択されます。どちらかというと、子要素を全部選択したいですよね。また継承と関連が入り混じっていると選びにくいので継承に限定します。
val targetNodes = diagramViewManager.selectedPresentations
.filterIsInstance<INodePresentation>()
.flatMap { node ->
node.links
.filter { it.model is IGeneralization }
.filter { it.target == node }
.map { it.source }
}.toTypedArray()
.filter { it.model is IGeneralization }
を加えて、線のモデルが継承(IGeneralization)に限定し、.map{it.source}
で子要素を選択しています。
図要素とモデルの対応関係はastah* API 利用ガイドで確認できます。
今回のコード
次回
[#3 複数の要素にステレオタイプを一括適用する]