preface

Recently, G2 was used in the project to realize charts, and several non-classical charts (color block trend chart and scale chart) were encountered. Here we summarize and hope it will be helpful for you.

Color block trend chart

Scale figure

1. Brief introduction to G2

G2

G2 is a set of graphics syntax based on visual coding, which is data-driven and highly easy to use and expandable. Users do not need to pay attention to all kinds of tedious implementation details, and can build a variety of interactive statistical charts with one statement.

The installation

This section describes the NPM installation

Run the following command to complete the installation

npm install @antv/g2 --save
Copy the code

After a successful installation, you can reference it using import or require.

import G2 from '@antv/g2';

const chart = new G2.Chart({
  container: 'c1',
  width: 600,
  height: 300,
});
Copy the code

IO/en /docs/man… antV-g2.giee. IO /zh/docs/man…

Color block trend chart implementation

Color block trend diagram is mainly realized by combining area diagram and funnel diagram, as detailed below:

Area map implementation

The official website has examples of area map implementation, here is the code:

const data = [
  { year: '1991', value: 15468 },
  { year: '1992', value: 16100 },
  { year: '1993', value: 15900 },
  { year: '1994', value: 17409 },
  { year: '1995', value: 17000 },
  { year: '1996', value: 31056 },
  { year: '1997', value: 31982 },
  { year: '1998', value: 32040 },
  { year: '1999', value: 33233 }
];
const chart = new G2.Chart({
  container: 'container',
  forceFit: true,
  height: 500
});
chart.source(data);
chart.scale({
  value: {
    min: 10000
  },
  year: {
    range: [ 0, 1 ]
  }
});
chart.axis('value', {
  label: {
    formatter: val => {
      return (val / 10000).toFixed(1) + 'k'; }}}); chart.tooltip({ crosshairs: {type: 'line'}}); chart.area().position('year*value');
chart.line().position('year*value').size(2);
chart.render();
Copy the code

Funnel diagram implementation

Specific examples can also see the official website, here only show the source code example:

const { DataView } = DataSet;
let data = [
  { action: 'Browse website', pv: 50000 },
  { action: 'Add to cart', pv: 35000 },
  { action: 'Generate order', pv: 25000 },
  { action: 'Payment order', pv: 15000 },
  { action: 'Close the deal', pv: 8000 }
];
const dv = new DataView().source(data);
dv.transform({
  type: 'map',
  callback(row) {
    row.percent = row.pv / 50000;
    returnrow; }}); data = dv.rows; const chart = new G2.Chart({ container:'container',
  forceFit: true,
  height: 500,
  padding: [ 20, 120, 95 ]
});
chart.source(data, {
  percent: {
    nice: false}}); chart.axis(false);
