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
ShutTap
Helper I
Helper I

Create stacked column chart

Hi. How to add "Legend" and use it in visualisation? Please help, with some examples of declaring and using, or with link on learning documentation

32 REPLIES 32
ShutTap
Helper I
Helper I

"legend" i mean legend as labels and legend as stack

v-viig
Community Champion
Community Champion

Hello @ShutTap,

 

You might take a look at powerbi-visuals-utils-chartutils that support legend.

You might also take a look at Tornado Chart that uses this module.

 

Ignat Vilesov,

Software Engineer

 

Microsoft Power BI Custom Visuals

pbicvsupport@microsoft.com

 

Thanks, very helpful
One more question: which analogue of getCategoricalObjectValue but for Values (in "legend"), it's needed to add default color to groups of legend, that repeated from category to category

 

getCategoricalObjectValue<Fill>(category, k, 'colorSelector', 'fill', defaultColor)

but fo legends value.

Legends

it's named "series", ok...

v-viig
Community Champion
Community Champion

You might get a default color for each category by using colorPalette.getColor method.

Please take a look at this example to find out more.

 

Ignat Vilesov,

Software Engineer

 

Microsoft Power BI Custom Visuals

pbicvsupport@microsoft.com

Thanks, great 🙂

One more question.

I take color from colorPallete, display colors of legends (series) in Editor, but i don't know how associate it. To edit colors from Editor

v-viig
Community Champion
Community Champion

You should follow these steps below to assign colors for fromatting panel:

  • Specify a property that has color options (example)
  • Parse color for each category (example)
  • Create a instance of SelectionId for category. Selection id is used for binding a color to a proper category (example)
  • Enumerate colors (example)

 

Please let me know if you have extra questions.

 

Ignat Vilesov,

Software Engineer

 

Microsoft Power BI Custom Visuals

pbicvsupport@microsoft.com

Thank you very much! 🙂
When visualisation updates i clear old data with .exit().remove(); but it not working. What i doing wrong?
Also i try to package project to PowerBI Desktop, it exports without errors, but in PBI Desctop clear white plane without any data. What it can be?
P.S. There is no official documentation?

v-viig
Community Champion
Community Champion

Can you post a piece of code that clears old data?

Does your visual work in Power BI web? Are there any exceptions?

 

Ignat Vilesov,

Software Engineer

 

Microsoft Power BI Custom Visuals

pbicvsupport@microsoft.com

 

In PowerBI web is working correctly, exception - no clearing old data.
For example:

let barsSeries = this.barContainer.selectAll('dev').data(viewModel.dataPoints);
			barsSeries.enter().append('rect').classed('bar', true).attr({
				width: xScale.rangeBand(),
				height: d => height - yScale(<number>d.value),
				x: d => xScale(d.category)+margins.left,
				y: d => yScale(<number>d.sumHeight)+margins.top,
				fill: d => d.color,
				'fill-opacity': viewModel.settings.generalView.opacity / 100
			});

and clear it

barsSeries.exit().remove();
v-viig
Community Champion
Community Champion

This code looks well for me but I have a couple of questions:

  1. Are you sure that viewModel.dataPoints and viewModel.settings.generalView.opacity are always defined?
  2. Can you share entire source code to investigate this issue deeper?

 

Ignat Vilesov,

Software Engineer

 

Microsoft Power BI Custom Visuals

pbicvsupport@microsoft.com

module powerbi.extensibility.visual {
    "use strict";
	
	interface VisualViewModel {
		dataPoints: VisualDataPoint[];
		dataLegends: VisualDataLegend[];
		dataMax: number;
		settings: VisualSettings;
	};
	
	interface VisualDataPoint {
		category: string;
		valueName: string;
		value: PrimitiveValue;
		sumHeight: PrimitiveValue;
		conventions: string;
		color: string;
		selectionId: powerbi.visuals.ISelectionId;
	};
	
	interface VisualDataLegend {
		valueName: string;
		index: PrimitiveValue;
		conventions: string;
		color: string;
		selectionId: powerbi.visuals.ISelectionId;
	};
	
	interface VisualSettings {
		enableAxis: {
			show: boolean;
		};

		generalView: {
			opacity: number;
		};
		
		legend: {
			show: boolean;
		};
		/*
		colorSelector: {
			defaultColor: string[];
		};*/
	}
	
	
	function visualTransform(options: VisualUpdateOptions, host: IVisualHost): VisualViewModel {
		let dataViews = options.dataViews;
		let defaultSettings: VisualSettings = {
			enableAxis: {
				show: false
			},
			generalView: {
				opacity: 100
			},
			legend: {
				show: true
			}/*,
			colorSelector: {
				defaultColor: []
			}*/
		};
		let viewModel: VisualViewModel = {
			dataPoints: [],
			dataLegends: [],
			dataMax: 0,
			settings: <VisualSettings>{}
		};

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

		let categorical = dataViews[0].categorical;
		let category = categorical.categories[0];
		let dataValue = categorical.values;
		
		let VisualDataPoints: VisualDataPoint[] = [];
		let VisualDataLegends: VisualDataLegend[] = [];
		let dataMax: number;

		let colorPalette: IColorPalette = host.colorPalette;
		let objects = dataViews[0].metadata.objects;
		let VisualSettings: VisualSettings = {
			enableAxis: {
				show: getValue<boolean>(objects, 'enableAxis', 'show', defaultSettings.enableAxis.show),
			},
			generalView: {
				opacity: getValue<number>(objects, 'generalView', 'opacity', defaultSettings.generalView.opacity),
			},
			legend: {
				show: getValue<boolean>(objects, 'legend', 'show', defaultSettings.legend.show),
			}/*,
			colorSelector: {
				defaultColor: []
			}*/
		};
		
		
		dataValue.sort(function(a,b) {return d3.ascending(a.source.groupName+'', b.source.groupName+'');});
		
		for (let k = 0; k < dataValue.length; k++) {
			
			//VisualSettings.colorSelector.defaultColor[k] = colorPalette.getColor(dataValue[k].source.groupName + '').value;

			VisualDataLegends.push({
				valueName: dataValue[k].source.groupName + '',
				index: k,
				conventions: 'dataValue[k].source.groupName',
				color: colorPalette.getColor(dataValue[k].source.groupName+'').value,
				selectionId: host.createSelectionIdBuilder().withMeasure(dataValue[k].source.groupName+'').createSelectionId()
			});
		}
		
		dataMax=0;
		dataValue.sort(function(a,b) {return d3.descending(a.source.groupName+'', b.source.groupName+'');});
		
		for (let i = 0; i < category.values.length; i++) {
			let sumHeight=0;
			for (let k = 0; k < dataValue.length; k++) {
				
				sumHeight += <number>dataValue[k].values[i];

				VisualDataPoints.push({
					category: category.values[i] + '',
					valueName: dataValue[k].source.groupName + '',
					value: dataValue[k].values[i],
					sumHeight: sumHeight,
					conventions: 'dataValue[k].source.groupName',
					color: colorPalette.getColor(dataValue[k].source.groupName+'').value,
					selectionId: host.createSelectionIdBuilder().withMeasure(dataValue[k].source.groupName+'').createSelectionId()
				});
			}
		dataMax = sumHeight > dataMax ? sumHeight : dataMax;
		}
		
		return {
			dataPoints: VisualDataPoints,
			dataLegends: VisualDataLegends,
			dataMax: dataMax,
			settings: VisualSettings,
		};
	}
	
	
    export class Visual implements IVisual {
		private svg: d3.Selection<SVGElement>;
		private host: IVisualHost;
		private selectionManager: ISelectionManager;
		private VisualContainer: d3.Selection<SVGElement>;
		private barContainer: d3.Selection<SVGElement>;
		private xAxis: d3.Selection<SVGElement>;
		private labelDataLegends: VisualDataLegend[];
		private VisualSettings: VisualSettings;
		private tooltipServiceWrapper: ITooltipServiceWrapper;
		private locale: string;
		private title: d3.Selection<SVGElement>;
		private legend: d3.Selection<SVGElement>;

