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/vizbuilder
This 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
level
name used in the descriptor object can be theuniqueName
, thefullName
, or thename
of the level; these three are matched in that order. - The
members
property in thecuts
descriptor items is an array of memberkey
, for the members defined in the cut on thatlevel
. - The
properties
property in thedrilldowns
descriptor items is an array of propertyname
, belonging to thelevel
defined along it. Likewise, thecaption
property is just a propertyname
. - The
formatter
property, if defined, should be a function that receives anumber
value, and outputs astring
. This formatting function will be used wherever the values for themeasure
defined along it is shown. - For each of the
measures
descriptor items, if amoe
is defined,lci
anduci
won'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
Formatter
functions as values, or - a function whose first parameter is an
olap-client
Calculation
(which means, it can be either aMeasure
object, or one of the strings"growth"
and"rca"
), which must return aFormatter
function 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_es
annotation, in the UI. - If the locale were set to
"en"
, or any other, since there's nocaption_en
annotation, it would fallback to use"caption"
, and would show"Year"
in the UI. - Ultimately, if there was no
caption
in 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_zh
ifcaption_zh_CHS
wasn't present.
© 2019 Datawheel, LLC
This project is made available under the MIT License.