chart.tooltip({
  showTitle: false,
  itemTpl: '
  • '
  • + '<span style="background-color:{color};" class="g2-tooltip-marker"></span>' + '{name}<br/>' + '< span style = "padding - left: 16 px" > browse number: {pv} < / span > < br / >' +

    + '</li>' }); chart.coord('rect').transpose().scale(1, -1); chart.intervalSymmetric().position('action*percent') .shape('funnel') .color('action'['#0050B3'.'#1890FF'.'#40A9FF'.'#69C0FF'.'#BAE7FF' ]) .label('action*pv', (action, pv) => { return action + ' ' + pv; }, { offset: 35, labelLine: { lineWidth: 1, stroke: 'rgba (0, 0, 0, 0.15)' } }) .tooltip('action*pv*percent', (action, pv, percent) => { return { name: action, percent: parseInt(percent * 100) + The '%', pv }; }); Data.foreach (obj => {// Middle label text chart.guide().text({top:true, position: { action: obj.action, percent: 'median' }, content: parseInt(obj.percent * 100) + The '%'// Display text content style: {fill:'#fff', fontSize: '12', textAlign: 'center', shadowBlur: 2, shadowColor: 'rgba(0, 0, 0, .45)'}}); }); chart.render();Copy the code

    Transform radar chart to realize y axis interval color differentiation

    We see that the radar map is not actually the whole y axis we want to color discrimination, let’s modify, or directly on the source code:

    const { DataView } = DataSet;
    let data = [
      { action: 'Browse website', pv: 1 },
      { action: 'Add to cart', pv: 1 },
      { action: 'Generate order', pv: 1 },
      { action: 'Payment order', pv: 1 },
      { action: 'Close the deal', pv: 1 } ]; // set pv to the same length const dv = new DataView().source(data); Dv.transform ({type: 'map',
      callback(row) {
        row.percent = row.pv ;
        returnrow; }}); data = dv.rows; const chart = new G2.Chart({ container:'container',
      forceFit: true,
      height: 500,
      padding: [ 20, 120, 95 ]
    });
    chart.source(data, {
      percent: {
        nice: false}}); chart.axis(false); // Do not display the coordinate axes chart.tooltip(false); // Do not display the prompt chart.coord('rect').transpose().scale(1, -1); // Chart. IntervalSymmetric (). Position ('action*percent')
      .shape('funnel')
      .color('action'['#0050B3'.'#1890FF'.'#40A9FF'.'#69C0FF'.'#BAE7FF'] // set the color chart.render();Copy the code

    View

    The View, which is generated and managed by Chart, has its own independent data source, coordinate system and layer for visualization of heterogeneous data and Chart combination. A Chart consists of one or more views. So the API on view is basically the same as chart.

    How to create view objects:

    chart.view();
    Copy the code

    To create a view, you need to create a chart object and then call chart.view(CFG) to generate it:

    Const view = chart.view({start: {x: 0, y: 0}, // end: {x: 1, y: 0) 1} // End point of view drawing area, x and y values in 0-1}); Chart.view (CFG) the parameter CFG in the chart.view(CFG) method can be null or pass in the following properties: {start: null, // The starting coordinate of the region to be drawn, default is {x: 0, y: 0} end: null, // The end coordinate of the region to be drawn, default is {x: 0, y: 0} 1, y: 1} data: null, // JSON array animate: {Boolean}Copy the code

    Property start draws the starting coordinates of the region as follows:

    {x: 0, // x range 0-1 y: 0 // y range 0-1}Copy the code

    For a View, our starting point is in the upper left corner.

    End Draws the terminal coordinates of the region, with the structure as follows:

    {x: 0, // x range 0-1 y: 0 // y range 0-1}Copy the code

    data

    The view’s data source, which can also be set using the view.source(data) method. IO/en /docs/ API…

    Chart and VIEW are combined to realize the early-warning broken line chart

    Next, we will combine chart and View to realize the color block trend chart:

    import G2 from '@antv/g2'
    const DataSet = require('@antv/data-set')
    
     const data = this.bardata
          // const num = Math.floor(this.bardata.length / 6)
          const ds = new DataSet({
            state: {
              dates: null
            }
          })
          const totalDv = ds.createView().source(data)
          const dvChart = ds.createView()
          dvChart.source(data).transform({
            type: 'filter',
            callback: obj => {
              return obj
            }
          })
          const chart = new G2.Chart({
            container: 'barId',
            forceFit: true,
            height: 400,
            animate: false}) const view = chart. View ({start: {x: 0, y: 0}, // specify the starting position of the graph. End: {x: 1, y: 0] }) const dv = new DataSet.view ().source(this.dataview) dv.transform({this.dataview)type: 'map',
            callback(row) {
              row.percent = row.pv
              return row
            }
          })
          const dataView = dv.rows
    
          view.source(dataView, {
            percent: {
              nice: false}}) // Import view data view.axis(false// Hide the axis view.coord ('rect')
            .transpose()
            .scale(1, 1)
          view
            .tooltip(false)
            .intervalSymmetric()
            .position('action*percent')
            .shape('funnel')
            .color('action'['#2196F3'.'#FFCC00'.'#FF9523'.'#FA3239'Source (dvChart, {date: {tickCount: 7, // display 7 mask:'YYYY/MM/D'  
            },
            price: {
              min: 0,
              max: totalDv.max('price')
            }
          })
          chart.tooltip({
            itemTpl: '<li>{price}</li>'}) // mouse over to display information processing chart.axis('date', {
            label: {
              offsetX: -20,
              formatter: val => {
                const item = val.split(' ')[0]
                const array = item.split('/')
                return array[1] + '/'+ array[2]}}})'price', {min: 0, Max: 5, maxLimit: 5, // Specifies the maximum value of the data, whether or not there is data larger than this value, the generated coordinate point is not greater than this value tickCount: 5, // Define the number of scale lines on the axis, default is 5 tickInterval: 1.25 // interval between the vertical axis}) chart.legend({position:'top-right'}) chart.line ().position()'date*price')
            .size(2)
            .color('#5C5B5B'Chart.area ().position()'date*price')
            .tooltip(
              'date*price',
              (date, price) => {
                return{date, price}} // Return the argument name corresponding to the variable name in itemTpl.'smooth'Opacity of area part is set to 0.01, so that only the edge line part can be seen. Const startDataX = data[0]. Date const endDataX = data[data.length-1].datefor (var i = 0; i < this.dataView.length; i++) {
            chart.guide().line({
              top: true, start: [startDataX, I + 1 + (I + 1) * 0.25], end: [endDataX, I + 1 + (I + 1) * 0.25], lineStyle: {stroke:'red',
                lineWidth: 1,
                lineDash: [3, 3]
              }
            })
          }
          chart.render()
    Copy the code

    Final effect

    Scale figure

    Scale diagram implementation mainly consists of two parts, scale and progress bar, we will explain separately below.

    Scale implementation

    The calibration is realized by modifying the bar chart.

    Bar charts

    import { Column } from '@antv/g2plot';
    
    const data = [
      {
        type: 'Home Appliances',
        sales: 38,
      },
      {
        type: Grain and Oil non-staple food,
        sales: 52,
      },
      {
        type: 'Fresh fruit',
        sales: 61,
      },
      {
        type: 'Beauty care',
        sales: 145,
      },
      {
        type: 'Baby and Baby Products',
        sales: 48,
      },
      {
        type: 'Imported food',
        sales: 38,
      },
      {
        type: 'Food and Drink',
        sales: 38,
      },
      {
        type: 'Household Cleaning',
        sales: 38,
      },
    ];
    
    const columnPlot = new Column(document.getElementById('container'), {
      title: {
        visible: true,
        text: 'Base bar chart',
      },
      forceFit: true,
      data,
      padding: 'auto',
      data,
      xField: 'type',
      yField: 'sales',
      meta: {
        type: {
          alias: 'categories',
        },
        sales: {
          alias: Sales volume (ten thousand),}}}); columnPlot.render();Copy the code

    Through different attribute Settings, to achieve the scale diagram:

     const container = document.getElementById(typeId)
          const data = numData
          this.cailbarWidth = 460
          const that = this
          const config = {
            title: {
              text: 'Cluster bar'
            },
            description: {
              text: 'A basic cluster bar chart'
            },
            legend: {
              visible: false,
              flipPage: false
            },
            tooltip: {
              visible: false
            },
            xAxis: {
              visible: false
            },
            yAxis: {
              grid: {
                visible: false
              },
              label: {
                visible: false
              },
              title: {
                visible: false
              }
            },
            color: serie => {
                if (serie === this.hasValue[0]) {
                  return '#0099ff'
                } else if (serie === this.hasValue[1]) {
                  return '#ffcc00'
                } else if (serie === this.hasValue[2]) {
                  return '#ff9900'
                } else if (serie === this.hasValue[3]) {
                  return '#ff3300'
                } else {
                  return '#b7b5b5'}}, // Set the color according to the different threshold.true,
              formatter: function(... value) {if(value[1]._origin.num ! = = 1) {return value[1]._origin.num
                }
              },
              style: function(value) {
                const styleData = [
                  {
                    fill: '#0099ff',
                    adjustPosition: true
                  },
                  {
                    fill: '#ffcc00'
                  },
                  {
                    fill: '#ff9900'
                  },
                  {
                    fill: '#ff3300'}] // Set the font color according to different thresholdsif (value === 2) {
                  that.itemNum = that.itemNum + 1
                  if (that.itemNum < 5) {
                    return styleData[that.itemNum - 1]
                  } else {
                    returnStyleData [that.itemNum - 5]}} // Set threshold digit display}}, forceFit:false,
            width: this.cailbarWidth,
            height: 72,
            xField: 'x',
            yField: 'y',
            groupField: 'serie', columnSize: 1 } const plot = new G2Plot.GroupColumn(container, { data, ... config }) plot.render()Copy the code

    Where numData value processing method

    const num = {
            min: 0,
            max: 100
          }
          for (let i = num.min; i < num.max; i++) {
            let item = ' '
            item = {
              valueX: i * 5,
              y: 1
            }
            if (item.valueX > num.max) {
              break} numdata.push (item)} // 5 rows to display a scalefor (var i = 0; i < numData.length; i++) {
              switch (i) {
                case 2:
                  numData[i].y = 2
                  numData[i].num = limitData.bbottom
                  break
                case 5:
                  numData[i].y = 2
                  numData[i].num = limitData.bottom
                  break
                case 14:
                  numData[i].y = 2
                  numData[i].num = limitData.top
                  break
                case 17:
                  numData[i].y = 2
                  numData[i].num = limitData.ttop
                  break
                default:
                  numData[i].num = 1
                  break} // 2.5.14.17 is a fixed position, and the scale length is increased to 2for (let j = 0; j < numData.length; j++) {
            numData[j].serie = 'Row' + j
            numData[j].x = 'common'
            if(numData[j].num ! == 1) {this.hasValue. Push (numData[j].serie)}} // this.hasValue is convenient for color controlCopy the code

    The progress bar

    <div class="progressContainerDetail">
        <div class="progress" :style="{ width: processShow + '%', backgroundColor: colorNum }"></div>
        <div
          :style="{ left: processShow - widthNum + '%', backgroundColor: colorNum }"
          class="div-content-pro"
        >
          {{ progress }}
        </div>
      </div>
      <style scoped lang="scss">
    .div-content-pro {
      color: #fff;
      position: absolute;
      left: 68%;
      top: -6px;
      text-align: center;
      background-color: #d5d043;
      width: 14%;
      border-radius: 10px;
    }
    .progressContainerDetail {
      position: absolute;
      left: -12px;
      top: 60px;
      height: 8px;
      width: 164px;
      border-radius: 10px;
      background-color: #ddd;
      margin-left: 80px;
    }
    .progress {
      position: absolute;
      top: -2px;
      border-radius: 10px;
      height: 12px;
      line-height: 20px;
    }
    </style>
    Copy the code

    Among them, in order to realize linkage between scale and progress bar and change color, we have done the following processing for the dynamic data on the upper side:

    ** * @author liujie22 * @desc */ HandlenumValue(num, dataOne, dataTwo) { const itNum = Math.abs(dataOne - dataTwo) const adNum = Math.abs(num - dataTwo)returnAdNum/itNum}, /** * @author liujie22 * @desc colorAction(num,limitData) {// Corresponding threshold position calibration interval const bbottomNum = FIRST_POINT * SECOND_POINT // Corresponding lower threshold interval const bottomNum = SECOND_POINT * SECOND_POINT // Lower threshold interval Const topNum = THIRD_POINT * SECOND_POINT // Upper threshold interval const ttopNum = FOUR_POINT * SECOND_POINT // Interval corresponding to the upper upper threshold const allWidth = ALL_POINTNUM // Total interval const widthBottomNum = bottomNum - bbottomNum // Interval between the lower and lower threshold Const widthTopNum = topnum-bottomnum // Number of intervals corresponding to the upper threshold and lower threshold const widthTtopNum = ttopNum - topNum // Number of intervals corresponding to the upper threshold and the upper threshold Const ttopNumWidth = allWidth -ttopnum // Number of intervals above upper limitif (this.typeId === 'liquid' || this.typeId === 'pressure') {// For hydraulic and pressureif (num <= limitData.bottomthreshold) {// The value is lower than the lower threshold. Calculate the percentage of the scale occupied by the value of the lower limit, and then multiply it by the scale occupied by the lower limit to obtain the percentage of the progress display position this.colorNum ='#0099ff'
              const numValue = num / limitData.bottomThreshold
              this.processShow = parseInt(numValue * bbottomNum)
            } else if (num > limitData.bottomThreshold && num <= limitData.lowThreshold) {// The value is between the lower threshold and the lower threshold. Calculate the percentage of the scale occupied by the value of the lower limit and lower limit, and then multiply by the scale occupied between the lower limit and upper limit plus the scale occupied by the lower limit, and calculate the progress percentage this.colorNum ='#ffcc00'
    
              const numValue = this.HandlenumValue(
                num,
                limitData.lowThreshold,
                limitData.bottomThreshold
              )
              this.processShow = parseInt(numValue * widthBottomNum) + bbottomNum
            } else if (num <= limitData.highThreshold && num > limitData.lowthreshold) {// The value is between the upper threshold and the lower threshold. Calculate the scale percentage occupied by the lower limit and upper limit of the value, and then multiply the scale occupied by the lower limit and upper limit plus the scale occupied by the lower limit, and calculate the progress percentage this.colorNum ='# 666'
              const numValue = this.HandlenumValue(num, limitData.highThreshold, limitData.lowThreshold)
              this.processShow = parseInt(numValue * widthTopNum) + bottomNum
            }
          } else if (this.typeId === 'temperate') {// When is the temperature scaleif (num <= limitData.bottomthreshold) {// The value is lower than the lower threshold.if (limitData.bottomthreshold < 0) {// The lower limit threshold is less than 0. Calculate the percentage of the scale occupied by the value of the lower limit and multiply it by the scale occupied by the lower limit to obtain the percentage of the progress display position (less than 0 requires absolute value) this.colorNum ='#0099ff'
                const itNum = Math.abs(num) - Math.abs(limitData.bottomThreshold)
                const absNum = Math.abs(limitData.bottomThreshold) - itNum
                const numValue = absNum / Math.abs(limitData.bottomThreshold)
                this.processShow = parseInt(numValue * bbottomNum)
              } else{// The lower threshold is greater than 0. Calculate the percentage of the scale occupied by the value of the lower limit, and then multiply it by the scale occupied by the lower limit to obtain the percentage of the progress display position this.colorNum ='#0099ff'
                const numValue = num / limitData.bottomThreshold
                this.processShow = parseInt(numValue * bbottomNum)
              }
            } else if (num > limitData.bottomThreshold && num <= limitData.lowThreshold) {// The value is between the lower threshold and the lower threshold. Calculate the percentage of the scale occupied by the value of the lower limit and lower limit, and then multiply by the scale occupied between the lower limit and upper limit plus the scale occupied by the lower limit, and calculate the progress percentage this.colorNum ='#ffcc00'
              const itNum = Math.abs(limitData.bottomThreshold) - Math.abs(num)
              const numValue = itNum / Math.abs(limitData.bottomThreshold)
              this.processShow = parseInt(numValue * widthBottomNum) + bbottomNum
            } else if (num <= limitData.highThreshold && num > limitData.lowthreshold) {// The value is between the upper threshold and the lower threshold. Calculate the scale percentage occupied by the lower limit and upper limit of the value, and then multiply the scale occupied by the lower limit and upper limit plus the scale occupied by the lower limit, and calculate the progress percentage this.colorNum ='# 666'
              const numValue = this.HandlenumValue(num, limitData.highThreshold, limitData.lowThreshold)
              this.processShow = parseInt(numValue * widthTopNum) + bottomNum
            }
          } else{// When it is another scale. Figure out the percentage of the scale occupied by the lower limit and the upper limit, and then multiply the scale occupied by the lower limit and the upper limit plus the scale occupied by the lower limit to figure out the percentage of progressif (num <= limitData.highThreshold) {
              this.colorNum = '# 666'
              const numValue = num / limitData.highThreshold
              this.processShow = parseInt(numValue * widthTopNum) + bottomNum
            }
          }
          if (num > limitData.highThreshold && num <= limitData.topthreshold) {// The value is between the upper threshold and the upper threshold. Calculate the percentage of the scale occupied by the upper limit and the upper limit, then multiply by the scale occupied by the upper limit and the upper limit plus the scale occupied by the upper limit, and calculate the progress percentage this.colorNum ='#ff9900'
            const numValue = this.HandlenumValue(num, limitData.topThreshold, limitData.highThreshold)
            this.processShow = parseInt(numValue * widthTtopNum) + topNum
          } else if (num > limitData.topthreshold) {// The value is greater than the upper threshold. constlimitNum = FOUR_POINT * SECOND_POINT
            const fourNum = parseInt((limitData.topThreshold * allWidth) / limitNum) // Indicates the maximum value when the progress is 100if(num > fourNum) {// If the value exceeds the maximum, press 100% to display this.colorNum ='#ff3300'
              this.processShow = 100
            } else{// within the maximum value. Calculate the percentage of the scale occupied by the value from the upper limit to the maximum, then multiply by the proportion of the scale occupied between the upper limit and the proportion of the scale occupied by the limit, and calculate the progress percentage this.colorNum ='#ff3300'
              const numValue = this.HandlenumValue(num, allWidth, limitData.topthreshold) this.processShow = parseInt(numValue * ttopNumWidth) + ttopNum}}, Note: // Scale chart position parametersexportConst FIRST_POINT = 3 // The lower limit corresponds to the display positionexportConst SECOND_POINT = 5 // Lower limit corresponds to display positionexportConst THIRD_POINT = 14 // Upper limit corresponds to display positionexportConst FOUR_POINT = 17 // Upper limit corresponds to display positionexportConst ALL_POINTNUM = 100 // scale widthexport const DETAIL_WIDTH = 300
    Copy the code

    Finally, the progress follows the scale for interval display, and the cutting follows the scale to set the threshold color change.

    conclusion

    This paper mainly introduces the realization of color block trend graph and scale graph. In fact, it is mainly the idea of the combination of chart and View, the correct combination of chart and View, as well as G2 and G2Plot attribute setting, and the combination of div to achieve the scale graph that can meet the conditions. Are put together by themselves, I hope to help you a little.