Wednesday Feb 26, 2014

Customizing the Axis Labels in ADF Graphs

The various data visualization (DVT) graphs provided as part of the ADF Faces component set provide a very rich and comprehensive set of visualizations for representing your data.  One of the issues with them, that some folks struggle with however, is the fact that not all the features are controlled in an a completely declarative manner. 

In this article I want to concentrate on labeling capabilities for a graph axis, looking first of all that the declarative approaches, but then following that up with the more advanced programmatic option. 

Managing Labels Declaratively 

Control over the labels on the axis tick points is a good example of making the simple things declarative and the advanced things possible.  For basic numeric formatting you can do everything with tags - for example formatting as currency, percentage or with a certain precision.    

This is a default (bar)graph plotting employee salary against name, notice how the Y1 Axis has defaulted to a fairly sensible representation of the salary data using 0-14K:


I can change that default scaling by setting the scaling attribute in the <dvt:y1TickLabel> tag. This allows scaling at the level of none | thousand | million | billion | trillion | quadrillion (enough to show national debt then!):

<dvt:y1TickLabel id="y1TickLabel1" scaling="none"/>

Changes the graph to:


We can then further change the pattern of the numbers themselves by embedding <af:convertNumber> inside of the <dvt:y1TickLabel> tag.

e.g.

<dvt:y1TickLabel id="y1TickLabel1" scaling="none">
  <af:convertNumber type="currency" currencyCode="USD"/>
</dvt:y1TickLabel> 

Adds currency formatting:

And using the <dvt:graphFont> we can change colors and style:

<dvt:y1TickLabel id="y1TickLabel1" scaling="none">
  <dvt:graphFont name="SansSerif" size="8" color="#FF0000" bold="true" italic="true" />
  <af:convertNumber type="currency" currencyCode="USD"/>
</dvt:y1TickLabel> 

Giving:


Need More Control?  Using the TickLabelCallback...

So we can achieve quite a lot by simply using the tags.  However, what about a more complex requirement such as replacing a numerical value on an axis with a totally different string e.g. converting to a Roman Numeral (I, IV XII etc.) or maybe converting a millisecond value to a formatted date string?  To do this, ADF provides a simple callback that you can implement to map a value to whatever string you need.  Here's a simple case where I've plotted the salaries in department 100 of the standard HR EMPLOYEES demo table against the HireDate on a scatter plot.  For the sake of illustration I've actually converted the HireDate to it's time value (e.g. a long value representing the number of milliseconds since 01/01/1970) .  In a real graph I'd use the proper support that we have for representing time and date and just map a date object. Then you get to use the timeSelector and can directly set the formatting, however, bear with me because I'm just illustrating the point here.

Here's the default output with the millisecond version of the date, as you can see the millisecond value gets automatically scaled to the billions level.

To override the default representation of the millisecond value we will need to create a java class that implements the oracle.dss.graph.TickLabelCallback interface.  Here's the simple example I'll use in this case:

import java.text.SimpleDateFormat;
import oracle.dss.graph.DataTickLabelInfo;
import oracle.dss.graph.GraphConstants;
import oracle.dss.graph.TickLabelCallback;
import oracle.dss.graph.TickLabelInfo;

public class MSToDateFormatLabelCallback implements TickLabelCallback, Serializable {

  @Override
  public String getTickLabel(TickLabelInfo tickLabelInfo, int axisID) {
    String label = null;
    if (axisID == GraphConstants.X1TICKLABEL) {
      long timeInMillis = (long) ((DataTickLabelInfo) tickLabelInfo).getValue();
      SimpleDateFormat fmt = new SimpleDateFormat("MM/yy");
      label = fmt.format(timeInMillis);
    } else {
      label = "" + ((DataTickLabelInfo) tickLabelInfo).getValue();
    }
    return label;
  }
} 

As you can see the formatting  is applied only to the specified axis and we have to upcast the tickLabelInfo argument to a DataTickLabelInfo in order to gain access to the value that is being applied. Note that the callback class must also be Serializable. 

