1 /* 2 * Copyright (c) 2021 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16 package ohos.devtools.views.charts; 17 18 import com.intellij.ui.JBColor; 19 import com.intellij.ui.components.JBPanel; 20 import ohos.devtools.views.charts.model.ChartDataModel; 21 import ohos.devtools.views.charts.model.ChartDataRange; 22 import ohos.devtools.views.charts.model.ChartStandard; 23 import ohos.devtools.views.charts.model.ChartType; 24 import ohos.devtools.views.charts.tooltip.LegendTooltip; 25 import ohos.devtools.views.charts.utils.ChartUtils; 26 import ohos.devtools.views.common.ColorConstants; 27 import ohos.devtools.views.layout.chartview.ProfilerChartsView; 28 29 import java.awt.AlphaComposite; 30 import java.awt.BasicStroke; 31 import java.awt.BorderLayout; 32 import java.awt.Color; 33 import java.awt.Cursor; 34 import java.awt.FlowLayout; 35 import java.awt.Graphics; 36 import java.awt.Graphics2D; 37 import java.awt.Point; 38 import java.awt.Polygon; 39 import java.awt.Stroke; 40 import java.awt.event.MouseEvent; 41 import java.awt.event.MouseListener; 42 import java.awt.event.MouseMotionListener; 43 import java.math.BigDecimal; 44 import java.util.ArrayList; 45 import java.util.LinkedHashMap; 46 import java.util.List; 47 48 import static java.awt.AlphaComposite.SRC_OVER; 49 import static java.awt.BasicStroke.CAP_BUTT; 50 import static java.awt.BasicStroke.JOIN_ROUND; 51 import static ohos.devtools.views.charts.utils.ChartConstants.CHART_HEADER_HEIGHT; 52 import static ohos.devtools.views.charts.utils.ChartConstants.DEFAULT_CHART_COLOR; 53 import static ohos.devtools.views.charts.utils.ChartConstants.DEFAULT_SELECT; 54 import static ohos.devtools.views.charts.utils.ChartConstants.SCALE_LINE_LEN; 55 import static ohos.devtools.views.charts.utils.ChartConstants.TRANSLUCENT_VALUE; 56 import static ohos.devtools.views.charts.utils.ChartConstants.Y_AXIS_STR_OFFSET_X; 57 import static ohos.devtools.views.charts.utils.ChartConstants.Y_AXIS_STR_OFFSET_Y; 58 import static ohos.devtools.views.charts.utils.ChartUtils.divide; 59 import static ohos.devtools.views.charts.utils.ChartUtils.divideInt; 60 import static ohos.devtools.views.charts.utils.ChartUtils.multiply; 61 import static ohos.devtools.views.common.ColorConstants.TIMELINE_SCALE; 62 import static ohos.devtools.views.common.LayoutConstants.FLOAT_VALUE; 63 import static ohos.devtools.views.common.LayoutConstants.INITIAL_VALUE; 64 import static ohos.devtools.views.charts.utils.ChartConstants.CHART_MAX_Y; 65 import static ohos.devtools.views.charts.utils.ChartConstants.CHART_SECTION_NUM_Y; 66 67 /** 68 * Abstract parent class of all charts 69 * 70 * @since 2021/5/19 16:39 71 */ 72 public abstract class ProfilerChart extends JBPanel implements MouseListener, MouseMotionListener { 73 private static final int NUM_5 = 5; 74 75 /** 76 * The start time of the x-axis when drawing 77 */ 78 protected int startTime; 79 80 /** 81 * The end time of the x-axis when drawing 82 */ 83 protected int endTime; 84 85 /** 86 * Enable/disable select function 87 * 88 * @see "true: chart fold, false: chart expand" 89 * @see "chart expand: show ruler and tooltip" 90 */ 91 protected boolean fold; 92 93 /** 94 * Enable/disable select function 95 */ 96 protected boolean enableSelect; 97 98 /** 99 * Whether the mouse enters chart 100 * 101 * @see "Here is the entry into the paint chart, not the chart component" 102 */ 103 protected boolean enterChart; 104 105 /** 106 * Chart name, used as key in selected map 107 */ 108 protected final String chartName; 109 110 /** 111 * Right of chart 112 */ 113 protected int right = 0; 114 115 /** 116 * Coordinate axis X0 point when drawing chart 117 * 118 * @see "It is the coordinate axis X0 point used in daily drawing, not the coordinate axis origin of Swing" 119 */ 120 protected int xZero = 0; 121 122 /** 123 * Coordinate axis Y0 point when drawing chart 124 * 125 * @see "It is the coordinate axis Y0 point used in daily drawing, not the coordinate axis origin of Swing" 126 */ 127 protected int yZero = 0; 128 129 /** 130 * The x-axis is the coordinate of the starting plot 131 * 132 * @see "The dynamic timeline and chart appear from right to left" 133 */ 134 protected int startXCoordinate = 0; 135 136 /** 137 * The top of this panel 138 */ 139 protected int panelTop = 0; 140 141 /** 142 * The maximum value that can be displayed on the x-axis 143 */ 144 protected int maxDisplayX = 1; 145 146 /** 147 * Minimum scale interval on X-axis scale line 148 */ 149 protected int minMarkIntervalX = 1; 150 151 /** 152 * Y-axis label 153 */ 154 protected String axisLabelY = ""; 155 156 /** 157 * Y-axis maximum unit 158 */ 159 protected int maxUnitY = CHART_MAX_Y; 160 161 /** 162 * Number of y-axis scale segments 163 */ 164 protected int sectionNumY = CHART_SECTION_NUM_Y; 165 166 /** 167 * Top of chart 168 */ 169 protected int top = CHART_HEADER_HEIGHT; 170 171 /** 172 * The anchor point of dragging box selection, that is, the fixed point 173 */ 174 protected int dragAnchorPoint = INITIAL_VALUE; 175 176 /** 177 * The starting point of dragging and dropping box selection 178 */ 179 protected int dragStartPoint = INITIAL_VALUE; 180 181 /** 182 * The ending point of dragging and dropping box selection 183 */ 184 protected int dragEndPoint = INITIAL_VALUE; 185 186 /** 187 * Bottom parent panel 188 */ 189 protected final ProfilerChartsView bottomPanel; 190 191 /** 192 * YAxisLable 193 */ 194 protected ArrayList<String> yAxisList = new ArrayList<>(); 195 196 /** 197 * Data map 198 * 199 * @see "Key: time, Value: The values of chart at this point in time>" 200 */ 201 protected volatile LinkedHashMap<Integer, List<ChartDataModel>> dataMap; 202 203 /** 204 * Legends 205 */ 206 protected JBPanel legends; 207 208 /** 209 * Tooltip 210 */ 211 protected LegendTooltip tooltip; 212 213 /** 214 * Enable/disable drag select 215 */ 216 protected boolean enableDragSelect = false; 217 218 /** 219 * Chart type 220 */ 221 protected ChartType chartType; 222 223 /** 224 * Number of pixels per X-axis unit 225 */ 226 protected BigDecimal pixelPerX; 227 228 /** 229 * Number of pixels per Y-axis unit 230 */ 231 protected BigDecimal pixelPerY; 232 233 /** 234 * Update when mouse moved 235 * 236 * @see "Use function getMousePosition() will be null sometime." 237 */ 238 private Point mousePoint; 239 240 /** 241 * Whether the chart can be dragged or not 242 */ 243 private boolean canDragged = false; 244 245 /** 246 * Whether the chart is being dragged or not 247 */ 248 private boolean dragging = false; 249 250 251 /** 252 * Constructor 253 * 254 * @param bottomPanel ProfilerChartsView 255 * @param name chart name 256 */ ProfilerChart(ProfilerChartsView bottomPanel, String name)257 ProfilerChart(ProfilerChartsView bottomPanel, String name) { 258 this.bottomPanel = bottomPanel; 259 this.chartName = name; 260 // 设置可透明显示 261 this.setOpaque(false); 262 this.setLayout(new BorderLayout()); 263 this.addMouseListener(this); 264 this.addMouseMotionListener(this); 265 this.tooltip = new LegendTooltip(); 266 // 添加图例组件的布局 267 legends = new JBPanel(new FlowLayout(FlowLayout.RIGHT)); 268 legends.setOpaque(false); 269 initLegends(); 270 this.add(legends, BorderLayout.NORTH); 271 } 272 273 /** 274 * Constructor 275 * 276 * @param bottomPanel ProfilerChartsView 277 * @param name chart name 278 * @param yAxisList yAxisList 279 */ ProfilerChart(ProfilerChartsView bottomPanel, String name, ArrayList yAxisList)280 ProfilerChart(ProfilerChartsView bottomPanel, String name, ArrayList yAxisList) { 281 this.bottomPanel = bottomPanel; 282 this.chartName = name; 283 this.yAxisList = yAxisList; 284 // Set transparent display 285 this.setOpaque(false); 286 this.setLayout(new BorderLayout()); 287 this.addMouseListener(this); 288 this.addMouseMotionListener(this); 289 this.tooltip = new LegendTooltip(); 290 // 添加图例组件的布局 291 legends = new JBPanel(new FlowLayout(FlowLayout.RIGHT)); 292 legends.setOpaque(false); 293 initLegends(); 294 this.add(legends, BorderLayout.NORTH); 295 } 296 297 /** 298 * Init legends 299 */ initLegends()300 protected abstract void initLegends(); 301 302 /** 303 * Build legends of chart 304 * 305 * @param lastModels Data on the far right side of the panel 306 * @see "The legend shows the y value corresponding to the rightmost X axis, not the mouse hover position" 307 */ buildLegends(List<ChartDataModel> lastModels)308 protected abstract void buildLegends(List<ChartDataModel> lastModels); 309 310 /** 311 * Paint chart 312 * 313 * @param graphics Graphics 314 */ paintChart(Graphics graphics)315 protected abstract void paintChart(Graphics graphics); 316 317 /** 318 * Build tooltip content 319 * 320 * @param showKey Key to show 321 * @param actualKey The actual value of the key in the data map 322 * @param newChart Is it a new chart 323 */ buildTooltip(int showKey, int actualKey, boolean newChart)324 protected abstract void buildTooltip(int showKey, int actualKey, boolean newChart); 325 326 /** 327 * User defined events 328 * 329 * @param event MouseEvent 330 */ leftMouseClickEvent(MouseEvent event)331 protected abstract void leftMouseClickEvent(MouseEvent event); 332 333 /** 334 * User defined events 335 * 336 * @param event MouseEvent 337 */ rightMouseClickEvent(MouseEvent event)338 protected abstract void rightMouseClickEvent(MouseEvent event); 339 340 /** 341 * User defined events 342 * 343 * @param event MouseEvent 344 */ mouseDraggedEvent(MouseEvent event)345 protected abstract void mouseDraggedEvent(MouseEvent event); 346 347 /** 348 * User defined events 349 * 350 * @param event MouseEvent 351 */ mouseReleaseEvent(MouseEvent event)352 protected abstract void mouseReleaseEvent(MouseEvent event); 353 354 /** 355 * Refresh chart 356 * 357 * @param startTime The start time of the x-axis when drawing 358 * @param endTime The end time of the x-axis when drawing 359 * @param dataMap Map of chart data 360 */ refreshChart(int startTime, int endTime, LinkedHashMap<Integer, List<ChartDataModel>> dataMap)361 public void refreshChart(int startTime, int endTime, LinkedHashMap<Integer, List<ChartDataModel>> dataMap) { 362 this.startTime = startTime; 363 this.endTime = endTime; 364 this.dataMap = dataMap; 365 refreshLegends(); 366 this.repaint(); 367 this.revalidate(); 368 } 369 370 /** 371 * Refresh legends 372 */ refreshLegends()373 protected void refreshLegends() { 374 // Find the closest value in the array of time 375 int lastTime = getLastTime(); 376 List<ChartDataModel> models = dataMap.get(lastTime); 377 if (models != null && !models.isEmpty()) { 378 buildLegends(models); 379 } 380 } 381 382 @Override paintComponent(Graphics graphics)383 protected void paintComponent(Graphics graphics) { 384 super.paintComponent(graphics); 385 graphics.setColor(JBColor.GRAY); 386 initPoint(); 387 // Save the maximum value of current y-axis 388 int crtMaxY = this.maxUnitY; 389 paintChart(graphics); 390 // If maxUnitY was update in the process of paint chart, it needs to be redrawn at this time. Otherwise, 391 // the current drawing is still based on the old value, which will cause the maximum value to exceed the panel 392 if (crtMaxY != this.maxUnitY) { 393 repaint(); 394 return; 395 } 396 drawYAxis(graphics); 397 if (enableSelect) { 398 paintSelectedArea(graphics); 399 drawSelectedRuler(graphics); 400 } 401 // chart expand: show ruler 402 if (!fold) { 403 drawMouseRuler(graphics); 404 } 405 // chart expand: show tooltip 406 if (enterChart && !fold) { 407 showTooltip(false); 408 } 409 } 410 411 /** 412 * Initialization of points and scale information 413 */ initPoint()414 protected void initPoint() { 415 // Calculate the top, bottom, left and right margins of the drawing area in the panel 416 int left = 0; 417 right = left + this.getWidth(); 418 int bottom = this.getHeight(); 419 xZero = left; 420 yZero = bottom; 421 // How many pixels does one unit of x-axis occupy 422 pixelPerX = divide(right - left, maxDisplayX); 423 // How many pixels does one unit of y-axis occupy 424 pixelPerY = divide(top - yZero, maxUnitY); 425 // If the time exceeds maxDisplayX and continues to move forward, the drawing should start from x0 426 if (endTime < maxDisplayX) { 427 startXCoordinate = right - multiply(pixelPerX, endTime); 428 } else { 429 startXCoordinate = xZero; 430 } 431 } 432 433 /** 434 * Draw Y-axis 435 * 436 * @param graphics Graphics 437 */ drawYAxis(Graphics graphics)438 protected void drawYAxis(Graphics graphics) { 439 // Calculate the length of each scale segment and the pixel increment when drawing circularly 440 int interval = divideInt(maxUnitY, sectionNumY); 441 int index = 0; 442 for (int value = interval; value <= maxUnitY; value += interval) { 443 int yAxis = yZero + multiply(pixelPerY, value); 444 // Draw Y-axis scale 445 graphics.setColor(TIMELINE_SCALE); 446 graphics.drawLine(xZero, yAxis, xZero + SCALE_LINE_LEN, yAxis); 447 // Draw the string of Y-axis scale 448 String str = getYaxisLabelStr(value); 449 graphics.setColor(JBColor.foreground()); 450 graphics.drawString(str, xZero + Y_AXIS_STR_OFFSET_X, yAxis + Y_AXIS_STR_OFFSET_Y); 451 } 452 } 453 454 /** 455 * Gets the string in Y-axis units 456 * 457 * @param value int 458 * @return String 459 */ getYaxisLabelStr(int value)460 protected String getYaxisLabelStr(int value) { 461 return value == maxUnitY ? value + axisLabelY : value + ""; 462 } 463 464 /** 465 * Draw the box selection area 466 * 467 * @param graphics Graphics 468 * @see "In fact, two translucent rectangles are drawn here, which are covered on the chart. With the mouse drag, 469 * the size of the rectangle is changed to realize the box selection" 470 */ paintSelectedArea(Graphics graphics)471 protected void paintSelectedArea(Graphics graphics) { 472 if (this.bottomPanel.getPublisher().getStandard().getSelectedRange(chartName) == null) { 473 return; 474 } 475 ChartDataRange selectedRange = this.bottomPanel.getPublisher().getStandard().getSelectedRange(chartName); 476 int selectedStartTime = selectedRange.getStartTime(); 477 int selectedEndTime = selectedRange.getEndTime(); 478 int selectedStartX = multiply(pixelPerX, selectedStartTime - this.startTime) + startXCoordinate; 479 int selectedEndX = multiply(pixelPerX, selectedEndTime - this.startTime) + startXCoordinate; 480 int height = this.bottomPanel.getHeight(); 481 // Here, the brush is transparent, and the covered rectangle is drawn 482 Graphics2D g2d = castGraphics2D(graphics); 483 if (g2d != null) { 484 g2d.setComposite(AlphaComposite.getInstance(SRC_OVER, TRANSLUCENT_VALUE)); 485 } 486 graphics.setColor(ColorConstants.CHART_BG); 487 graphics.fillRect(0, 0, selectedStartX, height); 488 graphics.fillRect(selectedEndX, 0, right - selectedEndX, height); 489 } 490 491 /** 492 * Draw a ruler that follows the mouse 493 * 494 * @param graphics Graphics 495 */ drawMouseRuler(Graphics graphics)496 protected void drawMouseRuler(Graphics graphics) { 497 if (mousePoint == null) { 498 return; 499 } 500 501 Graphics2D g2d = castGraphics2D(graphics); 502 if (g2d == null) { 503 return; 504 } 505 // Define dashed bar features 506 float[] dash = {FLOAT_VALUE, 0f, FLOAT_VALUE}; 507 BasicStroke bs = new BasicStroke(1, CAP_BUTT, JOIN_ROUND, 1.0f, dash, FLOAT_VALUE); 508 // Save original line features 509 Stroke stroke = g2d.getStroke(); 510 BasicStroke defaultStroke = castBasicStroke(stroke); 511 g2d.setColor(ColorConstants.RULER); 512 g2d.setStroke(bs); 513 int mouseX = (int) mousePoint.getX(); 514 g2d.drawLine(mouseX, panelTop, mouseX, this.getHeight()); 515 // After drawing, the default format should be restored, otherwise the graphics drawn later are dotted lines 516 g2d.setStroke(defaultStroke); 517 } 518 519 /** 520 * castGraphics2D 521 * 522 * @param graphics graphics 523 * @return Graphics2D 524 */ castGraphics2D(Graphics graphics)525 protected Graphics2D castGraphics2D(Graphics graphics) { 526 Graphics2D graph = null; 527 if (graphics instanceof Graphics2D) { 528 graph = (Graphics2D) graphics; 529 } 530 return graph; 531 } 532 533 /** 534 * castBasicStroke 535 * 536 * @param stroke stroke 537 * @return BasicStroke 538 */ castBasicStroke(Stroke stroke)539 protected BasicStroke castBasicStroke(Stroke stroke) { 540 BasicStroke basicStroke = null; 541 if (stroke instanceof BasicStroke) { 542 basicStroke = (BasicStroke) stroke; 543 } 544 return basicStroke; 545 } 546 547 /** 548 * Draw a ruler when the chart is being selected 549 * 550 * @param graphics Graphics 551 */ drawSelectedRuler(Graphics graphics)552 protected void drawSelectedRuler(Graphics graphics) { 553 ChartDataRange selectedRange = this.bottomPanel.getPublisher().getStandard().getSelectedRange(chartName); 554 if (selectedRange != null) { 555 graphics.setColor(ColorConstants.RULER); 556 int startX = startXCoordinate + multiply(pixelPerX, selectedRange.getStartTime() - startTime); 557 graphics.drawLine(startX, panelTop, startX, this.getHeight()); 558 if (!enableDragSelect) { 559 drawInvertedTriangle(startX, graphics); 560 } 561 int endX = startXCoordinate + multiply(pixelPerX, selectedRange.getEndTime() - startTime); 562 graphics.drawLine(endX, panelTop, endX, this.getHeight()); 563 if (!enableDragSelect) { 564 drawInvertedTriangle(endX, graphics); 565 } 566 } 567 } 568 569 /** 570 * Draw an inverted triangle 571 * 572 * @param bottomVertexX Vertex below inverted triangle 573 * @param graphics Graphics 574 */ drawInvertedTriangle(int bottomVertexX, Graphics graphics)575 private void drawInvertedTriangle(int bottomVertexX, Graphics graphics) { 576 Polygon polygon = new Polygon(); 577 polygon.addPoint(bottomVertexX, panelTop + NUM_5); 578 polygon.addPoint(bottomVertexX - NUM_5, panelTop); 579 polygon.addPoint(bottomVertexX + NUM_5, panelTop); 580 polygon.addPoint(bottomVertexX, panelTop + NUM_5); 581 graphics.setColor(JBColor.foreground()); 582 graphics.fillPolygon(polygon); 583 } 584 585 /** 586 * Check the position of the mouse to determine whether it is necessary to display the tool tip 587 * 588 * @param event MouseEvent 589 */ checkMouseForTooltip(MouseEvent event)590 protected void checkMouseForTooltip(MouseEvent event) { 591 // If the X coordinate of the mouse is less than the X starting coordinate of chart, the tooltip is not required 592 if (event.getX() < startXCoordinate) { 593 enterChart = false; 594 tooltip.hideTip(); 595 return; 596 } 597 if (!enterChart) { 598 enterChart = true; 599 } 600 if (!fold) { 601 // Tooltip position needs to be refreshed when mouse move 602 tooltip.followWithMouse(event); 603 showTooltip(true); 604 } 605 } 606 607 /** 608 * Show tooltip 609 * 610 * @param newChart Is it a new chart 611 */ showTooltip(boolean newChart)612 protected void showTooltip(boolean newChart) { 613 if (dragging && !enableDragSelect) { 614 tooltip.hideTip(); 615 return; 616 } 617 // If the X coordinate of the mouse is less than the X starting coordinate of chart, the tooltip is not required 618 if (mousePoint == null || mousePoint.getX() < startXCoordinate) { 619 tooltip.hideTip(); 620 return; 621 } 622 if (dataMap == null || dataMap.size() == 0) { 623 tooltip.hideTip(); 624 return; 625 } 626 int[] timeArray = dataMap.keySet().stream().mapToInt(Integer::valueOf).toArray(); 627 if (timeArray.length == 0) { 628 return; 629 } 630 // The time to display 631 // the time corresponding to the mouse = (mouse X coordinate - x start coordinate of drawing chart) / the 632 // number of pixels corresponding to 1 unit of X axis + start time 633 int showKey = divide(mousePoint.getX() - startXCoordinate, pixelPerX) + startTime; 634 // The display time is not necessarily in the dataMap time array, and the closest time needs to be found, 635 // and then the value is obtained through this time 636 int actualKey = timeArray[ChartUtils.searchClosestIndex(timeArray, showKey)]; 637 buildTooltip(showKey, actualKey, newChart); 638 } 639 640 /** 641 * Gets the chart color of the current data 642 * 643 * @param index chart data index 644 * @param models Data list 645 * @return Color 646 */ getCurrentLineColor(int index, List<ChartDataModel> models)647 protected Color getCurrentLineColor(int index, List<ChartDataModel> models) { 648 Color color = DEFAULT_CHART_COLOR; 649 if (models == null || models.size() == 0) { 650 tooltip.hideTip(); 651 return color; 652 } 653 for (ChartDataModel model : models) { 654 if (model.getIndex() == index && model.getColor() != null) { 655 color = model.getColor(); 656 } 657 } 658 return color; 659 } 660 661 /** 662 * Get the index of the next chart data 663 * 664 * @param current Index of current chart data 665 * @param models All chart's data models 666 * @return Next chart's index 667 */ getNextLineIndex(int current, List<ChartDataModel> models)668 protected int getNextLineIndex(int current, List<ChartDataModel> models) { 669 int next = INITIAL_VALUE; 670 if (models == null || models.isEmpty()) { 671 return next; 672 } 673 int size = models.size(); 674 for (int index = 0; index < size; index++) { 675 ChartDataModel model = models.get(index); 676 int newIndex = index + 1; 677 if (model.getIndex() == current && newIndex < size) { 678 next = models.get(index + 1).getIndex(); 679 break; 680 } 681 } 682 return next; 683 } 684 685 /** 686 * Find the sum of the values of all elements in the collection after the index is specified 687 * 688 * @param models Data list 689 * @param index Specify index 690 * @return int 691 */ getListSum(List<ChartDataModel> models, int index)692 public int getListSum(List<ChartDataModel> models, int index) { 693 int sum = 0; 694 if (index == INITIAL_VALUE || models == null) { 695 return sum; 696 } 697 for (ChartDataModel model : models) { 698 if (model.getIndex() < index) { 699 continue; 700 } 701 sum += model.getValue(); 702 } 703 return sum; 704 } 705 706 /** 707 * Find the value of ChartDataModel in the list by index 708 * 709 * @param models Data list 710 * @param index Specify index 711 * @return int 712 */ getModelValueByIndex(List<ChartDataModel> models, int index)713 protected int getModelValueByIndex(List<ChartDataModel> models, int index) { 714 int value = 0; 715 if (index == INITIAL_VALUE || models == null) { 716 return value; 717 } 718 for (ChartDataModel model : models) { 719 if (model.getIndex() == index) { 720 value = model.getValue(); 721 break; 722 } 723 } 724 return value; 725 } 726 727 /** 728 * Get the last time on the current chart, because End time in standard does not necessarily have data 729 * 730 * @return int 731 */ getLastTime()732 protected int getLastTime() { 733 // Gets the set of values for End time time 734 int[] timeArray = dataMap.keySet().stream().mapToInt(Integer::valueOf).toArray(); 735 if (timeArray.length == 0) { 736 return INITIAL_VALUE; 737 } 738 739 // In the array of time, find the closest value 740 ChartDataRange range = bottomPanel.getPublisher().getStandard().getDisplayRange(); 741 return timeArray[ChartUtils.searchClosestIndex(timeArray, range.getEndTime())]; 742 } 743 744 /** 745 * Initialize Y-axis maximum units 746 */ initMaxUnitY()747 public void initMaxUnitY() { 748 this.maxUnitY = CHART_MAX_Y; 749 } 750 751 @Override mouseClicked(MouseEvent event)752 public void mouseClicked(MouseEvent event) { 753 if (!enableSelect) { 754 return; 755 } 756 int button = event.getButton(); 757 // If the left click point is less than the starting point, it will not be updated 758 if (button == MouseEvent.BUTTON1 && event.getX() < startXCoordinate) { 759 return; 760 } 761 if (button == MouseEvent.BUTTON1) { 762 // Left click trigger box to select scene 763 this.setCursor(new Cursor(Cursor.E_RESIZE_CURSOR)); 764 mouseLeftClick(event); 765 } else { 766 // Right click to cancel the selection and clear the selection range 767 this.setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); 768 mouseRightClick(event); 769 } 770 } 771 mouseLeftClick(MouseEvent event)772 private void mouseLeftClick(MouseEvent event) { 773 int mouseX = event.getX(); 774 // By default, 10px is selected in the back box of the mouse position. 775 // If it exceeds the far right, 10px is selected from the starting point to the left 776 int mouseXNumber = mouseX + DEFAULT_SELECT; 777 if (mouseXNumber < right) { 778 dragStartPoint = mouseX; 779 dragEndPoint = mouseX + DEFAULT_SELECT; 780 } else { 781 dragStartPoint = right - DEFAULT_SELECT; 782 dragEndPoint = right; 783 } 784 int selectStart = getTimeByMouseX(dragStartPoint); 785 int selectEnd = getTimeByMouseX(dragEndPoint); 786 this.bottomPanel.getPublisher().getStandard().updateSelectedStart(chartName, selectStart); 787 this.bottomPanel.getPublisher().getStandard().updateSelectedEnd(chartName, selectEnd); 788 // Pause refreshing data 789 this.bottomPanel.getPublisher().pauseRefresh(); 790 // Refresh the interface manually once, otherwise the interface will not darken 791 ChartDataRange range = bottomPanel.getPublisher().getStandard().getDisplayRange(); 792 bottomPanel.getPublisher().notifyRefresh(range.getStartTime(), range.getEndTime()); 793 // Call the method to be override 794 leftMouseClickEvent(event); 795 } 796 mouseRightClick(MouseEvent event)797 private void mouseRightClick(MouseEvent event) { 798 dragStartPoint = INITIAL_VALUE; 799 dragEndPoint = INITIAL_VALUE; 800 this.bottomPanel.getPublisher().getStandard().clearSelectedRange(chartName); 801 // Manually refresh the interface once 802 ChartDataRange range = bottomPanel.getPublisher().getStandard().getDisplayRange(); 803 bottomPanel.getPublisher().notifyRefresh(range.getStartTime(), range.getEndTime()); 804 // Call the method to be override 805 rightMouseClickEvent(event); 806 } 807 808 @Override mousePressed(MouseEvent event)809 public void mousePressed(MouseEvent event) { 810 if (!enableSelect) { 811 return; 812 } 813 // If the pressed point is less than the starting point, it will not be updated 814 if (event.getX() < startXCoordinate) { 815 return; 816 } 817 ChartDataRange selectedRange = this.bottomPanel.getPublisher().getStandard().getSelectedRange(chartName); 818 // selectedRange is null, which means that there is no click and the drag starts directly. 819 // This scene is not considered for the moment 820 if (selectedRange == null) { 821 return; 822 } 823 int mouseX = event.getX(); 824 // After dragging the scroll bar, the value of dragStartPoint will change, 825 // so we get point by time from select range 826 dragStartPoint = getPointXByTime(selectedRange.getStartTime()); 827 dragEndPoint = getPointXByTime(selectedRange.getEndTime()); 828 if (Math.abs(mouseX - dragStartPoint) > NUM_5 && Math.abs(mouseX - dragEndPoint) > NUM_5) { 829 canDragged = false; 830 return; 831 } 832 // Determine the anchor point when dragging 833 boolean isCloseToStart = Math.abs(mouseX - dragStartPoint) < NUM_5; 834 if (isCloseToStart) { 835 dragAnchorPoint = dragEndPoint; 836 canDragged = true; 837 } 838 boolean isCloseToEnd = Math.abs(mouseX - dragEndPoint) < NUM_5; 839 if (isCloseToEnd) { 840 dragAnchorPoint = dragStartPoint; 841 canDragged = true; 842 } 843 } 844 845 @Override 846 public void mouseReleased(MouseEvent event) { 847 if (!enableSelect) { 848 return; 849 } 850 // Release event not processed with non left key 851 if (event.getButton() != MouseEvent.BUTTON1) { 852 return; 853 } 854 /* 855 * The trigger sequence of drag events is: press > drag > release 856 * The trigger sequence of click events is: press > release > click 857 */ 858 if (!dragging) { 859 return; 860 } 861 // Call the method to be override 862 mouseReleaseEvent(event); 863 dragging = false; 864 } 865 866 @Override 867 public void mouseEntered(MouseEvent event) { 868 mousePoint = event.getPoint(); 869 // If the X coordinate of the mouse is less than the X starting coordinate of chart, the tooltip is not required 870 if (event.getX() < startXCoordinate) { 871 enterChart = false; 872 tooltip.hideTip(); 873 return; 874 } 875 876 enterChart = true; 877 if (!fold) { 878 // Tooltip position needs to be refreshed when mouse move 879 tooltip.followWithMouse(event); 880 showTooltip(true); 881 } 882 } 883 884 @Override 885 public void mouseExited(MouseEvent event) { 886 mousePoint = null; 887 enterChart = false; 888 tooltip.hideTip(); 889 this.repaint(); 890 } 891 892 @Override 893 public void mouseDragged(MouseEvent event) { 894 if (!enableSelect || !canDragged) { 895 // Call repaint, mainly to update the ruler following the mouse movement 896 this.repaint(); 897 return; 898 } 899 dragging = true; 900 ChartDataRange selectedRange = this.bottomPanel.getPublisher().getStandard().getSelectedRange(chartName); 901 if (selectedRange == null) { 902 // Call repaint, mainly to update the ruler following the mouse movement 903 this.repaint(); 904 return; 905 } 906 int mouseX = event.getX(); 907 if (mouseX > dragAnchorPoint) { 908 // If the mouse position is larger than the anchor position, it means that you drag it on the right side of 909 // the anchor and update end to the mouse point 910 dragRightRuler(mouseX); 911 } else { 912 // If the mouse position is smaller than the anchor position, it means that it is dragging on the left side 913 // of the anchor, then update end to the anchor and start to the mouse point 914 dragLeftRuler(mouseX); 915 } 916 checkCursorStyle(mouseX); 917 // Call the method to be override 918 mouseDraggedEvent(event); 919 this.repaint(); 920 } 921 922 private void dragLeftRuler(int mouseX) { 923 dragStartPoint = mouseX; 924 ChartStandard standard = this.bottomPanel.getPublisher().getStandard(); 925 if (dragAnchorPoint != INITIAL_VALUE) { 926 dragEndPoint = dragAnchorPoint; 927 standard.updateSelectedEnd(chartName, getTimeByMouseX(dragAnchorPoint)); 928 } 929 standard.updateSelectedStart(chartName, getTimeByMouseX(mouseX)); 930 } 931 932 private void dragRightRuler(int mouseX) { 933 dragEndPoint = mouseX; 934 ChartStandard standard = this.bottomPanel.getPublisher().getStandard(); 935 if (dragAnchorPoint != INITIAL_VALUE) { 936 dragStartPoint = dragAnchorPoint; 937 standard.updateSelectedStart(chartName, getTimeByMouseX(dragAnchorPoint)); 938 } 939 standard.updateSelectedEnd(chartName, getTimeByMouseX(mouseX)); 940 } 941 942 @Override 943 public void mouseMoved(MouseEvent event) { 944 mousePoint = event.getPoint(); 945 checkCursorStyle(event.getX()); 946 checkMouseForTooltip(event); 947 this.repaint(); 948 } 949 950 /** 951 * Through the X coordinate of the mouse, calculate the corresponding time 952 * 953 * @param mouseX X coordinate of mouse 954 * @return Corresponding X-axis time 955 * @see "Note that the time corresponding to the time x axis calculated here is not necessarily in the keyset of 956 * the datamap drawing chart. It may be between two values. When using it, you need to find the closest value to 957 * the keyset of the datamap" 958 */ 959 private int getTimeByMouseX(int mouseX) { 960 // Time corresponding to mouse = (mouse X coordinate - x start coordinate of drawing chart) / number 961 // of pixels corresponding to 1 unit of X axis + start time 962 return ChartUtils.divide(mouseX - startXCoordinate, pixelPerX) + startTime; 963 } 964 965 /** 966 * Calculate the corresponding X-axis coordinate through time 967 * 968 * @param time Current time 969 * @return Corresponding X-axis coordinate value 970 */ 971 private int getPointXByTime(int time) { 972 // Corresponding coordinates on the mouse = number of pixels corresponding to 1 unit of X axis * 973 // (current time - x start time of drawing chart) + X start coordinates of drawing chart 974 return ChartUtils.multiply(pixelPerX, time - startTime) + startXCoordinate; 975 } 976 977 /** 978 * Update the mouse style according to the coordinates of the mouse 979 * 980 * @param crtMouseX X-axis coordinates of mouse 981 */ 982 private void checkCursorStyle(int crtMouseX) { 983 ChartDataRange selectedRange = this.bottomPanel.getPublisher().getStandard().getSelectedRange(chartName); 984 if (selectedRange == null) { 985 return; 986 } 987 988 int selectStart = startXCoordinate + ChartUtils.multiply(pixelPerX, selectedRange.getStartTime() - startTime); 989 int selectEnd = startXCoordinate + ChartUtils.multiply(pixelPerX, selectedRange.getEndTime() - startTime); 990 // When the mouse is close to the ruler, the mouse will be reset 991 if (Math.abs(selectStart - crtMouseX) < NUM_5) { 992 this.setCursor(new Cursor(Cursor.W_RESIZE_CURSOR)); 993 } else if (Math.abs(selectEnd - crtMouseX) < NUM_5) { 994 this.setCursor(new Cursor(Cursor.E_RESIZE_CURSOR)); 995 } else { 996 this.setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); 997 } 998 } 999 1000 public LegendTooltip getTooltip() { 1001 return tooltip; 1002 } 1003 1004 public void setMaxDisplayX(int maxDisplayX) { 1005 this.maxDisplayX = maxDisplayX; 1006 } 1007 1008 public void setMinMarkIntervalX(int minMarkIntervalX) { 1009 this.minMarkIntervalX = minMarkIntervalX; 1010 } 1011 1012 public String getAxisLabelY() { 1013 return axisLabelY; 1014 } 1015 1016 public void setAxisLabelY(String axisLabelY) { 1017 this.axisLabelY = axisLabelY; 1018 } 1019 1020 public void setMaxUnitY(int maxUnitY) { 1021 this.maxUnitY = maxUnitY; 1022 } 1023 1024 public int getMaxUnitY() { 1025 return maxUnitY; 1026 } 1027 1028 public void setSectionNumY(int sectionNumY) { 1029 this.sectionNumY = sectionNumY; 1030 } 1031 1032 public int getStartTime() { 1033 return startTime; 1034 } 1035 1036 public void setStartTime(int startTime) { 1037 this.startTime = startTime; 1038 } 1039 1040 public int getEndTime() { 1041 return endTime; 1042 } 1043 1044 public void setEndTime(int endTime) { 1045 this.endTime = endTime; 1046 } 1047 1048 public ProfilerChartsView getBottomPanel() { 1049 return bottomPanel; 1050 } 1051 1052 public void setEnableSelect(boolean enableSelect) { 1053 this.enableSelect = enableSelect; 1054 } 1055 1056 public void setFold(boolean fold) { 1057 this.fold = fold; 1058 } 1059 1060 public int getMinMarkIntervalX() { 1061 return minMarkIntervalX; 1062 } 1063 1064 public void setEnableDragSelect(boolean enableDragSelect) { 1065 this.enableDragSelect = enableDragSelect; 1066 } 1067 } 1068