		static Config = {
			xScalePadding: 0.2,
			solidOpacity: 1,
			transparentOpacity: 0.5,
			margins: {
				top: 30,
				right: 160,
				bottom: 30,
				left: 0,
			},
			xAxisFontMultiplier: 0.04,
		};
		
		
		constructor(options: VisualConstructorOptions) {
			this.host = options.host;
			this.selectionManager = options.host.createSelectionManager();
			this.tooltipServiceWrapper = createTooltipServiceWrapper(this.host.tooltipService, options.element);
			let svg = this.svg = d3.select(options.element).append('svg').classed('Visual', true);
			this.locale = options.host.locale;
			this.barContainer = svg.append('g').classed('barContainer', true);
			this.xAxis = svg.append('g').classed('xAxis', true);
			this.title = svg.append("g").classed("textLabel", true);
			this.legend = svg.append("g").classed("legend", true);
		}

        public update(options: VisualUpdateOptions, title: string) {
			let viewModel: VisualViewModel = visualTransform(options, this.host);
			let settings = this.VisualSettings = viewModel.settings;
			this.labelDataLegends = viewModel.dataLegends;

			let width = options.viewport.width;
			let height = options.viewport.height;
			let margins = Visual.Config.margins;
			
			this.svg.attr({
				width: width,
				height: height
			});
			
			if (settings.enableAxis.show) { height -= margins.bottom; }
			if (settings.legend.show) { width -= margins.right; }
			height -= margins.top;
			width -= margins.left;
			
			this.xAxis.style({
				'font-size': d3.min([height, width]) * Visual.Config.xAxisFontMultiplier,
			});

			let yScale = d3.scale.linear()
				.domain([0, viewModel.dataMax])
				.range([height, 0]);
			let xScale = d3.scale.ordinal()
				.domain(viewModel.dataPoints.map(d => d.category))
				.rangeRoundBands([0, width], Visual.Config.xScalePadding, 0.2);

			let xAxis = d3.svg.axis()
				.scale(xScale)
				.orient('bottom');

			this.xAxis.attr('transform', 'translate('+margins.left+', ' + (height+margins.top) + ')')
				.call(xAxis);
			
			// This must be an anonymous function instead of a lambda because
			// d3 uses 'this' as the reference to the element that was clicked.
			let selectionManager = this.selectionManager;
			let allowInteractions = this.host.allowInteractions;
			
			let legend = this.legend.selectAll('dev').data(viewModel.dataLegends);
			
			//bars (with series)
			let barsSeries = this.barContainer.selectAll('dev').data(viewModel.dataPoints);
			barsSeries.enter().append('rect').classed('bar', true).attr({
				width: xScale.rangeBand(),
				height: d => height - yScale(<number>d.value),
				x: d => xScale(d.category)+margins.left,
				y: d => yScale(<number>d.sumHeight)+margins.top,
				fill: d => d.color,
				'fill-opacity': viewModel.settings.generalView.opacity / 100
			});
			
			//console.log(viewModel.dataPoints);
			
			barsSeries.on('click', function(d) {
				// Allow selection only if the visual is rendered in a view that supports interactivity (e.g. Report)
				if (allowInteractions) {
					selectionManager.select(d.selectionId).then((ids: ISelectionId[]) => {
						barsSeries.attr({
							'fill-opacity': ids.length > 0 ? Visual.Config.transparentOpacity : Visual.Config.solidOpacity
						});

						d3.select(this).attr({
							'fill-opacity': Visual.Config.solidOpacity
						});
						
						if (settings.legend.show) {
							legend.attr({
								'fill-opacity': Visual.Config.solidOpacity
							});
						}
					});
					(<Event>d3.event).stopPropagation();
				}
			});
			
			
			//Legends
			if (settings.legend.show) {
				
			legend.enter().append('circle').classed('legendPoint', true).attr({
				r: 4,
				cx: width+margins.right-8,
				cy: d => <number>d.index * 14+margins.top-4,
				fill: d => d.color
			});
			
			legend.enter().append('text').classed('legend', true).attr({
				x: width+margins.right-20,
				y: d => <number>d.index * 14+margins.top,
				'font-size': 12,
				fill: d => '#777',
				'text-anchor': 'end'
			}).text(d => d.valueName);
			
			this.tooltipServiceWrapper.addTooltip(this.barContainer.selectAll('.bar'),
				(tooltipEvent: TooltipEventArgs<number>) => this.getTooltipData(tooltipEvent.data),
				(tooltipEvent: TooltipEventArgs<number>) => null);
			
			// Title
			let fontSizeValue: number = Math.min(width, height) / 5;
			let fontSizeLabel: number = fontSizeValue / 4;
			this.title.append('text').attr({
				x: "50%",
				y: 16,
				fill: '#000',
				"text-anchor": "middle"
			}).text('Гистограмма с накоплением');

			
			
			legend.on('click', function(bar) {
				// Allow selection only if the visual is rendered in a view that supports interactivity (e.g. Report)
				if (allowInteractions) {
					selectionManager.select(bar.selectionId).then((ids: ISelectionId[]) => {
						barsSeries.attr({
							'fill-opacity': ids.length > 0 ? Visual.Config.transparentOpacity : Visual.Config.solidOpacity
						});
						
						legend.attr({ //hide all legend
							'fill-opacity': ids.length > 0 ? Visual.Config.transparentOpacity : Visual.Config.solidOpacity
						});
						
						barsSeries.attr({ //show active bars
							'fill-opacity': d => ids[0]['key'] == d.selectionId['key'] ? Visual.Config.solidOpacity : Visual.Config.transparentOpacity
						});
						
						d3.select(this).attr({ //show active legend
							'fill-opacity': Visual.Config.solidOpacity
						});
					});
					(<Event>d3.event).stopPropagation();
				}
			});
			}
			
			barsSeries.exit().remove();
			legend.exit().remove();
		}
		
		
		public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstanceEnumeration {
			let objectName = options.objectName;
			let objectEnumeration: VisualObjectInstance[] = [];
			
			switch (objectName) {
				case 'enableAxis':
					objectEnumeration.push({
						objectName: objectName,
						properties: {
							show: this.VisualSettings.enableAxis.show,
						},
						selector: null
					});
					break;
				case 'colorSelector':
					for (let labelDataLegend of this.labelDataLegends) {
						objectEnumeration.push({
							objectName: objectName,
							displayName: labelDataLegend.valueName,
							properties: {
								fill: {
									solid: {
										color: labelDataLegend.color
									}
								}
							},
							selector: labelDataLegend.selectionId.getSelector()
						});
					}
					break;
				case 'generalView':
					objectEnumeration.push({
						objectName: objectName,
						properties: {
							opacity: this.VisualSettings.generalView.opacity,
						},
						validValues: {
							opacity: {
								numberRange: {
									min: 10,
									max: 100
								}
							}
						},
						selector: null
					});
					break;
				
				case 'legend':
					objectEnumeration.push({
						objectName: objectName,
						displayName: 'show',
						properties: {
							show: this.VisualSettings.legend.show
						},
						selector: null
					});
					break;
			};

			return objectEnumeration;
		}

