Palettes

A palette is a special type of property that you can use to determine a color based on a tuple or a value.

Palettes come in two different types: categorical palettes and continuous palettes. A categorical palette is used when you want to assign a distinct color to each tuple in a slot. A continuous palette is used if you want to use a color to represent a numerical value.

Categorical palettes

A categorical palette lets you retrieve a color based on a tuple. You define a categorical palette for the visualization in the Visualization definition:

<properties>
    <palette name="colors" type="cat" slot="series" />
</properties>

When you define a palette property in your vizdef.xml file, you can access the palette like any other property:

protected update( _info: UpdateInfo ): void
{
    // Retrieve the palette and cast to CatPalette.
    const palette = _info.props.get( "colors" ) as CatPalette;

    // Get the series tuples from column 2 in the data set.
    const seriesTuples = _info.cols[ 2 ].tuples;

    // Retrieve a color for each tuple in the list.
    const colors = seriesTuples.map( _tuple => palette.getColor( _tuple ) );
}

A common example of using categorical palettes is when you want to set the color of an element that is bound to data. For example, the following code shows all tuples of the series slot in a list and assigns a different color to each element:

protected update( _info: UpdateInfo ): void
{
    // Retrieve the tuples from slot 0. If the data was cleared, then
    // use an empty list to indicate there are no tuples.
    const tuples = _info.data ? _info.data.cols[ 0 ].tuples : [];

    // Create a scale based on visualization height and tuple keys.
    const scale = d3.scaleBand()
        .range( [ 0, _info.node.clientHeight ] )
        .domain( tuples.map( _t => _t.key ) );

    const palette = _info.props.get( "color" ) as CatPalette;
    const svg = d3.select( _info.node );

    // For each tuple, create an svg text element and assign a color
    // from the palette.
    svg.selectAll( "text" ).data( tuples, ( t: any ) => t.key )
        .join( "text" )
        .attr( "y", t => scale( t.key ) )
        .attr( "fill", t => palette.getFillColor( t ) )
        .text( t => t.caption );
}

Notice the use of getFillColor in the code above. The difference between getColor and getFillColor is that the latter will look at the selected state of the tuple and adjust the color to make it lighter if the tuple should appear as deselected. Since you are binding actual tuples to our text elements, you get hit testing for free. This means that the user can click a tuple to select it and see the color of the other tuples change automatically.

Continuous palettes

A continuous palette can return a color based on a numerical value. You define a palette for a visualization in the Visualization definition:

<properties>
    <palette name="colors" type="cont" slot="values" />
</properties>

Retrieving colors from a continuous palette is a two-step process:

  1. Get the ColorStops of the palette by providing a slot instance. The slot instance should match the slot name that was specified in the vizdef.
  2. On the ColorStops instance, call getColor passing a numerical value.

Both the ContPalette and the ColorStops class have a getFillColor and getOutlineColor method that lets you retrieve a color based on a data point. This can be useful if your visualization needs to render elements that should reflect the selection or highlight state of the data. An example of a scatter chart visualization:

    protected create( _node: HTMLElement ): void
    {
        // Create an svg node that resizes to the visualization.
        d3.select( _node ).append( "svg" ).attr( "width", "100%" ).attr( "height", "100%" );
    }

    protected update( _info: UpdateInfo ): void
    {
        const data = _info.data;

        // Determine the size of the visualization
        const width = _info.node.clientWidth;
        const height = _info.node.clientHeight;

        // Determine the value domain for x and y.
        const domainX = data ? data.cols[ 1 ].domain.asArray() : [ 0, 0 ];
        const domainY = data ? data.cols[ 2 ].domain.asArray() : [ 0, 0 ];

        // Create linear scales for positioning the data points.
        const scaleX = d3.scaleLinear().range( [ 0, width ] ).domain( domainX );
        const scaleY = d3.scaleLinear().range( [ height, 0 ] ).domain( domainY );

        const palette = _info.props.get( "color" ) as ContPalette;

        // Render a circle with fixed radius for each data point.
        const svg = d3.select( _info.node ).select( "svg" );
        svg.selectAll( "circle" ).data( data ? data.rows : [] )
            .join( "circle" )
                .attr( "cx", d => scaleX( Number( d.value( 1 ) ) ) )
                .attr( "cy", d => scaleY( Number( d.value( 2 ) ) ) )
                .attr( "r", 12 )
                .attr( "stroke-width", 2 )
                // Stroke and fill color are based on selected and highlighted state.
                .attr( "stroke", d => palette.getOutlineColor( d ) )
                .attr( "fill", d => palette.getFillColor( d ) );
    }

For completeness of the example, this is the vizdef.xml file that was used:

<?xml version="1.0" encoding="UTF-8"?>
<visualizationDefinition version="3.1" xmlns="http://www.ibm.com/xmlns/prod/ba/vipr/vizBundle/vizdef.3">
    <slots>
        <slot name="categories" type="cat" optional="false" />
        <slot name="x" type="cont" optional="false" />
        <slot name="y" type="cont" optional="false" />
        <slot name="color" type="cont" optional="true" channel="color" />
    </slots>

    <dataSets>
        <dataSet name="data">
            <ref slot="categories" />
            <ref slot="x" />
            <ref slot="y" />
            <ref slot="color" />
        </dataSet>
    </dataSets>

    <properties>
        <group name="general">
            <palette name="color" type="cont" slot="color" />
        </group>
    </properties>

     <capabilities>
        <decorations>
            <decoration name="selected" type="boolean" target="datapoint, tuple" />
            <decoration name="hasSelection" type="boolean" target="dataset" />
            <decoration name="highlighted" type="boolean" target="datapoint, tuple" />
        </decorations>
    </capabilities>
</visualizationDefinition>

Palettes and slots

In the above examples, the palette definition in the vizdef.xml file was always linked to a slot. This allows the palette to determine the color of a data point and it allows RenderBase to generate a legend for that slot based on the palette.

In some situations, you don’t know in advance to which slot a palette are linked. For instance, if you have two categorical slots and the palette might be linked to one of the two slots. In that case, you can omit the slot attribute in the palette definition and override the RenderBase.getSlotForPalette method. This method takes a palette name and returns its corresponding slot name that you want the palette to be linked to. The method is called only for palettes that have no slot that is associated with them. An example:

protected getSlotForPalette( _data: DataSet, _palette: string ): string
{
    // Link the 'catColors' palette to the series slot if that slot is
    // mapped. Otherwise link it to the categories slot.
    if ( palette === "catColors" )
        return _data.cols[ SERIES ].mapped ? "series" : "categories";
    return null;
}