LATEST ENTRIES
CATEGORIES
ARCHIVES
SPONSORED LINK
MOBILE
qrcode
LINKS
PROFILE
OTHERS

11
--
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
--
>>
<<
--

Agata's Blog

MATLABやPythonとともにD3.jsを使ってデータ解析とデータビジュアライゼーションに挑戦する某エンジニアのBlog
D3.js v4 で世界地図を描く方法
0

    D3で「つまづきの石」になりがちなのが、地図の描画とフォースレイアウトです。そこで今回は、そんなD3の難関のひとつである地図を作成してみます。

     

    大まかな手順は以下の通りです。

     

    Step.1  GeoJSON 又は TopoJSON 形式の地図データを準備する

    Step.2  d3.json メソッドで、準備したJSONデータを読み込む

    Step.3  d3.geoPath メソッドで、SVGのPath要素として地図を描画する

     

    いろいろな本やサイトを見ていると、インターネットで公開されている地図データを GeoJSON/TopoJSON に変換する方法が紹介されていたりします。でも今回は、まずはD3で地図を作成する基本的な手順を知ってもらうために、既に GeoJSON ファイルが準備できているものとして、Step.2 から進めます。

     

    下記のサンプルコードは、公開されている GeoJSON ファイルをインターネット上から読み込んで、メルカトール図法の世界地図を作成するものです。JavaScriptソースコードの23行名以降が Step.2に、また13~17行目と25行目が Step.3 に、それぞれ対応しています。

     

    ちなみに D3 v3からv4へのバージョンアップに伴い、関連するメソッドが以下のように変化しているので、これまでv3を使っていたかたは注意が必要です。

     

    • d3.geo.path() ⇒ d3.geoPath()
    • d3.geo.mercator() ⇒ d3.geoMercator()

     

     

    過去の関連記事

    D3 v4 を使って地震データの一覧表を作成する

    D3 v4で「レ・ミゼラブル」の人物関係を可視化

    過去30日間に世界で発生した地震を可視化する その2

    過去30日間に世界で発生した地震を可視化する その1

     

    JUGEMテーマ:JavaScript

    このエントリーをはてなブックマークに追加
    | D3.js | 19:51 | comments(0) | - | - |
    D3.js v4によるVoronoi図の作り方
    0

      以前、このブログでD3.jsを使ったVoronoi図の作成方法を紹介しました (【D3.js】ボロノイ図の作成)。ところが、v4へのバージョンアップに伴い、Voronoi図を作成するための関数も d3.geom.voronoi() から d3.voronoi() に変更されたため、以前作成したプログラムのままでは D3 v4 でVoronoi図を作成できなくなってしまいました。

       

      そこで、改めて D3 v4 を使って Voronoi図の作成方法について紹介します (Voronoi図そのものの説明については割愛します。Wikipediaの説明などを参照して下さい)。

       

      それでは、下記のサンプルプログラムを使って説明を進めます。1~10行目はもう大丈夫ですね。これまで何度も出てきた、SVGを描画するための領域を定義している部分です。14~16行目では、前回紹介した方法でオリジナルのカラーグラデーションを定義しています。ここでは、0~1の数値を入力引数として与えると、LightGreenからLightCyanになめらかに変化する色を返す関数として定義しました。19~22行目では、乱数を使ってx-y平面上にVoronoi図の母点40個を設定しています。

       

      いよいよ25~28行目が今回のポイントです。そもそも d3.voronoi() というメソッドは何を返しているのでしょうか?答えは、Voronoi図を作成するための「関数」を返しています。具体的には、母点のx-y座標の配列を入力引数として与えると、Voronoi図の領域などを自動的に計算して出力する関数です。d3.voronoi() メソッドに対して、データ配列のどの値をx,y座標の数値として渡すかを、26, 27行目の .x(), .y() メソッドで定義しています。最後の .extent() メソッドは、Voronoi図を描画する範囲を指定するためのものです (JavaScriptやCSSの内容は、下記のタブを選択すると表示されます)。

       

      こうして定義した関数を使って、いよいよ31~41行目でVoronoi図を描画しています。ここで、SVGのPath要素にバインドするデータとして、voronoi.polygons(data) を与えています。これは、25行目で定義した voronoi という関数に data を与えて、それぞれの母点に対するVoronoi領域をSVGのPath要素として描画するためのものです。36~38行目の部分は、上記の関数が返したポリゴンの頂点を結んでSVGのPath要素を生成するためのものです。これらの部分については、後で詳細に見ることにします。最後に40, 41行目では、各ポリゴン要素にマウスオーバー/マウスアウトのイベントが発生した場合に、クラス要素を書き換えています。これは、CSS側でクラス名に従って色を変えるように指定しているためです。つまり、JavaScriptによるSVG要素のクラス名変更処理と、CSSによる色の設定が協調することで、マウスオーバー/マウスアウトされたポリゴン要素だけの色が変化するようにしているというわけです。

       

       

      さて、これだけだとなかなか分かりにくいので、32行目に出てきた voronoi.polygons(data) の動きもう少し詳細に見てみることにしましょう。例えばChromeなどのブラウザの開発者モードを使って、JavaScriptコンソールで voronoi.polygons(data) を呼び出すと、以下のような配列を生成していることが分かります。Voronoi図との対応が分かるように、1枚の画像の左側にブラウザ表示、右下に voronoi.polygons(data) の出力、右上に生成されたSVG要素を、それぞれ示しています。

       

      まず、右下と右上を比較すると、voronoi.polygons(data) の出力は、ぞれぞれのVoronoi領域を表現するためのPathの頂点のx-y座標の集合であることが分かります。ただ、x-y座標の数値のままではSVGのPath要素として生成できないので、36~38行目の関数でPath要素を指定するフォーマットに整形しているというわけです。

       

       

       

      JUGEMテーマ:JavaScript

      このエントリーをはてなブックマークに追加
      | D3.js | 19:08 | comments(0) | - | - |
      D3.js v4でオリジナルのカラーマップを作成する方法
      0

        以前、d3-scale-chromatic.v1.min.js を使ってグラデーションを作成する方法を紹介しました (D3.js v4で利用可能なカラーマップ一覧)。今回は、d3-scale-chromatic.v1.min.js を読み込まずに、任意のカラーマップを作成する方法を紹介します。

         

        このブログで D3.js v4 で折れ線グラフを作成する方法について紹介したときに、scaleLinear() というメソッドが出てきました。そのときは、データの値の範囲をSVG画像の座標軸にマッピングするために、このメソッドを使っていました。ところがこのメソッド、使い方によっては、数値の範囲をRBGの色の範囲にマッピングすることも可能です。試しに 0~1 の範囲の数値を、0 => cyan, 0.5 => yellow, 1 => orange となるようなオリジナルのカラーマップを作ってみました。

         

        ポイントは、14~16行目の部分です。この部分で、0~1の数値を入力引数として、cyan~yellow~orange の範囲で連続的に変化するRGB値を出力する関数を作成しています。色の指定は、この例のように既に定義されているカラーネームを使うこともできますし、RGB値を直接入力することもできます (せっかくなので、サンプルコードにアニメーションも加えてみました)。

         


         

        JUGEMテーマ:JavaScript

        このエントリーをはてなブックマークに追加
        | D3.js | 21:27 | comments(0) | - | - |
        D3.js v4で等高線プロットを作成する
        0

          D3.js は、以前は1つのライブラリでしたが、v4 にバージョンアップしてからはモジュール化されています。たとえば、これまでに紹介した、カラーグラデーションを作成するための d3-scale-chromatic.v1.min.js というのもモジュールです。

           

          D3.jsにはいろいろなモジュールがありますが、等高線をプロットするためのモジュール、d3-contour.v1.min.js もその1つです。等高線プロットは、いろいろなデータを可視化するうえで無くてはならない手法のひとつなのですが、残念ながらこのモジュールの説明は(特に日本語のページでは)まだまだ少ないのが現状です。そこで今回は、このモジュールを使って簡単な等高線プロットを作成してみました。

           

          まずは d3.v4.min.js といっしょに d3-contour.v1.min.js を読み込みます(下の「HTML」タグをクリックすると、これらのモジュールを読み込む処理を確認できます)。せっかくなので、今回はついでに d3-scale-chromatic.v1.min.js も読み込んでいます。

           

          javascript プログラムでは、14~19行目でサンプルデータを作成しています。ここでは d3.randomNormal というメソッドを使って、(x,y) ともに正規分布に従う乱数400個を生成しています。

           

          次の22~44行目が今回のポイントです。まず22~26行目では、x-y平面上に散布されたデータから密度を計算して、等高線を示すポリゴンデータを生成する関数を定義しています。このなかでは、作成する等高線レベルの数(この例では10個)と等高線プロットの描画サイズを指定しています。

           

          そのうえで、29~44行目では、まずこの関数をつかってポリゴンデータを生成(30行目)したうえで、そのデータをSVGのpath要素として描画しています(35行目~)。37行目以降の部分では、少し見栄えをよくするためにカラーグラデーションと透明度を調整して、個々のポリゴンに色をつけています。

           

           

          JUGEMテーマ:JavaScript

          このエントリーをはてなブックマークに追加
          | D3.js | 20:40 | comments(0) | - | - |
          D3.js v4で利用可能なカラーマップ一覧
          0

            D3.jsがVer.4にバージョンアップしてから、いろいろなカラーマップが利用可能になりました。せっかくなので、今回はv4で追加されたカラーマップを紹介します。

             

            v4から追加されたカラーマップのなかに、0〜1の数値を入力するとRGB形式のカラーベクトルが返ってくるという、便利な関数があります。これらの関数を使うには、D3.js本体 (d3.v4.min.js) のほかに、d3-scale-chromatic.v1.min.js も追加で読み込む必要があります。以下では、このライブラリに含まれているカラーマップ関数一覧をプルダウンメニューから選択すると、そのカラーマップを画面上に表示するプログラムを、D3.jsを使って作成してみました。

             

            javascript のソースコードを表示するには、以下の「JavaScript」タブを選択して下さい。

             

            ます、17行目以降で、関数名の一覧を配列に保存しています。この配列を使って、47~56行目の部分でプルダウンメニューのDOM要素を作成しています (D3.jsのサンプルプログラムではほとんど見かけませんが、じつはこんなふうに D3 でプルダウンメニューを作成することもできます)。このなかに、49行目で on メソッドを使って、プルダウンメニューが変更されたら、カラーバーの表示を更新するための関数が呼び出されるようにしています。 59~71行目は初期状態のカラーバーを作成しています。また、74行目以降で、プルダウンメニューが変更されたときに呼び出される関数を記述しています。

             

             

             

             

            JUGEMテーマ:JavaScript

            このエントリーをはてなブックマークに追加
            | D3.js | 22:19 | comments(0) | - | - |
            D3.js v4でTree図を作成する (3)
            0

              D3.jsを使う魅力は、なんといってもユーザの操作に従ってダイナミックに動くグラフを作成できるという点です。というわけで、前回までに作成した「静的なツリー図」を、インタラクティブに動く「動的なツリー図」にしてみましょう。

               

              以前作成したサンプルと同様に、対象となる html 内のプルダウン要素を指定して、その要素がユーザによって更新されたときにツリー図を更新するようにしました。具体的には、44行目でプルダウン要素を指定したうえで、その要素がユーザ操作によって更新されたときの動作を99行目以降で設定しています。更新処理のなかでは、(1) ノードの位置、(2) エッジの端点、(3) テキストの透明度 のそれぞれを更新しています。ここでは、前回説明したd3.hierarchy()メソッドで生成されたオブジェクトのうち、階層の深さを示す depth の値を参照することで、これら3つの更新処理を実現しています。

               

               

              JUGEMテーマ:JavaScript

              このエントリーをはてなブックマークに追加
              | D3.js | 22:29 | comments(0) | - | - |
              D3.js v4でTree図を作成する (2)
              1

              前回は、D3.js Ver.4を使って簡単なツリー図を作成してみました。そのなかでいろんな関数が出てきましたが、それらはいったい何をしているのでしょうか。ちょっと覗いてみることにしましょう。

               

              ツリー図を作成するうえで、ポイントとなるのは次の4行です。

               

              // Treeレイアウト
              var treemap = d3.tree()
              .size([width, height]);
              // 階層データの前処理
              var rootNode = d3.hierarchy(treeData);
              // ノード描画用のデータ
              var nodes = treemap(rootNode);
              // ノード間を結ぶパス描画用のデータ
              var links = nodes.links();
              

               

              最初の d3.tree().size([width, height]) は、描画領域の幅 (width) と高さ (height) にあわせてツリー図を作成するためのメソッドです。次の d3.hierarchy(treeData) は、JSONのような階層構造をもつデータをパースするための関数です。パースした後のオブジェクト rootNode を、先ほど準備した treemap() 関数に入れると、D3.js が自動的に各ノードの x,y 座標を計算して、階層の深さなどの情報とあわせたオブジェクトを返してくれます。ちょっと試しに console.log() を使って変数 nodes を見てみましょう。

               

               

              SVG描画領域の幅と高さにあわせて、各ノードの x,y 座標や階層の深さ (depth) の情報が入っているのがわかります。次にこのオブジェクトに .links() メソッドを適用すると、ノードとノードを結ぶエッジの情報を含む構造体が返ってきます。こちらも console.log を使って中身を覗いてみましょう。

               

               

              オブジェクトのなかに、エッジの両端 (sourceとtarget) それぞれの x,y 座標が入っているのがわかります。

               

              前回のプログラムでは、こうして得られた x,y 座標の情報をもとに、SVG上で円とパスを配置してツリー図を作成していたというわけです。

               

              ただ、これだけだとなんとも味気ないツリー図なので、次回はこのツリー図にいろいろと手を加えてみることにします。

               

               

              JUGEMテーマ:JavaScript

              このエントリーをはてなブックマークに追加
              | D3.js | 21:56 | comments(0) | - | - |
              D3.js v4でTree図を作成する (1)
              0

                D3.js が Ver.4 にアップデートしていろいろなところが変わりましたが、Treeレイアウトの作成方法もその1つです。この変更によって、残念ながら Ver.3 でのTreeレイアウトのプログラムは Ver.4 では動作しなくなってしまいました。

                 

                さらに困ったことに、Ver.4 でTreeレイアウトを作成しようとしても、簡単なサンプルがなかなか見当たらないのが現状です (特に日本語では) 。例えばインターネットを検索すると「d3.js version4でシンプルなTree図を作成」という記事も見つかるのですが、ノードどうしを線でつなぐ部分の関数が複雑だったり、コードの説明がほとんどなかったりして、おそらくD3.jsを学びはじめた人にとってはハードルが高そうです。

                 

                そこで、D3.js Ver.4 を使って、ごくごくシンプルなTree図を作成するサンプルプログラムを作ってみました。コードの中にも簡単な説明を入れましたが、それぞれの関数について次回紹介したいと思います。

                 

                 

                JUGEMテーマ:JavaScript

                このエントリーをはてなブックマークに追加
                | D3.js | 21:06 | comments(0) | - | - |
                【D3.js】Chordダイアグラムの作成
                0
                  JUGEMテーマ:JavaScript

                  D3.js には Chordダイアグラム (Chord diagram) を作成するための d3.layout.chord() というメソッドがあるのですが、そもそも Chordダイアグラム自体に馴染みがないというかたが多いのではないでしょうか。そこで、Chordダイアグラムについて簡単に説明したうえで、D3.jsによる作成方法を紹介します。

                  Chordダイアグラムは、グループ間の関連性を図示するときによく使われます。例えば、ある集団を「男性/女性」という2つのグループに分けた場合と、「犬好き/猫好き」という2つのグループに分けた場合について考えてみましょう。現実の世のなかには「犬も猫も好き」な人もいれば、「どちらもキライ!」という人もいますが、ここでは簡単のために、みんな犬か猫のどちらか一方が好きだとします。さて、こうするとこの集団のメンバーは、4 種類 (「男性で犬好き」「男性で猫好き」「女性で犬好き」「女性で猫好き」) に分類できることになりますね。具体的に考えるために、この集団は男性12人と女性4人から成り、それぞれ以下のように犬好きと猫好きに分かれているとします。
                   
                  犬好き 猫好き 合計
                  男性 7 5 12
                  女性 1 3 4
                  合計 8 8

                  さて、このようなデータを可視化するときに、Chord ダイアグラムが役立ちます。Chord ダイアグラムを使うと、こういうグループ間の関連性を一目で把握できるようになります。上の表をもとに、実際に作成したChordダイアグラムが下図になります。



                  ここでは図の右側、青色とオレンジ色の円弧が「男性/女性」のグループを表しています。また、左側の緑色と赤色の円弧が「犬好き/猫好き」のグループを表しています。そしてそれぞれのグループ間を橋渡ししている赤と緑の帯が、それぞれの関連性 (「男性で犬好き」など) を表現しています。このように、Chordダイアグラムを使うと、先の表にあるような2種類のグループ間の関連や連携が、見事に1枚の図に表現されていることが分かります。

                  サンプルページ

                  // D3.jsによる基本10色
                  var color = d3.scale.category10();
                  
                  // Chord diagramの元となるデータ
                  var matrix = [
                      [0, 0, 7, 5],
                      [0, 0, 1, 3],
                      [7, 1, 0, 0],
                      [5, 3, 0, 0]
                  ];
                  
                  // D3.layout.chord()メソッドを使ってChordダイアグラムを作成
                  var chord = d3.layout.chord()
                      .padding(.05)
                      .matrix(matrix);
                  
                  var width = 600,
                      height = 500,
                      innerRadius = Math.min(width, height) * 0.4,
                      outerRadius = innerRadius * 1.1;
                  
                  var svg = d3.select("#MyGraph")
                      .attr("width", width)
                      .attr("height", height)
                      .append("g")
                      .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
                  
                  // Chord diagramを描画
                  svg.append("g").selectAll("path")
                      .data(chord.groups)
                      .enter()
                      .append("path")
                      .style("fill", function(d){ return color(d.index); })
                      .style("stroke", function(d) { return color(d.index); })
                      .style("opacity", 0.8)
                      .attr("d", d3.svg.arc().innerRadius(innerRadius).outerRadius(outerRadius))
                      .on("mouseover", fade(.1)) // マウスをのせた部分を強調表示
                      .on("mouseout", fade(0.8)); // マウスを移動すると描画を元に戻す
                  
                  // 目盛りを描画
                  var ticks = svg.append("g").selectAll("g")
                      .data(chord.groups)
                      .enter()
                      .append("g")
                      .selectAll("g")
                      .data(groupTicks)
                      .enter()
                      .append("g")
                      .attr("transform", function(d) {
                        return "rotate(" + (d.angle * 180 / Math.PI - 90) + ")"
                            + "translate(" + outerRadius + ",0)";
                      });
                  
                  ticks.append("line")
                      .attr("x1", 1)
                      .attr("y1", 0)
                      .attr("x2", 5)
                      .attr("y2", 0)
                      .style("stroke", "#000");
                  
                  // 目盛りの数字を描画
                  ticks.append("text")
                      .attr("x", 8)
                      .attr("dy", ".35em")
                      .attr("transform", function(d) {
                          return d.angle > Math.PI ? "rotate(180)translate(-16)" : null;
                      })
                      .style("text-anchor", function(d) {
                          return d.angle > Math.PI ? "end" : null;
                      })
                      .text(function(d) { return d.label; });
                  
                  svg.append("g")
                      .attr("class", "chord")
                      .selectAll("path")
                      .data(chord.chords)
                      .enter()
                      .append("path")
                      .attr("d", d3.svg.chord().radius(innerRadius))
                      .style("fill", function(d) { return color(d.target.index); })
                      .style("opacity", 0.9);
                  
                  // 描画する目盛りの数字と角度を返す関数
                  function groupTicks(d) {
                    var k = (d.endAngle - d.startAngle) / d.value;
                    return d3.range(0, d.value).map(function(v, i) {
                      return {
                        angle: v * k + d.startAngle,
                        label: i % 5 ? null : v
                      };
                    });
                  }
                  
                  // mouseover/mouseout 時のSVG要素の透明度を決定する関数
                  function fade(opacity) {
                    return function(g, i) {
                      svg.selectAll(".chord path")
                          .filter(function(d) {
                              return d.source.index != i && d.target.index != i;
                          })
                          .transition()
                          .style("opacity", opacity);
                    };
                  }
                  



                   
                  このエントリーをはてなブックマークに追加
                  | D3.js | 23:07 | comments(0) | - | - |
                  データの変化をSVG要素に反映する
                  0
                    JUGEMテーマ:JavaScript

                    データの要素数や要素の順序が変化する様子を、アニメーションで可視化したい場合があります。今回はそんな場面で役に立つ、セレクションの仕組みについて見ていきます。

                    d3.selectAll については、これまでにもしばしば使ってきました。しかしこれまでは、データの要素数や順序が変化しない、いわば「静的」なデータの可視化方法の説明にとどまっていました。データの要素数や要素の順序の変化に応じてDOM要素やSVG要素をダイナミックに操作するためには、D3.js では Enter, Update, Exit という3つの操作を行います。これらの操作は、最初はなかなか理解しにくいのですが、いったん動きを理解してしまうと、D3.js プログラミングの見通しが一気に広がります。

                    Enter, Update, Exit という3つの操作によって、それぞれ以下の動作を実現します。
                    • Enter:与えられたデータに対応するDOM/SVG要素がないときに、DOM/SVG要素を追加する
                    • Update:データに応じてDOM/SVG要素を更新する
                    • Exit:DOM/SVG要素に対応するデータがないときに、該当するDOM/SVG要素を削除する
                    今回のサンプルプログラムでは、10〜60までの6個の数値を要素として持つ配列 (14行目の dataSet) をもとに、(3つの小円という有名なチュートリアル記事に敬意を表して) まず 6 個の円 (circle要素) を表示しますその後、3秒ごとにこの配列の要素の順番をシャッフルしたうえで要素数もランダムに変化させた配列 (60行目の data) を生成します。そのうえで、配列の要素数が変化していれば表示する円の数も変化させ、また各要素の順番が変化していればそれに応じて円の表示位置も変化させるようにします。

                    サンプルページ



                    プログラムは以下のとおりです。

                    まず、18〜21行目で配列の各要素と circle 要素を紐づけています。紐づけには、data() メソッドのコールバック関数を定義することで、配列の各要素の値を使うよう指定しています。このブログにこれまで掲載したプログラムでは、data() メソッドのコールバック関数は特に指定していませんでした。それでも問題なく動いていたのは、data() メソッドはコールバック関数が指定されないと自動的に配列のインデックスによって各要素と紐づけるよう実装されているからなのです。そのうえで、23行目以降では Update, Enter, Exit 処理を順に実施しています。こうすることで、データの要素数や要素の順序の変化を、DOM要素やSVG要素に反映することができます。

                    // 周囲にマージンを確保する
                    var margin = {top: 40, right: 40, bottom: 80, left: 80};
                    var width = 800 - margin.left - margin.right;
                    var height = 500 - margin.top - margin.bottom;
                    
                    // SVGの表示領域を生成
                    var svg = d3.select("#MyGraph")
                      .attr("width", width + margin.left + margin.right)
                      .attr("height", height + margin.top + margin.bottom)
                      .append("g")
                      .attr("transform", "translate(" + margin.left + ", " + height/2 + ")");
                    
                    // データセットの初期値
                    var dataSet = [10, 20, 30, 40, 50, 60];
                    
                    function update(data){
                      // データセットをSVG要素に紐づける
                      var circle = svg.selectAll("circle")
                        .data(data, function(d){
                          return d;
                        });
                      
                      // Update: SVG要素を更新する
                      circle.attr("class", "update")
                        .transition()
                        .duration(1000)
                        .attr("cx", function(d,i){
                          return 120*i;
                        });
                      
                      // Enter: SVG要素を表示する
                      circle.enter()
                        .append("circle")
                        .attr("class", "enter")
                        .attr("cy", 0)
                        .attr("cx", function(d, i){
                          return 120*i;
                        })
                        .style("fill", "royalblue")
                        .attr("r", 0)
                        .transition()
                        .duration(1000)
                        .attr("r", function(d, i){
                          return d;
                        });
                            
                      // Exit: 不要になったSVG要素を削除する
                      circle.exit()
                        .transition()
                        .duration(1000)
                        .attr("r", 0)
                        .remove();
                    }
                    
                    // 初期状態
                    update(dataSet);
                    
                    // 3秒ごとにdataSetの要素をシャッフルし要素数も変化させて表示を更新する
                    setInterval(function() {
                      var data = d3.shuffle(dataSet).slice(0, Math.floor(Math.random() * 7));
                      console.log(data);
                      update(data);
                    }, 3000);
                    

                     
                    このエントリーをはてなブックマークに追加
                    | D3.js | 22:35 | comments(0) | - | - |