cancel
Showing results for 
Search instead for 
Did you mean: 
Reply
kranky
Frequent Visitor

R Custom Visual - Setting colours for each value in a field/column

I apologise if an answer to this has been posted previously - if it has I can't seem to locate it...

 

I'm writing an RHTML based custom visual (basically a bar plot that does grouping and stacking and overlaps bars). I've got the basics working so it displays the bars correctly (with fixed column colours, etc.), and now I'm just trying to finesse the properties to display things exactly as I want. I've managed to set up simple properties and property groups in the capabilities.json and settings.ts files that flow through to R from the Power BI properties pane by working through the funnel plot tutorial. So far so good. But I haven't been able to get a colour property to display yet - what I want to do is enable the user to pick a colour for each distinct value of a category field/column in the data and I just can't see how that works. Do I need to write some typescript to cover this in the visuals.ts, or is this something I can accomplish using just the two files mentioned above?

 

An excerpt from the capabilities.json:

 

  "dataRoles": [
    {
      "displayName": "Column Groups",
	  "description": "Grouping to be used on the x-axis of the column chart.",
      "kind": "Grouping",
      "name": "columnGroups"
    },
    {
      "displayName": "Column Overlay Groups",
	  "description": "Within each value of Column Groups, the columns to overlay on each other.",
      "kind": "Grouping",
      "name": "overlayGroups"
    },
    {
      "displayName": "Stacking Groups",
	  "description": "Optional stacking of columns within Column and Overlay Groups.",
      "kind": "Grouping",
      "name": "stackingGroups"
    },
    {
      "displayName": "Heights",
	  "description": "Heights of each column.",
      "kind": "GroupingOrMeasure",
      "name": "heights"
    }
	
  ],
  "dataViewMappings": [
    {
	  "conditions": [
	    {
			"columnGroups": {
				"max": 1
			},
			"overlayGroups": {
				"max": 1
			},
			"stackingGroups": {
				"max": 1
			},
			"heights": {
				"max": 1
			}
		}
	  ],
      "scriptResult": {
        "dataInput": {
          "table": {
            "rows": {
              "select": [
                {
                  "for": {
                    "in": "columnGroups"
                  }
                },
                {
                  "for": {
                    "in": "overlayGroups"
                  }
                },
                {
                  "for": {
                    "in": "stackingGroups"
                  }
                },
                {
                  "for": {
                    "in": "heights"
                  }
                }
              ],
              "dataReductionAlgorithm": {
                "top": {}
              }
            }
          }
        },
		"script": {
          "scriptProviderDefault": "R",
          "scriptOutputType": "html",
          "source": {
            "objectName": "rcv_script",
            "propertyName": "source"
          },
          "provider": {
            "objectName": "rcv_script",
            "propertyName": "provider"
          }
        }
      }
    } 
 ],

