プライマリ・コンテンツに移動
Oracle® Fusion Middleware Oracle ADF FacesによるWebユーザー・インタフェースの開発
12c (12.2.1.2.0)
E82917-02
目次へ移動
目次

前
次

F コード・サンプル

この付録では、このガイド全体の項で参照されている、完全な長さのコード・サンプルを示します。

この付録の内容は次のとおりです。

F.1 第4章「ADF Facesのクライアント・サイド・アーキテクチャの使用」のサンプル

JavaScriptパーティションをクライアントに配信する際には、adf-js-partitions.xmlファイルが関与します。パーティションでJavaScript機能をグループ化します。

次に、ADF Facesアーキテクチャを使用する場合のコード例を示します。

F.1.1 adf-js-partitions.xmlファイル

ADF Facesのデフォルトのadf-js-partitions.xmlファイルには、独自のパーティション・ファイルを作成することでオーバーライドできるパーティションがあります。詳細は、「JavaScriptライブラリのパーティション化」を参照してください。次の例に、デフォルトのADF Facesのadf-js-partitions.xmlファイルを示します。

<?xml version="1.0" encoding="utf-8"?>
 
<partitions xmlns="http://xmlns.oracle.com/adf/faces/partition">
 
  <partition>
    <partition-name>boot</partition-name>
    <feature>AdfBootstrap</feature>
  </partition>
 
  <partition>
    <partition-name>core</partition-name>
 
    <feature>AdfCore</feature>
 
    <!-- Behavioral component super classes -->
    <feature>AdfUIChoose</feature>
    <feature>AdfUICollection</feature>
    <feature>AdfUICommand</feature>
    <feature>AdfUIDialog</feature>
    <feature>AdfUIDocument</feature>
    <feature>AdfUIEditableValue</feature>
    <feature>AdfUIForm</feature>
    <feature>AdfUIGo</feature>
    <feature>AdfUIInput</feature>
    <feature>AdfUIObject</feature>
    <feature>AdfUIOutput</feature>
    <feature>AdfUIPanel</feature>
    <feature>AdfUIPopup</feature>
    <feature>AdfUISelectBoolean</feature>
    <feature>AdfUISelectInput</feature>
    <feature>AdfUISelectOne</feature>
    <feature>AdfUISelectMany</feature>
    <feature>AdfUIShowDetail</feature>
    <feature>AdfUISubform</feature>
    <feature>AdfUIValue</feature>
 
    <!-- These are all so common that we group them with core -->
    <feature>AdfRichDocument</feature>
    <feature>AdfRichForm</feature>
    <feature>AdfRichPopup</feature>
    <feature>AdfRichSubform</feature>
    <feature>AdfRichCommandButton</feature>
    <feature>AdfRichCommandLink</feature>
 
    <!-- 
      Dialog is currently on every page for messaging.  No use
      in putting these in a separate partition.
    -->
    <feature>AdfRichPanelWindow</feature>
    <feature>AdfRichDialog</feature>
 
    <!-- af:showPopupBehavior is so small/common, belongs in core -->
    <feature>AdfShowPopupBehavior</feature>
  </partition>
 
  <partition>
    <partition-name>accordion</partition-name>
    <feature>AdfRichPanelAccordion</feature>
  </partition>
 
  <partition>
    <partition-name>border</partition-name>
    <feature>AdfRichPanelBorderLayout</feature>
  </partition>
 
  <partition>
    <partition-name>box</partition-name>
    <feature>AdfRichPanelBox</feature>
  </partition>
  
  <partition>
    <partition-name>calendar</partition-name>
    <feature>AdfUICalendar</feature>
    <feature>AdfRichCalendar</feature>
    <feature>AdfCalendarDragSource</feature>
    <feature>AdfCalendarDropTarget</feature>
  </partition>
  
  <partition>
    <partition-name>collection</partition-name>
    <feature>AdfUIDecorateCollection</feature>
    <feature>AdfRichPanelCollection</feature>
  </partition>
 
  <partition>
    <partition-name>color</partition-name>
    <feature>AdfRichChooseColor</feature>
    <feature>AdfRichInputColor</feature>
  </partition>
 
  <partition>
    <partition-name>date</partition-name>
    <feature>AdfRichChooseDate</feature>
    <feature>AdfRichInputDate</feature>
  </partition>
 
  <partition>
    <partition-name>declarativeComponent</partition-name>
    <feature>AdfUIInclude</feature>
    <feature>AdfUIDeclarativeComponent</feature>
    <feature>AdfRichDeclarativeComponent</feature>
  </partition>
 
  <partition>
    <partition-name>detail</partition-name>
    <feature>AdfRichShowDetail</feature>
  </partition>
 
  <partition>
    <partition-name>dnd</partition-name>
    <feature>AdfDragAndDrop</feature>
    <feature>AdfCollectionDragSource</feature>
    <feature>AdfStampedDropTarget</feature>
    <feature>AdfCollectionDropTarget</feature>
    <feature>AdfAttributeDragSource</feature>
    <feature>AdfAttributeDropTarget</feature>
    <feature>AdfComponentDragSource</feature>
    <feature>AdfDropTarget</feature>    
  </partition>
  
  <partition>
    <partition-name>detailitem</partition-name>
    <feature>AdfRichShowDetailItem</feature>
  </partition>
 
  <partition>
    <partition-name>file</partition-name>
    <feature>AdfRichInputFile</feature>
  </partition>
 
  <partition>
    <partition-name>form</partition-name>
    <feature>AdfRichPanelFormLayout</feature>
    <feature>AdfRichPanelLabelAndMessage</feature>
  </partition>
 
  <partition>
    <partition-name>format</partition-name>
    <feature>AdfRichOutputFormatted</feature>
  </partition>
 
  <partition>
    <partition-name>frame</partition-name>
    <feature>AdfRichInlineFrame</feature>
  </partition>
 
  <partition>
    <partition-name>header</partition-name>
    <feature>AdfRichPanelHeader</feature>
    <feature>AdfRichShowDetailHeader</feature>
  </partition>
 
  <partition>
    <partition-name>imagelink</partition-name>
    <feature>AdfRichCommandImageLink</feature>
  </partition>
 
  <partition>
    <partition-name>iedit</partition-name>
    <feature>AdfInlineEditing</feature>
  </partition>
 
  <partition>
    <partition-name>input</partition-name>
    <feature>AdfRichInputText</feature>
    <feature>AdfInsertTextBehavior</feature>
  </partition>
 
  <partition>
    <partition-name>label</partition-name>
    <feature>AdfRichOutputLabel</feature>
  </partition>
 
  <partition>
    <partition-name>list</partition-name>
    <feature>AdfRichPanelList</feature>
  </partition>
 
  <partition>
    <partition-name>lov</partition-name>
    <feature>AdfUIInputPopup</feature>
    <feature>AdfRichInputComboboxListOfValues</feature>
    <feature>AdfRichInputListOfValues</feature>
  </partition>
 
  <partition>
    <partition-name>media</partition-name>
    <feature>AdfRichMedia</feature>
  </partition>
 
  <partition>
    <partition-name>message</partition-name>
    <feature>AdfUIMessage</feature>
    <feature>AdfUIMessages</feature>
    <feature>AdfRichMessage</feature>
    <feature>AdfRichMessages</feature>
  </partition>
 
  <partition>
    <partition-name>menu</partition-name>
    <feature>AdfRichCommandMenuItem</feature>
    <feature>AdfRichGoMenuItem</feature>
    <feature>AdfRichMenuBar</feature>
    <feature>AdfRichMenu</feature>
  </partition>
 
  <partition>
    <partition-name>nav</partition-name>
    <feature>AdfUINavigationPath</feature>
    <feature>AdfUINavigationLevel</feature>
    <feature>AdfRichBreadCrumbs</feature>
    <feature>AdfRichCommandNavigationItem</feature>
    <feature>AdfRichNavigationPane</feature>
  </partition>
 
  <partition>
    <partition-name>note</partition-name>
    <feature>AdfRichNoteWindow</feature>
  </partition>
 
  <partition>
    <partition-name>poll</partition-name>
    <feature>AdfUIPoll</feature>
    <feature>AdfRichPoll</feature>
  </partition>
 
  <partition>
    <partition-name>progress</partition-name>
    <feature>AdfUIProgress</feature>
    <feature>AdfRichProgressIndicator</feature>
  </partition>
 
  <partition>
    <partition-name>print</partition-name>
    <feature>AdfShowPrintablePageBehavior</feature>
  </partition>
 
  <partition>
    <partition-name>scrollComponentIntoView</partition-name>
    <feature>AdfScrollComponentIntoViewBehavior</feature>
  </partition>
 
  <partition>
    <partition-name>query</partition-name>
    <feature>AdfUIQuery</feature>
    <feature>AdfRichQuery</feature>
    <feature>AdfRichQuickQuery</feature>
  </partition>
 
  <partition>
    <partition-name>region</partition-name>
    <feature>AdfUIRegion</feature>
    <feature>AdfRichRegion</feature>
  </partition>
 
  <partition>
    <partition-name>reset</partition-name>
    <feature>AdfUIReset</feature>
    <feature>AdfRichResetButton</feature>
  </partition>
 
  <partition>
    <partition-name>rte</partition-name>
    <feature>AdfRichTextEditor</feature>
    <feature>AdfRichTextEditorInsertBehavior</feature>
  </partition>
 
  <partition>
    <partition-name>select</partition-name>
 
    <feature>AdfRichSelectBooleanCheckbox</feature>
    <feature>AdfRichSelectBooleanRadio</feature>
    <feature>AdfRichSelectManyCheckbox</feature>
    <feature>AdfRichSelectOneRadio</feature>
  </partition>
 
  <partition>
    <partition-name>selectmanychoice</partition-name>
    <feature>AdfRichSelectManyChoice</feature>
  </partition>
 
  <partition>
    <partition-name>selectmanylistbox</partition-name>
    <feature>AdfRichSelectManyListbox</feature>
  </partition>
 
  <partition>
    <partition-name>selectonechoice</partition-name>
    <feature>AdfRichSelectOneChoice</feature>
  </partition>
 
  <partition>
    <partition-name>selectonelistbox</partition-name>
    <feature>AdfRichSelectOneListbox</feature>
  </partition>
 
  <partition>
    <partition-name>shuttle</partition-name>
    <feature>AdfUISelectOrder</feature>
    <feature>AdfRichSelectManyShuttle</feature>
    <feature>AdfRichSelectOrderShuttle</feature>
  </partition>
 
  <partition>
    <partition-name>slide</partition-name>
    <feature>AdfRichInputNumberSlider</feature>
    <feature>AdfRichInputRangeSlider</feature>
  </partition>
 
  <partition>
    <partition-name>spin</partition-name>
    <feature>AdfRichInputNumberSpinbox</feature>
  </partition>
 
  <partition>
    <partition-name>status</partition-name>
    <feature>AdfRichStatusIndicator</feature>
  </partition>
 
  <partition>
    <partition-name>stretch</partition-name>
    <feature>AdfRichDecorativeBox</feature>
    <feature>AdfRichPanelSplitter</feature>
    <feature>AdfRichPanelStretchLayout</feature>
    <feature>AdfRichPanelDashboard</feature>
    <feature>AdfPanelDashboardBehavior</feature>
    <feature>AdfDashboardDropTarget</feature>
  </partition>
 
  <partition>
    <partition-name>tabbed</partition-name>
    <feature>AdfUIShowOne</feature>
    <feature>AdfRichPanelTabbed</feature>
  </partition>
 
  <partition>
    <partition-name>table</partition-name>
    <feature>AdfUIIterator</feature>
    <feature>AdfUITable</feature>
    <feature>AdfUITable2</feature>
    <feature>AdfUIColumn</feature>
    <feature>AdfRichColumn</feature>
    <feature>AdfRichTable</feature>
  </partition>
 
  <partition>
    <partition-name>toolbar</partition-name>
    <feature>AdfRichCommandToolbarButton</feature>
    <feature>AdfRichToolbar</feature>
  </partition>
 
  <partition>
    <partition-name>toolbox</partition-name>
    <feature>AdfRichToolbox</feature>
  </partition>
 
  <partition>
    <partition-name>train</partition-name>
    <feature>AdfUIProcess</feature>
    <feature>AdfRichCommandTrainStop</feature>
    <feature>AdfRichTrainButtonBar</feature>
    <feature>AdfRichTrain</feature>
  </partition>
 
  <partition>
    <partition-name>tree</partition-name>
    <feature>AdfUITree</feature>
    <feature>AdfUITreeTable</feature>
    <feature>AdfRichTree</feature>
    <feature>AdfRichTreeTable</feature>
  </partition>
 
  <!--
    Some components which typically do have client-side representation,
    but small enough that we might as well download in a single partition
    in the event that any of these are needed.
  -->
  <partition>
    <partition-name>uncommon</partition-name>
    <feature>AdfRichGoButton</feature>
    <feature>AdfRichIcon</feature>
    <feature>AdfRichImage</feature>
    <feature>AdfRichOutputText</feature>
    <feature>AdfRichPanelGroupLayout</feature>
    <feature>AdfRichSeparator</feature>
    <feature>AdfRichSpacer</feature>
    <feature>AdfRichGoLink</feature>
  </partition>
 
  <partition>
    <partition-name>eum</partition-name>
    <feature>AdfEndUserMonitoring</feature>
  </partition>
 
  <partition>
    <partition-name>ads</partition-name>
    <feature>AdfActiveDataService</feature>
  </partition>
 
  <partition>
    <partition-name>automation</partition-name>
    <feature>AdfAutomationTest</feature>
  </partition>
 