		public destroy(): void {
			// Perform any cleanup tasks here
		}

		private getTooltipData(value: any): VisualTooltipDataItem[] {
			return [{
				displayName: value.valueName,
				value: value.value.toString(),
				color: value.color
			}];
		}
    }
}
v-viig
Community Champion
Community Champion

There's an issue in selector:

this.barContainer.selectAll('dev').data(viewModel.dataPoints);

 

You must select all elements by ".bar" selector instead of "dev" ("dev" selector assumes that there're DOM element called <DEV> but there aren't).

 

Ignat Vilesov,

Software Engineer

 

Microsoft Power BI Custom Visuals

pbicvsupport@microsoft.com

Thanks,

 

replaced

module powerbi.extensibility.visual {
    "use strict";
	
	interface VisualViewModel {
		dataPoints: VisualDataPoint[];
		dataLegends: VisualDataLegend[];
		dataMax: number;
		settings: VisualSettings;
	};
	
	interface VisualDataPoint {
		category: string;
		valueName: string;
		value: PrimitiveValue;
		sumHeight: PrimitiveValue;
		conventions: string;
		color: string;
		selectionId: powerbi.visuals.ISelectionId;
	};
	
	interface VisualDataLegend {
		valueName: string;
		index: PrimitiveValue;
		conventions: string;
		color: string;
		selectionId: powerbi.visuals.ISelectionId;
	};
	
	interface VisualSettings {
		enableAxis: {
			show: boolean;
		};

		generalView: {
			opacity: number;
		};
		
		legend: {
			show: boolean;
		};
		/*
		colorSelector: {
			defaultColor: string[];
		};*/
	}
	
	
	function visualTransform(options: VisualUpdateOptions, host: IVisualHost): VisualViewModel {
		let dataViews = options.dataViews;
		let defaultSettings: VisualSettings = {
			enableAxis: {
				show: false
			},
			generalView: {
				opacity: 100
			},
			legend: {
				show: true
			}/*,
			colorSelector: {
				defaultColor: []
			}*/
		};
		let viewModel: VisualViewModel = {
			dataPoints: [],
			dataLegends: [],
			dataMax: 0,
			settings: <VisualSettings>{}
		};

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

		let categorical = dataViews[0].categorical;
		let category = categorical.categories[0];
		let dataValue = categorical.values;
		
		let VisualDataPoints: VisualDataPoint[] = [];
		let VisualDataLegends: VisualDataLegend[] = [];
		let dataMax: number;

		let colorPalette: IColorPalette = host.colorPalette;
		let objects = dataViews[0].metadata.objects;
		let VisualSettings: VisualSettings = {
			enableAxis: {
				show: getValue<boolean>(objects, 'enableAxis', 'show', defaultSettings.enableAxis.show),
			},
			generalView: {
				opacity: getValue<number>(objects, 'generalView', 'opacity', defaultSettings.generalView.opacity),
			},
			legend: {
				show: getValue<boolean>(objects, 'legend', 'show', defaultSettings.legend.show),
			}/*,
			colorSelector: {
				defaultColor: []
			}*/
		};
		
		
		dataValue.sort(function(a,b) {return d3.ascending(a.source.groupName+'', b.source.groupName+'');});
		
		for (let k = 0; k < dataValue.length; k++) {
			
			//VisualSettings.colorSelector.defaultColor[k] = colorPalette.getColor(dataValue[k].source.groupName + '').value;

			VisualDataLegends.push({
				valueName: dataValue[k].source.groupName + '',
				index: k,
				conventions: 'dataValue[k].source.groupName',
				color: colorPalette.getColor(dataValue[k].source.groupName+'').value,
				selectionId: host.createSelectionIdBuilder().withMeasure(dataValue[k].source.groupName+'').createSelectionId()
			});
		}
		
		dataMax=0;
		dataValue.sort(function(a,b) {return d3.descending(a.source.groupName+'', b.source.groupName+'');});
		
		for (let i = 0; i < category.values.length; i++) {
			let sumHeight=0;
			for (let k = 0; k < dataValue.length; k++) {
				
				sumHeight += <number>dataValue[k].values[i];

				VisualDataPoints.push({
					category: category.values[i] + '',
					valueName: dataValue[k].source.groupName + '',
					value: dataValue[k].values[i],
					sumHeight: sumHeight,
					conventions: 'dataValue[k].source.groupName',
					color: colorPalette.getColor(dataValue[k].source.groupName+'').value,
					selectionId: host.createSelectionIdBuilder().withMeasure(dataValue[k].source.groupName+'').createSelectionId()
				});
			}
		dataMax = sumHeight > dataMax ? sumHeight : dataMax;
		}
		
		return {
			dataPoints: VisualDataPoints,
			dataLegends: VisualDataLegends,
			dataMax: dataMax,
			settings: VisualSettings,
		};
	}
	
	
    export class Visual implements IVisual {
		private svg: d3.Selection<SVGElement>;
		private host: IVisualHost;
		private selectionManager: ISelectionManager;
		private VisualContainer: d3.Selection<SVGElement>;
		private barContainer: d3.Selection<SVGElement>;
		private xAxis: d3.Selection<SVGElement>;
		private labelDataLegends: VisualDataLegend[];
		private VisualSettings: VisualSettings;
		private tooltipServiceWrapper: ITooltipServiceWrapper;
		private locale: string;
		private title: d3.Selection<SVGElement>;
		private legend: d3.Selection<SVGElement>;