How do I define a colour parameter in the objects section of the capabilities.json file to show a colour picker for each distinct value of overlayGroups? And how would this look in the settings.ts file (which is admittedly very basic at the moment as I've only added a few simple numeric properties under one property group)? Do I need custom code in the visuals.ts file to enable this?

 

Thanks in advance,

 

Frank

9 REPLIES 9
dm-p
Super User I
Super User I

Hi @kranky,

The info is hard to find - the doc used to cover it pretty well but it's been squirreled-away in sample repos rather than being particulalry overt as to its theory.

I personally haven't done this for R visuals but I've just had a quick look at how they are created and it pretty much looks the same as for TypeScript.

The short verison is, you'll (likely) need to customise:

  • capabilities.json
  • visual.ts (specifically enumerateObjectInstances, and your code that maps your view model, or however you're managing mapping the dataView)
  • (possibly) settings.ts, depending on your requirements

It's probably easier if we can see the rest of your code, but we should be able to help get you there, particularly with respect to the R code you're adding in. Are you able to share a GitHub link or similar? Any sample data would also be useful, so that it's possible to replicate your setup and test before advising further. If you're not able to share publicly, feel free to PM details across.

Thanks!

Daniel

 





Did I answer your question? Mark my post as a solution!

Proud to be a Super User!


My course: Introduction to Developing Power BI Visuals


On how to ask a technical question, if you really want an answer (courtesy of SQLBI)




kranky
Frequent Visitor

Hi Daniel,

I don’t have a repository I can link to give you the code I’ve written so far (such as it is). I’m not overly familiar with TypeScript, so I’ll have to read up about that when I get a chance. The visual.ts hasn’t been modified from the structure given by the template, so enumerateObjectInstances looks like this:

    /**
     * This function gets called for each of the objects defined in the capabilities files and allows you to select which of the
     * objects and properties you want to expose to the users in the property pane.
     *
     */
    public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions):
        VisualObjectInstance[] | VisualObjectInstanceEnumerationObject {
        return VisualSettings.enumerateObjectInstances(this.settings || VisualSettings.getDefault(), options);
    }

 

As for the data, it’ll look something like this:

 

columnGroups	overlayGroups	heights
A	Actual	15
B	Actual	10
C	Actual	30
D	Actual	40
E	Actual	35
A	Target	40
B	Target	70
C	Target	35
D	Target	90
E	Target	15

The idea being that there’ll be 5 column groups (A – E), each with two columns (one for Actual and another for Target) with one column overlapping the other (by a variable amount). I’d like to be able to set colours for Actual and Target in this example. The plot itself is relatively easy to generate with R…

I’ll follow up with the full capabilities.json file, and settings.ts.

Thanks,

Frank

 

kranky
Frequent Visitor

capabilities.json:

{
  "dataRoles": [
    {
      "displayName": "Column Groups",
	  "description": "Grouping to be used on the x-axis of the column chart.",
      "kind": "Grouping",
      "name": "columnGroups"
    },
    {
      "displayName": "Column Overlay Groups",
	  "description": "Within each value of Column Groups, the columns to overlay on each other.",
      "kind": "Grouping",
      "name": "overlayGroups"
    },
    {
      "displayName": "Stacking Groups",
	  "description": "Optional stacking of columns within Column and Overlay Groups.",
      "kind": "Grouping",
      "name": "stackingGroups"
    },
    {
      "displayName": "Heights",
	  "description": "Heights of each column.",
      "kind": "GroupingOrMeasure",
      "name": "heights"
    }
	
  ],
  "dataViewMappings": [
    {
	  "conditions": [
	    {
			"columnGroups": {
				"max": 1
			},
			"overlayGroups": {
				"max": 1
			},
			"stackingGroups": {
				"max": 1
			},
			"heights": {
				"max": 1
			}
		}
	  ],
      "scriptResult": {
        "dataInput": {
          "table": {
            "rows": {
              "select": [
                {
                  "for": {
                    "in": "columnGroups"
                  }
                },
                {
                  "for": {
                    "in": "overlayGroups"
                  }
                },
                {
                  "for": {
                    "in": "stackingGroups"
                  }
                },
                {
                  "for": {
                    "in": "heights"
                  }
                }
              ],
              "dataReductionAlgorithm": {
                "top": {}
              }
            }
          }
        },
		"script": {
          "scriptProviderDefault": "R",
          "scriptOutputType": "html",
          "source": {
            "objectName": "rcv_script",
            "propertyName": "source"
          },
          "provider": {
            "objectName": "rcv_script",
            "propertyName": "provider"
          }
        }
      }
    } 
 ],
  "objects": {
    "rcv_script": {
      "properties": {
        "provider": {
          "type": {
            "text": true
          }
        },
        "source": {
          "type": {
            "scripting": {
              "source": true
            }
          }
        }
      }
    },
	"settings_overlay_params": {
      "displayName": "Settings",
      "description": "Column overlay settings",
      "properties": {
        "alphaMin": {
          "displayName": "Alpha Bottom",
		  "description": "Alpha setting for bottom-most column",
          "type": {
            "numeric":  true
              }
            },
		"overlay": {
          "displayName": "Degree of overlay",
		  "description": "How much should columns overlay 0.0 = no overlay, 1.0 = completely overlay",
          "type": {
            "numeric":  true
              }
            },
        "alphaMax": {
          "displayName": "Alpha Top",
		  "description": "Alpha setting for top-most column",
          "type": {
            "numeric":  true
              }
            }
          }
        },
	"settings_datacolour_params": {
      "displayName": "Data Colours",
      "description": "Base colours for columns",
      "properties": {
        "fill": {
          "displayName": "Column colours",
          "description": "Specify a colour for each value in the overlay group",
          "type": {
            "fill": {
              "solid": {
                "color": true
              }
            }
          }
        }
	  }
	}
  },
   "suppressDefaultTitle": true
}
kranky
Frequent Visitor

settings.ts:

"use strict";

import { dataViewObjectsParser } from "powerbi-visuals-utils-dataviewutils";
import DataViewObjectsParser = dataViewObjectsParser.DataViewObjectsParser;

export class VisualSettings extends DataViewObjectsParser {
      public settings_overlay_params: settings_overlay_params = new settings_overlay_params();
      public settings_datacolour_params: settings_datacolour_params = new settings_datacolour_params();
      }

    export class settings_overlay_params {
      public alphaMin: number = 0.4;   
      public alphaMax: number = 0.8;
	  public overlay: number = 0.2;
    }
	
    export class settings_datacolour_params {
      public columnColour: string = "orange";   
    }
kranky
Frequent Visitor

source('./r_files/flatten_HTML.r')

############### Library Declarations ###############
libraryRequireInstall("ggplot2");
libraryRequireInstall("plotly");
libraryRequireInstall("stringr");
libraryRequireInstall("reshape2");
libraryRequireInstall("ggplotify");
libraryRequireInstall("grid");
libraryRequireInstall("readxl");
####################################################

#Enable debugging in RStudio
fileRda = "C:/Users/biedermannf/Temp/tempData.Rda"
if(file.exists(dirname(fileRda)))
{
  if(Sys.getenv("RSTUDIO")!="")
    load(file= fileRda)
  else
    save(list = ls(all.names = TRUE), file=fileRda)
}

###############Internal function definitions#################
# Functions used only for this visual 
#############################################################


#############################################################
# Set up parameters/report variables
#############################################################

validToPlot <- TRUE

# Data
if(!exists("columnGroups")){
	columnGroups <- NULL
	validToPlot <- FALSE
}
if(!exists("overlayGroups") && validToPlot){
	# Set up a dummy variable with a single value
	overlayGroups <- data.frame(OG = factor(rep("Dummy Value", length(columnGroups[, 1])), levels = "Dummy Value"))
}
if(!exists("stackingGroups")){
	stackingGroups <- NULL
}
if(!exists("heights")){
	heights <- NULL
	validToPlot <- FALSE
}
#############################################################
# Column grouping parameters
#############################################################
# Default the column group labels to be slanted at 45 degrees
# and split into lines 30 chars wide.
x.axis.offset.labels <- FALSE
x.axis.labels.srt <- 45
x.axis.hjust <- 1
x.axis.maxwidth <- 30

# overlapping column parameters

# Min and max alpha - bottom column will have min alpha,
# graduating to max alpha for top column
alphaMin <- 0.2
if(exists("settings_overlay_params_alphaMin")){
	alphaMin <- min(1.0, max(0.0, settings_overlay_params_alphaMin))
}

alphaMax <- 0.8
if(exists("settings_overlay_params_alphaMax")){
	alphaMax <- min(1.0, max(0.0,settings_overlay_params_alphaMax))
}

overlay <- 0.8
if(exists("settings_overlay_params_overlay")){
	overlay <- min(1.0, max(0.0, settings_overlay_params_overlay))
}
#############################################################
# Stacking parameters
#############################################################


#############################################################

# Check inputs
if((!exists("columnGroups") ||!exists("heights"))) # invalid input 
{
  validToPlot <- FALSE
}

if(validToPlot){
	# Set up variables to hold the names of the supplied data columns (there'll only be one column in each incoming data frame)
	cGrpsName <- names(columnGroups)[[1]]
	oGrpsName <- names(overlayGroups)[[1]]
	heightsName <- names(heights)[[1]]

	# Set up column groups
    columnGroups[, cGrpsName] <- str_wrap(columnGroups[, cGrpsName], width = 30)
	colGrps <- factor(columnGroups, levels = unique(columnGroups), ordered = T)
	
	# Set up overlay groups
	oGrps <- NULL
	nGrps <- 1
	if(exists("overlayGroups")){
		oGrps <- levels(overlayGroups[, oGrpsName])
		nGrps <- length(oGrps)
	}
  columnWidth <- 0.9 / (nGrps - (nGrps - 1) * overlay)
}
	
if(validToPlot){
	# Base ggplot
	g <- ggplot(data=data.frame(columnGroups, overlayGroups, heights), aes(x = get(cGrpsName), y = get(heightsName), fill = get(oGrpsName)))

	# Loop through each overlay group (there should be at least a dummy group here) and add to plot
    for(i in 1:nGrps){
      g <- g + geom_col(data = data.frame(columnGroups, overlayGroups, heights)[overlayGroups[, oGrpsName] == oGrps[i], ], aes(x = get(cGrpsName), y = get(heightsName), fill = get(oGrpsName)), width = columnWidth, position = position_nudge(x = 0.9*((i-1)/(nGrps-1)-1/2) + columnWidth * (1/2-(i-1)/(nGrps-1))) )
 	}	 

	if(nGrps == 1){
		
		# Don't produce a legend as it'll just display "Dummy Value"
		# ToDo: this won't necessarily be the case if data coming only has one value in a supplied overlayGroups column - needs to be fixed
		
		g <- g + theme(legend = element_blank())

		# Set the colours of the columns
		g <- g + scale_fill_manual(values=alpha(c("springgreen4", "cornflowerblue"), c(alphaMin, alphaMax)), breaks = oGrps)
		
	} else {
		
		# Set the colours of the columns
		g <- g + scale_fill_manual(values=alpha(c("springgreen4", "cornflowerblue"), seq(alphaMin, alphaMax, length = nGrps)), breaks = oGrps)

		# Add the correct axis labelling
		g <- g + labs(x = cGrpsName, y = heightsName);

		# Rotate the x axis text labels (if required)
		g <- g + theme(axis.text.x = element_text(size = 6, angle = x.axis.labels.srt, hjust = x.axis.hjust, vjust = 1), legend.title = element_blank(), axis.title.x = element_blank());
	
	}
	
	#
	g <- g + theme(plot.margin = unit(c(0, 0, 0, 0), "native"))

	# plotly doesn't currently like mucking around with grobs - leave it commented out for now.
	# if (x.axis.offset.labels == TRUE){
	  
	  # # Adjust alternate tick lengths to be longer

	  # gg <- ggplotGrob(g)
	  
	  # xaxis <- gg$grobs[[which(gg$layout$name == "axis-b")]]
	  
	  # # Get the tick marks and tick mark labels   
	  # ticks <- xaxis$children[[2]]
	  
	  # # Get the tick marks
	  # marks = ticks$grobs[[1]]
	  
	  # # change the length in an alternating way
	  
	  # long <- unit.c(unit(1, "npc") - unit(18, "pt"), unit(1, "npc"))
	  # short <- unit.c(unit(1, "npc") - unit(4, "pt"), unit(1, "npc"))
	  
	  # # update the length
	  # marks$y = unit.c(rep(unit.c(short, long), 4), short)
	  
	  # # Put the tick marks back into the plot
	  # ticks$grobs[[1]] <- marks
	  # xaxis$children[[2]] <- ticks
	  # gg$grobs[[which(gg$layout$name == "axis-b")]] <- xaxis
	  
	  # g <- as.ggplot(gg)
	# }
	
} else {
	g <- ggplot() + theme(axis.line = element_blank()) + labs(title = "Invalid data - cannot produce plot")
}

p <- ggplotly(g);

####################################################

############# Create and save widget ###############
internalSaveWidget(p, 'out.html');
####################################################

Cheers, @kranky - I'll set up a project as per your supplied files and I'll report back as soon as I can with findings.

Daniel 🙂




Did I answer your question? Mark my post as a solution!

Proud to be a Super User!


My course: Introduction to Developing Power BI Visuals


On how to ask a technical question, if you really want an answer (courtesy of SQLBI)




dm-p
Super User I
Super User I

Hi @kranky,

I've set up a visual project with your files and sample data, and everything seems to be running okay.

The challenge now is having unpacked how the R visuals work vs. TypeScript, the approach used for these visuals doesn't seem to work in the same way as for R (to my knowledge).

Usually, you have full access to the dataRoles in the dataView when the visual renders, and you use this information to build your list for the properties pane, using enumerateObjectInstances to push them in. When using R, the whole result of the execution is provided as the scriptResult, e.g.:

image.png

The above dataView is from the developer visual for the R project. For TypeScript visuals, you'd get data in the metadata object and whichever dataViewMapping you were using, e.g. table.

I've decoded the payloadBase64 and this is essentially the JavaScript and HTML to render the visual, post-processing with R. What this looks like is we can add simple properties to the pane but any data-bound ones come with a separate challenge; there doesn't seem to be suitable hook in a similar place to the TypeScript workflow and I was hoping that there might be, so I apologise if I got your hopes up.

I'll yield the floor to someone else, but it might be better to get in touch with the custom visuals team directly to see if they can definitively comfirm your question - you can email them at pbicvsupport@microsoft.com

I'd be keen to hear if anyone else can solve this, or if the team have any advice on how it can be managed in the R visuals.

Regards,

Daniel





Did I answer your question? Mark my post as a solution!

Proud to be a Super User!


My course: Introduction to Developing Power BI Visuals


On how to ask a technical question, if you really want an answer (courtesy of SQLBI)




kranky
Frequent Visitor

Hi again Daniel,

 

Thanks very much for having a look at it - I appreciate the time you spent trying to get it working. It looks like I'm not going mad after all and can't set colours the way I wanted to be able to (unless the visuals team has some insight on how to do it). I may just end up doing something simple like passing in a colour theme based on some enumerated values (which is a bit too much like hard-coding for my taste).

 

I'll post any information I can gather here in case someone else runs into the same issue.

 

Thanks again.

 

Frank

kranky
Frequent Visitor

There doesn't appear to be a way to pass dynamic/data driven properties through to R according to the PBI visuals team. Their recommendation was to implement the visual in TypeScript.

Helpful resources

Announcements
PBI User Groups

Welcome to the User Group Public Preview

Check out new user group experience and if you are a leader please create your group

April Update

Check it Out!

Click here to read more about the April 2021 Updates!

secondImage

The largest Power BI virtual conference

100+ sessions, 100+ speakers, Product managers, MVPs, and experts. All about Power BI. Attend online or watch the recordings.

secondImage

Experience what’s next for Power BI

See the latest Power BI innovations, updates, and demos from the Microsoft Business Applications Launch Event.

Top Kudoed Authors