1/******************************************************************************* 2 * Copyright (c) 2000, 2009 IBM Corporation and others. 3 * All rights reserved. This program and the accompanying materials 4 * are made available under the terms of the Eclipse Public License v1.0 5 * which accompanies this distribution, and is available at 6 * http://www.eclipse.org/legal/epl-v10.html 7 * 8 * Contributors: 9 * IBM Corporation - initial API and implementation 10 *******************************************************************************/ 11package org.eclipse.test.performance.ui; 12 13import java.io.File; 14import java.io.PrintStream; 15import java.text.NumberFormat; 16import java.util.ArrayList; 17import java.util.HashMap; 18import java.util.Iterator; 19import java.util.List; 20import java.util.Map; 21 22import org.eclipse.swt.SWT; 23import org.eclipse.swt.graphics.Color; 24import org.eclipse.swt.graphics.Font; 25import org.eclipse.swt.graphics.FontData; 26import org.eclipse.swt.graphics.GC; 27import org.eclipse.swt.graphics.Image; 28import org.eclipse.swt.graphics.ImageData; 29import org.eclipse.swt.graphics.Point; 30import org.eclipse.swt.graphics.Rectangle; 31import org.eclipse.swt.graphics.Resource; 32import org.eclipse.swt.widgets.Display; 33import org.eclipse.test.internal.performance.results.db.BuildResults; 34import org.eclipse.test.internal.performance.results.db.ConfigResults; 35import org.eclipse.test.internal.performance.results.db.DB_Results; 36import org.eclipse.test.internal.performance.results.db.ScenarioResults; 37import org.eclipse.test.internal.performance.results.utils.Util; 38 39/** 40 * Abstract class to build graph with bars 41 */ 42public class FingerPrintGraph { 43 44 // Sizes 45 static final int MARGIN= 5; // margin on all four sides 46 static final int BAR_HEIGHT= 6; // height of bar 47 static final int GAP= 10; // gap between bars 48 static final int TGAP= 5; // gap between lines and labels 49 static final int LINE_HEIGHT = 2*BAR_HEIGHT + GAP; 50 51 // fraction of width reserved for bar graph 52 static final double RATIO= 0.6; 53 54 // Formatting constants 55 static final NumberFormat NUMBER_FORMAT; 56 static { 57 NUMBER_FORMAT = NumberFormat.getInstance(); 58 NUMBER_FORMAT.setMaximumFractionDigits(1); 59 } 60 61 // Graphic constants 62 static final Display DEFAULT_DISPLAY = Display.getDefault(); 63 static final Color BLACK= DEFAULT_DISPLAY.getSystemColor(SWT.COLOR_BLACK); 64 static final Color BLUE= DEFAULT_DISPLAY.getSystemColor(SWT.COLOR_BLUE); 65 static final Color GREEN= DEFAULT_DISPLAY.getSystemColor(SWT.COLOR_GREEN); 66 static final Color RED = DEFAULT_DISPLAY.getSystemColor(SWT.COLOR_RED); 67 static final Color GRAY = DEFAULT_DISPLAY.getSystemColor(SWT.COLOR_GRAY); 68 static final Color DARK_GRAY = DEFAULT_DISPLAY.getSystemColor(SWT.COLOR_DARK_GRAY); 69 static final Color YELLOW = DEFAULT_DISPLAY.getSystemColor(SWT.COLOR_YELLOW); 70 static final Color WHITE = DEFAULT_DISPLAY.getSystemColor(SWT.COLOR_WHITE); 71 72 // Bar graph kinds 73 static final int NO_TIME = 0; // i.e. percentage 74 static final int TIME_LINEAR = 1; 75 static final int TIME_LOG = 2; 76 static final int[] SUPPORTED_GRAPHS = { 77// NO_TIME, 78 TIME_LINEAR, 79 TIME_LOG, 80 }; 81 82 // Graphic fields 83 GC gc; 84 Image image; 85 int imageWidth; 86 int imageHeight; 87 int graphWidth; 88 int graphHeight; 89 Map resources = new HashMap(); 90 91 // Data fields 92 int count = 0; 93 ConfigResults[] results = new ConfigResults[10]; 94 BarGraphArea[] areas; 95 96 // Values 97 double maxValue = 0.0; 98 double minValue = Double.MAX_VALUE; 99 100 // File info 101 File outputDir; 102 String imageName; 103 private final String defaultDimName = DB_Results.getDefaultDimension().getName(); 104 105 /* 106 * Member class defining a bar graph area. 107 * This area applies to a configuration results and is made of several zones. 108 */ 109 class BarGraphArea { 110 List zones; 111 private ConfigResults configResults; 112 113 /* 114 * Member class defining a zone inside a bar graph area. 115 * Typically made of a rectangle and an associated text used as tooltip. 116 */ 117 class AreaZone { 118 Rectangle zone; 119 String title; 120 121 AreaZone(Rectangle zone, String tooltip) { 122 super(); 123 this.zone = zone; 124 this.title = tooltip; 125 } 126 127 void print(String url, PrintStream stream) { 128 stream.print(" echo '<area shape=\"RECT\""); 129 if (this.title != null) { 130 stream.print(" title=\""+this.title+"\""); 131 } 132 stream.print("coords=\""); 133 stream.print(this.zone.x); 134 stream.print(','); 135 stream.print(this.zone.y); 136 stream.print(','); 137 stream.print(this.zone.x+this.zone.width); 138 stream.print(','); 139 stream.print(this.zone.y+this.zone.height); 140 stream.print('"'); 141 if (url != null) { 142 stream.print(" href=\""); 143 stream.print(url); 144 stream.print('"'); 145 } 146 stream.print(">';\n"); 147 } 148 } 149 150 BarGraphArea(ConfigResults results) { 151 this.configResults = results; 152 this.zones = new ArrayList(); 153 } 154 155 void print(PrintStream stream) { 156 String url = this.configResults.getName() + "/" + ((ScenarioResults) this.configResults.getParent()).getFileName() + ".html"; 157 int size = this.zones.size(); 158 for (int i=0; i<size; i++) { 159 AreaZone zone = (AreaZone) this.zones.get(i); 160 zone.print(url, stream); 161 } 162 } 163 164 void addArea(Rectangle rec, String tooltip) { 165 AreaZone zone = new AreaZone(rec, tooltip); 166 this.zones.add(zone); 167 } 168 169 } 170 171 172FingerPrintGraph(File dir, String fileName, int width, List results) { 173 super(); 174 this.imageWidth = width; 175 this.count = results.size(); 176 this.results = new ConfigResults[this.count]; 177 results.toArray(this.results); 178 this.outputDir = dir; 179 this.imageName = fileName; 180} 181 182/** 183 */ 184void drawBars(int kind) { 185 186 // Get/Set graphical resources 187 Font italicFont = (Font) this.resources.get("italicFont"); 188 if (italicFont == null) { 189 String fontDataName = this.gc.getFont().getFontData()[0].toString(); 190 FontData fdItalic = new FontData(fontDataName); 191 fdItalic.setStyle(SWT.ITALIC); 192 italicFont = new Font(DEFAULT_DISPLAY, fdItalic); 193 this.resources.put("italicFont", italicFont); 194 } 195 Color blueref = (Color) this.resources.get("blueref"); 196 if (blueref == null) { 197 blueref = new Color(DEFAULT_DISPLAY, 200, 200, 255); 198 this.resources.put("blueref", blueref); 199 } 200 Color lightyellow= (Color) this.resources.get("lightyellow"); 201 if (lightyellow == null) { 202 lightyellow = new Color(DEFAULT_DISPLAY, 255, 255, 160); 203 this.resources.put("lightyellow", lightyellow); 204 } 205 Color darkyellow= (Color) this.resources.get("darkyellow"); 206 if (darkyellow == null) { 207 darkyellow = new Color(DEFAULT_DISPLAY, 160, 160, 0); 208 this.resources.put("darkyellow", darkyellow); 209 } 210 Color okColor= (Color) this.resources.get("lightgreen"); 211 if (okColor == null) { 212 okColor = new Color(DEFAULT_DISPLAY, 95, 191, 95); 213 this.resources.put("lightgreen", okColor); 214 } 215 Color failureColor = (Color) this.resources.get("lightred"); 216 if (failureColor == null) { 217 failureColor = new Color(DEFAULT_DISPLAY, 220, 50, 50); 218 this.resources.put("lightred", failureColor); 219 } 220 221 // Build each scenario bar graph 222 this.areas = new BarGraphArea[this.count]; 223 double max = kind == TIME_LOG ? Math.log(this.maxValue) : this.maxValue; 224 for (int i=0, y=MARGIN; i < this.count; i++, y+=LINE_HEIGHT) { 225 226 // get builds info 227 ConfigResults configResults = this.results[i]; 228 this.areas[i] = new BarGraphArea(configResults); 229 BarGraphArea graphArea = this.areas[i]; 230 BuildResults currentBuildResults = configResults.getCurrentBuildResults(); 231 double currentValue = currentBuildResults.getValue(); 232 double currentError = currentBuildResults.getError(); 233 double error = configResults.getError(); 234 boolean singleTest = Double.isNaN(error); 235 boolean isSignificant = singleTest || error < Utils.STANDARD_ERROR_THRESHOLD; 236 boolean isCommented = currentBuildResults.getComment() != null; 237 BuildResults baselineBuildResults = configResults.getBaselineBuildResults(); 238 double baselineValue = baselineBuildResults.getValue(); 239 double baselineError = baselineBuildResults.getError(); 240 241 // draw baseline build bar 242 Color whiteref = (Color) this.resources.get("whiteref"); 243 if (whiteref == null) { 244 whiteref = new Color(DEFAULT_DISPLAY, 240, 240, 248); 245 this.resources.put("whiteref", whiteref); 246 } 247 this.gc.setBackground(whiteref); 248 double baselineGraphValue = kind == TIME_LOG ? Math.log(baselineValue) : baselineValue; 249 int baselineBarLength= (int) (baselineGraphValue / max * this.graphWidth); 250 int baselineErrorLength= (int) (baselineError / max * this.graphWidth / 2); 251 int labelxpos = MARGIN + baselineBarLength; 252 if (kind == TIME_LOG || baselineErrorLength <= 1) { 253 this.gc.fillRectangle(MARGIN, y + (GAP/2), baselineBarLength, BAR_HEIGHT); 254 Rectangle rec = new Rectangle(MARGIN, y + (GAP/2), baselineBarLength, BAR_HEIGHT); 255 this.gc.drawRectangle(rec); 256 graphArea.addArea(rec, "Time for baseline build "+baselineBuildResults.getName()+": "+Util.timeString((long)baselineValue)); 257 } else { 258 int wr = baselineBarLength - baselineErrorLength; 259 Rectangle recValue = new Rectangle(MARGIN, y + (GAP/2), wr, BAR_HEIGHT); 260 this.gc.fillRectangle(recValue); 261 this.gc.setBackground(YELLOW); 262 Rectangle recError = new Rectangle(MARGIN+wr, y + (GAP/2), baselineErrorLength*2, BAR_HEIGHT); 263 this.gc.fillRectangle(recError); 264 Rectangle rec = new Rectangle(MARGIN, y + (GAP/2), baselineBarLength+baselineErrorLength, BAR_HEIGHT); 265 this.gc.drawRectangle(rec); 266 StringBuffer tooltip = new StringBuffer("Time for baseline build "); 267 tooltip.append(baselineBuildResults.getName()); 268 tooltip.append(": "); 269 tooltip.append(Util.timeString((long)baselineValue)); 270 tooltip.append(" [±"); 271 tooltip.append(Util.timeString((long)baselineError)); 272 tooltip.append(']'); 273 graphArea.addArea(rec, tooltip.toString()); 274 labelxpos += baselineErrorLength; 275 } 276 277 // set current build bar color 278 if (baselineValue < currentValue) { 279 if (isCommented) { 280 this.gc.setBackground(GRAY); 281 } else { 282 this.gc.setBackground(failureColor); 283 } 284 } else { 285 this.gc.setBackground(okColor); 286 } 287 288 // draw current build bar 289 double currentGraphValue = kind == TIME_LOG ? Math.log(currentValue) : currentValue; 290 int currentBarLength= (int) (currentGraphValue / max * this.graphWidth); 291 int currentErrorLength= (int) (currentError / max * this.graphWidth / 2); 292 if (kind == TIME_LOG || currentErrorLength <= 1) { 293 this.gc.fillRectangle(MARGIN, y + (GAP/2) + BAR_HEIGHT, currentBarLength, BAR_HEIGHT); 294 Rectangle rec = new Rectangle(MARGIN, y + (GAP/2) + BAR_HEIGHT, currentBarLength, BAR_HEIGHT); 295 this.gc.drawRectangle(rec); 296 String tooltip = "Time for current build "+currentBuildResults.getName()+": "+Util.timeString((long)currentValue); 297 if (isCommented) { 298 tooltip += ". " + currentBuildResults.getComment(); 299 } 300 graphArea.addArea(rec, tooltip); 301 if (labelxpos < (MARGIN+currentBarLength)) { 302 labelxpos = MARGIN + currentBarLength; 303 } 304 } else { 305 int wr = currentBarLength - currentErrorLength; 306 Rectangle recValue = new Rectangle(MARGIN, y + (GAP/2) + BAR_HEIGHT, wr, BAR_HEIGHT); 307 this.gc.fillRectangle(recValue); 308 this.gc.setBackground(YELLOW); 309 Rectangle recError = new Rectangle(MARGIN+wr, y + (GAP/2) + BAR_HEIGHT, currentErrorLength*2, BAR_HEIGHT); 310 this.gc.fillRectangle(recError); 311 Rectangle rec = new Rectangle(MARGIN, y + (GAP/2) + BAR_HEIGHT, currentBarLength+currentErrorLength, BAR_HEIGHT); 312 this.gc.drawRectangle(rec); 313 StringBuffer tooltip = new StringBuffer("Time for current build "); 314 tooltip.append(currentBuildResults.getName()); 315 tooltip.append(": "); 316 tooltip.append(Util.timeString((long)currentValue)); 317 tooltip.append(" [±"); 318 tooltip.append(Util.timeString((long)currentError)); 319 tooltip.append(']'); 320 if (isCommented) { 321 tooltip.append(". "); 322 tooltip.append(currentBuildResults.getComment()); 323 } 324 graphArea.addArea(rec, tooltip.toString()); 325 if (labelxpos < (MARGIN+currentBarLength+currentErrorLength)) { 326 labelxpos = MARGIN + currentBarLength+currentErrorLength; 327 } 328 } 329 330 // set delta value style and color 331 boolean hasFailure = currentBuildResults.getFailure() != null; 332 if (hasFailure) { 333 if (isCommented) { 334 this.gc.setForeground(DARK_GRAY); 335 } else { 336 this.gc.setForeground(RED); 337 } 338 } else { 339 this.gc.setForeground(BLACK); 340 } 341 342 // draw delta value 343 double delta = -configResults.getDelta(); 344 String label = delta > 0 ? "+" : ""; 345 label += NUMBER_FORMAT.format(delta*100) + "%"; 346 Point labelExtent= this.gc.stringExtent(label); 347 int labelvpos= y + (LINE_HEIGHT - labelExtent.y) / 2; 348 this.gc.drawString(label, labelxpos+TGAP, labelvpos, true); 349 this.gc.setForeground(BLACK); 350 this.gc.setFont(null); 351 int titleStart = (int) (RATIO * this.imageWidth); 352 if (singleTest || !isSignificant) { 353 String deltaTooltip = null; 354 if (singleTest) { 355 deltaTooltip = "This test performed only one iteration; hence its reliability cannot be assessed"; 356 } else if (!isSignificant) { 357 deltaTooltip = "This test has a bad reliability: error is "+NUMBER_FORMAT.format(error*100)+"% (> 3%)!"; 358 } 359 Image warning = (Image) this.resources.get("warning"); 360 int xi = labelxpos+TGAP+labelExtent.x; 361 this.gc.drawImage(warning, xi, labelvpos); 362 ImageData imageData = warning.getImageData(); 363 // Set zones 364 // - first one is between end of bar and warning image beginning 365 Rectangle deltaZone = new Rectangle(labelxpos, labelvpos-2, xi-labelxpos, labelExtent.y+4); 366 graphArea.addArea(deltaZone, null); 367 // - second one is the warning image 368 Rectangle warningZone = new Rectangle(xi, labelvpos, imageData.width, imageData.height); 369 graphArea.addArea(warningZone, deltaTooltip); 370 // - last one is between end of the warning image and the scenario title beginning 371 int warningImageEnd = xi+imageData.width; 372 Rectangle emptyZone = new Rectangle(warningImageEnd, labelvpos, titleStart-warningImageEnd, imageData.height); 373 graphArea.addArea(emptyZone, deltaTooltip); 374 } else { 375 // No tooltip => delta zone is between end of bar and the scenario title beginning 376 Rectangle deltaZone = new Rectangle(labelxpos, labelvpos-2, titleStart-labelxpos, labelExtent.y+4); 377 graphArea.addArea(deltaZone, null); 378 } 379 380 // set title style 381 Color oldfg= this.gc.getForeground(); 382 this.gc.setForeground(BLUE); 383 384 // draw scenario title 385 int x= titleStart; 386 ScenarioResults scenarioResults = (ScenarioResults) configResults.getParent(); 387 String title = scenarioResults.getLabel() + " (" + this.defaultDimName + ")"; 388 Point e= this.gc.stringExtent(title); 389 this.gc.drawLine(x, labelvpos + e.y - 1, x + e.x, labelvpos + e.y - 1); 390 this.gc.drawString(title, x, labelvpos, true); 391 this.gc.setForeground(oldfg); 392 this.gc.setFont(null); 393 Rectangle titleZone = new Rectangle(x, labelvpos, e.x, e.y); 394 graphArea.addArea(titleZone, null/*no tooltip*/); 395 if (!configResults.isBaselined()) { 396 Image warning = (Image) this.resources.get("warning"); 397 this.gc.drawImage(warning, x+e.x, labelvpos); 398 ImageData imageData = warning.getImageData(); 399 Rectangle warningZone = new Rectangle(x+e.x, labelvpos, imageData.width, imageData.height); 400 String titleTooltip = "This test has no baseline result, hence use build "+configResults.getBaselineBuildName()+" for reference!"; 401 graphArea.addArea(warningZone, titleTooltip); 402 } 403 } 404} 405 406void drawLinearScale() { 407 408 // Draw scale background 409 drawScaleBackground(); 410 411 // Draw scale grid lines 412 int gridValue = 100; 413 int n = (int) (this.maxValue / gridValue); 414 while (n > 10) { 415 switch (gridValue) { 416 case 100: 417 gridValue = 200; 418 break; 419 case 200: 420 gridValue = 500; 421 break; 422 case 500: 423 gridValue = 1000; 424 break; 425 default: 426 gridValue += 1000; 427 break; 428 } 429 n = (int) (this.maxValue / gridValue); 430 } 431 int gridWidth = (int) (this.graphWidth * gridValue / this.maxValue); 432 int x = MARGIN; 433 long value = 0; // TODO use minValue instead 434 while (x < this.graphWidth) { 435 436 // draw line 437 this.gc.setForeground(GRAY); 438 if (x > 0) { 439 this.gc.setLineStyle(SWT.LINE_DOT); 440 this.gc.drawLine(x, MARGIN, x, this.graphHeight + TGAP); 441 } 442 443 // draw value 444 this.gc.setForeground(BLACK); 445 String val= Util.timeString(value); 446 Point point= this.gc.stringExtent(val); 447 this.gc.drawString(val, x - point.x / 2, this.graphHeight + TGAP, true); 448 449 // compute next grid position 450 x += gridWidth; 451 value += gridValue; // value is expressed in seconds 452 } 453 this.gc.setLineStyle(SWT.LINE_SOLID); 454 this.gc.drawLine(0, this.graphHeight, this.graphWidth, this.graphHeight); 455} 456 457void drawLogarithmScale() { 458 459 // Draw scale background 460 drawScaleBackground(); 461 462 // Draw scale grid lines 463 double max = Math.log(this.maxValue); 464 int gridValue = 100; 465 int x = MARGIN; 466 long value = 0; // TODO use minValue instead 467 while (x < this.graphWidth) { 468 469 // draw line 470 this.gc.setForeground(GRAY); 471 if (x > MARGIN) { 472 this.gc.setLineStyle(SWT.LINE_DOT); 473 this.gc.drawLine(x, MARGIN, x, this.graphHeight + TGAP); 474 } 475 476 // draw value 477 this.gc.setForeground(BLACK); 478 String str = Util.timeString(value); 479 Point point= this.gc.stringExtent(str); 480 this.gc.drawString(str, x - point.x / 2, this.graphHeight + TGAP, true); 481 482 // compute next grid position 483 value += gridValue; 484 int v = (int) (value / 100); 485 int c = 1; 486 while (v > 10) { 487 v = v / 10; 488 c *= 10; 489 } 490 switch (v) { 491 case 3: 492 gridValue = 200*c; 493 break; 494 case 5: 495 gridValue = 500*c; 496 break; 497 case 10: 498 gridValue = 1000*c; 499 break; 500 } 501 x = MARGIN + (int) (this.graphWidth * Math.log(value) / max); 502 } 503 this.gc.setLineStyle(SWT.LINE_SOLID); 504 this.gc.drawLine(0, this.graphHeight, this.graphWidth, this.graphHeight); 505} 506 507/** 508 * Draw the scale depending on the bar time graph kind. 509 */ 510void drawScale(int kind) { 511 switch (kind) { 512 case TIME_LINEAR: 513 drawLinearScale(); 514 break; 515 case TIME_LOG: 516 drawLogarithmScale(); 517 break; 518 } 519} 520 521private void drawScaleBackground() { 522 523 // Draw striped background 524 Color lightblue = (Color) this.resources.get("lightblue"); 525 if (lightblue == null) { 526 lightblue = new Color(DEFAULT_DISPLAY, 237, 243, 254); 527 this.resources.put("lightblue", lightblue); 528 } 529 this.gc.setBackground(lightblue); 530 for (int i= 0; i<this.count; i++) { 531 if (i % 2 == 0) { 532 this.gc.fillRectangle(0, MARGIN + i * LINE_HEIGHT, this.imageWidth, LINE_HEIGHT); 533 } 534 } 535 536 // Draw bottom vertical line 537 int yy= MARGIN + this.count * LINE_HEIGHT; 538 this.gc.drawLine(MARGIN, MARGIN, MARGIN, yy + TGAP); 539} 540 541String getImageName(int kind) { 542 switch (kind) { 543 case TIME_LINEAR: 544 return this.imageName+"_linear"; 545 case TIME_LOG: 546 return this.imageName+"_log"; 547 } 548 return this.imageName; 549} 550 551void paint(int kind) { 552 553 // Set image 554 this.graphHeight = MARGIN + this.count * LINE_HEIGHT; 555 this.imageHeight = this.graphHeight + GAP + 16 + MARGIN; 556 this.image = new Image(DEFAULT_DISPLAY, this.imageWidth, this.imageHeight); 557 this.gc = new GC(this.image); 558 559 // draw white background 560 this.gc.setBackground(WHITE); 561 this.gc.fillRectangle(0, 0, this.imageWidth, this.imageHeight); 562 563 // Set widths and heights 564 int width= (int) (RATIO * this.imageWidth); // width for results bar 565 this.graphWidth= width - this.gc.stringExtent("-999.9%").x - TGAP - MARGIN; // reserve space //$NON-NLS-1$ 566 567 // Get warning image width 568 Image warning = (Image) this.resources.get("warning"); 569 if (warning == null) { 570 warning = new Image(this.gc.getDevice(), new File(this.outputDir, Utils.WARNING_OBJ).toString()); 571 this.resources.put("warning", warning); 572 } 573 this.graphWidth -= warning.getImageData().width; 574 575 // Set maximum of values 576 this.maxValue = 0.0; 577 this.minValue = Double.MAX_VALUE; 578 for (int i= 0; i<this.count; i++) { 579 BuildResults baselineBuildResults = this.results[i].getBaselineBuildResults(); 580 double value = baselineBuildResults.getValue(); 581 double error = baselineBuildResults.getError(); 582 if (!Double.isNaN(error)) value += Math.abs(error); 583 if (value < 1000000 && value > this.maxValue) { 584 this.maxValue = value; 585 } 586 if (value < this.minValue) { 587 this.minValue = value; 588 } 589 BuildResults currentBuildResults = this.results[i].getCurrentBuildResults(); 590 value = currentBuildResults.getValue(); 591 error = currentBuildResults.getError(); 592 if (!Double.isNaN(error)) value += Math.abs(error); 593 if (value < 1000000 && value > this.maxValue) { 594 this.maxValue = value; 595 } 596 if (value < this.minValue) { 597 this.minValue = value; 598 } 599 } 600 this.minValue = 0; // do not use minValue for now... 601 602 // Draw the scale 603 drawScale(kind); 604 605 // Draw the bars 606 drawBars(kind); 607 608 // Dispose 609 this.gc.dispose(); 610} 611 612/** 613 * Create, paint and save all supported bar graphs and add the corresponding 614 * image and map references in the given stream. 615 * 616 * @param stream 617 */ 618final public void paint(PrintStream stream) { 619 620 // Paint supported graphs 621 int length = SUPPORTED_GRAPHS.length; 622 for (int i=0; i<length; i++) { 623 int kind = SUPPORTED_GRAPHS[i]; 624 paint(kind); 625 save(kind, stream); 626 } 627 628 // Dispose created graphic resources 629 Iterator iterator = this.resources.values().iterator(); 630 while (iterator.hasNext()) { 631 Resource resource = (Resource) iterator.next(); 632 resource.dispose(); 633 } 634 this.resources.clear(); 635} 636 637void print(int kind, PrintStream stream) { 638 String imgName = getImageName(kind); 639 stream.print(" if ($type==\"fp_type="+kind+"\") {\n"); 640 stream.print(" echo '<img src=\""); 641 stream.print(imgName); 642 stream.print(".gif\" usemap=\"#"); 643 stream.print(imgName); 644 stream.print("\" name=\""); 645 stream.print(imgName.substring(imgName.lastIndexOf('.'))); 646 stream.print("\">';\n"); 647 stream.print(" echo '<map name=\""); 648 stream.print(imgName); 649 stream.print("\">';\n"); 650 if (this.areas != null) { 651 for (int i=0; i<this.count; i++) { 652 this.areas[i].print(stream); 653 } 654 } 655 stream.print(" echo '</map>';\n"); 656 stream.print(" }\n"); 657} 658 659void save(int kind, PrintStream stream) { 660 File file = new File(this.outputDir, getImageName(kind)+".gif"); 661 Utils.saveImage(file, this.image); 662 if (file.exists()) { 663 print(kind, stream); 664 } else { 665 stream.print("<br><br>There is no fingerprint for "); 666 stream.print(this.imageName); 667 stream.print(" (kind="); 668 stream.print(kind); 669 stream.print(")<br><br>\n"); 670 } 671} 672} 673