</partitions>

F.2 第29章「マップ・コンポーネントの使用方法」のサンプル

mapProvider機能を使用すると、テーマ・マップを構成できます。oracle.adf.view.faces.bi.component.thematicMap.mapProvider.MapProviderorace.adf.view.faces.bi.component.thematicMap.mapProvider.LayerAreaなどのMapProvider APIを使用すると、カスタム・ベースマップを構成し、すべて同じ機能を持つ組込みのベースマップとして扱うことができます。

次は、DVTマップ・コンポーネントを作成するサンプル・コードです。

F.2.1 テーマ・マップのカスタム・ベース・マップのサンプル・コード

テーマ・マップ用のカスタム・ベース・マップを作成する場合は、oracle.adf.view.faces.bi.component.thematicMap.mapProvider.MapProviderを拡張します。次に示すのは、カナダのカスタム・ベース・マップ例のためのmapProviderクラスの実装です。

package oracle.adfdemo.view.feature.rich.thematicMap;
 
import java.awt.Rectangle;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
 
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
 
import java.net.URL;
 
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.zip.ZipInputStream;
 
import javax.faces.context.FacesContext;
 
import oracle.adf.share.logging.ADFLogger;
import oracle.adf.view.faces.bi.component.thematicMap.mapProvider.LayerArea;
import oracle.adf.view.faces.bi.component.thematicMap.mapProvider.MapProvider;
import oracle.adf.view.faces.bi.component.thematicMap.mapProvider.utils.MapProviderUtils;
 
/**
 * Sample MapProvider instance that translates GeoJSON data from a zip file to create a custom Canada basemap.
 */
public class DemoMapProvider extends MapProvider {
  private static String zipFile = "/resources/thematicMap/elocation.zip";
  private static final ADFLogger _logger = ADFLogger.createADFLogger(DemoMapProvider.class);
  private List<String> hierarchy;
 
  public DemoMapProvider() {
    hierarchy = new ArrayList<String>();
    hierarchy.add("states");
  }
 
  @Override
  public Collection<LayerArea> getLayerAreas(String layer, Locale locale) {
    String text = getGeoJsonString();
    Map geoJSON = null;
    try {
      geoJSON = (Map) DemoMapProvider.parseJsonString(text);
    } catch (IOException e) {
      _logger.severe("Could not parse geometries.", e);
    }
    return DemoMapProvider.createLayerAreas(geoJSON);
  }
 
  @Override
  public Collection<LayerArea> getChildAreas(String layer, Locale locale, Collection<String> areas) {
    return new ArrayList<LayerArea>();
  }
 
  @Override
  public double getMaxZoomFactor() {
    return 5.0;
  }
 
  @Override
  public List<String> getHierarchy() {
    return hierarchy;
  }
 
  /**
   * Loads the geographic geometries stored in local file and returns as string
   * @return
   */
  private static String getGeoJsonString() {
    StringBuilder sb = new StringBuilder();
    BufferedReader br = null;
    ZipInputStream is = null;
    try {
      URL zipUrl = FacesContext.getCurrentInstance().getExternalContext().getResource(zipFile);
      is = new ZipInputStream(zipUrl.openStream());
      is.getNextEntry();
      br = new BufferedReader(new InputStreamReader(is, "UTF8"));
      String aux = br.readLine();
      while (aux != null) {
        sb.append(aux);
        aux = br.readLine();
      }
    } catch (Exception e) {
      _logger.severe("Could not load geometries from the file " + zipFile, e);
    } finally {
      if (br != null) {
        try {
          is.close();
          br.close();
        } catch (IOException e) { 
          // ignore
        }
      }
    }
    return sb.toString();
  }
 