Once you have this class, then you need to apply it to the chart instance by calling the relevant set*TickLabelCallback. So for example I might set this in the backing bean for a page in the setter used by the binding attribute on the dvt graph component

public class GraphPageHandler {
    private UIGraph scatterPlot;
    public void setScatterPlot(UIGraph scatterPlot) {
        this.scatterPlot = scatterPlot;
        scatterPlot.setX1TickLabelCallback(new MSToDateFormatLabelCallback());
    } 

Now with the callback in place and the addition of:

  1. Using  the axisMinValue attribute along with axisMinAutoScaled="false" on the <dvt:x1Axis>  to reset the start date for the plot to the year 2000 rather than the default 1970
  2. Some of the currency formatting that we've already looked at to format the Y axis

Here's the result:

Tuesday Feb 25, 2014

Stretching an inputText inside of a Table Column

A quick trick to note if you have a table that includes a column containing an <af:inputText>. If the user is able to re-size the columns within the table, then it's not unreasonable to have the inputText stretch along with the column to take advantage of the full width available. 

The first thing to do is to set the simple property of the inputText to "true".  This almost does the trick, but you'll find that as you stretch the column there is a small trailing gap between the end of the input field and the column which grows as the column is stretched. So although the input is stretched as well it does not fill the cell which looks a little messy.

The second trick then is to set the contentStyle property on the inputText to "min-width:100%;" Do this and you will eliminate that gap and the input will now fill the column cell completely as you resize. 

Monday Feb 24, 2014

Programmatic UI Scrolling in ADF Faces

I've been dealing with a use case recently where we needed to synchronize two panels in a user interface, where (in the simple version) selecting an object on one side of the screen would also select a corresponding object in a scrolling area on the other side of the screen and scroll in into view.  the scrolling panel in this case being a panelGroupLayout / scroll, not a table.

Declarative  scrolling is really simple, you can drop a UI element such as commandButton onto the page and then nest, within that, the behavior tag <af:scrollComponentIntoViewBehavior>. This tag is great if you have a long page and want to provide the users with a way to navigate to different sections, however, it needs to be wired to a command component and the user needs to physically invoke the operation.

In this case, everything needed to happen in reaction to a series of complex programmatic events and so, the use of the behavior tag did not cut it.  So I did a small amount of testing to see if it was possible to achieve the same result in a hands off fashion from within java code.

It turns out to be pretty simple to do, but there are different approaches depending on your version:

Programatic Scrolling in 11.1.2.n and 12c

In the newer versions of ADF this issue is addressed head on with a new API on the AdfFacesContext -> scrollComponentIntoView(component, focus);  This API takes a UIComponent reference for the component to scroll to, and the second argument is a boolean flag to indicate if the component should take focus as well (for example if it was an input field).  

Programatic Scrolling in 11.1.1.n

 Prior to ADF Faces 11.1.2 the handy  scrollComponentIntoView() api does not exist, however, we can acheive the same effect using some JavaScript. (Note if you are on a version of ADF that does support the scrollComponentIntoView() Java API then use that, not this.)

As with the behavior tag, you need to do two things.

  1. The target component has to have clientComponent="true" as this scrolling operation all takes place on the client
  2. You need the full id of the target component.  This should include any naming containers such as templates and regions.  The safest way to do this is to call getClientId() on the java component itself. 