		static Config = {
			xScalePadding: 0.2,
			solidOpacity: 1,
			transparentOpacity: 0.5,
			margins: {
				top: 30,
				right: 160,
				bottom: 30,
				left: 0,
			},
			xAxisFontMultiplier: 0.04,
		};
		
		
		constructor(options: VisualConstructorOptions) {
			this.host = options.host;
			this.selectionManager = options.host.createSelectionManager();
			this.tooltipServiceWrapper = createTooltipServiceWrapper(this.host.tooltipService, options.element);
			let svg = this.svg = d3.select(options.element).append('svg').classed('Visual', true);
			this.locale = options.host.locale;
			this.barContainer = svg.append('g').classed('barContainer', true);
			this.xAxis = svg.append('g').classed('xAxis', true);
			this.title = svg.append("g").classed("textLabel", true);
			this.legend = svg.append("g").classed("legend", true);
		}

        public update(options: VisualUpdateOptions, title: string) {
			let viewModel: VisualViewModel = visualTransform(options, this.host);
			let settings = this.VisualSettings = viewModel.settings;
			this.labelDataLegends = viewModel.dataLegends;

			let width = options.viewport.width;
			let height = options.viewport.height;
			let margins = Visual.Config.margins;
			
			this.svg.attr({
				width: width,
				height: height
			});
			
			if (settings.enableAxis.show) { height -= margins.bottom; }
			if (settings.legend.show) { width -= margins.right; }
			height -= margins.top;
			width -= margins.left;
			
			this.xAxis.style({
				'font-size': d3.min([height, width]) * Visual.Config.xAxisFontMultiplier,
			});

			let yScale = d3.scale.linear()
				.domain([0, viewModel.dataMax])
				.range([height, 0]);
			let xScale = d3.scale.ordinal()
				.domain(viewModel.dataPoints.map(d => d.category))
				.rangeRoundBands([0, width], Visual.Config.xScalePadding, 0.2);

			let xAxis = d3.svg.axis()
				.scale(xScale)
				.orient('bottom');

			this.xAxis.attr('transform', 'translate('+margins.left+', ' + (height+margins.top) + ')')
				.call(xAxis);
			
			// This must be an anonymous function instead of a lambda because
			// d3 uses 'this' as the reference to the element that was clicked.
			let selectionManager = this.selectionManager;
			let allowInteractions = this.host.allowInteractions;
			
			let legend = this.legend.selectAll('.legend').data(viewModel.dataLegends);
			
			//bars (with series)
			let barsSeries = this.barContainer.selectAll('.bar').data(viewModel.dataPoints);
			barsSeries.enter().append('rect').classed('bar', true).attr({
				width: xScale.rangeBand(),
				height: d => height - yScale(<number>d.value),
				x: d => xScale(d.category)+margins.left,
				y: d => yScale(<number>d.sumHeight)+margins.top,
				fill: d => d.color,
				'fill-opacity': viewModel.settings.generalView.opacity / 100
			});
			
			//console.log(viewModel.dataPoints);
			
			barsSeries.on('click', function(d) {
				// Allow selection only if the visual is rendered in a view that supports interactivity (e.g. Report)
				if (allowInteractions) {
					selectionManager.select(d.selectionId).then((ids: ISelectionId[]) => {
						barsSeries.attr({
							'fill-opacity': ids.length > 0 ? Visual.Config.transparentOpacity : Visual.Config.solidOpacity
						});

						d3.select(this).attr({
							'fill-opacity': Visual.Config.solidOpacity
						});
						
						if (settings.legend.show) {
							legend.attr({
								'fill-opacity': Visual.Config.solidOpacity
							});
						}
					});
					(<Event>d3.event).stopPropagation();
				}
			});
			
			
			//Legends
			if (settings.legend.show) {
				
			legend.enter().append('circle').classed('legend', true).attr({
				r: 4,
				cx: width+margins.right-8,
				cy: d => <number>d.index * 14+margins.top-4,
				fill: d => d.color
			});
			
			legend.enter().append('text').classed('legend', true).attr({
				x: width+margins.right-20,
				y: d => <number>d.index * 14+margins.top,
				'font-size': 12,
				fill: d => '#777',
				'text-anchor': 'end'
			}).text(d => d.valueName);
			
			legend.on('click', function(bar) {
				// Allow selection only if the visual is rendered in a view that supports interactivity (e.g. Report)
				if (allowInteractions) {
					selectionManager.select(bar.selectionId).then((ids: ISelectionId[]) => {
						barsSeries.attr({
							'fill-opacity': ids.length > 0 ? Visual.Config.transparentOpacity : Visual.Config.solidOpacity
						});
						
						legend.attr({ //hide all legend
							'fill-opacity': ids.length > 0 ? Visual.Config.transparentOpacity : Visual.Config.solidOpacity
						});
						
						barsSeries.attr({ //show active bars
							'fill-opacity': d => ids[0]['key'] == d.selectionId['key'] ? Visual.Config.solidOpacity : Visual.Config.transparentOpacity
						});
						
						d3.select(this).attr({ //show active legend
							'fill-opacity': Visual.Config.solidOpacity
						});
					});
					(<Event>d3.event).stopPropagation();
				}
			});
			}
			
			this.tooltipServiceWrapper.addTooltip(this.barContainer.selectAll('.bar'),
				(tooltipEvent: TooltipEventArgs<number>) => this.getTooltipData(tooltipEvent.data),
				(tooltipEvent: TooltipEventArgs<number>) => null);
			
			// Title
			let fontSizeValue: number = Math.min(width, height) / 5;
			let fontSizeLabel: number = fontSizeValue / 4;
			this.title.append('text').attr({
				x: "50%",
				y: 16,
				fill: '#000',
				"text-anchor": "middle"
			}).text('Гистограмма с накоплением');
			
			barsSeries.exit().remove();
			legend.exit().remove();
		}
		
		
		public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstanceEnumeration {
			let objectName = options.objectName;
			let objectEnumeration: VisualObjectInstance[] = [];
			
			switch (objectName) {
				case 'enableAxis':
					objectEnumeration.push({
						objectName: objectName,
						properties: {
							show: this.VisualSettings.enableAxis.show,
						},
						selector: null
					});
					break;
				case 'colorSelector':
					for (let labelDataLegend of this.labelDataLegends) {
						objectEnumeration.push({
							objectName: objectName,
							displayName: labelDataLegend.valueName,
							properties: {
								fill: {
									solid: {
										color: labelDataLegend.color
									}
								}
							},
							selector: labelDataLegend.selectionId.getSelector()
						});
					}
					break;
				case 'generalView':
					objectEnumeration.push({
						objectName: objectName,
						properties: {
							opacity: this.VisualSettings.generalView.opacity,
						},
						validValues: {
							opacity: {
								numberRange: {
									min: 10,
									max: 100
								}
							}
						},
						selector: null
					});
					break;
				
				case 'legend':
					objectEnumeration.push({
						objectName: objectName,
						displayName: 'show',
						properties: {
							show: this.VisualSettings.legend.show
						},
						selector: null
					});
					break;
			};

			return objectEnumeration;
		}