  /**
   * Parses a JSON string and converts it to the correct Java object
   * @param str The JSON string to parse
   * @return
   * @throws IOException
   */
  private static Object parseJsonString(String str) throws IOException {
    if (str == null)
      return null;
 
    str = str.trim();
 
    char firstChar = str.charAt(0);
    if (firstChar == '[' || firstChar == '{') {
      // Array or Map
      // Count the number of open/close arrays or objects
      int numOpen = 0;
 
      // Quote handling: Count the number of open single/double quotes, except when there is an
      // open one already.  This handles nested single quotes in double quotes, and vice versa.
      int numSingleQuotes = 0;
      int numDoubleQuotes = 0;
 
      // Iterate through and split by pieces
      int prevIndex = 1;
      List<String> parts = new ArrayList<String>();
      for (int i = 1; i < str.length() - 1; i++) {
        char iChar = str.charAt(i);
        if (iChar == '[' || iChar == '{')
          numOpen++;
        else if (iChar == ']' || iChar == '}')
          numOpen--;
        else if (iChar == '\'' && numDoubleQuotes % 2 == 0)
          numSingleQuotes++;
        else if (iChar == '"' && numSingleQuotes % 2 == 0)
          numDoubleQuotes++;
 
        // If split index, store the substring
        if (numOpen == 0 && (numSingleQuotes % 2 == 0 && numDoubleQuotes % 2 == 0) && iChar == ',') {
          parts.add(str.substring(prevIndex, i));
          prevIndex = i + 1;
        }
      }
 
      // Grab the last part if present
      if (prevIndex < str.length() - 1) {
        parts.add(str.substring(prevIndex, str.length() - 1));
      }
 
      // Decode the parts into the result
      if (firstChar == '[') {
        List ret = new ArrayList();
        for (int arrayIndex = 0; arrayIndex < parts.size(); arrayIndex++)
          ret.add(parseJsonString(parts.get(arrayIndex)));
        return ret;
      } else if (firstChar == '{') {
        Map ret = new HashMap();
        for (String part : parts) {
          part = part.trim();
          int colonIndex = part.indexOf(':');
          String mapKey = part.substring(0, colonIndex);
          mapKey = mapKey.substring(1, mapKey.length() - 1); // 1 to -1 to avoid the quotes
          Object mapValue = parseJsonString(part.substring(colonIndex + 1, part.length()));
          ret.put(mapKey, mapValue);
        }
        return ret;
      }
      return null;
    } else if (firstChar == '"') // String
      return str.substring(1, str.length() - 1);
    else if ("true".equals(str))
      return true;
    else if ("false".equals(str))
      return false;
    else
      return Double.parseDouble(str);
  }
 
  /**
   * Converts a GeoJSON object to a list of LayerArea objects
   * @param geoJSON The GeoJSON object containing this basemap layer's area geometry data
   * @return
   */
  private static List<LayerArea> createLayerAreas(Map geoJSON) {
    List territories = Arrays.asList(MapProviderBean.territoryNames);
    HashMap<String, DemoLayerArea> areaMap = new HashMap<String, DemoLayerArea>();
    if (geoJSON != null) {
      List features = (List) geoJSON.get("features");
      int numFeatures = features.size();
      for (int j = 0; j < numFeatures; j++) {
        Map feature = (Map) features.get(j);
        Map properties = (Map) feature.get("properties");
        String label = (String) properties.get("POLYGON_NAME");
        // We just want to render canada
        if (!territories.contains(label))
          continue;
 
        Map geometry = (Map) feature.get("geometry");
 
        Rectangle2D labelBox = null;
        List<Double> labelBoxList = (List<Double>) feature.get("label_box");
        if (labelBoxList != null) {
          int minX = (int) (labelBoxList.get(0) / 2000);
          int minY = (int) (labelBoxList.get(1) / 2000);
          int maxX = (int) (labelBoxList.get(2) / 2000);
          int maxY = (int) (labelBoxList.get(3) / 2000);
          labelBox = new Rectangle(minX, -minY, maxX - minX, maxY - minY);
        }
        DemoLayerArea area = areaMap.get(label);
        if (area != null)
          area.setPath(area.getPath() + DemoMapProvider.simplifyGeometries(geometry));
        else
          areaMap.put(label,
                      new DemoLayerArea(label, null, label, labelBox, DemoMapProvider.simplifyGeometries(geometry),
                                        null));
      }
    }
 
    List<LayerArea> layerAreas = new ArrayList<LayerArea>();
    for (Map.Entry<String, DemoLayerArea> entry : areaMap.entrySet())
      layerAreas.add(entry.getValue());
    return layerAreas;
  }
 
  /**
   * Converts and simplifies area geometries to relative path commands
   * @param geometry The map containing an area's coordinates
   * @return
   */
  private static String simplifyGeometries(Map geometry) {
    StringBuilder sb = new StringBuilder();
    String type = (String) geometry.get("type");
    List coords = (List) geometry.get("coordinates");
    int len = coords.size();
    if ("Polygon".equals(type)) {
      for (int i = 0; i < len; i++)
        sb.append(DemoMapProvider.simplifyGeometriesHelper((List) coords.get(i)));
    } else if ("MultiPolygon".equals(type)) {
      for (int i = 0; i < len; i++) {
        List nestCoords = (List) coords.get(i);
        int nestCoordsLen = nestCoords.size();
        for (int j = 0; j < nestCoordsLen; j++)
          sb.append(DemoMapProvider.simplifyGeometriesHelper((List) coords.get(j)));
      }
    }
    return sb.toString();
  }
 
  /**
   * Helper method for parsing a GeoJSON geometry object
   * @param coords The list of coordinates to simplify and convert to a relative path command
   * @return
   */
  private static String simplifyGeometriesHelper(List coords) {
    List<Point2D> points = new ArrayList<Point2D>();
    int len = coords.size();
    // Convert coordinates to Point2D objects so we can use MapProviderUtils to simplify area gemoetries
    // Also reduce data precision by dividing by 2000
    for (int i = 0; i < len; i += 2)
      points.add(new Point2D.Double(Math.floor((Double) coords.get(i) / 2000),
                                    -Math.floor((Double) coords.get(i + 1) / 2000)));
    return MapProviderUtils.convertToPath(points);
  }
}

F.2.2 テーマ・マップのカスタム・ベース・マップのエリア・レイヤーのサンプル・コード

テーマ・マップ用のカスタム・ベース・マップを作成する場合は、oracle.adf.view.faces.bi.component.thematicMap.mapProvider.LayerAreaを拡張し、テーマ・マップ・コンポーネントがdvt:areaLayerをレンダリングするために必要なデータを取得します。次に示すのは、カナダのカスタム・ベース・マップ例のためのlayerAreaクラスの実装です。

package oracle.adfdemo.view.feature.rich.thematicMap;
 
import java.awt.geom.Rectangle2D;
 
import oracle.adf.view.faces.bi.component.thematicMap.mapProvider.LayerArea;
 
public class DemoLayerArea extends LayerArea {
 
  private String id;
  private String shortLabel;
  private String longLabel;
  private Rectangle2D labelBox;
  private String path;
  private String parent;
  
  public DemoLayerArea(String id, String shortLabel, String longLabel, Rectangle2D labelBox, String path, String parent) {
    this.id = id;
    this.shortLabel = shortLabel;
    this.longLabel = longLabel;
    this.labelBox = labelBox;
    this.path = path;
    this.parent = parent;
  }
 
  @Override
  public String getId() {
    return id;
  }
 
  @Override
  public String getShortLabel() {
    return shortLabel;
  }
 
  @Override
  public String getLongLabel() {
    return longLabel;
  }
 
  @Override
  public Rectangle2D getLabelBox() {
    return labelBox;
  }
  
  @Override
  public String getPath() {
    return path;
  }
  
  public void setPath(String path) {
    this.path = path;
  }
 
  @Override
  public String getParent() {
    return parent;
  }
}

F.3 第31章「ツリーマップおよびサンバースト・コンポーネントの使用」のサンプル

UI-first開発を使用してツリーマップあるいはサンバーストにデータを追加するには、ツリー・モデルを作成してJDeveloperにおけるクラス、Beanあるいはメソッドを参照する、クラス、マネージドBean、およびメソッドを作成します。

次は、ツリーマップとサンバースト・コンポーネントを作成するサンプル・コードです。

F.3.1 ツリーマップおよびサンバースト人口調査データ例のサンプル・コード

ツリーマップあるいはサンバーストをUI優先開発を使用して作成するには、クラスとマネージドBeanを使用してツリー・ノードとツリー・モデルを定義し、ツリーにデータを移入し、かつツリーマップあるいはサンバースト構成の必要に応じ、追加のメソッドを追加します。

次の例に、国勢調査のデータ例でのツリー・ノードを定義するコードのサンプルを示します。ラベル、サイズ、および色についての必要な設定は、パラメータとしてツリー・ノードに渡されます。

