zoukankan      html  css  js  c++  java
  • django+xadmin+echarts实现数据可视化

    使用xadmin后功能比较强大,在后台展示统计图表,这个需求真的有点烫手,最终实现效果如下图:

    xadmin后台与echarts完全融合遇到以下问题:

      1.没有现成的数据model

      2.获得指定时间段的数据

      3.添加自定义菜单

      4.图表不能在当前页展示(后台点击每个model都是内嵌在当前页)

      5.echarts动态展示数据


    下面解决第一个问题:

      目前现状是得从一个千万级的大表里提取近12个月,近30天,近24小时3个时间维度的数据,同事建议使用中间表,于是乎建了3个。

      model如下:

    # 定义发送短信按小时统计模型类
    class Count24(models.Model):
        alia_day_time = models.CharField(
            max_length=20,
            verbose_name='年月'
        )
        total_nums = models.IntegerField(
            default=0,
            verbose_name='发送总数'
        )
        error_nums = models.IntegerField(
            default=0,
            verbose_name='失败总数'
        )
    
        class Meta:
            verbose_name = verbose_name_plural = '短信发送按小时统计'
    
        def __str__(self):
            return '{0}: {1} {2}'.format(self.alia_day_time, self.total_nums,self.error_nums)
    
    
    # 定义机构发送短信统计模型类
    class OrganizationCount(models.Model):
        alia_month_time = models.CharField(
            max_length=20,
            verbose_name='年月'
        )
        alia_date_time = models.CharField(
            max_length=20,
            verbose_name='年月日'
        )
        total_nums = models.IntegerField(
            default=0,
            verbose_name='发送总数'
        )
        error_nums = models.IntegerField(
            default=0,
            verbose_name='失败总数'
        )
        name = models.CharField(
            max_length=50,
            verbose_name='机构',
        )
    
        class Meta:
            verbose_name = verbose_name_plural = '机构发送短信统计'
    
        def __str__(self):
            return '{0}: {1} {2} {3} {4} {5}'.format(self.id, self.alia_month_time,self.alia_date_time,self.total_nums, self.error_nums, self.name)
    
    
    # 定义通道发送短信统计模型类
    class ChannelCount(models.Model):
        alia_month_time = models.CharField(
            max_length=20,
            verbose_name='年月'
        )
        alia_date_time = models.CharField(
            max_length=20,
            verbose_name='年月日'
        )
        total_nums = models.IntegerField(
            default=0,
            verbose_name='发送总数'
        )
        error_nums = models.IntegerField(
            default=0,
            verbose_name='失败总数'
        )
        name = models.CharField(
            max_length=50,
            verbose_name='通道',
        )
    
        class Meta:
            verbose_name = verbose_name_plural = '通道发送短信统计'
    
        def __str__(self):
            return '{0}: {1} {2} {3} {4} {5}'.format(self.id, self.alia_month_time,self.alia_date_time, self.total_nums, self.error_nums, self.name)

      sql查询如下(当然这样sql我是憋不出来的。。。):

    # 查询得到新表数据
    SELECT req_time, alia_time, count(*) as total_nums, count(t.`status`=2 or null) as error_nums, name FROM
    (select *, DATE_FORMAT(req_time,'%Y-%m') as alia_time, LEFT(body,LOCATE('',body)) as name from sms_smslog
    where LOCATE('',body) >0
    and LEFT(body,1)='' ) as t GROUP BY alia_time , name;
    
    # 插入新表【机构】
    INSERT into sms_organizationcount (alia_month_time, alia_date_time, total_nums, error_nums, `name`) 
    SELECT alia_month_time, alia_date_time, count(*) as total_nums, count(t.`status`=2 or null) as error_nums, name FROM
    
    
    (select *, DATE_FORMAT(req_time,'%Y-%m') as alia_month_time, DATE_FORMAT(req_time,'%Y-%m-%d') as alia_date_time,
     LEFT(body,LOCATE('',body)) as name from sms_smslog
     where LOCATE('',body) >0
    and LEFT(body,1)='' ) as t GROUP BY alia_date_time , name;
    
    # 插入新表【通道】
    INSERT into sms_channelcount (alia_month_time, alia_date_time, total_nums, error_nums, `name`) 
    SELECT alia_month_time, alia_date_time, count(*) as total_nums, count(t.`status`=2 or null) as error_nums, name FROM
    
    
    (select *, DATE_FORMAT(req_time,'%Y-%m') as alia_month_time, DATE_FORMAT(req_time,'%Y-%m-%d') as alia_date_time, channel as name from sms_smslog ) as t GROUP BY alia_date_time , channel;
    
    # 插入新表【24小时】
    INSERT into sms_count24 (alia_day_time, total_nums, error_nums) 
    SELECT alia_day_time, count(*) as total_nums, count(t.`status`=2 or null) as error_nums FROM
    
    
    (select *, DATE_FORMAT(req_time,'%Y-%m-%d %H') as alia_day_time from sms_smslog) as t GROUP BY alia_day_time;

      这样数据雏形就出来了,数据肯定不能这样插入,dba也不会答应的,下一篇会详细解决这个问题。


    下面解决第二个问题:

      近12个月,近30天,近24小时,显然都是动态的。下面几个方法值得收藏下:

    import time
    from datetime import datetime, date, timedelta
    
    # 生成近期多少天的日期
    def gen_dates(end_date, days):
        day = timedelta(days=1)
        for i in range(days):
            yield (end_date - day * i)
    
    
    # 生成近期多少小时
    def gen_hour(end_date, hours):
        hour = timedelta(hours=1)
        for i in range(hours):
            yield (end_date - hour * i).strftime('%Y-%m-%d %H')
    
    
    # 解决datetime类型不能序列化问题
    class CJsonEncoder(json.JSONEncoder):
        def default(self, obj):
            if isinstance(obj, datetime):
                return obj.strftime('%Y/%m/%d %H:%M:%S')
            elif isinstance(obj, date):
                return obj.strftime('%Y/%m/%d')
            else:
                return json.JSONEncoder.default(self, obj)
    
    
    # 生成近期12个月
    def gen_months():
        now = datetime.now()
        today_year = now.year
        last_year = int(now.year) - 1
        today_year_months = range(1, now.month + 1)
        last_year_months = range(now.month + 1, 13)
        data_list_lasts = []
        for last_year_month in last_year_months:
            date_list = '%s-%s' % (last_year, last_year_month)
            data_list_lasts.append(date_list)
        data_list_todays = []
        for today_year_month in today_year_months:
            data_list = '%s-%s' % (today_year, today_year_month)
            data_list_todays.append(data_list)
        data_year_month = data_list_lasts + data_list_todays
        # data_year_month.reverse()
        return data_year_month

      循环得到的时间list,拼接sql就可以查询到所需要的数据了。


    下面解决第三个问题:

      使用django来展示数据,主线一般是写视图,配路由,模板渲染。

      a.写视图,获取数据逻辑上面写的差不多了,最终得返回echarts什么格式的数据

    def msgsend_recent_30days_failed(request):
        # 获取近30天短信失败量
        con = Cache_data_to_redis().connection
        cursor = con.cursor()
        date_li, mon_li, data = [], [], {}
        end_date = datetime.now().date()
        # 获取近30天
        for i in gen_dates(end_date, 30):
            date_li.append(i)
        date_li = date_li[::-1]
        data['date_li'] = json.dumps(date_li, cls=CJsonEncoder)
        for i in date_li:
            sql = "SELECT error_nums as error from sms_organizationcount  where alia_date_time='{alia_date_time}';".format(alia_date_time=i)
            cursor.execute(sql)
            num = cursor.fetchone()
            if num == None:
                num = {}
                num['error'] = 0
            mon_li.append(num['error'])
        data['mon'] = mon_li
        return render(request, 'data_analysis/msgsend_recent_30days_failed.html', context=data)
    
    #得到的数据如下:
    {
        'date_li': '["2018/07/15", "2018/07/16", "2018/07/17", "2018/07/18", "2018/07/19", "2018/07/20", "2018/07/21", "2018/07/22", "2018/07/23", "2018/07/24", "2018/07/25", "2018/07/26", "2018/07/27", "2018/07/28", "2018/07/29", "2018/07/30", "2018/07/31", "2018/08/01", "2018/08/02", "2018/08/03", "2018/08/04", "2018/08/05", "2018/08/06", "2018/08/07", "2018/08/08", "2018/08/09", "2018/08/10", "2018/08/11", "2018/08/12", "2018/08/13"]', 
        'mon': [0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
    }

      b.配路由,首先在xadmin后台添加自定义菜单

    # app下的urls.py添加路由
    url(r'^data_analysis/msgsend_recent_30days_failed/$', views.msgsend_recent_30days_failed, name='msgsend_recent_30days_failed'),
    
    # adminx里面添加自定义菜单和url
    class GlobalSetting(object):
        site_title = "短信后台管理系统"
        site_footer = "http://smsweb.corp.ncfgroup.com/xadmin"
        menu_style = "accordion"
    
        # 菜单
        def get_site_menu(self):
            return [
                {
                    'title': '近期数据统计和分析',
                    'perm': self.get_model_perm(SMSLog, 'view'),
                    'icon': 'fa fa-bar-chart-o',
                    'menus': (
                        {
                            'title': '短信整体情况',
                            # 写死的url进行替换
                            'url': self.get_model_url(SMSLog,'changelist').replace('xadmin/sms/smslog/','sms/data_analysis/msgsend_recent_24hours/'),
                            # 'url': 'http://10.17.20.86:8004/sms/data_analysis/msgsend_recent_24hours/',
                            'perm': self.get_model_perm(SMSLog, 'view'),
                            'icon': 'fa fa-smile-o'
                        },
                    )
                }
            ]
     
    xadmin.site.register(views.CommAdminView, GlobalSetting)

    下面解决第四个问题:

      这个问题重新描述下:点数据统计图表的时候会在当前页展示出来,要是想回到主页需要点后退,这样操作就很不舒服了。找到xadmin源码里对应菜单处,添加上a标签就可以解决这个比较尴尬的问题。

    xadmin/templates/xadmin/includes/sitemenu_accordion.html最后一行上面加上:

    <script>
        var anchors = document.getElementById("nav-panel-1").getElementsByTagName("a");
        for(i=0;i<anchors.length;i++){
            var anchor_item = anchors[i];
            anchor_item.setAttribute("target","_blank");
        }
    </script>

    下面解决最后一个问题:

    使用echarms模板需要改动以下地方,

    (1)使用sublink时,自动获取服务器host;

    (2)x轴上的坐标原点可能不是紧挨着y轴,错位了一点,xAxis下面加上boundaryGap : false,

    (3)title改成自己的

    (4)x轴与y轴数据对应见下面完整代码

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>近30天通道短信情况</title>
    </head>
    <body>
    <!-- 为ECharts准备一个具备大小(宽高)的Dom -->
    <div id="lineMain" style="height:400px"></div>
    <!-- ECharts单文件引入 -->
    <script src="http://echarts.baidu.com/build/dist/echarts.js"></script>
    <script type="text/javascript">
        var target = {{ target|safe }}
    // 路径配置
    require.config({
      paths: {
        echarts: 'http://echarts.baidu.com/build/dist'
      }
    });
    // 使用
    require(
          [
            'echarts',
            'echarts/chart/bar',
            'echarts/chart/line'
          ],
          drawEcharts
    );
    
    function drawEcharts(ec){
      drawLine(ec);
    }
    
    function drawLine(ec){
      var myLineChart = ec.init(document.getElementById('lineMain'));
        var date_li = {{ date_li|safe }}
        var num = {{ num|safe }}
        var sub = window.location.href.match('(.*)/(.*)/')[1];
        var sublink = sub + '/msgsend_recent_12months_channel/';
        // 动态push数据到series
        var series = [];
        for (var k = 0; k< target.length;k++){
            var item = {
                name:target[k],
                type:'line',
                data:num[k],
            };
            series.push(item);
        };
      var option2 = {
        title : {
        text: '近30天渠道短信情况',
            subtext: '近12个月渠道短信情况',
            sublink: sublink,
      },
      tooltip : {
        trigger: 'axis'
      },
        grid:{
            y2: 80
        },
      legend: {
            orient: 'horizontal',
            y:  'bottom',
            data:target,
      },
      toolbox: {
        show : true,
        feature : {
          mark : {show: true},
          dataView : {show: true, readOnly: false},
          magicType : {show: true, type: ['line', 'bar']},
          restore : {show: true},
          saveAsImage : {show: true}
        }
      },
      calculable : true,
      xAxis : [
        {
          type : 'category',
          boundaryGap : false,
          data : date_li
        }
      ],
      yAxis : [
        {
          type : 'value',
        }
      ],
      series : series, 
    };
    myLineChart.setOption(option2,true);
    }
    </script>
    </body>
    </html>
    实践出真知~
  • 相关阅读:
    PHP $_SERVER
    一年成为Emacs高手(像神一样使用编辑器)
    mysql 加入列,改动列,删除列。
    傅立叶变换的深入理解(转帖)
    Java Swing 探索(一)LayoutManager
    Word2007怎样从随意页開始设置页码 word07页码设置毕业论文
    IIS7 和IIS8.0 HTTP 错误 500.19
    JAVA基于AE调用GP实现泰森多边形
    Servlet 第六课: Session的使用
    ORM框架
  • 原文地址:https://www.cnblogs.com/NolaLi/p/9469549.html
Copyright © 2011-2022 走看看