Vislib Point Series Charts (#10370)

Backports PR #9642

**Commit 1:**
adding vislib chart grid

* Original sha: 066c3fefa9
* Authored by ppisljar <peter.pisljar@gmail.com> on 2016-12-24T17:38:16Z

**Commit 2:**
updating vislib to correctly render all new features

* Original sha: 6d642caecc
* Authored by ppisljar <peter.pisljar@gmail.com> on 2016-12-24T17:40:32Z

**Commit 3:**
adding new options to kibana visualizations

* Original sha: ec9a614d70
* Authored by ppisljar <peter.pisljar@gmail.com> on 2016-12-24T17:41:33Z

**Commit 4:**
update vis icon on save

* Original sha: 401ed70df6
* Authored by ppisljar <peter.pisljar@gmail.com> on 2016-12-24T19:20:05Z

**Commit 5:**
updating documentation

* Original sha: 978e9e265a
* Authored by ppisljar <peter.pisljar@gmail.com> on 2016-12-25T09:49:48Z

**Commit 6:**
fixing unit tests

* Original sha: d2168d7c22
* Authored by ppisljar <peter.pisljar@gmail.com> on 2016-12-25T12:12:20Z

**Commit 7:**
cleaning up

* Original sha: 3d90a32138
* Authored by ppisljar <peter.pisljar@gmail.com> on 2016-12-25T21:06:32Z

**Commit 8:**
updating based on UI review

* Original sha: d00dc51476
* Authored by ppisljar <peter.pisljar@gmail.com> on 2017-01-09T09:38:12Z

**Commit 9:**
adding visualize editor unit tests

* Original sha: 5e76d9c74f
* Authored by ppisljar <peter.pisljar@gmail.com> on 2017-01-09T14:42:46Z

**Commit 10:**
selenium tests

* Original sha: 68e953b040
* Authored by ppisljar <peter.pisljar@gmail.com> on 2017-01-09T15:47:55Z

**Commit 11:**
additional option tabs

* Original sha: af6ad1127a
* Authored by ppisljar <peter.pisljar@gmail.com> on 2017-01-11T12:59:06Z

**Commit 12:**
some more changes to the tabs/options [thanks CJ]

* Original sha: 3a5ab5ceaf
* Authored by ppisljar <peter.pisljar@gmail.com> on 2017-01-12T10:26:34Z

**Commit 13:**
going back from Category/Value axis to X/Y axis

* Original sha: 08b01f03a6
* Authored by ppisljar <peter.pisljar@gmail.com> on 2017-01-12T10:36:53Z

**Commit 14:**
fixing unselected dropdown options

* Original sha: 8544079ffb
* Authored by ppisljar <peter.pisljar@gmail.com> on 2017-01-16T11:38:43Z

**Commit 15:**
updating based on last UI review

* Original sha: e9de4f75b1
* Authored by ppisljar <peter.pisljar@gmail.com> on 2017-01-20T12:47:02Z

**Commit 16:**
updating based on last review

* Original sha: 2b97717f50
* Authored by ppisljar <peter.pisljar@gmail.com> on 2017-01-25T11:07:35Z

**Commit 17:**
updating based on last review

* Original sha: 5a499db34e
* Authored by ppisljar <peter.pisljar@gmail.com> on 2017-01-25T11:15:27Z

**Commit 18:**
fixing issue with axis titles

* Original sha: a2a2681390
* Authored by ppisljar <peter.pisljar@gmail.com> on 2017-01-25T13:38:05Z

**Commit 19:**
allowing to specify only upper or only lower data bound

* Original sha: 87804d1942
* Authored by ppisljar <peter.pisljar@gmail.com> on 2017-01-25T14:24:43Z

**Commit 20:**
updating based on brandons review

* Original sha: 7e742627f8
* Authored by ppisljar <peter.pisljar@gmail.com> on 2017-02-10T11:05:54Z

**Commit 21:**
fixing horizontal bar chart labels

* Original sha: 3d437e5335
* Authored by ppisljar <peter.pisljar@gmail.com> on 2017-02-13T11:30:08Z

**Commit 22:**
fixing test

* Original sha: b93c84dac3
* Authored by ppisljar <peter.pisljar@gmail.com> on 2017-02-13T13:18:43Z

**Commit 23:**
adding backward compatibility

* Original sha: b9a78c4e36
* Authored by ppisljar <peter.pisljar@gmail.com> on 2017-02-13T17:40:40Z

**Commit 24:**
updating based on last review

* Original sha: 37634bf211
* Authored by ppisljar <peter.pisljar@gmail.com> on 2017-02-15T09:10:15Z

**Commit 25:**
fixing selenium tests

* Original sha: c3c818cbbc
* Authored by ppisljar <peter.pisljar@gmail.com> on 2017-02-15T15:55:41Z
This commit is contained in:
jasper 2017-02-15 12:30:11 -05:00 committed by Peter Pisljar
parent 31d207c0e2
commit c7e61d2708
62 changed files with 2463 additions and 563 deletions

View file

@ -25,10 +25,8 @@ To create a visualization:
. Choose the visualization type:
+
[horizontal]
<<area-chart,Area chart>>:: Visualize the total contribution of several
different series.
<<xy-chart,Line, Area and Bar chart>>:: Compare different series in X/Y charts.
<<data-table,Data table>>:: Display the raw data of a composed aggregation.
<<line-chart,Line chart>>:: Compare different series.
<<markdown-widget,Markdown widget>>:: Display free-form information or
instructions.
<<metric-chart,Metric>>:: Display a single number.
@ -38,7 +36,6 @@ instructions.
locations.
Timeseries:: Compute and combine data from multiple time series
data sets.
<<vertical-bar-chart,Vertical bar chart>>:: Graph values in a bar chart.
. Specify a search query to retrieve the data for your visualization:
** To enter new search criteria, select the index pattern for the indices that
@ -104,12 +101,10 @@ For more information about working with sub aggregations, see
https://www.elastic.co/blog/kibana-aggregation-execution-order-and-you[Kibana,
Aggregation Execution Order, and You].
include::visualize/area.asciidoc[]
include::visualize/xychart.asciidoc[]
include::visualize/datatable.asciidoc[]
include::visualize/line.asciidoc[]
include::visualize/markdown.asciidoc[]
include::visualize/metric.asciidoc[]
@ -118,8 +113,6 @@ include::visualize/pie.asciidoc[]
include::visualize/tilemap.asciidoc[]
include::visualize/vertbar.asciidoc[]
include::visualize/tagcloud.asciidoc[]
include::visualize/heatmap.asciidoc[]

View file

@ -1,76 +0,0 @@
[[area-chart]]
== Area Charts
This chart's Y axis is the _metrics_ axis. The following aggregations are available for this axis:
*Count*:: The {es-ref}search-aggregations-metrics-valuecount-aggregation.html[_count_] aggregation returns a raw count of
the elements in the selected index pattern.
*Average*:: This aggregation returns the {es-ref}search-aggregations-metrics-avg-aggregation.html[_average_] of a numeric
field. Select a field from the drop-down.
*Sum*:: The {es-ref}search-aggregations-metrics-sum-aggregation.html[_sum_] aggregation returns the total sum of a numeric
field. Select a field from the drop-down.
*Min*:: The {es-ref}search-aggregations-metrics-min-aggregation.html[_min_] aggregation returns the minimum value of a
numeric field. Select a field from the drop-down.
*Max*:: The {es-ref}search-aggregations-metrics-max-aggregation.html[_max_] aggregation returns the maximum value of a
numeric field. Select a field from the drop-down.
*Unique Count*:: The {es-ref}search-aggregations-metrics-cardinality-aggregation.html[_cardinality_] aggregation returns
the number of unique values in a field. Select a field from the drop-down.
*Percentiles*:: The {es-ref}search-aggregations-metrics-percentile-aggregation.html[_percentile_] aggregation divides the
values in a numeric field into percentile bands that you specify. Select a field from the drop-down, then specify one
or more ranges in the *Percentiles* fields. Click the *X* to remove a percentile field. Click *+ Add* to add a
percentile field.
*Percentile Rank*:: The {es-ref}search-aggregations-metrics-percentile-rank-aggregation.html[_percentile ranks_]
aggregation returns the percentile rankings for the values in the numeric field you specify. Select a numeric field
from the drop-down, then specify one or more percentile rank values in the *Values* fields. Click the *X* to remove a
values field. Click *+Add* to add a values field.
You can add an aggregation by clicking the *+ Add Metrics* button.
include::x-axis-aggs.asciidoc[]
For example, a chart of dates with incident counts can display dates in chronological order, or you can raise the
priority of the incident-reporting aggregation to show the most active dates first. The chronological order might show
a time-dependent pattern in incident count, and sorting by active dates can reveal particular outliers in your data.
include::color-picker.asciidoc[]
You can click the *Advanced* link to display more customization options for your metrics or bucket aggregation:
*Exclude Pattern*:: Specify a pattern in this field to exclude from the results.
*Include Pattern*:: Specify a pattern in this field to include in the results.
*JSON Input*:: A text field where you can add specific JSON-formatted properties to merge with the aggregation
definition, as in the following example:
[source,shell]
{ "script" : "doc['grade'].value * 1.2" }
NOTE: In Elasticsearch releases 1.4.3 and later, this functionality requires you to enable
{es-ref}modules-scripting.html[dynamic Groovy scripting].
The availability of these options varies depending on the aggregation you choose.
Select the *Options* tab to change the following aspects of the chart:
*Chart Mode*:: When you have multiple Y-axis aggregations defined for your chart, you can use this drop-down to affect
how the aggregations display on the chart:
_stacked_:: Stacks the aggregations on top of each other.
_overlap_:: The aggregations overlap, with translucency indicating areas of overlap.
_wiggle_:: Displays the aggregations as a https://en.wikipedia.org/wiki/Streamgraph[streamgraph].
_percentage_:: Displays each aggregation as a proportion of the total.
_silhouette_:: Displays each aggregation as variance from a central line.
Checkboxes are available to enable and disable the following behaviors:
*Line Mode*:: You can choose between straight line, smoothed line and stepped line.
*Set Y-Axis Extents*:: Check this box and enter values in the *y-max* and *y-min* fields to set the Y axis to specific
values.
*Scale Y-Axis to Data Bounds*:: The default Y axis bounds are zero and the maximum value returned in the data. Check
this box to change both upper and lower bounds to match the values returned in the data.
*Order buckets by descending sum*:: Check this box to enforce sorting of buckets by descending sum in the visualization
*Show Tooltip*:: Check this box to enable the display of tooltips.
[float]
[[area-viewing-detailed-information]]
==== Viewing Detailed Information
include::visualization-raw-data.asciidoc[]

View file

@ -1,69 +0,0 @@
[[line-chart]]
== Line Charts
This chart's Y axis is the _metrics_ axis. The following aggregations are available for this axis:
include::y-axis-aggs.asciidoc[]
Before you choose a buckets aggregation, specify if you are splitting slices within a single chart or splitting into
multiple charts. A multiple chart split must run before any other aggregations. When you split a chart, you can change
if the splits are displayed in a row or a column by clicking the *Rows | Columns* selector.
include::x-axis-aggs.asciidoc[]
include::color-picker.asciidoc[]
You can click the *Advanced* link to display more customization options for your metrics or bucket aggregation:
*Exclude Pattern*:: Specify a pattern in this field to exclude from the results.
*Exclude Pattern Flags*:: A standard set of Java flags for the exclusion pattern.
*Include Pattern*:: Specify a pattern in this field to include in the results.
*Include Pattern Flags*:: A standard set of Java flags for the inclusion pattern.
*JSON Input*:: A text field where you can add specific JSON-formatted properties to merge with the aggregation
definition, as in the following example:
[source,shell]
{ "script" : "doc['grade'].value * 1.2" }
NOTE: In Elasticsearch releases 1.4.3 and later, this functionality requires you to enable
{es-ref}modules-scripting.html[dynamic Groovy scripting].
The availability of these options varies depending on the aggregation you choose.
Select the *Options* tab to change the following aspects of the chart:
*Y-Axis Scale*:: You can select *linear*, *log*, or *square root* scales for the chart's Y axis. You can use a log
scale to display data that varies exponentially, such as a compounding interest chart, or a square root scale to
regularize the display of data sets with variabilities that are themselves highly variable. This kind of data, where
the variability is itself variable over the domain being examined, is known as _heteroscedastic_ data. For example, if
a data set of height versus weight has a relatively narrow range of variability at the short end of height, but a wider
range at the taller end, the data set is heteroscedastic.
*Line Mode*:: You can choose between straight line, smoothed line and stepped line.
*Show Connecting Lines*:: Check this box to draw lines between the points on the chart.
*Show Circles*:: Check this box to draw each data point on the chart as a small circle.
*Current time marker*:: For charts of time-series data, check this box to draw a red line on the current time.
*Set Y-Axis Extents*:: Check this box and enter values in the *y-max* and *y-min* fields to set the Y axis to specific
values.
*Show Tooltip*:: Check this box to enable the display of tooltips.
*Scale Y-Axis to Data Bounds*:: The default Y-axis bounds are zero and the maximum value returned in the data. Check
this box to change both upper and lower bounds to match the values returned in the data.
*Order buckets by descending sum*:: Check this box to enforce sorting of buckets by descending sum in the visualization
After changing options, click the *Apply changes* button to update your visualization, or the grey *Discard
changes* button to keep your visualization in its current state.
[float]
[[bubble-chart]]
==== Bubble Charts
You can convert a line chart visualization to a bubble chart by performing the following steps:
. Click *Add Metrics* for the visualization's Y axis, then select *Dot Size*.
. Select a metric aggregation from the drop-down list.
. In the *Options* tab, uncheck the *Show Connecting Lines* box.
. Click the *Apply changes* button.
[float]
[[line-viewing-detailed-information]]
==== Viewing Detailed Information
include::visualization-raw-data.asciidoc[]

View file

@ -1,78 +0,0 @@
[[vertical-bar-chart]]
== Vertical Bar Charts
This chart's Y axis is the _metrics_ axis. The following aggregations are available for this axis:
*Count*:: The {es-ref}search-aggregations-metrics-valuecount-aggregation.html[_count_] aggregation returns a raw count of
the elements in the selected index pattern.
*Average*:: This aggregation returns the {es-ref}search-aggregations-metrics-avg-aggregation.html[_average_] of a numeric
field. Select a field from the drop-down.
*Sum*:: The {es-ref}search-aggregations-metrics-sum-aggregation.html[_sum_] aggregation returns the total sum of a numeric
field. Select a field from the drop-down.
*Min*:: The {es-ref}search-aggregations-metrics-min-aggregation.html[_min_] aggregation returns the minimum value of a
numeric field. Select a field from the drop-down.
*Max*:: The {es-ref}search-aggregations-metrics-max-aggregation.html[_max_] aggregation returns the maximum value of a
numeric field. Select a field from the drop-down.
*Unique Count*:: The {es-ref}search-aggregations-metrics-cardinality-aggregation.html[_cardinality_] aggregation returns
the number of unique values in a field. Select a field from the drop-down.
*Percentiles*:: The {es-ref}search-aggregations-metrics-percentile-aggregation.html[_percentile_] aggregation divides the
values in a numeric field into percentile bands that you specify. Select a field from the drop-down, then specify one
or more ranges in the *Percentiles* fields. Click the *X* to remove a percentile field. Click *+ Add* to add a
percentile field.
*Percentile Rank*:: The {es-ref}search-aggregations-metrics-percentile-rank-aggregation.html[_percentile ranks_]
aggregation returns the percentile rankings for the values in the numeric field you specify. Select a numeric field
from the drop-down, then specify one or more percentile rank values in the *Values* fields. Click the *X* to remove a
values field. Click *+Add* to add a values field.
You can add an aggregation by clicking the *+ Add Aggregation* button.
Enter a string in the *Custom Label* field to change the display label.
The _buckets_ aggregations determine what information is being retrieved from your data set.
Before you choose a buckets aggregation, specify if you are splitting slices within a single chart or splitting into
multiple charts. A multiple chart split must run before any other aggregations. When you split a chart, you can change
if the splits are displayed in a row or a column by clicking the *Rows | Columns* selector.
include::x-axis-aggs.asciidoc[]
include::color-picker.asciidoc[]
Enter a string in the *Custom Label* field to change the display label.
You can click the *Advanced* link to display more customization options for your metrics or bucket aggregation:
*Exclude Pattern*:: Specify a pattern in this field to exclude from the results.
*Include Pattern*:: Specify a pattern in this field to include in the results.
*JSON Input*:: A text field where you can add specific JSON-formatted properties to merge with the aggregation
definition, as in the following example:
[source,shell]
{ "script" : "doc['grade'].value * 1.2" }
NOTE: In Elasticsearch releases 1.4.3 and later, this functionality requires you to enable
{es-ref}modules-scripting.html[dynamic Groovy scripting].
The availability of these options varies depending on the aggregation you choose.
Select the *Options* to change the following aspects of the table:
*Bar Mode*:: When you have multiple Y-axis aggregations defined for your chart, you can use this drop-down to affect
how the aggregations display on the chart:
_stacked_:: Stacks the aggregations on top of each other.
_percentage_:: Displays each aggregation as a proportion of the total.
_grouped_:: Groups the results horizontally by the lowest-priority sub-aggregation.
Checkboxes are available to enable and disable the following behaviors:
*Show Tooltip*:: Check this box to enable the display of tooltips.
*Scale Y-Axis to Data Bounds*:: The default Y axis bounds are zero and the maximum value returned in the data. Check
this box to change both upper and lower bounds to match the values returned in the data.
*Order buckets by descending sum*:: Check this box to enforce sorting of buckets by descending sum in the visualization
[float]
[[vertbar-viewing-detailed-information]]
==== Viewing Detailed Information
include::visualization-raw-data.asciidoc[]