import java.awt.Color;
import java.util.ArrayList;
import java.util.List;
 
public class TreeNode {
  private final String m_text;
  private final Number m_size;
  private final Color m_color;
  private final List<TreeNode> m_children = new ArrayList<TreeNode>();
  
  public TreeNode(String text, Number size, Color color) {
    m_text = text;
    m_size = size;
    m_color = color;
  }
  public String getText() {
    return m_text;
  }
  public Number getSize() {
    return m_size;
  }
  public Color getColor() {
    return m_color;
  }
  public void addChild(TreeNode child) {
    m_children.add(child);
  }
  public void addChildren(List<TreeNode> children) {
    m_children.addAll(children);
  }
  public List<TreeNode> getChildren() {
    return m_children;
  }
  @Override
  public String toString() {
    return m_text + ": " + m_color + " " + Math.round(m_size.doubleValue());
  }
}

UI優先開発におけるツリーマップあるいはサンバーストにデータを供給するには、クラスあるいはマネージドBeanをユーザーのアプリケーションに追加します。アプリケーションは前述の例のツリー・ノードを展開し、そこにデータを移入します。ツリー・モデルを設定するクラスは、org.apache.myfaces.trinidad.model.TreeModelクラスの実装にする必要があります。ツリー・モデルを定義したら、org.apache.myfaces.trinidad.model.ChildPropertyTreeModelを実装するメソッドを作成してツリー・モデルを完成させます。

国勢データの例において、ルートと子ノードの構造を設定し、子レベルにデータを移入し、ノードの色およびサイズを定義するサンプル・クラスを次に示します。

import java.awt.Color;
 
import java.util.ArrayList;
import java.util.List;
 
import org.apache.myfaces.trinidad.model.ChildPropertyTreeModel;
import org.apache.myfaces.trinidad.model.TreeModel;
 
 
public class CensusData {
 
  public static TreeModel getUnitedStatesData() {
    return getModel(ROOT);
  }
 
  public static TreeModel getRegionWestData() {
    return getModel(REGION_W);
  }
 
  public static TreeModel getRegionNortheastData() {
    return getModel(REGION_NE);
  }
 
  public static TreeModel getRegionMidwestData() {
    return getModel(REGION_MW);
  }
 
  public static TreeModel getRegionSouthData() {
    return getModel(REGION_S);
  }
 
  public static TreeModel getDivisionPacificData() {
    return getModel(DIVISION_P);
  }
 
  private static TreeModel getModel(DataItem rootItem) {
    TreeNode root = getTreeNode(rootItem);
    return new ChildPropertyTreeModel(root, "children");
  }
 
  private static TreeNode getTreeNode(DataItem dataItem)
  {
    // Create the node itself
    TreeNode node = new CensusTreeNode(dataItem.getName(),
                        dataItem.getPopulation(),
                        getColor(dataItem.getIncome(), MIN_INCOME, MAX_INCOME),
                        dataItem.getIncome());
 
    // Create its children
    List<TreeNode> children = new ArrayList<TreeNode>();
    for(DataItem childItem : dataItem.children) {
      children.add(getTreeNode(childItem));
    }
 
    // Add the children and return
    node.addChildren(children);
    return node;
  }
 
  private static Color getColor(double value, double min, double max) {
    double percent = Math.max((value - min) / max, 0);
    if(percent > 0.5) {
      double modifier = (percent - 0.5) * 2;
      return new Color((int)(modifier*102), (int)(modifier*153), (int)(modifier*51));
    }
    else {
      double modifier = percent *2;
      return new Color((int)(modifier*204), (int)(modifier*51), 0);
    }
  }
 
  public static class DataItem {
    private final String name;
    private final int population;
    private final int income;
    private final List<DataItem> children;
 
    public DataItem(String name, int population, int income) {
      this.name = name;
      this.population = population;
      this.income = income;
      this.children = new ArrayList<DataItem>();
    }
 
    public void addChild(DataItem child) {
      this.children.add(child);
    }
 
    public String getName() {
      return name;
    }
 
    public int getPopulation() {
      return population;
    }
 
    public int getIncome() {
      return income;
    }
 
    public List<CensusData.DataItem> getChildren() {
      return children;
    }
  }
 
  private static final int MIN_INCOME = 0;
  private static final int MAX_INCOME = 70000;
 
  private static final DataItem ROOT = new DataItem("United States", 301461533, 51425);
 
  private static final DataItem REGION_NE = new DataItem("Northeast Region", 54906297, 57208);
  private static final DataItem REGION_MW = new DataItem("Midwest Region", 66336038, 49932);
  private static final DataItem REGION_S = new DataItem("South Region", 110450832, 47204);
  private static final DataItem REGION_W = new DataItem("West Region", 69768366, 56171);
 
  private static final DataItem DIVISION_NE = new DataItem("New England", 14315257, 61511);
  private static final DataItem DIVISION_MA = new DataItem("Middle Atlantic", 40591040, 55726);
  private static final DataItem DIVISION_ENC = new DataItem("East North Central", 46277998, 50156);
  private static final DataItem DIVISION_WNC = new DataItem("West North Central", 20058040, 49443);
  private static final DataItem DIVISION_SA = new DataItem("South Atlantic", 57805475, 50188);
  private static final DataItem DIVISION_ESC = new DataItem("East South Central", 17966553, 41130);
  private static final DataItem DIVISION_WSC = new DataItem("West South Central", 34678804, 45608);
  private static final DataItem DIVISION_M = new DataItem("Mountain", 21303294, 51504);
  private static final DataItem DIVISION_P = new DataItem("Pacific", 48465072, 58735);
 
  static {
    // Set up the regions
    ROOT.addChild(REGION_NE);
    ROOT.addChild(REGION_MW);
    ROOT.addChild(REGION_S);
    ROOT.addChild(REGION_W);
 
    // Set up the divisions
    REGION_NE.addChild(DIVISION_NE);
    REGION_NE.addChild(DIVISION_MA);
    REGION_MW.addChild(DIVISION_ENC);
    REGION_MW.addChild(DIVISION_WNC);
    REGION_S.addChild(DIVISION_SA);
    REGION_S.addChild(DIVISION_ESC);
    REGION_S.addChild(DIVISION_WSC);
    REGION_W.addChild(DIVISION_M);
    REGION_W.addChild(DIVISION_P);
 
    // Set up the states
    DIVISION_NE.addChild(new DataItem("Connecticut", 3494487, 67721));
    DIVISION_NE.addChild(new DataItem("Maine", 1316380, 46541));
    DIVISION_NE.addChild(new DataItem("Massachusetts", 6511176, 64496));
    DIVISION_NE.addChild(new DataItem("New Hampshire", 1315419, 63033));
    DIVISION_NE.addChild(new DataItem("Rhode Island", 1057381, 55569));
    DIVISION_NE.addChild(new DataItem("Vermont", 620414, 51284));
 
    DIVISION_MA.addChild(new DataItem("New Jersey", 8650548, 68981));
    DIVISION_MA.addChild(new DataItem("New York", 19423896, 55233));
    DIVISION_MA.addChild(new DataItem("Pennsylvania", 12516596, 49737));
 
    DIVISION_ENC.addChild(new DataItem("Indiana", 6342469, 47465));
    DIVISION_ENC.addChild(new DataItem("Illinois", 12785043, 55222));
    DIVISION_ENC.addChild(new DataItem("Michigan", 10039208, 48700));
    DIVISION_ENC.addChild(new DataItem("Ohio", 11511858, 47144));
    DIVISION_ENC.addChild(new DataItem("Wisconsin", 5599420, 51569));
 
    DIVISION_WNC.addChild(new DataItem("Iowa", 2978880, 48052));
    DIVISION_WNC.addChild(new DataItem("Kansas", 2777835, 48394));
    DIVISION_WNC.addChild(new DataItem("Minnesota", 5188581, 57007));
    DIVISION_WNC.addChild(new DataItem("Missouri", 5904382, 46005));
    DIVISION_WNC.addChild(new DataItem("Nebraska", 1772124, 47995));
    DIVISION_WNC.addChild(new DataItem("North Dakota", 639725, 45140));
    DIVISION_WNC.addChild(new DataItem("South Dakota", 796513, 44828));
 
    DIVISION_SA.addChild(new DataItem("Delaware", 863832, 57618));
    DIVISION_SA.addChild(new DataItem("District of Columbia", 588433, 56519));
    DIVISION_SA.addChild(new DataItem("Florida", 18222420, 47450));
    DIVISION_SA.addChild(new DataItem("Georgia", 9497667, 49466));
    DIVISION_SA.addChild(new DataItem("Maryland", 5637418, 69475));
    DIVISION_SA.addChild(new DataItem("North Carolina", 9045705, 45069));
    DIVISION_SA.addChild(new DataItem("South Carolina", 4416867, 43572));
    DIVISION_SA.addChild(new DataItem("Virginia", 7721730, 60316));
    DIVISION_SA.addChild(new DataItem("West Virginia", 1811403, 37356));
 
    DIVISION_ESC.addChild(new DataItem("Alabama", 4633360, 41216));
    DIVISION_ESC.addChild(new DataItem("Kentucky", 4252000, 41197));
    DIVISION_ESC.addChild(new DataItem("Mississippi", 2922240, 36796));
    DIVISION_ESC.addChild(new DataItem("Tennessee", 6158953, 42943));
 
    DIVISION_WSC.addChild(new DataItem("Arkansas", 2838143, 38542));
    DIVISION_WSC.addChild(new DataItem("Louisiana", 4411546, 42167));
    DIVISION_WSC.addChild(new DataItem("Oklahoma", 3610073, 41861));
    DIVISION_WSC.addChild(new DataItem("Texas", 23819042, 48199));
 
    DIVISION_M.addChild(new DataItem("Arizona", 6324865, 50296));
    DIVISION_M.addChild(new DataItem("Colorado", 4843211, 56222));
    DIVISION_M.addChild(new DataItem("Idaho", 1492573, 46183));
    DIVISION_M.addChild(new DataItem("Montana", 956257, 43089));
    DIVISION_M.addChild(new DataItem("Nevada", 2545763, 55585));
    DIVISION_M.addChild(new DataItem("New Mexico", 1964860, 42742));
    DIVISION_M.addChild(new DataItem("Utah", 2651816, 55642));
    DIVISION_M.addChild(new DataItem("Wyoming", 523949, 51990));
 
    DIVISION_P.addChild(new DataItem("Alaska", 683142, 64635));
    DIVISION_P.addChild(new DataItem("California", 36308527, 60392));
    DIVISION_P.addChild(new DataItem("Hawaii", 1280241, 64661));
    DIVISION_P.addChild(new DataItem("Oregon", 3727407, 49033));
    DIVISION_P.addChild(new DataItem("Washington", 6465755, 56384));
  }
 