 Then you can just invoke this little function.  Note that the code does not have any error handling if you get the component ID wrong so feel free to improve it to make it more robust:

import org.apache.myfaces.trinidad.render.ExtendedRenderKitService;
import org.apache.myfaces.trinidad.util.Service; 

private void scrollIntoView(String componentId){
  StringBuilder builder = new StringBuilder();
  builder.append("var c = AdfPage.PAGE.findComponent('");
  builder.append(componentId);
  builder.append("'); c.scrollIntoView(true,null);");
        
  FacesContext fctx = FacesContext.getCurrentInstance();
  ExtendedRenderKitService erks =
    Service.getRenderKitService(fctx, ExtendedRenderKitService.class);
  erks.addScript(fctx, builder.toString());        
} 

If you use this function an it appears not to work you may be hitting a timing issue where the framework is, in turn, resetting the focus after you have set it.  If that seems to be happening in your case, then the solution is to re-write the above JavaScript slightly to execute the scrollIntoView() after a short delay.  You can use a JavaScript  setTimeout() function on the window object to do this.  Delaying the scroll call for about 10ms should be sufficient.

Thursday Feb 20, 2014

JAX-RS 2.0 MessageBodyReader not found error

From time to time we all make silly mistakes which lead to much head scratching and time being wasted. This is a short missive about just such a mistake that I made yesterday which then had me totally stumped (for a a few minutes at least!) this morning.

So, here's the story.  I was taking a long flight back to the UK from California yesterday and had wanted to keep working on a client UI for a RESTful service provided by our build infrastructure.  As you can imagine, it's not going to work very well to access those services in flight, so I spent the wait time in the lounge building out a quick mock implementation of the data structure using the same POJOs so I could switch to that and continue with the UI development offline. As part of this I thoughtlessly added a new constructor into my main POJO that the REST service JSON structure mapped in to.  All was well.

This morning I'm back online, so I switch the implementation of my UI data provider back to the real service to see what the UI now looks like with more data - it fails? Huh? OK back to the test client - it also fails:

Exception in thread "main" 
  org.glassfish.jersey.message.internal.MessageBodyProviderNotFoundException: 
  MessageBodyReader not found for media type=application/json, 
  type=class com.groundside.model.Orchestration, 
  genericType=class com.groundside.model.Orchestration.
 at org.glassfish.jersey.message.internal.ReaderInterceptorExecutor$TerminalReaderInterceptor.aroundReadFrom(ReaderInterceptorExecutor.java:207)
 at org.glassfish.jersey.message.internal.ReaderInterceptorExecutor.proceed(ReaderInterceptorExecutor.java:139)
 at org.glassfish.jersey.message.internal.MessageBodyFactory.readFrom(MessageBodyFactory.java:1109)
 at org.glassfish.jersey.message.internal.InboundMessageContext.readEntity(InboundMessageContext.java:853)
 at org.glassfish.jersey.message.internal.InboundMessageContext.readEntity(InboundMessageContext.java:785)
 at org.glassfish.jersey.client.ClientResponse.readEntity(ClientResponse.java:267)
 at org.glassfish.jersey.client.InboundJaxrsResponse$1.call(InboundJaxrsResponse.java:111)
 at org.glassfish.jersey.internal.Errors.process(Errors.java:315)
 at org.glassfish.jersey.internal.Errors.process(Errors.java:297)
 at org.glassfish.jersey.internal.Errors.process(Errors.java:228)
 at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:397)
 at org.glassfish.jersey.client.InboundJaxrsResponse.readEntity(InboundJaxrsResponse.java:108) 

So now I spin round and make all the checks I can think of.  The libraries are all unchanged, perhaps the shape of the service has been changed overnight? - nope, every thing is the same.  The mime type coming from the service is still application/json as well. 

So I'm pretty confused. Back to first principles though. Has really nothing changed? Well in fact yes there was a change - just one little, tiny thing, that new constructor.  Of course that was it.  One of the requirements for the JAX-RS unmarshalling is that the entity class should have an empty constructor.  Before it was implicit, but now I'd added that new constructor for the mocking which included arguments and that replaced the implicit empty constructor and broke the above requirement to boot.  So it was simple to fix in the end just annoying. Hey ho...

About

Hawaii, Yes! Duncan has been around Oracle technology way too long but occasionally has interesting things to say. He works in the Development Tools Division at Oracle, but you guessed that right? In his spare time he contributes to the Hudson CI Server Project at Eclipse
Follow DuncanMills on Twitter

Note that comments on this blog are moderated so (1) There may be a delay before it gets published (2) I reserve the right to ignore silly questions and comment spam is not tolerated - it gets deleted so don't even bother, we all have better things to do with our lives.
However, don't be put off, I want to hear what you have to say!

Search

Archives
« February 2014 »
MonTueWedThuFriSatSun
     
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
21
22
23
27
28
  
       
Today