mirror of
https://github.com/grafana/grafana.git
synced 2026-08-19 01:34:54 -05:00
Merge branch 'master' into influxdb_alias_seriename
This commit is contained in:
+8
-2
@@ -1,4 +1,9 @@
|
|||||||
# 4.0-pre (unreleased)
|
# 4.0-beta2 (unrelased)
|
||||||
|
|
||||||
|
### Bugfixes
|
||||||
|
* **Graph Panel**: Bar width if bars was only used in series override, [#6528](https://github.com/grafana/grafana/issues/6528)
|
||||||
|
|
||||||
|
# 4.0-beta1 (2016-11-09)
|
||||||
|
|
||||||
### Enhancements
|
### Enhancements
|
||||||
* **Login**: Adds option to disable username/password logins, closes [#4674](https://github.com/grafana/grafana/issues/4674)
|
* **Login**: Adds option to disable username/password logins, closes [#4674](https://github.com/grafana/grafana/issues/4674)
|
||||||
@@ -18,12 +23,13 @@
|
|||||||
* **Background Tasks**: Now support automatic purging of old rendered images, closes [#2172](https://github.com/grafana/grafana/issues/2172)
|
* **Background Tasks**: Now support automatic purging of old rendered images, closes [#2172](https://github.com/grafana/grafana/issues/2172)
|
||||||
* **Dashboard**: After inactivity hide nav/row actions, fade to nice clean view, can be toggled with `d v`, also added kiosk mode, toggled via `d k` [#6476](https://github.com/grafana/grafana/issues/6476)
|
* **Dashboard**: After inactivity hide nav/row actions, fade to nice clean view, can be toggled with `d v`, also added kiosk mode, toggled via `d k` [#6476](https://github.com/grafana/grafana/issues/6476)
|
||||||
* **Dashboard**: Improved dashboard row menu & add panel UX [#6442](https://github.com/grafana/grafana/issues/6442)
|
* **Dashboard**: Improved dashboard row menu & add panel UX [#6442](https://github.com/grafana/grafana/issues/6442)
|
||||||
|
* **Graph Panel**: Support for stacking null values [#2912](https://github.com/grafana/grafana/issues/2912), [#6287](https://github.com/grafana/grafana/issues/6287), thanks @benrubson!
|
||||||
|
|
||||||
### Breaking changes
|
### Breaking changes
|
||||||
* **SystemD**: Change systemd description, closes [#5971](https://github.com/grafana/grafana/pull/5971)
|
* **SystemD**: Change systemd description, closes [#5971](https://github.com/grafana/grafana/pull/5971)
|
||||||
* **lodash upgrade**: Upgraded lodash from 2.4.2 to 4.15.0, this contains a number of breaking changes that could effect plugins. closes [#6021](https://github.com/grafana/grafana/pull/6021)
|
* **lodash upgrade**: Upgraded lodash from 2.4.2 to 4.15.0, this contains a number of breaking changes that could effect plugins. closes [#6021](https://github.com/grafana/grafana/pull/6021)
|
||||||
|
|
||||||
### Bugfixes
|
### Bug fixes
|
||||||
* **Table Panel**: Fixed problem when switching to Mixed datasource in metrics tab, fixes [#5999](https://github.com/grafana/grafana/pull/5999)
|
* **Table Panel**: Fixed problem when switching to Mixed datasource in metrics tab, fixes [#5999](https://github.com/grafana/grafana/pull/5999)
|
||||||
* **Playlist**: Fixed problem with play order not matching order defined in playlist, fixes [#5467](https://github.com/grafana/grafana/pull/5467)
|
* **Playlist**: Fixed problem with play order not matching order defined in playlist, fixes [#5467](https://github.com/grafana/grafana/pull/5467)
|
||||||
* **Graph panel**: Fixed problem with auto decimals on y axis when datamin=datamax, fixes [#6070](https://github.com/grafana/grafana/pull/6070)
|
* **Graph panel**: Fixed problem with auto decimals on y axis when datamin=datamax, fixes [#6070](https://github.com/grafana/grafana/pull/6070)
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@ machine:
|
|||||||
GOPATH: "/home/ubuntu/.go_workspace"
|
GOPATH: "/home/ubuntu/.go_workspace"
|
||||||
ORG_PATH: "github.com/grafana"
|
ORG_PATH: "github.com/grafana"
|
||||||
REPO_PATH: "${ORG_PATH}/grafana"
|
REPO_PATH: "${ORG_PATH}/grafana"
|
||||||
GODIST: "go1.7.1.linux-amd64.tar.gz"
|
GODIST: "go1.7.3.linux-amd64.tar.gz"
|
||||||
post:
|
post:
|
||||||
- mkdir -p download
|
- mkdir -p download
|
||||||
- test -e download/$GODIST || curl -o download/$GODIST https://storage.googleapis.com/golang/$GODIST
|
- test -e download/$GODIST || curl -o download/$GODIST https://storage.googleapis.com/golang/$GODIST
|
||||||
|
|||||||
@@ -25,31 +25,12 @@ to add and configure a `notification` object. This is done from the Alerting/Not
|
|||||||
On the notifications list page hit the `New Notification` button to go the the page where you
|
On the notifications list page hit the `New Notification` button to go the the page where you
|
||||||
can configure and setup a new notification.
|
can configure and setup a new notification.
|
||||||
|
|
||||||
You you specify name and type, and type specific options. You can also test the notification to make
|
You specify name and type, and type specific options. You can also test the notification to make
|
||||||
sure it's working and setup correctly.
|
sure it's working and setup correctly.
|
||||||
|
|
||||||
<!-- You can reach this page from the Alerting submenu or Alert List page header. -->
|
|
||||||
<!-- When you configure a notification you can have it be a global notifiations, meaning -->
|
|
||||||
<!-- it will be sent for all alerts within Grafana. This is useful to make sure you won’t miss to configure -->
|
|
||||||
<!-- notifications for an alert. You can find the alert notification page in the main menu under alerting. -->
|
|
||||||
<!-- -->
|
|
||||||
|
|
||||||
<!-- ## Add a notifications to an Alert -->
|
|
||||||
<!-- You can add and remove notifications from an alert by going to the `Notifications` sub menu in the alerting tab. -->
|
|
||||||
<!-- -->
|
|
||||||
<!-- -->
|
|
||||||
<!-- <img class="no-shadow" src="/img/docs/v4/alerttab_notifications_submenu.png"> -->
|
|
||||||
<!-- -->
|
|
||||||
<!-- -->
|
|
||||||
<!-- Click the `+` button to add a new notification and the `x` to remove. Notifications with a blue backgrounds are enabled by default for all alerts and cannot be modified from this view. -->
|
|
||||||
<!-- -->
|
|
||||||
<!-- -->
|
|
||||||
<!-- <img class="no-shadow" src="/img/docs/v4/add_remove_notifications.png"> -->
|
|
||||||
<!-- -->
|
|
||||||
|
|
||||||
### Send on all alerts
|
### Send on all alerts
|
||||||
|
|
||||||
This option will make this notification used for all alert rules, existing and new.
|
When checked this option will make this notification used for all alert rules, existing and new.
|
||||||
|
|
||||||
## Supported notification types
|
## Supported notification types
|
||||||
|
|
||||||
@@ -61,12 +42,25 @@ To enable email notification you have to setup [SMTP settings](/installation/con
|
|||||||
in the Grafana config. Email notification will upload an image of the alert graph to an
|
in the Grafana config. Email notification will upload an image of the alert graph to an
|
||||||
external image destination if available or fallback on attaching the image in the email.
|
external image destination if available or fallback on attaching the image in the email.
|
||||||
|
|
||||||
|
### Slack
|
||||||
|
|
||||||
|
{{< imgbox max-width="40%" img="/img/docs/v4/slack_notification.png" caption="Alerting Slack Notification" >}}
|
||||||
|
|
||||||
|
To set up slack you need to configure an incoming webhook url at slack. You can follow their guide for how
|
||||||
|
to do that https://api.slack.com/incoming-webhooks If you want to include screenshots of the firing alerts
|
||||||
|
in the slack messages you have to configure the [external image destination](#external-image-store) in Grafana.
|
||||||
|
|
||||||
|
Setting | Description
|
||||||
|
---------- | -----------
|
||||||
|
Recipient | allows you to override the slack recipient.
|
||||||
|
Mention | make it possible to include a mention in the slack notification sent by Grafana. Ex @here or @channel
|
||||||
|
|
||||||
### Webhook
|
### Webhook
|
||||||
|
|
||||||
The webhook notification is a simple way to send information about an state change over HTTP to a custom endpoint.
|
The webhook notification is a simple way to send information about an state change over HTTP to a custom endpoint.
|
||||||
Using this notification you could integrated Grafana into any system you choose, by yourself.
|
Using this notification you could integrated Grafana into any system you choose, by yourself.
|
||||||
|
|
||||||
Example json schema:
|
Example json body:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"title": "My alert",
|
"title": "My alert",
|
||||||
@@ -85,19 +79,6 @@ Example json schema:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Slack
|
|
||||||
|
|
||||||
{{< imgbox max-width="40%" img="/img/docs/v4/slack_notification.png" caption="Alerting Slack Notification" >}}
|
|
||||||
|
|
||||||
To set up slack you need to configure an incoming webhook url at slack. You can follow their guide for how
|
|
||||||
to do that https://api.slack.com/incoming-webhooks If you want to include screenshots of the firing alerts
|
|
||||||
in the slack messages you have to configure the [external image destination](#external-image-store) in Grafana.
|
|
||||||
|
|
||||||
Setting | Description
|
|
||||||
---------- | -----------
|
|
||||||
Recipient | allows you to override the slack recipient.
|
|
||||||
Mention | make it possible to include a mention in the slack notification sent by Grafana. Ex @here or @channel
|
|
||||||
|
|
||||||
### PagerDuty
|
### PagerDuty
|
||||||
|
|
||||||
To set up PagerDuty, all you have to do is to provide an api key.
|
To set up PagerDuty, all you have to do is to provide an api key.
|
||||||
@@ -120,7 +101,3 @@ config file.
|
|||||||
This is not an optional requirement, you can get slack and email notifications without setting this up.
|
This is not an optional requirement, you can get slack and email notifications without setting this up.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
+++
|
+++
|
||||||
title = "Alerting Engine Rules Guide"
|
title = "Alerting Engine & Rules Guide"
|
||||||
description = "Configuring Alert Rules"
|
description = "Configuring Alert Rules"
|
||||||
keywords = ["grafana", "alerting", "guide", "rules"]
|
keywords = ["grafana", "alerting", "guide", "rules"]
|
||||||
type = "docs"
|
type = "docs"
|
||||||
@@ -73,7 +73,7 @@ in the scenario below.
|
|||||||
- No new notifications are sent as the alert rule is already in state `Alerting`.
|
- No new notifications are sent as the alert rule is already in state `Alerting`.
|
||||||
|
|
||||||
So as you can see from the above scenario Grafana will not send out notifications when other series cause the alert
|
So as you can see from the above scenario Grafana will not send out notifications when other series cause the alert
|
||||||
to fire if the rule already is in state ´Alerting`. To improve support for queries that return multiple series
|
to fire if the rule already is in state `Alerting`. To improve support for queries that return multiple series
|
||||||
we plan to track state **per series** in a future release.
|
we plan to track state **per series** in a future release.
|
||||||
|
|
||||||
### No Data / Null values
|
### No Data / Null values
|
||||||
@@ -107,6 +107,12 @@ The message can contain anything, information about how you might solve the issu
|
|||||||
The actual notifications are configured and shared between multiple alerts. Read the
|
The actual notifications are configured and shared between multiple alerts. Read the
|
||||||
[Notifications]({{< relref "notifications.md" >}}) guide for how to configure and setup notifications.
|
[Notifications]({{< relref "notifications.md" >}}) guide for how to configure and setup notifications.
|
||||||
|
|
||||||
|
## Alert State History & Annotations
|
||||||
|
|
||||||
|
Alert state changes are recorded in the internal annotation table in Grafana's database. The state changes
|
||||||
|
are visualized as annotations in the alert rule's graph panel. You can also go into the `State history`
|
||||||
|
submenu in the alert tab to view & clear state history.
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
{{< imgbox max-width="40%" img="/img/docs/v4/alert_test_rule.png" caption="Test Rule" >}}
|
{{< imgbox max-width="40%" img="/img/docs/v4/alert_test_rule.png" caption="Test Rule" >}}
|
||||||
|
|||||||
@@ -14,13 +14,22 @@ weight = 1
|
|||||||
|
|
||||||
Description | Download
|
Description | Download
|
||||||
------------ | -------------
|
------------ | -------------
|
||||||
Stable .deb for Debian-based Linux | [3.1.1 (x86-64 deb)](https://grafanarel.s3.amazonaws.com/builds/grafana_3.1.1-1470047149_amd64.deb)
|
Stable for Debian-based Linux | [3.1.1 (x86-64 deb)](https://grafanarel.s3.amazonaws.com/builds/grafana_3.1.1-1470047149_amd64.deb)
|
||||||
|
Latest Beta for Debian-based Linux | [4.0.0-beta1 (x86-64 deb)](https://grafanarel.s3.amazonaws.com/builds/grafana_4.0.0-1478693311beta1_amd64.deb)
|
||||||
|
|
||||||
## Install Stable
|
## Install Stable
|
||||||
|
|
||||||
$ wget https://grafanarel.s3.amazonaws.com/builds/grafana_3.1.1-1470047149_amd64.deb
|
```
|
||||||
|
$ wget https://grafanarel.s3.amazonaws.com/builds/grafana_3.1.1-1470047149_amd64.deb
|
||||||
|
$ sudo apt-get install -y adduser libfontconfig
|
||||||
|
$ sudo dpkg -i grafana_3.1.1-1470047149_amd64.deb
|
||||||
|
```
|
||||||
|
|
||||||
|
## Install Latest Beta
|
||||||
|
|
||||||
|
$ wget https://grafanarel.s3.amazonaws.com/builds/grafana_4.0.0-1478693311beta1_amd64.deb
|
||||||
$ sudo apt-get install -y adduser libfontconfig
|
$ sudo apt-get install -y adduser libfontconfig
|
||||||
$ sudo dpkg -i grafana_3.1.1-1470047149_amd64.deb
|
$ sudo dpkg -i grafana_4.0.0-1478693311beta1_amd64.deb
|
||||||
|
|
||||||
## APT Repository
|
## APT Repository
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ weight = 2
|
|||||||
|
|
||||||
Description | Download
|
Description | Download
|
||||||
------------ | -------------
|
------------ | -------------
|
||||||
Stable .RPM for CentOS / Fedora / OpenSuse / Redhat Linux | [3.1.1 (x86-64 rpm)](https://grafanarel.s3.amazonaws.com/builds/grafana-3.1.1-1470047149.x86_64.rpm)
|
Stable for CentOS / Fedora / OpenSuse / Redhat Linux | [3.1.1 (x86-64 rpm)](https://grafanarel.s3.amazonaws.com/builds/grafana-3.1.1-1470047149.x86_64.rpm)
|
||||||
|
Latest Beta for CentOS / Fedora / OpenSuse / Redhat Linux | [4.0.0-beta1 (x86-64 rpm)](https://grafanarel.s3.amazonaws.com/builds/grafana-4.0.0-1478693311beta1.x86_64.rpm)
|
||||||
|
|
||||||
## Install Stable
|
## Install Stable
|
||||||
|
|
||||||
@@ -33,6 +34,21 @@ Or install manually using `rpm`.
|
|||||||
|
|
||||||
$ sudo rpm -i --nodeps grafana-3.1.1-1470047149.x86_64.rpm
|
$ sudo rpm -i --nodeps grafana-3.1.1-1470047149.x86_64.rpm
|
||||||
|
|
||||||
|
## Or Install Latest Beta
|
||||||
|
|
||||||
|
$ sudo yum install https://grafanarel.s3.amazonaws.com/builds/grafana-4.0.0-1478693311beta1.x86_64.rpm
|
||||||
|
|
||||||
|
Or install manually using `rpm`.
|
||||||
|
|
||||||
|
#### On CentOS / Fedora / Redhat:
|
||||||
|
|
||||||
|
$ sudo yum install initscripts fontconfig
|
||||||
|
$ sudo rpm -Uvh grafana-4.0.0-1478693311beta1.x86_64.rpm
|
||||||
|
|
||||||
|
#### On OpenSuse:
|
||||||
|
|
||||||
|
$ sudo rpm -i --nodeps grafana-4.0.0-1478693311beta1.x86_64.rpm
|
||||||
|
|
||||||
## Install via YUM Repository
|
## Install via YUM Repository
|
||||||
|
|
||||||
Add the following to a new file at `/etc/yum.repos.d/grafana.repo`
|
Add the following to a new file at `/etc/yum.repos.d/grafana.repo`
|
||||||
|
|||||||
@@ -13,7 +13,9 @@ weight = 3
|
|||||||
|
|
||||||
Description | Download
|
Description | Download
|
||||||
------------ | -------------
|
------------ | -------------
|
||||||
Stable Zip package for Windows | [grafana.3.1.1.windows-x64.zip](https://grafanarel.s3.amazonaws.com/winbuilds/dist/grafana-3.1.1.windows-x64.zip)
|
Latest stable package for Windows | [grafana.3.1.1.windows-x64.zip](https://grafanarel.s3.amazonaws.com/winbuilds/dist/grafana-3.1.1.windows-x64.zip)
|
||||||
|
Latest beta package for Windows | [grafana.4.0.0-beta1.windows-x64.zip](https://grafanarel.s3.amazonaws.com/builds/grafana-4.0.0-beta1.windows-x64.zip)
|
||||||
|
|
||||||
|
|
||||||
## Configure
|
## Configure
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
"company": "Coding Instinct AB"
|
"company": "Coding Instinct AB"
|
||||||
},
|
},
|
||||||
"name": "grafana",
|
"name": "grafana",
|
||||||
"version": "4.0.0-pre1",
|
"version": "4.0.0-beta1",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "http://github.com/grafana/grafana.git"
|
"url": "http://github.com/grafana/grafana.git"
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ func (e *InfluxDBExecutor) Execute(ctx context.Context, queries tsdb.QuerySlice,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if setting.Env == setting.DEV {
|
if setting.Env == setting.DEV {
|
||||||
glog.Debug("Influxdb query", "raw query", query)
|
glog.Debug("Influxdb query", "raw query", rawQuery)
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err := e.createRequest(rawQuery)
|
req, err := e.createRequest(rawQuery)
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ type InfluxdbQueryParser struct{}
|
|||||||
func (qp *InfluxdbQueryParser) Parse(model *simplejson.Json, dsInfo *tsdb.DataSourceInfo) (*Query, error) {
|
func (qp *InfluxdbQueryParser) Parse(model *simplejson.Json, dsInfo *tsdb.DataSourceInfo) (*Query, error) {
|
||||||
policy := model.Get("policy").MustString("default")
|
policy := model.Get("policy").MustString("default")
|
||||||
rawQuery := model.Get("query").MustString("")
|
rawQuery := model.Get("query").MustString("")
|
||||||
interval := model.Get("interval").MustString("")
|
|
||||||
alias := model.Get("alias").MustString("")
|
alias := model.Get("alias").MustString("")
|
||||||
|
|
||||||
measurement := model.Get("measurement").MustString("")
|
measurement := model.Get("measurement").MustString("")
|
||||||
@@ -37,7 +36,8 @@ func (qp *InfluxdbQueryParser) Parse(model *simplejson.Json, dsInfo *tsdb.DataSo
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if interval == "" {
|
interval := model.Get("interval").MustString("")
|
||||||
|
if interval == "" && dsInfo.JsonData != nil {
|
||||||
dsInterval := dsInfo.JsonData.Get("timeInterval").MustString("")
|
dsInterval := dsInfo.JsonData.Get("timeInterval").MustString("")
|
||||||
if dsInterval != "" {
|
if dsInterval != "" {
|
||||||
interval = dsInterval
|
interval = dsInterval
|
||||||
|
|||||||
@@ -5,9 +5,15 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"regexp"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/tsdb"
|
"github.com/grafana/grafana/pkg/tsdb"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
regexpOperatorPattern *regexp.Regexp = regexp.MustCompile(`^\/.*\/$`)
|
||||||
|
)
|
||||||
|
|
||||||
type QueryBuilder struct{}
|
type QueryBuilder struct{}
|
||||||
|
|
||||||
func (qb *QueryBuilder) Build(query *Query, queryContext *tsdb.QueryContext) (string, error) {
|
func (qb *QueryBuilder) Build(query *Query, queryContext *tsdb.QueryContext) (string, error) {
|
||||||
@@ -43,18 +49,28 @@ func (qb *QueryBuilder) renderTags(query *Query) []string {
|
|||||||
str += " "
|
str += " "
|
||||||
}
|
}
|
||||||
|
|
||||||
value := tag.Value
|
//If the operator is missing we fall back to sensible defaults
|
||||||
nValue, err := strconv.ParseFloat(tag.Value, 64)
|
if tag.Operator == "" {
|
||||||
|
if regexpOperatorPattern.Match([]byte(tag.Value)) {
|
||||||
if tag.Operator == "=~" || tag.Operator == "!~" {
|
tag.Operator = "=~"
|
||||||
value = fmt.Sprintf("%s", value)
|
} else {
|
||||||
} else if err == nil {
|
tag.Operator = "="
|
||||||
value = fmt.Sprintf("%v", nValue)
|
}
|
||||||
} else {
|
|
||||||
value = fmt.Sprintf("'%s'", value)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
res = append(res, fmt.Sprintf(`%s"%s" %s %s`, str, tag.Key, tag.Operator, value))
|
textValue := ""
|
||||||
|
numericValue, err := strconv.ParseFloat(tag.Value, 64)
|
||||||
|
|
||||||
|
// quote value unless regex or number
|
||||||
|
if tag.Operator == "=~" || tag.Operator == "!~" {
|
||||||
|
textValue = tag.Value
|
||||||
|
} else if err == nil {
|
||||||
|
textValue = fmt.Sprintf("%v", numericValue)
|
||||||
|
} else {
|
||||||
|
textValue = fmt.Sprintf("'%s'", tag.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
res = append(res, fmt.Sprintf(`%s"%s" %s %s`, str, tag.Key, tag.Operator, textValue))
|
||||||
}
|
}
|
||||||
|
|
||||||
return res
|
return res
|
||||||
|
|||||||
@@ -86,16 +86,34 @@ func TestInfluxdbQueryBuilder(t *testing.T) {
|
|||||||
So(rawQuery, ShouldEqual, `Raw query`)
|
So(rawQuery, ShouldEqual, `Raw query`)
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("can render regex tags", func() {
|
Convey("can render normal tags without operator", func() {
|
||||||
query := &Query{Tags: []*Tag{&Tag{Operator: "=~", Value: "value", Key: "key"}}}
|
query := &Query{Tags: []*Tag{&Tag{Operator: "", Value: `value`, Key: "key"}}}
|
||||||
|
|
||||||
So(strings.Join(builder.renderTags(query), ""), ShouldEqual, `"key" =~ value`)
|
So(strings.Join(builder.renderTags(query), ""), ShouldEqual, `"key" = 'value'`)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("can render regex tags without operator", func() {
|
||||||
|
query := &Query{Tags: []*Tag{&Tag{Operator: "", Value: `/value/`, Key: "key"}}}
|
||||||
|
|
||||||
|
So(strings.Join(builder.renderTags(query), ""), ShouldEqual, `"key" =~ /value/`)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("can render regex tags", func() {
|
||||||
|
query := &Query{Tags: []*Tag{&Tag{Operator: "=~", Value: `/value/`, Key: "key"}}}
|
||||||
|
|
||||||
|
So(strings.Join(builder.renderTags(query), ""), ShouldEqual, `"key" =~ /value/`)
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("can render number tags", func() {
|
Convey("can render number tags", func() {
|
||||||
query := &Query{Tags: []*Tag{&Tag{Operator: "=", Value: "1", Key: "key"}}}
|
query := &Query{Tags: []*Tag{&Tag{Operator: "=", Value: "10001", Key: "key"}}}
|
||||||
|
|
||||||
So(strings.Join(builder.renderTags(query), ""), ShouldEqual, `"key" = 1`)
|
So(strings.Join(builder.renderTags(query), ""), ShouldEqual, `"key" = 10001`)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("can render number tags with decimals", func() {
|
||||||
|
query := &Query{Tags: []*Tag{&Tag{Operator: "=", Value: "10001.1", Key: "key"}}}
|
||||||
|
|
||||||
|
So(strings.Join(builder.renderTags(query), ""), ShouldEqual, `"key" = 10001.1`)
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("can render string tags", func() {
|
Convey("can render string tags", func() {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
<p class="small" style="position: absolute; top: 48px; right: 10px">
|
<p class="small" style="position: absolute; top: 48px; right: 10px">
|
||||||
<span class="shortcut-table-key">mod</span> =
|
<span class="shortcut-table-key">mod</span> =
|
||||||
<span class="muted">CTRL on windows, CMD key on Mac</span>
|
<span class="muted">CTRL on windows or linux and CMD key on Mac</span>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div ng-repeat="(category, shortcuts) in ctrl.shortcuts" class="shortcut-category">
|
<div ng-repeat="(category, shortcuts) in ctrl.shortcuts" class="shortcut-category">
|
||||||
|
|||||||
@@ -102,6 +102,7 @@ export default class TimeSeries {
|
|||||||
this.stats.min = Number.MAX_VALUE;
|
this.stats.min = Number.MAX_VALUE;
|
||||||
this.stats.avg = null;
|
this.stats.avg = null;
|
||||||
this.stats.current = null;
|
this.stats.current = null;
|
||||||
|
this.stats.timeStep = Number.MAX_VALUE;
|
||||||
this.allIsNull = true;
|
this.allIsNull = true;
|
||||||
this.allIsZero = true;
|
this.allIsZero = true;
|
||||||
|
|
||||||
@@ -110,11 +111,22 @@ export default class TimeSeries {
|
|||||||
var currentTime;
|
var currentTime;
|
||||||
var currentValue;
|
var currentValue;
|
||||||
var nonNulls = 0;
|
var nonNulls = 0;
|
||||||
|
var previousTime;
|
||||||
|
|
||||||
for (var i = 0; i < this.datapoints.length; i++) {
|
for (var i = 0; i < this.datapoints.length; i++) {
|
||||||
currentValue = this.datapoints[i][0];
|
currentValue = this.datapoints[i][0];
|
||||||
currentTime = this.datapoints[i][1];
|
currentTime = this.datapoints[i][1];
|
||||||
|
|
||||||
|
// Due to missing values we could have different timeStep all along the series
|
||||||
|
// so we have to find the minimum one (could occur with aggregators such as ZimSum)
|
||||||
|
if (previousTime !== undefined) {
|
||||||
|
let timeStep = currentTime - previousTime;
|
||||||
|
if (timeStep < this.stats.timeStep) {
|
||||||
|
this.stats.timeStep = timeStep;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
previousTime = currentTime;
|
||||||
|
|
||||||
if (currentValue === null) {
|
if (currentValue === null) {
|
||||||
if (ignoreNulls) { continue; }
|
if (ignoreNulls) { continue; }
|
||||||
if (nullAsZero) {
|
if (nullAsZero) {
|
||||||
@@ -145,10 +157,6 @@ export default class TimeSeries {
|
|||||||
result.push([currentTime, currentValue]);
|
result.push([currentTime, currentValue]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.datapoints.length >= 2) {
|
|
||||||
this.stats.timeStep = this.datapoints[1][1] - this.datapoints[0][1];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.stats.max === -Number.MAX_VALUE) { this.stats.max = null; }
|
if (this.stats.max === -Number.MAX_VALUE) { this.stats.max = null; }
|
||||||
if (this.stats.min === Number.MAX_VALUE) { this.stats.min = null; }
|
if (this.stats.min === Number.MAX_VALUE) { this.stats.min = null; }
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,39 @@
|
|||||||
///<reference path="../../../headers/common.d.ts" />
|
///<reference path="../../../headers/common.d.ts" />
|
||||||
|
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
class GrafanaDatasource {
|
class GrafanaDatasource {
|
||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor(private backendSrv) {}
|
constructor(private backendSrv) {}
|
||||||
|
|
||||||
query(options) {
|
query(options) {
|
||||||
return this.backendSrv.get('/api/metrics/test', {
|
return this.backendSrv.post('/api/tsdb/query', {
|
||||||
from: options.range.from.valueOf(),
|
from: options.range.from.valueOf().toString(),
|
||||||
to: options.range.to.valueOf(),
|
to: options.range.to.valueOf().toString(),
|
||||||
scenario: 'random_walk',
|
queries: [
|
||||||
interval: options.intervalMs,
|
{
|
||||||
maxDataPoints: options.maxDataPoints
|
"refId": "A",
|
||||||
|
"scenarioId": "random_walk",
|
||||||
|
"intervalMs": options.intervalMs,
|
||||||
|
"maxDataPoints": options.maxDataPoints,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}).then(res => {
|
||||||
|
|
||||||
|
var data = [];
|
||||||
|
if (res.results) {
|
||||||
|
_.forEach(res.results, queryRes => {
|
||||||
|
for (let series of queryRes.series) {
|
||||||
|
data.push({
|
||||||
|
target: series.name,
|
||||||
|
datapoints: series.points
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {data: data};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -183,6 +183,24 @@ module.directive('grafanaGraph', function($rootScope, timeSrv) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Series could have different timeSteps,
|
||||||
|
// let's find the smallest one so that bars are correctly rendered.
|
||||||
|
function getMinTimeStepOfSeries(data) {
|
||||||
|
var min = Number.MAX_VALUE;
|
||||||
|
|
||||||
|
for (let i = 0; i < data.length; i++) {
|
||||||
|
if (!data[i].stats.timeStep) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data[i].stats.timeStep < min) {
|
||||||
|
min = data[i].stats.timeStep;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return min;
|
||||||
|
}
|
||||||
|
|
||||||
// Function for rendering panel
|
// Function for rendering panel
|
||||||
function render_panel() {
|
function render_panel() {
|
||||||
panelWidth = elem.width();
|
panelWidth = elem.width();
|
||||||
@@ -279,9 +297,7 @@ module.directive('grafanaGraph', function($rootScope, timeSrv) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
if (data.length && data[0].stats.timeStep) {
|
options.series.bars.barWidth = getMinTimeStepOfSeries(data) / 1.5;
|
||||||
options.series.bars.barWidth = data[0].stats.timeStep / 1.5;
|
|
||||||
}
|
|
||||||
addTimeAxis(options);
|
addTimeAxis(options);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ define([
|
|||||||
'jquery',
|
'jquery',
|
||||||
'lodash'
|
'lodash'
|
||||||
],
|
],
|
||||||
function ($, _) {
|
function ($) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
function GraphTooltip(elem, dashboard, scope, getSeriesFn) {
|
function GraphTooltip(elem, dashboard, scope, getSeriesFn) {
|
||||||
@@ -21,7 +21,10 @@ function ($, _) {
|
|||||||
var initial = last*ps;
|
var initial = last*ps;
|
||||||
var len = series.datapoints.points.length;
|
var len = series.datapoints.points.length;
|
||||||
for (var j = initial; j < len; j += ps) {
|
for (var j = initial; j < len; j += ps) {
|
||||||
if (series.datapoints.points[j] > posX) {
|
// Special case of a non stepped line, highlight the very last point just before a null point
|
||||||
|
if ((series.datapoints.points[initial] != null && series.datapoints.points[j] == null && ! series.lines.steps)
|
||||||
|
//normal case
|
||||||
|
|| series.datapoints.points[j] > posX) {
|
||||||
return Math.max(j - ps, 0)/ps;
|
return Math.max(j - ps, 0)/ps;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,23 +54,35 @@ function ($, _) {
|
|||||||
//now we know the current X (j) position for X and Y values
|
//now we know the current X (j) position for X and Y values
|
||||||
var last_value = 0; //needed for stacked values
|
var last_value = 0; //needed for stacked values
|
||||||
|
|
||||||
|
var minDistance, minTime;
|
||||||
|
|
||||||
for (i = 0; i < seriesList.length; i++) {
|
for (i = 0; i < seriesList.length; i++) {
|
||||||
series = seriesList[i];
|
series = seriesList[i];
|
||||||
|
|
||||||
if (!series.data.length || (panel.legend.hideEmpty && series.allIsNull)) {
|
if (!series.data.length || (panel.legend.hideEmpty && series.allIsNull)) {
|
||||||
|
// Init value & yaxis so that it does not brake series sorting
|
||||||
results.push({ hidden: true, value: 0, yaxis: 0 });
|
results.push({ hidden: true, value: 0, yaxis: 0 });
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!series.data.length || (panel.legend.hideZero && series.allIsZero)) {
|
if (!series.data.length || (panel.legend.hideZero && series.allIsZero)) {
|
||||||
|
// Init value & yaxis so that it does not brake series sorting
|
||||||
results.push({ hidden: true, value: 0, yaxis: 0 });
|
results.push({ hidden: true, value: 0, yaxis: 0 });
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
hoverIndex = this.findHoverIndexFromData(pos.x, series);
|
hoverIndex = this.findHoverIndexFromData(pos.x, series);
|
||||||
hoverDistance = Math.abs(pos.x - series.data[hoverIndex][0]);
|
hoverDistance = pos.x - series.data[hoverIndex][0];
|
||||||
pointTime = series.data[hoverIndex][0];
|
pointTime = series.data[hoverIndex][0];
|
||||||
|
|
||||||
|
// Take the closest point before the cursor, or if it does not exist, the closest after
|
||||||
|
if (! minDistance
|
||||||
|
|| (hoverDistance >=0 && (hoverDistance < minDistance || minDistance < 0))
|
||||||
|
|| (hoverDistance < 0 && hoverDistance > minDistance)) {
|
||||||
|
minDistance = hoverDistance;
|
||||||
|
minTime = pointTime;
|
||||||
|
}
|
||||||
|
|
||||||
if (series.stack) {
|
if (series.stack) {
|
||||||
if (panel.tooltip.value_type === 'individual') {
|
if (panel.tooltip.value_type === 'individual') {
|
||||||
value = series.data[hoverIndex][1];
|
value = series.data[hoverIndex][1];
|
||||||
@@ -89,6 +104,7 @@ function ($, _) {
|
|||||||
hoverIndex = this.findHoverIndexFromDataPoints(pos.x, series, hoverIndex);
|
hoverIndex = this.findHoverIndexFromDataPoints(pos.x, series, hoverIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Be sure we have a yaxis so that it does not brake series sorting
|
||||||
yaxis = 0;
|
yaxis = 0;
|
||||||
if (series.yaxis) {
|
if (series.yaxis) {
|
||||||
yaxis = series.yaxis.n;
|
yaxis = series.yaxis.n;
|
||||||
@@ -106,8 +122,8 @@ function ($, _) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find point which closer to pointer
|
// Time of the point closer to pointer
|
||||||
results.time = _.min(results, 'distance').time;
|
results.time = minTime;
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
};
|
};
|
||||||
@@ -153,6 +169,8 @@ function ($, _) {
|
|||||||
|
|
||||||
seriesHtml = '';
|
seriesHtml = '';
|
||||||
|
|
||||||
|
absoluteTime = dashboard.formatDate(seriesHoverInfo.time, tooltipFormat);
|
||||||
|
|
||||||
// Dynamically reorder the hovercard for the current time point if the
|
// Dynamically reorder the hovercard for the current time point if the
|
||||||
// option is enabled, sort by yaxis by default.
|
// option is enabled, sort by yaxis by default.
|
||||||
if (panel.tooltip.sort === 2) {
|
if (panel.tooltip.sort === 2) {
|
||||||
@@ -169,8 +187,6 @@ function ($, _) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
var distance, time;
|
|
||||||
|
|
||||||
for (i = 0; i < seriesHoverInfo.length; i++) {
|
for (i = 0; i < seriesHoverInfo.length; i++) {
|
||||||
hoverInfo = seriesHoverInfo[i];
|
hoverInfo = seriesHoverInfo[i];
|
||||||
|
|
||||||
@@ -178,11 +194,6 @@ function ($, _) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! distance || hoverInfo.distance < distance) {
|
|
||||||
distance = hoverInfo.distance;
|
|
||||||
time = hoverInfo.time;
|
|
||||||
}
|
|
||||||
|
|
||||||
var highlightClass = '';
|
var highlightClass = '';
|
||||||
if (item && i === item.seriesIndex) {
|
if (item && i === item.seriesIndex) {
|
||||||
highlightClass = 'graph-tooltip-list-item--highlight';
|
highlightClass = 'graph-tooltip-list-item--highlight';
|
||||||
@@ -198,7 +209,6 @@ function ($, _) {
|
|||||||
plot.highlight(hoverInfo.index, hoverInfo.hoverIndex);
|
plot.highlight(hoverInfo.index, hoverInfo.hoverIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
absoluteTime = dashboard.formatDate(time, tooltipFormat);
|
|
||||||
self.showTooltip(absoluteTime, seriesHtml, pos);
|
self.showTooltip(absoluteTime, seriesHtml, pos);
|
||||||
}
|
}
|
||||||
// single series tooltip
|
// single series tooltip
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ class GraphCtrl extends MetricsPanelCtrl {
|
|||||||
// fill factor
|
// fill factor
|
||||||
fill : 1,
|
fill : 1,
|
||||||
// line width in pixels
|
// line width in pixels
|
||||||
linewidth : 2,
|
linewidth : 1,
|
||||||
// show hide points
|
// show hide points
|
||||||
points : false,
|
points : false,
|
||||||
// point radius in pixels
|
// point radius in pixels
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ describe('grafanaGraph', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should set barWidth', function() {
|
it('should set barWidth', function() {
|
||||||
expect(ctx.plotOptions.series.bars.barWidth).to.be(10/1.5);
|
expect(ctx.plotOptions.series.bars.barWidth).to.be(1/1.5);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -59,34 +59,34 @@
|
|||||||
.dashnav-action-icons,
|
.dashnav-action-icons,
|
||||||
.dashnav-move-timeframe {
|
.dashnav-move-timeframe {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: opacity 1.5s ease-in-out;
|
transition: all 1.5s ease-in-out 1s;
|
||||||
}
|
}
|
||||||
|
|
||||||
// navbar buttons
|
// navbar buttons
|
||||||
.navbar-brand-btn,
|
.navbar-brand-btn,
|
||||||
.navbar-inner {
|
.navbar-inner {
|
||||||
border: none;
|
border-color: transparent;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
transition: background 1.5s ease-in-out;
|
transition: all 1.5s ease-in-out 1s;
|
||||||
.fa {
|
.fa {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: opacity 1.5s ease-in-out;
|
transition: all 1.5s ease-in-out 1s;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar-page-btn {
|
.navbar-page-btn {
|
||||||
border: none;
|
border-color: transparent;
|
||||||
transform: translate3d(-50px, 0, 0);
|
|
||||||
background: transparent;
|
background: transparent;
|
||||||
transition: transform 1.5s ease-in-out;
|
transform: translate3d(-50px, 0, 0);
|
||||||
|
transition: all 1.5s ease-in-out 1s;
|
||||||
.icon-gf {
|
.icon-gf {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: opacity 1.5s ease-in-out;
|
transition: all 1.5s ease-in-out 1s;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.gf-timepicker-nav-btn {
|
.gf-timepicker-nav-btn {
|
||||||
transform: translate3d(40px, 0, 0);
|
transform: translate3d(40px, 0, 0);
|
||||||
transition: transform 1.5s ease-in-out;
|
transition: transform 1.5s ease-in-out 1s;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,6 @@
|
|||||||
|
|
||||||
.panel-alert-state {
|
.panel-alert-state {
|
||||||
&--alerting {
|
&--alerting {
|
||||||
background-color: mix($critical,$panel-bg, 3%);
|
|
||||||
animation: alerting-panel 1.6s cubic-bezier(1,.1,.73,1) 0s infinite alternate;
|
animation: alerting-panel 1.6s cubic-bezier(1,.1,.73,1) 0s infinite alternate;
|
||||||
box-shadow: 0 0 10px rgba($critical,0.5);
|
box-shadow: 0 0 10px rgba($critical,0.5);
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
|
|||||||
Vendored
+13
-16
@@ -1201,24 +1201,21 @@ Licensed under the MIT license.
|
|||||||
points[k + m] = null;
|
points[k + m] = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
// a little bit of line specific stuff that
|
|
||||||
// perhaps shouldn't be here, but lacking
|
|
||||||
// better means...
|
|
||||||
if (insertSteps && k > 0
|
|
||||||
&& points[k - ps] != null
|
|
||||||
&& points[k - ps] != points[k]
|
|
||||||
&& points[k - ps + 1] != points[k + 1]) {
|
|
||||||
// copy the point to make room for a middle point
|
|
||||||
for (m = 0; m < ps; ++m)
|
|
||||||
points[k + ps + m] = points[k + m];
|
|
||||||
|
|
||||||
// middle point has same y
|
if (insertSteps && k > 0 && (!nullify || points[k - ps] != null)) {
|
||||||
points[k + 1] = points[k - ps + 1];
|
// copy the point to make room for a middle point
|
||||||
|
for (m = 0; m < ps; ++m)
|
||||||
|
points[k + ps + m] = points[k + m];
|
||||||
|
|
||||||
// we've added a point, better reflect that
|
// middle point has same y
|
||||||
k += ps;
|
points[k + 1] = points[k - ps + 1] || 0;
|
||||||
}
|
|
||||||
|
// if series has null values, let's give the last !null value a nice step
|
||||||
|
if(nullify)
|
||||||
|
points[k] = p[0];
|
||||||
|
|
||||||
|
// we've added a point, better reflect that
|
||||||
|
k += ps;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+47
-58
@@ -78,41 +78,46 @@ charts or filled areas).
|
|||||||
i = 0, j = 0, l, m;
|
i = 0, j = 0, l, m;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (i >= points.length)
|
// browse all points from the current series and from the previous series
|
||||||
|
if (i >= points.length && j >= otherpoints.length)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
// newpoints will replace current series with
|
||||||
|
// as many points as different timestamps we have in the 2 (current & previous) series
|
||||||
l = newpoints.length;
|
l = newpoints.length;
|
||||||
|
px = points[i + keyOffset];
|
||||||
|
py = points[i + accumulateOffset];
|
||||||
|
qx = otherpoints[j + keyOffset];
|
||||||
|
qy = otherpoints[j + accumulateOffset];
|
||||||
|
bottom = 0;
|
||||||
|
|
||||||
if (points[i] == null) {
|
if (i < points.length && px == null) {
|
||||||
// copy gaps
|
// let's ignore null points from current series, nothing to do with them
|
||||||
|
i += ps;
|
||||||
|
}
|
||||||
|
else if (j < otherpoints.length && qx == null) {
|
||||||
|
// let's ignore null points from previous series, nothing to do with them
|
||||||
|
j += otherps;
|
||||||
|
}
|
||||||
|
else if (i >= points.length) {
|
||||||
|
// no more points in the current series, simply take the remaining points
|
||||||
|
// from the previous series so that next series will correctly stack
|
||||||
|
for (m = 0; m < ps; ++m)
|
||||||
|
newpoints.push(otherpoints[j + m]);
|
||||||
|
bottom = qy;
|
||||||
|
j += otherps;
|
||||||
|
}
|
||||||
|
else if (j >= otherpoints.length) {
|
||||||
|
// no more points in the previous series, of course let's take
|
||||||
|
// the remaining points from the current series
|
||||||
for (m = 0; m < ps; ++m)
|
for (m = 0; m < ps; ++m)
|
||||||
newpoints.push(points[i + m]);
|
newpoints.push(points[i + m]);
|
||||||
i += ps;
|
i += ps;
|
||||||
}
|
}
|
||||||
else if (j >= otherpoints.length) {
|
|
||||||
// for lines, we can't use the rest of the points
|
|
||||||
if (!withlines) {
|
|
||||||
for (m = 0; m < ps; ++m)
|
|
||||||
newpoints.push(points[i + m]);
|
|
||||||
}
|
|
||||||
i += ps;
|
|
||||||
}
|
|
||||||
else if (otherpoints[j] == null) {
|
|
||||||
// oops, got a gap
|
|
||||||
for (m = 0; m < ps; ++m)
|
|
||||||
newpoints.push(null);
|
|
||||||
fromgap = true;
|
|
||||||
j += otherps;
|
|
||||||
}
|
|
||||||
else {
|
else {
|
||||||
// cases where we actually got two points
|
// next available points from current and previous series have the same timestamp
|
||||||
px = points[i + keyOffset];
|
|
||||||
py = points[i + accumulateOffset];
|
|
||||||
qx = otherpoints[j + keyOffset];
|
|
||||||
qy = otherpoints[j + accumulateOffset];
|
|
||||||
bottom = 0;
|
|
||||||
|
|
||||||
if (px == qx) {
|
if (px == qx) {
|
||||||
|
// so take the point from the current series and skip the previous' one
|
||||||
for (m = 0; m < ps; ++m)
|
for (m = 0; m < ps; ++m)
|
||||||
newpoints.push(points[i + m]);
|
newpoints.push(points[i + m]);
|
||||||
|
|
||||||
@@ -122,55 +127,39 @@ charts or filled areas).
|
|||||||
i += ps;
|
i += ps;
|
||||||
j += otherps;
|
j += otherps;
|
||||||
}
|
}
|
||||||
|
// next available point with the smallest timestamp is from the previous series
|
||||||
else if (px > qx) {
|
else if (px > qx) {
|
||||||
// we got past point below, might need to
|
// so take the point from the previous series so that next series will correctly stack
|
||||||
// insert interpolated extra point
|
for (m = 0; m < ps; ++m)
|
||||||
if (withlines && i > 0 && points[i - ps] != null) {
|
newpoints.push(otherpoints[j + m]);
|
||||||
intery = py + (points[i - ps + accumulateOffset] - py) * (qx - px) / (points[i - ps + keyOffset] - px);
|
|
||||||
newpoints.push(qx);
|
// we might be able to interpolate
|
||||||
newpoints.push(intery + qy);
|
if (i > 0 && points[i - ps] != null)
|
||||||
for (m = 2; m < ps; ++m)
|
newpoints[l + accumulateOffset] += py + (points[i - ps + accumulateOffset] - py) * (qx - px) / (points[i - ps + keyOffset] - px);
|
||||||
newpoints.push(points[i + m]);
|
|
||||||
bottom = qy;
|
bottom = qy;
|
||||||
}
|
|
||||||
|
|
||||||
j += otherps;
|
j += otherps;
|
||||||
}
|
}
|
||||||
else { // px < qx
|
// (px < qx) next available point with the smallest timestamp is from the current series
|
||||||
if (fromgap && withlines) {
|
else {
|
||||||
// if we come from a gap, we just skip this point
|
// so of course let's take the point from the current series
|
||||||
i += ps;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (m = 0; m < ps; ++m)
|
for (m = 0; m < ps; ++m)
|
||||||
newpoints.push(points[i + m]);
|
newpoints.push(points[i + m]);
|
||||||
|
|
||||||
// we might be able to interpolate a point below,
|
// we might be able to interpolate a point below,
|
||||||
// this can give us a better y
|
// this can give us a better y
|
||||||
if (withlines && j > 0 && otherpoints[j - otherps] != null)
|
if (j > 0 && otherpoints[j - otherps] != null)
|
||||||
bottom = qy + (otherpoints[j - otherps + accumulateOffset] - qy) * (px - qx) / (otherpoints[j - otherps + keyOffset] - qx);
|
bottom = qy + (otherpoints[j - otherps + accumulateOffset] - qy) * (px - qx) / (otherpoints[j - otherps + keyOffset] - qx);
|
||||||
|
|
||||||
newpoints[l + accumulateOffset] += bottom;
|
newpoints[l + accumulateOffset] += bottom;
|
||||||
|
|
||||||
i += ps;
|
i += ps;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fromgap = false;
|
if (l != newpoints.length && withbottom)
|
||||||
|
newpoints[l + 2] = bottom;
|
||||||
if (l != newpoints.length && withbottom)
|
|
||||||
newpoints[l + 2] += bottom;
|
|
||||||
}
|
|
||||||
|
|
||||||
// maintain the line steps invariant
|
|
||||||
if (withsteps && l != newpoints.length && l > 0
|
|
||||||
&& newpoints[l] != null
|
|
||||||
&& newpoints[l] != newpoints[l - ps]
|
|
||||||
&& newpoints[l + 1] != newpoints[l - ps + 1]) {
|
|
||||||
for (m = 0; m < ps; ++m)
|
|
||||||
newpoints[l + ps + m] = newpoints[l + m];
|
|
||||||
newpoints[l + 1] = newpoints[l - ps + 1];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
datapoints.points = newpoints;
|
datapoints.points = newpoints;
|
||||||
|
|||||||
Reference in New Issue
Block a user