  public static class CensusTreeNode extends TreeNode {
    private int income;
 
    public CensusTreeNode(String text, Number size, Color color, int income) {
      super(text, size, color);
      this.income = income;
    }
 
    public int getIncome() {
      return income;
    }
  }
}

最後に、UI-first開発におけるツリー・モデルを完成するには、マネージドBeanを、データを含むクラスまたはBeanを参照するユーザーのアプリケーションに加え、オプションとして、ツリーマップあるいはサンバーストのカスタマイズのために他のメソッドを追加します。

国勢調査のツリーマップをインスタンス化し、それに国勢調査データを移入するコードのサンプルを次に示します。この例はまた、ツリーマップのノードの行データをラベル表示のための文字列に変換するサンプル・メソッド(convertToString)を含んでいます。

import org.apache.myfaces.trinidad.component.UIXHierarchy;
import org.apache.myfaces.trinidad.model.RowKeySet;
import org.apache.myfaces.trinidad.model.TreeModel;
import oracle.adf.view.faces.bi.component.treemap.UITreemap;
 
public class SampleTreemap {
  // Data Model Attrs
  private TreeModel currentModel;
  private final CensusData censusData = new CensusData();
  private String censusRoot = "United States";
  private UITreemap treemap;
 
  public TreeModel getCensusRootData() {
    return censusData.getUnitedStatesData();
  }
  public TreeModel getCensusData() {
    if ("West Region".equals(censusRoot))
      return censusData.getRegionWestData();
    else if ("South Region".equals(censusRoot))
      return censusData.getRegionSouthData();
    else if ("Midwest Region".equals(censusRoot))
      return censusData.getRegionMidwestData();
    else if ("Northeast Region".equals(censusRoot))
      return censusData.getRegionNortheastData();
    else if ("Pacific Division".equals(censusRoot))
      return censusData.getDivisionPacificData();
    else
      return censusData.getUnitedStatesData();
  }
  public TreeModel getData() {
    // Return cached data model if available
    if(currentModel != null)
      return currentModel;
    currentModel = getCensusData();
    return currentModel;
  }
  public void setCensusRoot(String censusRoot) {
    this.censusRoot = censusRoot;
  }
  public String getCensusRoot() {
    return censusRoot;
  }
  //Converts the rowKeySet into a string of node text labels.
  public static String convertToString(RowKeySet rowKeySet, UIXHierarchy hierarchy) {
    StringBuilder s = new StringBuilder();
    if (rowKeySet != null) {
      for (Object rowKey : rowKeySet) {
        TreeNode rowData = (TreeNode)hierarchy.getRowData(rowKey);
        s.append(rowData.getText()).append(", ");
      }
      // Remove the trailing comma
      if (s.length() > 0)
        s.setLength(s.length() - 2);
    }
    return s.toString();
  }
  public void setTreemap(UITreemap treemap) {
        this.treemap = treemap;
  }
  public UITreemap getTreemap() {
        return treemap;
  }
}

両方のコンポーネントは同じツリー・モデルを使用するため、サンバースト国勢調査の例を設定するコードはほとんど同一です。例は、「サンバースト・マネージドBeanのサンプル・コード」を参照してください。

F.3.2 サンバースト・マネージドBeanのサンプル・コード

次のサンプル・コードは、国勢調査サンバーストをインスタンス化し、それに国勢調査データを移入します。この例はまた、サンバーストのノードの行データをラベル表示のための文字列に変換するサンプル・メソッド(convertToString)を含んでいます。

import oracle.adf.view.faces.bi.component.sunburst.UISunburst;
import org.apache.myfaces.trinidad.component.UIXHierarchy;
import org.apache.myfaces.trinidad.model.RowKeySet;
import org.apache.myfaces.trinidad.model.TreeModel;
 
public class SunburstSample {
  // Components
  private UISunburst sunburst;
  // Attributes
  private TreeModel currentModel;
  private final CensusData censusData = new CensusData();
  private String censusRoot = "United States";
  
  public TreeModel getCensusRootData() {
    return censusData.getUnitedStatesData();
  }
  public TreeModel getCensusData() {
    if ("West Region".equals(censusRoot))
      return censusData.getRegionWestData();
    else if ("South Region".equals(censusRoot))
      return censusData.getRegionSouthData();
    else if ("Midwest Region".equals(censusRoot))
      return censusData.getRegionMidwestData();
    else if ("Northeast Region".equals(censusRoot))
      return censusData.getRegionNortheastData();
    else if ("Pacific Division".equals(censusRoot))
      return censusData.getDivisionPacificData();
    else
      return censusData.getUnitedStatesData();
  }
  public TreeModel getData() {
    // Return cached data model if available
    if(currentModel != null)
      return currentModel;
    currentModel = getCensusData();
    return currentModel;
  }
  public void setCensusRoot(String censusRoot) {
    this.censusRoot = censusRoot;
  }
  public String getCensusRoot() {
    return censusRoot;
  }
  public static String convertToString(RowKeySet rowKeySet, UIXHierarchy hierarchy) {
    StringBuilder s = new StringBuilder();
    if (rowKeySet != null) {
      for (Object rowKey : rowKeySet) {
        TreeNode rowData = (TreeNode)hierarchy.getRowData(rowKey);
        s.append(rowData.getText()).append(", ");
      }
      // Remove the trailing comma
      if (s.length() > 0)
        s.setLength(s.length() - 2);
    }
    return s.toString();
  }  
  public void setSunburst(UISunburst sunburst) {    
    this.sunburst = sunburst;
  }
  public UISunburst getSunburst() {
    return sunburst;
  }
}

F.4 第32章「ダイアグラム・コンポーネントの使用」のサンプル

JDeveloperのダイアグラムの作成ウィザードにはデフォルトのクライアント・レイアウトが用意されており、それを使用するか編集すれば、ダイアグラムのレイアウトを指定することができます。デフォルトのレイアウトは、力指向グラフ描画アルゴリズムに基づいています。

次に示すのは、DVTダイアグラムのレイアウト・フレームワークを使用して、ダイアグラム・コンポーネントを作成するコード例です。

F.4.1 デフォルト・クライアント・レイアウトのコード・サンプル

UI優先開発を使用してダイアグラムを作成する場合、JDeveloperにより、力指向グラフ描画アルゴリズムに基づくデフォルト・クライアント・レイアウト・オプションが提供され、それを使用したり、編集できます。

var Application1DiagramLayout = function(layoutContext, optLinkLength, initialTemp) {
  this._layoutContext = layoutContext;
  this._optLinkLength = optLinkLength;
  this._initialTemp = initialTemp;
};
 
