5.3.0 • Published 2 months ago

@visa/visa-charts-utils v5.3.0

Weekly downloads
-
License
SEE LICENCE IN LI...
Repository
github
Last release
2 months ago

Utils

Visa Charts Utils (utils) are utility components and functions which are imported and leveraged by all Visa Chart Components (VCC). Many utils are leveraged by VCC to help each chart with specific core functionalities, and can be used alongside VCC or independently (in some cases).

API Contents

# Installation Steps:

To use utils in your projects run yarn add @visa/visa-charts-utils. The utils export contains nearly 100 different functions across more than 2 dozen files. You can import only the utils you need into a specific file using a destructuring pattern, like so:

import Utils from '@visa/visa-charts-utils';
const { calculateLuminance, calculateRelativeLuminance } = Utils;

const greyLuminance = calculateLuminance('#767676');
const whiteLuminance = calculateLuminance('white');
const wcagContrastRatio = calculateRelativeLuminance(greyLuminance, whiteLuminance);

Overview of the utils

Each main util file is outlined below. Some of these utils are designed with environmental requirements (such as assuming an element uses d3's __data__ DOM property), while others are made with a purely functional pattern and require no special setup to use right away. Purely functional exports are marked as [ functional ].

# Accessibility Controller <>

The accessibility controller is an invisible, minimal HTML interface that sits on top of each chart area that greatly increases the accessibility of each chart. The controller sends keyboard input into the chart space to produce focus indication as well as simulate the chart's interactive features. Using HTML elements inside the controller interface allows for more robust semantic options for screen reader users, such as describing chart elements as images or buttons (when appropriate), which is currently not available to SVG elements. The controller is also in charge of how keyboard navigation patterns are handled, generally designed to mimic the information structure produced by the chart space.

The controller is a unique and powerful utility that comes with every Visa Chart Component.

Many of the functions in this file interact closely with the accessibility prop that each Visa Chart Component ships with.

An image depicting a chart and a series of symbols that resemble controls, such as left, right, select, undo, etc.

Notable Exports:

# setAccessibilityController({...}):

This function initializes and maintains the root-level controller node and is designed to hook into the VCC lifecycle (so that it stays up to date as a chart changes).

# setElementFocusHandler({...}):

This function adds a focus listener to each chart element (excluding group-level elements) that will move the user's keyboard focus into the chart's accessibility controller if activated. Normally, chart elements cannot be focused by keyboard interaction, so this generally will only move focus state if a user clicks a chart element (since clicking triggers a focus event as well). This allows users to click a chart element and then begin keyboard navigating from that point. Note that the initial focus indicator is only shown once keyboard navigation input is supplied after clicking.

# setElementInteractionAccessState(node: any, selected: boolean, selectable: boolean):

This function ensures that if a chart is interactive and the developer has specified accessibility.elementsAreInterface: true that the element's corresponding controller node is rendered as a button, instead of a div with role=img.

# setElementAccessID({ node: any; uniqueID?: string }):

This function manages the unique ids of each chart element, which is how the controller interacts with the focus indicator and keyboard navigation within the chart space.

# checkAccessFocus(parentGNode: any):

This function checks whether a focus indicator source exists. This is useful when used with retainAccessFocus during a chart's lifecycle (as focused elements may become deleted if the data changes).

# retainAccessFocus({parentGNode: any; focusDidExist?: boolean; recursive?: boolean;}):

This function ensures that keyboard focus which previously existed before a chart's state change is maintained. This also maintains the visual state of the focus indicator, if it exists.

Notable Internal Functions:

# getInteractionResult({...}):

This is the main engine that handles keyboard interaction and navigation on both child and group elements. It is used exclusively by the controller nodes. This function handles the different navigation styles of offset (EG alluvial-diagram), recursive (EG circle-packing), grouped (EG line-chart, stacked-bar-chart, etc), and non-grouped chart types (EG bar-chart, pie-chart, etc). It also handles the special cases where a user can navigate among cousin chart elements within the same semantic grouping (such as bar-chart when groupAccessor is passed or in world-map).

This function resolves with either:

  • A selection (if SPACEBAR is pressed) specifying the id of the selected element.
  • A new focus target, based on the given key pressed by the user.
  • No result, if the key pressed does not resolve in either a selection or a new focus target.

# drawKeyboardFocusClone({...}):

This important function handles the cloning, placement, and display of the keyboard focus indicator. All of these features may change slightly depending on the chart and element type that is being focused.

# Accessibility Descriptions

This util handles the descriptive area that precedes a Visa Chart Component, which helps to explain the various features of the chart to a screen reader user. Most of the functions in this utility are a direct plug into a chart's lifecycle and based on developer declarations when using the accessibility prop that each Visa Chart Component ships with. For more in-depth descriptions of each of these description sections, see their corresponding props in any component README file under the Accessibility Props section.

Preceding each chart we provide instructions to screen reader users on how to navigate the experience. This leverages our keyboard instructions menu (if the chart is interactive). See our keyboard-instructions component for more information about this menu and our keyboard instructions.

// An example of accessibility descriptions provided by a developer. The exports in this util will apply these descriptions to the chart.

const accessibility = {
  longDescription: 'An alluvial diagram which shows the movement of users to different groups between 2018 and 2019.',
  executiveSummary: 'Group C is now the largest group in 2019.',
  contextExplanation: 'This chart is standalone, and can be manipulated by the preceding buttons.',
  purpose: 'This chart highlights that most users are now in the group C',
  structureNotes:
    'The groups are sorted from high to low, with the group C at the bottom. Links are used to visualize the population of the group moving between different groups year over year.',
  statisticalNotes: 'Group C is visibly larger than all of the other groups combined in 2019'
};

Notable Exports:

# initializeDescriptionRoot({...}):

This function initializes and maintains the description root as well as keyboard interaction instruction sections of the description area. This function will redraw its contents if a Visa Chart Component receives a new highestHeadingLevel prop.

# setAccessTitle(rootEle: any, title: string):

This function runs to initialize any value passed to a Visa Chart Component's accessibility.title or mainTitle props as well as any updates made to either of those values. This information is added as a heading element within the description root. The lifecycle of this is managed internally by each individual Visa Chart Component. See the accessibility prop, the accessibility prop used within a component, or the mainTitle prop used within a component for more details.

# setAccessSubtitle(rootEle: any, subtitle: string):

This function runs to initialize any value passed to a Visa Chart Component's subtitle prop as well as any updates made to that value. This information is added as a heading element within the description root. The lifecycle of this is managed internally by each individual Visa Chart Component. See the accessibility prop or the accessibility prop used within a component for more details.

# setAccessLongDescription(rootEle: any, description: string):

This function runs to initialize any value passed to a Visa Chart Component's accessibility.longDescription prop as well as any updates made to that value. This information is added as a text element within the description root. The lifecycle of this is managed internally by each individual Visa Chart Component. See the accessibility prop or the accessibility prop used within a component for more details.

# setAccessContext(rootEle: any, context: string):

This function runs to initialize any value passed to a Visa Chart Component's accessibility.contextExplanation prop as well as any updates made to that value. This information is added as a text element within the description root. The lifecycle of this is managed internally by each individual Visa Chart Component. See the accessibility prop or the accessibility prop used within a component for more details.

# setAccessExecutiveSummary(rootEle: any, summary: string):

This function runs to initialize any value passed to a Visa Chart Component's accessibility.executiveSummary prop as well as any updates made to that value. This information is added as a text element within the description root. The lifecycle of this is managed internally by each individual Visa Chart Component. See the accessibility prop or the accessibility prop used within a component for more details.

# setAccessPurpose(rootEle: any, purpose: string):

This function runs to initialize any value passed to a Visa Chart Component's accessibility.purpose prop as well as any updates made to that value. This information is added as a text element within the description root. The lifecycle of this is managed internally by each individual Visa Chart Component. See the accessibility prop or the accessibility prop used within a component for more details.

# setAccessStatistics(rootEle: any, statistics: string):

This function runs to initialize any value passed to a Visa Chart Component's accessibility.statisticalNotes prop as well as any updates made to that value. This information is added as a text element within the description root. The lifecycle of this is managed internally by each individual Visa Chart Component. See the accessibility prop or the accessibility prop used within a component for more details.

# setAccessChartCounts({...}):

This function will programmatically produce a summary count of the elements rendered on a Visa Chart Component, such as This is a bar-chart with 12 bars. This information is added as a text element within the description root. This function automatically handles the difference between recursive and non-recursive rendering styles (when deep layers may be hidden from rendering intentionally, thus reducing chart counts).

# setAccessXAxis({...}):

This function will programmatically produce summary information about what is displayed on a Visa Chart Component's x axis. This information is added as a text element within the description root. This function relies heavily on values supplied to the xAxis prop, for example hiding the axis using xAxis.visible will remove this summary information. See also: an example of the xAxis prop used on a chart.

# setAccessYAxis({...}):

This function will programmatically produce summary information about what is displayed on a Visa Chart Component's y axis. This information is added as a text element within the description root. This function relies heavily on values supplied to the yAxis prop, for example hiding the axis using yAxis.visible will remove this summary information. See also: an example of the yAxis prop used on a chart.

# setAccessStructure(rootEle: any, structure: string):

This function runs to initialize any value passed to a Visa Chart Component's accessibility.structureNotes prop as well as any updates made to that value. This information is added as a text element within the description root. The lifecycle of this is managed internally by each individual Visa Chart Component. See the accessibility prop or the accessibility prop used within a component for more details.

# setAccessAnnotation(rootEle: any, annotations: any, referenceLines: any):

This function runs to initialize any value passed to a Visa Chart Component's annotations as well as referenceLines and any updates made to these arrays. The lifecycle of this is managed internally by each individual Visa Chart Component. See an example of the annotations prop's accessibilityDescription used within a component for more details. You can also find more information in the documentation for the annotation util in this README file.

# setAccessibilityDescriptionWidth(uniqueID, width):

This function ensures that the width of the description root either matches the width of the chart, or has a minimum width of 200px. It is important to maintain a minimum width because the description root's primary node is visible when a keyboard only user navigates to it (it must be easy to read).

This function initializes when the width prop passed to a Visa Chart Component and will update if its value changes. This is handled within the lifecycle of each chart component.

# findTagLevel(startLevel: any, depthFromStart?: number):

This function will produce a hierarchy of HTML elements based on the highestHeadingLevel prop sent in to any Visa Chart Component. This is used to calculate the component's own (visible) title and subtitle as well as the contents of the description root used by screen readers and keyboard only users. This is used by each Visa Chart Component as well as internally by the initializeDescriptionRoot function.

# Accessibility Utilities <>

Many of the functions in this file interact closely with the accessibility prop that each Visa Chart Component ships with.

A bar chart example that has detected Window's High Contrast dark mode and inverted the contrast values of all of its elements.

Notable Exports:

# initializeElementAccess(node: any):

This initialization adds attributes to a Visa Chart Component chart element to ensure that it can be recognized by the accessibility controller and description utilities but will not be accessible via screen reader or keyboard navigation. Chart elements (SVG or otherwise) are not intended to be directly accessible by these technologies, as they lack the semantic expressiveness of the HTML-based controller.

# hideNonessentialGroups(rootNode: any, exception: any):

This function hides group elements and their children from a screen reader, keyboard navigation, and the accessibility controller and description utilities (using the hideNode utility function). An exception group may be passed, which is intended for the root group element that contains the main chart geometries. This node is typically ignored so that its contents may be initialized using initializeElementAccess.

# setTooltipAccess(el: any):

This function hides the tooltip from screen reader access.

# setLegendAccess(root: any, id: string):

This function hides the legend from screen reader and keyboard navigation access (using the hideNode utility function) and adds a high contrast listener (similar to the setHighContrastListener utility function).

# hideNode(node: any, excludeFocusable?: boolean):

This is a commonly used function which simply ensures that a given element will not be accessible via screen reader or keyboard navigation, will not be counted by any programmatic accessibility description utilities (such as setAccessChartCounts), and will not be recognized by the accessibility controller.

# createUrl(id: string) [ functional ]:

This function expects a string formatted as a valid HTML id and will return a string that can be used to reference the element with that ID in style or attribute declarations. This function handles an array of strange browser and environment quirks when producing valid urls for style/attributes.

Example use:

const textureID = 'custom-texture-fill';
const textureUrl = createUrl(textureID);
// simplest form returned is "url(#custom-texture-fill)"
select(element).attr('fill', textureUrl);

# setHighContrastListener(root: any, id: string):

This function builds a luminance-only inversion filter and then applies that filter if the chart is rendered in IE11 or Microsoft Edge and a High Contrast dark scheme is applied at the system level.

To accomplish a luminance-only inversion, the filter will first invert contrast (for example, turning a dark red into a light green) and then the filter will rotate the hue of the space 180 degrees (so that the light green is now a light red). This retains the original hue (red) but changes it from dark to light. By applying this filter to the root of an entire SVG chart space or legend, it can approximate matching Microsoft's High Contrast dark schemes (except that the color palette is not limited).

This function will also add a JavaScript-driven media query listener which detects Microsoft's -ms-high-contrast setting. Since this is a listener, it can detect changes made live (without reload). But note that this listener is experimental and -ms-high-contrast is considered Non-Standard. Real high contrast accessibility should include a limited color palette as well, which developers are expected to apply directly to charts in tandem with this automatic feature.

# Alt Text Generator <>

This utility handles creating textual descriptions of a Visa Chart Component's elements and groups, based on their data values and contents. This utility is leveraged by the accessibility controller and some of the features can be controlled via props sent to a chart's accessibility prop, such as accessibility.includeDataKeyNames and accessibility.elementDescriptionAccessor. See the accessibility prop type for more details.

Example showing a mac's voiceover screen reader announcing the alt text on a bar chart element as a user navigates.

Notable Exports:

# createLabel({...}):

This function produces an automatic textual description of a Visa Chart Component geometry element based on input data used to create that element. This function handles producing labels for all chart geometries in VCC library, such as bars, line points, pie slices, etc as well as handling special data structures that produce grouped, nested, and recursive elements.

An example circle in a circle-packing chart:

'D. 16. Canada. Node 1 of 2. This Node contains 0 child elements';

By default, Visa Chart Components have terse labels, but these can be expanded using accessibility.includeDataKeyNames. An example of the previous label with this prop set:

'Type D. Value 16. Country Canada. Node 1 of 2. This Node contains 0 child elements';

In addition, labels can be customized to include almost any additional information if a data object has a property specified by accessibility.elementDescriptionAccessor. An example of the previous label with this prop set:

'Type D. Value 16. Country Canada. Note This product Type is also in the United States, data is redundant. Node 1 of 2. This Node contains 0 child elements';

# createGroupLabel({...}):

Like createLabel, this function creates an automatic textual description of a Visa Chart Component element, except for group-level data instead of child-level geometry elements. Typically group labels just contain summary information about what groupAccessor or seriesAccessor was used to create the group, what number in a series the group is, and how many children the group contains.

An example line in a line chart:

'Product X. Line 1 of 2 which contains 12 interactive points.';

This utility can also be used to contain summary information about a group like in a stacked bar chart:

'2017. 83. Stack 2 of 5 which contains 3 interactive bars.';

By default, Visa Chart Components have terse labels, but these can be expanded using accessibility.includeDataKeyNames. An example of the previous label with this prop set:

'year 2017. Sum 83. Stack 2 of 5 which contains 3 interactive bars.';

# Annotations <>

This is a wrapper on d3-svg-annotation by Susie Lu that allows developers to specify data values (and use simple arithmetic operations) in addition to exact pixel or numeric dimensions (as is standard to d3-svg-annotation already).

An image depicting an example annotation on a bar-chart component

Notable Exports:

# annotate({...}):

Adds annotations to a chart, based on props sent to the chart. Developers can specify annotations using the documentation provided by d3-svg-annotation as well as using data values.

Example annotation prop sent to a chart:

<bar-chart
  {...props}
  annotations = [
    {
      // see d3-svg-annotation for the main api available from that library
      "note": {
        "label": "Social Media Intern returned to college",
        "bgPadding": 20,
        "title": "Staff Change",
        "align": "middle",
        "wrap": 130
      },

      /*
        devs must explain where they are on a chart for users who cannot see
        this text is exposed to screen reader users in addition to note text
      */
      "accessibilityDescription": "There has been a drop in tweet activity due to staff change in Q3.",

      /*
        OR, devs must identify the annotation as decorative, this can be useful when leveraging
        annotations for additional lines, labels, etc. which are already described elsewhere in
        the chart. If your annotation describes something on the chart, this should not be used.
      */
      "accessibilityDecorationOnly": true,

      /*
        devs can take advantage of VCCs label collision (via vega-label) for annotations
        currently we only support hiding annotations if they collide, this behavior
        can be enabled by passing collisionHideOnly with a value of true
      */
      "collisionHideOnly": true,

      /*
        data objects can be passed in directly and will use the chart's scale
        if any other props are passed for x/y, those will override this object
      */
      "data": {
        "label": "Q3",
        "value": 2125
      },

      // if a value is wrapped in array brackets, it will use the chart's scale
      "y": [2600],

      // parseAsDates is an add-on function provided by VCC that allows you to place
      // annotation elements on a date scale, specify the axis ("x" or "y") or accessor key when defining
      // "parseAsDates":["date"] // accepts "x" or "xAccessor" which is "date" in this example

      // we can also pass an array of two values in order to calculate the diff between them
      // this is an example of calculating the difference of dates (assuming parseAsDates is sent)
      // "x": ["2016-02-01, 2016-01-01],

      // percentages may also be passed, in string format
      "x": "62%",

      // when no bracket or % is used, numbers result in actual pixel space
      "dy": -85,

      // these additional placement options are available via the resolveValue function in the annotations.ts util
      // they are available and can be used on the following annotation attributes
      // annotation.x/y
      // annotation.dx/dy
      // annotation.subject.x1/x2/y1/y2/width/height
      // annotation.connector.points
      // here is an example of passing to connector points (assuming parseAsDates["x"] is also sent)
      // "connector": { ...
        // "points": [
              // [["2016-02-01","2016-01-01"],[98765,4321]],
              // [["2016-03-01","2016-01-01"],[98765,4321]],
              // ...
        // ]
      //}

      // types relate to those provided out of the box by d3-svg-annotation
      "type": "annotationCallout",
      "connector": {
        "end": "dot",
        "endScale": 3
      },

      // visa-charts colors can be passed in and will be used
      "color": "oss_dark_grey"
  ]
></bar-chart>

Example use by a Visa Chart Component:

annotate({
  source: this.svg,
  data: this.annotations,
  xScale: this.x,
  xAccessor: this.ordinalAccessor,
  yScale: this.y,
  yAccessor: this.valueAccessor
});

# Axes <>

This util adds axes to Visa Chart Components, customizable by props sent to each chart. Inspired heavily by d3.js's axis, with modifications to suit VCC design system and lifecycle. The primary export function drawAxis({...}) is expected to work with a Visa Chart Component. See a chart component's documentation for how to use the x and y axis props.

# Browser and OS Detection <>

This util can be used to query which browser or operating system is being used. This utility is able to detect different versions of Chrome, Edge, Internet Explorer, Opera and UCBrowser.

Notable Exports:

# getBrowser():

This function returns a string representing the browser being used.

Example use:

const browser = getBrowser(); // 'IE', 'Edge', 'Safari', etc

# Calculating Stats <>

This util is leveraged by the scatter-plot Visa Chart Component to calculate its least squares coefficient for use in a fit line.

Notable Exports:

# leastSquares(xSeries: number[], ySeries: number[]) [ functional ]:

This function expects two arrays of numbers and will return an array containing the slope, intercept, and r-squared values based on the data.

An image depicting an example scatterplot component with a fitted trend line.

Example use:

const calculation = leastSquares([4, 5, 6, 7, 8], [1, 2, 3, 4, 5]); // [1, -3, 1]

# Collision Detection (leveraging vega-label) <>

This util is leveraged by VCC components to implement a VCC specific version of vega-label's occupancy bitmap approach for collision detection of chart labels.

Notable Exports:

# resolveLabelCollision({...}):

This function expects to receive d3 selections of marks, labels, potential places which labels can be moved to, and even existing bitmaps (for incremental checking of additional labels, e.g., series labels on line-chart). Under the hood it leverages vega-label's bitmap occupancy algorithm and has been extended/adapted for use in VCC. Collision related props (e.g., collisionPlacement) have been made available via VCC components, but are not on by default. NOTE: running the collision function appears to be adding ~20ms to render times in most cases, sometimes upwards of 30ms. Please keep this in mind when using collision related props in VCC.

An image depicting an example of an occupancy bitmap applied to the pie chart component in order to detect label collisions.

An image depicting an example of an occupancy bitmap applied to the line chart component in order to detect label collisions.

Example use:

// direct use in chart, note this function is also called in our dataLabel utility
this.bitmaps = resolveLabelCollision({
  bitmaps: this.bitmaps, // bitmaps previously created in component lifecycle, containing marks
  labelSelection: seriesUpdate, // d3 selections of labels to place and determine collisions for
  avoidMarks: [this.updateDots], // marks to write to bitmap and use for bounds to orient labels
  validPositions: ['middle', 'top', 'bottom'], // anchor positions to try and place label in
  offsets: [1, 1, 1], // how much to offset each position in pixels
  accessors: [this.seriesAccessor], // the VCC accessors used to join labels to marks
  size: [roundTo(this.width, 0), roundTo(this.innerPaddedHeight, 0)], // size of graph
  hideOnly: this.labelDetails.visible && this.labelDetails.collisionHideOnly // toggle of whether to place or just hide labels if collision is detected
});
// returns an occupancy bitmap, populated with marks and labels already drawn to the graph
// output of the function is to set the following attributes of the labels passed in:
// 1. data-label-hidden - attribute identifying if algorithm hide the element
// 2. visibility - style attribute used to hide element
// 3. x/y - position updated when placing labels based on validPositions
// 4. text-anchor - position updated when placing labels based on validPositions

// use of function for annotations
bitmaps = resolveLabelCollision({
  bitmaps: bitmaps, // existing bitmap is passed in with all chart marks already on it
  labelSelection: annotationsG.selectAll('.annotation-detect-collision text'), // we applied a specific class in the annotation util and leverage that here for text element selections
  avoidMarks: [], // no avoid marks needed for annotations
  validPositions: ['middle'], // we only check annotations based on their specified placement
  offsets: [1],
  accessors: ['cidx'], // not used currently
  size: [roundTo(width, 0), roundTo(height, 0)] // we need the whole width for annotations
  // hideOnly is set on a per annotation basis and thus is not used in this call to the function
});
// for annotations, we check if the any of the text is hidden, if so, we then hide the entire annotation via style.visibility

# Colors <>

This util contains a wide array of color-related functions, some for convenient styling operations, others for leveraging VCC's color schemes, and others for ensuring accessibility programmatically.

Notable Exports:

# autoTextColor(backgroundColor: string) [ functional ]:

This function will find the most appropriate text color (foreground) given a background color. This function is designed with accessibility compliance in mind, using the WCAG 2 contrast ratio formula to find the highest contrast text possible. Any valid HTML string for a color may be sent: 'red', '#ffffff', 'rgb(12, 200, 15), etc. Alpha values will be ignored. A hex color will be returned, either '#ffffff' or '#222222'.

An image depicting an example bar-chart component with different color text on each bar, to contrast against the different bar colors.

Example use:

const textColorOnBlue = autoTextColor('blue'); // '#ffffff'
const textColorOnSoftWhite = autoTextColor('rgb(215, 215, 215)'); // '#222222'

# calculateLuminance(color: string) [ functional ]:

This function will calculate the perceived luminance value of a valid HTML color. Formula and specification from WCAG 2.0. Any valid HTML string for a color may be sent: 'red', '#ffffff', 'rgb(12, 200, 15), etc. Alpha values will be ignored. A number will be returned, between 0 (black, no luminance) and 1 (white, full luminance).

Example use:

const whiteLuminance = calculateLuminance('#ffffff'); // 1
const blackLuminance = calculateLuminance('black'); // 0

# calculateRelativeLuminance(luminance1: number, luminance2: number) [ functional ]:

This function will calculate the contrast ratio between two luminance values according to accessibility standards. Luminance values must be a number between 0 and 1 (inclusive). Input luminance values may be supplied in any order, the output will always place the highest value as the numerator. Formula and specification from WCAG 2.0. A number will be returned between 1 (the colors have identical luminance) and 21 (the colors are complete opposites, literally black and white).

Note that WCAG 2.1 AA contrast ratio requirements for non-text and large text contrast must be at least 3:1 and regular text must be at least 4.5:1.

Example use:

const contrastRatioBW = calculateRelativeLuminance(whiteLuminance, blackLuminance); // 21
const contrastRatioWW = calculateRelativeLuminance(whiteLuminance, whiteLuminance); // 1
const contrastRatioBB = calculateRelativeLuminance(blackLuminance, blackLuminance); // 1

# convertVisaColor(colorArr: string[]) [ functional ]:

This function expects an array of strings. It will assume the array contains only Visa Chart Component color codes if at least the first entry has an underscore in the string. For performance, no further checking or validation is done. Mixed arrays of color strings and VCC color codes may produce unwanted results. This operation is done in-place on the input array and will either return the array untouched or it will attempt to convert VCC color codes into hex codes.

Example use:

const validScheme = convertVisaColor(['blue', 'green']); // ['blue', 'green']
const validVCCScheme = convertVisaColor(['oss_light_grey', 'oss_dark_grey']); // ['#D7D7D7', '#363636']
const invalidScheme = convertVisaColor(['oss_light_grey', 'green']); // ['#D7D7D7', undefined]

# ensureTextContrast(textColor: string) [ functional ]:

This function takes a text color as an argument and will attempt to darken that color (while maintaining saturation and hue) until it passes WCAG 2.1 AA contrast standards (4.5:1). If it already passes, it will not be darkened. This function assumes the text is being used on a white background.

Note that any valid HTML string for a color may be sent: 'red', '#ffffff', 'rgb(12, 200, 15), etc. Alpha values will be ignored.

An image depicting an example scatter plot component with different colored points and darker text matching a highlighted group of points.

Example use:

const geometryColor = '#8CD6C2';
const textColorToMatch = ensureTextContrast(geometryColor); // '#2e816b'

# getAccessibleStrokes(fillColor: string) [ functional ]:

This function will find up to two strokes for a given input color for use as a border on a filled element or as a line (in the case of a line chart). Note that any valid HTML string for a color may be sent: 'red', '#ffffff', 'rgb(12, 200, 15), etc. Alpha values will be ignored.

The output is an array of hex codes. The first value is computed by getContrastingStroke and will always have a 3:1 contrast against the original color. If the original color is dark enough against white by itself, it is added to the array as a second color (this is done to programmatically signal whether the returned contrasting stroke should or should not be used, given the context).

To demonstrate: For Visa Chart Components with textures, the interior fill will always use the first color for the texture's stroke. If the fill is dark enough, no stroke will be shown on the exterior unless the chart is interacted with.

An image depicting an example bar chart component with dark grey fill and light grey line pattern on it.

Example use:

const greyFill = '#767676';
const strokes = getAccessibleStrokes(greyFill); // ['#d2d2d2','#767676']

# getContrastingStroke(fillColor: string) [ functional ]:

This function receives a color string and will return a color string that is at least 3:1 contrast against the original color. This function can help geometries pass WCAG 2.1 non-text contrast ratio requirements even when light colors are used as input to a component. Note that any valid HTML string for a color may be sent: 'red', '#ffffff', 'rgb(12, 200, 15), etc. Alpha values will be ignored.

An image depicting an example scatter-plot component nearly white marks that have dark blue outlines.

Example use:

const nearlyWhiteFill = '#FEFEFF';
const highContrastStroke = getContrastingStroke(nearlyWhiteFill); // '#8484ff'

# visaColorToHex(color: string) [ functional ]:

This function will take a string as input and return either a hex code based on a Visa Chart Components color code. If no VCC color code is passed, the original string is returned instead.

Example use:

const ossToGreyHexCode = visaColorToHex('oss_dark_grey'); // '#363636'
const greyHexCode = visaColorToHex('#363636'); // '#363636'

# Data Labels <>

This util is used to place and format data labels all across Visa Chart Components. Each chart has its own set of placement rules (if any). See a chart component's documentation for how to use the dataLabel prop.

# Data Transformation <>

This util is used to perform various data preparation and transformation operations, primarily to prepare each Visa Chart Component's data table props. One function is unused by VCC but is useful for developers wishing to create valid nested data.

Notable Exports:

# fixNestedSparseness(data: any, ordinalAccessor: string, groupAccessor: string, valueAccessor: string, defaultValue?: number):

For charts that nest their data (Stacked Bar, Clustered Bar, etc), the data must not be sparse. This utility is an in-place operation on an array of objects that will add any missing datum with corresponding ordinal and group accessor values. Value accessor values will populate as either 0 (by default) or a provided default numeric value. Developers are expected to handle their own data preparation, this utility is provided as a convenience (and an example of one way this operation can be done).

As with any borrowed data algorithm, developers should assess this function to determine if it suits their needs before using.

Example use:

// this dataset has 3 categories and 2 groups, but only 3 datum (should have 6)
const badData = [{ cat: 'a', group: 'x', val: 5 }, { cat: 'b', group: 'x', val: 5 }, { cat: 'c', group: 'y', val: 5 }];
fixNestedSparseness(this.badData, 'cat', 'group', 'val', 1);
/*
badData is no longer sparse and can be properly nested:
[
  {cat: "a", group: "x", val: 5},
  {cat: "b", group: "x", val: 5},
  {cat: "c", group: "y", val: 5},
  {val: 1, cat: "c", group: "x"},
  {val: 1, cat: "a", group: "y"},
  {val: 1, cat: "b", group: "y"}
]
*/

# Formatting Dates <>

This util is for formatting dates used in VCC data labels, tooltips, axes, and in accompanying data tables. For guidance on how to specify time formatting, see d3-time-format (which this util leverages).

# Formatting Numbers <>

This util is for formatting any non-date number used in VCC data labels, tooltips, axes, and in accompanying data tables. For guidance on how to specify number formatting, see Numeral.js's formatting (which this util leverages).

Notable Exports:

# roundTo(value: number, decimal: number) [ functional ]:

Rounding is an unbelievably complex issue in any language that uses IEEE's standard for floating point numbers (virtually 99% of all languages, JavaScript/TypeScript included). This util allows a developer to specify which decimal point they would like to round to and has very few faults in how it implements rounding. This function uses Number.EPSILON as well as descaling (according to the desired decimal place).

Example use:

Math.round(1.005 * 1000) / 1000; // Returns 1 instead of expected 1.01!
roundTo(1.005, 2); // 1.01

Number(parseFloat('1.555').toFixed(2)); // Returns 1.55 instead of 1.56.
roundTo(1.555, 2); // 1.56

# formatStats(value: number, format: string) [ functional ]:

This function takes in a number and the string representation of a format and returns a string representation of the number formatted by numeral.js. This utility is used for formatting of labels throughout VCC, but can also be used outside of VCC, for example, a custom number formatting Angular pipe.

Example use:

// see built in formats available at http://numeraljs.com/#format
const exampleNumber = 2343289.4798;

formatStats(exampleNumber, '0.0a'); // returns '2.3m'
formatStats(exampleNumber, '$0,0.00'); // '$2,343,289.48'
formatStats(0.082343289798, '0.00%'); // returns '8.23%'
formatStats(exampleNumber, '$0[.][0][0][a]'); // returns '$2.3m' or locale specific currency symbol
formatStats(100.325, '0[.][00][a]'); // returns '100.33'

// you can also use a custom format added with registerNumeralFormat() util
formatStats(100.325, '0[.][00][a] USD'); // returns '100.33 USD'
// example of how to use this function within an angular pipe
import { Pipe, PipeTransform } from '@angular/core';
import Utils from '@visa/visa-charts-utils';
const { formatStats } = Utils;

@Pipe({
  name: 'formatStats'
})
export class FormatStatsPipe implements PipeTransform {
  transform(input: any, format?: string): any {
    if (!format) {
      format = '0[.][0][0]a';
    }

    if (Number.isNaN(input)) {
      return null;
    }

    return formatStats(input, format);
  }
}

# getNumeralInstance():

This function returns the instance of numeral loaded by the formatStats util.

Example use:

const numeral = getNumeralInstance();

// checking registered numeral locales and formats
console.log('registered: locales', numeral.locales, ', formats: ', numeral.formats);

# registerNumeralLocale( name: string, locale: object{}, overwrite: boolean) [ functional ]:

This function calls numeral.register internally to enable the addition of locales to the number formatting provided via numeral.js. See the docs about locales for more information.

Example use:

// see http://numeraljs.com/#locales and
// https://github.com/adamwdraper/Numeral-js/tree/master/src/locales
// for more examples
registerNumeralLocale('ja', {
  delimiters: {
    thousands: ',',
    decimal: '.'
  },
  abbreviations: {
    thousand: '千',
    million: '百万',
    billion: '十億',
    trillion: '兆'
  },
  ordinal: function(number) {
    return '.';
  },
  currency: {
    symbol: '¥'
  }
});

# setNumeralLocale(name: string) [ functional ]:

This function calls numeral.locale internally to set the current locale rules to be applied within the number formatting provided via numeral.js. See the docs about locales for more information.

Example use:

// see http://numeraljs.com/#locales
// assuming locale 'ja' has been loaded and registered in our instance of numeral
setNumeralLocale('ja');
formatStats(2343289.4798, '$0[.][0][0][a]'); // returns '¥2.3百万' based on the 'ja' locale config

setNumeralLocale('en');
formatStats(2343289.4798, '$0[.][0][0][a]'); // returns '$2.3m' based on the 'en' locale config

# registerNumeralFormat( name: string, format: object{}) [ functional ]:

This function calls numeral.register internally to enable the addition of formats to the number formatting provided via numeral.js. See the docs about custom formats for more information.

Example use:

// see http://numeraljs.com/#custom-formats and
// https://github.com/adamwdraper/Numeral-js/tree/master/src/formats
// for more examples
registerNumeralFormat('full-currency-code', {
  regexps: {
    format: /([A-Z]{3})$/,
    unformat: /([A-Z]{3})$/
  },
  format: function(value, format, roundingFunction) {
    var currencyCode = format.substring(format.length - 3);
    var space = numeral._.includes(format, ` ${currencyCode}`) ? ' ' : '';
    var output;

    // check for space before currency code
    var regexCode = new RegExp(`/( ${currencyCode})$/`, 'g');
    format = format.replace(regexCode, '');

    output = numeral._.numberToFormat(value, format, roundingFunction);

    if (numeral._.includes(output, ')')) {
      output = output.split('');

      output.splice(-1, 0, space + currencyCode);

      output = output.join('');
    } else {
      output = output + space + currencyCode;
    }

    return output;
  },
  unformat: function(string) {
    return numeral._.stringToNumber(string) * 0.01;
  }
});

# Interactivity <>

This util handles verifying and executing all interaction state in Visa Chart Components: Checking hover status with checkHovered() when a chart receives hoverHighlight prop, click/select status with checkClicked() when a chart receives clickHighlight prop, or both at once using checkInteraction(). It also handles building custom SVG stroke filters using buildStrokes() (which could be useful to emulate, such as when a developer wants to build a custom legend to accompany a chart).

An image depicting an example heat map component being focused by a keyboard and showing a tooltip.

# Legends <>

This util handles building and maintaining lifecycle for legends (of all shapes and sizes) used in Visa Chart Components. Legend types range from gradients, to categorical blocks, lines, symbols, and diverging/sequential scales represented as range bands and ordinal blocks. This util is meant to be consumed by Visa Chart Components exclusively but could be leveraged by someone clever enough in their own environment.

An example legend with 6 different line types shown.

# License Bundle <>

This simple util is in place to ensure that required open source licenses are bundled with our code, including our own. It is maintained by our team each time a new dependency is added to one of our components and attaches itself (an object containing license information) to the window ensuring all licenses associated with our packages can be found at window.VisaChartsLibOSSLicenses anywhere Visa Chart Components is being used. Dev dependencies are not included as they are not distributed via our minified builds.

// an example of the license information maintained in this utility
const licenses = [
  {
    dependency: 'visa-chart-components',
    github_link: 'https://github.com/visa/visa-chart-components',
    license: {
      type: 'MIT',
      update_date: '11/15/2020',
      link: 'https://github.com/visa/visa-chart-components/blob/master/LICENSE.md',
      text: `
        Copyright (c) Visa, Inc.

        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:

        The above copyright notice and this permission notice shall be included in
        all copies or substantial portions of the Software.

        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
        THE SOFTWARE.
      `
    }
  },
  // {...}, {...}]

# Localization <>

This util handles building and maintaining lifecycle for localization (languages and numerical locales) used in Visa Chart Components. In VCC you can use English and Hungarian out of the box, and you can also create and use your own language to translate charts to your desired language. There are two approaches you can use to enable localization within VCC, (1) chart based and (2) globally, each approach is described in more details below.

Notes:

  1. VCC handles languages externally and internally. Chart details that can be overwritten in an external app during usage (like the title of a chart), are expected to be internationalized by the developer in the app that is using VCC. Internal language expressions, like keyboard instructions of a chart, are handled internally.
  2. The fallback language and numerical locale for VCC is en (US English) and US (US numerical expressions, abbreviations and currency).

Chart-based Initialization Process

A single chart can be used with a language and numerical locale by passing the given language and numerical locale directly to the chart via the localization prop object. Internally VCC will leverage the localization.overwrite property to either ignore an object passed that has already been provided, or overwrite the existing configurations. Take caution with this approach if you are re-rendering and updating charts frequently as it could cause some unwanted behavior.

// language import
import { hu } from './your-language-file-here';
// numerical locale import
import { HU } from './your-numeral-locale-file-here';
<bar-chart
  localization={{
    language: hu, // string|object
    numeralLocale: HU, // string|object
    overwrite: false // boolean; if true the latest passed language and numeralLocale object will be used
    skipValidation: false // boolean; if true, disables validations for this chart
  }}
  ...

Global Initialization Process

In addition to the above, you can also register your localization config before you render a chart and apply this config across one or many charts using the localization property. In this scenario you will use some of VCC's utility functions in combination with string based prop values being s