A React component to visualize multiple kinds of charts according to the data returned by a olap-client query.
We suggest the use of the pnpm package manager, but npm and yarn can be used without a problem.
npm i @datawheel/vizbuilderThis component only exports a React component that makes the charts based on one or multiple queries. The internal code does all the possible remixes that make sense based on the query results it is passed, and the additional parameters it is given. These parameters mainly restrict the types of charts generated and add parameters to the d3plus config for each chart, and are usually dependent to the datasource it is being used for this visualization.
To use it, import the named component from the package:
import {Vizbuilder} from "@datawheel/vizbuilder";This package doesn't include a stylesheet. It's up to your implementation to setup the UI of the component. The DOM nodes have classnames under the vb- namespace. It's suggested to take advantage of CSS grid or CSS flexbox to structure the layout and order of the elements.
- Type: 
QueryResult | QueryResult[] - Required
 
A single query result, or an array of these, to generate the charts. Each QueryResult object should contain the needed info about the cube where the information is coming from, query used to get the data, and the data aggregation itself. Check the QueryResult interface to know details of the object structure.
- Type: 
ChartType[] - Optional, default value: 
["barchart", "barchartyear", "donut", "geomap", "histogram", "lineplot", "pie", "stacked", "treemap"] 
An array of the names of the kinds of charts the instance can generate.
- Type: 
string - Optional, default value: 
undefined 
A classname for CSS styling.
- Type: 
number - Optional, default value: 
20000 
The maximum number of data points the instance can use to generate a chart, reduce it if the chart rendering frozes the browser.
- Type: 
string - Optional, default value: 
"en" 
If you implement another (or many) locale, this property sets the one shown initially.
- Type: 
Record<string, D3plusConfig> | ((measure: OlapClient.Measure) => D3plusConfig) - Optional, default value: 
{} 
Useful to set a specific d3plus config to use depending on the measure used in the query. The config parameters resulting from using this property have priority over all other config params determined by internal heuristic.
This property accepts a function or an object. If a function is passed, it will be called with the relevant olap-client Measure instance object, and must return an object with the properties wanted to be merged to the chart config. If an object is passed, the value whose key matches the measure.name will be merged to the chart config.
- Type: 
boolean - Optional, default value: 
false 
Toggles showing confidence intervals / margins of error when available in the query.
- Type: 
React.ReactNode - Optional, default value: 
undefined 
Allows the user to add a toolbar inside the area of the component. In the DOM tree, it is placed before the charts, but the layout can be modified with CSS.
- Type: 
Record<string, D3plusConfig> | ((level: OlapClient.Level) => D3plusConfig) - Optional, default value: 
{} 
Useful to set a specific d3plus config to use depending on the level used in the query.
Can be passed a function or an object. If a function is passed, it will be called with the relevant olap-client Level instance object, which will be a geographic-type of level; it must return an object with the properties wanted to be assigned to the chart config. If instead an object is passed, the object value whose key matches with either level.uniqueName, level.fullName, or level.name, in that order, will be assigned to the chart config.
- Type: 
Record<string, Translation> - Optional, default value: 
{} 
An object with localization labels to use within the component. The keys are locale codes and the values are objects that comply with the Translation interface. This is an example of the structure:
const translations = {
  "en": {
    action_apply: "Apply",
    action_back: "Back",
    action_close: "Close",
    ...
  },
  "es": {
    action_apply: "Aplicar",
    action_back: "Volver",
    action_close: "Cerrar",
    ...
  }
}An example of the message keys is available in this file. These are also the default labels used if this parameter is not set, or if the defaultLocale parameter points to a locale not available.
- Type: 
D3plusConfig - Optional, default value: 
undefined 
A general use d3plus config object, which will be applied to all charts.
The package has some typings set in TypeScript to help development. All these are also exported by the package. It is encouraged to use them to verify the structures are complying with the required properties.
A string from the following list:
type ChartType =
  | "barchart"
  | "barchartyear"
  | "donut"
  | "geomap"
  | "histogram"
  | "lineplot"
  | "pie"
  | "stacked"
  | "treemap";An object with information about the cube where the info belongs to, the parameters used to execute the query, and the resulting dataset. This component is designed to work around the data structures defined on the olap-client package, so these properties can be easily obtained after executing a query with it. All properties are required.
