興味のある人のためのJetBrains MPS#3

ビンゴ・ボンゴとジンボ・ジャンボ、親愛なる友人たち!



私は2日間ダーチャに光を当てていませんでした。私は実質的に枯れて冬眠しましたが、再びここに来ました! この投稿では、天気予報の記述と小さなコードの記述を開始しますが、マウスを突かないでください ! やった! ついに!







どんな予測をしたいのか



とても簡単です! 今のところ、私たちは翌日だけを予測し、自分たちで規則を考え出します。 むしろ、ルールはありません。 翌日、今日とまったく同じように温度を表示します。 プロジェクションエディタの機能を示す小さなジョークを1つ作成してみましょう。







コンセプト



この場合、クールな機能に頼ります-ソースデータへのリンクのみを含むコンセプトを作成し、Swingコンポーネントにデータをグラフとして表示します。 どのようにできるかについてですが、スイングはしますが、私は恐怖が嫌いです。







画像







概念PredictionResultを作成し、参照「入力」を追加します。これは、現在のスコープASTの概念の実装への参照です。 しかし、スコープやscopeは必要ないので、このタイプのすべての要素を検索しても大丈夫です(ちなみに、 ScopesはMPS +で最も簡単なトピックではありません。いつか。)しかし、 WeatherDataのIDを追加する必要があります。構造とエディターのアスペクトを少し変更しましょう







画像



implementsの後にINamedConceptを追加し、 WeatherDataの概念に名前が付けられましたが、割り当てないため、 Editorを変更します。







画像



ここで、名前を含む1行を追加しました。 言語を再構築して、何が起こったのか見てみましょう。







画像



Hooray、このWeatherDataを 「今日」という名前に変更し、 PredictionResultコンセプトに戻ってエディターのアスペクトを変更します。







画像



これまでのところ。 明日の予測、data%name_of_weather_data%を表示します

PredictionListに概念を追加します。これは、これまでのところ入力データのみが存在するルート概念です。







画像







画像







あなたが収集する場合、それは判明します







画像



...私たちが欲しかったもの。 リストからWeatherDataを選択できます( WeatherDataが1つしかないことは問題ありませんが、拡張可能です)。







さて、今度は予測をなんとかクールに表示する必要があります。 javax.swing-誰も知らない場合は、swingコンポーネントで出力することを既に書きました -Javaのネイティブグラフィカルインターフェイスを開発するためのパッケージ。 IntelliJ上に構築されています。 Swingコンポーネントはエディターで使用できます。 ウリヤ。







全体を描く前に、私たちがどのように行動するかを書き留めます。







  1. グラフの幅をピクセル単位で取得し、60 * 24-下部の分数で除算します。 これは、横軸に沿ってポイントを正しく表示するために必要です。







  2. すべての温度を1つの測定単位(たとえば摂氏)に変換し(その後、調整して摂氏または華氏で表示できるようにします)、最高温度と最低温度を見つけます。 最大値から最小値を減算し、度単位で完全な「高さ」を取得します。 一番下の行は、グラフの高さをこの量で割ると、「1度のピクセル」の数が得られるということです。 これは、グラフに温度を投影するために必要です。







  3. 入力データの配列を時間(00:00に近い-もちろん少ない)で並べ替えて処理します。 式によってxを計算します







    $$表示$$ time_in_minutes * factor_of_point_1 $$表示$$







    y







    $$表示$$温度*係数__of__point 2 $$表示$$







    PSフォーミュラは恐ろしい







  4. 描く!


段階的な行の書き込みであなたを苦しめないために、私は全体を投げ捨てて、多かれ少なかれ難しい場所を歩きます。







