百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

在Android折线图中实现不同Y值区间显示不同颜色

bigegpt 2024-08-10 12:15 8 浏览

最近在参与一个安卓项目,不忙的时候也会接一些开发任务,遇到一个需求,要实现一个折线图,且根据Y轴的值对应区间显示不同颜色。Android应用中各种图标的实现多数都是在用MPAndroidChart,我个人没什么深入的研究,就随大流吧。找了一些折线图实现的例子,都没有满足需求的实现方式,但是确认了图标中的线是由LineChartRenderer实现的。

既然现有方法无法实现需求,只能对原方法进行重写了。我们项目主要使用kotlin,所以下面我也以kotlin的语法进行展示,Android studio好像有kotlin和Java的转换工具,如果刚好有同学的需求和我这个类似,用的又是Java的话,可以用工具转成Java类。

先直接展示重写后的MyLineChartRenderer.kt

package com.example.mykotlinandroid.view

import android.graphics.*
import com.github.mikephil.charting.animation.ChartAnimator
import com.github.mikephil.charting.charts.LineChart
import com.github.mikephil.charting.data.Entry
import com.github.mikephil.charting.data.LineDataSet
import com.github.mikephil.charting.highlight.Highlight
import com.github.mikephil.charting.interfaces.dataprovider.LineDataProvider
import com.github.mikephil.charting.interfaces.datasets.IDataSet
import com.github.mikephil.charting.interfaces.datasets.ILineDataSet
import com.github.mikephil.charting.renderer.LineChartRenderer
import com.github.mikephil.charting.utils.ColorTemplate
import com.github.mikephil.charting.utils.ViewPortHandler
import java.util.*

