echarts of D3 v4.x (2-6) - area (region) diagram

Posted by kkeim on Mon, 30 Dec 2019 19:30:31 +0100

Area map reference ecrats address: http://echarts.baidu.com/examples/editor.html?c=area-stack

Area map github address: https://github.com/dkr380205984/myComponent/blob/master/src/page/d3/area.vue

There are almost no difficulties in the implementation of area map. If you have known how to write my broken line map.

Function point: mouse move in event is a little more complex, in fact, it is a calculation problem. See code Notes for details.

Special point: x-axis abscissa. The scale used is not conventional. In order to generate a better picture, it is necessary to select the scale with no blank on both sides.

    //x-axis scale
    //d3.scalePoint - creates an ordinal fixed-point scale. Leave no space on either side of the x-axis
    //d3.scaleBand - creates an ordinal segmented scale. Leaves both sides of the x-axis blank

This series is an advanced series. If you don't understand the previous articles, you may need to look at the first article of each series. I don't take the trouble to write the general configuration items like the teacher over and over again, which also wastes my personal time. If you don't understand anything, you can leave a message and ask questions. I think the notes are clear.

Source code is as follows

<template>
  <div id="area"></div>
</template>

<script>
import * as d3 from 'd3'
export default {
  data: function () {
    return {
      data: [{
        name: 'millet',
        value: 40.7
      }, {
        name: 'HUAWEI',
        value: 20.8
      }, {
        name: 'association',
        value: 26.4
      }, {
        name: 'Samsung',
        value: 40.8
      }, {
        name: 'Apple',
        value: 30.8
      }, {
        name: 'Other',
        value: 48.8
      } ],
      width: '',
      heigth: '',
      padding: {
        left: '30px',
        right: '30px',
        top: '20px',
        bottom: '20px'
      }
    }
  },
  methods: {
    getStyle: function (obj, attr) {
      if (obj.currentStyle) {
        return obj.currentStyle[attr]
      } else {
        return document.defaultView.getComputedStyle(obj, null)[attr]
      }
    }
  },
  mounted () {
    let _this = this
    let dom = document.getElementById('area')
    // dom container width and height, parameter padding
    let width = parseFloat(this.width) || parseFloat(this.getStyle(dom, 'width'))
    let height = parseFloat(this.height) || parseFloat(this.getStyle(dom, 'height'))
    let padLeft = parseFloat(this.padding.left)
    let padRight = parseFloat(this.padding.right)
    let padTop = parseFloat(this.padding.top)
    let padBottom = parseFloat(this.padding.bottom)
    let minHeight = parseFloat(this.minHeight) || 0
    if (isNaN(width) || isNaN(height)) {
      console.error('width or height Parameter error')
      return
    }
    // Check whether there is a problem with the padding parameter
    if (isNaN(padLeft) || isNaN(padRight) || isNaN(padTop) || isNaN(padBottom)) {
      console.error('padding Parameter error')
      return
    }
    // Start drawing, create svg canvas
    let svg = d3.select('#area')
      .append('svg')
      .attr('width', width)
      .attr('height', height)
    // x-axis scale
    // d3.scalePoint - creates an ordinal fixed-point scale. Leave no space on either side of the x-axis
    // d3.scaleBand - creates an ordinal segmented scale. Leaves both sides of the x-axis blank
    // It looks more beautiful without leaving blank here
    let xData = _this.data.map((item) => item.name)
    let xScale = d3.scalePoint().domain(xData).range([0, width - padLeft - padRight])
    let xAxis = d3.axisBottom().scale(xScale)
    // y scale
    let yData = _this.data.map((item) => item.value)
    let max = d3.max(yData)
    let yScale = d3.scaleLinear().domain([0, max]).range([height - padTop - padBottom, minHeight]).nice()
    max = yScale.domain()[1]
    let yAxis = d3.axisLeft().scale(yScale)
    // Create a color transition before creating the region generator
    let defs = svg.append('defs')
    let linearGradient = defs.append('linearGradient')
      .attr('id', 'linearColor')
      .attr('x1', '0%')
      .attr('y1', '0%')
      .attr('x2', '0%')
      .attr('y2', '100%')
    let a = '#19CAAD'
    let b = '#BEEDC7'
    linearGradient.append('stop')
      .attr('offset', '0%')
      .style('stop-color', 'white')
      .style('stop-color', a.toString())
    linearGradient.append('stop')
      .style('stop-color', 'white')
      .attr('offset', '100%')
      .style('stop-color', b.toString())
    // Create a region generator
    let area = d3.area()
      .x(function (d, i) {
        return xScale(d.name) + padLeft
        // Return padleft + (width - padleft - padright) / "this. Data. Length * (I + 0.5) / / set x0 and x1 accessors. Of course, you can set them separately. It's unnecessary
      })
      .y0(function (d, i) {
        return height - padBottom // Set y0 to x-axis
      })
      .y1(function (d, i) {
        return yScale(d.value)
      })
      .curve(d3.curveCatmullRom)
    svg.append('path')
      .attr('d', area(_this.data))
      .style('fill', 'url(#' + linearGradient.attr('id') + ')')
    // Add a line
    let line = d3.line()
      .x(function (d, i) {
        // There's a problem with xScale
        return padLeft + xScale(d.name)
      })
      .y(function (d, i) {
        return yScale(d.value)
      })
      .curve(d3.curveCatmullRom)
    svg.append('path')
      .attr('stroke', '#19CAAD')
      .attr('stroke-width', '3px')
      .attr('fill', 'none')
      .attr('class', 'line')
      .attr('d', line(_this.data))
    // Add circle description
    let circle = svg.selectAll('.circle')
      .data(_this.data)
      .enter()
      .append('circle')
      .attr('class', 'circle')
      .style('fill', 'white')
      .style('stroke', '#19CAAD')
      .style('stroke-width', '2')
      .attr('r', 3)
      .attr('cx', function (d, i) {
        return padLeft + xScale(d.name)
      })
      .attr('cy', function (d) {
        return yScale(d.value)
      })
    // Complete the animation by masking
    // Design idea: add a mask, cover the area map, and then pan left to right, which looks like the animation effect of the area map itself
    let shadow = svg.append('rect')
      .attr('width', width)
      .attr('height', height)
      .style('fill', 'white')
      .attr('x', 0)
    // Move mask, complete animation
    shadow.transition()
      .delay(500)
      .duration(2500)
      .ease(d3.easeLinear)
      .attr('x', width)
      .attr('width', 0)
    // Add toolbips
    let toolTips = d3.select('body').append('div')
      .attr('class', 'toolTips')
      .style('opacity', 0)
      .style('position', 'absolute')
    // Add guides
    let subline = svg.append('line')
      .attr('class', 'subline')
      .attr('stroke', 'rgba(0,0,0,0.2)')
      .attr('stroke-width', '1')
      // Set dashed line
      .attr('stroke-dasharray', '4,4')
      .attr('y1', height - padBottom)
      .attr('y2', padTop)
      .style('opacity', 0)
    svg.on('mouseover', function () {
      toolTips.style('opacity', 1)
      subline.style('opacity', 1)
    })
    svg.on('mousemove', function () {
      // By calculation, which node is closer to the current mouse
      let count = (d3.event.offsetX - padLeft) / (width - padLeft - padRight) * (_this.data.length - 1)
      count = Math.round(count) >= _this.data.length ? _this.data.length - 1 : count // Determine whether count is the value of > = data.length, and establish the boundary value
      let node = _this.data[Math.round(count)]
      let html = `<div class="clearfix"><div class="border" style="background:'#F4606C'"></div><span>${node.name}: ${node.value}</span></div>`
      let mouseX = d3.event.clientX + 25
      let mouseY = d3.event.clientY + 25
      // If your style uses scoped, your style should be written to App.vue, otherwise the style of the inserted element will not take effect
      toolTips.html(`<div class="tolTp">${html}</div>`)
        .style('left', mouseX + 'px')
        .style('top', mouseY + 'px')
      // Animate the circle
      circle
        .transition()
        .ease(d3.easeBounceOut)
        .duration(100)
        .attr('r', function (d, i) {
          if (i === Math.round(count)) {
            return 6
          } else {
            return 3
          }
        })
      subline
        .transition()
        .duration(50)
        .attr('x1', padLeft + xScale(node.name))
        .attr('x2', padLeft + xScale(node.name))
    })
    svg.on('mouseout', function (d) {
      subline.style('opacity', 0)
      toolTips.style('opacity', 0)
      toolTips.html('')
      circle.transition()
        .ease(d3.easeBounceOut)
        .duration(100).attr('r', function (d, i) { return 3 })
    })
    // Last added due to the highest level of axis
    // Add x axis
    svg.append('g')
      .attr('transform', 'translate(' + padLeft + ',' + (height - padBottom) + ')')
      .call(xAxis)
      .style('font-size', '12px')
    // Draw y axis
    svg.append('g')
      .attr('transform', 'translate(' + padLeft + ',' + padTop + ')')
      .call(yAxis)
      .style('font-size', '12px')
  }
}
</script>

<style lang="less">
#area{
  width: 600px;
  height: 600px;
  margin: 20px 20px;
  padding: 15px 25px;
  border:1px solid #cccccc;
  position: relative;
}
.tolTp{
  padding:8px 12px;
  background: rgba(0, 0, 0, 0.7);
  color:white;
  .border{
    width: 6px;
    height: 6px;
    border-radius: 3px;
    background: #83bff6;
    float: left;
    margin:7px 8px 7px 0;
  }
  span{
    float: left;
    line-height: 20px;
  }
}
</style>

 

Topics: github Vue less