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

Register now to learn Fabric in free live sessions led by the best Microsoft experts. From Apr 16 to May 9, in English and Spanish.

Reply
RonanC
New Member

Custom visual barchart and negative values

Good Day

 

Im just attempting to create a custom barchart. I have used several tutorials and they are all basically the same as the one in the documentation. However one issue I have noticed is that I am unable to plot negative values on the bar chart. If i do this, the bar just does not appear.

 

I have been attempting to fix this by changing the domain and ranges of the yScale as well as messing about with bar height and position. The error that I keep facing however is that when I plot a bar, the bar will always stretch down to the farthest point that it can reach. This is shown belowGraph.png

 

For claritys sake let me show you my data source. It is not much.

 

Jess: 70

Aimee: 32

Chris: 28

Erik: 24
Jehan: 3

Ced: -20

Ronan: -35

 

The first 5 values should be starting at the 0 tick untill their respective values and the two last negative values should be stopping at 20 and 35 respectively.

 

Ill also post the code if it will help?

 

module powerbi.extensibility.visual {

    interface DataPoint {
        category: string;
        value: number;
        colour: string;
        identity: powerbi.visuals.ISelectionId;
        highlighted: boolean;
    };

    interface ViewModel {
        dataPoints: DataPoint[];
        maxValue: number;
        highlights: boolean;
    };

    export class Visual implements IVisual {

        private host: IVisualHost;
        private svg: d3.Selection<SVGElement>;
        private barGroup: d3.Selection<SVGElement>;
        private xPadding: number = 0.1;
        private selectionManager: ISelectionManager;
        private xAxisGroup: d3.Selection<SVGElement>;
        private yAxisGroup: d3.Selection<SVGElement>;

        private settings = {
            axis: {
                x: {
                    padding: 50
                },
                y: {
                    padding: 50
                }
            },
            border: {
                top: 10
            }
        }

        constructor(options: VisualConstructorOptions) {
            this.host = options.host;
            this.svg = d3.select(options.element)
                .append("svg")
                .classed("my-little-bar-chart", true);
            this.barGroup = this.svg.append("g")
                .classed("bar-group", true);

            this.xAxisGroup = this.svg.append("g")
                .classed("x-axis", true);

            this.yAxisGroup = this.svg.append("g")
                .classed("y-axis", true);

            this.selectionManager = this.host.createSelectionManager();
        }

        public update(options: VisualUpdateOptions) {

            

            let viewModel = this.getViewModel(options);

            let width = options.viewport.width;
            let height = options.viewport.height;

            this.svg.attr({
                width: width,
                height: height
            });

            let yScale = d3.scale.linear()
                .domain([-60, viewModel.maxValue])
                .range([height - this.settings.axis.x.padding, 0+this.settings.border.top]);

            let yAxis = d3.svg.axis()
                .scale(yScale)
                .orient("left")
                .tickSize(1);

            this.yAxisGroup
                .call(yAxis)
                .attr({
                    transform: "translate(" + this.settings.axis.y.padding + ",0)"
                })
                .style({
                    fill: "#777777"
                })
                .selectAll("text")
                .style({
                    "text-anchor": "end",
                    "font-size": "x-small"
                });

            let xScale = d3.scale.ordinal()
                .domain(viewModel.dataPoints.map(d => d.category))
                .rangeRoundBands([this.settings.axis.y.padding, width], this.xPadding);

            let xAxis = d3.svg.axis()
                .scale(xScale)
                .orient("bottom")
                .tickSize(1);

            this.xAxisGroup
                .call(xAxis)
                .attr({
                    transform: "translate(0, " + (height - this.settings.axis.x.padding) + ")"
                })
                .style({
                    fill: "#777777"
                })
                .selectAll("text")
                .attr({
                    transform: "rotate(-35)"
                })
                .style({
                    "text-anchor": "end",
                    "font-size": "x-small"
                });

            let bars = this.barGroup
                .selectAll(".bar")
                .data(viewModel.dataPoints);

            bars.enter()
                .append("rect")
                .classed("bar", true);

            bars
                .attr({
                    width: xScale.rangeBand(),
                    height: d => {
                        if(d.value>= 0)
                        {
                            console.log("-" + yScale(1))
                            console.log("--" + yScale(2))
                            console.log("---" + yScale(32))
                            console.log(d.category +" "+(height - yScale(d.value) - this.settings.axis.x.padding- yScale(32)));
                            return height - yScale(d.value) - this.settings.axis.x.padding;
                        }
                        else{
                            console.log(d.category +d.value);
                            var _temp = Math.abs(d.value);
                            return height + yScale(_temp)- this.settings.axis.x.padding;
                        }
                        
                    },
                    y: d => 
                    {   
                        if(d.value>= 0)
                        {
                            return yScale(d.value);
                        }
                        else{
                            return yScale(0);
                        }
                        
                    },
                    x: d => xScale(d.category)
                })
                .style({
                    fill: d => d.colour,
                    "fill-opacity": d => viewModel.highlights ? d.highlighted ? 1.0 : 0.5 : 1.0
                })
                .on("click", (d) => {
                    this.selectionManager
                        .select(d.identity, true)
                        .then(ids => {
                            bars.style({
                                "fill-opacity": d =>
                                    ids.length > 0 ?
                                        ids.indexOf(d.identity) >= 0 ?
                                            1.0 :
                                            0.5 :
                                        1.0
                            });
                        });
                });

            bars.exit()
                .remove();
        }