class MyLineChartRenderer(
    chart: LineDataProvider, animator: ChartAnimator?,
    viewPortHandler: ViewPortHandler?
) : LineChartRenderer(chart, animator, viewPortHandler) {

    private var mHighlightCirclePaint: Paint? = null
    private var isHeart = false
    private lateinit var pos: FloatArray
    private lateinit var colors: IntArray
    private lateinit var range: IntArray

    init {
        mChart = chart
        mCirclePaintInner = Paint(Paint.ANTI_ALIAS_FLAG)
        mCirclePaintInner.style = Paint.Style.FILL
        mCirclePaintInner.color = Color.WHITE
        mHighlightCirclePaint = Paint()
    }

    private var mLineBuffer = FloatArray(4)

    override fun drawLinear(c: Canvas?, dataSet: ILineDataSet) {
        val entryCount = dataSet.entryCount
        val isDrawSteppedEnabled = dataSet.mode == LineDataSet.Mode.STEPPED
        val pointsPerEntryPair = if (isDrawSteppedEnabled) 4 else 2
        val trans = mChart.getTransformer(dataSet.axisDependency)
        val phaseY = mAnimator.phaseY
        mRenderPaint.style = Paint.Style.STROKE

        // if the data-set is dashed, draw on bitmap-canvas
        val canvas: Canvas? = if (dataSet.isDashedLineEnabled) {
            mBitmapCanvas
        } else {
            c
        }
        mXBounds[mChart] = dataSet

        // if drawing filled is enabled
        if (dataSet.isDrawFilledEnabled && entryCount > 0) {
            drawLinearFill(c, dataSet, trans, mXBounds)
        }

        // more than 1 color
        if (dataSet.colors.size > 1) {
            if (mLineBuffer.size <= pointsPerEntryPair * 2) mLineBuffer =
                FloatArray(pointsPerEntryPair * 4)
            for (j in mXBounds.min..mXBounds.range + mXBounds.min) {
                var e: Entry = dataSet.getEntryForIndex(j) ?: continue
                if (e.y == 0f) continue
                mLineBuffer[0] = e.x
                mLineBuffer[1] = e.y * phaseY
                if (j < mXBounds.max) {
                    e = dataSet.getEntryForIndex(j + 1)
                    if (e == null) break
                    if (e.y == 0f) break
                    mLineBuffer[2] = e.x
                    if (isDrawSteppedEnabled) {
                        mLineBuffer[3] = mLineBuffer[1]
                        mLineBuffer[4] = mLineBuffer[2]
                        mLineBuffer[5] = mLineBuffer[3]
                        mLineBuffer[6] = e.x
                        mLineBuffer[7] = e.y * phaseY
                    } else {
                        mLineBuffer[3] = e.y * phaseY
                    }
                } else {
                    mLineBuffer[2] = mLineBuffer[0]
                    mLineBuffer[3] = mLineBuffer[1]
                }
                trans.pointValuesToPixel(mLineBuffer)
                if (!mViewPortHandler.isInBoundsRight(mLineBuffer[0])) break

                // make sure the lines don't do shitty things outside
                // bounds
                if (!mViewPortHandler.isInBoundsLeft(mLineBuffer[2])
                    || !mViewPortHandler.isInBoundsTop(mLineBuffer[1]) && !mViewPortHandler
                        .isInBoundsBottom(mLineBuffer[3])
                ) continue

                // get the color that is set for this line-segment
                mRenderPaint.color = dataSet.getColor(j)
                canvas!!.drawLines(mLineBuffer, 0, pointsPerEntryPair * 2, mRenderPaint)
            }
        } else { // only one color per dataset
            if (mLineBuffer.size < (entryCount * pointsPerEntryPair).coerceAtLeast(
                    pointsPerEntryPair
                ) * 2
            ) mLineBuffer = FloatArray(
                (entryCount * pointsPerEntryPair).coerceAtLeast(pointsPerEntryPair) * 4
            )
            var e1: Entry?
            var e2: Entry
            e1 = dataSet.getEntryForIndex(mXBounds.min)
            if (e1 != null) {
                var j = 0
                for (x in mXBounds.min..mXBounds.range + mXBounds.min) {
                    e1 = dataSet.getEntryForIndex(if (x == 0) 0 else x - 1)
                    e2 = dataSet.getEntryForIndex(x)
                    if (e1.y == 0f || e2.y == 0f) {
                        continue
                    }
                    mLineBuffer[j++] = e1.x
                    mLineBuffer[j++] = e1.y * phaseY
                    if (isDrawSteppedEnabled) {
                        mLineBuffer[j++] = e2.x
                        mLineBuffer[j++] = e1.y * phaseY
                        mLineBuffer[j++] = e2.x
                        mLineBuffer[j++] = e1.y * phaseY
                    }
                    mLineBuffer[j++] = e2.x
                    mLineBuffer[j++] = e2.y * phaseY
                }
                if (j > 0) {
                    trans.pointValuesToPixel(mLineBuffer)
                    val size =
                        ((mXBounds.range + 1) * pointsPerEntryPair).coerceAtLeast(pointsPerEntryPair) * 2
                    mRenderPaint.color = dataSet.color
                    if (isHeart) {
                        mRenderPaint.shader = LinearGradient(
                            0f,
                            mViewPortHandler.contentRect.top,
                            0f,
                            mViewPortHandler.contentRect.bottom,
                            colors,
                            pos,
                            Shader.TileMode.CLAMP
                        )
                    }
                    canvas!!.drawLines(mLineBuffer, 0, size, mRenderPaint)
                }
            }
        }
        mRenderPaint.pathEffect = null
    }

    /**
     * cache for the circle bitmaps of all datasets
     */
    private val mImageCaches = HashMap<IDataSet<*>, DataSetImageCache>()
    private val mCirclesBuffer = FloatArray(2)

    override fun drawCircles(c: Canvas) {
        mRenderPaint.style = Paint.Style.FILL
        val phaseY = mAnimator.phaseY
        mCirclesBuffer[0] = 0f
        mCirclesBuffer[1] = 0f
        val dataSets = mChart.lineData.dataSets
        for (i in dataSets.indices) {
            val dataSet = dataSets[i]
            if (!dataSet.isVisible || !dataSet.isDrawCirclesEnabled || dataSet.entryCount == 0) continue
            mCirclePaintInner.color = dataSet.circleHoleColor
            val trans = mChart.getTransformer(dataSet.axisDependency)
            mXBounds[mChart] = dataSet
            val circleRadius = dataSet.circleRadius
            val circleHoleRadius = dataSet.circleHoleRadius
            val drawCircleHole =
                dataSet.isDrawCircleHoleEnabled && circleHoleRadius < circleRadius && circleHoleRadius > 0f
            val drawTransparentCircleHole = drawCircleHole &&
                    dataSet.circleHoleColor == ColorTemplate.COLOR_NONE
            var imageCache: DataSetImageCache?
            if (mImageCaches.containsKey(dataSet)) {
                imageCache = mImageCaches[dataSet]
            } else {
                imageCache = DataSetImageCache(mRenderPaint, mCirclePaintInner)
                mImageCaches[dataSet] = imageCache
            }
            val changeRequired = imageCache!!.init(dataSet)

            // only fill the cache with new bitmaps if a change is required
            if (changeRequired) {
                imageCache.fill(dataSet, drawCircleHole, drawTransparentCircleHole)
            }
            val boundsRangeCount = mXBounds.range + mXBounds.min
            for (j in mXBounds.min..boundsRangeCount) {
                val e = dataSet.getEntryForIndex(j) ?: break
                if (e.y == 0f) continue
                mCirclesBuffer[0] = e.x
                mCirclesBuffer[1] = e.y * phaseY
                trans.pointValuesToPixel(mCirclesBuffer)
                if (!mViewPortHandler.isInBoundsRight(mCirclesBuffer[0])) break
                if (!mViewPortHandler.isInBoundsLeft(mCirclesBuffer[0]) ||
                    !mViewPortHandler.isInBoundsY(mCirclesBuffer[1])
                ) continue
                val circleBitmap = imageCache.getBitmap(j)
                if (circleBitmap != null) {
                    c.drawBitmap(
                        circleBitmap,
                        mCirclesBuffer[0] - circleRadius,
                        mCirclesBuffer[1] - circleRadius,
                        null
                    )
                }
            }
        }
    }

    private class DataSetImageCache(val mRenderPaint: Paint, val mCirclePaintInner: Paint) {
        private val mCirclePathBuffer = Path()
        private var circleBitmaps: Array<Bitmap?>? = null

        /**
         * Sets up the cache, returns true if a change of cache was required.
         *
         * @param set
         * @return
         */
        fun init(set: ILineDataSet): Boolean {
            val size = set.circleColorCount
            var changeRequired = false
            if (circleBitmaps == null) {
                circleBitmaps = arrayOfNulls(size)
                changeRequired = true
            } else if (circleBitmaps!!.size != size) {
                circleBitmaps = arrayOfNulls(size)
                changeRequired = true
            }
            return changeRequired
        }

        /**
         * Fills the cache with bitmaps for the given dataset.
         *
         * @param set
         * @param drawCircleHole
         * @param drawTransparentCircleHole
         */
        fun fill(set: ILineDataSet, drawCircleHole: Boolean, drawTransparentCircleHole: Boolean) {
            val colorCount = set.circleColorCount
            val circleRadius = set.circleRadius
            val circleHoleRadius = set.circleHoleRadius
            for (i in 0 until colorCount) {
                val conf = Bitmap.Config.ARGB_8888
                val circleBitmap = Bitmap.createBitmap(
                    (circleRadius * 2.1).toInt(),
                    (circleRadius * 2.1).toInt(), conf
                )
                val canvas = Canvas(circleBitmap)
                circleBitmaps!![i] = circleBitmap
                mRenderPaint.color = set.getCircleColor(i)
                if (drawTransparentCircleHole) {
                    // Begin path for circle with hole
                    mCirclePathBuffer.reset()
                    mCirclePathBuffer.addCircle(
                        circleRadius,
                        circleRadius,
                        circleRadius,
                        Path.Direction.CW
                    )

                    // Cut hole in path
                    mCirclePathBuffer.addCircle(
                        circleRadius,
                        circleRadius,
                        circleHoleRadius,
                        Path.Direction.CCW
                    )

                    // Fill in-between
                    canvas.drawPath(mCirclePathBuffer, mRenderPaint)
                } else {
                    canvas.drawCircle(
                        circleRadius,
                        circleRadius,
                        circleRadius,
                        mRenderPaint
                    )
                    if (drawCircleHole) {
                        canvas.drawCircle(
                            circleRadius,
                            circleRadius,
                            circleHoleRadius,
                            mCirclePaintInner
                        )
                    }
                }
            }
        }

        /**
         * Returns the cached Bitmap at the given index.
         *
         * @param index
         * @return
         */
        fun getBitmap(index: Int): Bitmap? {
            return circleBitmaps!![index % circleBitmaps!!.size]
        }
    }

    /***
     * 对高亮的值进行显示小圆点  如果没有此需要可删除
     */
    override fun drawHighlighted(c: Canvas, indices: Array<Highlight>) {
        super.drawHighlighted(c, indices)
        val phaseY = mAnimator.phaseY
        val lineData = mChart.lineData.getDataSetByIndex(0)
        val trans = mChart.getTransformer(lineData.axisDependency)
        mCirclesBuffer[0] = 0f
        mCirclesBuffer[1] = 0f
        for (high in indices) {
            val e = lineData.getEntryForXValue(high.x, high.y)
            mCirclesBuffer[0] = e.x
            mCirclesBuffer[1] = e.y * phaseY
            trans.pointValuesToPixel(mCirclesBuffer)
            mHighlightCirclePaint!!.color = lineData.highLightColor
            //根据不同的区间显示小圆点的颜色
            if (isHeart) {
                if (e.y >= range[0]) {
                    mHighlightCirclePaint!!.color = colors[0]
                } else if (e.y < range[0] && e.y >= range[1]) {
                    mHighlightCirclePaint!!.color = colors[2]
                } else if (e.y >= range[2] && e.y < range[1]) {
                    mHighlightCirclePaint!!.color = colors[4]
                } else {
                    mHighlightCirclePaint!!.color = colors[6]
                }
            }
            c.drawCircle(mCirclesBuffer[0], mCirclesBuffer[1], 10f, mHighlightCirclePaint!!)
            mHighlightCirclePaint!!.color = Color.WHITE
            c.drawCircle(mCirclesBuffer[0], mCirclesBuffer[1], 5f, mHighlightCirclePaint!!)
        }
    }

    override fun drawHorizontalBezier(dataSet: ILineDataSet) {
        val phaseY = mAnimator.phaseY
        val trans = mChart.getTransformer(dataSet.axisDependency)
        mXBounds[mChart] = dataSet
        cubicPath.reset()
        if (mXBounds.range >= 1) {
            var prev = dataSet.getEntryForIndex(mXBounds.min)
            var cur = prev

            // let the spline start
            cubicPath.moveTo(cur.x, cur.y * phaseY)
            for (j in mXBounds.min + 1..mXBounds.range + mXBounds.min) {
                prev = cur
                cur = dataSet.getEntryForIndex(j)
                val cpx = (prev.x
                        + (cur.x - prev.x) / 2.0f)
                cubicPath.cubicTo(
                    cpx, prev.y * phaseY,
                    cpx, cur.y * phaseY,
                    cur.x, cur.y * phaseY
                )
            }
        }

        // if filled is enabled, close the path
        if (dataSet.isDrawFilledEnabled) {
            cubicFillPath.reset()
            cubicFillPath.addPath(cubicPath)
            // create a new path, this is bad for performance
            drawCubicFill(mBitmapCanvas, dataSet, cubicFillPath, trans, mXBounds)
        }
        mRenderPaint.color = dataSet.color
        mRenderPaint.style = Paint.Style.STROKE
        trans.pathValueToPixel(cubicPath)
        if (isHeart) {
            mRenderPaint.shader = LinearGradient(
                0f, mViewPortHandler.contentRect.top,
                0f, mViewPortHandler.contentRect.bottom, colors, pos, Shader.TileMode.CLAMP
            )
        }
        mBitmapCanvas.drawPath(cubicPath, mRenderPaint)
        mRenderPaint.pathEffect = null
    }

    /***
     * @param isHeart true 开启分区间显示的颜色
     * @param medium 不同层级的判断条件
     * @param colors 不同区间的颜色值,从上到下的颜色 我这里是3个值  那么分成四段 colors数组长度就为4
     */
    fun setHeartLine(isHeart: Boolean, medium: Int, larger: Int, limit: Int, colors: IntArray) {
        this.isHeart = isHeart
        range = IntArray(3)
        range[0] = limit
        range[1] = larger
        range[2] = medium
        val pos = FloatArray(4)
        val yMax = (mChart as LineChart).axisLeft.axisMaximum
        val yMin = (mChart as LineChart).axisLeft.axisMinimum
        pos[0] = (yMax - limit) / (yMax - yMin)
        pos[1] = (limit - larger) / (yMax - yMin) + pos[0]
        pos[2] = (larger - medium) / (yMax - yMin) + pos[1]
        pos[3] = 1f
        this.pos = FloatArray(pos.size * 2)
        this.colors = IntArray(colors.size * 2)
        var index = 0
        for (i in pos.indices) {
            this.colors[index] = colors[i]
            this.colors[index + 1] = colors[i]
            if (i == 0) {
                this.pos[index] = 0f
                this.pos[index + 1] = pos[i]
            } else {
                this.pos[index] = pos[i - 1]
                this.pos[index + 1] = pos[i]
            }
            index += 2
        }
    }
}

