I'm trying to work on this visual for Power BI but I am coming across a weird issue. This is supposed to be a bar chart (will convert to line) that supports a measure on both X and Y axis. When I try to plot with my data, I get an empty visual with no errors. My formatting options that I added (like tooltips or toggle options) don't even appear in the visual formatting options pane. I have messed around and I can not get this to work or really change at regardless of what I do, short of going in and throwing random characters into my code to break the syntax. I have even tried going to older visual files that I have tinkered with in the past and made sure that I'm not doing anything wrong, such as misusing modules, or placing them improperly. I even re-imported all of my modules, checked my variables, checked for typos, etc. I cannot seem to get this thing to work. I even tried removing the part in my capabilities that allows measures to be put into the axis to see if it would plot with normal value columns. To no avail, unfortunately. Maybe I have stagnated eyes and I have been missing something obvious, or it's something more complicated than that.
I would greatly appreciate appreciate any help. Even if it's something unrelated to the issue.
"use strict";
import "@babel/polyfill";
import "./../style/visual.less";
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 EnumerateVisualObjectInstancesOptions = powerbi.EnumerateVisualObjectInstancesOptions;
import VisualObjectInstance = powerbi.VisualObjectInstance;
import DataView = powerbi.DataView;
import VisualObjectInstanceEnumerationObject = powerbi.VisualObjectInstanceEnumerationObject;
import * as d3 from "d3";
import { VisualSettings } from "./settings";
import ISelectionManager = powerbi.extensibility.ISelectionManager;
import { ChartDataPoint, ChartViewModel } from "./viewmodels/model";
import IVisualHost = powerbi.extensibility.visual.IVisualHost;
import * as DataViewObject from 'powerbi-visuals-utils-dataviewutils';
interface DataPoints {
duration: number;
value: number;
details: number;
wells: string;
colour: string;
identity: powerbi.visuals.ISelectionId;
highlighted: boolean;
};
interface ViewModel {
dataPoints: DataPoints[];
maxValue: number;
highlights: boolean;
};
export class Visual implements IVisual {
private host: IVisualHost;
private svg: d3.Selection<SVGElement>;
private barGroup: d3.Selection<SVGElement>;
private viewModel: ViewModel;
private locale: string;
private selectionManager: ISelectionManager;
private xAxisGroup: d3.Selection<SVGElement>;
private yAxisGroup: d3.Selection<SVGElement>;
private settings = {
axis: {
x: {
padding: {
default: 50,
value: 50
},
show: {
default: true,
value: true
}
},
y: {
padding: {
default: 50,
value: 50
}
},
border: {
top: {
default: 10,
value: 10
}
}
}
}
constructor(options: VisualConstructorOptions) {
this.host = options.host;
this.svg = d3.select(options.element)
.append(".svg")
.classed("Visual", true);
this.barGroup = this.svg.append("g")
.classed("bar-group", true); //this was chart
this.xAxisGroup = this.svg.append("g")
.classed("x-axis", true);
this.selectionManager = this.host.createSelectionManager();
this.yAxisGroup = this.svg.append("g")
.classed("y-axis", true);
}
//This contains the 'canvas', its scaling, and how it can or cannot interact
public update(options: VisualUpdateOptions) {
//this.updateSettings(options);
let viewModel = this.getViewModel(options);
let width = options.viewport.width;
let height = options.viewport.height;
let xAxisPadding = this.settings.axis.x.show.value ? this.settings.axis.x.padding.value : 0;
// let yAxisPadding = this.settings.axis.y.show.value ? this.settings.axis.y.padding.value : 0;
this.svg.attr({
width: width,
height: height
});
let yScale = d3.scale.linear()
.domain([0, this.viewModel.maxValue])
.range([height - xAxisPadding, 0 + this.settings.axis.border.top.value]);
let xScale = d3.scale.linear()
.domain(viewModel.dataPoints.map(d => d.duration))
// .rangeRoundBands([yAxisPadding, width], this.xPadding);
let xAxis = d3.svg.axis() //come back to this later if it causes issues. https://www.youtube.com/watch?v=zLNfXxDsa-s&list=PL6z9i4iVbl8C2mtjFlH3ECb3q00eFDLAG&index=14 3:40 in
.scale(xScale)
.orient("bottom")
.tickSize(.5);
let yAxis = d3.svg.axis()
.scale(yScale)
.orient("left")
.tickSize(.5);
this.xAxisGroup
.call(xAxis)
.attr({
transform: "translate(0 " + (height - xAxisPadding) + ")"
})
.style({
fill: "#777777"
})
.selectAll("text")
.attr({
"text-anchor": "end",
"font-size": "x-small"
});
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 bars = this.barGroup
.selectAll(".bar") //keep an eye on this. was '.lines'
.data(viewModel.dataPoints);
bars.enter()
.append("svg")
.classed("bar", true); //this was chart
bars
.attr({
//width: xScale.range(),
height: d => height = yScale(d.value) - xAxisPadding,
x: d => xScale(d.duration),
})
.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": ids.length > 0 ?
d => ids.indexOf(d.identity) >= 0 ? 1.0 : 0.5
: 1.0
});
})
});
bars.exit()
.remove();
}
/* private updateSettings(options: VisualUpdateOptions) {
this.settings.axis.x.show.value = DataViewObjects.getValue
(options.dataViews[0].metadata.objects, {
objectName: "xAxis",
propertyName: "show"
})
}*/
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({
duration: <number>values.values[i],
value: <number>values.values[i],
details: <number>categories.values[i],
wells: <string>categories.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;
}
public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstance[] | VisualObjectInstanceEnumerationObject {
let propertyGroupName = options.objectName;
let properties: VisualObjectInstance[] = [];
switch (propertyGroupName) {
case "xAxisGroup":
properties.push({
objectName: propertyGroupName,
properties: {
show: this.settings.axis.x.show.value
},
selector: null
});
break;
};
return properties
}
}
For anyone curious, my end goal (eventually) is to have a line plot that can support a measure (calculated column) in either axis. To give an example, dates are very common for the X axis. I would also like to be able to use a measure that can take a date range such as 9/5/2019-9/10/2019 and convert that to a duration. Output expected is 1-5.
The Y axis measure may do something like.. display all values that meet certain parameters, for instance.
{
"supportsHighlight": true,
"dataRoles": [
{
"displayName": "Y Axis",
"name": "values",
"kind": "Grouping"
},
{
"displayName": "Details",
"name": "details",
"kind": "Grouping"
},
{
"displayName": "Duration",
"name": "duration",
"kind": "Measure"
}
],
"objects": {
"xAxis": {
"displayName": "X Axis",
"properties": {
"show": {
"displayName": "Show X Axis",
"type": {
"bool": true
}
}
}
},
"dataPoint": {
"displayName": "Data colors",
"properties": {
"defaultColor": {
"displayName": "Default color",
"type": {
"fill": {
"solid": {
"color": true
}
}
}
},
"showAllDataPoints": {
"displayName": "Show all",
"type": {
"bool": true
}
},
"fill": {
"displayName": "Fill",
"type": {
"fill": {
"solid": {
"color": true
}
}
}
},
"fillRule": {
"displayName": "Color saturation",
"type": {
"fill": {}
}
},
"fontSize": {
"displayName": "Text Size",
"type": {
"formatting": {
"fontSize": true
}
}
}
}
}
},
"dataViewMappings": [
{
"categorical": {
"categories": {
"for": {
"in": "duration"
},
"dataReductionAlgorithm": {
"top": {
"count": 450
}
}
},
"values": {
"group": {
"by": "values",
"select": [
{
"bind": {
"to": "details"
}
}
]
},
"dataReductionAlgorithm": {
"top": {
"count": 450
}
}
}
}
}
]
}