View file

@ -0,0 +1,126 @@
[[xy-chart]]
== X/Y Charts
X/Y charts refer to Area, Line and Bar charts which allow you to plot your data on X/Y axis.
First you need to select your _metrics_ which define Value axis. The following aggregations are available for this axis:
*Count*:: The {es-ref}search-aggregations-metrics-valuecount-aggregation.html[_count_] aggregation returns a raw count of
the elements in the selected index pattern.
*Average*:: This aggregation returns the {es-ref}search-aggregations-metrics-avg-aggregation.html[_average_] of a numeric
field. Select a field from the drop-down.
*Sum*:: The {es-ref}search-aggregations-metrics-sum-aggregation.html[_sum_] aggregation returns the total sum of a numeric
field. Select a field from the drop-down.
*Min*:: The {es-ref}search-aggregations-metrics-min-aggregation.html[_min_] aggregation returns the minimum value of a
numeric field. Select a field from the drop-down.
*Max*:: The {es-ref}search-aggregations-metrics-max-aggregation.html[_max_] aggregation returns the maximum value of a
numeric field. Select a field from the drop-down.
*Unique Count*:: The {es-ref}search-aggregations-metrics-cardinality-aggregation.html[_cardinality_] aggregation returns
the number of unique values in a field. Select a field from the drop-down.
*Percentiles*:: The {es-ref}search-aggregations-metrics-percentile-aggregation.html[_percentile_] aggregation divides the
values in a numeric field into percentile bands that you specify. Select a field from the drop-down, then specify one
or more ranges in the *Percentiles* fields. Click the *X* to remove a percentile field. Click *+ Add* to add a
percentile field.
*Percentile Rank*:: The {es-ref}search-aggregations-metrics-percentile-rank-aggregation.html[_percentile ranks_]
aggregation returns the percentile rankings for the values in the numeric field you specify. Select a numeric field
from the drop-down, then specify one or more percentile rank values in the *Values* fields. Click the *X* to remove a
values field. Click *+Add* to add a values field.
You can add an aggregation by clicking the *+ Add Aggregation* button.
Enter a string in the *Custom Label* field to change the display label.
The _buckets_ aggregations determine what information is being retrieved from your data set.
Before you choose a buckets aggregation, specify if you are splitting slices within a single chart or splitting into
multiple charts. A multiple chart split must run before any other aggregations. When you split a chart, you can change
if the splits are displayed in a row or a column by clicking the *Rows | Columns* selector.
include::x-axis-aggs.asciidoc[]
include::color-picker.asciidoc[]
Enter a string in the *Custom Label* field to change the display label.
You can click the *Advanced* link to display more customization options for your metrics or bucket aggregation:
*Exclude Pattern*:: Specify a pattern in this field to exclude from the results.
*Include Pattern*:: Specify a pattern in this field to include in the results.
*JSON Input*:: A text field where you can add specific JSON-formatted properties to merge with the aggregation
definition, as in the following example:
[source,shell]
{ "script" : "doc['grade'].value * 1.2" }
NOTE: In Elasticsearch releases 1.4.3 and later, this functionality requires you to enable
{es-ref}modules-scripting.html[dynamic Groovy scripting].
The availability of these options varies depending on the aggregation you choose.
=== Options
Select the *Options* tab to change the way your data is visualized. Customization options are grouped into areas to provide easier access:
==== General Settings
*Legend Position*:: Allows you to move your legend to the *left*, *right*, *top* or *bottom*
*Show Tooltip*:: Enables or disables the display of tooltip on hovering over chart objects
*Order buckets by descending sum*:: Check this box to enforce sorting of buckets by descending sum in the visualization
==== Category Axis
The category axis is defined by the bucket aggregation you chose under Data tab. Here you can customize how its displayed:
*Show*:: You can chose if you want to display category axis or not
*Position*:: You can choose where you want to display category axis. If you position your category axis on the left or right the chart will turn to the horizontal type.
*Advanced Options*::
*Labels - Show Labels*:::: Allows you to hide axis labels
*Labels - Filter Labels*:::: If filter labels is enabled some labels will be hidden in case there is not enough space to display them
*Labels - Rotate*:::: You can enter the number in degrees for how much you want to rotate labels
*Labels - Truncate*:::: You can enter the size in pixels to which the label is truncated
==== Grid
You can enable grid on the chart. By default grid is displayed on the category axis only.
*Category Lines*:: You can disable the display of grid lines on category axis
*Value Axis*:: You can choose on which (if any) of the value axes you want to display grid lines
*Color*:: You can specify the color for gird lines
==== Value Axes
By default one value axis is defined on a chart, but you can add as much as you need. Clicking on the + sign will create a new value axis.
Each value axis has this options:
*Show*:: You can decide to hide the value axis completely
*Label*:: Allows to define a custom label
*Position*:: Options for position depend on the position of your category axis. If category axis is positioned on the top or bottom you can position value axis on the left or right. In the opposite case you can position your value axis on the top or bottom.
*Mode*:: Mode allows you to define how value axis represents the values. You can choose among the following:
_wiggle_:::: Displays the aggregations as a https://en.wikipedia.org/wiki/Streamgraph[streamgraph].
_percentage_:::: Displays each aggregation as a proportion of the total.
_silhouette_:::: Displays each aggregation as variance from a central line.
*Scale Type*:: Allows you to choose between *linear*, *square root* and *log* scale
*Advanced Options*::
*Labels - Show Labels*:::: Allows you to hide axis labels
*Labels - Filter Labels*:::: If filter labels is enabled some labels will be hidden in case there is not enough spave to display them
*Labels - Rotate*:::: You can enter the number in degrees for how much you want to rotate labels
*Labels - Truncate*:::: You can enter the size in pixels to which the label is truncated
*Scale to Data Bounds*:::: The default Y axis bounds are zero and the maximum value returned in the data. Check
this box to change both upper and lower bounds to match the values returned in the data.
*Custom Extents*:::: You can define custom minimum and maximum for each axis
==== Series
Each of the *Series* represents a metric you added in the data tab. For each Series you can define the following options:
*Show*:: Allows you to hide specific series.
*Type*:: Allows you to choose between *Area*, *Line* and *Histogram* types. This allows you to show each metrics as a different chart type.
*Mode*:: Allows you to choose how your values are showed on the chart.
_stacked_:::: Values for this series will be stacked. Stacking happens per value axis. This means that if you have two series on one value axis and both modes are set to stacked they will be stacked on top of each other. If one of the series modes is set to normal the other series values (in case series are split) will be split and the second series will be grouped next to them. If you want both series to be stacked but not to be stacked on top of each other you will want to plot them on separate value axes.
_normal_:::: In normal mode values will not be stacked.
*Value Axis*:: You can define to which value axis this series belongs. If you dont select a value it will belong to the first value axis.
Additional options might be available depending the on the *type* selected. For Area and Line types you can decide to smooth the lines. And for Line chart you can decide to not show lines or circles.
[float]
[[vertbar-viewing-detailed-information]]
== Viewing Detailed Information
include::visualization-raw-data.asciidoc[]

View file