MyLineChartRenderer.kt有了,就需要写一个使用这个的MyLineChart,我们对LineChart进行重写:

package com.example.mykotlinandroid.view

import android.content.Context
import android.util.AttributeSet
import com.github.mikephil.charting.charts.LineChart
import com.github.mikephil.charting.data.LineData

class MyLineChart: LineChart {

    constructor(context: Context): super(context)

    constructor(context: Context, attrs: AttributeSet) : super(context, attrs)

    constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(
        context,
        attrs,
        defStyle
    )

    override fun init() {
        super.init()
        mRenderer = MyLineChartRenderer(this, mAnimator, mViewPortHandler)
    }

    override fun getLineData(): LineData {
        return mData
    }

    override fun onDetachedFromWindow() {

        // releases the bitmap in the renderer to avoid oom error
        if (mRenderer != null && mRenderer is MyLineChartRenderer) {
            (mRenderer as MyLineChartRenderer).releaseBitmap()
        }
        super.onDetachedFromWindow()
    }
}

后面就可以直接使用了,在布局文件中直接引用重写的MyLineChart类:

<com.example.mykotlinandroid.view.MyLineChart
            android:id="@+id/lc_my_line_chart"
            android:layout_width="match_parent"
            android:layout_height="300dp"
            android:layout_below="@id/tv_title"
            android:layout_marginTop="20dp"/>