//pad factor for the node size
Application1DiagramLayout.PAD_FACTOR = 1.2;
//initial temperature factor - percent of ideal viewport dimension
Application1DiagramLayout.INIT_TEMP_FACTOR = .25; 
//number of iterations to run
Application1DiagramLayout.ITERATIONS = 200; 
 
/**
 * Main function that does the force directed layout (Layout entry point)
 * See algorithm in "Graph Drawing by Force-directed Placement" by Thomas M. J. Fruchterman and Edward M. Reingold
 * @param {DvtDiagramLayoutContext} layoutContext object that defines a context for layout call
 */
Application1DiagramLayout.forceDirectedLayout = function(layoutContext)
{
  //pretend that the layout area is just big enough to fit all the nodes
  var maxBounds = Application1DiagramLayout.getMaxNodeBounds(layoutContext);
  var nodeCount = layoutContext.getNodeCount();
  var area = nodeCount * (Application1DiagramLayout.PAD_FACTOR * maxBounds.w) * (Application1DiagramLayout.PAD_FACTOR * maxBounds.h);
  var initialTemp = Application1DiagramLayout.INIT_TEMP_FACTOR * Math.sqrt(area);
    
  //optimal link length - default is just the size of an ideal grid cell  
  var layoutAttrs = layoutContext.getLayoutAttributes();
  var optLinkLength = (layoutAttrs && layoutAttrs["optimalLinkLength"]) ? parseFloat(layoutAttrs["optimalLinkLength"]) : Math.sqrt(area / nodeCount);
 
  //initialize and run the layout  
  var layout = new Application1DiagramLayout(layoutContext, optLinkLength, initialTemp);
  layout.layoutNodes();
  layout.layoutLinks(); 
  
  //position labels
  layout.positionNodeLabels(); 
  layout.positionLinkLabels();
};
 
/**
 * Layout nodes
 */
Application1DiagramLayout.prototype.layoutNodes = function () {
  this.initForceDirectedPositions();
  var iter = Application1DiagramLayout.ITERATIONS; 
  var temp = this._initialTemp;  
  for (var i = 0; i < iter; i++) {
    this.runForceDirectedIteration(temp);
    //after each iteration, decrease the temperature - we do it linearly
    temp = this._initialTemp * (1 - (i + 1)/iter);
  }
};
 
/**
 * Reposition nodes using force directed algorithm for a single iteration
 * @param {number} t temperature for the iteration that used to limit node displacement
 */
Application1DiagramLayout.prototype.runForceDirectedIteration = function(t)
{    
  //calculate the repulsive force between each two nodes
  var nodeCount = this._layoutContext.getNodeCount();
  for (var ni = 0; ni < nodeCount; ni++) {
    var node = this._layoutContext.getNodeByIndex(ni);
    node.disp = new DvtDiagramPoint(0, 0);
    for (var ni2 = 0; ni2 < nodeCount; ni2++) {
      if (ni == ni2)
        continue;
      var node2 = this._layoutContext.getNodeByIndex(ni2); 
      
      var difference = this._subtractVectors(node.getPosition(), node2.getPosition());
      var distance = this._vectorLength(difference);
      var repulsion = (this._optLinkLength * this._optLinkLength) / distance;
      node.disp = this._addVectors(node.disp, this._scaleVector(difference, repulsion / distance ));
    }
  }
  
  //calculate the attractive force between linked nodes
  var linkCount = this._layoutContext.getLinkCount();
  for (var li = 0; li < linkCount; li++) {
    var link = this._layoutContext.getLinkByIndex(li);
    var node = this._getNodeAtCurrentLevel (link.getStartId());
    var node2 = this._getNodeAtCurrentLevel (link.getEndId());
    if (!node || !node2)
      continue;
    var difference = this._subtractVectors(node.getPosition(), node2.getPosition());
    var distance = this._vectorLength(difference);
    var attraction = (distance * distance) / this._optLinkLength;
    node.disp =  this._subtractVectors(node.disp, this._scaleVector(difference, attraction / distance ) );
    node2.disp = this._addVectors(node2.disp, this._scaleVector(difference, attraction / distance ) );    
  }
 
  //limit node displacement by the temperature t and set the position
  for (var ni = 0; ni < nodeCount; ni++) {
    var node = this._layoutContext.getNodeByIndex(ni);
    this._addGravity(node);
    var distance = this._vectorLength(node.disp);
    var pos = this._addVectors(node.getPosition(), this._scaleVector(node.disp, Math.min(distance, t) / distance));
    node.setPosition(pos);
  }
};
 
/**
 * Adds gravity force that attracts a node to the center, the gravity force does not allow disconnected nodes and branches to be pushed far away from the center
 * @param {DvtDiagramLayoutContextNode} node object that defines node context for the layout
 */
Application1DiagramLayout.prototype._addGravity = function(node) {
  var gravityAdjustment = .2; 
  var distance = this._vectorLength(node.getPosition()); //distance from the center (0,0)
  var attraction = (distance * distance) / this._optLinkLength;
  node.disp =  this._subtractVectors(node.disp, this._scaleVector(node.getPosition(), attraction / distance * gravityAdjustment ) );
};
 
/**
 * Initializes node positions - node positions in force directed layout must be initialized such that no
 * two nodes have the same position. Position nodes in a circle.
 */
Application1DiagramLayout.prototype.initForceDirectedPositions = function() {
  var nodeCount = this._layoutContext.getNodeCount();
  var angleStep = 2*Math.PI / nodeCount;
  var radius = this._optLinkLength;
  for (var ni = 0; ni < nodeCount; ni++) {
    var x = radius * Math.cos(angleStep * ni);
    var y = radius * Math.sin(angleStep * ni);
    var node = this._layoutContext.getNodeByIndex(ni);
    node.setPosition(new DvtDiagramPoint(x, y));  
  }
};
 
/**
 * Calculate vector length
 * @param {DvtDiagramPoint} p vector
 * @return {number} vector length
 */
Application1DiagramLayout.prototype._vectorLength = function(p) {
  return Math.sqrt(p.x * p.x + p.y * p.y);
};
 
/**
 * Scale vector
 * @param {DvtDiagramPoint} p vector
 * @param {number} scale scale
 * @return {DvtDiagramPoint} resulting vector
 */
Application1DiagramLayout.prototype._scaleVector = function(p, scale) {
  return new DvtDiagramPoint(p.x * scale, p.y * scale);
};
 
/**
 * Adds vectors
 * @param {DvtDiagramPoint} p1 vector
 * @param {DvtDiagramPoint} p2 vector
 * @return {DvtDiagramPoint} resulting vector
 */
Application1DiagramLayout.prototype._addVectors = function(p1, p2) {
  return new DvtDiagramPoint(p1.x + p2.x, p1.y + p2.y);
};
 
/**
 * Subtract vectors
 * @param {DvtDiagramPoint} p1 vector
 * @param {DvtDiagramPoint} p2 vector
 * @return {DvtDiagramPoint} resulting vector 
 */
Application1DiagramLayout.prototype._subtractVectors = function(p1, p2) {
  return new DvtDiagramPoint(p1.x - p2.x, p1.y - p2.y);
};
 
/**
 * Finds a node for the link by the node id. In case of a link that does not connect nodes across containers, that will be a node itself. 
 * In case when a link connects nodes across containers, that might be one of the ancestor nodes - the node that has been processed at the current level.
 * @param {string} nodeId id of the node to check
 */
Application1DiagramLayout.prototype._getNodeAtCurrentLevel = function(nodeId) {
  var node;
  do {
    if (!nodeId)
      return null;
    node = this._layoutContext.getNodeById(nodeId);
    nodeId = node.getContainerId();
  } while (!node.disp);
  return node;
};
 
/**
 * Create links
 */
Application1DiagramLayout.prototype.layoutLinks = function () {
  for (var li = 0;li < this._layoutContext.getLinkCount();li++) {
    var link = this._layoutContext.getLinkByIndex(li);
    link.setPoints(this.getEndpoints(link));
  }
};
 
/**
 * Get endpoints for the link
 * @param {DvtDiagramLayoutContextLink} link object  that defines link context for the layout
 * @return {array} an array that contains the start X, Y coordinates and the end X, Y coordinates for the link
 */
