Issue
I am trying to create a pie chart that displays an array of countries at the first level and the cities for each country at the second level.
I have a JSON file (below) with data that I modified in order to get closer to what I am trying to achieve but it does not seem to work (I'm probably way off...)
[
{city: "Budapest", country: "Hungary"},
{city: "Shenzen", country: "China"},
{city: "Shenzen", country: "China"},
{city: "Shenzen", country: "China"},
{city: "Istanbul", country: "Turkey"},
{city: "Ho Chi Minh", country: "Vietnam"},
{city: "Shenzen", country: "China"},
{city: "Budapes", country: "Hungary"},
{city: "Budapest", country: "Hungary"},
{city: "Shenzen", country: "China"},
{city: "Shenzen", country: "China"},
{city: "Shenzen", country: "China"},
{city: "Istanbul", country: "Turkey"},
{city: "Budapest", country: "Hungary"},
{city: "Shenzen", country: "China"},
{city: "Shenzen", country: "China"},
{city: "Shenzen", country: "China"},
{city: "Istanbul", country: "Turkey"},
]
Below is the modified data that I am using:
[
{country: "Hungary", cities: ["Budapest", "Budapes", "Budapest", "Budapest"]},
{country: "Chine", cities: ["Shenzen", "Shenzen", "Shenzen"]},
{country: "Turkey", cities: ["Istanbul", "Istanbul", "Istanbul"]},
{country: "Vietnam", cities: ["Ho Chi Minh"]},
]
Essentially, I am trying to make a pie chart that shows the 4 countries in the middle and each slices is then broken into the cities for each country. Any help would be highly appreciated.
Code I used for modifying the data:
let countries: any = [];
let intermediete: any = [
...new Set(data.map((col: any) => col.country)),
].reduce((a: any, v: any) => ({ ...a, [v]: [] }), {});
data.forEach((location: any) => {
intermediete[location.country].push(location.city);
});
for (let i = 0; i < Object.keys(intermediete).length; i++) {
countries.push({
country: Object.keys(intermediete)[i],
cities: Object.values(intermediete)[i],
});
}
data = countries;
console.log(data);
let cuntries_count: any = [];
let cities_count: any = [];
data
.map((col: any) => col.cities)
.forEach((element: any) => {
cuntries_count.push(element.length);
cities_count.push([...new Set(element)].length);
});
this.locations_pie_data = {
labels: Object.keys(intermediete),
datasets: [
{
type: 'doughnut',
data: cities_count,
backgroundColor: [...new Set(data.map(() => this.randomHEX()))],
},
{
type: 'doughnut',
data: cuntries_count,
backgroundColor: [...new Set(data.map(() => this.randomHEX()))],
},
],
options: {
rotation: 0,
circumference: 90,
plugins: {
legend: {
position: 'right',
},
},
},
};
});
The desired output should look something like the image below
(inner circle > countries, outer circle > cities in each country)
Solution
Please take a look at below runnable code and see how it could be done.
When it comes to the
legend
, you'll probably have to implement a solution as explained in this answer.
let data = [
{city: "Budapest", country: "Hungary"},
{city: "Shenzen", country: "China"},
{city: "Beijing", country: "China"},
{city: "Shenzen", country: "China"},
{city: "Istanbul", country: "Turkey"},
{city: "Ho Chi Minh", country: "Vietnam"},
{city: "Shenzen", country: "China"},
{city: "Debrecen", country: "Hungary"},
{city: "Budapest", country: "Hungary"},
{city: "Shenzen", country: "China"},
{city: "Shenzen", country: "China"},
{city: "Shenzen", country: "China"},
{city: "Istanbul", country: "Turkey"},
{city: "Budapest", country: "Hungary"},
{city: "Beijing", country: "China"},
{city: "Shenzen", country: "China"},
{city: "Shenzen", country: "China"},
{city: "Istanbul", country: "Turkey"}
];
const colors = ['204, 0, 0', '0, 0, 255', '0, 153, 0', '153, 51, 255'];
data = data
.sort((o1, o2) => o1.country.localeCompare(o2.country));
const countries = data
.reduce((acc, o) => {
let country = acc.find(v => v.country == o.country);
if (!country) {
country = { country: o.country, cities: 0 };
acc.push(country);
}
++country.cities;
return acc;
}, []);
countries.forEach((c, i) => c.color = colors[i]);
const cities = data
.reduce((acc, o) => {
let city = acc.find(v => v.city == o.city);
if (!city) {
city = { country: o.country, city: o.city, count: 0 };
acc.push(city);
}
++city.count;
return acc;
}, []);
cities.forEach(c => c.color = countries.find(o => o.country == c.country).color);
Chart.register(ChartDataLabels);
new Chart('myChart', {
type: 'doughnut',
data: {
datasets: [{
data: cities.map(o => o.count),
labels: cities.map(o => o.city),
backgroundColor: cities.map(c => 'rgba(' + c.color + ', 0.2)'),
borderWidth: 3
},
{
data: countries.map(c => c.cities),
labels: countries.map(c => c.country),
backgroundColor: countries.map(c => 'rgb(' + c.color + ', 0.4)'),
borderWidth: 3
}
]
},
options: {
cutout: '40%',
plugins: {
datalabels: {
textAlign: 'center',
formatter: (v, ctx) => {
const dataset = ctx.chart.data.datasets[ctx.datasetIndex];
return dataset.labels[ctx.dataIndex] + ': ' + dataset.data[ctx.dataIndex];
}
}
}
}
});
canvas {
max-height: 400px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.8.0/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels@2"></script>
<canvas id="myChart"></canvas>
Answered By - uminder
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.