Skip to main content
cancel
Showing results for 
Search instead for 
Did you mean: 

Find everything you need to get certified on Fabric—skills challenges, live sessions, exam prep, role guidance, and more. Get started

Reply
stawpeed
Frequent Visitor

Problem with the order of donut's slice

Hi everyone,

I am trying to build a custom visual that show 2 donut chart (inner and outer donuts), data and visual as below:

stawpeed_0-1703059327208.png

I was expected that slice's order of outer donut to be sorted base on Categories and Subcategories. But it seems to be sorted base on Values.

In Show data as table view, it already sorted by 'Sum of Value':

stawpeed_1-1703059533660.png

How could I get this problem fixed?

 

Below is my visual.ts and capabilities.json

"use strict";

import * as d3 from "d3";
import { group } from "d3-array";
import powerbi from "powerbi-visuals-api";
import VisualConstructorOptions = powerbi.extensibility.visual.VisualConstructorOptions;
import VisualUpdateOptions = powerbi.extensibility.visual.VisualUpdateOptions;
import IVisual = powerbi.extensibility.visual.IVisual;
import DataView = powerbi.DataView;
import IVisualHost = powerbi.extensibility.IVisualHost;
type Selection<T extends d3.BaseType> = d3.Selection<T, any, any, any>;

export class Visual implements IVisual {
    private host: IVisualHost;
    private svg: Selection<SVGElement>;
    private container: Selection<SVGElement>;
    private firstDonutContainer: Selection<SVGElement>;
    private secondDonutContainer: Selection<SVGElement>;

    constructor(options: VisualConstructorOptions) {
        this.svg = d3.select(options.element)
            .append('svg')
            .classed('doubleDonutChart', true); // Change class to doubleDonutChart
        this.container = this.svg.append("g")
            .classed('container', true);

        // Create the first (inner) donut chart
        this.firstDonutContainer = this.container.append("g")
            .classed('firstDonutContainer', true);

        // Create the second (outer) donut chart
        this.secondDonutContainer = this.container.append("g")
            .classed('secondDonutContainer', true);
    }

    public update(options: VisualUpdateOptions) {
        // Assign the size of viewport
        let width: number = options.viewport.width;
        let height: number = options.viewport.height;
        this.svg.attr("width", width);
        this.svg.attr("height", height);

        // Assuming data contains a category field and a measure field
        let dataView: DataView = options.dataViews[0];
        let categoryValues = dataView.categorical.categories[0].values;
        let measureValues = dataView.categorical.values[0].values.map(val => Number(val));

// Grouping data by categories and calculating the sum
let groupedData = Array.from(d3.group(measureValues, (d, i) => categoryValues[i]), ([key, group]) => ({ category: key, value: d3.sum(group) }));

// Sort the data based on the original order of categories
groupedData.sort((a, b) => d3.descending(a.category, b.category));

// Create a color scale for categories
const categoryColorScale = d3.scaleOrdinal()
    .domain(categoryValues as string[])  // Assuming category values are strings
    .range(["#b4bbbf", "#717171", "#B4BDFF", "#83A2FF"]);  // Adjust the color range as needed

// Creating the first (inner) donut chart using d3 pie function with innerRadius
let pie = d3.pie<any>().value(d => d.value);
let pieData = pie(groupedData).sort((a, b) => d3.ascending(a.data.key, b.data.key));


        let radius: number = Math.min(width, height) / 3;
        let innerRadius: number = radius / 1.4; // Set inner radius for the first donut

        let firstArc: d3.Arc<any, d3.DefaultArcObject> = d3.arc()
            .innerRadius(innerRadius) // Set inner radius for the first donut
            .outerRadius(radius);

        // Update the first (inner) donut chart
        let firstDonutArcs = this.firstDonutContainer.selectAll("path")
            .data(pieData);

        firstDonutArcs.enter().append("path")
            .merge(firstDonutArcs as d3.Selection<SVGPathElement, any, SVGElement, any>)
            .attr("d", function (d: d3.DefaultArcObject | any) {
                return firstArc(d) as string;
            })
            .attr("fill", (d, i) => String(categoryColorScale(d.data.category)))
            .attr('stroke', 'white')
            .style('stroke-width', '0.5px')
            .style('opacity', 1);

        firstDonutArcs.exit().remove();

        // Center the first (inner) donut chart in the middle of the SVG
        this.firstDonutContainer.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");

        // Assuming there is a second category and measure in the data
        let subcategoryValues = dataView.categorical.categories[1].values;
        let secondMeasureValues = dataView.categorical.values[0].values.map(val => Number(val));

// Grouping data by subcategories and calculating the sum
let groupedSecondData = Array.from(d3.group(secondMeasureValues, (d, i) => subcategoryValues[i]), ([key, group]) => ({ category: key, value: d3.sum(group) }));

// Sort the data based on the original order of subcategories
groupedSecondData.sort((a, b) => d3.descending(a.category, b.category));

// Create a color scale for subcategories
const subcategoryColorScale = d3.scaleOrdinal()
    .domain(subcategoryValues as string[])  // Assuming subcategory values are strings
    .range(["#7D1D57", "#AD719A", "#8DA2AC", "#496E87", "#6d8b9e", "#F1E6EE", "#717171", "#E6E6E6"]);  // Adjust the color range as needed

// Creating the second (outer) donut chart using d3 pie function with innerRadius and outerRadius
let secondPie = d3.pie<any>().value(d => d.value);
let secondPieData = secondPie(groupedSecondData).sort((a, b) => d3.descending(a.data.key, b.data.key));


        let outerRadius: number = radius * 1.3; // Set outer radius for the second donut

        let secondArc: d3.Arc<any, d3.DefaultArcObject> = d3.arc()
            .innerRadius(radius) // Inner radius is the outer radius of the first donut
            .outerRadius(outerRadius);

        // Update the second (outer) donut chart
        let secondDonutArcs = this.secondDonutContainer.selectAll("path")
            .data(secondPieData);

        secondDonutArcs.enter().append("path")
            .merge(secondDonutArcs as d3.Selection<SVGPathElement, any, SVGElement, any>)
            .attr("d", function (d: d3.DefaultArcObject | any) {
                return secondArc(d) as string;
            })
            .attr("fill", (d, i) => String(subcategoryColorScale(d.data.category)))
            .attr('stroke', 'white')
            .style('stroke-width', '0.5px')
            .style('opacity', 1);

        secondDonutArcs.exit().remove();

        // Center the second (outer) donut chart in the middle of the SVG
        this.secondDonutContainer.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
    }
}
{
    "dataRoles": [
        {
            "displayName": "Category Data",
            "name": "category",
            "kind": "Grouping"
        },
        {
            "displayName": "Measure Data",
            "name": "measure",
            "kind": "Measure"
        }
    ],
  
    "dataViewMappings": [
        {
            "categorical": {
                "categories": {
                    "for": {
                        "in": "category"
                    },
                    "dataReductionAlgorithm": {
                        "top": {}
                    }
                },
                "values": {
                    "select": [
                        {
                            "for": {
                                "in": "measure"
                            }
                        }
                    ]
                }
            }
        }
    ],    
    "sorting": {
        "implicit": {
            "clauses": [
                {
                    "role": "category",
                    "direction": 1
                },
                {
                    "role": "measure",
                    "direction": 2
                }
            ]
        }
    },
    "privileges": []
}
0 REPLIES 0

Helpful resources

Announcements
September Hackathon Carousel

Microsoft Fabric & AI Learning Hackathon

Learn from experts, get hands-on experience, and win awesome prizes.

Top Kudoed Authors