把初始化的代码也贴上来:

package com.example.mykotlinandroid.fragment

import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.example.mykotlinandroid.R
import com.example.mykotlinandroid.databinding.FragmentLineChartBinding
import com.example.mykotlinandroid.view.MyLineChartRenderer
import com.github.mikephil.charting.components.XAxis
import com.github.mikephil.charting.data.Entry
import com.github.mikephil.charting.data.LineData
import com.github.mikephil.charting.data.LineDataSet

class LineChartFragment(mContext: Context) : Fragment(R.layout.fragment_line_chart) {

    private lateinit var binding: FragmentLineChartBinding

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {

        binding = FragmentLineChartBinding.inflate(inflater)
        initView()
        return binding.root
    }
    private fun initView() {
       //准备测试数据
        var list = ArrayList<Entry>()
        for (i in 1..10) {
            if (i % 2 == 0) {
                list.add(Entry(i.toFloat() - 1, i.toFloat() * 2))
            } else {
                list.add(Entry(i.toFloat() - 1, i.toFloat() * -2))
            }
        }
        val dataSet = LineDataSet(list, "lineDataSet")
        dataSet.mode = LineDataSet.Mode.HORIZONTAL_BEZIER
        val lineData = LineData(dataSet)
        binding.lcMyLineChart.data = lineData
        binding.lcMyLineChart.legend.isEnabled = false

        binding.lcMyLineChart.description.isEnabled = false

        //设置每一个分区的颜色,将设置内容传递给MyLineChartRenderer.kt
        if (binding.lcMyLineChart.renderer is MyLineChartRenderer) {
            val renderer: MyLineChartRenderer =
                binding.lcMyLineChart.renderer as MyLineChartRenderer
            val medium = 3
            val larger = 9
            val limit = 17
            val colors = IntArray(4)
            colors[0] = resources.getColor(R.color.blue, null)
            colors[1] = resources.getColor(R.color.black, null)
            colors[2] = resources.getColor(R.color.purple_200, null)
            colors[3] = resources.getColor(R.color.teal_200, null)
            //最关键的逻辑就是下面这一步了
            renderer.setHeartLine(true, medium, larger, limit, colors)
        }

        binding.lcMyLineChart.xAxis.axisMaximum = 10f
        binding.lcMyLineChart.xAxis.axisMinimum = 0f
        binding.lcMyLineChart.axisLeft.axisMaximum = 22f
        binding.lcMyLineChart.axisLeft.axisMinimum = -22f
        binding.lcMyLineChart.axisLeft.setDrawGridLines(false)
        binding.lcMyLineChart.axisRight.isEnabled = false
//        binding.lcMyLineChart.axisRight.setDrawGridLines(false)

        binding.lcMyLineChart.setScaleEnabled(false)
        binding.lcMyLineChart.xAxis.position = XAxis.XAxisPosition.BOTTOM
    }
}

