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 ohos.devtools.views.charts.model.ChartDataModel; 19 import ohos.devtools.views.layout.chartview.ProfilerChartsView; 20 21 import java.awt.Graphics; 22 import java.awt.Polygon; 23 import java.awt.event.MouseEvent; 24 import java.util.Comparator; 25 import java.util.List; 26 27 import static ohos.devtools.views.charts.model.ChartType.FILLED_LINE; 28 import static ohos.devtools.views.charts.utils.ChartUtils.divideInt; 29 import static ohos.devtools.views.charts.utils.ChartUtils.multiply; 30 import static ohos.devtools.views.common.LayoutConstants.INITIAL_VALUE; 31 32 /** 33 * Filled line chart 34 * 35 * @since 2021/5/19 16:39 36 */ 37 public class FilledLineChart extends ProfilerChart { 38 private static final int NUM_2 = 2; 39 private static final int NUM_3 = 3; 40 private static final int Y_AXIS_COORDINATE_MAX_VALUE = 100; 41 42 43 /** 44 * Do line charts need to be stacked 45 */ 46 private final boolean stacked; 47 48 /** 49 * y Axis Coordinate Fixed 50 */ 51 private boolean yAxisCoordinateFixed = false; 52 53 /** 54 * Constructor 55 * 56 * @param bottomPanel ProfilerChartsView 57 * @param name chart name 58 * @param stacked Do line charts need to be stacked 59 */ FilledLineChart(ProfilerChartsView bottomPanel, String name, boolean stacked)60 public FilledLineChart(ProfilerChartsView bottomPanel, String name, boolean stacked) { 61 super(bottomPanel, name); 62 this.stacked = stacked; 63 chartType = FILLED_LINE; 64 } 65 66 /** 67 * Constructor 68 * 69 * @param bottomPanel ProfilerChartsView 70 * @param name chart name 71 * @param stacked Do line charts need to be stacked 72 * @param yAxisCoordinateFixed yAxisCoordinateFixed 73 */ FilledLineChart(ProfilerChartsView bottomPanel, String name, boolean stacked, boolean yAxisCoordinateFixed)74 public FilledLineChart(ProfilerChartsView bottomPanel, String name, boolean stacked, boolean yAxisCoordinateFixed) { 75 super(bottomPanel, name); 76 this.stacked = stacked; 77 this.yAxisCoordinateFixed = yAxisCoordinateFixed; 78 chartType = FILLED_LINE; 79 } 80 81 /** 82 * Init legends 83 */ 84 @Override initLegends()85 protected void initLegends() { 86 } 87 88 /** 89 * Build legends of chart 90 * 91 * @param lastModels Data on the far right side of the panel 92 * @see "The legend shows the y value corresponding to the rightmost X axis, not the mouse hover position" 93 */ 94 @Override buildLegends(List<ChartDataModel> lastModels)95 protected void buildLegends(List<ChartDataModel> lastModels) { 96 } 97 98 /** 99 * Paint chart 100 * 101 * @param graphics Graphics 102 */ 103 @Override paintChart(Graphics graphics)104 protected void paintChart(Graphics graphics) { 105 if (dataMap == null || dataMap.size() == 0) { 106 return; 107 } 108 List<ChartDataModel> lines = dataMap.entrySet().iterator().next().getValue(); 109 // Sort from small to large according to [index] 110 lines.sort(Comparator.comparingInt(ChartDataModel::getIndex)); 111 lines.forEach((line) -> paintFilledLine(line.getIndex(), graphics)); 112 } 113 114 /** 115 * Draw filled poly line 116 * 117 * @param index index of line chart 118 * @param graphics Graphics 119 * @see "Stacked method: the y value of the current broken line point is the sum of all the Y values below 120 * the current broken line. After the Y point is added, the Y point of the line below it should be added from 121 * right to left to form a closed figure." 122 * @see "If line charts do not need to be stacked, The model should go from [small to large] according to the index, 123 * and the value should go from [large to small]. Otherwise, smaller values will be masked." 124 */ paintFilledLine(int index, Graphics graphics)125 private void paintFilledLine(int index, Graphics graphics) { 126 Polygon polygon = new Polygon(); 127 int[] timeArray = dataMap.keySet().stream().mapToInt(Integer::valueOf).toArray(); 128 graphics.setColor(getCurrentLineColor(index, dataMap.get(timeArray[0]))); 129 /* 130 * Stacked scheme: add the point of the current index line from left to back, and then add the point of 131 * the next index line from right to left 132 */ 133 // Adds the point of the current polyline from left to right 134 for (int time : timeArray) { 135 int pointX = startXCoordinate + multiply(pixelPerX, time - startTime); 136 int valueY; 137 if (stacked) { 138 valueY = getListSum(dataMap.get(time), index); 139 } else { 140 valueY = getModelValueByIndex(dataMap.get(time), index); 141 } 142 // If the current value exceeds the maximum, the maximum update value is 1.5 times the current value 143 if (yAxisCoordinateFixed) { 144 maxUnitY = Y_AXIS_COORDINATE_MAX_VALUE; 145 } else { 146 if (valueY > maxUnitY) { 147 maxUnitY = divideInt(valueY * NUM_3, NUM_2); 148 } 149 } 150 int pointY = yZero + multiply(pixelPerY, valueY); 151 polygon.addPoint(pointX, pointY); 152 } 153 // Draw the line below 154 paintAssistLine(index, polygon, timeArray); 155 // Use the brush to fill the polygon to form a filled poly line 156 graphics.fillPolygon(polygon); 157 } 158 paintAssistLine(int index, Polygon polygon, int[] timeArray)159 private void paintAssistLine(int index, Polygon polygon, int[] timeArray) { 160 // If nextLine does not exist, it indicates that index is the last line. You can directly add Y0 points 161 // at the beginning and end of the line. You don't need to cycle through all the points 162 int nextLineIndex = getNextLineIndex(index, dataMap.get(timeArray[0])); 163 if (nextLineIndex == INITIAL_VALUE || !stacked) { 164 int endX = startXCoordinate + multiply(pixelPerX, timeArray[timeArray.length - 1] - startTime); 165 int startX = startXCoordinate + multiply(pixelPerX, timeArray[0] - startTime); 166 polygon.addPoint(endX, yZero); 167 polygon.addPoint(startX, yZero); 168 } else { 169 // Add the point of the next index line from right to left 170 for (int time = timeArray.length - 1; time >= 0; time--) { 171 // Calculate the X and Y points of the data on the line chart 172 int pointX = startXCoordinate + multiply(pixelPerX, timeArray[time] - startTime); 173 int sum = getListSum(dataMap.get(timeArray[time]), nextLineIndex); 174 // If the current value exceeds the maximum, the maximum update value is 1.5 times the current value 175 if (yAxisCoordinateFixed) { 176 maxUnitY = Y_AXIS_COORDINATE_MAX_VALUE; 177 } else { 178 if (sum > maxUnitY) { 179 maxUnitY = divideInt(sum * NUM_3, NUM_2); 180 } 181 } 182 int pointY = yZero + multiply(pixelPerY, sum); 183 polygon.addPoint(pointX, pointY); 184 } 185 } 186 } 187 188 /** 189 * Build tooltip content 190 * 191 * @param showKey Key to show 192 * @param actualKey The actual value of the key in the data map 193 * @param newChart Is it a new chart 194 */ 195 @Override buildTooltip(int showKey, int actualKey, boolean newChart)196 protected void buildTooltip(int showKey, int actualKey, boolean newChart) { 197 } 198 199 /** 200 * User defined events 201 * 202 * @param event MouseEvent 203 */ 204 @Override leftMouseClickEvent(MouseEvent event)205 protected void leftMouseClickEvent(MouseEvent event) { 206 } 207 208 /** 209 * User defined events 210 * 211 * @param event MouseEvent 212 */ 213 @Override rightMouseClickEvent(MouseEvent event)214 protected void rightMouseClickEvent(MouseEvent event) { 215 } 216 217 /** 218 * User defined events 219 * 220 * @param event MouseEvent 221 */ 222 @Override mouseDraggedEvent(MouseEvent event)223 protected void mouseDraggedEvent(MouseEvent event) { 224 } 225 226 /** 227 * User defined events 228 * 229 * @param event MouseEvent 230 */ 231 @Override mouseReleaseEvent(MouseEvent event)232 protected void mouseReleaseEvent(MouseEvent event) { 233 } 234 } 235