|
| 1 | +// |
| 2 | +// AAMixedChartComposer.swift |
| 3 | +// AAInfographicsDemo |
| 4 | +// |
| 5 | +// Created by AnAn on 2025/6/5. |
| 6 | +// Copyright © 2025 An An. All rights reserved. |
| 7 | +// |
| 8 | + |
| 9 | +import AAInfographics |
| 10 | + |
| 11 | +class AAMixedChartComposer { |
| 12 | + |
| 13 | + static func barMixedColumnrangeWithPatternFillChart() -> AAOptions { |
| 14 | + // 配置常量 - 使用二级枚举 |
| 15 | + let Colors = [ |
| 16 | + "darker": ["#603EAC", "#7560B1", "#4390AD", "#AF8D0E"], |
| 17 | + "actual": ["#8B5CF6", "#A78BFA", "#60CDF5", "#FACC15"] |
| 18 | + ] |
| 19 | + |
| 20 | + let Dimensions = [ |
| 21 | + "pointWidth": 20 as Float, |
| 22 | + "capHeight": 32 as Float, |
| 23 | + "capWidth": 2 as Float |
| 24 | + ] |
| 25 | + |
| 26 | + let YAxis = [ |
| 27 | + "min": 0 as Double, |
| 28 | + "max": 100 as Double, |
| 29 | + "tickInterval": 10 as Float |
| 30 | + ] as [String : Any] |
| 31 | + |
| 32 | + // 原始数据 |
| 33 | + let sleepData = [ |
| 34 | + "ideal": [ |
| 35 | + ["low": 20, "high": 32, "category": "深睡"], |
| 36 | + ["low": 40, "high": 60, "category": "浅睡"], |
| 37 | + ["low": 10, "high": 25, "category": "REM"], |
| 38 | + ["low": 0, "high": 5, "category": "清醒"] |
| 39 | + ], |
| 40 | + "actual": [ |
| 41 | + ["value": 27, "label": "1小时22分钟(27%)"], |
| 42 | + ["value": 53, "label": "3小时42分钟(53%)"], |
| 43 | + ["value": 18, "label": "1小时49分钟(18%)"], |
| 44 | + ["value": 2, "label": "5分钟(2%)"] |
| 45 | + ] |
| 46 | + ] |
| 47 | + |
| 48 | + /** |
| 49 | + // 数据处理函数 |
| 50 | + function createCategories() { |
| 51 | + return sleepData.ideal.map(item => item.category); |
| 52 | + } |
| 53 | + |
| 54 | + */ |
| 55 | + |
| 56 | + // 数据处理函数 |
| 57 | + func createCategories() -> [String] { |
| 58 | + return sleepData["ideal"]!.map { item in |
| 59 | + return item["category"] as! String |
| 60 | + } |
| 61 | + } |
| 62 | + |
| 63 | + func createBoxplotData() -> [[String: Any]] { |
| 64 | + return sleepData["ideal"]!.enumerated().map { index, item in |
| 65 | + let low = item["low"] as! Int |
| 66 | + let high = item["high"] as! Int |
| 67 | + return [ |
| 68 | + "x": index, |
| 69 | + "low": low, |
| 70 | + "q1": low, |
| 71 | + "median": low, |
| 72 | + "q3": high, |
| 73 | + "high": high, |
| 74 | + "fillColor": "url(#customPattern\(index))", |
| 75 | + "whiskerColor": Colors["darker"]![index] |
| 76 | + ] |
| 77 | + } |
| 78 | + } |
| 79 | + |
| 80 | + func createActualData() -> [[String: Any]] { |
| 81 | + return sleepData["actual"]!.enumerated().map { index, item in |
| 82 | + let value = item["value"] as! Int |
| 83 | + let label = item["label"] as! String |
| 84 | + return [ |
| 85 | + "y": value, |
| 86 | + "color": Colors["actual"]![index], |
| 87 | + "label": label |
| 88 | + ] |
| 89 | + } |
| 90 | + } |
| 91 | + |
| 92 | + // 创建plotLines函数 |
| 93 | + func createPlotLines() -> [AAPlotLinesElement] { |
| 94 | + return sleepData["ideal"]!.enumerated().map { index, item in |
| 95 | + let category = item["category"] as! String |
| 96 | + let label = sleepData["actual"]![index]["label"] as! String |
| 97 | + return AAPlotLinesElement() |
| 98 | + .value(Double(index)) |
| 99 | + .width(0) |
| 100 | + .label(AALabel() |
| 101 | + .text("<span style=\"color:#999999\">\(category)</span> <span style=\"color:#000000\">\(label)</span>".aa_toPureHTMLString()) |
| 102 | + .useHTML(true) |
| 103 | + .align(.left) |
| 104 | + .x(10) |
| 105 | + .y(-20) |
| 106 | + .style(AAStyle() |
| 107 | + .fontSize(20))) |
| 108 | + } |
| 109 | + } |
| 110 | + |
| 111 | + // 图表配置 |
| 112 | + let aaChart = AAChart() |
| 113 | + .type(.columnrange) |
| 114 | + .inverted(true) |
| 115 | + .events(AAChartEvents() |
| 116 | + .load(""" |
| 117 | + function() { |
| 118 | + const renderer = this.renderer; |
| 119 | + let defs = renderer.defs; |
| 120 | + if (!defs) { |
| 121 | + defs = renderer.createElement('defs').add(); |
| 122 | + renderer.defs = defs; |
| 123 | + } |
| 124 | +
|
| 125 | + const Colors = { |
| 126 | + darker: ['#603EAC', '#7560B1', '#4390AD', '#AF8D0E'] |
| 127 | + }; |
| 128 | +
|
| 129 | + const patternHTML = Colors.darker |
| 130 | + .map((color, index) => ` |
| 131 | + <pattern id="customPattern${index}" |
| 132 | + patternUnits="userSpaceOnUse" |
| 133 | + width="3" |
| 134 | + height="3" |
| 135 | + patternTransform="scale(1.4 1.4)"> |
| 136 | + <path d="M 0 3 L 3 0 M -0.5 0.5 L 0.5 -0.5 M 2.5 3.5 L 3.5 2.5" |
| 137 | + fill="none" |
| 138 | + stroke="${color}" |
| 139 | + stroke-width="1" |
| 140 | + stroke-dasharray="none"/> |
| 141 | + </pattern>`) |
| 142 | + .join(''); |
| 143 | +
|
| 144 | + defs.element.innerHTML += patternHTML; |
| 145 | + } |
| 146 | +""")) |
| 147 | + |
| 148 | + let aaXAxis = AAXAxis() |
| 149 | + .categories(createCategories()) |
| 150 | + .lineWidth(0) |
| 151 | + .labels(AALabels() |
| 152 | + .enabled(false)) |
| 153 | + .plotLines(createPlotLines()) |
| 154 | + |
| 155 | + let aaYAxis = AAYAxis() |
| 156 | + .min(YAxis["min"] as! Double) |
| 157 | + .max(YAxis["max"] as! Double) |
| 158 | + .tickInterval(YAxis["tickInterval"] as! Float) |
| 159 | + .title(nil) |
| 160 | + .gridLineWidth(0) |
| 161 | + .labels(AALabels().enabled(false)) |
| 162 | + |
| 163 | + let aaPlotOptions = AAPlotOptions() |
| 164 | + .bar(AABar() |
| 165 | + .grouping(false) |
| 166 | + .borderWidth(0) |
| 167 | + .pointWidth(Dimensions["pointWidth"] as! Float) |
| 168 | + .dataLabels(AADataLabels().enabled(false))) |
| 169 | + .boxplot(AABoxplot() |
| 170 | + .grouping(false) |
| 171 | + .lineWidth(0) |
| 172 | + .medianWidth(0) |
| 173 | + .medianColor("transparent") |
| 174 | + .stemWidth(0) |
| 175 | +// .pointWidth(Dimensions["pointWidth"] as! Float) |
| 176 | +// .whiskerLength(Dimensions["capHeight"] as! Float) |
| 177 | + .whiskerWidth(Dimensions["capWidth"] as! Float) |
| 178 | + .whiskerColor("transparent")) |
| 179 | + |
| 180 | + let aaTooltip = AATooltip() |
| 181 | + .enabled(false) |
| 182 | + |
| 183 | + let aaSeries = [ |
| 184 | + AASeriesElement() |
| 185 | + .name("实际睡眠") |
| 186 | + .type(.bar) |
| 187 | + .data(createActualData()) |
| 188 | + .zIndex(0), |
| 189 | + AASeriesElement() |
| 190 | + .name("理想睡眠区间") |
| 191 | + .type(.boxplot) |
| 192 | + .data(createBoxplotData()) |
| 193 | + .zIndex(1) |
| 194 | + .showInLegend(true) |
| 195 | + .clip(false) |
| 196 | + ] |
| 197 | + |
| 198 | + let aaOptions = AAOptions() |
| 199 | + .chart(aaChart) |
| 200 | + .title(AATitle().text("睡眠阶段 vs 理想区间")) |
| 201 | + .xAxis(aaXAxis) |
| 202 | + .yAxis(aaYAxis) |
| 203 | + .plotOptions(aaPlotOptions) |
| 204 | + .legend(AALegend().enabled(true)) |
| 205 | + .tooltip(aaTooltip) |
| 206 | + .series(aaSeries) |
| 207 | + |
| 208 | + return aaOptions |
| 209 | + } |
| 210 | +} |
0 commit comments