上面这个例子是将小于3归为一个颜色,3到9一个颜色,9到17一个颜色,大于17一个颜色。可以根据自己的需要进行调整,至于要设置更多的颜色区间,就需要去改MyLineChartRenderer.kt里的代码了。

这里有几个遗留问题,第一个是不能开启缩放Y轴,这个实现逻辑是根据Y轴值的初始位置进行判定的,一旦缩放Y轴,颜色不会根据Y轴的值变化而变化,除非在代码中每次缩放都加载一次MyLineChartRenderer;第二个问题是MyLineChartRenderer.kt的方法drawLinear中有一个逻辑判断坐标点Y值是否为0,如果为0就跳过这个点,继续下一个点,这就导致你在使用这种mode时:

dataSet.mode = LineDataSet.Mode.LINEAR

? ?无法显示原点?。我还遇到过线条在滑动几下后自动消失,还有没有其他问题就不清楚了,如果你是LINEAR,建议把这个逻辑注销掉?。?

相关推荐

C#.NET Autofac 详解(c# autoit)

简介Autofac是一个成熟的、功能丰富的.NET依赖注入(DI)容器。相比于内置容器,它额外提供:模块化注册、装饰器(Decorator)、拦截器(Interceptor)、强o的属性/方法注...

webapi 全流程(webapi怎么部署)

