From d2d71a6bc8e445ee60b7be2667676138e277d676 Mon Sep 17 00:00:00 2001
From: feiyu02 <risaku@163.com>
Date: 星期五, 28 六月 2024 17:41:08 +0800
Subject: [PATCH] 1. 修改走航报告自动输出模块

---
 src/main/kotlin/com/flightfeather/uav/biz/FactorFilter.kt                                      |   36 +++
 src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/model/DataAnalysisConfig.kt             |    8 
 src/main/kotlin/com/flightfeather/uav/common/chart/ChartUtil.kt                                |  130 +++++++++++-
 src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/exceptiontype/ExceptionValueMutation.kt |    2 
 src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/BaseExceptionContinuousSingle.kt        |    7 
 src/main/resources/templates/report-underway.ftl                                               |   99 +++++++++
 src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/BaseExceptionContinuous.kt              |   15 +
 src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/ExceptionAnalysisController.kt          |    6 
 src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/exceptiontype/ExceptionSlideAverage.kt  |  201 ++++++++++++++++++++
 src/main/kotlin/com/flightfeather/uav/common/chart/DataToChartUtil.kt                          |   13 
 src/main/kotlin/com/flightfeather/uav/biz/report/MissionReport.kt                              |   28 ++
 11 files changed, 500 insertions(+), 45 deletions(-)

diff --git a/src/main/kotlin/com/flightfeather/uav/biz/FactorFilter.kt b/src/main/kotlin/com/flightfeather/uav/biz/FactorFilter.kt
index 3589b05..080d087 100644
--- a/src/main/kotlin/com/flightfeather/uav/biz/FactorFilter.kt
+++ b/src/main/kotlin/com/flightfeather/uav/biz/FactorFilter.kt
@@ -35,7 +35,41 @@
 
     companion object{
         fun builder() = FactorFilter().Builder()
-        fun default() = builder().create()
+        fun default() = builder()
+            .withMain(FactorType.VOC)
+            .withSubs(listOf(
+                FactorType.H2S,
+                FactorType.O3,
+                FactorType.PM25,
+            ))
+            .withMain(FactorType.H2S)
+            .withSubs(listOf(
+                FactorType.VOC,
+                FactorType.O3,
+                FactorType.PM25,
+            ))
+            .withMain(FactorType.O3)
+            .withSubs(listOf(
+                FactorType.VOC,
+                FactorType.H2S,
+                FactorType.PM25,
+            ))
+            .withMain(FactorType.PM25)
+            .withSubs(listOf(
+                FactorType.VOC,
+                FactorType.H2S,
+                FactorType.O3,
+            ))
+//            .withSubs(listOf(
+//                FactorType.NO2,
+//                FactorType.CO,
+//                FactorType.SO2,
+//                FactorType.O3,
+//                FactorType.PM25,
+//                FactorType.PM10,
+//                FactorType.VOC
+//            ))
+            .create()
     }
 
     // 鎵�閫夊洜瀛愰泦鍚�
diff --git a/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/BaseExceptionContinuous.kt b/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/BaseExceptionContinuous.kt
index 2ea3314..c5ab169 100644
--- a/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/BaseExceptionContinuous.kt
+++ b/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/BaseExceptionContinuous.kt
@@ -70,6 +70,8 @@
 
     /**
      * 鍒ゆ柇寮傚父鍑虹幇鐨勮繛缁椂闀挎槸鍚︽弧瓒虫潯浠�
+     * @param sIndex
+     * @param eIndex
      */
     abstract fun judgeDuration(sIndex: Int, eIndex: Int): Boolean
 
@@ -83,6 +85,7 @@
 //            existException.add(false)
 //            exceptionData.add(mutableListOf())
 //        }
+        tagMap.clear()
         config.factorFilter.mainList().forEach {f->
             tagMap[f] = Tag()
         }