The PlainCube interface comes from olap-client, and can be obtained from a Cube instance calling the cube.toJSON() method. The dataset is the tidy data array returned by a jsonrecords query. The QueryParams interface is described next, and can be constructed from a olap-client Query instance.
interface QueryResult {
  cube: OlapClient.PlainCube;
  dataset: any[];
  params: QueryParams;
}The QueryParams interface describes the parameters used in the query, using raw objects, with the name of the property as identifier:
interface QueryParams {
  locale: string;
  booleans: {
    [name: string]: boolean;
  };
  cuts: Array<{
    dimension?: string;
    hierarchy?: string;
    level: string;
    members: string[];
  }>;
  drilldowns: Array<{
    dimension?: string;
    hierarchy?: string;
    level: string;
    properties?: string[];
    caption?: string;
  }>;
  filters: Array<{
    comparison: string;
    formatter?: (value: number) => string;
    measure: string;
    value: string;
  }>;
  measures: Array<{
    collection?: string;
    formatter?: (value: number) => string;
    lci?: string;
    measure: string;
    moe?: string;
    source?: string;
    uci?: string;
  }>;
}Some remarks:
- All the higher level properties (
booleans,cuts,drilldowns,filters, andmeasures) must be present, but can be empty arrays. - In the descriptor objects, those with a question mark (
?) at the end of the property name are optional, but encouraged to avoid collisions. - The 
levelname used in the descriptor object can be theuniqueName, thefullName, or thenameof the level; these three are matched in that order. - The 
membersproperty in thecutsdescriptor items is an array of memberkey, for the members defined in the cut on thatlevel. - The 
propertiesproperty in thedrilldownsdescriptor items is an array of propertyname, belonging to theleveldefined along it. Likewise, thecaptionproperty is just a propertyname. - The 
formatterproperty, if defined, should be a function that receives anumbervalue, and outputs astring. This formatting function will be used wherever the values for themeasuredefined along it is shown. - For each of the 
measuresdescriptor items, if amoeis defined,lcianduciwon't be considered. 
Notice the QueryParams object can also contain Formatter functions, so it can't be serialized and rehydrated from a JSON string. This is important if you plan to store the object in a Redux store, for example, as it can result in unexpected behavior when using some features.
For ease of development, this package also exports a helper function buildQueryParams as a named export, to quickly convert an olap-client Query object into a QueryParams object. Check the definition below for details on how to use it.
An object whose keys are message keys, and its values the localized string to show in the interface. Here is an example of a Translation object and how it should be used.
const translation: Translation = {
  /* These are actions shown in buttons in the UI */
  action_close: "Close",
  action_download: "Download {{format}}",
  action_enlarge: "Enlarge",
  action_fileissue: "File an issue",
  action_retry: "Retry",
  aggregators: {
    avg: "Average",
    max: "Max",
    min: "Min"
  },
  /* These labels are shown in the charts tooltip */
  chart_labels: {
    ci: "Confidence Interval",
    moe: "Margin of Error",
    source: "Source",
    collection: "Collection"
  },
  /* These labels are shown in the suggested error message when filing a new issue */
  error: {
    detail: "",
    message: "Error details: \"{{message}}\".",
    title: "Title: "
  },
  /* Message for the default NonIdealState when no charts are valid for queries */
  nonidealstate_msg: "No results",
  /* For listing words */
  sentence_connectors: {
    and: "and"
  },
  /* Sentence fragments for dynamically constructing chart titles (see example for use)*/
  title: {
    of_selected_cut_members: "of Selected {{members}} Members",
    top_drilldowns: "for Top {{drilldowns}}",
    by_drilldowns: "by {{drilldowns}}",
    over_time: "Over Time",
    measure_and_modifier: "{{modifier}} {{measure}}"
  }
}The types defined in the Struct namespace are for private use and might change between versions. Changes in these are not considered for semver version bumps, only the previously described.
Creates a QueryParams object from a olap-client Query object. The function has the following shape:
function buildQueryParams(
  query: OlapClient.Query,
  formatters?:
    | Record<string, (value: number) => string>
    | (measure: OlapClient.Measure | "growth" | "rca") => (value: number) => string
): QueryParams;The formatters parameter behaves similarly to the measureConfig property from the Vizbuilder component: it can receive either
- an object, whose keys are measure names from the cube, and 
Formatterfunctions as values, or - a function whose first parameter is an 
olap-clientCalculation(which means, it can be either aMeasureobject, or one of the strings"growth"and"rca"), which must return aFormatterfunction for these values. 
An example implementation would be:
import {buildQueryParams} from "@datawheel/vizbuilder";
[...]
const agg = await client.execQuery(query);
return {
  cube: cube.toJSON(),
  dataset: agg.data,
  params: buildQueryParams(agg.query, {
    "Total value": dollarFormatter,
    "Average value": dollarFormatter
  })
}
[...]As a way to circumvent the limitations in the OLAP server, the labels of the entities used to formulate the Query can be localized through the use of the caption annotation. Vizbuilder uses the locale of the query to pick the localized caption from the olap-client entity Annotations, and uses that caption in Axis, Titles and Labels.
If a Schema Entity contains the following properties:
"name": "Calendar Year",
"annotations": {
  "caption": "Year",
  "caption_es": "Año",
},- A Query with 
"es"locale would use the"Año"caption, from thecaption_esannotation, in the UI. - If the locale were set to 
"en", or any other, since there's nocaption_enannotation, it would fallback to use"caption", and would show"Year"in the UI. - Ultimately, if there was no 
captionin the Annotations, the UI would use thename"Calendar Year"as final fallback. - The locale value also supports extended locale codes, like 
"zh_CHS"/"zh_CHT". In these cases, an additional fallback step would look for acaption_zhifcaption_zh_CHSwasn't present. 
© 2019 Datawheel, LLC
This project is made available under the MIT License.