この付録の内容は次のとおりです。
JavaScriptパーティションをクライアントに配信する際には、adf-js-partitions.xmlファイルが関与します。パーティションでJavaScript機能をグループ化します。
次に、ADF Facesアーキテクチャを使用する場合のコード例を示します。
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>
mapProvider
機能を使用すると、テーマ・マップを構成できます。oracle.adf.view.faces.bi.component.thematicMap.mapProvider.MapProviderやorace.adf.view.faces.bi.component.thematicMap.mapProvider.LayerAreaなどのMapProvider APIを使用すると、カスタム・ベースマップを構成し、すべて同じ機能を持つ組込みのベースマップとして扱うことができます。
次は、DVTマップ・コンポーネントを作成するサンプル・コードです。
テーマ・マップ用のカスタム・ベース・マップを作成する場合は、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); } }
テーマ・マップ用のカスタム・ベース・マップを作成する場合は、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; } }
UI-first開発を使用してツリーマップあるいはサンバーストにデータを追加するには、ツリー・モデルを作成してJDeveloperにおけるクラス、Beanあるいはメソッドを参照する、クラス、マネージドBean、およびメソッドを作成します。
次は、ツリーマップとサンバースト・コンポーネントを作成するサンプル・コードです。
ツリーマップあるいはサンバーストを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のサンプル・コード」を参照してください。
次のサンプル・コードは、国勢調査サンバーストをインスタンス化し、それに国勢調査データを移入します。この例はまた、サンバーストのノードの行データをラベル表示のための文字列に変換するサンプル・メソッド(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; } }
JDeveloperのダイアグラムの作成ウィザードにはデフォルトのクライアント・レイアウトが用意されており、それを使用するか編集すれば、ダイアグラムのレイアウトを指定することができます。デフォルトのレイアウトは、力指向グラフ描画アルゴリズムに基づいています。
次に示すのは、DVTダイアグラムのレイアウト・フレームワークを使用して、ダイアグラム・コンポーネントを作成するコード例です。
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); };
ノードおよびリンクの単純な円形レイアウト用です。
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); } }
このダイアグラム・レイアウトは、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); } }