• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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