comparison flys-artifacts/src/main/java/de/intevation/flys/exports/XYChartGenerator.java @ 3242:1dca41dba135

Move annotation code to base class ChartGenerator flys-artifacts/trunk@4874 c6561f87-3c4e-4783-a992-168aeb5c3f6f
author Christian Lins <christian.lins@intevation.de>
date Wed, 04 Jul 2012 22:28:44 +0000
parents ed07dd55f487
children f76cef888ee1
comparison
equal deleted inserted replaced
3241:da3e58694cae 3242:1dca41dba135
1 package de.intevation.flys.exports; 1 package de.intevation.flys.exports;
2 2
3 import java.awt.BasicStroke;
4 import java.awt.Color; 3 import java.awt.Color;
5 import java.awt.Font; 4 import java.awt.Font;
6 import java.awt.Paint;
7 import java.awt.Stroke;
8
9 import java.text.NumberFormat; 5 import java.text.NumberFormat;
10
11 import java.util.ArrayList; 6 import java.util.ArrayList;
12 import java.util.HashMap; 7 import java.util.HashMap;
13 import java.util.List; 8 import java.util.List;
14 import java.util.Map; 9 import java.util.Map;
15 10
16 import org.w3c.dom.Document;
17
18 import org.apache.log4j.Logger; 11 import org.apache.log4j.Logger;
19
20 import org.jfree.chart.ChartFactory; 12 import org.jfree.chart.ChartFactory;
21 import org.jfree.chart.JFreeChart; 13 import org.jfree.chart.JFreeChart;
22 import org.jfree.chart.LegendItem; 14 import org.jfree.chart.LegendItem;
23 import org.jfree.chart.LegendItemCollection;
24 import org.jfree.chart.annotations.XYBoxAnnotation;
25 import org.jfree.chart.annotations.XYLineAnnotation;
26 import org.jfree.chart.annotations.XYTextAnnotation; 15 import org.jfree.chart.annotations.XYTextAnnotation;
27 import org.jfree.chart.axis.NumberAxis; 16 import org.jfree.chart.axis.NumberAxis;
28 import org.jfree.chart.axis.ValueAxis; 17 import org.jfree.chart.axis.ValueAxis;
29 import org.jfree.chart.plot.Marker; 18 import org.jfree.chart.plot.Marker;
30 import org.jfree.chart.plot.PlotOrientation; 19 import org.jfree.chart.plot.PlotOrientation;
31 import org.jfree.chart.plot.XYPlot; 20 import org.jfree.chart.plot.XYPlot;
32 import org.jfree.data.Range; 21 import org.jfree.data.Range;
33 import org.jfree.data.general.Series; 22 import org.jfree.data.general.Series;
23 import org.jfree.data.xy.XYDataset;
34 import org.jfree.data.xy.XYSeries; 24 import org.jfree.data.xy.XYSeries;
35 import org.jfree.data.xy.XYSeriesCollection; 25 import org.jfree.data.xy.XYSeriesCollection;
36 import org.jfree.data.xy.XYDataset; 26 import org.json.JSONArray;
37 27 import org.json.JSONException;
38 import org.jfree.ui.TextAnchor; 28 import org.w3c.dom.Document;
39 29
40 import de.intevation.artifactdatabase.state.ArtifactAndFacet; 30 import de.intevation.artifactdatabase.state.ArtifactAndFacet;
41 import de.intevation.artifactdatabase.state.Facet;
42
43 import de.intevation.flys.jfree.Bounds; 31 import de.intevation.flys.jfree.Bounds;
32 import de.intevation.flys.jfree.CollisionFreeXYTextAnnotation;
44 import de.intevation.flys.jfree.DoubleBounds; 33 import de.intevation.flys.jfree.DoubleBounds;
45 import de.intevation.flys.jfree.FLYSAnnotation; 34 import de.intevation.flys.jfree.FLYSAnnotation;
46 import de.intevation.flys.jfree.StickyAxisAnnotation;
47 import de.intevation.flys.jfree.CollisionFreeXYTextAnnotation;
48 import de.intevation.flys.jfree.StyledAreaSeriesCollection; 35 import de.intevation.flys.jfree.StyledAreaSeriesCollection;
49 import de.intevation.flys.jfree.StyledXYSeries; 36 import de.intevation.flys.jfree.StyledXYSeries;
50
51 import de.intevation.flys.themes.ThemeAccess;
52 import de.intevation.flys.utils.ThemeUtil;
53
54 import de.intevation.flys.artifacts.model.HYKFactory;
55
56 import org.json.JSONArray;
57 import org.json.JSONException;
58 37
59 38
60 /** 39 /**
61 * An abstract base class for creating XY charts. 40 * An abstract base class for creating XY charts.
62 * 41 *
72 * 51 *
73 * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a> 52 * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
74 */ 53 */
75 public abstract class XYChartGenerator extends ChartGenerator { 54 public abstract class XYChartGenerator extends ChartGenerator {
76 55
77 // TODO Consider storing the renderer here. 56 public class XYAxisDataset implements AxisDataset {
78 private class XYAxisDataset implements AxisDataset {
79 /** Symbolic integer, but also coding the priority (0 goes first). */ 57 /** Symbolic integer, but also coding the priority (0 goes first). */
80 protected int axisSymbol; 58 protected int axisSymbol;
81 /** List of assigned datasets (in order). */ 59 /** List of assigned datasets (in order). */
82 protected List<XYDataset> datasets; 60 protected List<XYDataset> datasets;
83 /** Range to use to include all given datasets. */ 61 /** Range to use to include all given datasets. */
178 } 156 }
179 } // class AxisDataset 157 } // class AxisDataset
180 158
181 /** Enumerator over existing axes. */ 159 /** Enumerator over existing axes. */
182 protected abstract YAxisWalker getYAxisWalker(); 160 protected abstract YAxisWalker getYAxisWalker();
183
184 protected static float ANNOTATIONS_AXIS_OFFSET = 0.02f;
185 161
186 public static final int AXIS_SPACE = 5; 162 public static final int AXIS_SPACE = 5;
187 163
188 /** The logger that is used in this generator. */ 164 /** The logger that is used in this generator. */
189 private static Logger logger = Logger.getLogger(XYChartGenerator.class); 165 private static Logger logger = Logger.getLogger(XYChartGenerator.class);
716 protected void setYBounds(int axis, Bounds bounds) { 692 protected void setYBounds(int axis, Bounds bounds) {
717 yBounds.put(axis, bounds); 693 yBounds.put(axis, bounds);
718 } 694 }
719 695
720 696
721 /** Get color for hyk zones by their type (which is the name). */
722 public Paint colorForHYKZone(String zoneName) {
723 if (zoneName.startsWith("R")) {
724 // Brownish.
725 return new Color(153, 60, 0);
726 }
727 else if (zoneName.startsWith("V")) {
728 // Greenish.
729 return new Color(0, 255, 0);
730 }
731 else if (zoneName.startsWith("B")) {
732 // Grayish.
733 return new Color(128, 128, 128);
734 }
735 else if (zoneName.startsWith("H")) {
736 // Blueish.
737 return new Color(0, 0, 255);
738 }
739 else {
740 // Default.
741 logger.debug("Unknown zone type found.");
742 return new Color(255, 0, 0);
743 }
744 }
745
746
747 /**
748 * Create annotation that sticks to "ground" (X) axis.
749 * @param area helper to calculate coordinates
750 * @param pos one-dimensional position (distance from axis)
751 * @param lineStyle the line style to use for the line.
752 */
753 protected static XYLineAnnotation createGroundStickAnnotation(
754 Area area, float pos, ThemeAccess.LineStyle lineStyle
755 ) {
756 // Style the line.
757 if (lineStyle != null) {
758 return new XYLineAnnotation(
759 pos, area.atGround(),
760 pos, area.ofGround(ANNOTATIONS_AXIS_OFFSET),
761 new BasicStroke(lineStyle.getWidth()),lineStyle.getColor());
762 }
763 else {
764 return new XYLineAnnotation(
765 pos, area.atGround(),
766 pos, area.ofGround(ANNOTATIONS_AXIS_OFFSET));
767 }
768 }
769
770
771 /**
772 * Create annotation that sticks to the second Y axis ("right").
773 * @param area helper to calculate coordinates
774 * @param pos one-dimensional position (distance from axis)
775 * @param lineStyle the line style to use for the line.
776 */
777 protected static XYLineAnnotation createRightStickAnnotation(
778 Area area, float pos, ThemeAccess.LineStyle lineStyle
779 ) {
780 // Style the line.
781 if (lineStyle != null) {
782 return new XYLineAnnotation(
783 area.ofRight(ANNOTATIONS_AXIS_OFFSET), pos,
784 area.atRight(), pos,
785 new BasicStroke(lineStyle.getWidth()), lineStyle.getColor());
786 }
787 else {
788 return new XYLineAnnotation(
789 area.atRight(), pos,
790 area.ofRight(ANNOTATIONS_AXIS_OFFSET), pos);
791 }
792 }
793
794
795 /**
796 * Create annotation that sticks to the first Y axis ("left").
797 * @param area helper to calculate coordinates
798 * @param pos one-dimensional position (distance from axis)
799 * @param lineStyle the line style to use for the line.
800 */
801 protected static XYLineAnnotation createLeftStickAnnotation(
802 Area area, float pos, ThemeAccess.LineStyle lineStyle
803 ) {
804 // Style the line.
805 if (lineStyle != null) {
806 return new XYLineAnnotation(
807 area.atLeft(), pos,
808 area.ofLeft(ANNOTATIONS_AXIS_OFFSET), pos,
809 new BasicStroke(lineStyle.getWidth()), lineStyle.getColor());
810 }
811 else {
812 return new XYLineAnnotation(
813 area.atLeft(), pos,
814 area.ofLeft(ANNOTATIONS_AXIS_OFFSET), pos);
815 }
816 }
817
818
819 /**
820 * Create a line from a axis to a given point.
821 * @param axis The "simple" axis.
822 * @param fromD1 from-location in first dimension.
823 * @param toD2 to-location in second dimension.
824 * @param area helper to calculate offsets.
825 * @param lineStyle optional line style.
826 */
827 protected static XYLineAnnotation createStickyLineAnnotation(
828 StickyAxisAnnotation.SimpleAxis axis, float fromD1, float toD2,
829 Area area, ThemeAccess.LineStyle lineStyle
830 ) {
831 double anchorX1 = 0d, anchorX2 = 0d, anchorY1 = 0d, anchorY2 = 0d;
832 switch(axis) {
833 case X_AXIS:
834 anchorX1 = fromD1;
835 anchorX2 = fromD1;
836 anchorY1 = area.atGround();
837 anchorY2 = toD2;
838 break;
839 case Y_AXIS:
840 anchorX1 = area.atLeft();
841 anchorX2 = toD2;
842 anchorY1 = fromD1;
843 anchorY2 = fromD1;
844 break;
845 case Y_AXIS2:
846 anchorX1 = area.atRight();
847 anchorX2 = toD2;
848 anchorY1 = fromD1;
849 anchorY2 = fromD1;
850 break;
851 }
852 // Style the line.
853 if (lineStyle != null) {
854 return new XYLineAnnotation(
855 anchorX1, anchorY1,
856 anchorX2, anchorY2,
857 new BasicStroke(lineStyle.getWidth()), lineStyle.getColor());
858 }
859 else {
860 return new XYLineAnnotation(
861 anchorX1, anchorY1,
862 anchorX2, anchorY2);
863 }
864 }
865
866
867 /**
868 * Add a text and a line annotation.
869 * @param area convenience to determine positions in plot.
870 * @param theme (optional) theme document
871 */
872 public void addStickyAnnotation(
873 StickyAxisAnnotation annotation,
874 XYPlot plot,
875 Area area,
876 ThemeAccess.LineStyle lineStyle,
877 ThemeAccess.TextStyle textStyle,
878 Document theme
879 ) {
880 // OPTIMIZE pre-calculate area-related values
881 final float TEXT_OFF = 0.03f;
882
883 XYLineAnnotation lineAnnotation = null;
884 XYTextAnnotation textAnnotation = null;
885
886 int rendererIndex = 0;
887
888 if (annotation.atX()) {
889 textAnnotation = new CollisionFreeXYTextAnnotation(
890 annotation.getText(), annotation.getPos(), area.ofGround(TEXT_OFF));
891 // OPTIMIZE externalize the calculation involving PI.
892 //textAnnotation.setRotationAngle(270f*Math.PI/180f);
893 lineAnnotation = createGroundStickAnnotation(
894 area, annotation.getPos(), lineStyle);
895 textAnnotation.setRotationAnchor(TextAnchor.CENTER_LEFT);
896 textAnnotation.setTextAnchor(TextAnchor.CENTER_LEFT);
897 }
898 else {
899 // Do the more complicated case where we stick to the Y-Axis.
900 // There is one nasty case (duration curves, where annotations
901 // might stick to the second y-axis).
902 XYAxisDataset dataset = (XYAxisDataset) getAxisDataset(
903 new Integer(annotation.getAxisSymbol()));
904 if (dataset == null) {
905 logger.warn("Annotation should stick to unfindable y-axis: "
906 + annotation.getAxisSymbol());
907 rendererIndex = 0;
908 }
909 else {
910 rendererIndex = dataset.getPlotAxisIndex();
911 }
912
913 // Stick to the "right" (opposed to left) Y-Axis.
914 if (rendererIndex != 0) {
915 // OPTIMIZE: Pass a different area to this function,
916 // do the adding to renderer outside (let this
917 // function return the annotations).
918 // Note that this path is travelled rarely.
919 Area area2 = new Area(plot.getDomainAxis(), plot.getRangeAxis(rendererIndex));
920 textAnnotation = new CollisionFreeXYTextAnnotation(
921 annotation.getText(), area2.ofRight(TEXT_OFF), annotation.getPos());
922 textAnnotation.setRotationAnchor(TextAnchor.CENTER_RIGHT);
923 textAnnotation.setTextAnchor(TextAnchor.CENTER_RIGHT);
924 lineAnnotation = createRightStickAnnotation(
925 area2, annotation.getPos(), lineStyle);
926 if (!Float.isNaN(annotation.getHitPoint()) && theme != null) {
927 // New line annotation to hit curve.
928 if (ThemeUtil.parseShowVerticalLine(theme)) {
929 XYLineAnnotation hitLineAnnotation =
930 createStickyLineAnnotation(
931 StickyAxisAnnotation.SimpleAxis.X_AXIS,
932 annotation.getHitPoint(), annotation.getPos(),// annotation.getHitPoint(),
933 area2, lineStyle);
934 plot.getRenderer(rendererIndex).addAnnotation(hitLineAnnotation,
935 org.jfree.ui.Layer.BACKGROUND);
936 }
937 if (ThemeUtil.parseShowHorizontalLine(theme)) {
938 XYLineAnnotation lineBackAnnotation =
939 createStickyLineAnnotation(
940 StickyAxisAnnotation.SimpleAxis.Y_AXIS2,
941 annotation.getPos(), annotation.getHitPoint(),
942 area2, lineStyle);
943 plot.getRenderer(rendererIndex).addAnnotation(lineBackAnnotation,
944 org.jfree.ui.Layer.BACKGROUND);
945 }
946 }
947 }
948 else { // Stick to the left y-axis.
949 textAnnotation = new CollisionFreeXYTextAnnotation(
950 annotation.getText(), area.ofLeft(TEXT_OFF), annotation.getPos());
951 textAnnotation.setRotationAnchor(TextAnchor.CENTER_LEFT);
952 textAnnotation.setTextAnchor(TextAnchor.CENTER_LEFT);
953 lineAnnotation = createLeftStickAnnotation(area, annotation.getPos(), lineStyle);
954 if (!Float.isNaN(annotation.getHitPoint()) && theme != null) {
955 // New line annotation to hit curve.
956 if (ThemeUtil.parseShowHorizontalLine(theme)) {
957 XYLineAnnotation hitLineAnnotation =
958 createStickyLineAnnotation(
959 StickyAxisAnnotation.SimpleAxis.Y_AXIS,
960 annotation.getPos(), annotation.getHitPoint(),
961 area, lineStyle);
962 plot.getRenderer(rendererIndex).addAnnotation(hitLineAnnotation,
963 org.jfree.ui.Layer.BACKGROUND);
964 }
965 if (ThemeUtil.parseShowVerticalLine(theme)) {
966 XYLineAnnotation lineBackAnnotation =
967 createStickyLineAnnotation(
968 StickyAxisAnnotation.SimpleAxis.X_AXIS,
969 annotation.getHitPoint(), annotation.getPos(),
970 area, lineStyle);
971 plot.getRenderer(rendererIndex).addAnnotation(lineBackAnnotation,
972 org.jfree.ui.Layer.BACKGROUND);
973 }
974 }
975 }
976 }
977
978 // Style the text.
979 if (textStyle != null) {
980 textStyle.apply(textAnnotation);
981 }
982
983 // Add the Annotations to renderer.
984 plot.getRenderer(rendererIndex).addAnnotation(textAnnotation,
985 org.jfree.ui.Layer.FOREGROUND);
986 plot.getRenderer(rendererIndex).addAnnotation(lineAnnotation,
987 org.jfree.ui.Layer.FOREGROUND);
988 }
989
990
991 /**
992 * Add the annotations (Sticky, Text and hyk zones) stored
993 * in the annotations field.
994 */
995 public void addAnnotationsToRenderer(XYPlot plot) {
996 logger.debug("addAnnotationsToRenderer");
997
998 if (annotations == null) {
999 logger.debug("addAnnotationsToRenderer: no annotations.");
1000 return;
1001 }
1002
1003 // Paints for the boxes/lines.
1004 Stroke basicStroke = new BasicStroke(1.0f);
1005
1006 Paint linePaint = new Color(255, 0,0,60);
1007 Paint fillPaint = new Color(0, 255,0,60);
1008 Paint tranPaint = new Color(0, 0,0, 0);
1009
1010 // OPTMIMIZE: Pre-calculate positions
1011 Area area = new Area(
1012 plot.getDomainAxis(0).getRange(),
1013 plot.getRangeAxis().getRange());
1014
1015 // Walk over all Annotation sets.
1016 for (FLYSAnnotation fa: annotations) {
1017
1018 // Access text styling, if any.
1019 Document theme = fa.getTheme();
1020 ThemeAccess.TextStyle textStyle = null;
1021 ThemeAccess.LineStyle lineStyle = null;
1022
1023 // Get Themeing information and add legend item.
1024 if (theme != null) {
1025 ThemeAccess themeAccess = new ThemeAccess(theme);
1026 textStyle = themeAccess.parseTextStyle();
1027 lineStyle = themeAccess.parseLineStyle();
1028 if (fa.getLabel() != null) {
1029 LegendItemCollection lic = new LegendItemCollection();
1030 LegendItemCollection old = plot.getFixedLegendItems();
1031 lic.add(createLegendItem(theme, fa.getLabel()));
1032 // (Re-)Add prior legend entries.
1033 if (old != null) {
1034 old.addAll(lic);
1035 }
1036 else {
1037 old = lic;
1038 }
1039 plot.setFixedLegendItems(old);
1040 }
1041 }
1042
1043 // The 'Sticky' Annotations (at axis, with line and text).
1044 for (StickyAxisAnnotation sta: fa.getAxisTextAnnotations()) {
1045 addStickyAnnotation(
1046 sta, plot, area, lineStyle, textStyle, theme);
1047 }
1048
1049 // Other Text Annotations (e.g. labels of manual points).
1050 for (XYTextAnnotation ta: fa.getTextAnnotations()) {
1051 // Style the text.
1052 if (textStyle != null) {
1053 textStyle.apply(ta);
1054 }
1055 ta.setY(area.above(0.05d, ta.getY()));
1056 plot.getRenderer().addAnnotation(ta, org.jfree.ui.Layer.FOREGROUND);
1057 }
1058
1059 // Hyks.
1060 for (HYKFactory.Zone zone: fa.getBoxes()) {
1061 // For each zone, create a box to fill with color, a box to draw
1062 // the lines and a text to display the type.
1063 fillPaint = colorForHYKZone(zone.getName());
1064
1065 XYBoxAnnotation boxA = new XYBoxAnnotation(zone.getFrom(), area.atGround(),
1066 zone.getTo(), area.ofGround(0.03f), basicStroke, tranPaint, fillPaint);
1067 XYBoxAnnotation boxB = new XYBoxAnnotation(zone.getFrom(), area.atGround(),
1068 zone.getTo(), area.atTop(), basicStroke, fillPaint, tranPaint);
1069
1070 XYTextAnnotation tex = new XYTextAnnotation(zone.getName(),
1071 zone.getFrom() + (zone.getTo() - zone.getFrom()) / 1.0d,
1072 area.ofGround(0.015f));
1073 if (textStyle != null) {
1074 textStyle.apply(tex);
1075 }
1076
1077 plot.getRenderer().addAnnotation(boxA, org.jfree.ui.Layer.BACKGROUND);
1078 plot.getRenderer().addAnnotation(boxB, org.jfree.ui.Layer.BACKGROUND);
1079 plot.getRenderer().addAnnotation(tex, org.jfree.ui.Layer.BACKGROUND);
1080 }
1081 }
1082 }
1083
1084
1085 /** 697 /**
1086 * Adjusts the axes of a plot. This method sets the <i>labelFont</i> of the 698 * Adjusts the axes of a plot. This method sets the <i>labelFont</i> of the
1087 * X axis. 699 * X axis.
1088 * 700 *
1089 * @param plot The XYPlot of the chart. 701 * @param plot The XYPlot of the chart.
1157 * @param domainAxis The domain axis that needs localization. 769 * @param domainAxis The domain axis that needs localization.
1158 */ 770 */
1159 protected void localizeRangeAxis(ValueAxis rangeAxis) { 771 protected void localizeRangeAxis(ValueAxis rangeAxis) {
1160 NumberFormat nf = NumberFormat.getInstance(getLocale()); 772 NumberFormat nf = NumberFormat.getInstance(getLocale());
1161 ((NumberAxis) rangeAxis).setNumberFormatOverride(nf); 773 ((NumberAxis) rangeAxis).setNumberFormatOverride(nf);
1162 }
1163
1164
1165 /**
1166 * Register annotations like MainValues for later plotting
1167 *
1168 * @param annotations list of annotations (data of facet).
1169 * @param aandf Artifact and the facet.
1170 * @param theme Theme document for given annotations.
1171 * @param visible The visibility of the annotations.
1172 */
1173 protected void doAnnotations(
1174 FLYSAnnotation annotations,
1175 ArtifactAndFacet aandf,
1176 Document theme,
1177 boolean visible
1178 ){
1179 // Running into trouble here.
1180 logger.debug("doAnnotations");
1181
1182 // Add all annotations to our annotation pool.
1183 annotations.setTheme(theme);
1184 if (aandf != null) {
1185 Facet facet = aandf.getFacet();
1186 annotations.setLabel(aandf.getFacetDescription());
1187 }
1188 else {
1189 logger.debug(
1190 "Art/Facet for Annotations is null. " +
1191 "This should never happen!");
1192 }
1193
1194 if (visible) {
1195 addAnnotations(annotations);
1196 }
1197 } 774 }
1198 775
1199 776
1200 /** 777 /**
1201 * Do Points out. 778 * Do Points out.
1276 // WQ.java holds example of using regex Matcher/Pattern. 853 // WQ.java holds example of using regex Matcher/Pattern.
1277 854
1278 return hash; 855 return hash;
1279 } 856 }
1280 857
1281
1282 /** Two Ranges that span a rectangular area. */
1283 public static class Area {
1284 protected Range xRange;
1285 protected Range yRange;
1286
1287 public Area(Range rangeX, Range rangeY) {
1288 this.xRange = rangeX;
1289 this.yRange = rangeY;
1290 }
1291
1292 public Area(ValueAxis axisX, ValueAxis axisY) {
1293 this.xRange = axisX.getRange();
1294 this.yRange = axisY.getRange();
1295 }
1296
1297 public double ofLeft(double percent) {
1298 return xRange.getLowerBound()
1299 + xRange.getLength() * percent;
1300 }
1301
1302 public double ofRight(double percent) {
1303 return xRange.getUpperBound()
1304 - xRange.getLength() * percent;
1305 }
1306
1307 public double ofGround(double percent) {
1308 return yRange.getLowerBound()
1309 + yRange.getLength() * percent;
1310 }
1311
1312 public double atTop() {
1313 return yRange.getUpperBound();
1314 }
1315
1316 public double atGround() {
1317 return yRange.getLowerBound();
1318 }
1319
1320 public double atRight() {
1321 return xRange.getUpperBound();
1322 }
1323
1324 public double atLeft() {
1325 return xRange.getLowerBound();
1326 }
1327
1328 public double above(double percent, double base) {
1329 return base + yRange.getLength() * percent;
1330 }
1331 }
1332 } 858 }
1333 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 : 859 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org