C#中的WebAPIMinimalApi没有控制器,普通api有控制器,MinimalApi是直达型,精简了很多中间代码,广泛适用于微服务架构MinimalApi一切都在组控制台应用程序类【Progr...

.NET外挂系列:3. 了解 harmony 中灵活的纯手工注入方式

一:背景1.讲故事上一篇我们讲到了注解特性,harmony在内部提供了20个HarmonyPatch重载方法尽可能的让大家满足业务开发,那时候我也说了,特性虽然简单粗暴,但只能解决95%...

C# 使用SemanticKernel调用本地大模型deepseek

一、先使用ollama部署好deepseek大模型。具体部署请看前面的头条使用ollama进行本地化部署deepseek大模型二、创建一个空的控制台dotnetnewconsole//添加依赖...

C#.NET 中间件详解(.net core中间件use和run)

简介中间件(Middleware)是ASP.NETCore的核心组件,用于处理HTTP请求和响应的管道机制。它是基于管道模型的轻量级、模块化设计,允许开发者在请求处理过程中插入自定义逻辑。...

IoC 自动注入:让依赖注册不再重复劳动

在ASP.NETCore中,IoC(控制反转)功能通过依赖注入(DI)实现。ASP.NETCore有一个内置的依赖注入容器,可以自动完成依赖注入。我们可以结合反射、特性或程序集扫描来实现自动...

