kibana/docs/development/visualize/development-create-visualization.asciidoc

468 lines
18 KiB
Plaintext

[[development-create-visualization]]
=== Developing Visualizations
This is a short description of functions and interfaces provided. For more information you should check the kibana
source code and the existing visualizations provided with it.
- <<development-visualization-factory>>
* <<development-base-visualization-type>>
* <<development-react-visualization-type>>
- <<development-vis-editors>>
* <<development-default-editor>>
* <<development-custom-editor>>
- <<development-visualization-request-handlers>>
* <<development-default-request-handler>>
* <<development-none-request-handler>>
* <<development-custom-request-handler>>
- <<development-visualization-response-handlers>>
* <<development-default-response-handler>>
* <<development-none-response-handler>>
* <<development-custom-response-handler>>
- <<development-vis-object>>
* <<development-vis-timefilter>>
- <<development-aggconfig>>
[[development-visualization-factory]]
=== Visualization Factory
Use the `VisualizationFactory` to create a new visualization.
The creation-methods create a new visualization tied to the underlying rendering technology.
You should also register the visualization with `VisTypesRegistryProvider`.
["source","js"]
-----------
import { CATEGORY } from 'ui/vis/vis_category';
import { VisFactoryProvider } from 'ui/vis/vis_factory';
import { VisTypesRegistryProvider } from 'ui/registry/vis_types';
const MyNewVisType = (Private) => {
const VisFactory = Private(VisFactoryProvider);
return VisFactory.createBaseVisualization({
name: 'my_new_vis',
title: 'My New Vis',
icon: 'my_icon',
description: 'Cool new chart',
category: CATEGORY.OTHER
...
});
}
VisTypesRegistryProvider.register(MyNewVisType);
-----------
The list of common parameters:
- *name*: unique visualization name, only lowercase letters and underscore
- *title*: title of your visualization as displayed in kibana
- *icon*: the icon class to use (font awesome)
- *image*: instead of icon you can provide svg image (imported)
- *description*: description of your visualization as shown in kibana
- *category*: the category your visualization falls into (one of `ui/vis/vis_category` values)
- *visConfig*: object holding visualization parameters
- *visConfig.defaults*: object holding default visualization configuration
- *visualization*: A constructor function for a Visualization.
- *requestHandler*: <string> one of the available request handlers or a <function> for a custom request handler
- *responseHandler*: <string> one of the available response handlers or a <function> for a custom response handler
- *editor*: <string> one of the available editors or Editor class for custom one
- *editorConfig*: object holding editor parameters
- *options.showTimePicker*: <bool> show or hide time picker (defaults to true)
- *options.showQueryBar*: <bool> show or hide query bar (defaults to true)
- *options.showFilterBar*: <bool> show or hide filter bar (defaults to true)
- *options.showIndexSelection*: <bool> show or hide index selection (defaults to true)
- *stage*: <string> Set this to "experimental" or "labs" to mark your visualization as experimental.
Labs visualizations can also be disabled from the advanced settings. (defaults to "production")
- *feedbackMessage*: <string> You can provide a message (which can contain HTML), that will be appended
to the experimental notification in visualize, if your visualization is experimental or in lab mode.
Each of the factories have some of the custom parameters, which will be described below.
[[development-base-visualization-type]]
==== Base Visualization Type
The base visualization type does not make any assumptions about the rendering technology you are going to use and
works with pure Javascript. It is the visualization type we recommend to use.
You need to provide a type with a constructor function, a render method which will be called every time
options or data change, and a destroy method which will be called to cleanup.
The render function receives the data object and status object which tells what actually changed.
Render function needs to return a promise, which should be resolved once the visualization is done rendering.
The status object provides information about changes since the previous render call.
Due to performance reasons you need to opt-in for each status change, that you want
to be informed about by Kibana. This is done by using the `requiresUpdateStatus` key
in your visualization registration object. You pass it an array, that contains all
the status updates you want to receive. By default none of it will be calculated.
The following snippet shows explain all available status updates. You should only
activate those changes, that you actually use in your `render` method.
["source","js"]
-----------
import { Status } from 'ui/vis/update_status';
// ...
return VisFactory.createBaseVisualization({
// ...
requiresUpdateStatus: [
// Check for changes in the aggregation configuration for the visualization
Status.AGGS,
// Check for changes in the actual data returned from Elasticsearch
Status.DATA,
// Check for changes in the parameters (configuration) for the visualization
Status.PARAMS,
// Check if the visualization has changes its size
Status.RESIZE,
// Check if the time range for the visualization has been changed
Status.TIME,
// Check if the UI state of the visualization has been changed
Status.UI_STATE
]
});
-----------
If you activate any of these status updates, the `status` object passed as second
parameter to the `render` method will contain a key for that status (e.g. `status[Status.DATA]`),
that is either `true` if a change has been detected or `false` otherwise.
image::images/visualize-flow.png[Main Flow]
- Your visualizations constructor will get called with `vis` object and the DOM-element to which it should render.
At this point you should prepare everything for rendering, but not render yet
- `<visualize>` component monitors `appState`, `uiState` and `vis` for changes
- on changes the `<visualize>`-directive will call your `requestHandler`.
Implementing a request handler is optional, as you might use one of the provided ones.
- response from `requestHandler` will get passed to `responseHandler`. It should convert raw data to something that
can be consumed by visualization. Implementing `responseHandler` is optional, as you might use of of the provided ones.
- On new data from the `responseHandler` or on when the size of the surrounding DOM-element has changed,
your visualization `render`-method gets called. It needs to return a promise which resolves once the visualization
is done rendering.
- the visualization should call `vis.updateState()` any time something has changed that requires to
re-render or fetch new data.
["source","js"]
-----------
import { VisFactoryProvider } from 'ui/vis/vis_factory';
import { VisTypesRegistryProvider } from 'ui/registry/vis_types';
class MyVisualization {
constructor(el, vis) {
this.el = el;
this.vis = vis;
}
async render(visData, status) {
...
return 'done rendering';
}
destroy() {
console.log('destroying');
}
}
const MyNewVisType = (Private) => {
const VisFactory = Private(VisFactoryProvider);
return VisFactory.createBaseVisualization({
name: 'my_new_vis',
title: 'My New Vis',
icon: 'my_icon',
description: 'Cool new chart',
visualization: MyVisualization
});
}
VisTypesRegistryProvider.register(MyNewVisType);
-----------
[[development-react-visualization-type]]
==== React Visualization Type
React visualization type assumes you are using React as your rendering technology.
Just pass in a React component to `visConfig.template`.
The visualization will receive `vis`, `appState`, `updateStatus` and `visData` as props.
It also has a `renderComplete` property, which needs to be called once the rendering has completed.
["source","js"]
-----------
import { ReactComponent } from './my_react_component';
const MyNewVisType = (Private) => {
const VisFactory = Private(VisFactoryProvider);
return VisFactory.createReactVisualization({
name: 'my_new_vis',
title: 'My New Vis',
icon: 'my_icon',
description: 'Cool new chart',
visConfig: {
template: ReactComponent
}
});
}
-----------
[[development-vis-editors]]
=== Visualization Editors
By default, visualizations will use the `default` editor.
This is the sidebar editor you see in many of the Kibana visualizations. You can also write your own editor.
[[development-default-editor]]
==== `default` editor controller
The default editor controller receives an `optionsTemplate` or `optionsTabs` parameter.
These can be either an AngularJS template or React component.
["source","js"]
-----------
{
name: 'my_new_vis',
title: 'My New Vis',
icon: 'my_icon',
description: 'Cool new chart',
editor: 'default',
editorConfig: {
optionsTemplate: '<my-custom-options-directive></my-custom-options-directive>' // or
optionsTemplate: MyReactComponent // or if multiple tabs are required:
optionsTabs: [
{ title: 'tab 1', template: '<div>....</div> },
{ title: 'tab 2', template: '<my-custom-options-directive></my-custom-options-directive>' },
{ title: 'tab 3', template: MyReactComponent }
]
}
}
-----------
[[development-custom-editor]]
==== custom editor controller
You can create a custom editor controller. To do so pass an Editor object (the same format as VisController class).
You can make your controller take extra configuration which is passed to the editorConfig property.
["source","js"]
-----------
import { VisFactoryProvider } from 'ui/vis/vis_factory';
class MyEditorController {
constructor(el, vis) {
this.el = el;
this.vis = vis;
this.config = vis.type.editorConfig;
}
async render(visData) {
console.log(this.config.my);
...
return 'done rendering';
}
destroy() {
console.log('destroying');
}
}
const MyNewVisType = (Private) => {
const VisFactory = Private(VisFactoryProvider);
return VisFactory.createAngularVisualization({
name: 'my_new_vis',
title: 'My New Vis',
icon: 'my_icon',
description: 'Cool new chart',
editor: MyEditorController,
editorConfig: { my: 'custom config' }
});
}
VisTypesRegistryProvider.register(MyNewVisType);
-----------
[[development-visualization-request-handlers]]
=== Visualization Request Handlers
Request handler gets called when one of the following keys on AppState change:
`vis`, `query`, `filters` or `uiState` and when timepicker is updated. On top
of that it will also get called on force refresh.
By default visualizations will use the `courier` request handler. They can also choose to use any of the other provided
request handlers. It is also possible to define your own request handler
(which you can then register to be used by other visualizations).
[[development-default-request-handler]]
==== courier request handler
'courier' is the default request handler which works with the 'default' side bar editor.
[[development-none-request-handler]]
==== `none` request handler
Using 'none' as your request handles means your visualization does not require any data to be requested.
[[development-custom-request-handler]]
==== custom request handler
You can define your custom request handler by providing a function with the following definition:
`function (vis, appState, uiState, searchSource) { ... }`
This function must return a promise, which should get resolved with new data that will be passed to responseHandler.
It's up to function to decide when it wants to issue a new request or return previous data
(if none of the objects relevant to the request handler changed).
["source","js"]
-----------
import { VisFactoryProvider } from 'ui/vis/vis_factory';
const myRequestHandler = async (vis, appState, uiState, searchSource) => {
const data = ... parse ...
return data;
};
const MyNewVisType = (Private) => {
const VisFactory = Private(VisFactoryProvider);
return VisFactory.createAngularVisualization({
name: 'my_new_vis',
title: 'My New Vis',
icon: 'my_icon',
description: 'Cool new chart',
requestHandler: myRequestHandler
});
}
VisTypesRegistryProvider.register(MyNewVisType);
-----------
[[development-visualization-response-handlers]]
=== Visualization Response Handlers
The response handler is a function that receives the data from a request handler, as well as an instance of Vis object.
Its job is to convert the data to a format visualization can use. By default 'default' request handler is used
which produces a table representation of the data. The data object will then be passed to visualization.
This response matches the visData property of the <visualization> directive.
[[development-default-response-handler]]
==== default response handler
The default response handler converts pure elasticsearch responses into a tabular format.
It is the recommended responseHandler. The response object contains a table property,
which is an array of all the tables in the response. Each of the table objects has two properties:
- `columns`: array of column objects, where each column object has a title property and an aggConfig property
- `rows`: array of rows, where each row is an array of non formatted cell values
Here is an example of a response with 1 table, 3 columns and 2 rows:
["source","js"]
-----------
{
tables: [{
columns: [{
title: 'column1',
aggConfig: ...
},{
title: 'column2',
aggConfig: ...
},{
title: 'column3',
aggConfig: ...
}],
rows: [
[ '404', 1262, 12.5 ]
[ '200', 343546, 60.1 ]
]
}];
}
-----------
[[development-none-response-handler]]
==== none response handler
None response handler is an identity function, which will return the same data it receives.
[[development-custom-response-handler]]
==== custom response handler
You can define your custom response handler by providing a function with the following definition:
'function (vis, response) { ... }'.
Function should return the transformed data object that visualization can consume.
["source","js"]
-----------
import { VisFactoryProvider } from 'ui/vis/vis_factory';
const myResponseHandler = (vis, response) => {
// transform the response (based on vis object?)
const resposne = ... transform data ...;
return response;
};
const MyNewVisType(Private) => {
const VisFactory = Private(VisFactoryProvider);
return VisFactory.createAngularVisualization({
name: 'my_new_vis',
title: 'My New Vis',
icon: 'my_icon',
description: 'Cool new chart',
responseHandler: myResponseHandler
});
}
VisTypesRegistryProvider.register(MyNewVisType);
-----------
[[development-vis-object]]
=== Vis object
The `vis` object holds the visualization state and is the window into kibana:
- *vis.params*: holds the visualization parameters
- *vis.indexPattern*: selected index pattern object
- *vis.getState()*: gets current visualization state
- *vis.updateState()*: updates current state with values from `vis.params`
- *vis.resetState()*: resets `vis.params` to the values in the current state
- *vis.forceReload()*: forces whole cycle (request handler gets called)
- *vis.getUiState()*: gets UI state of visualization
- *vis.uiStateVal(name, val)*: updates a property in UI state
- *vis.isEditorMode()*: returns true if in editor mode
- *vis.API.timeFilter*: allows you to access time picker
- *vis.API.queryFilter*: gives you access to queryFilter
- *vis.API.queryManager*: gives you access to add filters to the filter bar
- *vis.API.filterManager*: gives you access to filterManager
- *vis.API.kuery*: gives you access to the experimental `keury`-language filter bar
- *vis.API.events.click*: default click handler
- *vis.API.events.brush*: default brush handler
The visualization gets all its parameters in `vis.params`, which are default values merged with the current state.
If the visualization needs to update the current state, it should update the `vis.params` and call `vis.updateState()`
which will inform <visualize> about the change, which will call request and response handler and then your
visualization's render method.
For the parameters that should not be saved with the visualization you should use the UI state.
These hold viewer-specific state, such as popup open/closed, custom colors applied to the series etc.
You can access filter bar and time picker through the objects defined on `vis.API`
[[development-vis-timefilter]]
==== timeFilter
Update the timefilter time values and call update() method on it to update time picker
["source","js"]
-----------
timefilter.time.from = moment(ranges.xaxis.from);
timefilter.time.to = moment(ranges.xaxis.to);
timefilter.time.mode = 'absolute';
timefilter.update();
-----------
[[development-aggconfig]]
=== AggConfig object
The AggConfig object represents an aggregation search to Elasticsearch,
plus some additional functionality to manage data-values that belong to this aggregation.
This is primarily used internally in Kibana, but you may find you have a need for it
when writing your own visualization. Here we provide short description of some of the methods on it,
however the best reference would be to actually check the source code.
- *fieldFormatter(<type>)* : returns a function which will format your value according to field formatters defined on
the field. The type can be either 'text' or 'html'.
- *makeLabel()* : gets the label for the aggregation
- *isFilterable()* : return true if aggregation is filterable (you can then call createFilter)
- *createFilter(bucketKey)* : creates a filter for specific bucket key
- *getValue(bucket)* : gets value for a specific bucket
- *getField()* : gets the field used for this aggregation
- *getFieldDisplayName()* : gets field display name
- *getAggParams()* : gets the arguments to the aggregation