Spec-Zone .ru
спецификации, руководства, описания, API
|
001/* 002 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. 003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 004 * 005 * This code is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU General Public License version 2 only, as 007 * published by the Free Software Foundation. Oracle designates this 008 * particular file as subject to the "Classpath" exception as provided 009 * by Oracle in the LICENSE file that accompanied this code. 010 * 011 * This code is distributed in the hope that it will be useful, but WITHOUT 012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 014 * version 2 for more details (a copy is included in the LICENSE file that 015 * accompanied this code). 016 * 017 * You should have received a copy of the GNU General Public License version 018 * 2 along with this work; if not, write to the Free Software Foundation, 019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 020 * 021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 022 * or visit www.oracle.com if you need additional information or have any 023 * questions. 024 */ 025 026package javafx.scene.chart; 027 028import java.util.ArrayList; 029import java.util.Collections; 030import java.util.List; 031 032import javafx.animation.Animation; 033import javafx.animation.KeyFrame; 034import javafx.beans.property.BooleanProperty; 035import javafx.beans.property.ObjectProperty; 036import javafx.beans.property.ObjectPropertyBase; 037import javafx.beans.property.SimpleBooleanProperty; 038import javafx.beans.property.StringProperty; 039import javafx.beans.property.StringPropertyBase; 040import javafx.collections.ObservableList; 041import javafx.geometry.Pos; 042import javafx.geometry.Side; 043import javafx.scene.Node; 044import javafx.scene.control.Label; 045import javafx.scene.layout.Pane; 046import javafx.scene.layout.Region; 047 048import com.sun.javafx.charts.ChartLayoutAnimator; 049import com.sun.javafx.charts.Legend; 050import javafx.css.StyleableBooleanProperty; 051import javafx.css.StyleableObjectProperty; 052import javafx.css.CssMetaData; 053import com.sun.javafx.css.converters.BooleanConverter; 054import com.sun.javafx.css.converters.EnumConverter; 055import javafx.css.Styleable; 056import javafx.css.StyleableProperty; 057 058/** 059 * Base class for all charts. It has 3 parts the title, legend and chartContent. The chart content is populated by the 060 * specific subclass of Chart. 061 * 062 */ 063public abstract class Chart extends Region { 064 065 // -------------- PRIVATE FIELDS ----------------------------------------------------------------------------------- 066 067 private static final int MIN_WIDTH_TO_LEAVE_FOR_CHART_CONTENT = 200; 068 private static final int MIN_HEIGHT_TO_LEAVE_FOR_CHART_CONTENT = 150; 069 070 /** Title Label */ 071 private final Label titleLabel = new Label(); 072 /** 073 * This is the Pane that Chart subclasses use to contain the chart content, 074 * It is sized to be inside the chart area leaving space for the title and legend. 075 */ 076 private final Pane chartContent = new Pane() { 077 @Override protected void layoutChildren() { 078 final double top = snappedTopInset(); 079 final double left = snappedLeftInset(); 080 final double bottom = snappedBottomInset(); 081 final double right = snappedRightInset(); 082 final double width = getWidth(); 083 final double height = getHeight(); 084 final double contentWidth = snapSize(width - (left + right)); 085 final double contentHeight = snapSize(height - (top + bottom)); 086 layoutChartChildren(snapPosition(top), snapPosition(left),contentWidth,contentHeight); 087 } 088 @Override public boolean usesMirroring() { 089 return useChartContentMirroring; 090 } 091 }; 092 // Determines if chart content should be mirrored if node orientation is right-to-left. 093 boolean useChartContentMirroring = true; 094 095 /** Animator for animating stuff on the chart */ 096 private final ChartLayoutAnimator animator = new ChartLayoutAnimator(chartContent); 097 098 // -------------- PUBLIC PROPERTIES -------------------------------------------------------------------------------- 099 100 /** The chart title */ 101 private StringProperty title = new StringPropertyBase() { 102 @Override protected void invalidated() { 103 titleLabel.setText(get()); 104 } 105 106 @Override 107 public Object getBean() { 108 return Chart.this; 109 } 110 111 @Override 112 public String getName() { 113 return "title"; 114 } 115 }; 116 public final String getTitle() { return title.get(); } 117 public final void setTitle(String value) { title.set(value); } 118 public final StringProperty titleProperty() { return title; } 119 120 /** 121 * The side of the chart where the title is displayed 122 * @default Side.TOP 123 */ 124 private ObjectProperty<Side> titleSide = new StyleableObjectProperty<Side>(Side.TOP) { 125 @Override protected void invalidated() { 126 requestLayout(); 127 } 128 129 @Override 130 public CssMetaData<Chart,Side> getCssMetaData() { 131 return StyleableProperties.TITLE_SIDE; 132 } 133 134 @Override 135 public Object getBean() { 136 return Chart.this; 137 } 138 139 @Override 140 public String getName() { 141 return "titleSide"; 142 } 143 }; 144 public final Side getTitleSide() { return titleSide.get(); } 145 public final void setTitleSide(Side value) { titleSide.set(value); } 146 public final ObjectProperty<Side> titleSideProperty() { return titleSide; } 147 148 /** 149 * The node to display as the Legend. Subclasses can set a node here to be displayed on a side as the legend. If 150 * no legend is wanted then this can be set to null 151 */ 152 private final ObjectProperty<Node> legend = new ObjectPropertyBase<Node>() { 153 private Node old = null; 154 @Override protected void invalidated() { 155 Node newLegend = get(); 156 if (old != null) getChildren().remove(old); 157 if (newLegend != null) { 158 getChildren().add(newLegend); 159 newLegend.setVisible(isLegendVisible()); 160 } 161 old = newLegend; 162 } 163 164 @Override 165 public Object getBean() { 166 return Chart.this; 167 } 168 169 @Override 170 public String getName() { 171 return "legend"; 172 } 173 }; 174 protected final Node getLegend() { return legend.getValue(); } 175 protected final void setLegend(Node value) { legend.setValue(value); } 176 protected final ObjectProperty<Node> legendProperty() { return legend; } 177 178 /** 179 * When true the chart will display a legend if the chart implementation supports a legend. 180 */ 181 private final BooleanProperty legendVisible = new StyleableBooleanProperty(true) { 182 @Override protected void invalidated() { 183 requestLayout(); 184 } 185 186 @Override 187 public CssMetaData<Chart,Boolean> getCssMetaData() { 188 return StyleableProperties.LEGEND_VISIBLE; 189 } 190 191 @Override 192 public Object getBean() { 193 return Chart.this; 194 } 195 196 @Override 197 public String getName() { 198 return "legendVisible"; 199 } 200 }; 201 public final boolean isLegendVisible() { return legendVisible.getValue(); } 202 public final void setLegendVisible(boolean value) { legendVisible.setValue(value); } 203 public final BooleanProperty legendVisibleProperty() { return legendVisible; } 204 205 /** 206 * The side of the chart where the legend should be displayed 207 * 208 * @defaultValue Side.BOTTOM 209 */ 210 private ObjectProperty<Side> legendSide = new StyleableObjectProperty<Side>(Side.BOTTOM) { 211 @Override protected void invalidated() { 212 final Side legendSide = get(); 213 final Node legend = getLegend(); 214 if(legend instanceof Legend) ((Legend)legend).setVertical(Side.LEFT.equals(legendSide) || Side.RIGHT.equals(legendSide)); 215 requestLayout(); 216 } 217 218 @Override 219 public CssMetaData<Chart,Side> getCssMetaData() { 220 return StyleableProperties.LEGEND_SIDE; 221 } 222 223 @Override 224 public Object getBean() { 225 return Chart.this; 226 } 227 228 @Override 229 public String getName() { 230 return "legendSide"; 231 } 232 }; 233 public final Side getLegendSide() { return legendSide.get(); } 234 public final void setLegendSide(Side value) { legendSide.set(value); } 235 public final ObjectProperty<Side> legendSideProperty() { return legendSide; } 236 237 /** When true any data changes will be animated. */ 238 private BooleanProperty animated = new SimpleBooleanProperty(this, "animated", true); 239 240 /** 241 * Indicates whether data changes will be animated or not. 242 * 243 * @return true if data changes will be animated and false otherwise. 244 */ 245 public final boolean getAnimated() { return animated.get(); } 246 public final void setAnimated(boolean value) { animated.set(value); } 247 public final BooleanProperty animatedProperty() { return animated; } 248 249 // -------------- PROTECTED PROPERTIES ----------------------------------------------------------------------------- 250 251 /** 252 * Modifiable and observable list of all content in the chart. This is where implementations of Chart should add 253 * any nodes they use to draw their chart. This excludes the legend and title which are looked after by this class. 254 * 255 * @return Observable list of plot children 256 */ 257 protected ObservableList<Node> getChartChildren() { 258 return chartContent.getChildren(); 259 } 260 261 // -------------- CONSTRUCTOR -------------------------------------------------------------------------------------- 262 263 public Chart() { 264 titleLabel.setAlignment(Pos.CENTER); 265 getChildren().addAll(titleLabel, chartContent); 266 getStyleClass().add("chart"); 267 titleLabel.getStyleClass().add("chart-title"); 268 chartContent.getStyleClass().add("chart-content"); 269 // mark chartContent as unmanaged because any changes to its preferred size shouldn't cause a relayout 270 chartContent.setManaged(false); 271 } 272 273 // -------------- METHODS ------------------------------------------------------------------------------------------ 274 275 /** 276 * Play a animation involving the given keyframes. On every frame of the animation the chart will be relayed out 277 * 278 * @param keyFrames Array of KeyFrames to play 279 */ 280 void animate(KeyFrame...keyFrames) { animator.animate(keyFrames); } 281 282 /** 283 * Play the given animation on every frame of the animation the chart will be relayed out until the animation 284 * finishes. So to add a animation to a chart, create a animation on data model, during layoutChartContent() map 285 * data model to nodes then call this method with the animation. 286 * 287 * @param animation The animation to play 288 */ 289 protected void animate(Animation animation) { animator.animate(animation); } 290 291 /** Call this when you know something has changed that needs the chart to be relayed out. */ 292 protected void requestChartLayout() { 293 chartContent.requestLayout(); 294 } 295 296 /** 297 * This is used to check if any given animation should run. It returns true if animation is enabled and the node 298 * is visible and in a scene. 299 */ 300 protected final boolean shouldAnimate(){ 301 return getAnimated() && impl_isTreeVisible() && getScene() != null; 302 } 303 304 /** 305 * Called to update and layout the chart children available from getChartChildren() 306 * 307 * @param top The top offset from the origin to account for any padding on the chart content 308 * @param left The left offset from the origin to account for any padding on the chart content 309 * @param width The width of the area to layout the chart within 310 * @param height The height of the area to layout the chart within 311 */ 312 protected abstract void layoutChartChildren(double top, double left, double width, double height); 313 314 /** 315 * Invoked during the layout pass to layout this chart and all its content. 316 */ 317 @Override protected void layoutChildren() { 318 double top = snappedTopInset(); 319 double left = snappedLeftInset(); 320 double bottom = snappedBottomInset(); 321 double right = snappedRightInset(); 322 final double width = getWidth(); 323 final double height = getHeight(); 324 // layout title 325 if (getTitle() != null) { 326 titleLabel.setVisible(true); 327 if (getTitleSide().equals(Side.TOP)) { 328 final double titleHeight = snapSize(titleLabel.prefHeight(width-left-right)); 329 titleLabel.resizeRelocate(left,top,width-left-right,titleHeight); 330 top += titleHeight; 331 } else if (getTitleSide().equals(Side.BOTTOM)) { 332 final double titleHeight = snapSize(titleLabel.prefHeight(width-left-right)); 333 titleLabel.resizeRelocate(left,height-bottom-titleHeight,width-left-right,titleHeight); 334 bottom += titleHeight; 335 } else if (getTitleSide().equals(Side.LEFT)) { 336 final double titleWidth = snapSize(titleLabel.prefWidth(height-top-bottom)); 337 titleLabel.resizeRelocate(left,top,titleWidth,height-top-bottom); 338 left += titleWidth; 339 } else if (getTitleSide().equals(Side.RIGHT)) { 340 final double titleWidth = snapSize(titleLabel.prefWidth(height-top-bottom)); 341 titleLabel.resizeRelocate(width-right-titleWidth,top,titleWidth,height-top-bottom); 342 right += titleWidth; 343 } 344 } else { 345 titleLabel.setVisible(false); 346 } 347 // layout legend 348 final Node legend = getLegend(); 349 if (legend != null) { 350 boolean shouldShowLegend = isLegendVisible(); 351 if (shouldShowLegend) { 352 if (getLegendSide().equals(Side.TOP)) { 353 final double legendHeight = snapSize(legend.prefHeight(width-left-right)); 354 final double legendWidth = snapSize(legend.prefWidth(-1)); 355 legend.resizeRelocate(left + (((width - left - right)-legendWidth)/2), top, legendWidth, legendHeight); 356 if ((height - bottom - top - legendHeight) < MIN_HEIGHT_TO_LEAVE_FOR_CHART_CONTENT) { 357 shouldShowLegend = false; 358 } else { 359 top += legendHeight; 360 } 361 } else if (getLegendSide().equals(Side.BOTTOM)) { 362 final double legendHeight = snapSize(legend.prefHeight(width-left-right)); 363 final double legendWidth = snapSize(legend.prefWidth(-1)); 364 legend.resizeRelocate(left + (((width - left - right)-legendWidth)/2), height-bottom-legendHeight, legendWidth, legendHeight); 365 if ((height - bottom - top - legendHeight) < MIN_HEIGHT_TO_LEAVE_FOR_CHART_CONTENT) { 366 shouldShowLegend = false; 367 } else { 368 bottom += legendHeight; 369 } 370 } else if (getLegendSide().equals(Side.LEFT)) { 371 final double legendWidth = snapSize(legend.prefWidth(height-top-bottom)); 372 final double legendHeight = snapSize(legend.prefHeight(-1)); 373 legend.resizeRelocate(left,top +(((height-top-bottom)-legendHeight)/2),legendWidth,legendHeight); 374 if ((width - left - right - legendWidth) < MIN_WIDTH_TO_LEAVE_FOR_CHART_CONTENT) { 375 shouldShowLegend = false; 376 } else { 377 left += legendWidth; 378 } 379 } else if (getLegendSide().equals(Side.RIGHT)) { 380 final double legendWidth = snapSize(legend.prefWidth(height-top-bottom)); 381 final double legendHeight = snapSize(legend.prefHeight(-1)); 382 legend.resizeRelocate(width-right-legendWidth,top +(((height-top-bottom)-legendHeight)/2),legendWidth,legendHeight); 383 if ((width - left - right - legendWidth) < MIN_WIDTH_TO_LEAVE_FOR_CHART_CONTENT) { 384 shouldShowLegend = false; 385 } else { 386 right += legendWidth; 387 } 388 } 389 } 390 legend.setVisible(shouldShowLegend); 391 } 392 // whats left is for the chart content 393 chartContent.resizeRelocate(left,top,width-left-right,height-top-bottom); 394 } 395 396 /** 397 * Charts are sized outside in, user tells chart how much space it has and chart draws inside that. So minimum 398 * height is a constant 150. 399 */ 400 @Override protected double computeMinHeight(double width) { return 150; } 401 402 /** 403 * Charts are sized outside in, user tells chart how much space it has and chart draws inside that. So minimum 404 * width is a constant 200. 405 */ 406 @Override protected double computeMinWidth(double height) { return 200; } 407 408 /** 409 * Charts are sized outside in, user tells chart how much space it has and chart draws inside that. So preferred 410 * width is a constant 500. 411 */ 412 @Override protected double computePrefWidth(double height) { return 500.0; } 413 414 /** 415 * Charts are sized outside in, user tells chart how much space it has and chart draws inside that. So preferred 416 * height is a constant 400. 417 */ 418 @Override protected double computePrefHeight(double width) { return 400.0; } 419 420 // -------------- STYLESHEET HANDLING ------------------------------------------------------------------------------ 421 422 private static class StyleableProperties { 423 private static final CssMetaData<Chart,Side> TITLE_SIDE = 424 new CssMetaData<Chart,Side>("-fx-title-side", 425 new EnumConverter<Side>(Side.class), 426 Side.TOP) { 427 428 @Override 429 public boolean isSettable(Chart node) { 430 return node.titleSide == null || !node.titleSide.isBound(); 431 } 432 433 @Override 434 public StyleableProperty<Side> getStyleableProperty(Chart node) { 435 return (StyleableProperty<Side>)node.titleSideProperty(); 436 } 437 }; 438 439 private static final CssMetaData<Chart,Side> LEGEND_SIDE = 440 new CssMetaData<Chart,Side>("-fx-legend-side", 441 new EnumConverter<Side>(Side.class), 442 Side.BOTTOM) { 443 444 @Override 445 public boolean isSettable(Chart node) { 446 return node.legendSide == null || !node.legendSide.isBound(); 447 } 448 449 @Override 450 public StyleableProperty<Side> getStyleableProperty(Chart node) { 451 return (StyleableProperty<Side>)node.legendSideProperty(); 452 } 453 }; 454 455 private static final CssMetaData<Chart,Boolean> LEGEND_VISIBLE = 456 new CssMetaData<Chart,Boolean>("-fx-legend-visible", 457 BooleanConverter.getInstance(), Boolean.TRUE) { 458 459 @Override 460 public boolean isSettable(Chart node) { 461 return node.legendVisible == null || !node.legendVisible.isBound(); 462 } 463 464 @Override 465 public StyleableProperty<Boolean> getStyleableProperty(Chart node) { 466 return (StyleableProperty<Boolean>)node.legendVisibleProperty(); 467 } 468 }; 469 470 private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES; 471 static { 472 final List<CssMetaData<? extends Styleable, ?>> styleables = 473 new ArrayList<CssMetaData<? extends Styleable, ?>>(Region.getClassCssMetaData()); 474 styleables.add(TITLE_SIDE); 475 styleables.add(LEGEND_VISIBLE); 476 styleables.add(LEGEND_SIDE); 477 STYLEABLES = Collections.unmodifiableList(styleables); 478 } 479 } 480 481 /** 482 * @return The CssMetaData associated with this class, which may include the 483 * CssMetaData of its super classes. 484 */ 485 public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() { 486 return StyleableProperties.STYLEABLES; 487 } 488 489 /** 490 * {@inheritDoc} 491 */ 492 @Override 493 public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() { 494 return getClassCssMetaData(); 495 } 496 497} 498 499