{ final int chartWidth = 400; final int chartHeight = 200; final JPanel panel = new JPanel() { @Override protected void paintComponent(final Graphics graphics) { super.paintComponent(graphics); editorContext.getRepository().getModelAccess().runReadAction(new Runnable() { public void run() { string unit = node.unit; final list<Point2D.Double> labels = node.input.items.where({~it => !it.temperature.concept.isAbstract(); }).select({~it => message debug "Woaw!" + it.temperature.concept.isAbstract(), <no project>, <no throwable>; double x = it.time.hours * 60 + it.time.minutes; double y = it.temperature.getValueFromUnit(unit.toString()); new Point2D.Double(x, y); }).sortBy({~it => it.x; }, asc).toList; final double minTemp = labels.sortBy({~it => it.y; }, asc).first.y; final double maxTemp = labels.sortBy({~it => it.y; }, asc).last.y; final double yKoef = chartHeight / (maxTemp - minTemp); final double xKoef = chartWidth / (60.0 * 24.0); int prevY = chartHeight; int prevX = -1; Graphics2D g2 = ((Graphics2D) graphics); labels.forEach({~it => message debug unit + "/" + it.y, <no project>, <no throwable>; int xTranslated = (int) (it.x * xKoef); int yTranslated = chartHeight - (int) ((it.y - minTemp) * yKoef); g2.setStroke(new BasicStroke(1)); if (prevX > 0) { // It is first element, no need to draw trailing line g2.drawLine(prevX, prevY, xTranslated, yTranslated); } g2.drawString(String.format("%.2f", it.y) + unit, xTranslated + 3, chartHeight - Math.abs(chartHeight - (yTranslated + 20))); g2.setStroke(new BasicStroke(5)); g2.drawLine(xTranslated, yTranslated, xTranslated, yTranslated); prevX = xTranslated; prevY = yTranslated; }); } }); } }; panel.setPreferredSize(new Dimension(chartWidth, chartHeight)); return panel; }
      
      





最初に目を引くのはeditorContext.getRepository().getModelAccess().runReadAction...









これはMPSエディターのこのような機能です。どこからでもモデル/ノードにアクセスするには、このコードの実行を要求する必要があります。 これは、AndroidのrunOnUIThread



に似ています。意味はほぼ同じです。 つまり、メインスレッドから何かを取得する必要がある場合は、そのようにする必要があります。 runWriteAction



runWriteAction



、変更を加えるために必要であり、それも必要です。







内部で起こっていること:







1)ユニットを定義します

2)グラフの幅と高さを決定する

3) WeatherTimedData型の配列をjava.awt.geom.Point2D.Double型のリストに変換します。ここで、











x=60+







y =選択した測定値の温度(摂氏など)。







baseLanguage構文を使用します。これにより、コレクションの操作が容易になり、さまざまなパターン( mapfilterflatMapなど)の通常の使用が可能になります。 当然

通常の名前の代わりに、 selectwhereselectManyがそれぞれ使用されます。







注意! where({~it => !it.temperature.concept.isAbstract(); })



フィルタリングを担当するコード、つまりwhere({~it => !it.temperature.concept.isAbstract(); })



-新しいwhere({~it => !it.temperature.concept.isAbstract(); })



を初期化するとき、温度は初期化していません。 つまり、摂氏または華氏にはデフォルトがないため、抽象的な温度があり、このフィルタリングを追加しないと、エディターがフリーズします。 ここに、経験があります!







4)温度の上限と下限を取得し、軸上の投影に対して同じ「係数」を取得します

5)コンポーネントの描画は非常に単純な部分です。 最初のポイントを描画する場合-ポイントと温度シグネチャのみを描画し、最初のポイントを描画しない場合-前のポイントと現在のポイントの間に線を描画します。 それに加えて、あらゆる種類の視覚的ないたずら、端からの字下げ、テキストを見ることができます。







画像



わあ! 実際のスケジュールとは何ですか? コードエディタで 温度または時間を変更すると、どのリアクティブに更新されますか? わあ!







それでも、チャートの幅と高さはハードコードされており、単位を選択することもできません。







ここで、ハードコーディングされた「°C」、「°F」をすべて列挙データ型に置き換えます。 MPSのコンテキストでのみ、列挙の本質を説明する価値はないと思います。







列挙データ型は、プロパティで使用できる単純な列挙クラスです。

以前にstringinteger 、および_FPNumber_Stringのみを使用していた場合は、摂氏と華氏の2つの要素がある温度単位の列挙を作成できます。







WeatherPrediction.structureのRMB→新規→データ型の列挙→ TemperatureUnit







画像



タイプ、この場合は文字列を選択します

デフォルト値が必要なので、デフォルトなしで falseのままにします

デフォルト=最初のメンバー(摂氏)

メンバー識別子 -入力により要素を決定します。 TemperatureUnit値を変更するには、どちらを選択するかに応じて、各内部値または外部値と比較される文字列を入力する必要があります。







私が説明します:左と青はenum要素の内部値です。 隠されています。 右-外部、エディターでの表示に使用されます。







つまり、 メンバー識別子の 内部値から派生を選択した場合は、 摂氏または華氏のいずれかの値を設定する必要があります。 プレゼンテーションから派生を選択した場合、 °Cまたは°Fの線で値を設定する必要があります たとえば、カスタムIDを追加して、内部および外部の値に応じて値を設定することもできますが、私たちはこれを必要としません。







プレゼンテーションから派生を選択し、2つの要素を追加します。







明らかに!







ユニットプロパティをPredictionResultに追加します。







画像







次に、測定単位を選択するドロップダウンリストを追加する必要があります。







 string[] units = enum/TemperatureUnit/.members.select({~it => it.externalValue; }).toArray; final ModelAccess modelAccess = editorContext.getRepository().getModelAccess(); final JComboBox<string> box = new JComboBox<string>(units); box.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent p0) { modelAccess.executeCommand(new EditorCommand(editorContext) { protected void doExecute() { Object selectedItem = box.getSelectedItem(); node.unit = selectedItem.toString(); } }); } }); box.setSelectedIndex(0); box;
      
      





これは、 PredictionResultエディターコード内の別の$ swingコンポーネント$のコードです。 使用可能な温度単位のリストを取得し、ドロップダウンリストを作成して、イベントハンドラーを切断します。 また、 readActionまたはwriteActionの代わりに「MPSジョーク」を使用し、単にexecuteCommandを実行できます。 明らかに、読みやすさのために前の2つが存在します。







選択した項目をJComboBoxから変更すると、node.unitが変更されます。これは、上記で説明したように、文字列値によって設定されます。







言語を収集します。







画像







私を信じて、本当に華氏もあります。 JComboBoxとスケジュールを接続するためだけに残り、これは完了できますが、簡単に実行できます。 元のチャートレンダリングコードを用意します。







ハードコアユニット
 { public void run() { string unit = "°C"; final list<Point2D.Double> labels = node.input.items.select({~it => double x = it.time.hours * 60 + it.time.minutes; double y = it.temperature.getValueFromUnit(unit.toString()); new Point2D.Double(x, y); }).sortBy({~it => it.x; }, asc).toList; final double minTemp = labels.sortBy({~it => it.y; }, asc).first.y; final double maxTemp = labels.sortBy({~it => it.y; }, asc).last.y; final double yKoef = chartHeight / (maxTemp - minTemp); final double xKoef = chartWidth / (60.0 * 24.0); int prevY = chartHeight; int prevX = -1; Graphics2D g2 = ((Graphics2D) graphics); labels.forEach({~it => message debug unit + "/" + it.y, <no project>, <no throwable>; int xTranslated = (int) (it.x * xKoef); int yTranslated = chartHeight - (int) ((it.y - minTemp) * yKoef); g2.setStroke(new BasicStroke(1)); if (prevX > 0) { // It is first element, no need to draw trailing line g2.drawLine(prevX, prevY, xTranslated, yTranslated); } g2.drawString(String.format("%.2f", it.y) + unit, xTranslated + 3, chartHeight - Math.abs(chartHeight - (yTranslated + 20))); g2.setStroke(new BasicStroke(5)); g2.drawLine(xTranslated, yTranslated, xTranslated, yTranslated); prevX = xTranslated; prevY = yTranslated; }); } }
      
      





はい、あなたは? string unit = "°C";



を置き換えるだけですstring unit = "°C";



string unit = node.unit;



そして私たちはグッチです!







そして今、一番下の行:摂氏と華氏のグラフ、ええ!







画像







画像







PS

少なくともこの記事で伝えたかったことを理解するために、私は多くの注意をそらされたので、この記事ではタイプミスや矛盾がたくさんあると思います。 どんな日であれ、オープニングは、あなたにとって奇妙に思えるすべての瞬間にコメントを書いてください。たぶん私は物語の文脈から外れて、ある種の異端を書きました。







次の記事では、TextGenなどの側面について検討します。 テキスト形式で天気予報を生成します!








All Articles