Application1DiagramLayout.prototype.getEndpoints = function (link) {
  var n1 = this._layoutContext.getNodeById(link.getStartId());
  var n2 = this._layoutContext.getNodeById(link.getEndId());
  
  var n1Position = n1.getPosition();
  var n2Position = n2.getPosition();
  if (n1.getContainerId() || n2.getContainerId()) { //for cross-container link
    n1Position = this._layoutContext.localToGlobal(new DvtDiagramPoint(0, 0), n1);
    n2Position = this._layoutContext.localToGlobal(new DvtDiagramPoint(0, 0), n2);
  }
  
  var b1 = n1.getContentBounds();
  var b2 = n2.getContentBounds();
  
  var startX = n1Position.x + b1.x + .5 * b1.w;
  var startY = n1Position.y + b1.y + .5 * b1.h;
  var endX = n2Position.x + b2.x + .5 * b2.w;
  var endY = n2Position.y + b2.y + .5 * b2.h;  
  
  b1 = new DvtDiagramRectangle(n1Position.x + b1.x, n1Position.y + b1.y, b1.w, b1.h);
  b2 = new DvtDiagramRectangle(n2Position.x + b2.x, n2Position.y + b2.y, b2.w, b2.h);  
  var startP = this._findLinkNodeIntersection(b1, startX, startY, endX, endY, link.getStartConnectorOffset());
  var endP = this._findLinkNodeIntersection(b2, endX, endY, startX, startY, link.getEndConnectorOffset());
  return [startP.x, startP.y, endP.x, endP.y];
};
 
/**
 * Find a point where a link line intersects the node boundary - use that point as the start or the end connection point
 * @param {DvtDiagramRectangle} rect the bounds of the node content
 * @param {number} startX x coordinate for the line start
 * @param {number} startY y coordinate for the line start
 * @param {number} endX x coordinate for the line end
 * @param {number} endY y coordinate for the line end
 * @param {number} connOffset the offset of the start connector
 * @return {DvtDiagramPoint} a point where a link line intersects the node boundary
 */
Application1DiagramLayout.prototype._findLinkNodeIntersection = function (rect, startX, startY, endX, endY, connOffset) {
 
  var lineAngle = Math.atan2(endY - startY, endX - startX);
  var cornerAngle = Math.atan2(rect.h, rect.w); // rectangle diagonal from top left to right bottom
  var bottomRightAngle = cornerAngle; 
  var bottomLeftAngle = Math.PI - bottomRightAngle;
  var topRightAngle =  - bottomRightAngle;
  var topLeftAngle =  - bottomLeftAngle;
  var x = 0, y = 0;
  if (lineAngle >= topLeftAngle && lineAngle <= topRightAngle) {    // side top
    x = rect.x + rect.w * .5 + Math.tan(Math.PI / 2 - lineAngle) * (-rect.h * .5);
    y = rect.y;          
  }        
  else if (lineAngle <= bottomLeftAngle && lineAngle >= bottomRightAngle) {     // side bottom
    x = rect.x + rect.w * .5 + Math.tan(Math.PI / 2 - lineAngle) * (rect.h * .5);
    y = rect.y + rect.h;
  }
  else if (lineAngle <= bottomRightAngle && lineAngle >= topRightAngle) { // side right
    x = rect.x + rect.w;
    y = rect.y + rect.h * .5 + Math.tan(lineAngle) * (rect.w * .5);
  }        
  else {                                                                  //side left
    x = rect.x;
    y = rect.y + rect.h * .5 + Math.tan(lineAngle) * (- rect.w * .5);          
  }
        
  if (connOffset) {
    x += Math.cos(lineAngle) * connOffset;
    y += Math.sin(lineAngle) * connOffset;
  }
  return new DvtDiagramPoint(x, y);  
};
 
/**
 * Center node label below the node
 */
Application1DiagramLayout.prototype.positionNodeLabels = function () {
  for (var ni = 0; ni < this._layoutContext.getNodeCount();ni++) {
    var node = this._layoutContext.getNodeByIndex(ni);
    var nodeLabelBounds = node.getLabelBounds();
    if (nodeLabelBounds) {
      var nodeBounds = node.getContentBounds();
      var nodePos = node.getPosition();      
      var labelX = nodeBounds.x + nodePos.x + .5 * (nodeBounds.w - nodeLabelBounds.w);
      var labelY = nodeBounds.y + nodePos.y + nodeBounds.h;
      node.setLabelPosition(new DvtDiagramPoint(labelX, labelY));
    }
  }
};
 
/**
 * Position link label at the link center
 */
Application1DiagramLayout.prototype.positionLinkLabels = function (layoutContext) {
  for (var ni = 0;ni < this._layoutContext.getLinkCount();ni++) {
    var link = this._layoutContext.getLinkByIndex(ni);
    var linkLabelBounds = link.getLabelBounds();
    if (linkLabelBounds) {
      var points = link.getPoints();
      if (points.length >=4) {
        var startX = points[0], endX = points[points.length-2];
        var startY = points[1], endY = points[points.length-1];
        var labelX = startX + .5 * (endX - startX - linkLabelBounds.w);
        var labelY = startY + .5 * (endY - startY - linkLabelBounds.h);
        link.setLabelPosition(new DvtDiagramPoint(labelX, labelY));
      }
    }
  }
};
 
/**
 * Helper function that finds max node width and height
 * @param {DvtDiagramLayoutContext} layoutContext Object that defines a context for layout call
 * @return {DvtDiagramRectangle} a rectangle that represent a node with max width and max height
 */
Application1DiagramLayout.getMaxNodeBounds = function (layoutContext) {
  var nodeCount = layoutContext.getNodeCount();
  var maxW = 0 , maxH = 0;
  for (var ni = 0;ni < nodeCount;ni++) {
    var node = layoutContext.getNodeByIndex(ni);
    var bounds = node.getContentBounds();
    maxW = Math.max(bounds.w, maxW);
    maxH = Math.max(bounds.h, maxH);
  }
  return new DvtDiagramRectangle(0, 0, maxW, maxH);
};

F.4.2 単純な円形レイアウトのコード・サンプル

ノードおよびリンクの単純な円形レイアウト用です。

var LearningDiagramLayouts = {
    simpleVertical : function (layoutContext) {
        var largestNodeBounds = LearningDiagramLayouts.getMaxNodeBounds(layoutContext);
        var nodeGap = (2 * largestNodeBounds.h);
        var nodeCount = layoutContext.getNodeCount();
        var linkCount = layoutContext.getLinkCount();
        var xPos = 0;
        var yPos = 0;
        var labelXPos = largestNodeBounds.w + 10;
 
        for (var i = 0;i < nodeCount;i++) {
            var node = layoutContext.getNodeByIndex(i);
            var nodeHeight = node.getContentBounds().h;
            node.setPosition(new DvtDiagramPoint(xPos, yPos));
 
            var labelHeight = node.getLabelBounds().h;
            var labelYPos = yPos + ((nodeHeight - labelHeight) / 2);
 
            node.setLabelPosition(new DvtDiagramPoint(labelXPos, labelYPos));
            yPos += (nodeHeight + nodeGap);
        }
 
        var linkGap = 4;
        var largestLinkLabelBounds = LearningDiagramLayouts.getMaxLinkLabelBounds(layoutContext);
        for (var j = 0;j < linkCount;j++) {
            var link = layoutContext.getLinkByIndex(j);
            var startNode = layoutContext.getNodeById(link.getStartId());
            var endNode = layoutContext.getNodeById(link.getEndId());
            var linkStartPos = LearningDiagramLayouts.getNodeEdgeMidPoint(startNode, "s");
            var linkEndPos = LearningDiagramLayouts.getNodeEdgeMidPoint(endNode, "n");
            
            var linkLabelXPos;
            var linkLabelYPos = linkStartPos.y + ((linkEndPos.y - linkStartPos.y - link.getLabelBounds().h) / 2);
 
            if (linkEndPos.y > linkStartPos.y) {
                link.setPoints([linkStartPos.x, (linkStartPos.y + linkGap), linkEndPos.x, (linkEndPos.y - linkGap)]);
                linkLabelXPos = linkStartPos.x - (link.getLabelBounds().w + 20);
            } else {
                // need to re-route in this case 
                // Segment 1 - keep heading down for a short distance 
                var turn1 = new DvtDiagramPoint();
                turn1.x = linkStartPos.x;
                turn1.y = linkStartPos.y + (largestNodeBounds.h/2);
                // Segment 2 - head left far enough to avoid overlap with the other link labels
                var turn2 = new DvtDiagramPoint();
                turn2.x = turn1.x - (largestLinkLabelBounds.w + 40);
                turn2.y = turn1.y;
                // Segment 3 - Back up the diagram to a point equally above the end node
                var turn3 = new DvtDiagramPoint();
                turn3.x = turn2.x;
                turn3.y = linkEndPos.y - (largestNodeBounds.h/2);
                // Segment 4 - Back to the center line
                var turn4 = new DvtDiagramPoint();
                turn4.x = linkEndPos.x;
                turn4.y = turn3.y;
                // Segment 5 - And down to the end node for which 
                // we already have the coordinates
                
                //Now pass in the path:
                link.setPoints([linkStartPos.x, (linkStartPos.y + linkGap), 
                                                 turn1.x, turn1.y,
                                                 turn2.x, turn2.y,
                                                 turn3.x, turn3.y,
                                                 turn4.x, turn4.y,
                                                 linkEndPos.x, (linkEndPos.y - linkGap)]);
                                                 
               //Finally work out the X position of the label in this case
                linkLabelXPos = turn3.x - (link.getLabelBounds().w + 20);
            }
 
            link.setLabelPosition(new DvtDiagramPoint(linkLabelXPos, linkLabelYPos));
        }
    },
    /**
     * Utility function to return the size information for
     * the largest node being handled by the layout
     */
    getMaxNodeBounds : function (layoutContext) {
        var nodeCount = layoutContext.getNodeCount();
        var maxW = 0;
        var maxH = 0;
        for (var i = 0;i < nodeCount;i++) {
            var node = layoutContext.getNodeByIndex(i);
            var bounds = node.getContentBounds();
            maxW = Math.max(bounds.w, maxW);
            maxH = Math.max(bounds.h, maxH);
        }
        return new DvtDiagramRectangle(0, 0, maxW, maxH);
    },
    /**
     * Utility function to return the size information for
     * the largest link label being handled by the layout
     */
 
    getMaxLinkLabelBounds : function (layoutContext) {
        var linkCount = layoutContext.getLinkCount();
        var maxW = 0;
        var maxH = 0;
        for (var i = 0;i < linkCount;i++) {
            var link = layoutContext.getLinkByIndex(i);
            var bounds = link.getLabelBounds();
            maxW = Math.max(bounds.w, maxW);
            maxH = Math.max(bounds.h, maxH);
        }
        return new DvtDiagramRectangle(0, 0, maxW, maxH);
    },
    /**
     * Utility function to return the midpoint of a node edge.  
     * Edges are identified as compass points n e s w
     */
    getNodeEdgeMidPoint : function (node, edge) {
        var nodeSize = node.getContentBounds();
        var nodePosition = node.getPosition();
        var xpos;
        var ypos;
 
        switch (edge) {
            case "n":
            case "s":
                xpos = nodePosition.x + (nodeSize.w / 2);
                break;
            case "e":
                xpos = nodePosition.x + nodeSize.w;
                break;
            case "w":
                xpos = nodePosition.x;
                break;
            default :
                xpos = 0;
        }
 
        switch (edge) {
            case "e":
            case "w":
                ypos = nodePosition.y + (nodeSize.h / 2);
                break;
            case "s":
                ypos = nodePosition.y + nodeSize.h;
                break;
            case "n":
                ypos = nodePosition.y;
                break;
            default :
                ypos = 0;
        }
 
        return new DvtDiagramPoint(xpos, ypos);
    }
}

