我想得到点图表定制

I want to get dot chart customize

我正在学习 d3 图表,我想得到像图像一样的结果。 数据是 json,看起来像这样:

[{
    "date": "2020.12.1",
    "pay": 1    
},
{
    "date": "2021.1.2",
    "pay": 1    
},
{
    "date": "2021.2.1",
    "pay": 1    
},
...

pay = 1 //on time,
pay = 2 // missed,
pay = 3 // no data

谢谢关心。

举个例子。为简单起见,我对颜色图例中条目的位置进行了硬编码。在实践中,最好在 HTML 中做颜色图例,以便您可以利用自动水平布局。

<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <script src="https://d3js.org/d3.v7.js"></script>
</head>

<body>
  <div id="chart"></div>

  <script>
    /* --- set up --- */

    const margin = { top: 10, bottom: 50, left: 10, right: 10 };

    const width = 500 - margin.left - margin.right;
    const height = 140 - margin.top - margin.bottom;

    const svg = d3.select('#chart')
      .append('svg')
        .attr('width', width + margin.left + margin.right)
        .attr('height', height + margin.top + margin.bottom);

    const g = svg.append('g')
        .attr('transform', `translate(${margin.left},${margin.top})`);

    /* --- data --- */

    const parseTime = d3.timeParse('%Y.%-m.%-d');

    const data = [
      { date: "2015.1.1", pay: 2 },
      { date: "2015.2.1", pay: 2 },
      { date: "2015.3.1", pay: 2 },
      { date: "2015.4.1", pay: 2 },
      { date: "2015.5.1", pay: 2 },
      { date: "2015.6.1", pay: 2 },
      { date: "2015.7.1", pay: 2 },
      { date: "2015.8.1", pay: 2 },
      { date: "2015.9.1", pay: 2 },
      { date: "2015.10.1", pay: 2 },
      { date: "2015.11.1", pay: 2 },
      { date: "2015.12.1", pay: 2 },
      { date: "2016.1.1", pay: 2 },
      { date: "2016.2.1", pay: 2 },
      { date: "2016.3.1", pay: 2 },
      { date: "2016.4.1", pay: 2 },
      { date: "2016.5.1", pay: 2 },
      { date: "2016.6.1", pay: 2 },
      { date: "2016.7.1", pay: 1 },
      { date: "2016.8.1", pay: 1 },
      { date: "2016.9.1", pay: 1 },
      { date: "2016.10.1", pay: 1 },
      { date: "2016.11.1", pay: 1 },
      { date: "2016.12.1", pay: 1 },
      { date: "2017.1.1", pay: 1 },
      { date: "2017.2.1", pay: 1 },
      { date: "2017.3.1", pay: 1 },
      { date: "2017.4.1", pay: 1 },
      { date: "2017.5.1", pay: 1 },
      { date: "2017.6.1", pay: 1 },
      { date: "2017.7.1", pay: 1 },
      { date: "2017.8.1", pay: 1 },
      { date: "2017.9.1", pay: 1 },
      { date: "2017.10.1", pay: 1 },
      { date: "2017.11.1", pay: 1 },
      { date: "2017.12.1", pay: 1 },
      { date: "2018.1.1", pay: 1 },
      { date: "2018.2.1", pay: 1 },
      { date: "2018.3.1", pay: 3 },
      { date: "2018.4.1", pay: 2 },
      { date: "2018.5.1", pay: 1 },
      { date: "2018.6.1", pay: 3 },
      { date: "2018.7.1", pay: 1 },
      { date: "2018.8.1", pay: 3 },
      { date: "2018.9.1", pay: 3 },
      { date: "2018.10.1", pay: 3 },
      { date: "2018.11.1", pay: 1 },
      { date: "2018.12.1", pay: 1 },
      { date: "2019.1.1", pay: 3 },
      { date: "2019.2.1", pay: 1 },
      { date: "2019.3.1", pay: 2 },
      { date: "2019.4.1", pay: 3 },
      { date: "2019.5.1", pay: 3 },
      { date: "2019.6.1", pay: 1 },
      { date: "2019.7.1", pay: 1 },
      { date: "2019.8.1", pay: 1 },
      { date: "2019.9.1", pay: 3 },
      { date: "2019.10.1", pay: 2 },
      { date: "2019.11.1", pay: 2 },
      { date: "2019.12.1", pay: 2 },
      { date: "2020.1.1", pay: 1 },
      { date: "2020.2.1", pay: 2 },
      { date: "2020.3.1", pay: 2 },
      { date: "2020.4.1", pay: 1 },
      { date: "2020.5.1", pay: 3 },
      { date: "2020.6.1", pay: 1 },
      { date: "2020.7.1", pay: 1 },
      { date: "2020.8.1", pay: 3 },
      { date: "2020.9.1", pay: 1 },
      { date: "2020.10.1", pay: 2 },
      { date: "2020.11.1", pay: 1 },
      { date: "2020.12.1", pay: 1 },
      { date: "2021.1.1", pay: 3 },
      { date: "2021.2.1", pay: 2 },
      { date: "2021.3.1", pay: 1 },
      { date: "2021.4.1", pay: 1 },
      { date: "2021.5.1", pay: 1 },
      { date: "2021.6.1", pay: 2 },
      { date: "2021.7.1", pay: 3 },
      { date: "2021.8.1", pay: 3 },
      { date: "2021.9.1", pay: 2 },
      { date: "2021.10.1", pay: 2 },
      { date: "2021.11.1", pay: 3 },
      { date: "2021.12.1", pay: 3 },
    ]
      // convert the date strings to Date objects
      .map(({ date, pay }) => ({ date: parseTime(date), pay }));

    // group the payments by year
    const groupedByYear = d3.group(data, d => d.date.getFullYear());

    /* --- scales --- */

    // scale to place the groups according to the year
    const x = d3.scaleBand()
        .domain(groupedByYear.keys())
        .range([0, width]);

    // scales to place the dots in a group

    const numRows = 3;
    const numCols = 4;

    const row = d3.scalePoint()
        .domain(d3.range(numRows))
        .range([0, height])
        .padding(1);

    const col = d3.scalePoint()
        .domain(d3.range(numCols))
        .range([0, x.bandwidth()])
        .padding(1);

    // color scale for circles
    const ontime = 1;
    const missing = 2;
    const nodata = 3;
    
    const color = d3.scaleOrdinal()
        .domain([ontime, missing, nodata])
        .range(['DarkSlateGray', 'MediumVioletRed', 'Gainsboro']);
        
    // color scale for year labels
    const colorYear = d3.scaleSequential()
        // input is number of missed payments for that year
        .domain([0, 12])
        // output interpolates between the color for on time
        // and the color for missing
        .interpolator(d3.interpolateHcl(color(ontime), color(missing)));

    /* --- draw circles --- */

    // add one group for each year
    const groups = g.selectAll('g')
      .data(groupedByYear)
      .join('g')
        .attr('transform', ([year, payments]) => `translate(${x(year)})`);

    // calculate max radius size
    const radius = (Math.min(row.step(), col.step()) / 2) - 2;

    // add circles
    groups.selectAll('circle')
      .data(([year, payments]) => payments)
      .join('circle')
        .attr('transform', (d, i) => {
          const rowIndex = Math.floor(i / numCols);
          const colIndex = i % numCols;
          return `translate(${col(colIndex)},${row(rowIndex)})`;
        })
        .attr('fill', d => color(d.pay))
        .attr('r', radius);

    /* --- add axis for year labels --- */

    const xAxis = d3.axisBottom(x).tickSize(0);

    g.append('g')
        // move to the bottom of the chart
        .attr('transform', `translate(0,${height})`)
        // add axis
        .call(xAxis)
        // remove baseline
        .call(g => g.select('.domain').remove())
        // increase font size of the labels and set their color
        .call(
          g => g.selectAll('text')
              .attr('font-size', 14)
              .attr('fill', year => colorYear(
                // get number of months with missing payments
                groupedByYear.get(year)
                  .filter(d => d.pay === missing).length
              ))
        );

    /* --- add color legend --- */

    const fontSize = 14;

    const legendData = [
      {label: 'ON TIME', color: color(ontime), x: 0},
      {label: 'MISSED PAYMENT', color: color(missing), x: 100},
      {label: 'NO DATA', color: color(nodata), x: 270},
    ];

    const legendCells = g.append('g')
        .attr('transform', `translate(${margin.left},${height + 40})`)
      .selectAll('g')
      .data(legendData)
      .join('g')
        .attr('transform', d => `translate(${d.x})`);

    legendCells.append('circle')
        .attr('r', fontSize / 2)
        .attr('fill',  d => d.color);

    legendCells.append('text')
        .attr('dominant-baseline', 'middle')
        .attr('font-family', 'sans-serif')
        .attr('fill', 'black')
        .attr('font-size', fontSize)
        .attr('x', fontSize)
        .text(d => d.label);
  </script>
</body>

</html>