C#.NET 依赖注入详解(c#依赖注入的三种方式)

简介在C#.NET中,依赖注入(DependencyInjection,简称DI)是一种设计模式,用于实现控制反转(InversionofControl,IoC),以降低代码耦合、提高可...

C#从零开始实现一个特性的自动注入功能

在现代软件开发中,依赖注入(DependencyInjection,DI)是实现松耦合、模块化和可测试代码的一个重要实践。C#提供了优秀的DI容器,如ASP.NETCore中自带的Micr...

C#.NET 仓储模式详解(c#仓库货物管理系统)

简介仓储模式(RepositoryPattern)是一种数据访问抽象模式,它在领域模型和数据访问层之间创建了一个隔离层,使得领域模型无需直接与数据访问逻辑交互。仓储模式的核心思想是将数据访问逻辑封装...

C#.NET 泛型详解(c# 泛型 滥用)

简介泛型(Generics)是指在类型或方法定义时使用类型参数,以实现类型安全、可重用和高性能的数据结构与算法为什么需要泛型类型安全防止“装箱/拆箱”带来的性能损耗,并在编译时检测类型错误。可重用同一...

数据分析-相关性分析(相关性 分析)

相关性分析是一种统计方法,用于衡量两个或多个变量之间的关系强度和方向。它通过计算相关系数来量化变量间的线性关系,从而帮助理解变量之间的相互影响。相关性分析常用于数据探索和假设检验,是数据分析和统计建模...

geom_smooth()函数-R语言ggplot2快速入门18

在每节,先运行以下这几行程序。library(ggplot2)library(ggpubr)library(ggtext)#用于个性化图表library(dplyr)#用于数据处理p...

规范申报易错要素解析(规范申报易错要素解析)

为什么要规范申报?规范申报是以满足海关监管、征税、统计等工作为目的,纳税义务人及其代理人依法向海关如实申报的行为,也是海关审接单环节依法监管的重要工作。企业申报的内容须符合《中华人民共和国海关进出口货...

「Eurora」海关编码归类 全球海关编码查询 关务服务

  海关编码是什么?  海关编码即HS编码,为编码协调制度的简称。  其全称为《商品名称及编码协调制度的国际公约》(InternationalConventionforHarmonizedCo...

9月1日起,河南省税务部门对豆制品加工业试行新政7类豆制品均适用投入产出法

全媒体记者杨晓川报道9月2日,记者从税务部门获悉,为减轻纳税人税收负担,完善农产品增值税进项税额抵扣机制,根据相关规定,结合我省实际情况,经广泛调查研究和征求意见,从9月1日起,我省税务部门对豆制品...