精华内容
下载资源
问答
  • 沪指历史年度走势 上证指数 K线.zip
  • 深证指数 上证指数K线公式通达信指标公式源码.doc
  • 通达信指标公式源码 把上证指数k线图做成副图,很方便对比个股走势与大盘走势。.doc
  • 简单A股上证指数动态gif

    千次阅读 2015-12-28 12:24:56
  • 基于Highcharts箱线图实现k线图

    千次阅读 2018-01-27 15:19:41
    而d3可定制性是最高的,这些定制需要你一点点去写,也不好写,highcharts官网没有demo,网上也没有相关的案例,但是通过阅读他的说明文档,发现了箱线图这一图表类型,这不正是highcharts实现k线图最好的方法吗?...

    如何制作一个k线图?

    似乎可利用的插件或库有很多,比如echarts、highcharts、d3.js等。使用echarts是最简单的,有现成的demo可供参考,而d3可定制性是最高的,这些定制需要你一点点去写,也不好写,highcharts官网上没有demo,网上也没有相关的案例,但是通过阅读他的说明文档,发现了箱线图这一图表类型,这不正是highcharts实现k线图最好的方法吗?

    认识箱线图

    箱线图中一个箱体包含五个值:上边缘(最大观测值或样品最大值)、上四分位数(Q3)、中位数(Q2)、下四分位数(Q1)和下边缘(最小观测值或样品最小值)。另外在箱体外还可以用圆形点表示异常值,下面是箱线图组成部分示意图:
    这里写图片描述
    在 Highcharts 中,箱线图的箱体由主箱体(Box)、中位线(median)、颈部(Stem)和须线(Whishker)组成,异常值是用散点的形式展现,下面是示意图:
    这里写图片描述

    数据结构

    正如上面说的,箱线图中每个箱体包含五个值,Highcharts 可以识别的箱线数据点的定义方式包括:

    1、对象数组的形式,其中 x 值是可选的
        { x: Date.UTC(2013, 1, 7), low: 0, q1: 1, median: 2, q3: 3, high: 4 }
    2、包含五个元素的数组,x 值自动计算
        [0, 1, 2, 3, 4]
    3、包含六个元素的数组,其中第一个值是 x 值
        [Date.UTC(2013, 1, 7), 0, 1, 2, 3, 4]
    

    我们可以用上须线(high)表示k线图数据的最大值,下须线(low)表示k线图的最小值,箱体(q1,q3)表示k线图的开盘/收盘价,我的数据格式:

    效果预览

    这里写图片描述

    难点及解决:

    0、数据的生成,只是简单模拟日数据(毕竟对股票数据这块不太熟,线上项目肯定是从后台取的数据吧?)
    1、箱体颜色填充,看遍说明文档也没发现怎么填充不同的颜色
        发现series 配置 data 中可以配置className,但是怎么也出不来,放弃
        阅读源码,发现填充色默认为#ffffff,但如果设置为null,undefined或"",则根据设置的color来填充。苦笑...
    2、x轴标签问题,数据过多,会自动旋转,取消旋转,会出现溢出隐藏(默认是溢出显示省略号)
        设置 autoRotation 为 [-10],之前试了[0]不行,为什么[-10]是平的,而[0]不可以???坑!!!
    3、像x轴的网格线,y轴的轴线,默认宽度为0,需要自己设置下
    4、提示框悬停在箱线图上才会出现,需要设置shared为true,全屏扑捉提示框
    

    代码

    <!DOCTYPE HTML>
    <html>
        <head>
            <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
            <meta name="viewport" content="width=device-width, initial-scale=1">
            <title>基于Highcharts箱线图实现k线图</title>
            <style>
                .myTooltip p{
                    margin: 0;
                    padding: 0;
                    height: 25px;
                    line-height: 25px;
                    width: 120px;
                }
                .myTooltip p.blockSpan{
                    text-align: center;
                    font-weight: bold;
                }
                .myTooltip p.myRed{
                    color:#E30000;
                }
                .myTooltip p.myGreen{
                    color:#007130;
                }
                .myTooltip p span:first-child{
                    display: inline-block;
                    width: 50px;
                    text-align: right;
                    padding-right: 10px;
                }
            </style>
        </head>
        <body>
            <div id="container" style="height: 400px; margin: auto; width: 600px"></div>
    
    <script src="http://cdn.hcharts.cn/highcharts/highcharts.js"></script>
    <script src="http://cdn.hcharts.cn/highcharts/highcharts-more.src.js"></script> 
    <!--导出为-->
    <!--<script src="http://cdn.hcharts.cn/highcharts/modules/exporting.js"></script>-->
    
    <script type="text/javascript">
    //格式化时间
    function formatDate(date){
        //模拟股市开盘时间: 周一至周五(不考虑假期)
        var date = new Date(date),
            w = date.getDay(),
            Y = date.getFullYear(),
            M = date.getMonth() + 1,
            D = date.getDate();
        if(w>0 && w<6){
            return Y+'-'+(M<10?"0":"")+M+'-'+(D<10?"0":"")+D;
            //return date; //x轴为时间轴时的数据
        }
        return false;
    }
    function getDate(m){
        var date = new Date().getTime(),
            i = 0,
            dates = [];
        while(dates.length<m){
            var item = formatDate(date - i*24*60*60*1000);
            item && dates.push(item);
            i++;
        }
        dates.reverse();
        //console.log(dates);
        return dates;
    }
    //获取x轴数据
    var xdata = getDate(40);
    //获取涨跌幅随机数
    function rand(){
        var sum = 0;
        for(var i=0;i<10;i++){
            i%2==0?(sum += Math.random()):(sum -= Math.random());
        }
        return sum.toFixed(2)/100;//百分数
    }
    //获取 series 数据
    function getData(m){
        var data = [{
            low: 3142,//最小值
            q1: 3186,//最小实际值(开盘/收盘价)
            median: null,//中位数
            q3: 3225,//最大实际值(开盘/收盘价)
            high: 3225,//最大值
            name:xdata[0],
            RiseAndFall:1.22,//涨跌幅  百分数
            //className:'red',//给数据项加一个class,方便设置颜色,无效
            color: "#ff0000"
        }],
        obj,RiseAndFall,kpj,spj,RiseAndFall2,RiseAndFall3;
        for(var j=1;j<m;j++){
            RiseAndFall = rand();
            //kpj 开盘价  开盘价 = 前一个交易日的收盘价
            //前一个交易日是涨 收盘价为q3 否则为q1
            kpj = data[j-1].RiseAndFall>0?data[j-1].q3:data[j-1].q1;
            //spj 收盘价  收盘价 = 开盘价 + 开盘价*涨跌幅
            spj = kpj + kpj * RiseAndFall;
            RiseAndFall2 = rand();
            RiseAndFall3 = rand();
            obj = {
                low:+function(){
                    if(RiseAndFall>0){//是涨 最小值<=开盘价
                        return RiseAndFall2>0?kpj:(kpj+kpj*RiseAndFall2)
                    }else{//是跌 最小值<=收盘价
                        return RiseAndFall2>0?spj:(spj+spj*RiseAndFall2)
                    }
                }(),
                q1:RiseAndFall>0?kpj:spj,
                median: null,//中位数
                q3:RiseAndFall>0?spj:kpj,
                high:+function(){
                    if(RiseAndFall>0){//是涨 最大值>=收盘价
                        return RiseAndFall3>0?(spj+spj*RiseAndFall3):spj
                    }else{//是跌 最大值>=开盘价
                        return RiseAndFall3>0?(kpj+kpj*RiseAndFall3):kpj
                    }
                }(),
                name:xdata[j-1],
                RiseAndFall:RiseAndFall*100,
                color: RiseAndFall>0?"#ff0000":RiseAndFall<0?"#00ff00":"#bbbbbb"
            }
            data.push(obj);
        }
        return data;
    }
    var data = getData(xdata.length);
    Highcharts.chart('container', {
        chart: {
            backgroundColor:'#000',
            zoomType: 'x'//可以用鼠标缩放x轴
        },
        credits:{
            enabled:false//不启用版权信息 否则会显示highcharts的一些信息
        },
        title: {
            text: '上证指数[000001]',
            style:{
                color:'#ffffff'
            }
        },
        legend: {
            enabled: false
        },
        xAxis: {
            //type: 'datetime',
            //type:'category',
            title: {visible:false},
            categories: xdata,
            labels:{
                //autoRotation:false,//默认不旋转 无效 不会自动删减
                autoRotation:[-10],
                //overflow:undefined,
                //tickInterval:null,//间隔多少条数据 无效
                //tickPixelInterval:50,//间隔多少像素 无效
                style:{
                    color:'#ffffff'
                }
            },
            gridLineColor:'#666',
            gridLineDashStyle:'LongDash',
            gridLineWidth:1,
            //crosshair: true
            crosshair: {
                width: 1,
                color: '#ffffff',
                dashStyle: 'shortdot'
            }
        },
        yAxis: {
            title: {visible:false},
            lineWidth:1,
            labels:{
                style:{
                    color:'#ffffff'
                }
            },
            gridLineColor:'#666',
            gridLineDashStyle:'LongDash',
            //crosshair: true
            crosshair: {
                width: 1,
                color: '#ffffff',
                dashStyle: 'shortdot'
            }
        },
        tooltip:{
            shared:true,//共享提示框,整个绘图区都将捕捉鼠标指针的移动,显示提示框,同时将多个数据列的信息展示在同一个提示框里
            useHTML:true,
            formatter:function(){
                var options = this.points[0].point.options,
                    RiseAndFall = options.RiseAndFall,
                    high = options.high,
                    low = options.low,
                    kp = (options.q1*RiseAndFall)>(options.q3*RiseAndFall)?options.q3:options.q1,
                    sp = (options.q1*RiseAndFall)>(options.q3*RiseAndFall)?options.q1:options.q3,
                    html = '<div class="myTooltip"><p class="blockSpan">'+this.x+'</p>';
                //这里顺便添加class,设置提示框不同字体颜色,我是随便设置的   
                html += "<p class='"+(Math.random()>0.5?"myGreen":"myRed")+"'><span>开盘:</span><span>"+kp.toFixed(2)+"</span></p>";
                html += "<p class='"+(high<kp?"myGreen":"myRed")+"'><span>最高:</span><span>"+high.toFixed(2)+"</span></p>";
                html += "<p class='"+(low<sp?"myGreen":"myRed")+"'><span>最低:</span><span>"+low.toFixed(2)+"</span></p>";
                html += "<p class='"+(RiseAndFall<0?"myGreen":"myRed")+"'><span>收盘:</span><span>"+sp.toFixed(2)+"</span></p>";
                html += "<p class='"+(RiseAndFall<0?"myGreen":"myRed")+"'><span>涨跌幅:</span><span>"+RiseAndFall.toFixed(2)+"%</span></p>";
                html += "</div>";
                return html;
            }
        },
        plotOptions: {
            boxplot: {
                //中线
                medianWidth: 0,
                //上下边缘线
                //whiskerColor:'#fff',
                whiskerLength:0,
                //className:'myBoxplot'
            }
        },
        series: [{
            //name: 'boxplot',
            type: 'boxplot',
            fillColor:null,//关键:填充色,不做设置,填充色为白色,设置为null,undefined,''等,填充色为data的color
            data: data
        }]
    });
    </script>
        </body>
    </html>
    
    展开全文
  • 股票图,K线图,蜡烛图,高仿雪球股票

    千次阅读 2019-01-15 15:39:35
    股票图,K线图,蜡烛图,高仿雪球股票,教你一步步实现股票图 项目地址 讲K线图之前,先来一个引言. 作为Android开发者需要立即提升的三项技能: 分别是:UI,网络,线程,而UI又分:布局,绘制,以及触摸事件的反馈.其实...

    股票图,K线图,蜡烛图,高仿雪球股票,教你一步步实现股票图

    项目地址

    Picture

    Picture

    讲K线图之前,先来一个引言.

    作为Android开发者需要立即提升的三项技能:
    分别是:UI,网络,线程,而UI又分:布局,绘制,以及触摸事件的反馈.其实UI确实只有这么几个最主要的东西,但是很多人却没能搞明白.

    UI的这三方面,说容易也容易,说难也难.有同学当时也问到:怎么样才能算是掌握了这三个方面呢?
    凯哥当时的回答是:给出一个不算是为难的界面,能布局出来,绘制好,并掌握相应的触摸反馈,就算是基本上掌握UI了.

    实际上UI容易也是在这里,初步上手,对大部分比较认真的同学来说都是可以做到的,但是深入了解的,却比较少.例如,触摸Touch事件是如何分发的,
    它的原理是什么,这就需要大家更加深入的学习了.

    前面说到的UI的三个方面,其实在股票图里面都有比较好的体现,下面就这三个方法,讲解一下实现股票图的思路

    股票图基本知识

    了解股票图如何绘制,首先应该了解股票图的业务逻辑是怎样的,这篇文章是仿雪球股票写的,建议大家下载雪球股票软件体验一下.在写这个股票图之前,我对股票是一无所知(原谅我穷买不起股票),
    所以花了一点时间了解了一下股票图的基本信息,如果知道股票图是如何解读的,可以跳过这节.

    股票图的种类特别多,不同的种类的股票图也不一样,例如股票有港股,美股,上证,深圳,创业板等等.然后上证又有:分时,日K,月K等等.
    复杂程度完全可以直接绕晕人,没错,我就是看不懂所以不敢买.

    股票图的种类之多,本文也没有一一编写,这里主要是仿照了雪球股票之上证指数的:分时图,以及日K图.
    也就是股票的两大图种:分时图,以及蜡烛图.

    分时图

    Picture

    分时图有股票当天的涨跌情况,以及一些最高点,最低点,比分比,
    长按分时图,可以定位当时手指按下的时间所对应的股票点是多少点,并且可以左右滑动

    股票的开盘时间是早上09:30-11:30,下午是13:00-15:00.

    蜡烛图

    Picture

    蜡烛图和分时图类似,先除去那三条折线.分时图是把涨跌情况用折线表示,而蜡烛图是用一个矩形加一条竖线表示,和一根蜡烛一样,所以形象的称它为蜡烛图,
    其中竖线的最高点代表当日最高涨到了多少点,最低表示最低跌到了多少点.
    矩形的顶端,表示当日开盘是多少点,底端,表示收盘是多少点.
    颜色红,代表收盘后,相对于昨天,涨了,颜色绿,则表示跌了.
    三条折线分别代表了MA线,MA是“移动平均线”的简称,后面的数字:5、10、20.....是时间周期。MA5即5天收盘股票的平均点,其他的类推.博主这个例子没有实现MA线,作为大家的补充练手
    日K图每个月一个间隔.

    下面就分时图,蜡烛图,分别讲解其布局,绘制,触摸反馈

    分时图

    布局

    布局无论是xml引用layout编写,亦或是java直接new出来,或者是使用canvas直接绘制,最重要的不是应该使用
    RelativeLayout还是LinearLayout,而是应该剖析它的层次与结构.

    层次

    根据上面的基本介绍,分时图的可以分为以下几个层次:

    • 第1层:横线,竖线,以及底部时间(底部时间没有其他的元素,可以处于任意一层)

    • 第2层:折线,以及阴影部分

    • 第3层:文字,包括最高点,最低点,百分比

    结构

    分时图的结构相对简单,在基本介绍上已经说明其基本信息.

    股票的开盘时间是早上09:30-11:30,下午是13:00-15:00,所以其分上午,下午两部分.
    中间的虚线是昨天收盘的股票点,以此为基准线,计算折线图的位置.

    绘制

    布局分析好之后,就开始绘制这些基本信息.普通View的绘制,是写好xml或者java代码,然后交给每个view自己绘制,这里我们自己控制其绘制.

    绘制的步骤,其实就是布局中所说的层次,绘制的规则,则是布局中的结构.换句话说,这个结构,规则,就是数学中的公式,步骤就是我们解题的思路.

    详细绘制步骤

    开始.

    自定义一个View,覆写其四个构造方法(注意最好四个构造方法都覆写,这样就可以通过多种途径新建这个View),覆写onDraw()方法,画图的时候就是在这个方法进行绘制的.

    public class KLineView extends View {
            public KLineView(Context context) {
                super(context);
                init();
            }
    
            public KLineView(Context context, AttributeSet attrs) {
                super(context, attrs);
                init();
            }
    
            public KLineView(Context context, AttributeSet attrs, int defStyleAttr) {
                super(context, attrs, defStyleAttr);
                init();
            }
    
            @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
            public KLineView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
                super(context, attrs, defStyleAttr, defStyleRes);
                init();
            }
    
            @Override
            protected void onDraw(Canvas canvas) {
                super.onDraw(canvas);
            }
    }

    一般还需要初始化一些信息.为了让自己能看到每一步的绘制效果,编写一个添加测试数据方法,初始化的时候执行该方法即可.

    /**
     * canvas paint
     */
    private Paint mPaint;
    
    private void init() {
        mPaint = new Paint();
        createTestData();
    }
    
    
    /**
     * create the test data
     */
    private void createTestData() {
        baseData = 3120.50f;
        try {
            times = new ArrayList<>();
            prices = new ArrayList<>();
            @SuppressLint("SimpleDateFormat") SimpleDateFormat dateFormat = new SimpleDateFormat
                    ("yyyy-MM-dd HH:mm:ss");
            Date date = dateFormat.parse("2017-01-01 09:30:00");
            for (int i = 0; i < 240; i++) {
                if (i == 120) {
                    date = dateFormat.parse("2017-01-01 13:00:00");
                }
                date.setTime(date.getTime() + 60 * 1000);
                times.add(formatTime(dateFormat.format(date)));
    
                float tmp;
                if (i == 0) tmp = (float) (baseData + 5 - Math.random() * 10);
                else tmp = (float) (prices.get(i - 1) + 5 - Math.random() * 10);
                tmp = formatPrice(tmp);
                if (tmp > maxPrice) {
                    maxPrice = tmp;
                }
                if (tmp < minPrice) {
                    minPrice = tmp;
                }
                prices.add(tmp);
            }
    //            for (String str : times) {
    //                Log.e("time", str);
    //            }
    //            for (Float item : prices) {
    //                Log.e("time", item + "");
    //            }
        } catch (ParseException e) {
            e.printStackTrace();
        }
    
    }

    绘制线.

    使用MarkMan量取,分时图在720*1280分辨率下,高度是是410,则我们可以把其高度分成410份.
    它一共有5条横线,从上到下,每条线距离顶部的距离依次为:10,30,190,360,380.其中第3条为虚线.还有一条竖线,水平居中.

    依次画出每一条线.

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int viewHeight = getHeight();
        int viewWidth = getWidth();
        float item = viewHeight / 410f;
    
        /**
         * draw lines
         */
        drawLines(canvas, viewWidth, item);
    }
    
    /**
     * draw lines
     * <p>from top to bottom, it have 5 horizontal lines,
     * <br> 1 vertical line in the horizontal center.
     * </p>
     *
     * @param canvas    canvas
     * @param viewWidth the view's width
     * @param item      the view's height divided into 410
     */
    private void drawLines(Canvas canvas, int viewWidth, float item) {
        mPaint.setColor(Color.parseColor("#AAAAAA"));
        mPaint.setStrokeWidth(0f);
        canvas.drawLine(0, item * 10, viewWidth, item * 10, mPaint);
        canvas.drawLine(0, item * 30, viewWidth, item * 30, mPaint);
        drawDashEffect(canvas, 0, item * 190, viewWidth, item * 190);
        canvas.drawLine(0, item * 360, viewWidth, item * 360, mPaint);
        canvas.drawLine(0, item * 380, viewWidth, item * 380, mPaint);
        canvas.drawLine(viewWidth / 2.0f, item * 10, viewWidth / 2.0f, item * 380, mPaint);
    }
    
    /**
     * draw a doted line
     *
     * @param canvas canvas
     * @param x      startX
     * @param y      startY
     * @param endX   endX
     * @param endY   endY
     */
    private void drawDashEffect(Canvas canvas, float x, float y, float endX, float endY) {
        PathEffect effects = new DashPathEffect(new float[]{8, 8, 8, 8}, 1);
        Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
        p.setColor(Color.parseColor("#AAAAAA"));
        p.setPathEffect(effects);
        p.setStyle(Paint.Style.STROKE);
        Path path = new Path();
        path.moveTo(x, y);
        path.lineTo(endX, endY);
        canvas.drawPath(path, p);
    }

    绘制时间.

    时间的最简单,三个时间是固定的,位置也是固定的.

    需要注意的是,绘制文字的x,y坐标,x=文字的左边,y=文字的baseline,文字的baseline默认等于-mPaint.getFontMetrics().top

    想了解更多关于文字绘制的细节,请移步到这篇文章StyleTextView

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int viewHeight = getHeight();
        int viewWidth = getWidth();
        float item = viewHeight / 410f;
    
        /**
         * draw time
         */
        drawTimes(canvas, viewWidth, item);
    }
    
    /**
     * draw times
     * <br><br>
     * draw text method:
     * <p>params: 1:content, 2:x, 3: the baseline</p>
     * <br><b>Note:the baseline == -mPaint.getFontMetrics().top in default</b>
     * <br><br><b>More information, please
     * <a >click this</a></b>
     *
     * @param canvas    canvas
     * @param viewWidth view's width
     * @param item      the view's height divided into 410
     */
    private void drawTimes(Canvas canvas, int viewWidth, float item) {
        mPaint.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8f,
                getResources().getDisplayMetrics()));
        mPaint.setColor(Color.parseColor("#999999"));
        float textWidth = mPaint.measureText("09:30");
        canvas.drawText("09:30", item * 10, -mPaint.getFontMetrics().top + item * 380, mPaint);
        canvas.drawText("11:30", viewWidth / 2.0f - textWidth / 2.0f, -mPaint.getFontMetrics()
                .top + item * 380, mPaint);
        canvas.drawText("15:00", viewWidth - textWidth - item * 10, -mPaint.getFontMetrics().top
                + item * 380, mPaint);
    }

    绘制折线,以及折线的阴影面积.

    转到canvas上来说,其实就是绘制路径,在前面绘制横线的时候,绘制虚线其实就是绘制路径.
    注意绘制阴影的时候,要把画笔设置为实心的,这样才会有阴影的效果,同时路径path要多连接几个点,包括右下角,左下角,表明折线下方,第五条横线上方,就是阴影部分.

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int viewHeight = getHeight();
        int viewWidth = getWidth();
        float item = viewHeight / 410f;
    
        /**
         * draw broken line and shadow graph
         */
        drawBrokenLine(canvas, viewWidth, item, "#504F76DB", Paint.Style.FILL);
        drawBrokenLine(canvas, viewWidth, item, "#4F76DB", Paint.Style.STROKE);
    }
    
    /**
     * draw broken line
     *
     * @param canvas    canvas
     * @param viewWidth view's width
     * @param item      the view's height divided into 410
     * @param color     paint color
     * @param style     paint style,FILL: draw shadow, STROKE:draw line
     */
    private void drawBrokenLine(Canvas canvas, int viewWidth, float item, String color, Paint
            .Style style) {
        Path path = new Path();
        Paint paint = new Paint();
        float xItem = viewWidth / 2.0f / 120f;
    
        // get biggest  difference value, it will be calculated proportion
        float yCount = maxPrice - baseData > baseData - minPrice ? maxPrice - baseData : baseData
                - minPrice;
        //get one item height
        float yItem = 330 * item / yCount / 2.0f;
    
        //set path start point,item * 195 is baseData's y point.
        path.moveTo(0, item * 195);
        //set other points
        for (int i = 0; i < times.size(); i++) {
            path.lineTo(xItem * (i + 1), item * 195 + yItem * (baseData - prices.get(i)));
        }
        //if draw shadow, we should add 3 points to draw a complete graphics.
        //if draw lines, we should let lines bold.
        if (Paint.Style.FILL == style) {
            path.lineTo(viewWidth, item * 380);
            path.lineTo(0, item * 380);
            path.lineTo(0, item * 195);
            path.close();
        } else {
            paint.setStrokeWidth(2f);
        }
        paint.setColor(Color.parseColor(color));
        paint.setAntiAlias(true);
        paint.setStyle(style);
        canvas.drawPath(path, paint);
    }

    绘制最高点,最低点,以及百分比.

    有了绘制时间的经验,我们知道x,y分别代表的是文字的左下角,baseline,直接绘制即可.

    绘制最低点的时候需要注意,最低点距离第四条横线的距离,应该与第二条线距离最高点的距离一致.放大雪球股票的图,发现其K线图,以及后面要绘制的蜡烛图,这
    两个距离都不相等,虽然无伤大雅.但是如果我们能做到,那就更好不过.

    凯哥live中说到,设计或者产品出来一个交互,一个需求,你做不到,没什么关系,因为别人也做不到.但是假设别人做不到,但是你做到了,那么很明显,你就强于别人

    在前面绘制文字的时候提到过这篇文章StyleTextView,发布到郭霖的公众号后,有部分同学说,为什么这么麻烦搞这么多,感觉不需要这么复杂.
    实际上如果只是单纯做一个需求,确实不需要多复杂的代码,直接绘制是最简单的,但是绘制也涉及到留白的问题,在一个要求不是特别精确的View,一两个像素的差距,确实可有可无,甚至有同学直接根据
    实际运行出来的效果图,调整空白大小.

    但是你为什么调整空白大小,为什么要这么调,调了以后其他的机型适配吗?如果在一个很大的View上,字体大小很大,此时能保证也能满足正常视觉吗?

    故有时候追求一些细节,对自己的代码,以及技术,都是一种负责任的态度.

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int viewHeight = getHeight();
        int viewWidth = getWidth();
        float item = viewHeight / 410f;
    
        /**
         * draw max, min price and percent
         */
        drawPriceAndPercent(canvas, viewWidth, item);
    }
    
    /**
     * draw price and percent
     * <br><br>
     * draw text method:
     * <p>params: 1:content, 2:x, 3: the baseline</p>
     * <br><b>Note:the baseline == -mPaint.getFontMetrics().top in default</b>
     * <br><br><b>More information, please
     * <a >click this</a></b>
     *
     * @param canvas    canvas
     * @param viewWidth view's width
     * @param item      the view's height divided into 410
     */
    private void drawPriceAndPercent(Canvas canvas, int viewWidth, float item) {
        // get biggest  difference value, it will be calculated proportion
        float yCount = maxPrice - baseData > baseData - minPrice ? maxPrice - baseData : baseData
                - minPrice;
        mPaint.setStrokeWidth(2f);
    
    
        mPaint.setColor(Color.RED);
        //draw max price
        canvas.drawText(yCount + baseData + "", item * 10, -mPaint.getFontMetrics().top + item *
                30, mPaint);
        String percentStr = formatPrice(yCount * 100 / baseData) + "%";
        float textWidth = mPaint.measureText(percentStr);
        //draw max percent
        canvas.drawText(percentStr, viewWidth - textWidth - item * 10, -mPaint.getFontMetrics()
                .top + item * 30, mPaint);
    
    
        mPaint.setColor(Color.parseColor("#008000"));
        //draw min price
        canvas.drawText(baseData - yCount + "", item * 10, item * 360 - (mPaint.getFontMetrics()
                .descent - mPaint.getFontMetrics().ascent - mPaint.getTextSize() + mPaint
                .getFontMetrics().ascent - mPaint.getFontMetrics().top), mPaint);
        percentStr = "-" + percentStr;
        textWidth = mPaint.measureText(percentStr);
        //draw min percent
        canvas.drawText(percentStr, viewWidth - textWidth - item * 10, item * 360 - (mPaint
                .getFontMetrics().descent - mPaint.getFontMetrics().ascent -
                mPaint.getTextSize() + mPaint.getFontMetrics().ascent - mPaint.getFontMetrics()
                .top), mPaint);
    }

    至此,绘制基本已经结束了,直接运行,就能看到一个基本K线图,但是还差K线图的交互,也就是长按K线图的交互,这其实就是一个触摸反馈的过程

    Picture

    触摸

    网上有很多的触摸文章教程,这里就不展开篇幅讲解了,这里直接使用手势识别类:GestureDetector

    但是实际使用发现,假设手指长按了,就不能再接收到
    手指的移动事件,看GestureDetector发现,如果它判断是长按就直接break了,同时发现它也没有发送手指离开屏幕的事件,这都不是我想要的,所以我就把它源码直接复制出来了,删掉了一些用不到的事件,并
    添加了手指离开事件.第二个手指按下,离开事件.

    boolean onDown2(MotionEvent e);
    boolean onUp2(MotionEvent e);
    boolean onUp(MotionEvent e);

    添加手势触摸监听,首先在init初始化GestureDetector,并在onTouch中拦截触摸事件

    //初始化
     private void init() {
            detector = new GestureDetector(getContext(), new GestureDetector.OnGestureListener() {
    
                @Override
                public void onLongPress(MotionEvent e) {
                    showTouchLine(e.getRawX());
                    Log.e("onLongPress", getActionName(e));
                }
    
    
                @Override
                public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float
                        distanceY) {
                    Log.e("onScroll", getActionName(e2) + "  Y: " + distanceY + "  e2: " + e2.getRawY
                            ());
                    if (e2.getAction() == MotionEvent.ACTION_MOVE &amp;&amp; longPressFlag) {
                        showTouchLine(e2.getRawX());
                    }
                    return true;
                }
    
    
                @Override
                public boolean onDown2(MotionEvent e) {
                    return false;
                }
    
                @Override
                public boolean onUp2(MotionEvent e) {
                    return false;
                }
    
    
                @Override
                public boolean onUp(MotionEvent e) {
                    Log.e("onUp", getActionName(e));
                    hideTouchLine();
                    return true;
                }
    
                @Override
                public boolean onDown(MotionEvent e) {
    //                Log.e("onDown", getActionName(e));
                    return true;
                }
    
                @Override
                public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float
                        velocityY) {
    //                Log.e("onFling", getActionName(e2));
                    return true;
                }
    
                @Override
                public boolean onSingleTapUp(MotionEvent e) {
                    Log.e("onSingleTapUp", getActionName(e));
                    return true;
                }
    
                @Override
                public void onShowPress(MotionEvent e) {
    //                Log.e("onShowPress", getActionName(e));
                }
            });
    }
    
    
    //拦截触摸事件
    @Override
       public boolean onTouchEvent(MotionEvent event) {
           return detector.onTouchEvent(event);
       }
    
    
    
    /**
     * hide touch line
     */
    private void hideTouchLine() {
        touchIndex = -1;
        longPressFlag = false;
        if (touchMoveListener != null) {
            touchMoveListener.change("", "", "", "");
        }
        postInvalidate();
    }
    
    /**
     * show touch line
     */
    private void showTouchLine(float touchX) {
        longPressFlag = true;
        //根据触摸的坐标,计算当前被触摸的indext
        float itemX = (float) getWidth() / prices.size();
        for (int i = 1; i <= prices.size(); i++) {
            if (itemX * i >= touchX) {
                touchIndex = i - 1;
                break;
            }
        }
        //绘制触摸线
        postInvalidate();
        //交给外部的触摸回调监听
        if (touchMoveListener != null &amp;&amp; touchIndex >= 0) {
            touchMoveListener.change(times.get(touchIndex), prices.get(touchIndex) + "",
                    formatPrice((prices.get(touchIndex) - baseData) / baseData * 100) + "%",
                    "4613.93万");
        }
    }
    
    
    //onDraw处理触摸事件
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int viewHeight = getHeight();
        int viewWidth = getWidth();
        float item = viewHeight / 410f;
    
        /**
         * draw touch lines and point
         */
        drawTouchLines(canvas, viewWidth, item);
    }
    
    /**
     * draw touch lines and point
     *
     * @param canvas    canvas
     * @param viewWidth view's width
     * @param item      the view's height divided into 410
     */
    private void drawTouchLines(Canvas canvas, int viewWidth, float item) {
        if (longPressFlag) {
            // get biggest  difference value, it will be calculated proportion
            float yCount = maxPrice - baseData > baseData - minPrice ? maxPrice - baseData :
                    baseData - minPrice;
            float xItem = viewWidth / 2.0f / 120f;
            float yItem = 330 * item / yCount / 2.0f;
            float x = xItem * (touchIndex + 1);
            float y = item * 195 + yItem * (baseData - prices.get(touchIndex));
    
            //draw the lines
            mPaint.setColor(Color.parseColor("#999999"));
            canvas.drawLine(0, y, viewWidth, y, mPaint);
            canvas.drawLine(x, item * 10, x, item * 380, mPaint);
    
            //draw the point
            mPaint.setColor(Color.parseColor("#FFC125"));
            mPaint.setStrokeWidth(10f);
            canvas.drawPoint(x, y, mPaint);
        }
    }

    分时图总结

    至此,分时图的布局,绘制,触摸反馈都已经完整,如果再加上设置数据的方法,就可以作为一个基本的分时图使用了.
    详细代码请点击:(/app/src/main/java/com/siyehua/klinegraph/KLineView.java" target="_blank">KLineView

    蜡烛图

    布局

    层次

    • 第1层:横竖刻度线

    • 第2层:股票点,时间

    • 第3层:蜡烛,以及MA线(MA其实就是绘制折线,这个Demo中没有绘制)

    结构

    蜡烛图的结构相对复杂,首先是,数据是从右往左的呈现的,最右边是最新的数据,越往左时间越久.

    其次,蜡烛图没有昨天收盘的时候的股票点,也就是它没有基准线,它的涨跌情况都是与前一天对比.所有它的刻度范围是不固定的,需要根据
    当前呈现的数据,动态计算它的最高点和最低点

    绘制

    开始

    创建类,并初始化,并构建测试数据方便调试

    public class CandleView extends View {
        public CandleView(Context context) {
            super(context);
            init();
        }
    
        public CandleView(Context context, AttributeSet attrs) {
            super(context, attrs);
            init();
        }
    
        public CandleView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            init();
        }
    
        @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
        public CandleView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
            super(context, attrs, defStyleAttr, defStyleRes);
            init();
        }
    }
    
    
    private void init() {
    
        candles = new ArrayList<>();
        mPaint = new Paint();
        createTestData();
    }
    
    /**
     * create test data
     */
    private void createTestData() {
        //create 4 months data
        Date date = new Date();
        @SuppressLint("SimpleDateFormat") SimpleDateFormat dateFormat = new SimpleDateFormat
                ("yyyy-MM-dd HH:mm:ss");
        Float todayStart = 3150.10f;
        for (int i = 0; i < 1200; i++) {
            Candle candle = new Candle();
            date.setTime(date.getTime() - 24L * 60L * 60L * 1000L);
              candle.time = formatTime(dateFormat.format(date));
            candle.time = dateFormat.format(date);
            if (i == 0) candle.start = todayStart;
            else
                candle.start = formatPrice((float) (candles.get(i - 1).end + 100 - Math.random()
                        * 200));
            candle.end = formatPrice((float) (candle.start + candle.start * 0.05 - Math.random()
                    * candle.start * 0.1));
            float tmp = formatPrice((float) (candle.start * 0.05 - Math.random() * candle.start *
                    0.1));
            candle.max = formatPrice(candle.start + (tmp < 0 ? 0 : tmp));
            tmp = formatPrice((float) (candle.start * 0.05 - Math.random() * candle.start * 0.1));
            candle.min = formatPrice(candle.start + (tmp > 0 ? 0 : tmp));
            candles.add(candle);
        }
        for (int i = 0; i < candles.size(); i++) {
            float total = 0f;
            if (i < candles.size() - 5) {
                for (int j = i; j < i + 5; j++) {
                    total += candles.get(j).end;
                }
                candles.get(i).ma5 = total / 5;
            } else {
                candles.get(i).ma5 = candles.get(i).end;
            }
            total = 0f;
            if (i < candles.size() - 10) {
                for (int j = i; j < i + 10; j++) {
                    total += candles.get(j).end;
                }
                candles.get(i).ma10 = total / 10;
            } else {
                candles.get(i).ma10 = candles.get(i).end;
            }
            total = 0f;
            if (i < candles.size() - 20) {
                for (int j = i; j < i + 20; j++) {
                    total += candles.get(j).end;
                }
                candles.get(i).ma20 = total / 20;
            } else {
                candles.get(i).ma20 = candles.get(i).end;
            }
        }
    
    //        for (Candle candle : candles) {
    //            Log.e("item", candle.toString());
    //        }
    }

    绘制横竖线,并绘制刻度,时间

    蜡烛图的高度与分时图一致,总体高度410,第1条线距离顶部是10,View可绘制高度是370.

    在绘制刻度之前,要先计算出当前展示的数据的最小值,最大值,以及刻度的比例,才能根据比例来绘制刻度.

    /**
     * calculate min and max y,the scale y.
     */
    private void getYData() {
        //计算最大值与最小值
        maxPrice = 0;
        minPrice = Float.MAX_VALUE;
        for (int i = startIndex; i < startIndex + count; i++) {
            if (candles.get(i).start > maxPrice) maxPrice = candles.get(i).start;
            if (candles.get(i).start < minPrice) minPrice = candles.get(i).start;
            if (candles.get(i).end > maxPrice) maxPrice = candles.get(i).end;
            if (candles.get(i).end < minPrice) minPrice = candles.get(i).end;
            if (candles.get(i).max > maxPrice) maxPrice = candles.get(i).max;
            if (candles.get(i).max < minPrice) minPrice = candles.get(i).max;
            if (candles.get(i).min > maxPrice) maxPrice = candles.get(i).min;
            if (candles.get(i).min < minPrice) minPrice = candles.get(i).min;
        }
        //根据最大值最小值的,来计算刻度的最高点,最低点
        yScale = 1;
        int diff = (int) (maxPrice - minPrice);
        if (diff / 100000 >= 1) {
            yScale = 100000;
            minY = (int) minPrice / 100000 * 100000;
            maxY = ((int) maxPrice / 100000 + 1) * 100000;
        } else if (diff / 10000 >= 1) {
            yScale = 10000;
            minY = (int) minPrice / 10000 * 10000;
            maxY = ((int) maxPrice / 10000 + 1) * 10000;
        } else if (diff / 1000 >= 1) {
            yScale = 1000;
            minY = (int) minPrice / 1000 * 1000;
            maxY = ((int) maxPrice / 1000 + 1) * 1000;
        } else if (diff / 100 >= 1) {
            yScale = 100;
            minY = (int) minPrice / 100 * 100;
            maxY = ((int) maxPrice / 100 + 1) * 100;
        } else if (diff / 10 >= 1) {
            yScale = 10;
            minY = (int) minPrice / 10 * 10;
            maxY = ((int) maxPrice / 10 + 1) * 10;
        }
          Log.e("siyehua", maxPrice + "  " + minPrice + "  " + maxY + "  " + minY + "  " +
                  yScale + "  " + "  ");
    }

    计算好Y轴刻度后,则开始绘制

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int viewHeight = getHeight();
        int viewWidth = getWidth();
        float itemW = (float) viewWidth / count;
        float itemH = viewHeight / 410f;
        drawLinesAndText(canvas, viewWidth, viewHeight, itemW, itemH);
    }
    
    /**
     * draw lines and text
     *
     * @param canvas     canvas
     * @param viewWidth  the view's width
     * @param viewHeight the view's height
     * @param itemW      the view's wight divided into count
     * @param itemH      the view's height divided into 410
     */
    private void drawLinesAndText(Canvas canvas, int viewWidth, int viewHeight, float itemW,
                                  float itemH) {
        mPaint.setColor(Color.parseColor("#AAAAAA"));
        mPaint.setStrokeWidth(0f);
        mPaint.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8f,
                getResources().getDisplayMetrics()));
        /**
         * draw x lines and price text
         */
        getYData();
        int lineCount = (maxY - minY) / yScale;
        if (lineCount > 5) {//假设线条超过5条,则把刻度大小翻倍
            yScale *= 2;
            lineCount = (maxY - minY) / yScale;
        }
        //draw first line
        canvas.drawLine(0, itemH * 10, viewWidth, itemH * 10, mPaint);
        float percent = 370 / (float) lineCount;
        for (int i = 1; i < lineCount; i++) {
            //draw prices
            String content = minY + (lineCount - i) * yScale + "";
            canvas.drawText(content, itemH * 10, itemH * (10 + percent * i) - mPaint
                    .getFontMetrics().bottom, mPaint);
            //draw middle lines
            canvas.drawLine(0, itemH * (10 + percent * i), viewWidth, itemH * (10 + percent * i),
                    mPaint);
        }
        //draw last line
        canvas.drawLine(0, itemH * 380, viewWidth, itemH * 380, mPaint);
        /**
         * draw y lines and time
         */
        String tmpMonth = candles.get(startIndex).time.substring(5, 7);
        for (int i = startIndex + 1; i < startIndex + count; i++) {
            //假设明天的时间与今天不一样,说明一个月的间隔到了,则绘制一条竖线
            if (!tmpMonth.equals(candles.get(i + 1).time.substring(5, 7))) {
                tmpMonth = candles.get(i + 1).time.substring(5, 7);
                String timeStr = candles.get(i).time.substring(0, 7);
                //注意数据是从右到左呈现的,所有要从右边开始计算坐标
                float tmp = itemW * (count + startIndex - i) - itemW / 2;
                //draw times
                float timeWidth = mPaint.measureText(timeStr);
                canvas.drawText(timeStr, tmp - timeWidth / 2, itemH * 380 + -mPaint
                        .getFontMetrics().top, mPaint);
                //draw liens
                canvas.drawLine(tmp, itemH * 10, tmp, itemH * 380, mPaint);
            }
        }
    }

    绘制蜡烛

    绘制好线与文字,就可以绘制蜡烛了.蜡烛图在View的视觉上占了很大的份量,但是绘制的时候,实际上很简单,当刻度计算好后,只需要绘制一条线,以及一个实心的矩形即可.

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int viewHeight = getHeight();
        int viewWidth = getWidth();
        float itemW = (float) viewWidth / count;
        float itemH = viewHeight / 410f;
        drawCandles(canvas, viewWidth, viewHeight, itemW, itemH);
    }
    
    
    /**
     * draw candles
     *
     * @param canvas     canvas
     * @param viewWidth  the view's width
     * @param viewHeight the view's height
     * @param itemW      the view's wight divided into count
     * @param itemH      the view's height divided into 410
     */
    private void drawCandles(Canvas canvas, int viewWidth, int viewHeight, float itemW, float
            itemH) {
        mPaint.setStrokeWidth(2f);
        mPaint.setStyle(Paint.Style.FILL);
        String tmpMonth = candles.get(startIndex).time.substring(5, 7);
    
        //绘制每一个蜡烛
        for (int i = startIndex; i < startIndex + count; i++) {
            //set paint color
            if (candles.get(i).end > candles.get(i + 1).end) {
                mPaint.setColor(Color.RED);
            } else mPaint.setColor(Color.GREEN);
    
    
            float left, top, right, bottom;
    
            //绘制蜡烛灯芯,注意从右往左开始计算
            //draw line
            float tmp = itemW * (count + startIndex - i) - itemW / 2;
            left = tmp;
            top = ((maxY - candles.get(i).max) / (maxY - minY) * 370 + 10) * itemH;
            right = tmp;
            bottom = ((maxY - candles.get(i).min) / (maxY - minY) * 370 + 10) * itemH;
            if (top > bottom) {
                float a = top;
                top = bottom;
                bottom = a;
            }
            canvas.drawLine(left, top, right, bottom, mPaint);
    //          Log.e("siyehua", tmp + ", " + (((maxY - candles.get(i).start) / (maxY - minY) * 370 +
    //                  10) * itemH) + ", " + tmp + ", " + (((maxY - candles.get(i).end) / (maxY -
    //                  minY) * 370 + 10) * itemH));
    
            //绘制蜡烛,注意从右往左开始计算
            //draw candles
            left = itemW * (count - 1 + startIndex - i) + 2f;
            top = ((maxY - candles.get(i).start) / (maxY - minY) * 370 + 10) * itemH;
            right = itemW * (count + startIndex - i) - 2f;
            bottom = ((maxY - candles.get(i).end) / (maxY - minY) * 370 + 10) * itemH;
            if (top > bottom) {
                float a = top;
                top = bottom;
                bottom = a;
            }
            canvas.drawRect(left, top, right, bottom, mPaint);
        }
    
    }

    绘制其他元素

    雪球股票的蜡烛图,还有三条折线,以及左上角的文字提示.这些在分时图已经讲解了如何绘制了,只需要依样画葫芦即可.

    因为蜡烛图的复杂不在于它的绘制,而在于它的触摸反馈,所以这里把更多的精力花在触摸反馈的处理上.

    触摸

    初始化触摸类

    private void init() {
        detector = new GestureDetector(getContext(), new GestureDetector.OnGestureListener() {
            @Override
            public boolean onDown2(MotionEvent e) {
            //第2跟手指按下
                Log.e("onDown2", e.getX(1) + "");
                flag1 = flag2 = false;
                type = 2;
                down2X = e.getX(1);
                return true;
            }
    
            @Override
            public boolean onUp2(MotionEvent e) {
            //第2根手指抬起
                flag2 = true;
                if (flag1) type = 1;
                return true;
            }
    
            @Override
            public boolean onUp(MotionEvent e) {
            //第1根手指抬起
                if (type == 2) {
                    flag1 = true;
                    if (flag2) type = 1;
                } else hideTouchLine();
                return true;
            }
    
            @Override
            public boolean onDown(MotionEvent e) {
            //第1根手指按下
                Log.e("onDown", e.getRawX() + "");
    
                type = 1;
                downIndex = startIndex;
                downX = e.getX(0);
                return true;
            }
    
    
            @Override
            public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float
                    distanceY) {
                //16ms刷新一次,因为人的眼睛能反应过来的最快速度是16ms,而过快的刷新会造成内存上升,以及View闪烁
                //16ms refresh one time,because man resolution 16ms only.
                if (refreshFlag) {
                    refreshFlag = false;
                    handler.sendEmptyMessageDelayed(10086, 15);
                    if (type == 1) {
                    //单根手指刷新
                        showTouchLine(e2.getRawX());
                    } else {//多根手指刷新
                        if (e2.getPointerCount() >= 2) {
                            float moveDistance = Math.abs(e2.getX(0) - e2.getX(1)) - Math.abs
                                    (downX - down2X);
                            Log.e("onScroll", Math.abs(e2.getX(0) - e2.getX(1)) + "   " + Math
                                    .abs(downX - down2X) + "   " + moveDistance + "    " + getWidth()
                                    / 30);
                            scaleCandle(moveDistance);
                        }
                    }
                }
                return true;
            }
    
            @Override
            public void onLongPress(MotionEvent e) {
                if (type == 1) {
                    longPressFlag = true;
                    showTouchLine(e.getRawX());
                }
            }
    
            @Override
            public boolean onSingleTapUp(MotionEvent e) {
                return true;
            }
    
            @Override
            public void onShowPress(MotionEvent e) {
    
            }
    
            @Override
            public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float
                    velocityY) {
                return true;
            }
        });
    
    }
    
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return detector.onTouchEvent(event);
    }

    单根手指触摸

        /**
         * show touch line
         */
        private void showTouchLine(float touchX) {
            float itemX = (float) getWidth() / count;
    
            if (longPressFlag) {//长按,与分时图一个效果,先获取触摸的index,再刷新界面
                for (int i = 1; i <= count; i++) {
                    if (itemX * i >= touchX) {
                        touchIndex = i + 1;
                        break;
                    }
                }
                if (touchMoveListener != null &amp;&amp; touchIndex >= 0) {
                    touchMoveListener.change(candles.get(count + startIndex - touchIndex).time
                            .substring(0, 10), candles.get(count + startIndex - touchIndex).end + "",
                            formatPrice((candles.get(count + startIndex - touchIndex).end - candles
                                    .get(count + startIndex - touchIndex + 1).end) / candles.get
                                    (count + startIndex - touchIndex + 1).end * 100) + "%", "4613" +
                                    ".93万");
    
                }
            } else {//左右滑动,左右滑动,View不需要滑动,只需要修改startIndex的值,即达到滑动的效果
                int number = (int) ((touchX - downX) / itemX);
    //            Log.e("number", number + "");
                startIndex = downIndex + number;
                if (startIndex < 0) startIndex = 0;
                if (startIndex > candles.size() - count - 1) startIndex = candles.size() - count - 1;
            }
            postInvalidate();
        }
    
        /**
         * draw lines and text
         *
         * @param canvas     canvas
         * @param viewWidth  the view's width
         * @param viewHeight the view's height
         * @param itemW      the view's wight divided into count
         * @param itemH      the view's height divided into 410
         */
        private void drawTouchLines(Canvas canvas, int viewWidth, int viewHeight, float itemW, float
                itemH) {
            if (longPressFlag) {//长按
                float x = itemW * touchIndex - itemW / 2;
                float y;
                float a = ((maxY - candles.get(count + startIndex - touchIndex).start) / (maxY -
                        minY) * 370 + 10) * itemH;
                float b = ((maxY - candles.get(count + startIndex - touchIndex).end) / (maxY - minY)
                        * 370 + 10) * itemH;
                if (candles.get(count + startIndex - touchIndex).end < candles.get(count + startIndex
                        - touchIndex + 1).end) {
                    y = a > b ? a : b;
                } else y = a < b ? a : b;
    
    
                //draw the lines
                mPaint.setColor(Color.parseColor("#999999"));
                canvas.drawLine(0, y, viewWidth, y, mPaint);
                canvas.drawLine(x, itemH * 10, x, itemH * 380, mPaint);
    
                //draw the point
    //            mPaint.setColor(Color.parseColor("#FFC125"));
    //            mPaint.setStrokeWidth(10f);
    //            canvas.drawPoint(x, y, mPaint);
            }
        }

    多根手指缩放

    屏幕默认分为60根蜡烛,缩放后,只需要修改默认的蜡烛数目即可.

        private void scaleCandle(float moveDistance) {
            if (moveDistance > getWidth() / 30) {
    
                if (count == 20) count = 10;
                else if (count == 10) return;
                else count -= 20;
            } else if (moveDistance < -getWidth() / 30) {
                if (count == 240) return;
                else count += 20;
            }
            postInvalidate();
        }

    蜡烛图总结

    蜡烛图的需求,功能基本上已经实现,除了三条折线没有绘制.只需要再添加设置数据方法,该类便可直接使用.

    总结

    本文从布局,绘制,触摸,三个方面讲解了分时图,蜡烛图的一步步实现过程.其实股票图的业务上还有很多是没有讲解到的,例如大家应该都有注意到截图中
    下面有柱状图,其实这个应该也是图的一部分,还有一些高亮显示等问题.

    但其实基本的原理都是不变的,一个View的三个方面都有详细的讲解到,涉及到更多业务逻辑,无非也就是在这个基础上,绘制自己业务想要的效果,万变不离其宗,只要掌握好方法,再复杂的图也信手捏来.

    之所以要画这两个图,是因为之前有人问博主有没有别人写好的能直接用的股票图,搜了好久没发现有比较成熟的Android股票图绘制,有的都必须在项目的基础上改造,而且BUG也比较多

    最后在CSDN看了一个教程一步一步教你写股票走势图,
    这个教程的绘制是依赖另外一库的基础上改造的,而且也不是很成熟.作者花了大量的篇幅,主要问题并不是教大家如何绘制,而是教大家如何处理绘制股票图中遇到的一些问题,
    例如数组越界了等一些实质性的问题,最后还把这个系列商用了.如果大家在股票图的应用中遇到了一下问题,也可以参考这个系列的教程

    就目前来讲,Android暂时还没有一个较为成熟的股票图项目可以依赖,而使用半成熟的项目,本身就会遇到许多bug,与其花精力去修改别人的bug,不如自己编写一个.

    博主本人公司并没有涉及到股票图的绘制,个人对股票也不是很熟悉,以上所有的讲解都是个人理解,难免会有错误,欢迎大家留言交流指正.

    项目地址

    License

    Copyright 2017 siyehua
    
    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at
    
        http://www.apache.org/licenses/LICENSE-2.0
    
    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
    展开全文
  • Python读取上证指数csv

    千次阅读 2017-02-17 15:17:13
    Python。对上证指数的基本分析。

    前言


    这个系列,从这一篇起,我就不一一编号了。想到哪儿写到哪儿,学到哪儿写到哪儿。
    本系列文章是用Python对数据进行简单的分析,最终实现程序化交易的目的。比较琐碎。
    要想代码写得好,一个重要的因素就是,记得住的函数比较多。

    对上证指数进行简单分析


    timeopenhighlowclosevolumeamt
    12/19/199096.0599.9895.7999.98126000494311
    12/20/1990104.3104.3999.98104.391970084992
    12/21/1990109.07109.13103.73109.13280016096
    12/24/1990113.57114.55109.13114.55320031063
    12/25/1990120.09120.25114.55120.2515006510
    12/26/1990125.27125.27120.25125.271000053730
    12/27/1990125.27125.28125.27125.286600104644
    12/28/1990126.39126.45125.28126.451080088031
    ...

    读取数据


    CSV文件,内容如上表所示。
    import numpy as np
    import pandas as pd
    
    ''' 读数据、画图 '''
    
    shIdxDir = 'D:/data_index/'
    shIdxFileName = '000001.SH.csv'
    shIndexFilePath = shIdxDir + shIdxFileName
    
    shIdxDataFrame = pd.read_csv(shIndexFilePath, thousands=',')
    shIdxDataFrame.close = shIdxDataFrame.close.astype(float)
    

    基本绘图


    画出每日收盘价、MA60、60日标准差
    shIdxCloseSeries = shIdxDataFrame.close
    # print(shIdxCloseSeries)
    
    import matplotlib.pyplot as plt
    
    # plt.clf()
    plt.figure(1)
    plt.grid()
    plt.plot(shIdxCloseSeries, color='blue', label='original')
    
    wLen = 60
    shIdxCloseMaSeries = shIdxCloseSeries.rolling(wLen).mean()
    plt.plot(shIdxCloseMaSeries, color='red', label='ma')
    
    shIdxDataFrame.time = pd.to_datetime(shIdxDataFrame.time)
    shIdxTimeSeries = shIdxDataFrame.time
    shIdxCloseStdSeries = shIdxCloseSeries.rolling(wLen).std()
    
    plt.plot(shIdxCloseStdSeries, color='green', label='std')
    plt.legend(['original','rolling_mean','rolling_std'])
    还可以通过plt.title('xxx')写标题,plt.show()显示


    收益的简单分析


    我不是学金融的。术语有些不妥当。
    主要包括:每日收益率、7日的平均每天收益率、7日年化收益率、收益的算术平均值。
    需要注意的是,7日的平均每天收益,(第(k+7)天 - 第k天)/7。
    尽管每一天都有一个收益率,但连续7天的收益率的算术平均值,似乎不代表7日的平均收益。
    我在想,这就像,去的速度是30km/h,回的速度是40km/h,平均速度不是(30+40)/2,这只是速度的算术平均,不是平均速度。
    举个例子。第一天100元,第2天跌到1元,第3天为2元,第4天-第8天分别为4,8,16,32,64元。用每日收益率的算术平均算,并不是平均的每日收益率。
    希望有权威人士帮忙解决一下
    今天去百度了一下年化收益率。
    百度百科:年化收益率
    然而就是用的算术平均值算的……
    ''' 计算收益 '''
    # 每日收益率
    shIdxDailyReturnSeries = shIdxCloseSeries / shIdxCloseSeries.shift(1) - 1
    plt.figure(2)
    plt.clf()
    plt.grid()
    plt.subplot(411)
    plt.plot(shIdxDailyReturnSeries,color = 'blue')
    plt.title('Daily Return Rate')
    
    # 7日的收益率(1周的收益率)
    shIdx7DaysReturn = shIdxCloseSeries / shIdxCloseSeries.shift(7) - 1
    plt.subplot(412)
    plt.plot(shIdx7DaysReturn,color = 'blue')
    plt.title('Weekly Return Rate')
    
    # 直接用mid-term feature(7日)算年化
    yearlyTradingDays = 250
    shIdx7DaysYearlyReturn = shIdx7DaysReturn / 7 * yearlyTradingDays
    plt.subplot(413)
    plt.plot(shIdx7DaysYearlyReturn,color = 'blue')
    plt.title('7-Day Yearly Return Rate')
    #plt.show()
    
    # 用收益的算术平均算年化
    wLen = 7
    shIdxYearlyArithMeanReturn = shIdxDailyReturnSeries.rolling(wLen).mean() * yearlyTradingDays
    plt.subplot(414)
    plt.plot(shIdxYearlyArithMeanReturn,color = 'blue')
    plt.title('7-Day Arithmetic Average Return Rate')

    年化也有两种算法。暂时不清楚哪一种是权威的。

    另外,这里有“子图”的画法。


    两种年化算法做比较。
    # 两种年化收益的算法做比较
    delta = shIdx7DaysYearlyReturn - shIdxYearlyArithMeanReturn
    plt.figure(3)
    plt.grid()
    plt.plot(delta)
    plt.show()
    真是个悲伤的故事。差这么多。
    而且还有一个问题,用shift(7)去算,遇到休市,很有可能根本就不止7天 -_-! 而且交易日还设置的250天。总之,是有问题的。

    每日收益率的分布


    调用hist绘制直方图。看随机变量的分布。
    # 每日收益的分布
    # data = np.random.normal(5.0, 3.0, 1000)
    bins = np.arange(-0.15, 0.15, .005) #浮点数版本的range
    shIdxDailyReturnArray = np.array(shIdxDailyReturnSeries.fillna(0))
    plt.figure(4)
    plt.grid()
    plt.hist(shIdxDailyReturnArray,bins)
    plt.title('Daily Return Rate Histogram')
    plt.show()


    有点像正态。
    对每日收益率做归一化处理(减均值,除以标准差),再与标准正态分布做比较。

    # 对每日收益率标准化
    normalizedShIdxDailyReturnArray =\
    (shIdxDailyReturnArray-shIdxDailyReturnArray.mean()) / shIdxDailyReturnArray.std()
    
    # 标准正态分布的曲线
    mu = 0
    sigma = np.sqrt(1)
    x = np.linspace(mu - 3*sigma, mu + 3*sigma,100)
    y = 1/np.sqrt(2*np.pi)/sigma * np.exp(-(x-mu)**2 /2/sigma)
    plt.figure(5)
    plt.grid()
    bins = np.arange(-3,3,0.1)
    plt.hist(normalizedShIdxDailyReturnArray,bins,normed=1) # normed = 1 代表显示频率
    plt.title('Normalized Daily Return Rate Histogram')
    plt.plot(x,y,color='red')
    plt.show()


    所谓的“尖峰厚尾”现象。
    百度百科: 厚尾

    每日收益率的其他特性


    均值、极差、最大、最小、分位数、中位数median、方差、众数mode、变异系数 stdByMean…
    偏度skew、峰度kurt……
    还有自相关函数、谱……

    说明:当把每日收益率作为随机变量X时,这个X的mean、median、mode看起来都是0,分布也像个正态,偏度也不偏……
    所以,基本上没有办法准确地预测明天的收益如何!纯属赌博了……

    如果把每日收益率X(n)的前几个,和这一时刻的X(n)结合起来看,会不会有新的发现呢?


    每日收益率的对数分布


    并不是很清楚对数分布有什么用。若对随机变量取log,会怎样?

    平稳性


    稳定性



    重要问题


    这是一个重要的问题。基于以下几点发现。
    1. 从收益率来看,基本像是一个对称的分布。
    2. 如果第一天跌10%,第二天涨10%,赔钱;如果先涨再跌,还是赔钱。
    那么,结合1&2,为什么指数可以涨??神奇!

    展开全文
  • 上证指数“百点效应”实证研究--基于前景理论的分析,耿方方,张根明,通过研究上证指数落在百点附近区间的频率和日K线图的表现揭示了其分布规律和运行特征。实证结果表明,从市场总体看百点区间内样�
  • 首先绘制K线图 import pandas as pd ssec2015=pd.read_csv('ssec2015.csv') ssec2015=ssec2015.iloc[:,1:] ssec2015.head() Date Open High Low Close Volume ...
  • Python绘制加强版K线图:增加均线及成交量

    万次阅读 多人点赞 2019-01-10 11:06:16
    在之前,我们讲解了如何用Python绘制K线图。当时就有人问能否加入均线元素,当然能啦!不光均线,今天我还要把成交量给加上去。 获取数据并加工 为了计算年线(250日均线),我们需要保证数据至少在一年以上,所以...
  • python 获取股市数据 baostock + 画K线图 mpl_finance获取股票数据安装baostock库baostock库的特性获取A股K线数据核心代码如下完整代码如下画K线图安装mpl_finance库mpl_finance库的特性最简单的使用 获取股票数据 ...
  • ",问这话的人要么是股民、 要么是关注股市的领导, 而他们所问的收盘指数肯定是上证指数。 现在各类指数林林总总有几十种, 但没有一个指数能取代上证指数; 没有一个指数像上证指数那般在人们的内心根深蒂固, ...
  • 如何把自定指标显示在K线图页面自定义一个指标通过Option设置指标自定义一个带参数的指标HQChart代码地址 效果图 自定义一个指标 假设我们需要在K线上显示一个我们自己的指标, 指标内容: BS买卖点指标 (每个...
  • app.title = '2015 年上证指数'; var rawData = [ ['2015/12/31', '3570.47', '3539.18', '-33.69', '-0.94%', '3538.35', '3580.6', '176963664', '25403106', '-'], ['2015/12/30', '3566.73', '3572.88', '...
  • A股上证指数日变化趋势聚类分析

    千次阅读 2015-07-05 00:01:13
    比如每天A股上证指数变化趋势一般都是符合“持续上涨”、“持续下跌”、“平盘后迅速拉起”、“平盘后迅速下跌”、“深V字型”等几种模式的。我考虑能不能利用数据挖掘里的聚类技术–均值聚类和凝聚层次聚类来分析...
  • Echarts K线图—Ajax动态刷新赋值

    千次阅读 2018-06-07 22:05:33
    Echarts-K线图配置项说明: http://echarts.baidu.com/option.html#title案例:从数据库获取到开盘(open),收盘(close),最低(lowest),最高(highest)的数据和对应日期,实现K线图的功能。 //时间戳 转换为日期格式...
  • echarts报表实现股票k线图

    千次阅读 2019-12-29 22:10:14
    /*app.title = '2015 年上证指数';*/ var rawData = [['2015/12/31','3570.47','3539.18','-33.69','-0.94%','3538.35','3580.6','176963664','25403106','-'],['2015/12/30','3566.73','3572.88','9.14','0.26%'...
  • 股票行情K线图的效果 找到一个好的接口很重要~~(之前自己搜罗的一个接口查回来的数据 JSON key不带引号 恶心的一匹 然后 数据也不是很好用,直接舍弃) 股票行情图接口-日K历史数据 请求 (1)url:{domainname...
  • Echarts制作动态K线图和分时图

    千次阅读 2018-07-14 17:15:00
    1 K线图 <!DOCTYPE html> <caption class="caption"><h5>日K线:${pzId}</h5></caption> <!-- 为ECharts准备一个具备大小(宽高)的Dom --> ;height:400px;"> <!-- /container --> var upColor = ...
  • 股票图,K线图,蜡烛图,高仿雪球股票,教你一步步实现股票图 项目地址 _005.gif _006.gif 讲K线图之前,先来一个引言. 前两天听了朱凯大神的知乎live,其中说到一点,作为Android开发者需要...
  • EChart使用心得(三)K线图折线图…

    千次阅读 2017-02-20 16:02:01
    代码如下:数据改动,估计我最后在写一个多联动就不搞了 var myChart = echarts.init(document.querySelector("#main")); var option = { title : { text: '博雅数据'//左上角名字 }, tooltip : { //提示框
  • ts.get_hist_data('600848') #一次性获取全部日k线数据 得到结果: 第一列是日期,后边的是各类价格,包括开盘价、最高价、收盘价等等,具体在Tushare里边都有详细介绍。 3.调用Tushare抓取上证指数并作可视化 上证...
  • 接下来使用Echarts绘制柱状图、折线图、散点图、K线图、饼状图、雷达图。 整个工程的百度云链接:https://pan.baidu.com/s/1_gyBb6pT6_fizE3SSKkO8w 提取码:4zia 。 也可以去看官方教程,不过下载了上面的源码后...
  • 从tushare获取股票基金数据制作k线图

    千次阅读 2019-12-26 15:22:52
    为方便每日查看买的股票基金, 尝试写了一个脚本可每天看到数据和 代码如下: #!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Created on Wed Dec 18 16:20:19 2019 @author: xinran """ #先引入后面分析...
  • 通过baostock数据平台获取了货币供应量M1和M2的历史数据,并且获取了相同时间内上证综合...几乎是一条直线,而上证指数的波动就比较大,时常有图上的暴涨暴跌。 代码如下: import baostock as bs import panda...
  • 手把手教你画AndroidK线分时及指标

    万次阅读 2016-11-11 10:36:53
    先废话一下:来到公司之前,项目是由外包公司做的,面试初,没有接触过分时图k线这块,觉得好难,我能搞定不!但是一段时间之后,发现之前做的那是一片稀烂,但是这货是主功能啊,迟早的自己操刀,痛下决心,开搞,...
  • EChart使用心得(二)K线图事例

    千次阅读 2017-02-20 16:01:58
    代码解释如下:数据减去了十分之九 var myChart = echarts.init(document.querySelector("#main"));... title:{ //表格名称在左上角显示 text:"firstK", }, tooltip:{ //提示框,鼠标悬浮交互时的
  • Echarts画股票K线图,具体实操.

    千次阅读 2017-11-01 11:08:00
    text: '上证指数', left: 0 }, tooltip: { trigger: 'axis', axisPointer: { type: 'line' } }, legend: { data: ['日K', 'MA5', 'MA10', 'MA20', 'MA30'] }, grid:...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 11,167
精华内容 4,466
关键字:

上证指数k线图