package com.flightfeather.uav.common.chart
|
|
import org.jfree.chart.ChartFactory
|
import org.jfree.chart.ChartUtils
|
import org.jfree.chart.JFreeChart
|
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.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
|
|
/**
|
* 图表生成
|
* @date 2024/5/31
|
* @author feiyu02
|
*/
|
object ChartUtil {
|
|
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),
|
)
|
|
// 图表数据值坐标
|
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
|
}
|
|
/**
|
* 新建折线图并转换为byte数组
|
*/
|
fun lineToByteArray(title: String, dataset: List<ChartDataset>): ByteArray {
|
val chart = line(title, dataset)
|
val output = ByteArrayOutputStream()
|
ChartUtils.writeChartAsJPEG(output, chart, 1080, 607)
|
val byteArray = output.toByteArray()
|
output.flush()
|
output.close()
|
return byteArray
|
}
|
|
/**
|
* 新建图表数据集
|
*/
|
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 ChartDataset(dataset, minValue, maxValue)
|
}
|
|
/**
|
* 设置折线图样式
|
*/
|
private fun setLine(chart: JFreeChart) {
|
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)
|
rangeGridlineStroke = BasicStroke(1f)
|
isRangeGridlinesVisible = true
|
}
|
}
|
|
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 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))
|
setSeriesShapesVisible(series, false)
|
setSeriesPaint(series, paint)
|
}
|
}
|
}
|