        private getViewModel(options: VisualUpdateOptions): ViewModel {

            let dv = options.dataViews;

            let viewModel: ViewModel = {
                dataPoints: [],
                maxValue: 0,
                highlights: false
            };

            if (!dv
                || !dv[0]
                || !dv[0].categorical
                || !dv[0].categorical.categories
                || !dv[0].categorical.categories[0].source
                || !dv[0].categorical.values)
                return viewModel;

            let view = dv[0].categorical;
            let categories = view.categories[0];
            let values = view.values[0];
            let highlights = values.highlights;

            for (let i = 0, len = Math.max(categories.values.length, values.values.length); i < len; i++) {
                viewModel.dataPoints.push({
                    category: <string>categories.values[i],
                    value: <number>values.values[i],
                    colour: this.host.colorPalette.getColor(<string>categories.values[i]).value,
                    identity: this.host.createSelectionIdBuilder()
                        .withCategory(categories, i)
                        .createSelectionId(),
                    highlighted: highlights ? highlights[i] ? true : false : false
                });
            }

            viewModel.maxValue = d3.max(viewModel.dataPoints, d => d.value);
            viewModel.highlights = viewModel.dataPoints.filter(d => d.highlighted).length > 0;

            return viewModel;
        }
    }
    
    
}

The code above is probably full of errors and mistakes, but i have only just started o be nice. 😛

 

Im sorry if this answer does lie somewhere and I have not seen it.

 

Any help with this matter would be greatly appreciated.

Ronan

1 REPLY 1
v-viig
Community Champion
Community Champion

It seems yScale is incorrect as its min value is -60 but min/max values must be taken from the data set.

We'd recommend to find min value in the same way you find max value and use this min value in the domain isntead of -60.

 

Ignat Vilesov,

Software Engineer

 

Microsoft Power BI Custom Visuals

pbicvsupport@microsoft.com

Helpful resources

Announcements
Microsoft Fabric Learn Together

Microsoft Fabric Learn Together

Covering the world! 9:00-10:30 AM Sydney, 4:00-5:30 PM CET (Paris/Berlin), 7:00-8:30 PM Mexico City

PBI_APRIL_CAROUSEL1

Power BI Monthly Update - April 2024

Check out the April 2024 Power BI update to learn about new features.

April Fabric Community Update

Fabric Community Update - April 2024

Find out what's new and trending in the Fabric Community.

Top Kudoed Authors