zoukankan      html  css  js  c++  java
  • d3.js 教程 模仿echarts柱状图

    由于最近工作不是很忙,隧由把之前的charts项目用d3.js重写的一下,其实d3.js文档很多,但是入门不是很难,可是想真的能做一个完成的,交互良好的图还是要下一番功夫的。今天在echarts找到了一个柱状图,如图。

    模仿了一番,废话不多说。下面就开始我们的代码(注意是D3.v4版本)。

    1. js 类

    class Bar {
        constructor() {
            this._width = 1000;
            this._height = 700;
            this._padding = 10;
            this._offset = 35;
            this._margins = {right: 40,bottom: 40,left: 40,top: 40};
            this._scaleX = d3.scaleBand().rangeRound([0, this._width - this._margins.left - this._margins.right]);
            this._scaleY = d3.scaleLinear().range([this._height - this._margins.top - this._margins.bottom, 0]);
            this._color = '#3398DB';
            this._data = [];
            this._svg = null;
            this._body = null;
            this._tooltip = null;
            this._shadow = null;
            this._ticks = 5;
            this._key = 'key';
            this._value = 'value';
        }
        render() {
            if(!this._tooltip) {
                this._tooltip = d3.select('body')
                .append('div')
                .style('left', '40px')
                .style('top', '30px')
                .attr('class', 'tooltip')
                .html('');
            }
            if(!this._svg) {
                this._svg = d3.select('body')
                    .append('svg')
                    .attr('width', this._width)
                    .attr('height', this._height)
                this.renderAxes();
                this.renderClipPath();
            }
            this.renderBody();
        }
        renderAxes() {
            let axes = this._svg.append('g')
                .attr('class', 'axes');
    
            this.renderXAxis(axes);
            this.renderYAxis(axes);
        }
        renderXAxis(axes) {
            let xAxis = d3.axisBottom().scale(this._scaleX)
            axes.append('g')
                .attr('class', 'x axis')
                .attr('transform', `translate(${this.xStart()}, ${this.yStart()})`)
                .call(xAxis)
        }
        renderYAxis(axes) {
            let yAxis = d3.axisLeft().scale(this._scaleY).ticks(this._ticks);
            axes.append('g')
                .attr('class', 'y axis')
                .attr('transform', `translate(${this.xStart()}, ${this.yEnd()})`)
                .call(yAxis)
    
            d3.selectAll('.y .tick')
                .append('line')
                .attr('class', 'grid-line')
                .attr('x1', 0)
                .attr('y1', 0)
                .attr('x2', this.quadrantWidth())
                .attr('y2', 0)
        }
        renderClipPath() {
            this._svg.append('defs')
                .append('clip-path')
                .attr('id', 'body-clip')
                .append('rect')
                .attr('x', 0)
                .attr('y', 0)
                .attr('width', this.quadrantWidth())
                .attr('height', this.quadrantHeight())
        }
        renderBody() {
            if(!this._body) {
                this._body = this._svg.append('g')
                    .attr('class', 'body')
                    .attr('transform', `translate(${this._margins.left},${this._margins.top})`)
                    .attr('clip-path', 'url(#clipPath)')
                this.renderShadow()
            }
            this.renderBar();
            this.listenMousemove();
        }
        renderShadow() {
            this._shadow = this._body.append('rect')
                .attr('x', 0)
                .attr('y', 0)
                .attr('width', this.everyWidth())
                .attr('height', this._scaleY(0))
                .attr('fill', '#000')
                .attr('fill-opacity', 0)
        }
        renderBar() {
            let barElements = this._body
                .selectAll('rect.bar')
                .data(this._data);
    
            let barEnter =  barElements
                .enter()
                .append('rect')
                .attr('class', 'bar')
                .attr('x', d => this._scaleX(d[this._key]) + this.everyWidth() * 0.18)
                .attr('y', () => this._scaleY(0))
                .attr('width', this.everyWidth() * 0.64)
                .attr('height', () => this.quadrantHeight() - this._scaleY(0))
    
            let barUpdate = barEnter
                .merge(barElements)
                .transition()
                .duration(800)
                .ease(d3.easeCubicOut)
                .attr('y', d => this._scaleY(d[this._value]))
                .attr('height', d => {
                    console.log(this.quadrantHeight() - this._scaleY(d[this._value]))
                    return this.quadrantHeight() - this._scaleY(d[this._value])
                });
    
            let barExit = barElements
                .exit()
                .transition()
                .attr('y', () => this._scaleY(0))
                .attr('height', () => this.quadrantHeight() - this._scaleY(0))
                .remove();
        }
        listenMousemove() {
            this._svg.on('mousemove', () => {
                let px = d3.event.offsetX;
                let py = d3.event.offsetY;
                if(px < this.xEnd() && px > this.xStart() && py < this.yStart() && py > this.yEnd()) {
                    this.renderShadowAndTooltip(px, py, px - this.xStart());
                } else {
                    this.hideShadowAndTooltip();
                }
            })
        }
        renderShadowAndTooltip(x, y, bodyX) {
            let cutIndex = Math.floor(bodyX / this.everyWidth());
            this._shadow.transition().duration(50).ease(d3.easeLinear).attr('fill-opacity', .12).attr('x', cutIndex * this.everyWidth());
            if(x > this.quadrantWidth() - this._tooltip.style('width').slice(0,-2) - this._padding * 2) {
                x = x - this._tooltip.style('width').slice(0,-2) - this._padding * 2 - this._offset * 2;
            }
            if(y > this.quadrantHeight() - this._tooltip.style('height').slice(0,-2) - this._padding * 2) {
                y = y - this._tooltip.style('height').slice(0,-2) - this._padding * 2 - this._offset * 2;
            }
            this._tooltip.html(`${this._data[cutIndex][this._key]}<br/>数量统计: ${this._data[cutIndex][this._value]}`).transition().duration(100).ease(d3.easeLinear).style('display', 'inline-block').style('opacity', .6).style('left', `${x + this._offset + this._padding}px`).style('top', `${y + this._offset + this._padding}px`);
        }
        hideShadowAndTooltip() {
        this._shadow.transition().duration(10).attr('fill-opacity', 0);
        this._tooltip.transition().duration(50).style('opacity', 0).on('end', function() {d3.select(this).style('display', 'none')})
      } everyWidth() {
    return this.quadrantWidth() / this._data.length; } quadrantWidth() { return this._width - this._margins.left - this._margins.right; } quadrantHeight() { return this._height - this._margins.top - this._margins.bottom; } xStart() { return this._margins.left; } xEnd() { return this._width - this._margins.right; } yStart() { return this._height - this._margins.bottom; } yEnd() { return this._margins.top; } scaleX(a) { this._scaleX = this._scaleX.domain(a); } scaleY(a) { this._scaleY = this._scaleY.domain(a) } key(k) { if(!arguments.length) return this._key; this._key = k; this.scaleX(this._data.map(d => d[this._key])) return this; } value(v) { if(!arguments.length) return this._value; this._value = v; let arr = this._data.map(d => d[this._value]); let ele = Math.pow(10, d3.max(arr).toString().length - 1); let max = Math.ceil(d3.max(arr) / ele) * ele; this.scaleY([0, max]); return this; } data(data) { if(!arguments.length) return this._data; this._data = data; return this; } }

    2 CSS 文件很简单

    .domain {
      stroke-width: 2;
      fill: none;
      stroke: #888;
      shape-rendering: crispEdges;
    }
    .x .tick line {
      opacity: 0  ;
    }
    .tick text {
      font-size: 14px;
    }
    .grid-line {
      fill: none;
      stroke: #888;
      opacity: .4;
      shape-rendering: crispEdges;
    }
    .bar {
      fill: #3398DB;
    }
    .tooltip{
      font-size: 15px;
      width: auto;
      padding: 10px;
      height: auto;
      position: absolute;
      text-align: center;
      background-color: #000000;
      opacity: .6;
      border-radius:5px;
      color: #ffffff;
      display: none;
    }

    3 加下来就是html文件

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>$Title$</title>
        <link rel="stylesheet" type="text/css" href="css/base.css"/>
        <script type="text/javascript" src="js/d3.v4.js"></script>
        <script type="text/javascript" src="js/bar.js"></script>
    </head>
    <body>
    <script>
        var dataset = [{date: 'Mon', label: 15},{date: 'Tue', label: 52},{date: 'Wed', label: 200},{date: 'Thu', label: 235},{date: 'Fri', label: 390},{date: 'Sat', label: 330},{date: 'Sun', label: 221}];
        var bar = new Bar();
        bar
            .data(dataset)
            .key('date')
            .value('label')
            .render();
    </script>
    </body>
    </html>

    4 接着是效果图

    新上手的朋友们可以先学习一下ES6,然后在学习类的思想,d3.v3和v4 v5的版本差异比较大,直接学习d3.v4就可以了,最最后推荐一本书。D3 4.x数据可视化实战手册。这本书比较基础但是能够通过它养成良好的d3编程习惯。祝大家d3学习顺利。

    如果想下载代码或者预览这个DEMO请移步到原文!!!

    原文链接:http://www.bettersmile.cn

  • 相关阅读:
    矩阵快速幂
    快速排序
    用闭包来实现命令模式
    闭包和面向对象设计
    闭包及其作用
    阿里笔试
    如何在节点上添加样式
    getComputedStyle与currentStyle获取样式(style/class)
    今日头条笔试
    牛客网JavaScript编程规范
  • 原文地址:https://www.cnblogs.com/vadim-web/p/11456188.html
Copyright © 2011-2022 走看看