@@ -91,16 +94,18 @@
     override fun onNextData(data: BaseRealTimeData) {
         val isContinue = isContinuous(lastData, data)
         val hasException = judgeException(lastData, data)
-        config.factorFilter.mainList().forEach {f->
+        config.factorFilter.selectedList.forEach {s->
+            val f = s.main
             tagMap[f]?.let {
                 it.eIndex++
                 // 璧峰鏁版嵁
+                it.endData = lastData
                 if (it.endData == null) {
                     it.refreshAfterCheckResult(data)
                 }
                 // 鍒ゆ柇鐩搁偦鏁版嵁鏄惁杩炵画骞朵笖鏄惁婊¤冻寮傚父鍒ゆ柇
                 if (!isContinue) {
-                    checkResult()
+                    checkResult(s)
                     // 鏁版嵁涓嶈繛缁椂锛岃褰曞紓甯告儏鍐�
                     if (it.eIndex - it.sIndex >= durationCount) {
                         it.refreshAfterCheckResult(data)
@@ -111,7 +116,7 @@
                         it.exceptionData.add(data)
                     } else {
                         // 寮傚父涓嶅啀閲嶅鍑虹幇鏃讹紝璁板綍寮傚父鎯呭喌
-                        checkResult()
+                        checkResult(s)
                         if (it.eIndex - it.sIndex >= durationCount) {
                             it.refreshAfterCheckResult(data)
                         }
@@ -165,7 +170,7 @@
     open fun checkResult(factor: FactorFilter.SelectedFactor? = null) {
         val tag = tagMap[factor?.main]
         if (factor != null && tag != null) {
-            if (tag.existException && judgeDuration(tag.sIndex, tag.eIndex)) {
+            if (tag.existException && judgeDuration(tag.sIndex, tag.eIndex - 1)) {
                 tag.startData?.let {
                     resultList.add(newResult(it, lastData, factor, tag.exceptionData))
                 }
@@ -174,7 +179,7 @@
         } else {
             config.factorFilter.selectedList.forEach { f ->
                 val tag1 = tagMap[f.main] ?: return@forEach
-                if (tag1.existException && judgeDuration(tag1.sIndex, tag1.eIndex)) {
+                if (tag1.existException && judgeDuration(tag1.sIndex, tag1.eIndex - 1)) {
                     tag1.startData?.let {
                         resultList.add(newResult(it, lastData, f, tag1.exceptionData))
                     }
diff --git a/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/BaseExceptionContinuousSingle.kt b/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/BaseExceptionContinuousSingle.kt
index e65d775..f712600 100644
--- a/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/BaseExceptionContinuousSingle.kt
+++ b/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/BaseExceptionContinuousSingle.kt
@@ -11,7 +11,8 @@
     override fun onNextData(data: BaseRealTimeData) {
         val isContinue = isContinuous(lastData, data)
         val hasException = judgeException(lastData, data)
-        config.factorFilter.mainList().forEach {f->
+        config.factorFilter.selectedList.forEach {s->
+            val f = s.main
             tagMap[f]?.let {
                 it.eIndex++
                 if (lastData == null) {
@@ -19,7 +20,7 @@
                 }
                 // 鍒ゆ柇鐩搁偦鏁版嵁鏄惁杩炵画骞朵笖鏄惁婊¤冻寮傚父鍒ゆ柇
                 if (!isContinue) {
-                    checkResult()
+                    checkResult(s)
                     it.sIndex = it.eIndex
                     it.startData = data
                 } else {
@@ -31,7 +32,7 @@
                         }
                         it.existException = true
                     } else {
-                        checkResult()
+                        checkResult(s)
                     }
                 }
             }
diff --git a/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/ExceptionAnalysisController.kt b/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/ExceptionAnalysisController.kt
index 45008af..d73f3a7 100644
--- a/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/ExceptionAnalysisController.kt
+++ b/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/ExceptionAnalysisController.kt
@@ -2,6 +2,7 @@
 
 import com.flightfeather.uav.biz.FactorFilter
 import com.flightfeather.uav.biz.dataanalysis.exceptiontype.ExceptionDataExceed
+import com.flightfeather.uav.biz.dataanalysis.exceptiontype.ExceptionSlideAverage
 import com.flightfeather.uav.biz.dataanalysis.exceptiontype.ExceptionValueMutation
 import com.flightfeather.uav.biz.dataanalysis.model.DataAnalysisConfig
 import com.flightfeather.uav.biz.dataanalysis.model.ExceptionResult
@@ -25,8 +26,9 @@
     private fun initTask(config: DataAnalysisConfig) {
         taskList.clear()
         taskList.apply {
-            add(ExceptionDataExceed(config))
-            add(ExceptionValueMutation(config))
+//            add(ExceptionDataExceed(config))
+//            add(ExceptionValueMutation(config))
+            add(ExceptionSlideAverage(config))
         }
     }
 
diff --git a/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/exceptiontype/ExceptionSlideAverage.kt b/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/exceptiontype/ExceptionSlideAverage.kt
new file mode 100644
index 0000000..391e20d
--- /dev/null
+++ b/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/exceptiontype/ExceptionSlideAverage.kt
@@ -0,0 +1,201 @@
+package com.flightfeather.uav.biz.dataanalysis.exceptiontype
+
+import com.flightfeather.uav.biz.FactorFilter
+import com.flightfeather.uav.biz.dataanalysis.BaseExceptionAnalysis
+import com.flightfeather.uav.biz.dataanalysis.model.DataAnalysisConfig
+import com.flightfeather.uav.biz.dataanalysis.model.ExceptionType
+import com.flightfeather.uav.domain.entity.BaseRealTimeData
+import com.flightfeather.uav.socket.eunm.FactorType
+import kotlin.math.abs
+
+/**
+ * 婊戝姩骞冲潎鍊肩獊鍙樺紓甯�
+ */
+class ExceptionSlideAverage(config: DataAnalysisConfig) : BaseExceptionAnalysis(config) {
+
+    private val historyDataList = mutableListOf<BaseRealTimeData>()
+    private val tempDataList = mutableListOf<BaseRealTimeData>()
+    private var lastData: BaseRealTimeData? = null
+//    private val avgListReverse = mutableListOf<Pair<Double, Boolean>>()
+//    private var startData: BaseRealTimeData? = null
+//    private var sIndex = 0
+//    private var eIndex = -1
+//    private var existException = false
+
+    inner class Tag {
+        // 璧峰鏁版嵁涓嬫爣
+        var sIndex = 0
+
+        // 璧峰鏁版嵁瀵硅薄
+        var startData :BaseRealTimeData? = null
+
+        // 鏈熬鏁版嵁涓嬫爣
+        var eIndex = -1
+
+        // 鏈熬鏁版嵁瀵硅薄
+        var endData: BaseRealTimeData? = null
+
+        // 鏁版嵁缁勫潎鍊肩殑闆嗗悎
+        val avgListReverse = mutableListOf<Pair<Double, Boolean>>()
+
+        // 寮傚父鏁版嵁娈�
+        var exceptionData = mutableListOf<BaseRealTimeData>()
+
+        // 鏄惁瀛樺湪寮傚父
+        var existException = false
+
+        fun refreshAfterCheckResult(data: BaseRealTimeData) {
+            // 鍒ゆ柇骞舵洿鏂拌捣濮嬬偣浣嶇疆
+//            val len = config.changeTrendGroup - 1 + config.changeTrendTimes + config.changeTrendInterval
+            val len = config.changeTrendGroup
+            if ((eIndex - sIndex + 1) > len) {
+                sIndex = eIndex + 1 - len
+                startData = historyDataList[sIndex]
+                exceptionData.clear()
+                exceptionData.addAll(historyDataList.subList(sIndex, eIndex + 1))
+            }
+        }
+    }
+
+    protected val tagMap = mutableMapOf<FactorType, Tag>()
+
+    override fun init() {
+        super.init()
+        historyDataList.clear()
+        tempDataList.clear()
+        lastData = null
+
+        tagMap.clear()
+        config.factorFilter.mainList().forEach {f->
+            tagMap[f] = Tag()
+        }
+//        avgListReverse.clear()
+//        startData = null
+//        sIndex = 0
+//        eIndex = -1
+//        existException = false
+    }
+
+    override fun getExceptionType(): ExceptionType = ExceptionType.TYPE7
+
+    override fun onNextData(data: BaseRealTimeData) {
+        historyDataList.add(data)
+        // 鏁版嵁鍔犲叆涓存椂鏁扮粍
+        tempDataList.add(data)
+        // 鏁版嵁閲忚秴鍑鸿缃暟閲忔椂锛屽幓闄ゅ綋鍓嶆暟鎹粍棣栦釜鏁版嵁
+        if (tempDataList.size > config.changeTrendGroup) {
+            tempDataList.removeAt(0)
+        }
+        config.factorFilter.selectedList.forEach {s->
+            val f = s.main
+            tagMap[f]?.let {
+                it.eIndex++
+                it.endData = lastData
+                if (it.startData == null) {
+                    it.startData = data
+                }
+                // 鏁版嵁閲忕瓑浜庤缃暟閲忔椂锛岃绠楀綋鍓嶆暟鎹粍鍧囧��
+                if (tempDataList.size == config.changeTrendGroup) {
+                    calAvg(f, tempDataList)
+                    if (checkSlideAvg(f)) {
+                        it.existException = true
+                        it.exceptionData.add(data)
+                    } else {
+                        checkResult(s)
+                        it.refreshAfterCheckResult(data)
+                    }
+                }
+            }
+        }
+        lastData = data
+    }
+
+    override fun onDone() {
+        checkResult()
+    }
+
+    /**
+     * 璁$畻涓�缁勬暟鎹殑鍧囧��
+     */
+    private fun calAvg(type: FactorType, list: List<BaseRealTimeData>) {
+        var total = .0
+        var valid = true
+        val count = list.size
+        if (count == 0) return
+        list.forEach {
+            val v = it.getByFactorType(type)
+            if (v == null) {
+                valid = false
+            } else {
+                total += v
+            }
+        }
+        val avg = total / count
+        tagMap[type]?.avgListReverse?.add(0, Pair(avg, valid))
+    }
+
+    /**
+     * 璁$畻鏁版嵁缁勪箣闂寸殑鍧囧�煎樊寮傛槸鍚﹁繛缁秴杩囬檺瀹氭瘮鐜�
+     */
+    private fun checkSlideAvg(type: FactorType): Boolean {
+        val tag = tagMap[type] ?: return false
+        // 璁$畻婊戝姩鍧囧�兼渶浣庤姹備釜鏁�
+        val minSize = config.changeTrendTimes + config.changeTrendInterval
+        if (tag.avgListReverse.size < minSize) {
+            return false
+        } else {
+            // 婊戝姩鍧囧�兼弧瓒虫暟閲忔椂锛岃绠楀潎鍊间箣闂存槸鍚﹁繛缁秴杩囬檺瀹氭瘮鐜�
+            val rateList = mutableListOf<Pair<Double, Boolean>>()
+            for (i in tag.avgListReverse.indices) {
+                if (i >= config.changeTrendTimes) break
+                val r = calAvgChangeRate(tag.avgListReverse[i], tag.avgListReverse[i + config.changeTrendInterval])
+                rateList.add(r)
+            }
+            for (y in rateList) {
+                if (!y.second || y.first < config.changeTrendRate) {
+                    return false
+                }
+            }
+            return true
+        }
+    }
+
+    /**
+     * 璁$畻婊戝姩鍧囧�煎彉鍖栫巼
+     * 姹俛1鐩稿浜巃2鐨勫彉鍖栫巼
+     */
+    private fun calAvgChangeRate(a1: Pair<Double, Boolean>, a2: Pair<Double, Boolean>): Pair<Double, Boolean> {
+        val valid = a1.second && a2.second
+        return if (a2.first == .0) {
+            Pair(1.0, valid)
+        } else {
+            Pair(abs(a1.first - a2.first) / a2.first, valid)
+        }
+    }
+
+    /**
+     * 褰撳墠鏁版嵁鏈嚭鐜板紓甯告椂锛屾垨鏁版嵁寰幆缁撴潫鏃讹紝鍒ゆ柇鍚庣画姝ラ
+     */
+    private fun checkResult(factor: FactorFilter.SelectedFactor? = null) {
+        val tag = tagMap[factor?.main]
+        if (factor != null && tag != null) {
+            if (tag.existException) {
+                tag.startData?.let {
+                    resultList.add(newResult(it, lastData, factor, tag.exceptionData))
+                }
+                tag.existException = false
+            }
+        } else {
+            config.factorFilter.selectedList.forEach { f ->
+                val tag1 = tagMap[f.main] ?: return@forEach
+                if (tag1.existException) {
+                    tag1.startData?.let {
+                        resultList.add(newResult(it, lastData, f, tag1.exceptionData))
+                    }
+                    tag1.existException = false
+                }
+            }
+        }
+
+    }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/exceptiontype/ExceptionValueMutation.kt b/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/exceptiontype/ExceptionValueMutation.kt
index 0a9e73c..fdaa83c 100644
--- a/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/exceptiontype/ExceptionValueMutation.kt
+++ b/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/exceptiontype/ExceptionValueMutation.kt
@@ -34,6 +34,7 @@
             val b2 = r >= config.mutationRate
             if (b1) special = true
             res[f] = (b1 || b2)
+            if (res[f] == true) println("p: $pValue --- n: $nValue --- r: $r")
         }
 //        repeat(config.factorCount) { i->
 //            if (p?.getByFactorIndex(i) == null || n.getByFactorIndex(i) == null) {
@@ -56,6 +57,7 @@
         val b1 = special && (eIndex - sIndex) >= (config.mutationNum / 2)
         val b2 = (eIndex - sIndex) >= config.mutationNum
         special = false
+        println("sIndex: $sIndex --- eIndex: $eIndex --- special: $special")
         return b1 || b2
     }
 }
\ No newline at end of file
diff --git a/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/model/DataAnalysisConfig.kt b/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/model/DataAnalysisConfig.kt
index 06ffce6..877df87 100644
--- a/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/model/DataAnalysisConfig.kt
+++ b/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/model/DataAnalysisConfig.kt
@@ -20,4 +20,12 @@
     var mutationNum = 6
     // 绐佸彉鐜�
     var mutationRate = .5
+
+    // 姹傛粦鍔ㄥ钩鍧囧�肩殑鏁版嵁缁勪釜鏁�
+    var changeTrendGroup = 12
+    // 婊戝姩骞冲潎鍊艰繛缁�
+    var changeTrendInterval = 12
+    var changeTrendRate = 1
+    // 婊戝姩骞冲潎鍊煎彉鍖栫巼寮傚父杩炵画娆℃暟
+    var changeTrendTimes = 3
 }
\ No newline at end of file
diff --git a/src/main/kotlin/com/flightfeather/uav/biz/report/MissionReport.kt b/src/main/kotlin/com/flightfeather/uav/biz/report/MissionReport.kt
index ccc8b72..62bbcf6 100644
--- a/src/main/kotlin/com/flightfeather/uav/biz/report/MissionReport.kt
+++ b/src/main/kotlin/com/flightfeather/uav/biz/report/MissionReport.kt
@@ -4,7 +4,6 @@
 import com.flightfeather.uav.biz.dataanalysis.ExceptionAnalysisController
 import com.flightfeather.uav.biz.dataanalysis.model.ExceptionResult
 import com.flightfeather.uav.biz.dataprocess.PreData
-import com.flightfeather.uav.common.chart.ChartUtil
 import com.flightfeather.uav.common.chart.DataToChartUtil
 import com.flightfeather.uav.common.exception.BizException
 import com.flightfeather.uav.common.pdf.GeneratePdfUtil
@@ -13,7 +12,6 @@
 import com.flightfeather.uav.domain.entity.Mission
 import com.flightfeather.uav.domain.repository.MissionRep
 import com.flightfeather.uav.domain.repository.RealTimeDataRep
-import com.flightfeather.uav.socket.eunm.FactorType
 import org.springframework.beans.BeanUtils
 import org.springframework.beans.factory.annotation.Value
 import org.springframework.stereotype.Component
@@ -55,11 +53,22 @@
          */
         fun addExceptions(exceptions: List<ExceptionResult>) {
             this.exceptions = exceptions.map {
-                val byteArray = DataToChartUtil.lineToByteArray(it.selectedFactor, it.dataList)
-                val base64Str = ImageUtil.compressImage2(byteArray, 400, needPrefix = false)
                 val c = ExceptionChart()
                 BeanUtils.copyProperties(it, c)
-                c.pict = base64Str
+                // 鍒涘缓涓绘薄鏌撳洜瀛愮殑鏁版嵁鎶樼嚎鍥�
+                val byteArray = DataToChartUtil.lineToByteArray(it.selectedFactor?.main, it.dataList)
+                val base64Str = ImageUtil.compressImage2(byteArray, 800, needPrefix = false)
+                c.mainPict = base64Str
+                // 鍒涘缓鍏宠仈鍥犲瓙鐨勬暟鎹姌绾垮浘
+                if (it.selectedFactor?.subs?.isNotEmpty() == true) {
+                    val subList = mutableListOf<String>()
+                    it.selectedFactor!!.subs.forEach { type ->
+                        val byteArray1 = DataToChartUtil.lineToByteArray(type, it.dataList)
+                        val base64Str1 = ImageUtil.compressImage2(byteArray1, 800, needPrefix = false)
+                        subList.add(base64Str1)
+                    }
+                    c.subPictList = subList
+                }
                 return@map c
             }
         }
@@ -83,8 +92,11 @@
      * 鏁版嵁寮傚父鎵�鍦ㄦ椂娈电殑鎶樼嚎鍥�
      */
     class ExceptionChart : ExceptionResult() {
-        // 鏁版嵁鎶樼嚎鍥綛ase64缂栫爜
-        var pict: String? = null
+        // 姹℃煋鍥犲瓙鐨勫紓甯告暟鎹姌绾垮浘Base64缂栫爜
+        var mainPict: String? = null
+
+        // 鍏宠仈鍥犲瓙鐨勫紓甯告暟鎹姌绾垮浘Base64缂栫爜
+        var subPictList: List<String>? = null
     }
 
     private val templateName = "report-underway.ftl"
@@ -118,7 +130,7 @@
                 max = u.max
 
                 val byteArray = DataToChartUtil.lineToByteArray(t, realTimeData)
-                val base64Str = ImageUtil.compressImage2(byteArray, 400, needPrefix = false)
+                val base64Str = ImageUtil.compressImage2(byteArray, 800, needPrefix = false)
                 pict = base64Str
             })
         }
diff --git a/src/main/kotlin/com/flightfeather/uav/common/chart/ChartUtil.kt b/src/main/kotlin/com/flightfeather/uav/common/chart/ChartUtil.kt
index 4ce6f08..d448cb2 100644
--- a/src/main/kotlin/com/flightfeather/uav/common/chart/ChartUtil.kt
+++ b/src/main/kotlin/com/flightfeather/uav/common/chart/ChartUtil.kt
@@ -3,17 +3,21 @@
 import org.jfree.chart.ChartFactory
 import org.jfree.chart.ChartUtils
 import org.jfree.chart.JFreeChart
-import org.jfree.chart.labels.StandardCategoryItemLabelGenerator
+import org.jfree.chart.axis.NumberAxis
+import org.jfree.chart.axis.NumberTickUnit
 import org.jfree.chart.labels.StandardCategorySeriesLabelGenerator
 import org.jfree.chart.plot.CategoryPlot
+import org.jfree.chart.plot.PlotOrientation
 import org.jfree.chart.renderer.category.LineAndShapeRenderer
-import org.jfree.chart.title.TextTitle
 import org.jfree.data.category.DefaultCategoryDataset
 import java.awt.BasicStroke
 import java.awt.Color
 import java.awt.Font
 import java.awt.Paint
 import java.io.ByteArrayOutputStream
+import java.text.DecimalFormat
+import kotlin.math.max
+import kotlin.math.min
 
 /**
  * 鍥捐〃鐢熸垚
@@ -22,40 +26,87 @@
  */
 object ChartUtil {
 
-    data class ChartValue(val x: String?, val y: Number)
+    private const val FONT_NAME = "SimHei"
+    private val defaultColors = listOf(
+        Color(191, 0, 0),
+        Color(181, 125, 69),
+        Color(236, 204, 6),
+        Color(84, 151, 57),
+        Color(13, 221, 151),
+        Color(57, 75, 151),
+        Color(115, 51, 206),
+    )
 
-    fun line(title: String, dataset: DefaultCategoryDataset): JFreeChart {
-        val line = ChartFactory.createLineChart(title, "鏃堕棿", "娴撳害", dataset)
-//        line.categoryPlot.domainAxis.labelFont = Font("瀹嬩綋", Font.PLAIN, 12)
-//        line.categoryPlot.rangeAxis.labelFont = Font("瀹嬩綋", Font.PLAIN, 12)
+    // 鍥捐〃鏁版嵁鍊煎潗鏍�
+    data class ChartValue(val x: String?, val y: Double)
+    // 鍥捐〃鏁版嵁闆�
+    data class ChartDataset(val dataset: DefaultCategoryDataset, val minValue: Double, val maxValue: Double)
+
+    /**
+     * 鍒涘缓鎶樼嚎鍥�
+     */
+    fun line(title: String, chartDatasetList: List<ChartDataset>): JFreeChart? {
+        if (chartDatasetList.isEmpty()) return null
+
+        val line = ChartFactory.createLineChart(
+            title, "鏃堕棿", "娴撳害(渭g/m3)",
+            null, PlotOrientation.VERTICAL, true, false, false
+        )
+//        val line = ChartFactory.createLineChart(
+//            title, "", "娴撳害(渭g/m鲁)",
+//            null, PlotOrientation.VERTICAL, true, false, false
+//        )
+        var minValue = chartDatasetList[0].minValue
+        var maxValue = chartDatasetList[0].maxValue
+        val datasetList = mutableListOf<DefaultCategoryDataset>()
+        chartDatasetList.forEach {
+            minValue = min(minValue, it.minValue)
+            maxValue = max(maxValue, it.maxValue)
+            datasetList.add(it.dataset)
+        }
+        val plot = line.categoryPlot
         setLine(line)
+        setRenderer(plot, datasetList)
+        setX(plot)
+        setY(plot, "娴撳害(渭g/m3)", minValue, maxValue)
         return line
     }
 
-    fun lineToByteArray(title: String, dataset: DefaultCategoryDataset):ByteArray {
+    /**
+     * 鏂板缓鎶樼嚎鍥惧苟杞崲涓篵yte鏁扮粍
+     */
+    fun lineToByteArray(title: String, dataset: List<ChartDataset>): ByteArray {
         val chart = line(title, dataset)
         val output = ByteArrayOutputStream()
-        ChartUtils.writeChartAsPNG(output, chart, 600, 400)
+        ChartUtils.writeChartAsJPEG(output, chart, 1080, 607)
         val byteArray = output.toByteArray()
         output.flush()
         output.close()
         return byteArray
     }
 
-    fun newDataset(dataList:List<ChartValue>, seriesName:String): DefaultCategoryDataset {
+    /**
+     * 鏂板缓鍥捐〃鏁版嵁闆�
+     */
+    fun newDataset(dataList: List<ChartValue>, seriesName: String): ChartDataset? {
+        if (dataList.isEmpty()) return null
         val dataset = DefaultCategoryDataset()
+        var minValue = dataList[0].y
+        var maxValue = minValue
         dataList.forEach {
             dataset.setValue(it.y, seriesName, it.x)
+            minValue = min(minValue, it.y)
+            maxValue = max(maxValue, it.y)
         }
-        return dataset
+        return ChartDataset(dataset, minValue, maxValue)
     }
 
     /**
      * 璁剧疆鎶樼嚎鍥炬牱寮�
      */
     private fun setLine(chart: JFreeChart) {
-        chart.legend.itemFont = Font("SimHei", Font.PLAIN, 16)
-        chart.title.font = Font("SimHei", Font.BOLD, 20)
+        chart.legend.itemFont = Font(FONT_NAME, Font.PLAIN, 16)
+        chart.title.font = Font(FONT_NAME, Font.BOLD, 20)
         chart.categoryPlot.apply {
             backgroundPaint = Color(255, 255, 255)
             rangeGridlinePaint = Color(200, 200, 200)
@@ -64,11 +115,58 @@
         }
     }
 
-    private fun setRenderer(plot:CategoryPlot, datasetList:List<DefaultCategoryDataset>) {
-        val renderer = newBasicRenderer(paint = Color(191, 0, 0))
+    private fun setRenderer(plot: CategoryPlot, datasetList: List<DefaultCategoryDataset>) {
+        datasetList.forEachIndexed { index, v ->
+            val renderer = newBasicRenderer(paint = defaultColors[index % defaultColors.size])
+            plot.setDataset(index, v)
+            plot.setRenderer(index, renderer)
+        }
+
     }
 
-    private fun newBasicRenderer(series:Int = 0, paint:Paint): LineAndShapeRenderer {
+    private fun setX(plot: CategoryPlot) {
+        plot.domainAxis.apply {
+            // 璁剧疆x杞存瘡涓埢搴︾殑瀛椾綋
+            tickLabelFont = Font(FONT_NAME, Font.BOLD, 16)
+            // 璁剧疆x杞存爣绛剧殑瀛椾綋
+            labelFont = Font(FONT_NAME, Font.BOLD, 20)
+            // 璁剧疆x杞磋酱绾挎槸鍚︽樉绀�
+            isAxisLineVisible = false
+            // 璁剧疆x杞村埢搴︽槸鍚︽樉绀�
+            isTickMarksVisible = false
+
+            upperMargin = .0
+            lowerMargin = .0
+        }
+    }
+
+    private fun setY(plot: CategoryPlot, label:String, minValue: Double, maxValue: Double) {
+        val tickUnit = (maxValue - minValue) / 10
+        val axis1 = NumberAxis().apply {
+            this.label = label
+            // 鍒诲害灞曠ず鏍煎紡鍖�
+            numberFormatOverride = DecimalFormat("0.0")
+            if (tickUnit != .0) {
+                // 鍙栨秷鑷姩鍒嗛厤闂磋窛
+                isAutoTickUnitSelection = false
+                // 璁剧疆闂撮殧璺濈
+                setTickUnit(NumberTickUnit(tickUnit))
+                // 璁剧疆鏄剧ず鑼冨洿
+                setRange(minValue, maxValue)
+            }
+            // 璁剧疆y杞存瘡涓埢搴︾殑瀛椾綋
+            tickLabelFont = Font(FONT_NAME, Font.BOLD, 16)
+            // 璁剧疆y杞存爣绛剧殑瀛椾綋
+            labelFont = Font(FONT_NAME, Font.BOLD, 20)
+            // 璁剧疆y杞磋酱绾夸笉鏄剧ず
+            isAxisLineVisible = false
+            // 璁剧疆y杞村埢搴︿笉鏄剧ず
+            isTickMarksVisible = false
+        }
+        plot.rangeAxis = axis1
+    }
+
+    private fun newBasicRenderer(series: Int = 0, paint: Paint): LineAndShapeRenderer {
         return LineAndShapeRenderer().apply {
             legendItemLabelGenerator = StandardCategorySeriesLabelGenerator()
             setSeriesStroke(series, BasicStroke(3f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 2000f))
diff --git a/src/main/kotlin/com/flightfeather/uav/common/chart/DataToChartUtil.kt b/src/main/kotlin/com/flightfeather/uav/common/chart/DataToChartUtil.kt
index 1530fc2..6222166 100644
--- a/src/main/kotlin/com/flightfeather/uav/common/chart/DataToChartUtil.kt
+++ b/src/main/kotlin/com/flightfeather/uav/common/chart/DataToChartUtil.kt
@@ -1,6 +1,7 @@
 package com.flightfeather.uav.common.chart
 
 import com.flightfeather.uav.biz.FactorFilter
+import com.flightfeather.uav.common.exception.BizException
 import com.flightfeather.uav.common.utils.DateUtil
 import com.flightfeather.uav.domain.entity.BaseRealTimeData
 import com.flightfeather.uav.socket.eunm.FactorType
@@ -12,16 +13,16 @@
  */
 object DataToChartUtil {
 
-    fun lineToByteArray(type: FactorFilter.SelectedFactor?, data: List<BaseRealTimeData>): ByteArray {
-        val title = type?.main?.des ?: "鏈煡鐩戞祴鍥犲瓙"
-        val seriesName = type?.main?.des ?: "鏈煡绯诲垪"
+    fun lineToByteArray(type: FactorType?, data: List<BaseRealTimeData>): ByteArray {
+        val title = type?.des ?: "鏈煡鐩戞祴鍥犲瓙"
+        val seriesName = type?.des ?: "鏈煡绯诲垪"
         val dataList = data.map { d ->
             ChartUtil.ChartValue(
                 DateUtil.instance.dateToString(d.dataTime, DateUtil.DateStyle.HH_MM_SS),
-                d.getByFactorType(type?.main) ?: 0f
+                d.getByFactorType(type)?.toDouble() ?: .0
             )
         }
-        val dataset = ChartUtil.newDataset(dataList, seriesName)
-        return ChartUtil.lineToByteArray(title, dataset)
+        val dataset = ChartUtil.newDataset(dataList, seriesName) ?: throw BizException("鎶樼嚎鍥炬暟鎹泦涓虹┖")
+        return ChartUtil.lineToByteArray(title, listOf(dataset))
     }
 }
\ No newline at end of file
diff --git a/src/main/resources/templates/report-underway.ftl b/src/main/resources/templates/report-underway.ftl
index 3b39b2d..73f9bad 100644
--- a/src/main/resources/templates/report-underway.ftl
+++ b/src/main/resources/templates/report-underway.ftl
@@ -28,6 +28,11 @@
                 <Relationship Id="imageId_${item_index}"
                     Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image"
                     Target="media/image_${item_index}.png" />
+                <#list item.subPictList as subItem>
+                <Relationship Id="imageId_sub_${item_index}-${subItem_index}"
+                    Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image"
+                    Target="media/image_sub_${item_index}-${subItem_index}.png" />
+                </#list>
                 </#list>
                 <#list summary as item>
                 <Relationship Id="imageId2_${item_index}"
@@ -1247,7 +1252,7 @@
                                 <w:szCs w:val="28" />
                                 <w:lang w:val="en-US" w:eastAsia="zh-CN" />
                             </w:rPr>
-                            <w:t>浠�${item.startTime}鑷�${item.endTime}锛�${item.factorName}鍑虹幇${item.exception}<#if item.relatedSceneName??>锛岀粨鍚堢洃娴嬫暟鎹彉鍖栧拰鍙楃偣鍙嶅悜婧簮锛�<#list item.relatedSceneName as scene>${scene}銆�</#list>绛夊彲鑳藉瓨鍦ㄦ薄鏌撻闄┿��<#else>銆�</#if></w:t>
+                            <w:t>${item_index + 1}. 浠�${item.startTime}鑷�${item.endTime}锛�${item.factorName}鍑虹幇${item.exception}<#if item.relatedSceneName??>锛岀粨鍚堢洃娴嬫暟鎹彉鍖栧拰鍙楃偣鍙嶅悜婧簮锛�<#list item.relatedSceneName as scene>${scene}銆�</#list>绛夊彲鑳藉瓨鍦ㄦ薄鏌撻闄┿��<#else>銆�</#if></w:t>
                         </w:r>
                     </w:p>
                     <w:p>
@@ -1274,7 +1279,7 @@
                             <w:pict>
                                 <v:shape id="_x0000_i1025_${item_index}" o:spt="75"
                                     alt="f1725d6e3fb17a0e748045906f55ced" type="#_x0000_t75"
-                                    style="height:222.8pt;width:414.65pt;" filled="f"
+                                    style="height:225pt;width:400pt;" filled="f"
                                     o:preferrelative="t" stroked="f" coordsize="21600,21600">
                                     <v:path />
                                     <v:fill on="f" focussize="0,0" />
@@ -1288,6 +1293,87 @@
                             </w:pict>
                         </w:r>
                     </w:p>
+                    <#if item.subPictList??>
+                    <w:p>
+                        <w:pPr>
+                            <w:keepNext w:val="0" />
+                            <w:keepLines w:val="0" />
+                            <w:pageBreakBefore w:val="0" />
+                            <w:widowControl w:val="0" />
+                            <w:kinsoku />
+                            <w:wordWrap />
+                            <w:overflowPunct />
+                            <w:topLinePunct w:val="0" />
+                            <w:autoSpaceDE />
+                            <w:autoSpaceDN />
+                            <w:bidi w:val="0" />
+                            <w:adjustRightInd />
+                            <w:snapToGrid />
+                            <w:ind w:firstLine="560" w:firstLineChars="200" />
+                            <w:textAlignment w:val="auto" />
+                            <w:rPr>
+                                <w:rFonts w:hint="default" w:ascii="浠垮畫_GB2312" w:hAnsi="浠垮畫"
+                                    w:eastAsia="浠垮畫_GB2312"
+                                    w:cs="Times New Roman" />
+                                <w:sz w:val="28" />
+                                <w:szCs w:val="28" />
+                                <w:lang w:val="en-US" w:eastAsia="zh-CN" />
+                            </w:rPr>
+                        </w:pPr>
+                        <w:r>
+                            <w:rPr>
+                                <w:rFonts w:hint="eastAsia" w:ascii="浠垮畫_GB2312" w:hAnsi="浠垮畫"
+                                    w:eastAsia="浠垮畫_GB2312"
+                                    w:cs="Times New Roman" />
+                                <w:color w:val="000000" />
+                                <w:sz w:val="28" />
+                                <w:szCs w:val="28" />
+                                <w:lang w:val="en-US" w:eastAsia="zh-CN" />
+                            </w:rPr>
+                            <w:t>鍏宠仈鍥犲瓙<#list item.subFactorName as subItem><#if (subItem_index > 0)>銆�</#if>${subItem}</#list>鏁版嵁閲忕骇濡備笅锛�</w:t>
+                        </w:r>
+                    </w:p>
+                    <#list item.subPictList as subItem>
+                    <w:p>
+                        <w:pPr>
+                            <w:jc w:val="center" />
+                            <w:rPr>
+                                <w:rFonts w:hint="default" w:ascii="浠垮畫_GB2312" w:hAnsi="浠垮畫"
+                                    w:eastAsia="浠垮畫_GB2312" />
+                                <w:sz w:val="28" />
+                                <w:szCs w:val="28" />
+                                <w:highlight w:val="none" />
+                                <w:lang w:val="en-US" w:eastAsia="zh-CN" />
+                            </w:rPr>
+                        </w:pPr>
+                        <w:r>
+                            <w:rPr>
+                                <w:rFonts w:hint="default" w:ascii="浠垮畫_GB2312" w:hAnsi="浠垮畫"
+                                    w:eastAsia="浠垮畫_GB2312" />
+                                <w:sz w:val="28" />
+                                <w:szCs w:val="28" />
+                                <w:highlight w:val="none" />
+                                <w:lang w:val="en-US" w:eastAsia="zh-CN" />
+                            </w:rPr>
+                            <w:pict>
+                                <v:shape id="_x0000_i1025_sub_${item_index}-${subItem_index}" o:spt="75"
+                                    alt="f1725d6e3fb17a0e748045906f55ced" type="#_x0000_t75"
+                                    style="height:225pt;width:400pt;" filled="f"
+                                    o:preferrelative="t" stroked="f" coordsize="21600,21600">
+                                    <v:path />
+                                    <v:fill on="f" focussize="0,0" />
+                                    <v:stroke on="f" />
+                                    <v:imagedata r:id="imageId_sub_${item_index}-${subItem_index}"
+                                        o:title="f1725d6e3fb17a0e748045906f55ced" />
+                                    <o:lock v:ext="edit" aspectratio="t" />
+                                    <w10:wrap type="none" />
+                                    <w10:anchorlock />
+                                </v:shape>
+                            </w:pict>
+                        </w:r>
+                    </w:p>
+                    </#list>
+                    </#if>
                     </#list>
                     <w:p>
                         <w:pPr>
@@ -1417,7 +1503,7 @@
                             <w:pict>
                                 <v:shape id="_x0000_i1026_${item_index}" o:spt="75"
                                     alt="f1725d6e3fb17a0e748045906f55ced" type="#_x0000_t75"
-                                    style="height:222.8pt;width:414.65pt;" filled="f"
+                                    style="height:225pt;width:400pt;" filled="f"
                                     o:preferrelative="t" stroked="f" coordsize="21600,21600">
                                     <v:path />
                                     <v:fill on="f" focussize="0,0" />
@@ -1756,8 +1842,13 @@
     </pkg:part>
     <#list exceptions as item>
     <pkg:part pkg:name="/word/media/image_${item_index}.png" pkg:contentType="image/png">
-        <pkg:binaryData>${item.pict}</pkg:binaryData>
+        <pkg:binaryData>${item.mainPict}</pkg:binaryData>
     </pkg:part>
+    <#list item.subPictList as subItem>
+    <pkg:part pkg:name="/word/media/image_sub_${item_index}-${subItem_index}.png" pkg:contentType="image/png">
+        <pkg:binaryData>${subItem}</pkg:binaryData>
+    </pkg:part>
+    </#list>
     </#list>
     <#list summary as item>
     <pkg:part pkg:name="/word/media/image2_${item_index}.png" pkg:contentType="image/png">

--
Gitblit v1.9.3