I'm learning d3 charts and I want to get the result like the image. The data is json and it looks like this:
[{
"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
Thanks regard.
CodePudding user response:
Here's an example. For simplicity, I've hard coded the positions of entries in the color legend. In practice, it may be better to do the color legend in HTML so that you can take advantage of automatic horizontal layout.
<!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: 1 },
{ date: "2015.2.1", pay: 2 },
{ date: "2015.3.1", pay: 1 },
{ date: "2015.4.1", pay: 2 },
{ date: "2015.5.1", pay: 1 },
{ date: "2015.6.1", pay: 3 },
{ date: "2015.7.1", pay: 1 },
{ date: "2015.8.1", pay: 3 },
{ date: "2015.9.1", pay: 3 },
{ date: "2015.10.1", pay: 1 },
{ date: "2015.11.1", pay: 2 },
{ date: "2015.12.1", pay: 3 },
{ date: "2016.1.1", pay: 1 },
{ date: "2016.2.1", pay: 3 },
{ date: "2016.3.1", pay: 3 },
{ date: "2016.4.1", pay: 3 },
{ date: "2016.5.1", pay: 2 },
{ date: "2016.6.1", pay: 2 },
{ date: "2016.7.1", pay: 2 },
{ date: "2016.8.1", pay: 1 },
{ date: "2016.9.1", pay: 3 },
{ date: "2016.10.1", pay: 3 },
{ date: "2016.11.1", pay: 1 },
{ date: "2016.12.1", pay: 3 },
{ date: "2017.1.1", pay: 2 },
{ date: "2017.2.1", pay: 2 },
{ date: "2017.3.1", pay: 3 },
{ date: "2017.4.1", pay: 2 },
{ date: "2017.5.1", pay: 3 },
{ date: "2017.6.1", pay: 2 },
{ date: "2017.7.1", pay: 3 },
{ date: "2017.8.1", pay: 1 },
{ date: "2017.9.1", pay: 2 },
{ date: "2017.10.1", pay: 2 },
{ 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
const color = d3.scaleOrdinal()
.domain([1, 2, 3])
.range(['DarkSlateGray', 'MediumVioletRed', 'Gainsboro']);
/* --- 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
.call(g => g.selectAll('text').attr('font-size', 14));
/* --- add color legend --- */
const fontSize = 14;
const legendData = [
{label: 'ON TIME', color: color(1), x: 0},
{label: 'MISSED PAYMENT', color: color(2), x: 100},
{label: 'NO DATA', color: color(3), 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>
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>