F.4.3 単純な垂直レイアウトのコード・サンプル

このダイアグラム・レイアウトは、1ノード当たり1つの受信リンクと1つの送信リンクを超えないことを仮定し、制限付きの垂直レイアウトを使用しています。

var LearningDiagramLayouts = {
    simpleVertical : function (layoutContext) {
        var largestNodeBounds = LearningDiagramLayouts.getMaxNodeBounds(layoutContext);
        var nodeGap = (2 * largestNodeBounds.h);
        var nodeCount = layoutContext.getNodeCount();
        var linkCount = layoutContext.getLinkCount();
        var xPos = 0;
        var yPos = 0;
        var labelXPos = largestNodeBounds.w + 10;
 
        for (var i = 0;i < nodeCount;i++) {
            var node = layoutContext.getNodeByIndex(i);
            var nodeHeight = node.getContentBounds().h;
            node.setPosition(new DvtDiagramPoint(xPos, yPos));
 
            var labelHeight = node.getLabelBounds().h;
            var labelYPos = yPos + ((nodeHeight - labelHeight) / 2);
 
            node.setLabelPosition(new DvtDiagramPoint(labelXPos, labelYPos));
            yPos += (nodeHeight + nodeGap);
        }
 
        var linkGap = 4;
        var largestLinkLabelBounds = LearningDiagramLayouts.getMaxLinkLabelBounds(layoutContext);
        for (var j = 0;j < linkCount;j++) {
            var link = layoutContext.getLinkByIndex(j);
            var startNode = layoutContext.getNodeById(link.getStartId());
            var endNode = layoutContext.getNodeById(link.getEndId());
            var linkStartPos = LearningDiagramLayouts.getNodeEdgeMidPoint(startNode, "s");
            var linkEndPos = LearningDiagramLayouts.getNodeEdgeMidPoint(endNode, "n");
            
            var linkLabelXPos;
            var linkLabelYPos = linkStartPos.y + ((linkEndPos.y - linkStartPos.y - link.getLabelBounds().h) / 2);
 
            if (linkEndPos.y > linkStartPos.y) {
                link.setPoints([linkStartPos.x, (linkStartPos.y + linkGap), linkEndPos.x, (linkEndPos.y - linkGap)]);
                linkLabelXPos = linkStartPos.x - (link.getLabelBounds().w + 20);
            } else {
                // need to re-route in this case 
                // Segment 1 - keep heading down for a short distance 
                var turn1 = new DvtDiagramPoint();
                turn1.x = linkStartPos.x;
                turn1.y = linkStartPos.y + (largestNodeBounds.h/2);
                // Segment 2 - head left far enough to avoid overlap with the other link labels
                var turn2 = new DvtDiagramPoint();
                turn2.x = turn1.x - (largestLinkLabelBounds.w + 40);
                turn2.y = turn1.y;
                // Segment 3 - Back up the diagram to a point equally above the end node
                var turn3 = new DvtDiagramPoint();
                turn3.x = turn2.x;
                turn3.y = linkEndPos.y - (largestNodeBounds.h/2);
                // Segment 4 - Back to the center line
                var turn4 = new DvtDiagramPoint();
                turn4.x = linkEndPos.x;
                turn4.y = turn3.y;
                // Segment 5 - And down to the end node for which 
                // we already have the coordinates
                
                //Now pass in the path:
                link.setPoints([linkStartPos.x, (linkStartPos.y + linkGap), 
                                                 turn1.x, turn1.y,
                                                 turn2.x, turn2.y,
                                                 turn3.x, turn3.y,
                                                 turn4.x, turn4.y,
                                                 linkEndPos.x, (linkEndPos.y - linkGap)]);
                                                 
               //Finally work out the X position of the label in this case
                linkLabelXPos = turn3.x - (link.getLabelBounds().w + 20);
            }
 
            link.setLabelPosition(new DvtDiagramPoint(linkLabelXPos, linkLabelYPos));
        }
    },
    /**
     * Utility function to return the size information for
     * the largest node being handled by the layout
     */
    getMaxNodeBounds : function (layoutContext) {
        var nodeCount = layoutContext.getNodeCount();
        var maxW = 0;
        var maxH = 0;
        for (var i = 0;i < nodeCount;i++) {
            var node = layoutContext.getNodeByIndex(i);
            var bounds = node.getContentBounds();
            maxW = Math.max(bounds.w, maxW);
            maxH = Math.max(bounds.h, maxH);
        }
        return new DvtDiagramRectangle(0, 0, maxW, maxH);
    },
    /**
     * Utility function to return the size information for
     * the largest link label being handled by the layout
     */
 
    getMaxLinkLabelBounds : function (layoutContext) {
        var linkCount = layoutContext.getLinkCount();
        var maxW = 0;
        var maxH = 0;
        for (var i = 0;i < linkCount;i++) {
            var link = layoutContext.getLinkByIndex(i);
            var bounds = link.getLabelBounds();
            maxW = Math.max(bounds.w, maxW);
            maxH = Math.max(bounds.h, maxH);
        }
        return new DvtDiagramRectangle(0, 0, maxW, maxH);
    },
    /**
     * Utility function to return the midpoint of a node edge.  
     * Edges are identified as compass points n e s w
     */
    getNodeEdgeMidPoint : function (node, edge) {
        var nodeSize = node.getContentBounds();
        var nodePosition = node.getPosition();
        var xpos;
        var ypos;
 
        switch (edge) {
            case "n":
            case "s":
                xpos = nodePosition.x + (nodeSize.w / 2);
                break;
            case "e":
                xpos = nodePosition.x + nodeSize.w;
                break;
            case "w":
                xpos = nodePosition.x;
                break;
            default :
                xpos = 0;
        }
 
        switch (edge) {
            case "e":
            case "w":
                ypos = nodePosition.y + (nodeSize.h / 2);
                break;
            case "s":
                ypos = nodePosition.y + nodeSize.h;
                break;
            case "n":
                ypos = nodePosition.y;
                break;
            default :
                ypos = 0;
        }
 
        return new DvtDiagramPoint(xpos, ypos);
    }
}