Quantcast
Channel: All Developer posts
Viewing all articles
Browse latest Browse all 51369

Re: Adding a Second Measure to Custom Visual

$
0
0

Great!

I've added all files to this gist for you to download the full code, as I'd like to focus on explaining the changes I made so that you can build on them accordingly.

capabilities.json - dataRoles

I've modified the dataRoles to the following:

 

"dataRoles": [ { "displayName": "Bar Grouping", "name": "myCategory", "kind": "Grouping" }, { "displayName": "Actual", "name": "actual", "kind": "Measure" }, { "displayName": "Budget", "name": "budget", "kind": "Measure" } ]

 

 This will modify the visual's data roles as follows:

image.png

Notethat your development visual will likely still have the now removed myMeasure role in there, so your best bet is to ensure everything is fully removed and re-applied when working with the new code I've provided.

capabilities.json - dataViewMappings

The dataViewMappings now look as follows:

 

"dataViewMappings": [ { "conditions": [ { "myCategory": { "max": 1 }, "actual": { "max": 1 }, "budget": { "max": 1 } } ], "categorical": { "categories": { "for": { "in": "myCategory" }, "dataReductionAlgorithm": { "top": {} } }, "values": { "select": [ { "bind": { "to": "actual" } }, { "bind": { "to": "budget" } } ] } } } ]

 

This will group by myCategory and then bind both measures to each. For my test data, the visual table looks as follows:

image.png

Note that this is a public dataset and I'm treating Tip as actual and Total Bill as budget.

Let's take a look at the dataViewMapping in the developer visual for the data I've added:

image.png

The values array now has two entries - one for actual and one for budget. We can see this if we expand the source object for each and then roles - and this is how you can determine which entry is performing which role. The measure values for each are then in the associated values array (which will match the order of myCategory). For brevity, I've only expanded this for actual.

visual.ts - BarchartDataPoint interface

We need to change the interface spec to ensure that the 'shape' of each data point is correct. This now looks as follows:

 

export interface BarchartDataPoint{ category: string; actual: number; budget: number; }

 

(it's not essential, but good practice to use camelCase for properties, so I have modified all to suit)

This now means that our view model expects two numeric values for each category, which matches our dataViewMappings, so we need to map them into the view model next.

visual.ts - createViewModel function

If you're making the above changes incrementally, you'll now see errors in your code, because the BarchartDataPoint interface 'shape' does not match the object you're assigning in the createViewModel function. We need to update this.

With the above in mind, and how the dataViewMapping looks, we need to change the way we iterate over it.

  • You were previously iterating over the measure values; I have modified this to iterate over the category values just to make the next bit easier.
  • I'm going to do the simplest possible solution for the measures using array accessors of [0] for actual and [1] for budget.
  • To make your code super-resilient, you would filter the values array by role to ensure the value is definitely provided, but we can worry about that waaaaay later 😉

The portion of the function code to map the view model now looks as follows:

 

categoryNames.map((c, ci) => { /** c= category, ci = category array index */ BarchartDataPoints.push({ category: <string>c, actual: <number>categoricalDataView.values[0].values[ci], budget: <number>categoricalDataView.values[1].values[ci] }); });

 

You have a sort lower down, so that's been modified to use the actual value:

 

if(SortBySize){ BarchartDataPoints.sort((x,y) =>{return y.actual - x.actual}) }

 

visual.ts - update function

It's now a case or binding everything from the view model correctly to your chart logic.

The first fix is your maxValueY assignment - we just get this to match the highest value now that you have two measures:

 

let maxValueY: number = d3.max( viewModel.DataPoints, (dataPoint:BarchartDataPoint) => /** Get the higher of either measure per group */ +Math.max(dataPoint.actual, dataPoint.budget) );

 

Now, the actual shape! barSelectionMerged gets updated to now use the actual property from the data point:

 

barSelectionMerged .attr("x", (dataPoint: BarchartDataPoint) => xScale(dataPoint.category)) .attr("y", (dataPoint: BarchartDataPoint) => yScale(Number(dataPoint.actual))) .attr("width", xScale.bandwidth()) .attr("height", (dataPoint: BarchartDataPoint) => (plotArea.height - yScale(Number(dataPoint.actual)))) .style("fill",(dataPoint:BarchartDataPoint) => viewModel.BarColor);

 

...and barSelectionMerged2 gets the budget property as part of its yScale:

 

barSelectionMerged2 .attr("x", (dataPoint: BarchartDataPoint) => xScale(dataPoint.category) + xScale.bandwidth() / 4) .attr("y", (dataPoint: BarchartDataPoint) => yScale(Number(dataPoint.budget))) .attr("width", xScale.bandwidth() / 2) .attr("height", (dataPoint: BarchartDataPoint) => (plotArea.height - yScale(Number(dataPoint.budget)))) .style("fill", (dataPoint: BarchartDataPoint) => 'yellow') .style("fill-opacity", (dataPoint: BarchartDataPoint) => 1);

 

Verifying

We can now test in the developer visual! Here's how it looks for my data above:

image.png

And hopefully, this is where you need to be 🙂

Good luck!

Daniel


If my post solves your challenge, then please consider accepting as a solution to help other forum members find the answer more quickly 🙂


Viewing all articles
Browse latest Browse all 51369

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>