		public destroy(): void {
			// Perform any cleanup tasks here
		}

		private getTooltipData(value: any): VisualTooltipDataItem[] {
			return [{
				displayName: value.valueName,
				value: value.value.toString(),
				color: value.color
			}];
		}
    }
}

May be i don't understand something, but, for example, this.legend = svg.append("g").classed("legend", true); - created "layer", then let legend = this.legend.selectAll('.legend').data(viewModel.dataLegends); - put some prepared elements with class "legend", fill with data, then put some elements based at previous prepared elements legend.enter().append('circle').classed('legend', true)legend.enter().append('text').classed('legend', true) ?
So, we have some elements with data and visualization. Next, i have legend.on('click', function(bar)  that doing something at clicking at "legends". Why it working with last added legend.enter().append('text') only , but not working at both (legend.enter().append('circle') and legend.enter().append('text')). What i understand wrong?

 

v-viig
Community Champion
Community Champion

I'm not sure that your D3 flow is correct. It'b be good to use this flow: 

let legend = svg.selectAll('.legend').data(viewModel.dataLegends);

legend
    .enter()
    .append("g")
    .classed("legend", true);

let circle = legend.selectAll("circle").data([{put your data here}]);

circle
.enter()
.append('circle');

circle
.exit()
.remove();

let text = legend.selectAll("text").data([{put your data here}]);

text
.enter()
.append('text');

text
.exit()
.remove();
legend .exit() .remove();

Ignat Vilesov,

Software Engineer

 

Microsoft Power BI Custom Visuals

pbicvsupport@microsoft.com

 

trying to use it, is no difference...

v-viig
Community Champion
Community Champion

Can you share the updated source code to investigate this issue deeper?

 

Ignat Vilesov,

Software Engineer

 

Microsoft Power BI Custom Visuals

pbicvsupport@microsoft.com

I try to make working only "title" (textLabel), but it still not working... i don't undersand why)

export class Visual implements IVisual {
		private textLabel: d3.Selection<SVGElement>;
...

 

constructor(options: VisualConstructorOptions) {
			this.textLabel = svg.append("g").classed("textLabel", true);
...

 

public update(options: VisualUpdateOptions, title: string) {

...
// Title let fontSizeValue: number = Math.min(width, height) / 5; let fontSizeLabel: number = fontSizeValue / 4; let textLabel = this.textLabel; textLabel.append('text').attr({ x: width/2, y: 16+'px', fill: '#000', "text-anchor": "middle" }).text('Some text');
...

 

 UPD

I take example of sampleBarChart from Git, insert my code of textLabel - and situation repeats but only with textLabel. Bars are ok.

module powerbi.extensibility.visual {
    /**
     * Interface for BarCharts viewmodel.
     *
     * @interface
     * @property {BarChartDataPoint[]} dataPoints - Set of data points the visual will render.
     * @property {number} dataMax                 - Maximum data value in the set of data points.
     */
    interface BarChartViewModel {
        dataPoints: BarChartDataPoint[];
        dataMax: number;
        settings: BarChartSettings;
    };

    /**
     * Interface for BarChart data points.
     *
     * @interface
     * @property {number} value             - Data value for point.
     * @property {string} category          - Corresponding category of data value.
     * @property {string} color             - Color corresponding to data point.
     * @property {ISelectionId} selectionId - Id assigned to data point for cross filtering
     *                                        and visual interaction.
     */
    interface BarChartDataPoint {
        value: PrimitiveValue;
        category: string;
        color: string;
        selectionId: powerbi.visuals.ISelectionId;
    };

    /**
     * Interface for BarChart settings.
     *
     * @interface
     * @property {{show:boolean}} enableAxis - Object property that allows axis to be enabled.
     */
    interface BarChartSettings {
        enableAxis: {
            show: boolean;
        };

        generalView: {
            opacity: number;
        };
    }

    /**
     * Function that converts queried data into a view model that will be used by the visual.
     *
     * @function
     * @param {VisualUpdateOptions} options - Contains references to the size of the container
     *                                        and the dataView which contains all the data
     *                                        the visual had queried.
     * @param {IVisualHost} host            - Contains references to the host which contains services
     */
    function visualTransform(options: VisualUpdateOptions, host: IVisualHost): BarChartViewModel {
        let dataViews = options.dataViews;
        let defaultSettings: BarChartSettings = {
            enableAxis: {
                show: false,
            },
            generalView: {
                opacity: 100
            }
        };
        let viewModel: BarChartViewModel = {
            dataPoints: [],
            dataMax: 0,
            settings: <BarChartSettings>{}
        };

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

        let categorical = dataViews[0].categorical;
        let category = categorical.categories[0];
        let dataValue = categorical.values[0];

        let barChartDataPoints: BarChartDataPoint[] = [];
        let dataMax: number;

        let colorPalette: IColorPalette = host.colorPalette;
        let objects = dataViews[0].metadata.objects;
        let barChartSettings: BarChartSettings = {
            enableAxis: {
                show: getValue<boolean>(objects, 'enableAxis', 'show', defaultSettings.enableAxis.show),
            },
            generalView: {
                opacity: getValue<number>(objects, 'generalView', 'opacity', defaultSettings.generalView.opacity),
            }
        };
        for (let i = 0, len = Math.max(category.values.length, dataValue.values.length); i < len; i++) {
            let defaultColor: Fill = {
                solid: {
                    color: colorPalette.getColor(category.values[i] + '').value
                }
            };

            barChartDataPoints.push({
                category: category.values[i] + '',
                value: dataValue.values[i],
                color: getCategoricalObjectValue<Fill>(category, i, 'colorSelector', 'fill', defaultColor).solid.color,
                selectionId: host.createSelectionIdBuilder()
                    .withCategory(category, i)
                    .createSelectionId()
            });
        }
        dataMax = <number>dataValue.maxLocal;

        return {
            dataPoints: barChartDataPoints,
            dataMax: dataMax,
            settings: barChartSettings,
        };
    }

    export class BarChart implements IVisual {
        private svg: d3.Selection<SVGElement>;
        private host: IVisualHost;
        private selectionManager: ISelectionManager;
        private barChartContainer: d3.Selection<SVGElement>;
        private barContainer: d3.Selection<SVGElement>;
        private xAxis: d3.Selection<SVGElement>;
        private barDataPoints: BarChartDataPoint[];
        private barChartSettings: BarChartSettings;
        private tooltipServiceWrapper: ITooltipServiceWrapper;
        private locale: string;
		private textLabel: d3.Selection<SVGElement>;

        static Config = {
            xScalePadding: 0.1,
            solidOpacity: 1,
            transparentOpacity: 0.5,
            margins: {
                top: 0,
                right: 0,
                bottom: 25,
                left: 30,
            },
            xAxisFontMultiplier: 0.04,
        };

        /**
         * Creates instance of BarChart. This method is only called once.
         *
         * @constructor
         * @param {VisualConstructorOptions} options - Contains references to the element that will
         *                                             contain the visual and a reference to the host
         *                                             which contains services.
         */
        constructor(options: VisualConstructorOptions) {
            this.host = options.host;
            this.selectionManager = options.host.createSelectionManager();
            this.tooltipServiceWrapper = createTooltipServiceWrapper(this.host.tooltipService, options.element);
            let svg = this.svg = d3.select(options.element)
                .append('svg')
                .classed('barChart', true);

            this.locale = options.host.locale;

            this.barContainer = svg.append('g')
                .classed('barContainer', true);

            this.xAxis = svg.append('g')
                .classed('xAxis', true);
				this.textLabel = svg.append("g").classed("textLabel", true);
        }

        /**
         * Updates the state of the visual. Every sequential databinding and resize will call update.
         *
         * @function
         * @param {VisualUpdateOptions} options - Contains references to the size of the container
         *                                        and the dataView which contains all the data
         *                                        the visual had queried.
         */
        public update(options: VisualUpdateOptions) {
            let viewModel: BarChartViewModel = visualTransform(options, this.host);
            let settings = this.barChartSettings = viewModel.settings;
            this.barDataPoints = viewModel.dataPoints;

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

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

            if (settings.enableAxis.show) {
                let margins = BarChart.Config.margins;
                height -= margins.bottom;
            }

            this.xAxis.style({
                'font-size': d3.min([height, width]) * BarChart.Config.xAxisFontMultiplier,
            });

            let yScale = d3.scale.linear()
                .domain([0, viewModel.dataMax])
                .range([height, 0]);

            let xScale = d3.scale.ordinal()
                .domain(viewModel.dataPoints.map(d => d.category))
                .rangeRoundBands([0, width], BarChart.Config.xScalePadding, 0.2);

            let xAxis = d3.svg.axis()
                .scale(xScale)
                .orient('bottom');

            this.xAxis.attr('transform', 'translate(0, ' + height + ')')
                .call(xAxis);

            let bars = this.barContainer.selectAll('.bar').data(viewModel.dataPoints);
            bars.enter()
                .append('rect')
                .classed('bar', true);

            bars.attr({
                width: xScale.rangeBand(),
                height: d => height - yScale(<number>d.value),
                y: d => yScale(<number>d.value),
                x: d => xScale(d.category),
                fill: d => d.color,
                'fill-opacity': viewModel.settings.generalView.opacity / 100
            });
			
			// Title
			let fontSizeValue: number = Math.min(width, height) / 5;
			let fontSizeLabel: number = fontSizeValue / 4;
			let textLabel = this.textLabel; 
			textLabel.append('text').attr({
				x: width/2,
				y: 16+'px',
				fill: '#000',
				"text-anchor": "middle"
			}).text('Some text');

            this.tooltipServiceWrapper.addTooltip(this.barContainer.selectAll('.bar'),
                (tooltipEvent: TooltipEventArgs<number>) => this.getTooltipData(tooltipEvent.data),
                (tooltipEvent: TooltipEventArgs<number>) => null);

            let selectionManager = this.selectionManager;
            let allowInteractions = this.host.allowInteractions;

            // This must be an anonymous function instead of a lambda because
            // d3 uses 'this' as the reference to the element that was clicked.
            bars.on('click', function(d) {
				// Allow selection only if the visual is rendered in a view that supports interactivity (e.g. Report)
                if (allowInteractions) {
                    selectionManager.select(d.selectionId).then((ids: ISelectionId[]) => {
                        bars.attr({
                            'fill-opacity': ids.length > 0 ? BarChart.Config.transparentOpacity : BarChart.Config.solidOpacity
                        });

                        d3.select(this).attr({
                            'fill-opacity': BarChart.Config.solidOpacity
                        });
                    });

                    (<Event>d3.event).stopPropagation();
                }
            });

            bars.exit()
               .remove();
        }

        /**
         * Enumerates through the objects defined in the capabilities and adds the properties to the format pane
         *
         * @function
         * @param {EnumerateVisualObjectInstancesOptions} options - Map of defined objects
         */
        public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstanceEnumeration {
            let objectName = options.objectName;
            let objectEnumeration: VisualObjectInstance[] = [];

            switch (objectName) {
                case 'enableAxis':
                    objectEnumeration.push({
                        objectName: objectName,
                        properties: {
                            show: this.barChartSettings.enableAxis.show,
                        },
                        selector: null
                    });
                    break;
                case 'colorSelector':
                    for (let barDataPoint of this.barDataPoints) {
                        objectEnumeration.push({
                            objectName: objectName,
                            displayName: barDataPoint.category,
                            properties: {
                                fill: {
                                    solid: {
                                        color: barDataPoint.color
                                    }
                                }
                            },
                            selector: barDataPoint.selectionId.getSelector()
                        });
                    }
                    break;
                case 'generalView':
                    objectEnumeration.push({
                        objectName: objectName,
                        properties: {
                            opacity: this.barChartSettings.generalView.opacity,
                        },
                        validValues: {
                            opacity: {
                                numberRange: {
                                    min: 10,
                                    max: 100
                                }
                            }
                        },
                        selector: null
                    });
                    break;
            };

            return objectEnumeration;
        }

        /**
         * Destroy runs when the visual is removed. Any cleanup that the visual needs to
         * do should be done here.
         *
         * @function
         */
        public destroy(): void {
            // Perform any cleanup tasks here
        }

        private getTooltipData(value: any): VisualTooltipDataItem[] {
            let language = getLocalizedString(this.locale, "LanguageKey");
            return [{
                displayName: value.category,
                value: value.value.toString(),
                color: value.color,
                header: language && "displayed language " + language
            }];
        }
    }
}
v-viig
Community Champion
Community Champion

Your code creates a new element for each update call.

This issue can be fixed by using D3's data-binding. It will look something like this in your case:

 

const textLabelSelection = this.textLabel
    .selectAll("text")
    .data(["Some text"]);

textLabelSelection
    .enter()
    .append("text");

textLabelSelection
    .attr({
        x: width / 2,
        y: 16 + 'px',
    })
    .style({
        fill: '#000',
        "text-anchor": "middle",
    })
    .text((textValue) => textValue);

textLabelSelection
    .exit()
    .remove();

Ignat Vilesov,

Software Engineer

 

Microsoft Power BI Custom Visuals

pbicvsupport@microsoft.com

 

All done, thanks.

Colors are not associate with the editor (in Editor takes from bars, but can't edits from Editor)

module powerbi.extensibility.visual {
    "use strict";
	
	interface VisualViewModel {
		dataPoints: VisualDataPoint[];
		dataLegends: VisualDataLegend[];
		dataMax: number;
		settings: VisualSettings;
	};
	
	interface VisualDataPoint {
		category: string;
		valueName: string;
		value: PrimitiveValue;
		sumHeight: PrimitiveValue;
		conventions: string;
		color: string;
		selectionId: powerbi.visuals.ISelectionId;
	};
	
	interface VisualDataLegend {
		valueName: string;
		index: PrimitiveValue;
		conventions: string;
		color: string;
		selectionId: powerbi.visuals.ISelectionId;
	};
	
	interface VisualSettings {
		enableAxis: {
			show: boolean;
		};

		generalView: {
			opacity: number;
		};
		
		legend: {
			show: boolean;
		};
		/*
		colorSelector: {
			defaultColor: string[];
		};*/
	}
	
	
	function visualTransform(options: VisualUpdateOptions, host: IVisualHost): VisualViewModel {
		let dataViews = options.dataViews;
		let defaultSettings: VisualSettings = {
			enableAxis: {
				show: false
			},
			generalView: {
				opacity: 100
			},
			legend: {
				show: true
			}/*,
			colorSelector: {
				defaultColor: []
			}*/
		};
		let viewModel: VisualViewModel = {
			dataPoints: [],
			dataLegends: [],
			dataMax: 0,
			settings: <VisualSettings>{}
		};

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

		let categorical = dataViews[0].categorical;
		let category = categorical.categories[0];
		let dataValue = categorical.values;
		
		let VisualDataPoints: VisualDataPoint[] = [];
		let VisualDataLegends: VisualDataLegend[] = [];
		let dataMax: number;

		let colorPalette: IColorPalette = host.colorPalette;
		let objects = dataViews[0].metadata.objects;
		let VisualSettings: VisualSettings = {
			enableAxis: {
				show: getValue<boolean>(objects, 'enableAxis', 'show', defaultSettings.enableAxis.show),
			},
			generalView: {
				opacity: getValue<number>(objects, 'generalView', 'opacity', defaultSettings.generalView.opacity),
			},
			legend: {
				show: getValue<boolean>(objects, 'legend', 'show', defaultSettings.legend.show),
			}/*,
			colorSelector: {
				defaultColor: []
			}*/
		};
		
		
		
		dataValue.sort(function(a,b) {return d3.ascending(a.source.groupName+'', b.source.groupName+'');});
		for (let k = 0; k < dataValue.length; k++) {
			
			//VisualSettings.colorSelector.defaultColor[k] = colorPalette.getColor(dataValue[k].source.groupName + '').value;
			VisualDataLegends.push({
				valueName: dataValue[k].source.groupName + '',
				index: k,
				conventions: 'dataValue[k].source.groupName',
				color: colorPalette.getColor(dataValue[k].source.groupName+'').value,
				selectionId: host.createSelectionIdBuilder().withMeasure(dataValue[k].source.groupName+'').createSelectionId()
			});
		}
		
		
		dataMax=0;
		dataValue.sort(function(a,b) {return d3.descending(a.source.groupName+'', b.source.groupName+'');});
		
		for (let i = 0; i < category.values.length; i++) {
			let sumHeight=0;
			for (let k = 0; k < dataValue.length; k++) {
				
				sumHeight += <number>dataValue[k].values[i];

				VisualDataPoints.push({
					category: category.values[i] + '',
					valueName: dataValue[k].source.groupName + '',
					value: dataValue[k].values[i],
					sumHeight: sumHeight,
					conventions: 'dataValue[k].source.groupName',
					color: colorPalette.getColor(dataValue[k].source.groupName+'').value,
					selectionId: host.createSelectionIdBuilder().withMeasure(dataValue[k].source.groupName+'').createSelectionId()
				});
			}
		dataMax = sumHeight > dataMax ? sumHeight : dataMax;
		}
		
		return {
			dataPoints: VisualDataPoints,
			dataLegends: VisualDataLegends,
			dataMax: dataMax,
			settings: VisualSettings,
		};
	}
	
	
    export class Visual implements IVisual {
		private svg: d3.Selection<SVGElement>;
		private host: IVisualHost;
		private selectionManager: ISelectionManager;
		private VisualContainer: d3.Selection<SVGElement>;
		private barContainer: d3.Selection<SVGElement>;
		private xAxis: d3.Selection<SVGElement>;
		private labelDataLegends: VisualDataLegend[];
		private VisualSettings: VisualSettings;
		private tooltipServiceWrapper: ITooltipServiceWrapper;
		private locale: string;
		private textLabel: d3.Selection<SVGElement>;
		private legend: d3.Selection<SVGElement>;

		static Config = {
			xScalePadding: 0.2,
			solidOpacity: 1,
			transparentOpacity: 0.5,
			margins: {
				top: 30,
				right: 160,
				bottom: 30,
				left: 0,
			},
			xAxisFontMultiplier: 0.04,
		};
		
		
		constructor(options: VisualConstructorOptions) {
			this.host = options.host;
			this.selectionManager = options.host.createSelectionManager();
			this.tooltipServiceWrapper = createTooltipServiceWrapper(this.host.tooltipService, options.element);
			let svg = this.svg = d3.select(options.element).append('svg').classed('Visual', true);
			this.locale = options.host.locale;
			this.barContainer = svg.append('g').classed('barContainer', true);
			this.xAxis = svg.append('g').classed('xAxis', true);
			this.textLabel = svg.append("g").classed("textLabel", true);
			this.legend = svg.append("g").classed("legend", true);
		}

        public update(options: VisualUpdateOptions, title: string) {
			let viewModel: VisualViewModel = visualTransform(options, this.host);
			let settings = this.VisualSettings = viewModel.settings;
			this.labelDataLegends = viewModel.dataLegends;

			let width = options.viewport.width;
			let height = options.viewport.height;
			let margins = Visual.Config.margins;
			
			this.svg.attr({
				width: width,
				height: height
			});
			
			if (settings.enableAxis.show) { height -= margins.bottom; }
			if (settings.legend.show) { width -= margins.right; }
			height -= margins.top;
			width -= margins.left;
			
			this.xAxis.style({
				'font-size': d3.min([height, width]) * Visual.Config.xAxisFontMultiplier,
			});

			let yScale = d3.scale.linear()
				.domain([0, viewModel.dataMax])
				.range([height, 0]);
			let xScale = d3.scale.ordinal()
				.domain(viewModel.dataPoints.map(d => d.category))
				.rangeRoundBands([0, width], Visual.Config.xScalePadding, 0.2);

			let xAxis = d3.svg.axis()
				.scale(xScale)
				.orient('bottom');

			this.xAxis.attr('transform', 'translate('+margins.left+', ' + (height+margins.top) + ')')
				.call(xAxis);
			
			// This must be an anonymous function instead of a lambda because
			// d3 uses 'this' as the reference to the element that was clicked.
			let selectionManager = this.selectionManager;
			let allowInteractions = this.host.allowInteractions;
			
			//bars (with series)
			let barsSeries = this.barContainer.selectAll('rect').data(viewModel.dataPoints);
			let legend = this.legend.selectAll('.legendLine').data(settings.legend.show ? viewModel.dataLegends : [null]);
			
			barsSeries.enter().append('rect');
			barsSeries.attr({
				width: xScale.rangeBand(),
				height: d => height - yScale(<number>d.value),
				x: d => xScale(d.category)+margins.left,
				y: d => yScale(<number>d.sumHeight)+margins.top,
				fill: d => d.color,
				'fill-opacity': viewModel.settings.generalView.opacity / 100
			});
			
			barsSeries.on('click', function(d) {
				// Allow selection only if the visual is rendered in a view that supports interactivity (e.g. Report)
				if (allowInteractions) {
					selectionManager.select(d.selectionId).then((ids: ISelectionId[]) => {
						barsSeries.attr({
							'fill-opacity': ids.length > 0 ? Visual.Config.transparentOpacity : Visual.Config.solidOpacity
						});

						d3.select(this).attr({
							'fill-opacity': Visual.Config.solidOpacity
						});
						
						if (settings.legend.show) {
							legend.attr({
								'fill-opacity': Visual.Config.solidOpacity
							});
						}
					});
					(<Event>d3.event).stopPropagation();
				}
			});
			barsSeries.exit().remove();
			
			//Legends
			let legendLine = legend.enter().append('g').classed('legendLine', true);
			
			legendLine.append('circle').attr({
				r: 4,
				cx: width+margins.right-8,
				cy: d => <number>d.index * 14+margins.top-4,
				fill: d => d.color
			});
			legendLine.append('text').attr({
				x: width+margins.right-20,
				y: d => <number>d.index * 14+margins.top,
				'font-size': 12,
				fill: d => '#777',
				'text-anchor': 'end'
			}).text(d => d.valueName);
			
			legend.on('click', function(bar) {
				// Allow selection only if the visual is rendered in a view that supports interactivity (e.g. Report)
				if (allowInteractions) {
					selectionManager.select(bar.selectionId).then((ids: ISelectionId[]) => {
						barsSeries.attr({
							'fill-opacity': ids.length > 0 ? Visual.Config.transparentOpacity : Visual.Config.solidOpacity
						});
						
						legend.attr({ //hide all legend
							'fill-opacity': ids.length > 0 ? Visual.Config.transparentOpacity : Visual.Config.solidOpacity
						});
						
						barsSeries.attr({ //show active bars
							'fill-opacity': d => ids[0]['key'] == d.selectionId['key'] ? Visual.Config.solidOpacity : Visual.Config.transparentOpacity
						});
						
						d3.select(this).attr({ //show active legend
							'fill-opacity': Visual.Config.solidOpacity
						});
					});
					(<Event>d3.event).stopPropagation();
				}
			});
			legend.exit().remove();
			
			this.tooltipServiceWrapper.addTooltip(this.barContainer.selectAll('.bar'),
				(tooltipEvent: TooltipEventArgs<number>) => this.getTooltipData(tooltipEvent.data),
				(tooltipEvent: TooltipEventArgs<number>) => null);
			
			// Title
			let fontSizeValue: number = Math.min(width, height) / 5;
			let fontSizeLabel: number = fontSizeValue / 4;
			let textLabel = this.textLabel.selectAll('text').data(['textLabel']); 
			textLabel.enter().append('text');
			textLabel.attr({
				x: width/2,
				y: 16,
				fill: '#000',
				"text-anchor": "middle"
			}).text('Гистограмма с накоплением');
			
			textLabel.exit().remove();
		}
		
		
		public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstanceEnumeration {
			let objectName = options.objectName;
			let objectEnumeration: VisualObjectInstance[] = [];
			
			switch (objectName) {
				case 'enableAxis':
					objectEnumeration.push({
						objectName: objectName,
						properties: {
							show: this.VisualSettings.enableAxis.show,
						},
						selector: null
					});
					break;
				case 'colorSelector':
					for (let labelDataLegend of this.labelDataLegends) {
						objectEnumeration.push({
							objectName: objectName,
							displayName: labelDataLegend.valueName,
							properties: {
								fill: {
									solid: {
										color: labelDataLegend.color
									}
								}
							},
							selector: labelDataLegend.selectionId.getSelector()
						});
					}
					break;
				case 'generalView':
					objectEnumeration.push({
						objectName: objectName,
						properties: {
							opacity: this.VisualSettings.generalView.opacity,
						},
						validValues: {
							opacity: {
								numberRange: {
									min: 10,
									max: 100
								}
							}
						},
						selector: null
					});
					break;
				
				case 'legend':
					objectEnumeration.push({
						objectName: objectName,
						displayName: 'show',
						properties: {
							show: this.VisualSettings.legend.show
						},
						selector: null
					});
					break;
			};

			return objectEnumeration;
		}

		public destroy(): void {
			// Perform any cleanup tasks here
		}

		private getTooltipData(value: any): VisualTooltipDataItem[] {
			return [{
				displayName: value.valueName,
				value: value.value.toString(),
				color: value.color
			}];
		}
    }
}

 

And i think i wrong using sorting. How i can to get index of element in "append"? like d => d.value, but i => i.index or something like this 

UPD .attr('y', function(d,i) {return i;})

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.