@ -1,8 +1,8 @@
import VislibVisTypeVislibVisTypeProvider from 'ui/vislib_vis_type/vislib_vis_type';
import VisSchemasProvider from 'ui/vis/schemas';
import areaTemplate from 'plugins/kbn_vislib_vis_types/editors/area.html';
import pointSeriesTemplate from 'plugins/kbn_vislib_vis_types/editors/point_series.html';
export default function HistogramVisType(Private) {
export default function PointSeriesVisType(Private) {
const VislibVisType = Private(VislibVisTypeVislibVisTypeProvider);
const Schemas = Private(VisSchemasProvider);
@ -11,35 +11,87 @@ export default function HistogramVisType(Private) {
title: 'Area chart',
icon: 'fa-area-chart',
description: 'Great for stacked timelines in which the total of all series is more important ' +
'than comparing any two or more series. Less useful for assessing the relative change of ' +
'unrelated data points as changes in a series lower down the stack will have a difficult to gauge ' +
'effect on the series above it.',
'than comparing any two or more series. Less useful for assessing the relative change of ' +
'unrelated data points as changes in a series lower down the stack will have a difficult to gauge ' +
'effect on the series above it.',
params: {
defaults: {
grid: {
categoryLines: false,
style: {
color: '#eee'
}
},
categoryAxes: [
{
id: 'CategoryAxis-1',
type: 'category',
position: 'bottom',
show: true,
style: {
},
scale: {
type: 'linear'
},
labels: {
show: true,
truncate: 100
},
title: {}
}
],
valueAxes: [
{
id: 'ValueAxis-1',
name: 'LeftAxis-1',
type: 'value',
position: 'left',
show: true,
style: {
},
scale: {
type: 'linear',
mode: 'normal'
},
labels: {
show: true,
rotate: 0,
filter: false,
truncate: 100
},
title: {}
}
],
seriesParams: [{
show: 'true',
type: 'area',
mode: 'stacked',
data: {
label: 'Count'
},
drawLinesBetweenPoints: true,
showCircles: true,
interpolate: 'linear',
valueAxis: 'ValueAxis-1'
}],
addTooltip: true,
addLegend: true,
legendPosition: 'right',
scale: 'linear',
showCircles: true,
interpolate: 'linear',
mode: 'stacked',
scale: 'linear',
drawLinesBetweenPoints: true,
radiusRatio: 9,
times: [],
addTimeMarker: false,
defaultYExtents: false,
setYExtents: false
},
legendPositions: [{
value: 'left',
text: 'left',
}, {
value: 'right',
text: 'right',
}, {
value: 'top',
text: 'top',
}, {
value: 'bottom',
text: 'bottom',
}],
positions: ['top', 'left', 'right', 'bottom'],
chartTypes: ['line', 'area', 'histogram'],
axisModes: ['normal', 'percentage', 'wiggle', 'silhouette'],
scaleTypes: ['linear', 'log', 'square root'],
chartModes: ['normal', 'stacked'],
interpolationModes: [{
value: 'linear',
text: 'straight',
@ -50,22 +102,35 @@ export default function HistogramVisType(Private) {
value: 'step-after',
text: 'stepped',
}],
scales: ['linear', 'log', 'square root'],
modes: ['stacked', 'overlap', 'percentage', 'wiggle', 'silhouette'],
editor: areaTemplate
editor: pointSeriesTemplate,
optionTabs: [
{
name: 'advanced',
title: 'Metrics & Axes',
editor: '<div><vislib-series></vislib-series><vislib-value-axes>' +
'</vislib-value-axes><vislib-category-axis></vislib-category-axis></div>'
},
{ name: 'options', title: 'Panel Settings', editor: pointSeriesTemplate },
],
},
implementsRenderComplete: true,
schemas: new Schemas([
{
group: 'metrics',
name: 'metric',
title: 'Y-Axis',
min: 1,
aggFilter: '!std_dev',
defaults: [
{ schema: 'metric', type: 'count' }
]
},
{
group: 'metrics',
name: 'radius',
title: 'Dot Size',
min: 0,
max: 1,
aggFilter: ['count', 'avg', 'sum', 'min', 'max', 'cardinality']
},
{
group: 'buckets',
name: 'segment',
@ -77,7 +142,7 @@ export default function HistogramVisType(Private) {
{
group: 'buckets',
name: 'group',
title: 'Split Area',
title: 'Split Series',
min: 0,
max: 1,
aggFilter: '!geohash_grid'

View file

@ -0,0 +1,113 @@
<div class="kuiSideBarSection kuiSideBarSection__main">
<div class="kuiSideBarSectionTitle">
<div class="kuiSideBarSectionTitle__text">
X-Axis
</div>
</div>
<!-- General -->
<div class="kuiSideBarSection">
<div class="kuiSideBarFormRow">
<label class="kuiSideBarFormRow__label" for="categoryAxisShow">
Show
</label>
<div class="kuiSideBarFormRow__control">
<input class="kuiCheckBox" id="categoryAxisShow" type="checkbox" ng-model="vis.params.categoryAxes[0].show">
</div>
</div>
<div class="kuiSideBarFormRow">
<label class="kuiSideBarFormRow__label" for="categoryAxisPosition">
Position
</label>
<div class="kuiSideBarFormRow__control">
<select
id="categoryAxisPosition"
class="kuiSelect kuiSideBarSelect"
ng-model="vis.params.categoryAxes[0].position"
ng-options="mode for mode in vis.type.params.positions"
></select>
</div>
</div>
</div>
<!-- Advanced options -->
<div class="kuiSideBarSection">
<a
href=""
class="kuiSideBarOptionsLink"
ng-click="isCategoryAxisAdvancedOptionsOpen = !isCategoryAxisAdvancedOptionsOpen"
>
<span
aria-hidden="true"
ng-class="{ 'fa-caret-down': isCategoryAxisAdvancedOptionsOpen, 'fa-caret-right': !isCategoryAxisAdvancedOptionsOpen }"
class="fa fa-caret-right kuiSideBarOptionsLink__caret"
></span>
<span class="kuiSideBarOptionsLink__text">
<span ng-show="!isCategoryAxisAdvancedOptionsOpen">
Show
</span>
<span ng-show="isCategoryAxisAdvancedOptionsOpen">
Hide
</span>
Advanced Options
</span>
</a>
<div ng-show="isCategoryAxisAdvancedOptionsOpen">
<!-- Labels -->
<div class="kuiSideBarSection">
<h6 class="kuiSideBarFormSectionTitle">
Labels
</h6>
<div class="kuiSideBarFormRow">
<label class="kuiSideBarFormRow__label" for="showLabels">
Show Labels
</label>
<div class="kuiSideBarFormRow__control">
<input class="kuiCheckBox" id="showLabels" type="checkbox" ng-model="vis.params.categoryAxes[0].labels.show">
</div>
</div>
<div class="kuiSideBarFormRow">
<label class="kuiSideBarFormRow__label" for="showFilter">
Filter Labels
</label>
<div class="kuiSideBarFormRow__control">
<input class="kuiCheckBox" id="showFilter" type="checkbox" ng-model="vis.params.categoryAxes[0].labels.filter">
</div>
</div>
<div class="kuiSideBarFormRow">
<label class="kuiSideBarFormRow__label" for="rotateLabels">
Rotate
</label>
<div class="kuiSideBarFormRow__control">
<select
id="rotateLabels"
class="kuiSelect kuiSideBarSelect"
ng-model="vis.params.categoryAxes[0].labels.rotate"
ng-options="mode.value as mode.name for mode in rotateOptions"
></select>
</div>
</div>
<div class="kuiSideBarFormRow">
<label class="kuiSideBarFormRow__label" for="truncateLabels">
Truncate
</label>
<div class="kuiSideBarFormRow__control">
<input
id="truncateLabels"
class="kuiInput kuiSideBarInput"
type="number"
ng-model="vis.params.categoryAxes[0].labels.truncate"
>
</div>
</div>
</div>
</div>
</div>
</div>

View file

@ -0,0 +1,32 @@
import uiModules from 'ui/modules';
import vislibValueAxesTemplate from 'plugins/kbn_vislib_vis_types/controls/point_series/category_axis.html';
const module = uiModules.get('kibana');
module.directive('vislibCategoryAxis', function () {
return {
restrict: 'E',
template: vislibValueAxesTemplate,
replace: true,
link: function ($scope) {
$scope.rotateOptions = [
{ name: 'horizontal', value: 0 },
{ name: 'vertical', value: 90 },
{ name: 'angled', value: 75 },
];
let lastAxisTitle = '';
$scope.$watch(() => {
return $scope.vis.aggs.map(agg => {
return agg.params.field ? agg.makeLabel() : '';
}).join();
}, () => {
const agg = $scope.vis.aggs.find(agg => agg.schema.name === 'segment');
const label = agg ? agg.makeLabel() : '';
if (lastAxisTitle !== label) {
lastAxisTitle = label;
$scope.vis.params.categoryAxes[0].title.text = label;
}
});
}
};
});

View file

@ -0,0 +1,48 @@
<div>
<div class="kuiSideBarCollapsibleTitle">
<div
class="kuiSideBarCollapsibleTitle__label"
ng-click="isGridOpen = !isGridOpen"
>
<span
aria-hidden="true"
ng-class="{ 'fa-caret-down': isGridOpen, 'fa-caret-right': !isGridOpen }"
class="fa fa-caret-right kuiSideBarCollapsibleTitle__caret"
></span>
<span class="kuiSideBarCollapsibleTitle__text">
Grid
</span>
</div>
</div>
<div ng-show="isGridOpen" class="kuiSideBarCollapsibleSection">
<!-- General -->
<div class="kuiSideBarSection">
<div class="kuiSideBarFormRow">
<label class="kuiSideBarFormRow__label" for="showCategoryLines">
X-Axis Lines
</label>
<div class="kuiSideBarFormRow__control">
<input class="kuiCheckBox" id="showCategoryLines" type="checkbox" ng-model="vis.params.grid.categoryLines">
</div>
</div>
<div class="kuiSideBarFormRow">
<label class="kuiSideBarFormRow__label" for="gridAxis">
Y-Axis Lines
</label>
<div class="kuiSideBarFormRow__control">
<select
id="gridAxis"
class="kuiSelect kuiSideBarSelect"
ng-model="vis.params.grid.valueAxis"
ng-options="axis.id as axis.name for axis in vis.params.valueAxes"
>
<option value="">Don't show</option>
</select>
</div>
</div>
</div>
</div>

View file

@ -0,0 +1,15 @@
import uiModules from 'ui/modules';
import vislibGridTemplate from 'plugins/kbn_vislib_vis_types/controls/point_series/grid.html';
const module = uiModules.get('kibana');
module.directive('vislibGrid', function () {
return {
restrict: 'E',
template: vislibGridTemplate,
replace: true,
link: function ($scope) {
$scope.isGridOpen = true;
}
};
});

View file

@ -0,0 +1,4 @@
import 'plugins/kbn_vislib_vis_types/controls/point_series/value_axes.js';
import 'plugins/kbn_vislib_vis_types/controls/point_series/category_axis.js';
import 'plugins/kbn_vislib_vis_types/controls/point_series/series.js';
import 'plugins/kbn_vislib_vis_types/controls/point_series/grid.js';

View file

@ -0,0 +1,118 @@
<div class="kuiSideBarSection kuiSideBarSection__main">
<div class="kuiSideBarSectionTitle">
<div class="kuiSideBarSectionTitle__text">
Metrics
</div>
</div>
<div
ng-repeat="chart in vis.params.seriesParams track by $index"
class="kuiSideBarSection"
>
<div class="kuiSideBarCollapsibleTitle">
<div
class="kuiSideBarCollapsibleTitle__label"
ng-click="isSeriesOpen = !isSeriesOpen"
>
<span
aria-hidden="true"
ng-class="{ 'fa-caret-down': isSeriesOpen, 'fa-caret-right': !isSeriesOpen }"
class="fa fa-caret-right kuiSideBarCollapsibleTitle__caret"
></span>
<span class="kuiSideBarCollapsibleTitle__text">
{{chart.data.label}}
</span>
</div>
</div>
<div ng-show="isSeriesOpen" class="kuiSideBarCollapsibleSection">
<!-- General -->
<div class="kuiSideBarSection">
<div class="kuiSideBarFormRow">
<label class="kuiSideBarFormRow__label" for="{{ 'seriesType' + $index }}">
Type
</label>
<div class="kuiSideBarFormRow__control">
<select
id="{{ 'seriesType' + $index }}"
class="kuiSelect kuiSideBarSelect"
ng-model="chart.type"
ng-options="mode for mode in vis.type.params.chartTypes"
></select>
</div>
</div>
<div class="kuiSideBarFormRow">
<label class="kuiSideBarFormRow__label" for="{{ 'seriesMode' + $index }}">
Mode
</label>
<div class="kuiSideBarFormRow__control">
<select
id="{{ 'seriesMode' + $index }}"
class="kuiSelect kuiSideBarSelect"
ng-model="chart.mode"
ng-options="mode for mode in vis.type.params.chartModes"
></select>
</div>
</div>
<div class="kuiSideBarFormRow">
<label class="kuiSideBarFormRow__label" for="{{ 'seriesValueAxis' + $index }}">
Value Axis
</label>
<div class="kuiSideBarFormRow__control">
<select
id="{{ 'seriesValueAxis' + $index }}"
class="kuiSelect kuiSideBarSelect"
ng-model="chart.valueAxis"
ng-change="changeValueAxis($index)"
>
<option ng-repeat="axis in vis.params.valueAxes track by axis.id" value="{{axis.id}}">{{axis.name}}</option>
<option value="new">New Axis ...</option>
</select>
</div>
</div>
<div ng-show="chart.type == 'line' || chart.type == 'area'">
<div class="kuiSideBarFormRow">
<label class="kuiSideBarFormRow__label" for="{{ 'lineMode' + $index }}">
Line Mode
</label>
<div class="kuiSideBarFormRow__control">
<select
id="{{ 'lineMode' + $index }}"
class="kuiSelect kuiSideBarSelect"
ng-model="chart.interpolate"
ng-options="mode.value as mode.text for mode in vis.type.params.interpolationModes"
>
</select>
</div>
</div>
</div>
<div ng-show="chart.type == 'line'">
<div class="kuiSideBarFormRow">
<label class="kuiSideBarFormRow__label" for="{{ 'drawLines' + $index }}">
Show Line
</label>
<div class="kuiSideBarFormRow__control">
<input class="kuiCheckBox" id="{{ 'drawLines' + $index }}" type="checkbox" ng-model="chart.drawLinesBetweenPoints">
</div>
</div>
<div class="kuiSideBarFormRow">
<label class="kuiSideBarFormRow__label" for="{{ 'showCircles' + $index }}">
Show Circles
</label>
<div class="kuiSideBarFormRow__control">
<input class="kuiCheckBox" id="{{ 'showCircles' + $index }}" type="checkbox" ng-model="chart.showCircles">
</div>
</div>
</div>
</div>
</div>
</div>
<div style="height: 10px"></div>
</div>

View file

@ -0,0 +1,83 @@
import _ from 'lodash';
import uiModules from 'ui/modules';
import vislibSeriesTemplate from 'plugins/kbn_vislib_vis_types/controls/point_series/series.html';
const module = uiModules.get('kibana');
module.directive('vislibSeries', function () {
return {
restrict: 'E',
template: vislibSeriesTemplate,
replace: true,
link: function ($scope) {
function makeSerie(label) {
const last = $scope.series[$scope.series.length - 1];
return {
show: true,
mode: last ? last.mode : 'normal',
type: last ? last.type : 'line',
drawLinesBetweenPoints: true,
showCircles: true,
interpolate: 'linear',
data: {
label: label
},
valueAxis: $scope.vis.params.valueAxes[0].id
};
}
$scope.series = $scope.vis.params.seriesParams;
$scope.$watch(() => {
return $scope.vis.aggs.map(agg => {
try {
return agg.makeLabel();
} catch (e) {
return '';
}
}).join();
}, () => {
const schemaTitle = $scope.vis.type.schemas.metrics[0].title;
const metrics = $scope.vis.aggs.filter(agg => {
const isMetric = agg.type && agg.type.type === 'metrics';
return isMetric && agg.schema.title === schemaTitle;
});
// update labels for existing params or create new one
$scope.vis.params.seriesParams = metrics.map((agg, i) => {
const params = $scope.vis.params.seriesParams[i];
if (params) {
params.data.label = agg.makeLabel();
return params;
} else {
const series = makeSerie(agg.makeLabel());
return series;
}
});
});
$scope.$watch(() => {
return $scope.vis.params.seriesParams.map(series => series.type).join();
}, () => {
const types = _.uniq(_.map($scope.vis.params.seriesParams, 'type'));
$scope.savedVis.type = types.length === 1 ? types[0] : 'histogram';
});
$scope.$watch('vis.params.valueAxes.length', () => {
$scope.vis.params.seriesParams.forEach(series => {
if (!$scope.vis.params.valueAxes.find(axis => axis.id === series.valueAxis)) {
series.valueAxis = $scope.vis.params.valueAxes[0].id;
}
});
});
$scope.changeValueAxis = (index) => {
const series = $scope.vis.params.seriesParams[index];
$scope.updateAxisTitle();
if (series.valueAxis === 'new') {
const axis = $scope.addValueAxis();
series.valueAxis = axis.id;
}
};
}
};
});

View file

@ -0,0 +1,261 @@
<div class="kuiSideBarSection kuiSideBarSection__main">
<div class="kuiSideBarSectionTitle">
<div class="kuiSideBarSectionTitle__text">
Y-Axes
</div>
<button
aria-label="Add value axis"
ng-click="addValueAxis()"
tooltip="Add value axis"
tooltip-append-to-body="true"
type="button"
class="fa fa-plus kuiSideBarSectionTitle__action"
></button>
</div>
<div
ng-repeat="axis in vis.params.valueAxes track by axis.id"
class="kuiSideBarSection"
>
<div class="kuiSideBarCollapsibleTitle">
<div
class="kuiSideBarCollapsibleTitle__label"
ng-click="isValueAxisOpen = !isValueAxisOpen"
>
<span
aria-hidden="true"
ng-class="{ 'fa-caret-down': isValueAxisOpen, 'fa-caret-right': !isValueAxisOpen }"
class="fa fa-caret-right kuiSideBarCollapsibleTitle__caret"
></span>
<span class="kuiSideBarCollapsibleTitle__text">
{{axis.name}}
</span>
</div>
<div tooltip="{{getSeries(axis)}}">{{getSeriesShort(axis)}}</div>
<button
ng-hide="vis.params.valueAxes.length === 1"
aria-label="Remove value axis"
ng-click="removeValueAxis(axis)"
tooltip="Remove value axis"
tooltip-append-to-body="true"
type="button"
class="fa fa-remove kuiSideBarCollapsibleTitle__action"
></button>
</div>
<div ng-show="isValueAxisOpen" class="kuiSideBarCollapsibleSection">
<!-- General -->
<div class="kuiSideBarSection">
<div class="kuiSideBarFormRow">
<label class="kuiSideBarFormRow__label" for="{ 'valueAxisShow' + $index }">
Show
</label>
<div class="kuiSideBarFormRow__control">
<input class="kuiCheckBox" id="{ 'valueAxisShow' + $index }" type="checkbox" ng-model="axis.show">
</div>
</div>
<div class="kuiSideBarFormRow">
<label class="kuiSideBarFormRow__label" for="{{ 'valueAxisTitle' + $index }}">
Title
</label>
<div class="kuiSideBarFormRow__control">
<input
id="{{ 'valueAxisTitle' + $index }}"
class="kuiInput kuiSideBarInput"
type="text"
ng-model="axis.title.text"
>
</div>
</div>
<div class="kuiSideBarFormRow">
<label class="kuiSideBarFormRow__label" for="{{ 'valueAxisPosition' + $index }}">
Position
</label>
<div class="kuiSideBarFormRow__control">
<select
id="{{ 'valueAxisPosition' + $index }}"
class="kuiSelect kuiSideBarSelect"
ng-change="updateAxisName(axis)"
ng-model="axis.position"
ng-options="mode disable when isPositionDisabled(mode) for mode in vis.type.params.positions"
></select>
</div>
</div>
<div class="kuiSideBarFormRow">
<label class="kuiSideBarFormRow__label" for="{{ 'valueAxisMode' + $index }}">
Mode
</label>
<div class="kuiSideBarFormRow__control">
<select
id="{{ 'valueAxisMode' + $index }}"
class="kuiSelect kuiSideBarSelect"
ng-model="axis.scale.mode"
ng-options="mode for mode in vis.type.params.axisModes"
></select>
</div>
</div>
<div class="kuiSideBarFormRow">
<label class="kuiSideBarFormRow__label" for="{{ 'valueAxisScaleType' + $index }}">
Scale Type
</label>
<div class="kuiSideBarFormRow__control">
<select
id="{{ 'valueAxisScaleType' + $index }}"
class="kuiSelect kuiSideBarSelect"
ng-model="axis.scale.type"
ng-options="type for type in vis.type.params.scaleTypes"
></select>
</div>
</div>
</div>
<!-- Advanced options -->
<div class="kuiSideBarSection">
<a
href=""
class="kuiSideBarOptionsLink"
ng-click="isValueAxisAdvancedOptionsOpen = !isValueAxisAdvancedOptionsOpen"
>
<span
aria-hidden="true"
ng-class="{ 'fa-caret-down': isValueAxisAdvancedOptionsOpen, 'fa-caret-right': !isValueAxisAdvancedOptionsOpen }"
class="fa fa-caret-right kuiSideBarOptionsLink__caret"
></span>
<span class="kuiSideBarOptionsLink__text">
<span ng-show="!isValueAxisAdvancedOptionsOpen">
Show
</span>
<span ng-show="isValueAxisAdvancedOptionsOpen">
Hide
</span>
Advanced Options
</span>
</a>
<div ng-show="isValueAxisAdvancedOptionsOpen">
<!-- Labels -->
<div class="kuiSideBarSection">
<h6 class="kuiSideBarFormSectionTitle">
Labels
</h6>
<div class="kuiSideBarFormRow">
<label class="kuiSideBarFormRow__label" for="showLabels">
Show Labels
</label>
<div class="kuiSideBarFormRow__control">
<input class="kuiCheckBox" id="showLabels" type="checkbox" ng-model="axis.labels.show">
</div>
</div>
<div class="kuiSideBarFormRow">
<label class="kuiSideBarFormRow__label" for="showFilter">
Filter Labels
</label>
<div class="kuiSideBarFormRow__control">
<input class="kuiCheckBox" id="showFilter" type="checkbox" ng-model="axis.labels.filter">
</div>
</div>
<div class="kuiSideBarFormRow">
<label class="kuiSideBarFormRow__label" for="rotateLabels">
Rotate
</label>
<div class="kuiSideBarFormRow__control">
<select
id="rotateLabels"
class="kuiSelect kuiSideBarSelect"
ng-model="axis.labels.rotate"
ng-options="mode.value as mode.name for mode in rotateOptions"
></select>
</div>
</div>
<div class="kuiSideBarFormRow">
<label class="kuiSideBarFormRow__label" for="truncateLabels">
Truncate
</label>
<div class="kuiSideBarFormRow__control">
<input
id="truncateLabels"
class="kuiInput kuiSideBarInput"
type="number"
ng-model="axis.labels.truncate"
>
</div>
</div>
</div>
<!-- Custom Extents -->
<div class="kuiSideBarSection">
<h6 class="kuiSideBarFormSectionTitle">
Custom Extents
</h6>
<div class="kuiSideBarFormRow">
<label class="kuiSideBarFormRow__label" for="{ 'defaultYExtents' + $index }">
Scale to Data Bounds
</label>
<div class="kuiSideBarFormRow__control">
<input class="kuiCheckBox" id="{ 'defaultYExtents' + $index }" type="checkbox" ng-model="axis.scale.defaultYExtents">
</div>
</div>
<div class="kuiSideBarFormRow">
<label class="kuiSideBarFormRow__label" for="{ 'setYExtents' + $index }">
Set Axis Extents
</label>
<div class="kuiSideBarFormRow__control">
<input class="kuiCheckBox" id="{ 'setYExtents' + $index }" type="checkbox" ng-model="axis.scale.setYExtents" ng-change="updateExtents(axis)">
</div>
</div>
<div ng-if="axis.scale.setYExtents">
<div class="kuiSideBarFormRow">
<label class="kuiSideBarFormRow__label">
Max
</label>
<div class="kuiSideBarFormRow__control">
<input name="yMax"
class="kuiInput kuiSideBarInput"
type="number"
step="0.1"
ng-model="axis.scale.max"
>
</div>
</div>
<div class="kuiSideBarFormRow">
<label class="kuiSideBarFormRow__label">
Min
</label>
<div class="kuiSideBarFormRow__control">
<input name="yMin"
class="kuiInput kuiSideBarInput"
type="number"
step="0.1"
greater-than="{{axis.scale.type === 'log' ? 0 : ''}}"
ng-model="axis.scale.min"
>
</div>
</div>
<div ng-show="axis.scale.type === 'log' && axis.scale.min <= 0">
<span class="text-danger">Min must exceed 0 when a log scale is selected</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div style="height: 10px"></div>
</div>

View file

@ -0,0 +1,161 @@
import _ from 'lodash';
import uiModules from 'ui/modules';
import vislibValueAxesTemplate from 'plugins/kbn_vislib_vis_types/controls/point_series/value_axes.html';
const module = uiModules.get('kibana');
module.directive('vislibValueAxes', function () {
return {
restrict: 'E',
template: vislibValueAxesTemplate,
replace: true,
link: function ($scope) {
let isCategoryAxisHorizontal = true;
function mapPosition(position) {
switch (position) {
case 'bottom': return 'left';
case 'top': return 'right';
case 'left': return 'bottom';
case 'right': return 'top';
}
}
function mapPositionOposite(position) {
switch (position) {
case 'bottom': return 'top';
case 'top': return 'bottom';
case 'left': return 'right';
case 'right': return 'left';
}
}
$scope.rotateOptions = [
{ name: 'horizontal', value: 0 },
{ name: 'vertical', value: 90 },
{ name: 'angled', value: 75 },
];
$scope.$watch('vis.params.categoryAxes[0].position', position => {
isCategoryAxisHorizontal = ['top', 'bottom'].includes(position);
$scope.vis.params.valueAxes.forEach(axis => {
const axisIsHorizontal = ['top', 'bottom'].includes(axis.position);
if (axisIsHorizontal === isCategoryAxisHorizontal) {
axis.position = mapPosition(axis.position);
$scope.updateAxisName(axis);
}
});
});
$scope.getSeries = function (axis) {
const isFirst = $scope.vis.params.valueAxes[0] === axis;
const series = _.filter($scope.vis.params.seriesParams, series => {
return series.valueAxis === axis.id || (isFirst && !series.valueAxis);
});
return series.map(series => series.data.label).join(', ');
};
$scope.getSeriesShort = function (axis) {
const maxStringLength = 30;
return $scope.getSeries(axis).substring(0, maxStringLength);
};
$scope.isPositionDisabled = function (position) {
if (isCategoryAxisHorizontal) {
return ['top', 'bottom'].includes(position);
}
return ['left', 'right'].includes(position);
};
$scope.addValueAxis = function () {
const firstAxis = $scope.vis.params.valueAxes[0];
const newAxis = _.cloneDeep(firstAxis);
newAxis.id = 'ValueAxis-' + $scope.vis.params.valueAxes.reduce((value, axis) => {
if (axis.id.substr(0, 10) === 'ValueAxis-') {
const num = parseInt(axis.id.substr(10));
if (num >= value) value = num + 1;
}
return value;
}, 1);
newAxis.position = mapPositionOposite(firstAxis.position);
const axisName = _.capitalize(newAxis.position) + 'Axis-';
newAxis.name = axisName + $scope.vis.params.valueAxes.reduce((value, axis) => {
if (axis.name.substr(0, axisName.length) === axisName) {
const num = parseInt(axis.name.substr(axisName.length));
if (num >= value) value = num + 1;
}
return value;
}, 1);
$scope.vis.params.valueAxes.push(newAxis);
return newAxis;
};
$scope.removeValueAxis = function (axis) {
if ($scope.vis.params.valueAxes.length > 1) {
_.remove($scope.vis.params.valueAxes, function (valAxis) {
return valAxis.id === axis.id;
});
}
};
$scope.updateExtents = function (axis) {
if (!axis.scale.setYExtents) {
delete axis.scale.min;
delete axis.scale.max;
}
};
$scope.updateAxisName = function (axis) {
const axisName = _.capitalize(axis.position) + 'Axis-';
axis.name = axisName + $scope.vis.params.valueAxes.reduce((value, axis) => {
if (axis.name.substr(0, axisName.length) === axisName) {
const num = parseInt(axis.name.substr(axisName.length));
if (num >= value) value = num + 1;
}
return value;
}, 1);
};
const lastAxisTitles = {};
$scope.updateAxisTitle = function () {
$scope.vis.params.valueAxes.forEach((axis, axisNumber) => {
let label = '';
const isFirst = axisNumber === 0;
const matchingSeries = [];
$scope.vis.params.seriesParams.forEach((series, i) => {
const isMatchingSeries = (isFirst && !series.valueAxis) || (series.valueAxis === axis.id);
if (isMatchingSeries) {
let seriesNumber = 0;
$scope.vis.aggs.forEach(agg => {
if (agg.schema.name === 'metric') {
if (seriesNumber === i) matchingSeries.push(agg);
seriesNumber++;
}
});
}
});
if (matchingSeries.length === 1) {
label = matchingSeries[0].makeLabel();
}
if (lastAxisTitles[axis.id] !== label) {
lastAxisTitles[axis.id] = label;
axis.title.text = label;
}
});
};
$scope.$watch(() => {
return $scope.vis.aggs.map(agg => {
try {
return agg.makeLabel();
} catch (e) {
return '';
}
}).join();
}, () => {
$scope.updateAxisTitle();
});
}
};
});

View file

@ -0,0 +1,111 @@
import angular from 'angular';
import _ from 'lodash';
import expect from 'expect.js';
import ngMock from 'ng_mock';
import $ from 'jquery';
import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
import LineVisTypeProvider from 'plugins/kbn_vislib_vis_types/line';
import VisProvider from 'ui/vis';
import AggConfigProvider from 'ui/vis/agg_config';
describe('point series editor', function () {
let $parentScope;
let $scope;
let $container;
let $elem;
let lineVisType;
let Vis;
let indexPattern;
let AggConfig;
function makeConfig() {
return {
type: 'line',
params: lineVisType.params.defaults,
aggs: [
{ type: 'count', schema: 'metric', params: { field: 'bytes' } },
{ type: 'terms', schema: 'segment', params: { field: 'machine.os' } },
],
listeners: { click: _.noop }
};
}
beforeEach(ngMock.module('kibana'));
beforeEach(ngMock.inject(function ($rootScope, $compile, Private) {
AggConfig = Private(AggConfigProvider);
lineVisType = Private(LineVisTypeProvider);
Vis = Private(VisProvider);
indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
$parentScope = $rootScope;
$parentScope.vis = new Vis(indexPattern, makeConfig());
$parentScope.savedVis = {};
// share the scope
//_.defaults($parentScope, $rootScope, Object.getPrototypeOf($rootScope));
$container = $(document.createElement('div'))
.appendTo('body');
// make the element
$elem = angular.element('<div><vislib-series></vislib-series><vislib-value-axes>' +
'</vislib-value-axes><vislib-category-axis></vislib-category-axis></div>');
$container.append($elem);
// compile the html
$compile($elem)($parentScope);
// Digest everything
$elem.scope().$digest();
// give us a scope to work with
$scope = $elem.isolateScope();
}));
afterEach(function () {
$container.remove();
});
it('should show correct series', function () {
expect($parentScope.vis.params.seriesParams.length).to.be(1);
expect($parentScope.vis.params.seriesParams[0].data.label).to.be('Count');
});
it('should update series when new agg is added', function () {
const aggConfig = new AggConfig($parentScope.vis, { type: 'avg', schema: 'metric', params: { field: 'bytes' } });
$parentScope.vis.aggs.push(aggConfig);
$parentScope.$digest();
expect($parentScope.vis.params.seriesParams.length).to.be(2);
});
it('should only allow left and right value axis position when category axis is horizontal', function () {
expect($parentScope.isPositionDisabled('top')).to.be(true);
expect($parentScope.isPositionDisabled('bottom')).to.be(true);
expect($parentScope.isPositionDisabled('left')).to.be(false);
expect($parentScope.isPositionDisabled('right')).to.be(false);
});
it('should only allow top and bottom value axis position when category axis is vertical', function () {
$parentScope.vis.params.categoryAxes[0].position = 'left';
$parentScope.$digest();
expect($parentScope.vis.params.valueAxes[0].position).to.be('bottom');
expect($parentScope.isPositionDisabled('top')).to.be(false);
expect($parentScope.isPositionDisabled('bottom')).to.be(false);
expect($parentScope.isPositionDisabled('left')).to.be(true);
expect($parentScope.isPositionDisabled('right')).to.be(true);
});
it('should add value axis', function () {
$parentScope.addValueAxis();
expect($parentScope.vis.params.valueAxes.length).to.be(2);
});
it('should remove value axis', function () {
$parentScope.addValueAxis();
$parentScope.removeValueAxis({ id: 'ValueAxis-2' });
expect($parentScope.vis.params.valueAxes.length).to.be(1);
});
it('should not allow to remove the last value axis', function () {
$parentScope.removeValueAxis({ id: 'ValueAxis-1' });
expect($parentScope.vis.params.valueAxes.length).to.be(1);
});
});

View file

@ -1,11 +0,0 @@
<!-- vis type specific options -->
<div>
<label>
Chart Mode
<select class="form-control" ng-model="vis.params.mode"
ng-options="mode for mode in vis.type.params.modes"></select>
</label>
</div>
<line-interpolation-option></line-interpolation-option>
<point-series-options></point-series-options>
<vislib-basic-options></vislib-basic-options>

View file

@ -1,13 +0,0 @@
<!-- vis type specific options -->
<div class="vis-option-item form-group">
<label>
Bar Mode
</label>
<select class="form-control" ng-model="vis.params.mode" ng-options="mode for mode in vis.type.params.modes"></select>
<label>
Y-Axis Scale
</label>
<select class="form-control" ng-model="vis.params.scale" ng-options="mode for mode in vis.type.params.scales"></select>
</div>
<point-series-options></point-series-options>
<vislib-basic-options></vislib-basic-options>

View file

@ -1,23 +0,0 @@
<div>
<label>
Y-Axis Scale
</label>
<select class="form-control" ng-model="vis.params.scale" ng-options="mode for mode in vis.type.params.scales"></select>
<!-- vis type specific options -->
<line-interpolation-option></line-interpolation-option>
<div class="vis-option-item">
<div>
<label>
<input type="checkbox" value="{{drawLinesBetweenPoints}}" ng-disabled="!vis.params.showCircles" ng-model="vis.params.drawLinesBetweenPoints" name="drawLinesBetweenPoints" ng-checked="vis.params.drawLinesBetweenPoints">
Show Connecting Lines
</label>
</div>
<div>
<label>
<input type="checkbox" value="{{showCircles}}" ng-disabled="!vis.params.drawLinesBetweenPoints" ng-model="vis.params.showCircles" name="showCircles" ng-checked="vis.params.showCircles">
Show Circles
</label>
</div>
</div>
<point-series-options></point-series-options>
<vislib-basic-options></vislib-basic-options>

View file

@ -0,0 +1,57 @@
<div>
<!-- Global settings -->
<div class="kuiSideBarSection">
<div class="kuiSideBarSectionTitle">
<div class="kuiSideBarSectionTitle__text">
Settings
</div>
</div>
<div class="kuiSideBarFormRow">
<label class="kuiSideBarFormRow__label" for="legendPosition">
Legend Position
</label>
<div class="kuiSideBarFormRow__control">
<select
id="legendPosition"
class="kuiSelect kuiSideBarSelect"
ng-model="vis.params.legendPosition"
ng-options="position for position in ['top', 'left', 'right', 'bottom']"
></select>
</div>
</div>
<div class="kuiSideBarFormRow">
<label class="kuiSideBarFormRow__label" for="showCursor">
Show Tooltip
</label>
<div class="kuiSideBarFormRow__control">
<input class="kuiCheckBox" id="showCursor" type="checkbox" ng-model="vis.params.addTooltip">
</div>
</div>
<div class="kuiSideBarFormRow" ng-show="vis.hasSchemaAgg('segment', 'date_histogram')">
<label class="kuiSideBarFormRow__label" for="currentTimeMarker">
Current Time Marker
</label>
<div class="kuiSideBarFormRow__control">
<input class="kuiCheckBox" id="currentTimeMarker" type="checkbox"
ng-model="vis.params.addTimeMarker"
ng-checked="vis.params.addTimeMarker"
>
</div>
</div>
<div class="kuiSideBarFormRow" ng-show="!vis.hasSchemaAgg('segment', 'date_histogram')">
<label class="kuiSideBarFormRow__label" for="orderBuckets">
Order Buckets by Sum
</label>
<div class="kuiSideBarFormRow__control">
<input class="kuiCheckBox" id="orderBuckets" type="checkbox" ng-model="vis.params.orderBucketsBySum">
</div>
</div>
<vislib-grid></vislib-grid>
</div>
</div>

View file

@ -1,8 +1,8 @@
import VislibVisTypeVislibVisTypeProvider from 'ui/vislib_vis_type/vislib_vis_type';
import VisSchemasProvider from 'ui/vis/schemas';
import histogramTemplate from 'plugins/kbn_vislib_vis_types/editors/histogram.html';
import pointSeriesTemplate from 'plugins/kbn_vislib_vis_types/editors/point_series.html';
export default function HistogramVisType(Private) {
export default function PointSeriesVisType(Private) {
const VislibVisType = Private(VislibVisTypeVislibVisTypeProvider);
const Schemas = Private(VisSchemasProvider);
@ -14,45 +14,121 @@ export default function HistogramVisType(Private) {
'exact numbers or percentages. If you are not sure which chart you need, you could do worse than to start here.',
params: {
defaults: {
grid: {
categoryLines: false,
style: {
color: '#eee'
}
},
categoryAxes: [
{
id: 'CategoryAxis-1',
type: 'category',
position: 'bottom',
show: true,
style: {
},
scale: {
type: 'linear'
},
labels: {
show: true,
truncate: 100
},
title: {}
}
],
valueAxes: [
{
id: 'ValueAxis-1',
name: 'LeftAxis-1',
type: 'value',
position: 'left',
show: true,
style: {
},
scale: {
type: 'linear',
mode: 'normal'
},
labels: {
show: true,
rotate: 0,
filter: false,
truncate: 100
},
title: {}
}
],
seriesParams: [
{
show: 'true',
type: 'histogram',
mode: 'stacked',
data: {
label: 'Count'
},
drawLinesBetweenPoints: true,
showCircles: true
}
],
addTooltip: true,
addLegend: true,
legendPosition: 'right',
showCircles: true,
interpolate: 'linear',
scale: 'linear',
mode: 'stacked',
drawLinesBetweenPoints: true,
radiusRatio: 9,
times: [],
addTimeMarker: false,
defaultYExtents: false,
setYExtents: false
},
legendPositions: [{
value: 'left',
text: 'left',
positions: ['top', 'left', 'right', 'bottom'],
chartTypes: ['line', 'area', 'histogram'],
axisModes: ['normal', 'percentage', 'wiggle', 'silhouette'],
scaleTypes: ['linear', 'log', 'square root'],
chartModes: ['normal', 'stacked'],
interpolationModes: [{
value: 'linear',
text: 'straight',
}, {
value: 'right',
text: 'right',
value: 'cardinal',
text: 'smoothed',
}, {
value: 'top',
text: 'top',
}, {
value: 'bottom',
text: 'bottom',
value: 'step-after',
text: 'stepped',
}],
scales: ['linear', 'log', 'square root'],
modes: ['stacked', 'percentage', 'grouped'],
editor: histogramTemplate
editor: pointSeriesTemplate,
optionTabs: [
{
name: 'advanced',
title: 'Metrics & Axes',
editor: '<div><vislib-series></vislib-series><vislib-value-axes>' +
'</vislib-value-axes><vislib-category-axis></vislib-category-axis></div>'
},
{ name: 'options', title: 'Panel Settings', editor: pointSeriesTemplate },
],
},
implementsRenderComplete: true,
schemas: new Schemas([
{
group: 'metrics',
name: 'metric',
title: 'Y-Axis',
min: 1,
aggFilter: '!std_dev',
defaults: [
{ schema: 'metric', type: 'count' }
]
},
{
group: 'metrics',
name: 'radius',
title: 'Dot Size',
min: 0,
max: 1,
aggFilter: ['count', 'avg', 'sum', 'min', 'max', 'cardinality']
},
{
group: 'buckets',
name: 'segment',
@ -64,7 +140,7 @@ export default function HistogramVisType(Private) {
{
group: 'buckets',
name: 'group',
title: 'Split Bars',
title: 'Split Series',
min: 0,
max: 1,
aggFilter: '!geohash_grid'

View file

@ -0,0 +1,157 @@
import VislibVisTypeVislibVisTypeProvider from 'ui/vislib_vis_type/vislib_vis_type';
import VisSchemasProvider from 'ui/vis/schemas';
import pointSeriesTemplate from 'plugins/kbn_vislib_vis_types/editors/point_series.html';
export default function PointSeriesVisType(Private) {
const VislibVisType = Private(VislibVisTypeVislibVisTypeProvider);
const Schemas = Private(VisSchemasProvider);
return new VislibVisType({
name: 'horizontal_bar',
title: 'Horizontal bar chart',
icon: 'fa-bars',
description: 'Like histogram chart but with horizontal bars.',
params: {
defaults: {
grid: {
categoryLines: false,
style: {
color: '#eee'
}
},
categoryAxes: [
{
id: 'CategoryAxis-1',
type: 'category',
position: 'left',
show: true,
style: {
},
scale: {
type: 'linear'
},
labels: {
show: true,
rotate: 0,
filter: false,
truncate: 200
},
title: {}
}
],
valueAxes: [
{
id: 'ValueAxis-1',
name: 'LeftAxis-1',
type: 'value',
position: 'bottom',
show: true,
style: {
},
scale: {
type: 'linear',
mode: 'normal'
},
labels: {
show: true,
rotate: 75,
filter: true,
truncate: 100
},
title: {}
}
],
seriesParams: [{
show: true,
type: 'histogram',
mode: 'normal',
data: {
label: 'Count'
},
drawLinesBetweenPoints: true,
showCircles: true
}],
addTooltip: true,
addLegend: true,
legendPosition: 'right',
showCircles: true,
interpolate: 'linear',
scale: 'linear',
drawLinesBetweenPoints: true,
radiusRatio: 9,
times: [],
addTimeMarker: false,
defaultYExtents: false,
setYExtents: false
},
positions: ['top', 'left', 'right', 'bottom'],
chartTypes: ['line', 'area', 'histogram'],
axisModes: ['normal', 'percentage', 'wiggle', 'silhouette'],
scaleTypes: ['linear', 'log', 'square root'],
chartModes: ['normal', 'stacked'],
interpolationModes: [{
value: 'linear',
text: 'straight',
}, {
value: 'cardinal',
text: 'smoothed',
}, {
value: 'step-after',
text: 'stepped',
}],
editor: pointSeriesTemplate,
optionTabs: [
{
name: 'advanced',
title: 'Metrics & Axes',
editor: '<div><vislib-series></vislib-series><vislib-value-axes>' +
'</vislib-value-axes><vislib-category-axis></vislib-category-axis></div>'
},
{ name: 'options', title: 'Panel Settings', editor: pointSeriesTemplate },
],
},
schemas: new Schemas([
{
group: 'metrics',
name: 'metric',
title: 'Y-Axis',
min: 1,
defaults: [
{ schema: 'metric', type: 'count' }
]
},
{
group: 'metrics',
name: 'radius',
title: 'Dot Size',
min: 0,
max: 1,
aggFilter: ['count', 'avg', 'sum', 'min', 'max', 'cardinality']
},
{
group: 'buckets',
name: 'segment',
title: 'X-Axis',
min: 0,
max: 1,
aggFilter: '!geohash_grid'
},
{
group: 'buckets',
name: 'group',
title: 'Split Series',
min: 0,
max: 1,
aggFilter: '!geohash_grid'
},
{
group: 'buckets',
name: 'split',
title: 'Split Chart',
min: 0,
max: 1,
aggFilter: '!geohash_grid'
}
])
});
}

View file

@ -6,6 +6,7 @@ import pieVisTypeProvider from 'plugins/kbn_vislib_vis_types/pie';
import areaVisTypeProvider from 'plugins/kbn_vislib_vis_types/area';
import tileMapVisTypeProvider from 'plugins/kbn_vislib_vis_types/tile_map';
import heatmapVisTypeProvider from 'plugins/kbn_vislib_vis_types/heatmap';
import horizontalBarVisTypeProvider from 'plugins/kbn_vislib_vis_types/horizontal_bar';
visTypes.register(histogramVisTypeProvider);
visTypes.register(lineVisTypeProvider);
@ -13,3 +14,4 @@ visTypes.register(pieVisTypeProvider);
visTypes.register(areaVisTypeProvider);
visTypes.register(tileMapVisTypeProvider);
visTypes.register(heatmapVisTypeProvider);
visTypes.register(horizontalBarVisTypeProvider);

View file

@ -1,8 +1,8 @@
import VislibVisTypeVislibVisTypeProvider from 'ui/vislib_vis_type/vislib_vis_type';
import VisSchemasProvider from 'ui/vis/schemas';
import lineTemplate from 'plugins/kbn_vislib_vis_types/editors/line.html';
import pointSeriesTemplate from 'plugins/kbn_vislib_vis_types/editors/point_series.html';
export default function HistogramVisType(Private) {
export default function PointSeriesVisType(Private) {
const VislibVisType = Private(VislibVisTypeVislibVisTypeProvider);
const Schemas = Private(VisSchemasProvider);
@ -11,9 +11,56 @@ export default function HistogramVisType(Private) {
title: 'Line chart',
icon: 'fa-line-chart',
description: 'Often the best chart for high density time series. Great for comparing one series to another. ' +
'Be careful with sparse sets as the connection between points can be misleading.',
'Be careful with sparse sets as the connection between points can be misleading.',
params: {
defaults: {
grid: {
categoryLines: false,
style: {
color: '#eee'
}
},
categoryAxes: [
{
id: 'CategoryAxis-1',
type: 'category',
position: 'bottom',
show: true,
style: {
},
scale: {
type: 'linear'
},
labels: {
show: true,
truncate: 100
},
title: {}
}
],
valueAxes: [
{
id: 'ValueAxis-1',
name: 'LeftAxis-1',
type: 'value',
position: 'left',
show: true,
style: {
},
scale: {
type: 'linear',
mode: 'normal'
},
labels: {
show: true,
rotate: 0,
filter: false,
truncate: 100
},
title: {}
}
],
seriesParams: [],
addTooltip: true,
addLegend: true,
legendPosition: 'right',
@ -27,19 +74,11 @@ export default function HistogramVisType(Private) {
defaultYExtents: false,
setYExtents: false
},
legendPositions: [{
value: 'left',
text: 'left',
}, {
value: 'right',
text: 'right',
}, {
value: 'top',
text: 'top',
}, {
value: 'bottom',
text: 'bottom',
}],
positions: ['top', 'left', 'right', 'bottom'],
chartTypes: ['line', 'area', 'histogram'],
axisModes: ['normal', 'percentage', 'wiggle', 'silhouette'],
scaleTypes: ['linear', 'log', 'square root'],
chartModes: ['normal', 'stacked'],
interpolationModes: [{
value: 'linear',
text: 'straight',
@ -50,10 +89,17 @@ export default function HistogramVisType(Private) {
value: 'step-after',
text: 'stepped',
}],
scales: ['linear', 'log', 'square root'],
editor: lineTemplate
editor: pointSeriesTemplate,
optionTabs: [
{
name: 'advanced',
title: 'Metrics & Axes',
editor: '<div><vislib-series></vislib-series><vislib-value-axes>' +
'</vislib-value-axes><vislib-category-axis></vislib-category-axis></div>'
},
{ name: 'options', title: 'Panel Settings', editor: pointSeriesTemplate },
],
},
implementsRenderComplete: true,
schemas: new Schemas([
{
group: 'metrics',
@ -83,7 +129,7 @@ export default function HistogramVisType(Private) {
{
group: 'buckets',
name: 'group',
title: 'Split Lines',
title: 'Split Series',
min: 0,
max: 1,
aggFilter: '!geohash_grid'

View file

@ -75,7 +75,7 @@
class="vis-editor-full-options"></vis-editor-vis-options>
<div class="collapsible-sidebar" ng-if="!vis.type.fullEditor && chrome.getVisible()" >
<vis-editor-sidebar class="vis-editor-sidebar"></vis-editor-sidebar>
<vis-editor-sidebar vis="editableVis" class="vis-editor-sidebar"></vis-editor-sidebar>
</div>
<div class="vis-editor-canvas" ng-if="!vis.type.fullEditor" ng-class="{ embedded: !chrome.getVisible() }">

View file

@ -293,6 +293,7 @@ function VisEditor($scope, $route, timefilter, AppState, $window, kbnUrl, courie
$scope.doSave = function () {
// vis.title was not bound and it's needed to reflect title into visState
$state.vis.title = savedVis.title;
$state.vis.type = savedVis.type || $state.vis.type;
savedVis.visState = $state.vis;
savedVis.uiStateJSON = angular.toJson($scope.uiState.getChanges());

View file

@ -1,7 +1,7 @@
<div class="sidebar-container">
<form
class="sidebar-list"
ng-submit="visualizeEditor.$invalid ? dontApply() : stageEditableVis()"
ng-submit="visualizeEditor.$invalid ? stageEditableVis(false) : stageEditableVis()"
name="visualizeEditor"
novalidate
><!-- see http://goo.gl/9kgz5w -->
@ -24,13 +24,13 @@
Data
</a>
</li>
<li ng-class="{active: sidebar.section == 'options'}">
<li ng-repeat="tab in vis.type.params.optionTabs" ng-class="{active: sidebar.section == tab.name}">
<a
class="vis-editor-subnav-link"
ng-class="{'is-vis-editor-sub-nav-link-selected': sidebar.section == 'options'}"
ng-click="sidebar.section='options'"
ng-class="{'is-vis-editor-sub-nav-link-selected': sidebar.section == tab.name}"
ng-click="sidebar.section=tab.name"
>
Options
{{tab.title}}
</a>
</li>
</ul>
@ -86,11 +86,9 @@
<vis-editor-agg-group ng-if="vis.type.schemas.buckets" group-name="buckets"></vis-editor-agg-group>
</div>
<div class="vis-editor-config" ng-show="sidebar.section == 'options'">
<!-- vis options -->
<vis-editor-vis-options vis="vis" saved-vis="savedVis" ui-state="uiState"></vis-editor-vis-options>
<div class="vis-editor-config" ng-repeat="tab in vis.type.params.optionTabs" ng-show="sidebar.section == tab.name">
<vis-editor-vis-options vis="vis" saved-vis="savedVis" ui-state="uiState" editor="tab.editor"></vis-editor-vis-options>
</div>
</form>
</div>

View file

@ -13,10 +13,11 @@ uiModules
vis: '=',
savedVis: '=',
uiState: '=',
editor: '='
},
link: function ($scope, $el) {
const $optionContainer = $el.find('.visualization-options');
const $editor = $compile($scope.vis.type.params.editor)($scope);
const $editor = $compile($scope.editor)($scope);
$optionContainer.append($editor);
$scope.$watch('vis.type.schemas.all.length', function (len) {

View file

@ -13,7 +13,7 @@ describe('addToSiri', function () {
const series = new Map();
const point = {};
const id = 'id';
addToSiri(series, point, id);
addToSiri(series, point, id, id, { id: id });
expect(series.has(id)).to.be(true);
expect(series.get(id)).to.be.an('object');
@ -27,10 +27,10 @@ describe('addToSiri', function () {
const id = 'id';
const point = {};
addToSiri(series, point, id);
addToSiri(series, point, id, id, { id: id });
const point2 = {};
addToSiri(series, point2, id);
addToSiri(series, point2, id, id, { id: id });
expect(series.has(id)).to.be(true);
expect(series.get(id)).to.be.an('object');
@ -45,7 +45,7 @@ describe('addToSiri', function () {
const id = 'id';
const label = 'label';
const point = {};
addToSiri(series, point, id, label);
addToSiri(series, point, id, label, { id: id });
expect(series.has(id)).to.be(true);
expect(series.get(id)).to.be.an('object');

View file

@ -31,7 +31,7 @@ describe('getSeries', function () {
const chart = {
aspects: {
x: { i: 0 },
y: { i: 1, col: yCol },
y: { i: 1, col: yCol, agg: { id: 'id' } },
z: { i: 2 }
}
};
@ -119,7 +119,7 @@ describe('getSeries', function () {
aspects: {
x: { i: -1 },
series: { i: 0, agg: agg },
y: { i: 1, col: { title: '0' } }
y: { i: 1, col: { title: '0' }, agg: agg }
}
};

View file

@ -1,5 +1,5 @@
export default function PointSeriesAddToSiri() {
return function addToSiri(series, point, id, label) {
return function addToSiri(series, point, id, label, agg) {
id = id == null ? '' : id + '';
if (series.has(id)) {
@ -9,6 +9,9 @@ export default function PointSeriesAddToSiri() {
series.set(id, {
label: label == null ? id : label,
aggLabel: agg.type ? agg.type.makeLabel(agg) : label,
aggId: agg.parentId ? agg.parentId : agg.id,
count: 0,
values: [point]
});
};

View file

@ -15,7 +15,7 @@ export default function PointSeriesGetSeries(Private) {
.transform(function (series, row) {
if (!multiY) {
const point = partGetPoint(row, aspects.y, aspects.z);
if (point) addToSiri(series, point, point.series);
if (point) addToSiri(series, point, point.series, point.series, aspects.y.agg);
return;
}
@ -35,7 +35,7 @@ export default function PointSeriesGetSeries(Private) {
seriesLabel = prefix + seriesLabel;
}
addToSiri(series, point, seriesId, seriesLabel);
addToSiri(series, point, seriesId, seriesLabel, y.agg);
});
}, new Map())

View file

@ -903,6 +903,7 @@ fieldset {
.kuiSideBarSelect {
// TODO: @include kuiSelect styles when this is moved to the UI Framework and we're using SASS.
height: 24px;
width: 100%;
font-size: 12px;
padding: 0 15px;
}
@ -923,6 +924,10 @@ fieldset {
margin-bottom: 6px;
}
.kuiSideBarSection__main {
margin-bottom: 25px;
}
.kuiSideBarSectionTitle {
display: flex;
justify-content: space-between;
@ -931,6 +936,11 @@ fieldset {
border-bottom: 1px solid #D4D4D4;
}
.kuiSideBarSection__main .kuiSideBarSectionTitle {
background-color: #E4E4E4;
padding: 2px 10px;
}
.kuiSideBarSectionTitle__text {
font-size: 14px;
font-weight: 700;

View file

@ -92,7 +92,6 @@ describe('Vis Class', function () {
expect(vis).to.have.property('params');
expect(vis.params).to.have.property('addLegend', true);
expect(vis.params).to.have.property('addTooltip', true);
expect(vis.params).to.have.property('mode', 'stacked');
});
});

View file

@ -17,6 +17,12 @@ export default function VisTypeFactory(Private) {
this.requiresSearch = opts.requiresSearch == null ? true : opts.requiresSearch; // Default to true unless otherwise specified
this.fullEditor = opts.fullEditor == null ? false : opts.fullEditor;
this.implementsRenderComplete = opts.implementsRenderComplete || false;
if (!this.params.optionTabs) {
this.params.optionTabs = [
{ name: 'options', title: 'Options', editor: this.params.editor }
];
}
}
VisType.prototype.createRenderbot = function (vis, $el, uiState) {

View file

@ -0,0 +1,228 @@
import d3 from 'd3';
import _ from 'lodash';
import ngMock from 'ng_mock';
import expect from 'expect.js';
import $ from 'jquery';
import VislibLibDataProvider from 'ui/vislib/lib/data';
import 'ui/persisted_state';
import VislibLibAxisProvider from 'ui/vislib/lib/axis';
import VislibVisConfig from 'ui/vislib/lib/vis_config';
describe('Vislib Axis Class Test Suite', function () {
let Axis;
let Data;
let persistedState;
let yAxis;
let el;
let fixture;
let VisConfig;
let seriesData;
const data = {
hits: 621,
ordered: {
date: true,
interval: 30000,
max: 1408734982458,
min: 1408734082458
},
series: [
{
label: 'Count',
values: [
{
x: 1408734060000,
y: 8
},
{
x: 1408734090000,
y: 23
},
{
x: 1408734120000,
y: 30
},
{
x: 1408734130000,
y: 30
},
{
x: 1408734150000,
y: 28
}
]
},
{
label: 'Count2',
values: [
{
x: 1408734060000,
y: 8
},
{
x: 1408734090000,
y: 23
},
{
x: 1408734120000,
y: 30
},
{
x: 1408734140000,
y: 30
},
{
x: 1408734150000,
y: 28
}
]
}
],
xAxisFormatter: function (thing) {
return new Date(thing);
},
xAxisLabel: 'Date Histogram',
yAxisLabel: 'Count'
};
beforeEach(ngMock.module('kibana'));
beforeEach(ngMock.inject(function (Private, $injector) {
Data = Private(VislibLibDataProvider);
persistedState = new ($injector.get('PersistedState'))();
Axis = Private(VislibLibAxisProvider);
VisConfig = Private(VislibVisConfig);
el = d3.select('body').append('div')
.attr('class', 'x-axis-wrapper')
.style('height', '40px');
fixture = el.append('div')
.attr('class', 'x-axis-div');
const visConfig = new VisConfig({
type: 'histogram'
}, data, persistedState, $('.x-axis-div')[0]);
yAxis = new Axis(visConfig, {
type: 'value',
id: 'ValueAxis-1'
});
seriesData = data.series.map(series => {
return series.values;
});
}));
afterEach(function () {
fixture.remove();
el.remove();
});
describe('_stackNegAndPosVals Method', function () {
it('should correctly stack positive values', function () {
const expectedResult = [
{
x: 1408734060000,
y: 8,
y0: 8
},
{
x: 1408734090000,
y: 23,
y0: 23
},
{
x: 1408734120000,
y: 30,
y0: 30
},
{
x: 1408734140000,
y: 30,
y0: 0
},
{
x: 1408734150000,
y: 28,
y0: 28
}
];
const stackedData = yAxis._stackNegAndPosVals(seriesData);
expect(stackedData[1]).to.eql(expectedResult);
});
it('should correctly stack pos and neg values', function () {
const expectedResult = [
{
x: 1408734060000,
y: 8,
y0: 0
},
{
x: 1408734090000,
y: 23,
y0: 0
},
{
x: 1408734120000,
y: 30,
y0: 0
},
{
x: 1408734140000,
y: 30,
y0: 0
},
{
x: 1408734150000,
y: 28,
y0: 0
}
];
const dataClone = _.cloneDeep(seriesData);
dataClone[0].forEach(value => {
value.y = -value.y;
});
const stackedData = yAxis._stackNegAndPosVals(dataClone);
expect(stackedData[1]).to.eql(expectedResult);
});
it('should correctly stack mixed pos and neg values', function () {
const expectedResult = [
{
x: 1408734060000,
y: 8,
y0: 8
},
{
x: 1408734090000,
y: 23,
y0: 0
},
{
x: 1408734120000,
y: 30,
y0: 30
},
{
x: 1408734140000,
y: 30,
y0: 0
},
{
x: 1408734150000,
y: 28,
y0: 28
}
];
const dataClone = _.cloneDeep(seriesData);
dataClone[0].forEach((value, i) => {
if ((i % 2) === 1) value.y = -value.y;
});
const stackedData = yAxis._stackNegAndPosVals(dataClone);
expect(stackedData[1]).to.eql(expectedResult);
});
});
});

View file

@ -43,124 +43,21 @@ export default function AxisFactory(Private) {
const stackedMode = ['normal', 'grouped'].includes(this.axisConfig.get('scale.mode'));
if (stackedMode) {
this.stack.out((d, y0, y) => {
return this._stackNegAndPosVals(d, y0, y);
const self = this;
this.stack = this._stackNegAndPosVals;
}
}
_stackNegAndPosVals(data) {
const cache = {};
data.forEach(series => {
series.forEach(value => {
if (!cache[value.x]) cache[value.x] = [0, 0];
value.y0 = cache[value.x][value.y < 0 ? 0 : 1];
cache[value.x][value.y < 0 ? 0 : 1] += value.y;
});
}
}
/**
* Returns true for positive numbers
*/
_isPositive(num) {
return num >= 0;
}
/**
* Returns true for negative numbers
*/
_isNegative(num) {
return num < 0;
}
/**
* Adds two input values
*/
_addVals(a, b) {
return a + b;
}
/**
* Returns the results of the addition of numbers in a filtered array.
*/
_sumYs(arr, callback) {
const filteredArray = arr.filter(callback);
return (filteredArray.length) ? filteredArray.reduce(this._addVals) : 0;
}
/**
* Calculates the d.y0 value for stacked data in D3.
*/
_calcYZero(y, arr) {
if (y === 0 && this._lastY0) return this._sumYs(arr, this._lastY0 > 0 ? this._isPositive : this._isNegative);
if (y >= 0) return this._sumYs(arr, this._isPositive);
return this._sumYs(arr, this._isNegative);
}
_getCounts(i, j) {
const data = this.visConfig.data.chartData();
const dataLengths = {};
dataLengths.charts = data.length;
dataLengths.stacks = dataLengths.charts ? data[i].series.length : 0;
dataLengths.values = dataLengths.stacks ? data[i].series[j].values.length : 0;
return dataLengths;
}
_createCache() {
const cache = {
index: {
chart: 0,
stack: 0,
value: 0
},
yValsArr: []
};
cache.count = this._getCounts(cache.index.chart, cache.index.stack);
return cache;
}
/**
* Stacking function passed to the D3 Stack Layout `.out` API.
* See: https://github.com/mbostock/d3/wiki/Stack-Layout
* It is responsible for calculating the correct d.y0 value for
* mixed datasets containing both positive and negative values.
*/
_stackNegAndPosVals(d, y0, y) {
const data = this.visConfig.data.chartData();
// Storing counters and data characteristics needed to stack values properly
if (!this._cache) {
this._cache = this._createCache();
}
d.y0 = this._calcYZero(y, this._cache.yValsArr);
if (d.y0 > 0) this._lastY0 = 1;
if (d.y0 < 0) this._lastY0 = -1;
++this._cache.index.stack;
// last stack, or last value, reset the stack count and y value array
const lastStack = (this._cache.index.stack >= this._cache.count.stacks);
if (lastStack) {
this._cache.index.stack = 0;
++this._cache.index.value;
this._cache.yValsArr = [];
// still building the stack collection, push v value to array
} else if (y !== 0) {
this._cache.yValsArr.push(y);
}
// last value, prepare for the next chart, if one exists
const lastValue = (this._cache.index.value >= this._cache.count.values);
if (lastValue) {
this._cache.index.value = 0;
++this._cache.index.chart;
// no more charts, reset the queue and finish
if (this._cache.index.chart >= this._cache.count.charts) {
this._cache = this._createCache();
return;
}
// get stack and value count for next chart
const chartSeries = data[this._cache.index.chart].series;
this._cache.count.stacks = chartSeries.length;
this._cache.count.values = chartSeries.length ? chartSeries[this._cache.index.stack].values.length : 0;
}
});
return data;
}
render() {
@ -173,6 +70,7 @@ export default function AxisFactory(Private) {
const elSelector = this.axisConfig.get('elSelector');
const rootEl = this.axisConfig.get('rootEl');
$(rootEl).find(elSelector).find('svg').remove();
this.axisTitle.destroy();
}
getAxis(length) {
@ -271,13 +169,14 @@ export default function AxisFactory(Private) {
}
draw() {
let svg;
const self = this;
const config = this.axisConfig;
const style = config.get('style');
return function (selection) {
const n = selection[0].length;
if (config.get('show') && self.axisTitle) {
if (config.get('show') && self.axisTitle && ['left', 'top'].includes(config.get('position'))) {
self.axisTitle.render(selection);
}
selection.each(function () {
@ -292,7 +191,7 @@ export default function AxisFactory(Private) {
const axis = self.getAxis(length);
if (config.get('show')) {
const svg = div.append('svg')
svg = div.append('svg')
.attr('width', width)
.attr('height', height);
@ -313,9 +212,14 @@ export default function AxisFactory(Private) {
.style('stroke-opacity', style.opacity);
}
if (self.axisLabels) self.axisLabels.render(svg);
svg.call(self.adjustSize());
}
});
if (self.axisTitle && ['right', 'bottom'].includes(config.get('position'))) {
self.axisTitle.render(selection);
}
if (svg) svg.call(self.adjustSize());
};
}
}

View file

@ -42,7 +42,7 @@ export default function AxisConfigFactory() {
},
title: {
text: '',
elSelector: '.axis-wrapper-{pos} .axis-title'
elSelector: '.axis-wrapper-{pos} .axis-div',
}
};
@ -77,8 +77,11 @@ export default function AxisConfigFactory() {
const typeDefaults = axisConfigArgs.type === 'category' ? categoryDefaults : valueDefaults;
// _.defaultsDeep mutates axisConfigArgs nested values so we clone it first
const axisConfigArgsClone = _.cloneDeep(axisConfigArgs);
const isCategoryAxis = axisConfigArgsClone.type === 'category';
const isHorizontal = axisConfigArgsClone.position && ['top', 'bottom'].includes(axisConfigArgsClone.position);
_.merge(typeDefaults, isHorizontal || isCategoryAxis ? horizontalDefaults : verticalDefaults);
this._values = _.defaultsDeep({}, axisConfigArgsClone, typeDefaults, defaults);
_.merge(this._values, this.isHorizontal() ? horizontalDefaults : verticalDefaults);
this._values.elSelector = this._values.elSelector.replace('{pos}', this._values.position);
this._values.rootEl = chartConfig.get('el');

View file

@ -1,6 +1,5 @@
import d3 from 'd3';
import $ from 'jquery';
import _ from 'lodash';
export default function AxisLabelsFactory(Private) {
class AxisLabels {
constructor(axisConfig, scale) {
@ -19,29 +18,37 @@ export default function AxisLabelsFactory(Private) {
if (config.get('labels.rotate')) {
text
.style('text-anchor', function () {
return config.get('labels.rotateAnchor') === 'center' ? 'center' : 'end';
.style('text-anchor', function (d, i) {
const currentValue = $(this).css('text-anchor');
const rotateDeg = config.get('labels.rotate');
if (!rotateDeg) return currentValue;
else {
const position = config.get('position');
switch (position) {
case 'top': return 'end';
case 'bottom': return 'end';
default:
if (rotateDeg === 90 || rotateDeg === -90) return 'middle';
return currentValue;
}
}
})
.attr('dy', function () {
if (config.isHorizontal()) {
if (config.get('position') === 'top') return '-0.9em';
else return '0.3em';
}
return '0';
})
.attr('dx', function () {
return config.isHorizontal() ? '-0.9em' : '0';
return config.isHorizontal() ? '0.3em' : '0';
})
.attr('transform', function rotate(d, j) {
let rotateDeg = config.get('labels.rotate');
if (config.get('labels.rotateAnchor') === 'center') {
const position = config.get('position');
const rotateDeg = position === 'top' ? config.get('labels.rotate') : -config.get('labels.rotate');
if ($(this).css('text-anchor') === 'middle') {
const coord = text[0][j].getBBox();
const transX = ((coord.x) + (coord.width / 2));
const transY = ((coord.y) + (coord.height / 2));
return `rotate(${rotateDeg}, ${transX}, ${transY})`;
} else {
rotateDeg = config.get('position') === 'top' ? rotateDeg : -rotateDeg;
return `rotate(${rotateDeg})`;
const transX = this.attributes.x.nodeValue;
const transY = this.attributes.y.nodeValue;
return `rotate(${rotateDeg}, ${transX}, ${transY})`;
}
});
}

View file

@ -12,6 +12,10 @@ export default function AxisTitleFactory(Private) {
d3.select(this.axisConfig.get('rootEl')).selectAll(this.elSelector).call(this.draw());
}
destroy() {
$(this.axisConfig.get('rootEl')).find(this.elSelector).find('svg').remove();
}
draw() {
const config = this.axisConfig;
@ -23,6 +27,7 @@ export default function AxisTitleFactory(Private) {
const div = d3.select(el);
const width = $(el).width();
const height = $(el).height();
const titlePadding = 15;
const svg = div.append('svg')
.attr('width', width)
@ -31,9 +36,9 @@ export default function AxisTitleFactory(Private) {
const bbox = svg.append('text')
.attr('transform', function () {
if (config.isHorizontal()) {
return 'translate(' + width / 2 + ',11)';
return `translate(${width / 2},${titlePadding})`;
}
return 'translate(11,' + height / 2 + ') rotate(270)';
return `translate(${titlePadding},${height / 2}) rotate(270)`;
})
.attr('text-anchor', 'middle')
.text(config.get('title.text'))

View file

@ -0,0 +1,79 @@
import d3 from 'd3';
import _ from 'lodash';
export default function ChartTitleFactory(Private) {
const defaults = {
style: {
color: '#eee'
},
categoryLines: false,
valueAxis: undefined,
};
class ChartGrid {
constructor(handler, gridConfig) {
if (!gridConfig) return;
this._handler = handler;
this._values = _.defaultsDeep({}, gridConfig, defaults);
}
drawLine(svg, tick, axis, width, height) {
const isHorizontal = axis.axisConfig.isHorizontal();
const scale = axis.getScale();
svg.append('path')
.attr('d', () => {
const x0 = isHorizontal ? tick : 0;
const x1 = isHorizontal ? tick : width;
const y0 = !isHorizontal ? tick : 0;
const y1 = !isHorizontal ? tick : height;
const d3Line = d3.svg.line()
.x(d => isHorizontal ? scale(d[0]) : d[0])
.y(d => !isHorizontal ? scale(d[1]) : d[1]);
return d3Line([[x0, y0], [x1, y1]]);
})
.attr('fill', 'none')
.attr('stroke', this.get('style.color'))
.attr('stroke-width', 1);
}
drawCategoryLines(svg, width, height) {
const axis = this._handler.categoryAxes[0];
axis.getScale().ticks().forEach(tick => {
this.drawLine(svg, tick, axis, width, height);
});
}
drawValueLines(svg, width, height) {
const axis = this._handler.valueAxes.find(axis => axis.axisConfig.get('id') === this.get('valueAxis'));
axis.getScale().ticks().forEach(tick => {
this.drawLine(svg, tick, axis, width, height);
});
}
draw(width, height) {
const self = this;
return function (selection) {
if (!self._values) return;
selection.each(function () {
if (self.get('categoryLines')) self.drawCategoryLines(d3.select(this), width, height);
if (self.get('valueAxis', false)) self.drawValueLines(d3.select(this), width, height);
});
};
}
get(property, defaults) {
if (_.has(this._values, property) || typeof defaults !== 'undefined') {
return _.get(this._values, property, defaults);
} else {
throw new Error(`Accessing invalid config property: ${property}`);
return defaults;
}
}
set(property, value) {
return _.set(this._values, property, value);
}
}
return ChartGrid;
}

View file

@ -41,6 +41,8 @@ export default function DataFactory(Private) {
newData[key] = data[key].map(seri => {
return {
label: seri.label,
aggLabel: seri.aggLabel,
aggId: seri.aggId,
values: seri.values.map(val => {
const newVal = _.clone(val);
newVal.aggConfig = val.aggConfig;
@ -111,11 +113,7 @@ export default function DataFactory(Private) {
shouldBeStacked(seriesConfig) {
if (!seriesConfig) return false;
const isHistogram = (seriesConfig.type === 'histogram');
const isArea = (seriesConfig.type === 'area');
const stacked = (seriesConfig.mode === 'stacked');
return (isHistogram || isArea) && stacked;
return (seriesConfig.mode === 'stacked');
}
getStackedSeries(chartConfig, axis, series, first = false) {
@ -135,6 +133,7 @@ export default function DataFactory(Private) {
const id = axis.axisConfig.get('id');
stackedData[id] = this.getStackedSeries(chartConfig, axis, data, i === 0);
stackedData[id] = this.injectZeros(stackedData[id], handler.visConfig.get('orderBucketsBySum', false));
axis.axisConfig.set('stackedSeries', stackedData[id].length);
axis.stack(_.map(stackedData[id], 'values'));
});
return stackedData;

View file

@ -7,6 +7,7 @@ import VislibLibLayoutLayoutProvider from './layout/layout';
import VislibLibChartTitleProvider from './chart_title';
import VislibLibAlertsProvider from './alerts';
import VislibAxisProvider from './axis/axis';
import VislibGridProvider from './chart_grid';
import VislibVisualizationsVisTypesProvider from '../visualizations/vis_types';
export default function HandlerBaseClass(Private) {
@ -15,6 +16,7 @@ export default function HandlerBaseClass(Private) {
const ChartTitle = Private(VislibLibChartTitleProvider);
const Alerts = Private(VislibLibAlertsProvider);
const Axis = Private(VislibAxisProvider);
const Grid = Private(VislibGridProvider);
/**
* Handles building all the components of the visualization
@ -39,6 +41,7 @@ export default function HandlerBaseClass(Private) {
this.valueAxes = visConfig.get('valueAxes').map(axisArgs => new Axis(visConfig, axisArgs));
this.chartTitle = new ChartTitle(visConfig);
this.alerts = new Alerts(this, visConfig.get('alerts'));
this.grid = new Grid(this, visConfig.get('grid'));
if (visConfig.get('type') === 'point_series') {
this.data.stackData(this);

View file

@ -11,6 +11,7 @@ export default function TypeFactory(Private) {
*/
return {
histogram: pointSeries.column,
horizontal_bar: pointSeries.column,
line: pointSeries.line,
pie: Private(VislibLibTypesPieProvider),
area: pointSeries.area,

View file

@ -1,27 +1,49 @@
import _ from 'lodash';
import errors from 'ui/errors';
export default function ColumnHandler(Private) {
const createSeries = (cfg, series) => {
const stacked = ['stacked', 'percentage', 'wiggle', 'silhouette'].includes(cfg.mode);
let interpolate = cfg.interpolate;
const createSerieFromParams = (cfg, seri) => {
const matchingSeriParams = cfg.seriesParams ? cfg.seriesParams.find(seriConfig => {
return seri.aggLabel === seriConfig.data.label;
}) : null;
let interpolate = matchingSeriParams ? matchingSeriParams.interpolate : cfg.interpolate;
// for backward compatibility when loading URLs or configs we need to check smoothLines
if (cfg.smoothLines) interpolate = 'cardinal';
if (!matchingSeriParams) {
const stacked = ['stacked', 'percentage', 'wiggle', 'silhouette'].includes(cfg.mode);
return {
show: true,
type: cfg.type || 'line',
mode: stacked ? 'stacked' : 'normal',
interpolate: interpolate,
drawLinesBetweenPoints: cfg.drawLinesBetweenPoints,
showCircles: cfg.showCircles,
radiusRatio: cfg.radiusRatio,
data: seri
};
}
return {
show: matchingSeriParams.show,
type: matchingSeriParams.type,
mode: matchingSeriParams.mode,
interpolate: interpolate,
valueAxis: matchingSeriParams.valueAxis,
drawLinesBetweenPoints: matchingSeriParams.drawLinesBetweenPoints,
showCircles: matchingSeriParams.showCircles,
radiusRatio: matchingSeriParams.radiusRatio,
data: seri
};
};
const createSeries = (cfg, series) => {
return {
type: 'point_series',
series: _.map(series, (seri) => {
return {
show: true,
type: cfg.type || 'line',
mode: stacked ? 'stacked' : 'normal',
interpolate: interpolate,
drawLinesBetweenPoints: cfg.drawLinesBetweenPoints,
showCircles: cfg.showCircles,
radiusRatio: cfg.radiusRatio,
data: seri
};
return createSerieFromParams(cfg, seri);
})
};
};

View file

@ -15,7 +15,8 @@ export default function VisConfigFactory(Private) {
},
alerts: [],
categoryAxes: [],
valueAxes: []
valueAxes: [],
grid: {}
};

View file

@ -187,6 +187,10 @@
width: 100%;
}
.x-axis-div svg {
float: left; /* for some reason svg wont get positioned in top left corner of container div without this */
}
.axis-wrapper-top .axis-div svg {
margin-bottom: -5px;
}

View file

@ -28,7 +28,6 @@
/* SVG Element Default Styling */
rect {
stroke: none;
opacity: 1;
&:hover {
@ -50,6 +49,12 @@ circle {
opacity: 0.6;
}
.series > path,
.series > rect {
fill-opacity: 0.6;
stroke-opacity: 1;
}
.blur_shape {
opacity: 0.3 !important;
}

View file

@ -55,6 +55,14 @@ export default function PointSeriesFactory(Private) {
.attr('class', 'background');
}
addGrid(svg) {
const { width, height } = svg.node().getBBox();
return svg
.append('g')
.attr('class', 'grid')
.call(this.handler.grid.draw(width, height));
}
addClipPath(svg) {
const { width, height } = svg.node().getBBox();
const startX = 0;
@ -219,6 +227,7 @@ export default function PointSeriesFactory(Private) {
.attr('height', height);
self.addBackground(svg, width, height);
self.addGrid(svg);
self.addClipPath(svg);
self.addEvents(svg);
self.createEndZones(svg);
@ -227,7 +236,7 @@ export default function PointSeriesFactory(Private) {
self.series = [];
_.each(self.chartConfig.series, (seriArgs, i) => {
if (!seriArgs.show) return;
const SeriClass = seriTypes[seriArgs.type || self.handler.visConfig.get('chart.type')];
const SeriClass = seriTypes[seriArgs.type || self.handler.visConfig.get('chart.type')] || seriTypes.line;
const series = new SeriClass(self.handler, svg, data.series[i], seriArgs);
series.events = self.events;
svg.call(series.draw());

View file

@ -29,8 +29,8 @@ export default function PointSeriesProvider(Private) {
getGroupedCount() {
const stacks = [];
return this.baseChart.chartConfig.series.reduce(function (sum, series) {
const valueAxis = series.valueAxis;
return this.baseChart.chartConfig.series.reduce((sum, series) => {
const valueAxis = series.valueAxis || this.baseChart.handler.valueAxes[0].id;
const isStacked = series.mode === 'stacked';
const isHistogram = series.type === 'histogram';
if (!isHistogram) return sum;
@ -53,7 +53,7 @@ export default function PointSeriesProvider(Private) {
let i = 0;
const stacks = [];
for (const seri of this.baseChart.chartConfig.series) {
const valueAxis = seri.valueAxis;
const valueAxis = seri.valueAxis || this.baseChart.handler.valueAxes[0].id;
const isStacked = seri.mode === 'stacked';
if (!isStacked) {
if (seri.data === data) return i;

View file

@ -79,9 +79,8 @@ export default function AreaChartFactory(Private) {
// Append path
const path = layer.append('path')
.attr('data-label', data.label)
.style('fill', () => {
return color(data.label);
})
.style('fill', () => color(data.label))
.style('stroke', () => color(data.label))
.classed('overlap_area', function () {
return isOverlapping;
})

View file

@ -34,7 +34,7 @@ export default function ColumnChartFactory(Private) {
const isTooltip = this.handler.visConfig.get('tooltip.show');
const layer = svg.append('g')
.attr('class', 'series')
.attr('class', 'series histogram')
.attr('clip-path', 'url(#' + this.baseChart.clipPathId + ')');
const bars = layer.selectAll('rect')
@ -50,9 +50,8 @@ export default function ColumnChartFactory(Private) {
.enter()
.append('rect')
.attr('data-label', data.label)
.attr('fill', () => {
return color(data.label);
});
.attr('fill', () => color(data.label))
.attr('stroke', () => color(data.label));
self.updateBars(bars);
@ -117,7 +116,6 @@ export default function ColumnChartFactory(Private) {
if ((isHorizontal && d.y < 0) || (!isHorizontal && d.y > 0)) {
return yScale(d.y0);
}
/*if (!isHorizontal && d.y < 0) return yScale(d.y);*/
return yScale(d.y0 + d.y);
}

View file

@ -69,8 +69,9 @@ export default function LineChartFactory(Private) {
}
function cy(d) {
const y0 = d.y0 || 0;
const y = d.y || 0;
return yScale(y);
return yScale(y0 + y);
}
function cColor(d) {
@ -158,7 +159,8 @@ export default function LineChartFactory(Private) {
function cy(d) {
const y = d.y || 0;
return yScale(y);
const y0 = d.y0 || 0;
return yScale(y0 + y);
}
line.append('path')

View file

@ -4,6 +4,7 @@ import 'plugins/kbn_vislib_vis_types/controls/vislib_basic_options';
import 'plugins/kbn_vislib_vis_types/controls/point_series_options';
import 'plugins/kbn_vislib_vis_types/controls/line_interpolation_option';
import 'plugins/kbn_vislib_vis_types/controls/heatmap_options';
import 'plugins/kbn_vislib_vis_types/controls/point_series';
import VisSchemasProvider from 'ui/vis/schemas';
import VisVisTypeProvider from 'ui/vis/vis_type';
import AggResponsePointSeriesPointSeriesProvider from 'ui/agg_response/point_series/point_series';
@ -15,6 +16,44 @@ export default function VislibVisTypeFactory(Private) {
const pointSeries = Private(AggResponsePointSeriesPointSeriesProvider);
const VislibRenderbot = Private(VislibVisTypeVislibRenderbotProvider);
const updateParams = function (params) {
if (!params.seriesParams || !params.seriesParams.length) return;
const updateIfSet = (from, to, prop, func) => {
if (from[prop]) {
to[prop] = func ? func(from[prop]) : from[prop];
}
};
updateIfSet(params, params.seriesParams[0], 'drawLinesBetweenPoints');
updateIfSet(params, params.seriesParams[0], 'showCircles');
updateIfSet(params, params.seriesParams[0], 'radiusRatio');
updateIfSet(params, params.seriesParams[0], 'interpolate');
updateIfSet(params, params.seriesParams[0], 'type');
if (params.mode) {
const stacked = ['stacked', 'percentage', 'wiggle', 'silhouette'].includes(params.mode);
params.seriesParams[0].mode = stacked ? 'stacked' : 'normal';
const axisMode = ['stacked', 'overlap'].includes(params.mode) ? 'norlal' : params.mode;
params.valueAxes[0].scale.mode = axisMode;
delete params.mode;
}
if (params.smoothLines) {
params.seriesParams[0].interpolate = 'cardinal';
delete params.smoothLines;
}
updateIfSet(params, params.valueAxes[0].scale, 'setYExtents');
updateIfSet(params, params.valueAxes[0].scale, 'defaultYExtents');
if (params.scale) {
params.valueAxes[0].scale.type = params.scale;
delete params.scale;
}
updateIfSet(params, params.categoryAxes[0], 'expandLastBucket');
};
_.class(VislibVisType).inherits(VisType);
function VislibVisType(opts = {}) {
@ -28,6 +67,7 @@ export default function VislibVisTypeFactory(Private) {
}
VislibVisType.prototype.createRenderbot = function (vis, $el, uiState) {
updateParams(vis.params);
return new VislibRenderbot(vis, $el, uiState);
};

View file

@ -16,7 +16,7 @@ bdd.describe('visualize app', function describeIndexTests() {
bdd.describe('chart types', function indexPatternCreation() {
bdd.it('should show the correct chart types', function () {
const expectedChartTypes = [
'Area chart', 'Data table', 'Heatmap chart', 'Line chart', 'Markdown widget',
'Area chart', 'Data table', 'Heatmap chart', 'Horizontal bar chart', 'Line chart', 'Markdown widget',
'Metric', 'Pie chart', 'Tag cloud', 'Tile map', 'Timeseries', 'Vertical bar chart'
];
// find all the chart types and make sure there all there

View file

@ -52,7 +52,7 @@ bdd.describe('visualize app', function describeIndexTests() {
})
.then(function selectField() {
PageObjects.common.debug('Field = machine.ram');
return PageObjects.visualize.selectField('machine.ram');
return PageObjects.visualize.selectField('machine.ram', 'metrics');
})
.then(function clickGo() {
return PageObjects.visualize.clickGo();
@ -73,7 +73,7 @@ bdd.describe('visualize app', function describeIndexTests() {
return PageObjects.visualize.selectAggregation('Sum')
.then(function selectField() {
PageObjects.common.debug('Field = phpmemory');
return PageObjects.visualize.selectField('phpmemory');
return PageObjects.visualize.selectField('phpmemory', 'metrics');
})
.then(function clickGo() {
return PageObjects.visualize.clickGo();
@ -95,7 +95,7 @@ bdd.describe('visualize app', function describeIndexTests() {
return PageObjects.visualize.selectAggregation('Median')
.then(function selectField() {
PageObjects.common.debug('Field = bytes');
return PageObjects.visualize.selectField('bytes');
return PageObjects.visualize.selectField('bytes', 'metrics');
})
.then(function clickGo() {
return PageObjects.visualize.clickGo();
@ -117,7 +117,7 @@ bdd.describe('visualize app', function describeIndexTests() {
return PageObjects.visualize.selectAggregation('Min')
.then(function selectField() {
PageObjects.common.debug('Field = @timestamp');
return PageObjects.visualize.selectField('@timestamp');
return PageObjects.visualize.selectField('@timestamp', 'metrics');
})
.then(function clickGo() {
return PageObjects.visualize.clickGo();
@ -138,7 +138,7 @@ bdd.describe('visualize app', function describeIndexTests() {
return PageObjects.visualize.selectAggregation('Max')
.then(function selectField() {
PageObjects.common.debug('Field = relatedContent.article:modified_time');
return PageObjects.visualize.selectField('relatedContent.article:modified_time');
return PageObjects.visualize.selectField('relatedContent.article:modified_time', 'metrics');
})
.then(function clickGo() {
return PageObjects.visualize.clickGo();
@ -162,7 +162,7 @@ bdd.describe('visualize app', function describeIndexTests() {
return PageObjects.visualize.selectAggregation('Standard Deviation')
.then(function selectField() {
PageObjects.common.debug('Field = bytes');
return PageObjects.visualize.selectField('bytes');
return PageObjects.visualize.selectField('bytes', 'metrics');
})
.then(function clickGo() {
return PageObjects.visualize.clickGo();
@ -183,7 +183,7 @@ bdd.describe('visualize app', function describeIndexTests() {
return PageObjects.visualize.selectAggregation('Unique Count')
.then(function selectField() {
PageObjects.common.debug('Field = clientip');
return PageObjects.visualize.selectField('clientip');
return PageObjects.visualize.selectField('clientip', 'metrics');
})
.then(function clickGo() {
return PageObjects.visualize.clickGo();
@ -220,7 +220,7 @@ bdd.describe('visualize app', function describeIndexTests() {
return PageObjects.visualize.selectAggregation('Percentiles')
.then(function selectField() {
PageObjects.common.debug('Field = machine.ram');
return PageObjects.visualize.selectField('machine.ram');
return PageObjects.visualize.selectField('machine.ram', 'metrics');
})
.then(function clickGo() {
return PageObjects.visualize.clickGo();
@ -241,7 +241,7 @@ bdd.describe('visualize app', function describeIndexTests() {
return PageObjects.visualize.selectAggregation('Percentile Ranks')
.then(function selectField() {
PageObjects.common.debug('Field = bytes');
return PageObjects.visualize.selectField('memory');
return PageObjects.visualize.selectField('memory', 'metrics');
})
.then(function selectField() {
PageObjects.common.debug('Values = 99');

View file

@ -0,0 +1,192 @@
import expect from 'expect.js';
import {
bdd,
} from '../../../support';
import PageObjects from '../../../support/page_objects';
bdd.describe('visualize app', function describeIndexTests() {
bdd.before(function () {
const fromTime = '2015-09-19 06:31:44.000';
const toTime = '2015-09-23 18:31:44.000';
PageObjects.common.debug('navigateToApp visualize');
return PageObjects.common.navigateToUrl('visualize', 'new')
.then(function () {
PageObjects.common.debug('clickLineChart');
return PageObjects.visualize.clickLineChart();
})
.then(function clickNewSearch() {
return PageObjects.visualize.clickNewSearch();
})
.then(function setAbsoluteRange() {
PageObjects.common.debug('Set absolute time range from \"' + fromTime + '\" to \"' + toTime + '\"');
return PageObjects.header.setAbsoluteRange(fromTime, toTime);
})
.then(function clickBucket() {
PageObjects.common.debug('Bucket = X-Axis');
return PageObjects.visualize.clickBucket('X-Axis');
})
.then(function selectAggregation() {
PageObjects.common.debug('Aggregation = Date Histogram');
return PageObjects.visualize.selectAggregation('Date Histogram');
})
.then(function selectField() {
PageObjects.common.debug('Field = @timestamp');
return PageObjects.visualize.selectField('@timestamp');
})
// add another metrics
.then(function clickAddMetrics() {
PageObjects.common.debug('Add Metric');
return PageObjects.visualize.clickAddMetric();
})
.then(function () {
PageObjects.common.debug('Metric = Value Axis');
return PageObjects.visualize.clickBucket('Y-Axis');
})
.then(function selectAggregation() {
PageObjects.common.debug('Aggregation = Average');
return PageObjects.visualize.selectAggregation('Average');
})
.then(function selectField() {
PageObjects.common.debug('Field = memory');
return PageObjects.visualize.selectField('machine.ram', 'metrics');
})
// go to options page
.then(function gotoAxisOptions() {
PageObjects.common.debug('Going to axis options');
return PageObjects.visualizeOptions.clickAxisOptions();
})
// add another value axis
.then(function addAxis() {
PageObjects.common.debug('adding axis');
return PageObjects.visualizeOptions.clickAddAxis();
})
// set average count to use second value axis
.then(function setAxis() {
return PageObjects.visualizeOptions.toggleCollapsibleTitle('Average machine.ram')
.then(function () {
PageObjects.common.debug('Average memory value axis - ValueAxis-2');
return PageObjects.visualizeOptions.setSeriesAxis(1, 'ValueAxis-2');
});
})
.then(function clickGo() {
return PageObjects.visualize.clickGo();
})
.then(function () {
return PageObjects.header.isGlobalLoadingIndicatorHidden();
});
});
bdd.describe('secondary value axis', function () {
bdd.it('should show correct chart, take screenshot', function () {
const expectedChartValues = [
[ 37, 202, 740, 1437, 1371, 751, 188, 31, 42, 202, 683,
1361, 1415, 707, 177, 27, 32, 175, 707, 1408, 1355, 726, 201, 29 ],
[ 14018296036, 13284815935, 13198764883, 13093365683, 13067752146, 12976598848,
13561826081, 14339648875, 14011021362, 12775336396, 13304506791, 12988890398,
13143466970, 13244378772, 12154757448, 15907286281, 13757317120, 13022240959,
12807319386, 13375732998, 13190755620, 12627508458, 12731510199, 13153337344 ],
];
// Most recent failure on Jenkins usually indicates the bar chart is still being drawn?
// return arguments[0].getAttribute(arguments[1]);","args":[{"ELEMENT":"592"},"fill"]}] arguments[0].getAttribute is not a function
// try sleeping a bit before getting that data
return PageObjects.common.sleep(2000)
.then(function () {
return PageObjects.visualize.getLineChartData('fill="#6eadc1"');
})
.then(function showData(data) {
PageObjects.common.debug('count data=' + data);
PageObjects.common.debug('data.length=' + data.length);
PageObjects.common.saveScreenshot('Visualize-secondary-value-axis');
expect(data).to.eql(expectedChartValues[0]);
})
.then(function () {
return PageObjects.visualize.getLineChartData('fill="#57c17b"', 'ValueAxis-2');
})
.then(function showData(data) {
PageObjects.common.debug('average memory data=' + data);
PageObjects.common.debug('data.length=' + data.length);
expect(data).to.eql(expectedChartValues[1]);
});
});
bdd.it('should put secondary axis on the right', function () {
PageObjects.visualizeOptions.getRightValueAxes().then(length => {
expect(length).to.be(1);
});
});
});
bdd.describe('multiple chart types', function () {
bdd.it('should change average series type to histogram', function () {
return PageObjects.visualizeOptions.toggleCollapsibleTitle('RightAxis-1')
.then(function () {
return PageObjects.visualizeOptions.setSeriesType(1, 'histogram');
})
.then(function () {
return PageObjects.visualize.clickGo();
})
.then(function () {
return PageObjects.common.sleep(2000);
})
.then(function checkSeriesTypes() {
PageObjects.visualizeOptions.getHistogramSeries().then(length => {
expect(length).to.be(1);
});
});
});
});
bdd.describe('grid lines', function () {
bdd.before(function () {
return PageObjects.visualizeOptions.clickOptions();
});
bdd.it('should show category grid lines', function () {
return PageObjects.visualizeOptions.toggleGridCategoryLines()
.then(function () {
return PageObjects.visualize.clickGo();
})
.then(function () {
return PageObjects.common.sleep(2000);
})
.then(function () {
return PageObjects.visualizeOptions.getGridLines();
})
.then(function checkGridLines(gridLines) {
expect(gridLines.length).to.be(9);
gridLines.forEach(gridLine => {
expect(gridLine.y).to.be(0);
});
});
});
bdd.it('should show value axis grid lines', function () {
return PageObjects.visualizeOptions.setGridValueAxis('ValueAxis-2')
.then(function () {
return PageObjects.visualizeOptions.toggleGridCategoryLines();
})
.then(function () {
return PageObjects.visualize.clickGo();
})
.then(function () {
return PageObjects.common.sleep(5000);
})
.then(function () {
return PageObjects.visualizeOptions.getGridLines();
})
.then(function checkGridLines(gridLines) {
expect(gridLines.length).to.be(9);
gridLines.forEach(gridLine => {
expect(gridLine.x).to.be(0);
});
});
});
});
});

View file

@ -81,7 +81,7 @@ bdd.describe('visualize app', function describeIndexTests() {
// Most recent failure on Jenkins usually indicates the bar chart is still being drawn?
// return arguments[0].getAttribute(arguments[1]);","args":[{"ELEMENT":"592"},"fill"]}] arguments[0].getAttribute is not a function
// try sleeping a bit before getting that data
return PageObjects.common.sleep(5000)
return PageObjects.common.sleep(50000)
.then(function () {
return PageObjects.visualize.getBarChartData();
})

View file

@ -41,4 +41,5 @@ bdd.describe('visualize app', function () {
require('./_tile_map');
require('./_vertical_bar_chart');
require('./_heatmap_chart');
require('./_point_series_options');
});

View file

@ -7,6 +7,7 @@ import HeaderPage from './header_page';
import SettingsPage from './settings_page';
import ShieldPage from './shield_page';
import VisualizePage from './visualize_page';
import VisualizePointSeriesOptions from './visualize_point_series_options';
import MonitoringPage from './monitoring_page';
const common = new Common();
@ -17,6 +18,7 @@ const headerPage = new HeaderPage();
const settingsPage = new SettingsPage();
const shieldPage = new ShieldPage();
const visualizePage = new VisualizePage();
const visualizePointSeriesOptions = new VisualizePointSeriesOptions();
const monitoringPage = new MonitoringPage();
class PageObjects {
@ -37,6 +39,7 @@ class PageObjects {
settingsPage.init(remote);
shieldPage.init(remote);
visualizePage.init(remote);
visualizePointSeriesOptions.init(remote);
monitoringPage.init(remote);
}
@ -79,6 +82,10 @@ class PageObjects {
return this.assertInitialized() && visualizePage;
}
get visualizeOptions() {
return this.assertInitialized() && visualizePointSeriesOptions;
}
get monitoring() {
return this.assertInitialized() && monitoringPage;
}

View file

@ -9,6 +9,8 @@ export default class VisualizePage {
init(remote) {
this.remote = remote;
this.xAxisBucket = 'Category Axis';
this.yAxisBucket = 'Value Axis';
}
clickAreaChart() {
@ -39,6 +41,13 @@ export default class VisualizePage {
.click();
}
clickAddMetric() {
return this.remote
.setFindTimeout(defaultFindTimeout)
.findByCssSelector('[group-name="metrics"] .vis-editor-agg-add .vis-editor-agg-wide-btn div.btn')
.click();
}
clickMetric() {
return this.remote
.setFindTimeout(defaultFindTimeout)
@ -220,7 +229,7 @@ export default class VisualizePage {
selectAggregation(myString) {
return this.remote
.setFindTimeout(defaultFindTimeout)
.findByCssSelector('option[label="' + myString + '"]')
.findByCssSelector('vis-editor-agg-params:not(.ng-hide) option[label="' + myString + '"]')
.click();
}
@ -231,13 +240,13 @@ export default class VisualizePage {
.getVisibleText();
}
selectField(fieldValue) {
selectField(fieldValue, groupName = 'buckets') {
const self = this;
return PageObjects.common.try(function tryingForTime() {
return self.remote
.setFindTimeout(defaultFindTimeout)
// the css below should be more selective
.findByCssSelector('option[label="' + fieldValue + '"]')
.findByCssSelector(`[group-name="${groupName}"] option[label="${fieldValue}"]`)
.click();
});
}
@ -474,7 +483,7 @@ export default class VisualizePage {
// The current test shows dots, not a line. This function gets the dots and normalizes their height.
getLineChartData(cssPart) {
getLineChartData(cssPart, axis = 'ValueAxis-1') {
const self = this.remote;
let yAxisLabel = 0;
let yAxisHeight;
@ -482,10 +491,10 @@ export default class VisualizePage {
// 1). get the maximim chart Y-Axis marker value
return this.remote
.setFindTimeout(defaultFindTimeout)
.findByCssSelector('div.y-axis-div-wrapper > div > svg > g > g:last-of-type')
.findByCssSelector(`div.y-axis-div-wrapper > div > svg > g.${axis} > g:last-of-type`)
.getVisibleText()
.then(function (yLabel) {
yAxisLabel = yLabel.replace(',', '');
yAxisLabel = yLabel.replace(/,/g, '');
PageObjects.common.debug('yAxisLabel = ' + yAxisLabel);
return yLabel;
})
@ -505,18 +514,13 @@ export default class VisualizePage {
.then(function getChartWrapper() {
return self
.setFindTimeout(defaultFindTimeout * 2)
.findAllByCssSelector('.chart-wrapper')
.findAllByCssSelector(`.chart-wrapper circle[${cssPart}]`)
.then(function (chartTypes) {
// 5). for each chart element, find the green circle, then the cy position
function getChartType(chart) {
return chart
.findByCssSelector(`circle[${cssPart}]`)
.then(function (circleObject) {
// PageObjects.common.debug('circleObject = ' + circleObject + ' yAxisHeight= ' + yAxisHeight + ' yAxisLabel= ' + yAxisLabel);
return circleObject
.getAttribute('cy');
})
.getAttribute('cy')
.then(function (cy) {
// PageObjects.common.debug(' yAxisHeight=' + yAxisHeight + ' yAxisLabel=' + yAxisLabel + ' cy=' + cy +
// ' ((yAxisHeight - cy)/yAxisHeight * yAxisLabel)=' + ((yAxisHeight - cy) / yAxisHeight * yAxisLabel));

View file

@ -0,0 +1,171 @@
import {
defaultFindTimeout,
} from '../';
import PageObjects from './';
export default class VisualizePointSeriesOptions {
init(remote) {
this.remote = remote;
}
clickOptions() {
return this.remote
.setFindTimeout(defaultFindTimeout)
.findByPartialLinkText('Panel Settings')
.click();
}
clickAxisOptions() {
return this.remote
.setFindTimeout(defaultFindTimeout)
.findByPartialLinkText('Metrics & Axes')
.click();
}
clickAddAxis() {
return this.remote
.setFindTimeout(defaultFindTimeout)
.findByCssSelector('button[aria-label="Add value axis"]')
.click();
}
getValueAxesCount() {
return this.remote
.setFindTimeout(defaultFindTimeout)
.findAllByCssSelector('.kuiSideBarSection:contains("Value Axes") > .kuiSideBarSection')
.length;
}
getSeriesCount() {
return this.remote
.setFindTimeout(defaultFindTimeout)
.findAllByCssSelector('.kuiSideBarSection:contains("Series") > .kuiSideBarSection')
.length;
}
getRightValueAxes() {
return this.remote
.setFindTimeout(defaultFindTimeout)
.findAllByCssSelector('.axis-wrapper-right g.axis')
.then(function (data) {
return Promise.all([function () { return data.length; }]);
});
}
getHistogramSeries() {
return this.remote
.setFindTimeout(defaultFindTimeout)
.findAllByCssSelector('.series.histogram')
.then(function (data) {
return Promise.all([function () { return data.length; }]);
});
}
getGridLines() {
return this.remote
.setFindTimeout(defaultFindTimeout)
.findAllByCssSelector('g.grid > path')
.then(function (data) {
function getGridLine(gridLine) {
return gridLine
.getAttribute('d')
.then(dAttribute => {
const firstPoint = dAttribute.split('L')[0].replace('M', '').split(',');
return { x: parseFloat(firstPoint[0]), y: parseFloat(firstPoint[1]) };
});
}
const promises = data.map(getGridLine);
return Promise.all(promises);
})
.then(function (gridLines) {
return gridLines;
});
}
toggleGridCategoryLines() {
return this.remote
.setFindTimeout(defaultFindTimeout)
.findByCssSelector('#showCategoryLines')
.click();
}
setGridValueAxis(axis) {
return this.remote
.setFindTimeout(defaultFindTimeout)
.findByCssSelector(`select#gridAxis option[value="string:${axis}"]`)
.click();
}
toggleCollapsibleTitle(title) {
return this.remote
.setFindTimeout(defaultFindTimeout)
.findAllByCssSelector('.kuiSideBarCollapsibleTitle .kuiSideBarCollapsibleTitle__text')
.then(sidebarTitles => {
PageObjects.common.debug('found sidebar titles ' + sidebarTitles.length);
function getTitle(titleDiv) {
return titleDiv
.getVisibleText()
.then(titleString => {
PageObjects.common.debug('sidebar title ' + titleString);
if (titleString === title) {
PageObjects.common.debug('clicking sidebar title ' + titleString);
return titleDiv.click();
}
});
}
const sidebarTitlePromises = sidebarTitles.map(getTitle);
return Promise.all(sidebarTitlePromises);
});
}
setValue(newValue) {
return this.remote
.setFindTimeout(defaultFindTimeout * 2)
.findByCssSelector('button[ng-click="numberListCntr.add()"]')
.click()
.then(() => {
return this.remote
.setFindTimeout(defaultFindTimeout)
.findByCssSelector('input[ng-model="numberListCntr.getList()[$index]"]')
.clearValue();
})
.then(() => {
return this.remote
.setFindTimeout(defaultFindTimeout)
.findByCssSelector('input[ng-model="numberListCntr.getList()[$index]"]')
.type(newValue);
});
}
setValueAxisPosition(axis, position) {
return this.remote
.setFindTimeout(defaultFindTimeout)
.findByCssSelector(`select#valueAxisPosition${axis} option[label="${position}"]`)
.click();
}
setCategoryAxisPosition(newValue) {
return this.remote
.setFindTimeout(defaultFindTimeout)
.findByCssSelector(`select#categoryAxisPosition option[label="${newValue}"]`)
.click();
}
setSeriesAxis(series, axis) {
return this.remote
.setFindTimeout(defaultFindTimeout)
.findByCssSelector(`select#seriesValueAxis${series} option[value="${axis}"]`)
.click();
}
setSeriesType(series, type) {
return this.remote
.setFindTimeout(defaultFindTimeout)
.findByCssSelector(`select#seriesType${series} option[label="${type}"]`)
.click();
}
}