diff --git a/CHANGELOG.md b/CHANGELOG.md index 82b3ba2037e..8f2b6cfc187 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,10 @@ * **Cloudwatch**: Correctly obtain IAM roles within ECS container tasks [#7892](https://github.com/grafana/grafana/issues/7892) thx [@gomlgs](https://github.com/gomlgs) * **Units**: New number format: Scientific notation [#7781](https://github.com/grafana/grafana/issues/7781) thx [@cadnce](https://github.com/cadnce) * **Oauth**: Add common type for oauth authorization errors [#6428](https://github.com/grafana/grafana/issues/6428) thx [@amenzhinsky](https://github.com/amenzhinsky) +* **Templating**: Data source variable now supports multi value and panel repeats [#7030](https://github.com/grafana/grafana/issues/7030) thx [@mtanda](https://github.com/mtanda) + +## Fixes +* **Table Panel**: Fixed annotation display in table panel, [#8023](https://github.com/grafana/grafana/issues/8023) # 4.2.0 (2017-03-22) ## Minor Enhancements diff --git a/conf/defaults.ini b/conf/defaults.ini index 5c12937f4f8..9955c22ca17 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -60,14 +60,14 @@ cert_key = #################################### Database ############################ [database] # You can configure the database connection by specifying type, host, name, user and password -# as seperate properties or as on string using the url propertie. +# as separate properties or as on string using the url property. # Either "mysql", "postgres" or "sqlite3", it's your choice type = sqlite3 host = 127.0.0.1:3306 name = grafana user = root -# If the password contains # or ; you have to wrap it with trippel quotes. Ex """#password;""" +# If the password contains # or ; you have to wrap it with triple quotes. Ex """#password;""" password = # Use either URL or the previous fields to configure the database # Example: mysql://user:secret@host:port/database @@ -132,7 +132,7 @@ logging = false reporting_enabled = true # Set to false to disable all checks to https://grafana.com -# for new vesions (grafana itself and plugins), check is used +# for new versions (grafana itself and plugins), check is used # in some UI views to notify that grafana or plugin update exists # This option does not cause any auto updates, nor send any information # only a GET request to https://grafana.com to get latest versions diff --git a/conf/ldap.toml b/conf/ldap.toml index 305929b80d7..ae217106cb2 100644 --- a/conf/ldap.toml +++ b/conf/ldap.toml @@ -14,7 +14,7 @@ start_tls = false # set to true if you want to skip ssl cert validation ssl_skip_verify = false # set to the path to your root CA certificate or leave unset to use system defaults -# root_ca_cert = /path/to/certificate.crt +# root_ca_cert = "/path/to/certificate.crt" # Search user bind dn bind_dn = "cn=admin,dc=grafana,dc=org" diff --git a/docs/sources/http_api/dashboard.md b/docs/sources/http_api/dashboard.md index 5fed69b4a93..12d46dc6aa4 100644 --- a/docs/sources/http_api/dashboard.md +++ b/docs/sources/http_api/dashboard.md @@ -232,7 +232,7 @@ Get all tags of dashboards Status Codes: - **query** – Search Query -- **tags** – Tags to use +- **tag** – Tag to use - **starred** – Flag indicating if only starred Dashboards should be returned - **tagcloud** - Flag indicating if a tagcloud should be returned diff --git a/docs/sources/installation/ldap.md b/docs/sources/installation/ldap.md index d8538182b68..e8e1c8e57bd 100644 --- a/docs/sources/installation/ldap.md +++ b/docs/sources/installation/ldap.md @@ -38,7 +38,7 @@ start_tls = false # set to true if you want to skip ssl cert validation ssl_skip_verify = false # set to the path to your root CA certificate or leave unset to use system defaults -# root_ca_cert = /path/to/certificate.crt +# root_ca_cert = "/path/to/certificate.crt" # Search user bind dn bind_dn = "cn=admin,dc=grafana,dc=org" diff --git a/docs/sources/plugins/developing/development.md b/docs/sources/plugins/developing/development.md index 1f8db99424b..7d67dd22323 100644 --- a/docs/sources/plugins/developing/development.md +++ b/docs/sources/plugins/developing/development.md @@ -17,7 +17,7 @@ There are two blog posts about authoring a plugin that might also be of interest ## Short version 1. [Setup grafana](http://docs.grafana.org/project/building_from_source/) -2. Clone an example plugin into ```/var/lib/grafana/plugins``` or `data/plugins` (relative to grafana git repo if your running development version from source dir) +2. Clone an example plugin into ```/var/lib/grafana/plugins``` or `data/plugins` (relative to grafana git repo if you're running development version from source dir) 3. Code away! ## What languages? diff --git a/latest.json b/latest.json index 75f20d75a69..23ba2ba5b75 100644 --- a/latest.json +++ b/latest.json @@ -1,4 +1,4 @@ { - "stable": "4.1.1", - "testing": "4.1.1" + "stable": "4.2.0", + "testing": "4.2.0" } diff --git a/pkg/services/sqlstore/sqlstore.go b/pkg/services/sqlstore/sqlstore.go index a2106ddbffd..27fba0068d1 100644 --- a/pkg/services/sqlstore/sqlstore.go +++ b/pkg/services/sqlstore/sqlstore.go @@ -198,6 +198,9 @@ func LoadConfig() { if DbCfg.Type == "sqlite3" { UseSQLite3 = true + // only allow one connection as sqlite3 has multi threading issues that casue table locks + // DbCfg.MaxIdleConn = 1 + // DbCfg.MaxOpenConn = 1 } DbCfg.SslMode = sec.Key("ssl_mode").String() DbCfg.CaCertPath = sec.Key("ca_cert_path").String() diff --git a/pkg/tsdb/influxdb/query.go b/pkg/tsdb/influxdb/query.go index 50d155131fd..19f374b938b 100644 --- a/pkg/tsdb/influxdb/query.go +++ b/pkg/tsdb/influxdb/query.go @@ -34,13 +34,18 @@ func (query *Query) Build(queryContext *tsdb.QueryContext) (string, error) { return "", err } - res = strings.Replace(res, "$timeFilter", query.renderTimeFilter(queryContext), 1) - res = strings.Replace(res, "$interval", interval.Text, 1) - res = strings.Replace(res, "$__interval_ms", strconv.FormatInt(interval.Value.Nanoseconds()/int64(time.Millisecond), 10), 1) - res = strings.Replace(res, "$__interval", interval.Text, 1) + res = replaceVariable(res, "$timeFilter", query.renderTimeFilter(queryContext)) + res = replaceVariable(res, "$interval", interval.Text) + res = replaceVariable(res, "$__interval_ms", strconv.FormatInt(interval.Value.Nanoseconds()/int64(time.Millisecond), 10)) + res = replaceVariable(res, "$__interval", interval.Text) return res, nil } +func replaceVariable(str string, variable string, value string) string { + count := strings.Count(str, variable) + return strings.Replace(str, variable, value, count) +} + func getDefinedInterval(query *Query, queryContext *tsdb.QueryContext) (*tsdb.Interval, error) { defaultInterval := tsdb.CalculateInterval(queryContext.TimeRange) diff --git a/public/app/core/services/context_srv.ts b/public/app/core/services/context_srv.ts index f3d319f11c6..ddff1093720 100644 --- a/public/app/core/services/context_srv.ts +++ b/public/app/core/services/context_srv.ts @@ -11,6 +11,7 @@ export class User { orgRole: any; timezone: string; helpFlags1: number; + lightTheme: boolean; constructor() { if (config.bootData.user) { diff --git a/public/app/core/utils/ticks.ts b/public/app/core/utils/ticks.ts new file mode 100644 index 00000000000..7e7abbcd8f0 --- /dev/null +++ b/public/app/core/utils/ticks.ts @@ -0,0 +1,27 @@ +/** + * Calculate tick step. + * Implementation from d3-array (ticks.js) + * https://github.com/d3/d3-array/blob/master/src/ticks.js + * @param start Start value + * @param stop End value + * @param count Ticks count + */ +export function tickStep(start: number, stop: number, count: number): number { + let e10 = Math.sqrt(50), + e5 = Math.sqrt(10), + e2 = Math.sqrt(2); + + let step0 = Math.abs(stop - start) / Math.max(0, count), + step1 = Math.pow(10, Math.floor(Math.log(step0) / Math.LN10)), + error = step0 / step1; + + if (error >= e10) { + step1 *= 10; + } else if (error >= e5) { + step1 *= 5; + } else if (error >= e2) { + step1 *= 2; + } + + return stop < start ? -step1 : step1; +} diff --git a/public/app/features/alerting/partials/alert_tab.html b/public/app/features/alerting/partials/alert_tab.html index 76841db75ba..4c63c4507fc 100644 --- a/public/app/features/alerting/partials/alert_tab.html +++ b/public/app/features/alerting/partials/alert_tab.html @@ -129,7 +129,7 @@
Message - +
diff --git a/public/app/features/templating/adhoc_variable.ts b/public/app/features/templating/adhoc_variable.ts index b3c9980c7c7..f3ff80acfc8 100644 --- a/public/app/features/templating/adhoc_variable.ts +++ b/public/app/features/templating/adhoc_variable.ts @@ -45,7 +45,9 @@ export class AdhocVariable implements Variable { } this.filters = urlValue.map(item => { - var values = item.split('|'); + var values = item.split('|').map(value => { + return this.unescapeDelimiter(value); + }); return { key: values[0], operator: values[1], @@ -58,10 +60,20 @@ export class AdhocVariable implements Variable { getValueForUrl() { return this.filters.map(filter => { - return filter.key + '|' + filter.operator + '|' + filter.value; + return [filter.key, filter.operator, filter.value].map(value => { + return this.escapeDelimiter(value); + }).join('|'); }); } + escapeDelimiter(value) { + return value.replace('|', '__gfp__'); + } + + unescapeDelimiter(value) { + return value.replace('__gfp__', '|'); + } + setFilters(filters: any[]) { this.filters = filters; } diff --git a/public/app/features/templating/specs/adhoc_variable_specs.ts b/public/app/features/templating/specs/adhoc_variable_specs.ts index 15856940540..c9246941309 100644 --- a/public/app/features/templating/specs/adhoc_variable_specs.ts +++ b/public/app/features/templating/specs/adhoc_variable_specs.ts @@ -11,10 +11,11 @@ describe('AdhocVariable', function() { filters: [ {key: 'key1', operator: '=', value: 'value1'}, {key: 'key2', operator: '!=', value: 'value2'}, + {key: 'key3', operator: '=', value: 'value3a|value3b'}, ] }); var urlValue = variable.getValueForUrl(); - expect(urlValue).to.eql(["key1|=|value1", "key2|!=|value2"]); + expect(urlValue).to.eql(["key1|=|value1", "key2|!=|value2", "key3|=|value3a__gfp__value3b"]); }); }); @@ -23,7 +24,7 @@ describe('AdhocVariable', function() { it('should restore filters', function() { var variable = new AdhocVariable({}); - variable.setValueFromUrl(["key1|=|value1", "key2|!=|value2"]); + variable.setValueFromUrl(["key1|=|value1", "key2|!=|value2", "key3|=|value3a__gfp__value3b"]); expect(variable.filters[0].key).to.be('key1'); expect(variable.filters[0].operator).to.be('='); @@ -32,6 +33,10 @@ describe('AdhocVariable', function() { expect(variable.filters[1].key).to.be('key2'); expect(variable.filters[1].operator).to.be('!='); expect(variable.filters[1].value).to.be('value2'); + + expect(variable.filters[2].key).to.be('key3'); + expect(variable.filters[2].operator).to.be('='); + expect(variable.filters[2].value).to.be('value3a|value3b'); }); }); diff --git a/public/app/headers/common.d.ts b/public/app/headers/common.d.ts index aa4825829a1..9ea5e96654d 100644 --- a/public/app/headers/common.d.ts +++ b/public/app/headers/common.d.ts @@ -67,3 +67,8 @@ declare module 'remarkable' { var config: any; export default config; } + +declare module 'd3' { + var d3: any; + export default d3; +} diff --git a/public/app/plugins/panel/graph/axes_editor.html b/public/app/plugins/panel/graph/axes_editor.html index e446bb96490..b0ab759bf18 100644 --- a/public/app/plugins/panel/graph/axes_editor.html +++ b/public/app/plugins/panel/graph/axes_editor.html @@ -39,10 +39,10 @@
X-Axis
- +
- +
@@ -50,22 +50,28 @@
- +
- +
- +
+ +
+ + +
+
diff --git a/public/app/plugins/panel/graph/axes_editor.ts b/public/app/plugins/panel/graph/axes_editor.ts index b73ddf3868e..adb3d1d6706 100644 --- a/public/app/plugins/panel/graph/axes_editor.ts +++ b/public/app/plugins/panel/graph/axes_editor.ts @@ -30,6 +30,7 @@ export class AxesEditorCtrl { this.xAxisModes = { 'Time': 'time', 'Series': 'series', + 'Histogram': 'histogram' // 'Data field': 'field', }; diff --git a/public/app/plugins/panel/graph/data_processor.ts b/public/app/plugins/panel/graph/data_processor.ts index dcd202c5bee..87b48f78550 100644 --- a/public/app/plugins/panel/graph/data_processor.ts +++ b/public/app/plugins/panel/graph/data_processor.ts @@ -29,6 +29,7 @@ export class DataProcessor { switch (this.panel.xaxis.mode) { case 'series': + case 'histogram': case 'time': { return options.dataList.map((item, index) => { return this.timeSeriesHandler(item, index, options); @@ -48,6 +49,9 @@ export class DataProcessor { if (this.panel.xaxis.mode === 'series') { return 'series'; } + if (this.panel.xaxis.mode === 'histogram') { + return 'histogram'; + } return 'time'; } } @@ -74,6 +78,15 @@ export class DataProcessor { this.panel.xaxis.values = ['total']; break; } + case 'histogram': { + this.panel.bars = true; + this.panel.lines = false; + this.panel.points = false; + this.panel.stack = false; + this.panel.legend.show = false; + this.panel.tooltip.shared = false; + break; + } } } diff --git a/public/app/plugins/panel/graph/graph.ts b/public/app/plugins/panel/graph/graph.ts index 06af66ddbe7..5a684a42fd3 100755 --- a/public/app/plugins/panel/graph/graph.ts +++ b/public/app/plugins/panel/graph/graph.ts @@ -12,10 +12,12 @@ import './jquery.flot.events'; import $ from 'jquery'; import _ from 'lodash'; import moment from 'moment'; -import kbn from 'app/core/utils/kbn'; +import kbn from 'app/core/utils/kbn'; +import {tickStep} from 'app/core/utils/ticks'; import {appEvents, coreModule} from 'app/core/core'; import GraphTooltip from './graph_tooltip'; import {ThresholdManager} from './threshold_manager'; +import {convertValuesToHistogram, getSeriesValues} from './histogram'; coreModule.directive('grafanaGraph', function($rootScope, timeSrv) { return { @@ -290,6 +292,29 @@ coreModule.directive('grafanaGraph', function($rootScope, timeSrv) { addXSeriesAxis(options); break; } + case 'histogram': { + let bucketSize: number; + let values = getSeriesValues(data); + + if (data.length && values.length) { + let histMin = _.min(_.map(data, s => s.stats.min)); + let histMax = _.max(_.map(data, s => s.stats.max)); + let ticks = panel.xaxis.buckets || panelWidth / 50; + bucketSize = tickStep(histMin, histMax, ticks); + let histogram = convertValuesToHistogram(values, bucketSize); + + data[0].data = histogram; + data[0].alias = data[0].label = data[0].id = "count"; + data = [data[0]]; + + options.series.bars.barWidth = bucketSize * 0.8; + } else { + bucketSize = 0; + } + + addXHistogramAxis(options, bucketSize); + break; + } case 'table': { options.series.bars.barWidth = 0.7; options.series.bars.align = 'center'; @@ -384,6 +409,38 @@ coreModule.directive('grafanaGraph', function($rootScope, timeSrv) { }; } + function addXHistogramAxis(options, bucketSize) { + let ticks, min, max; + + if (data.length) { + ticks = _.map(data[0].data, point => point[0]); + + // Expand ticks for pretty view + min = Math.max(0, _.min(ticks) - bucketSize); + max = _.max(ticks) + bucketSize; + + ticks = []; + for (let i = min; i <= max; i += bucketSize) { + ticks.push(i); + } + } else { + // Set defaults if no data + ticks = panelWidth / 100; + min = 0; + max = 1; + } + + options.xaxis = { + timezone: dashboard.getTimezone(), + show: panel.xaxis.show, + mode: null, + min: min, + max: max, + label: "Histogram", + ticks: ticks + }; + } + function addXTableAxis(options) { var ticks = _.map(data, function(series, seriesIndex) { return _.map(series.datapoints, function(point, pointIndex) { diff --git a/public/app/plugins/panel/graph/histogram.ts b/public/app/plugins/panel/graph/histogram.ts new file mode 100644 index 00000000000..e6ad7ebb0f6 --- /dev/null +++ b/public/app/plugins/panel/graph/histogram.ts @@ -0,0 +1,48 @@ +import _ from 'lodash'; + +/** + * Convert series into array of series values. + * @param data Array of series + */ +export function getSeriesValues(data: any): number[] { + let values = []; + + // Count histogam stats + for (let i = 0; i < data.length; i++) { + let series = data[i]; + for (let j = 0; j < series.data.length; j++) { + if (series.data[j][1] !== null) { + values.push(series.data[j][1]); + } + } + } + + return values; +} + +/** + * Convert array of values into timeseries-like histogram: + * [[val_1, count_1], [val_2, count_2], ..., [val_n, count_n]] + * @param values + * @param bucketSize + */ +export function convertValuesToHistogram(values: number[], bucketSize: number): any[] { + let histogram = {}; + + for (let i = 0; i < values.length; i++) { + let bound = getBucketBound(values[i], bucketSize); + if (histogram[bound]) { + histogram[bound] = histogram[bound] + 1; + } else { + histogram[bound] = 1; + } + } + + return _.map(histogram, (count, bound) => { + return [Number(bound), count]; + }); +} + +function getBucketBound(value: number, bucketSize: number): number { + return Math.floor(value / bucketSize) * bucketSize; +} diff --git a/public/app/plugins/panel/graph/module.ts b/public/app/plugins/panel/graph/module.ts index 01f7a485aa6..e98d1c25ad7 100644 --- a/public/app/plugins/panel/graph/module.ts +++ b/public/app/plugins/panel/graph/module.ts @@ -59,6 +59,7 @@ class GraphCtrl extends MetricsPanelCtrl { mode: 'time', name: null, values: [], + buckets: null }, // show/hide lines lines : true, diff --git a/public/app/plugins/panel/graph/specs/histogram_specs.ts b/public/app/plugins/panel/graph/specs/histogram_specs.ts new file mode 100644 index 00000000000..71b3def8d1d --- /dev/null +++ b/public/app/plugins/panel/graph/specs/histogram_specs.ts @@ -0,0 +1,65 @@ +/// + +import { describe, beforeEach, it, expect } from '../../../../../test/lib/common'; + +import { convertValuesToHistogram, getSeriesValues } from '../histogram'; + +describe('Graph Histogam Converter', function () { + + describe('Values to histogram converter', () => { + let values; + let bucketSize = 10; + + beforeEach(() => { + values = [1, 2, 10, 11, 17, 20, 29]; + }); + + it('Should convert to series-like array', () => { + bucketSize = 10; + let expected = [ + [0, 2], [10, 3], [20, 2] + ]; + + let histogram = convertValuesToHistogram(values, bucketSize); + expect(histogram).to.eql(expected); + }); + + it('Should not add empty buckets', () => { + bucketSize = 5; + let expected = [ + [0, 2], [10, 2], [15, 1], [20, 1], [25, 1] + ]; + + let histogram = convertValuesToHistogram(values, bucketSize); + expect(histogram).to.eql(expected); + }); + }); + + describe('Series to values converter', () => { + let data; + + beforeEach(() => { + data = [ + { + data: [[0, 1], [0, 2], [0, 10], [0, 11], [0, 17], [0, 20], [0, 29]] + } + ]; + }); + + it('Should convert to values array', () => { + let expected = [1, 2, 10, 11, 17, 20, 29]; + + let values = getSeriesValues(data); + expect(values).to.eql(expected); + }); + + it('Should skip null values', () => { + data[0].data.push([0, null]); + + let expected = [1, 2, 10, 11, 17, 20, 29]; + + let values = getSeriesValues(data); + expect(values).to.eql(expected); + }); + }); +}); diff --git a/public/app/plugins/panel/heatmap/README.md b/public/app/plugins/panel/heatmap/README.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/public/app/plugins/panel/heatmap/axes_editor.ts b/public/app/plugins/panel/heatmap/axes_editor.ts new file mode 100644 index 00000000000..5b84f272d95 --- /dev/null +++ b/public/app/plugins/panel/heatmap/axes_editor.ts @@ -0,0 +1,49 @@ +/// + +import kbn from 'app/core/utils/kbn'; + +export class AxesEditorCtrl { + panel: any; + panelCtrl: any; + unitFormats: any; + logScales: any; + dataFormats: any; + + /** @ngInject */ + constructor($scope) { + $scope.editor = this; + this.panelCtrl = $scope.ctrl; + this.panel = this.panelCtrl.panel; + + this.unitFormats = kbn.getUnitFormats(); + + this.logScales = { + 'linear': 1, + 'log (base 2)': 2, + 'log (base 10)': 10, + 'log (base 32)': 32, + 'log (base 1024)': 1024 + }; + + this.dataFormats = { + 'Timeseries': 'timeseries', + 'ES histogram': 'es_histogram' + }; + } + + setUnitFormat(subItem) { + this.panel.yAxis.format = subItem.value; + this.panelCtrl.render(); + } +} + +/** @ngInject */ +export function axesEditor() { + 'use strict'; + return { + restrict: 'E', + scope: true, + templateUrl: 'public/app/plugins/panel/heatmap/partials/axes_editor.html', + controller: AxesEditorCtrl, + }; +} diff --git a/public/app/plugins/panel/heatmap/display_editor.ts b/public/app/plugins/panel/heatmap/display_editor.ts new file mode 100644 index 00000000000..775017b00da --- /dev/null +++ b/public/app/plugins/panel/heatmap/display_editor.ts @@ -0,0 +1,26 @@ +/// + +export class HeatmapDisplayEditorCtrl { + panel: any; + panelCtrl: any; + + /** @ngInject */ + constructor($scope) { + $scope.editor = this; + this.panelCtrl = $scope.ctrl; + this.panel = this.panelCtrl.panel; + + this.panelCtrl.render(); + } +} + +/** @ngInject */ +export function heatmapDisplayEditor() { + 'use strict'; + return { + restrict: 'E', + scope: true, + templateUrl: 'public/app/plugins/panel/heatmap/partials/display_editor.html', + controller: HeatmapDisplayEditorCtrl, + }; +} diff --git a/public/app/plugins/panel/heatmap/heatmap_ctrl.ts b/public/app/plugins/panel/heatmap/heatmap_ctrl.ts new file mode 100644 index 00000000000..c9877a2b41c --- /dev/null +++ b/public/app/plugins/panel/heatmap/heatmap_ctrl.ts @@ -0,0 +1,282 @@ +/// + +import {MetricsPanelCtrl} from 'app/plugins/sdk'; +import _ from 'lodash'; +import kbn from 'app/core/utils/kbn'; +import TimeSeries from 'app/core/time_series'; +import {axesEditor} from './axes_editor'; +import {heatmapDisplayEditor} from './display_editor'; +import rendering from './rendering'; +import { convertToHeatMap, elasticHistogramToHeatmap, calculateBucketSize, getMinLog} from './heatmap_data_converter'; + +let X_BUCKET_NUMBER_DEFAULT = 30; +let Y_BUCKET_NUMBER_DEFAULT = 10; + +let panelDefaults = { + heatmap: { + }, + cards: { + cardPadding: null, + cardRound: null + }, + color: { + mode: 'spectrum', + cardColor: '#b4ff00', + colorScale: 'sqrt', + exponent: 0.5, + colorScheme: 'interpolateOranges', + fillBackground: false + }, + dataFormat: 'timeseries', + xBucketSize: null, + xBucketNumber: null, + yBucketSize: null, + yBucketNumber: null, + xAxis: { + show: true + }, + yAxis: { + show: true, + format: 'short', + decimals: null, + logBase: 1, + splitFactor: null, + min: null, + max: null, + removeZeroValues: false + }, + tooltip: { + show: true, + seriesStat: false, + showHistogram: false + }, + highlightCards: true +}; + +let colorModes = ['opacity', 'spectrum']; +let opacityScales = ['linear', 'sqrt']; + +// Schemes from d3-scale-chromatic +// https://github.com/d3/d3-scale-chromatic +let colorSchemes = [ + // Diverging + {name: 'Spectral', value: 'interpolateSpectral', invert: 'always'}, + {name: 'RdYlGn', value: 'interpolateRdYlGn', invert: 'always'}, + + // Sequential (Single Hue) + {name: 'Blues', value: 'interpolateBlues', invert: 'dark'}, + {name: 'Greens', value: 'interpolateGreens', invert: 'dark'}, + {name: 'Greys', value: 'interpolateGreys', invert: 'dark'}, + {name: 'Oranges', value: 'interpolateOranges', invert: 'dark'}, + {name: 'Purples', value: 'interpolatePurples', invert: 'dark'}, + {name: 'Reds', value: 'interpolateReds', invert: 'dark'}, + + // Sequential (Multi-Hue) + {name: 'BuGn', value: 'interpolateBuGn', invert: 'dark'}, + {name: 'BuPu', value: 'interpolateBuPu', invert: 'dark'}, + {name: 'GnBu', value: 'interpolateGnBu', invert: 'dark'}, + {name: 'OrRd', value: 'interpolateOrRd', invert: 'dark'}, + {name: 'PuBuGn', value: 'interpolatePuBuGn', invert: 'dark'}, + {name: 'PuBu', value: 'interpolatePuBu', invert: 'dark'}, + {name: 'PuRd', value: 'interpolatePuRd', invert: 'dark'}, + {name: 'RdPu', value: 'interpolateRdPu', invert: 'dark'}, + {name: 'YlGnBu', value: 'interpolateYlGnBu', invert: 'dark'}, + {name: 'YlGn', value: 'interpolateYlGn', invert: 'dark'}, + {name: 'YlOrBr', value: 'interpolateYlOrBr', invert: 'dark'}, + {name: 'YlOrRd', value: 'interpolateYlOrRd', invert: 'darm'} +]; + +export class HeatmapCtrl extends MetricsPanelCtrl { + static templateUrl = 'module.html'; + + opacityScales: any = []; + colorModes: any = []; + colorSchemes: any = []; + selectionActivated: boolean; + unitFormats: any; + data: any; + series: any; + timeSrv: any; + dataWarning: any; + + /** @ngInject */ + constructor($scope, $injector, private $rootScope, timeSrv) { + super($scope, $injector); + this.$rootScope = $rootScope; + this.timeSrv = timeSrv; + this.selectionActivated = false; + + _.defaultsDeep(this.panel, panelDefaults); + this.opacityScales = opacityScales; + this.colorModes = colorModes; + this.colorSchemes = colorSchemes; + + // Bind grafana panel events + this.events.on('render', this.onRender.bind(this)); + this.events.on('data-received', this.onDataReceived.bind(this)); + this.events.on('data-error', this.onDataError.bind(this)); + this.events.on('data-snapshot-load', this.onDataReceived.bind(this)); + this.events.on('init-edit-mode', this.onInitEditMode.bind(this)); + } + + onInitEditMode() { + this.addEditorTab('Axes', axesEditor, 2); + this.addEditorTab('Display', heatmapDisplayEditor, 3); + this.unitFormats = kbn.getUnitFormats(); + } + + zoomOut(evt) { + this.publishAppEvent('zoom-out', 2); + } + + onRender() { + if (!this.range) { return; } + + let xBucketSize, yBucketSize, heatmapStats, bucketsData; + let logBase = this.panel.yAxis.logBase; + + if (this.panel.dataFormat === 'es_histogram') { + heatmapStats = this.parseHistogramSeries(this.series); + bucketsData = elasticHistogramToHeatmap(this.series); + + // Calculate bucket size based on ES heatmap data + let xBucketBoundSet = _.map(_.keys(bucketsData), key => Number(key)); + let yBucketBoundSet = _.map(this.series, series => Number(series.alias)); + xBucketSize = calculateBucketSize(xBucketBoundSet); + yBucketSize = calculateBucketSize(yBucketBoundSet, logBase); + if (logBase !== 1) { + // Use yBucketSize in meaning of "Split factor" for log scales + yBucketSize = 1 / yBucketSize; + } + } else { + let xBucketNumber = this.panel.xBucketNumber || X_BUCKET_NUMBER_DEFAULT; + let xBucketSizeByNumber = Math.floor((this.range.to - this.range.from) / xBucketNumber); + + // Parse X bucket size (number or interval) + let isIntervalString = kbn.interval_regex.test(this.panel.xBucketSize); + if (isIntervalString) { + xBucketSize = kbn.interval_to_ms(this.panel.xBucketSize); + } else if (isNaN(Number(this.panel.xBucketSize)) || this.panel.xBucketSize === '' || this.panel.xBucketSize === null) { + xBucketSize = xBucketSizeByNumber; + } else { + xBucketSize = Number(this.panel.xBucketSize); + } + + // Calculate Y bucket size + heatmapStats = this.parseSeries(this.series); + let yBucketNumber = this.panel.yBucketNumber || Y_BUCKET_NUMBER_DEFAULT; + if (logBase !== 1) { + yBucketSize = this.panel.yAxis.splitFactor; + } else { + if (heatmapStats.max === heatmapStats.min) { + if (heatmapStats.max) { + yBucketSize = heatmapStats.max / Y_BUCKET_NUMBER_DEFAULT; + } else { + yBucketSize = 1; + } + } else { + yBucketSize = (heatmapStats.max - heatmapStats.min) / yBucketNumber; + } + yBucketSize = this.panel.yBucketSize || yBucketSize; + } + + bucketsData = convertToHeatMap(this.series, yBucketSize, xBucketSize, logBase); + } + + // Set default Y range if no data + if (!heatmapStats.min && !heatmapStats.max) { + heatmapStats = {min: -1, max: 1, minLog: 1}; + yBucketSize = 1; + } + + this.data = { + buckets: bucketsData, + heatmapStats: heatmapStats, + xBucketSize: xBucketSize, + yBucketSize: yBucketSize + }; + } + + onDataReceived(dataList) { + this.series = dataList.map(this.seriesHandler.bind(this)); + + this.dataWarning = null; + const datapointsCount = _.reduce(this.series, (sum, series) => { + return sum + series.datapoints.length; + }, 0); + + if (datapointsCount === 0) { + this.dataWarning = { + title: 'No data points', + tip: 'No datapoints returned from data query' + }; + } else { + for (let series of this.series) { + if (series.isOutsideRange) { + this.dataWarning = { + title: 'Data points outside time range', + tip: 'Can be caused by timezone mismatch or missing time filter in query', + }; + break; + } + } + } + + this.render(); + } + + onDataError() { + this.series = []; + this.render(); + } + + seriesHandler(seriesData) { + let series = new TimeSeries({ + datapoints: seriesData.datapoints, + alias: seriesData.target + }); + + series.flotpairs = series.getFlotPairs(this.panel.nullPointMode); + series.minLog = getMinLog(series); + + let datapoints = seriesData.datapoints || []; + if (datapoints && datapoints.length > 0) { + let last = datapoints[datapoints.length - 1][1]; + let from = this.range.from; + if (last - from < -10000) { + series.isOutsideRange = true; + } + } + + return series; + } + + parseSeries(series) { + let min = _.min(_.map(series, s => s.stats.min)); + let minLog = _.min(_.map(series, s => s.minLog)); + let max = _.max(_.map(series, s => s.stats.max)); + + return { + max: max, + min: min, + minLog: minLog + }; + } + + parseHistogramSeries(series) { + let bounds = _.map(series, s => Number(s.alias)); + let min = _.min(bounds); + let minLog = _.min(bounds); + let max = _.max(bounds); + + return { + max: max, + min: min, + minLog: minLog + }; + } + + link(scope, elem, attrs, ctrl) { + rendering(scope, elem, attrs, ctrl); + } +} diff --git a/public/app/plugins/panel/heatmap/heatmap_data_converter.ts b/public/app/plugins/panel/heatmap/heatmap_data_converter.ts new file mode 100644 index 00000000000..ba1cd223584 --- /dev/null +++ b/public/app/plugins/panel/heatmap/heatmap_data_converter.ts @@ -0,0 +1,526 @@ +/// + +import _ from 'lodash'; +import TimeSeries from 'app/core/time_series2'; + +let VALUE_INDEX = 0; +let TIME_INDEX = 1; + +interface XBucket { + x: number; + buckets: any; +} + +interface YBucket { + y: number; + values: number[]; +} + +function elasticHistogramToHeatmap(series) { + let seriesBuckets = _.map(series, (s: TimeSeries) => { + return convertEsSeriesToHeatmap(s); + }); + let buckets = mergeBuckets(seriesBuckets); + return buckets; +} + +function convertEsSeriesToHeatmap(series: TimeSeries, saveZeroCounts = false) { + let xBuckets: XBucket[] = []; + + _.forEach(series.datapoints, point => { + let bound = series.alias; + let count = point[VALUE_INDEX]; + + if (!count) { + return; + } + + let values = new Array(Math.round(count)); + values.fill(Number(bound)); + + let valueBuckets = {}; + valueBuckets[bound] = { + y: Number(bound), + values: values + }; + + let xBucket: XBucket = { + x: point[TIME_INDEX], + buckets: valueBuckets + }; + + // Don't push buckets with 0 count until saveZeroCounts flag is set + if (count !== 0 || (count === 0 && saveZeroCounts)) { + xBuckets.push(xBucket); + } + }); + + let heatmap: any = {}; + _.forEach(xBuckets, (bucket: XBucket) => { + heatmap[bucket.x] = bucket; + }); + + return heatmap; +} + +/** + * Convert set of time series into heatmap buckets + * @return {Object} Heatmap object: + * { + * xBucketBound_1: { + * x: xBucketBound_1, + * buckets: { + * yBucketBound_1: { + * y: yBucketBound_1, + * bounds: {bottom, top} + * values: [val_1, val_2, ..., val_K], + * points: [[val_Y, val_X, series_name], ..., [...]], + * seriesStat: {seriesName_1: val_1, seriesName_2: val_2} + * }, + * ... + * yBucketBound_M: {} + * }, + * values: [val_1, val_2, ..., val_K], + * points: [ + * [val_Y, val_X, series_name], (point_1) + * ... + * [...] (point_K) + * ] + * }, + * xBucketBound_2: {}, + * ... + * xBucketBound_N: {} + * } + */ +function convertToHeatMap(series, yBucketSize, xBucketSize, logBase) { + let seriesBuckets = _.map(series, s => { + return seriesToHeatMap(s, yBucketSize, xBucketSize, logBase); + }); + + let buckets = mergeBuckets(seriesBuckets); + return buckets; +} + +/** + * Convert buckets into linear array of "cards" - objects, represented heatmap elements. + * @param {Object} buckets + * @return {Array} Array of "card" objects + */ +function convertToCards(buckets) { + let cards = []; + _.forEach(buckets, xBucket => { + _.forEach(xBucket.buckets, (yBucket, key) => { + if (yBucket.values.length) { + let card = { + x: Number(xBucket.x), + y: Number(key), + yBounds: yBucket.bounds, + values: yBucket.values, + seriesStat: getSeriesStat(yBucket.points) + }; + + cards.push(card); + } + }); + }); + + return cards; +} + +/** + * Special method for log scales. When series converted into buckets with log scale, + * for simplification, 0 values are converted into 0, not into -Infinity. On the other hand, we mean + * that all values less than series minimum, is 0 values, and we create special "minimum" bucket for + * that values (actually, there're no values less than minimum, so this bucket is empty). + * 8-16| | ** | | * | **| + * 4-8| * |* *|* |** *| * | + * 2-4| * *| | ***| |* | + * 1-2|* | | | | | This bucket contains minimum series value + * 0.5-1|____|____|____|____|____| This bucket should be displayed as 0 on graph + * 0|____|____|____|____|____| This bucket is for 0 values (should actually be -Infinity) + * So we should merge two bottom buckets into one (0-value bucket). + * + * @param {Object} buckets Heatmap buckets + * @param {Number} minValue Minimum series value + * @return {Object} Transformed buckets + */ +function mergeZeroBuckets(buckets, minValue) { + _.forEach(buckets, xBucket => { + let yBuckets = xBucket.buckets; + + let emptyBucket = { + bounds: {bottom: 0, top: 0}, + values: [], + points: [] + }; + + let nullBucket = yBuckets[0] || emptyBucket; + let minBucket = yBuckets[minValue] || emptyBucket; + + let newBucket = { + y: 0, + bounds: {bottom: minValue, top: minBucket.bounds.top || minValue}, + values: [], + points: [] + }; + + if (nullBucket.values) { + newBucket.values = nullBucket.values.concat(minBucket.values); + } + if (nullBucket.points) { + newBucket.points = nullBucket.points.concat(minBucket.points); + } + + let newYBuckets = {}; + _.forEach(yBuckets, (bucket, bound) => { + bound = Number(bound); + if (bound !== 0 && bound !== minValue) { + newYBuckets[bound] = bucket; + } + }); + newYBuckets[0] = newBucket; + xBucket.buckets = newYBuckets; + }); + + return buckets; +} + +/** + * Remove 0 values from heatmap buckets. + */ +function removeZeroBuckets(buckets) { + _.forEach(buckets, xBucket => { + let yBuckets = xBucket.buckets; + let newYBuckets = {}; + _.forEach(yBuckets, (bucket, bound) => { + if (bucket.y !== 0) { + newYBuckets[bound] = bucket; + } + }); + xBucket.buckets = newYBuckets; + }); + + return buckets; +} + +/** + * Count values number for each timeseries in given bucket + * @param {Array} points Bucket's datapoints with series name ([val, ts, series_name]) + * @return {Object} seriesStat: {seriesName_1: val_1, seriesName_2: val_2} + */ +function getSeriesStat(points) { + return _.countBy(points, p => p[2]); +} + +/** + * Convert individual series to heatmap buckets + */ +function seriesToHeatMap(series, yBucketSize, xBucketSize, logBase = 1) { + let datapoints = series.datapoints; + let seriesName = series.label; + let xBuckets = {}; + + // Slice series into X axis buckets + // | | ** | | * | **| + // | * |* *|* |** *| * | + // |** *| | ***| |* | + // |____|____|____|____|____|_ + // + _.forEach(datapoints, point => { + let bucketBound = getBucketBound(point[TIME_INDEX], xBucketSize); + pushToXBuckets(xBuckets, point, bucketBound, seriesName); + }); + + // Slice X axis buckets into Y (value) buckets + // | **| |2|, + // | * | --\ |1|, + // |* | --/ |1|, + // |____| |0| + // + _.forEach(xBuckets, xBucket => { + if (logBase !== 1) { + xBucket.buckets = convertToLogScaleValueBuckets(xBucket, yBucketSize, logBase); + } else { + xBucket.buckets = convertToValueBuckets(xBucket, yBucketSize); + } + }); + return xBuckets; +} + +function pushToXBuckets(buckets, point, bucketNum, seriesName) { + let value = point[VALUE_INDEX]; + if (value === null || value === undefined || isNaN(value)) { return; } + + // Add series name to point for future identification + point.push(seriesName); + + if (buckets[bucketNum] && buckets[bucketNum].values) { + buckets[bucketNum].values.push(value); + buckets[bucketNum].points.push(point); + } else { + buckets[bucketNum] = { + x: bucketNum, + values: [value], + points: [point] + }; + } +} + +function pushToYBuckets(buckets, bucketNum, value, point, bounds) { + if (buckets[bucketNum]) { + buckets[bucketNum].values.push(value); + buckets[bucketNum].points.push(point); + } else { + buckets[bucketNum] = { + y: bucketNum, + bounds: bounds, + values: [value], + points: [point] + }; + } +} + +function getValueBucketBound(value, yBucketSize, logBase) { + if (logBase === 1) { + return getBucketBound(value, yBucketSize); + } else { + return getLogScaleBucketBound(value, yBucketSize, logBase); + } +} + +/** + * Find bucket for given value (for linear scale) + */ +function getBucketBounds(value, bucketSize) { + let bottom, top; + bottom = Math.floor(value / bucketSize) * bucketSize; + top = (Math.floor(value / bucketSize) + 1) * bucketSize; + + return {bottom, top}; +} + +function getBucketBound(value, bucketSize) { + let bounds = getBucketBounds(value, bucketSize); + return bounds.bottom; +} + +function convertToValueBuckets(xBucket, bucketSize) { + let values = xBucket.values; + let points = xBucket.points; + let buckets = {}; + _.forEach(values, (val, index) => { + let bounds = getBucketBounds(val, bucketSize); + let bucketNum = bounds.bottom; + pushToYBuckets(buckets, bucketNum, val, points[index], bounds); + }); + + return buckets; +} + +/** + * Find bucket for given value (for log scales) + */ +function getLogScaleBucketBounds(value, yBucketSplitFactor, logBase) { + let top, bottom; + if (value === 0) { + return {bottom: 0, top: 0}; + } + + let value_log = logp(value, logBase); + let pow, powTop; + if (yBucketSplitFactor === 1 || !yBucketSplitFactor) { + pow = Math.floor(value_log); + powTop = pow + 1; + } else { + let additional_bucket_size = 1 / yBucketSplitFactor; + let additional_log = value_log - Math.floor(value_log); + additional_log = Math.floor(additional_log / additional_bucket_size) * additional_bucket_size; + pow = Math.floor(value_log) + additional_log; + powTop = pow + additional_bucket_size; + } + bottom = Math.pow(logBase, pow); + top = Math.pow(logBase, powTop); + + return {bottom, top}; +} + +function getLogScaleBucketBound(value, yBucketSplitFactor, logBase) { + let bounds = getLogScaleBucketBounds(value, yBucketSplitFactor, logBase); + return bounds.bottom; +} + +function convertToLogScaleValueBuckets(xBucket, yBucketSplitFactor, logBase) { + let values = xBucket.values; + let points = xBucket.points; + + let buckets = {}; + _.forEach(values, (val, index) => { + let bounds = getLogScaleBucketBounds(val, yBucketSplitFactor, logBase); + let bucketNum = bounds.bottom; + pushToYBuckets(buckets, bucketNum, val, points[index], bounds); + }); + + return buckets; +} + +/** + * Merge individual buckets for all series into one + * @param {Array} seriesBuckets Array of series buckets + * @return {Object} Merged buckets. + */ +function mergeBuckets(seriesBuckets) { + let mergedBuckets: any = {}; + _.forEach(seriesBuckets, (seriesBucket, index) => { + if (index === 0) { + mergedBuckets = seriesBucket; + } else { + _.forEach(seriesBucket, (xBucket, xBound) => { + if (mergedBuckets[xBound]) { + if (xBucket.points) { + mergedBuckets[xBound].points = xBucket.points.concat(mergedBuckets[xBound].points); + } + if (xBucket.values) { + mergedBuckets[xBound].values = xBucket.values.concat(mergedBuckets[xBound].values); + } + + _.forEach(xBucket.buckets, (yBucket, yBound) => { + let bucket = mergedBuckets[xBound].buckets[yBound]; + if (bucket && bucket.values) { + mergedBuckets[xBound].buckets[yBound].values = bucket.values.concat(yBucket.values); + + if (bucket.points) { + mergedBuckets[xBound].buckets[yBound].points = bucket.points.concat(yBucket.points); + } + } else { + mergedBuckets[xBound].buckets[yBound] = yBucket; + } + + let points = mergedBuckets[xBound].buckets[yBound].points; + if (points) { + mergedBuckets[xBound].buckets[yBound].seriesStat = getSeriesStat(points); + } + }); + } else { + mergedBuckets[xBound] = xBucket; + } + }); + } + }); + + return mergedBuckets; +} + +// Get minimum non zero value. +function getMinLog(series) { + let values = _.compact(_.map(series.datapoints, p => p[0])); + return _.min(values); +} + +/** + * Logarithm for custom base + * @param value + * @param base logarithm base + */ +function logp(value, base) { + return Math.log(value) / Math.log(base); +} + +/** + * Calculate size of Y bucket from given buckets bounds. + * @param bounds Array of Y buckets bounds + * @param logBase Logarithm base + */ +function calculateBucketSize(bounds: number[], logBase = 1): number { + let bucketSize = Infinity; + + if (bounds.length === 0) { + return 0; + } else if (bounds.length === 1) { + return bounds[0]; + } else { + bounds = _.sortBy(bounds); + for (let i = 1; i < bounds.length; i++) { + let distance = getDistance(bounds[i], bounds[i - 1], logBase); + bucketSize = distance < bucketSize ? distance : bucketSize; + } + } + + return bucketSize; +} + +/** + * Calculate distance between two numbers in given scale (linear or logarithmic). + * @param a + * @param b + * @param logBase + */ +function getDistance(a: number, b: number, logBase = 1): number { + if (logBase === 1) { + // Linear distance + return Math.abs(b - a); + } else { + // logarithmic distance + let ratio = Math.max(a, b) / Math.min(a, b); + return logp(ratio, logBase); + } +} + +/** + * Compare two heatmap data objects + * @param objA + * @param objB + */ +function isHeatmapDataEqual(objA: any, objB: any): boolean { + let is_eql = !emptyXOR(objA, objB); + + _.forEach(objA, (xBucket: XBucket, x) => { + if (objB[x]) { + if (emptyXOR(xBucket.buckets, objB[x].buckets)) { + is_eql = false; + return false; + } + + _.forEach(xBucket.buckets, (yBucket: YBucket, y) => { + if (objB[x].buckets && objB[x].buckets[y]) { + if (objB[x].buckets[y].values) { + is_eql = _.isEqual(_.sortBy(yBucket.values), _.sortBy(objB[x].buckets[y].values)); + if (!is_eql) { + return false; + } + } else { + is_eql = false; + return false; + } + } else { + is_eql = false; + return false; + } + }); + + if (!is_eql) { + return false; + } + } else { + is_eql = false; + return false; + } + }); + + return is_eql; +} + +function emptyXOR(foo: any, bar: any): boolean { + return (_.isEmpty(foo) || _.isEmpty(bar)) && !(_.isEmpty(foo) && _.isEmpty(bar)); +} + +export { + convertToHeatMap, + elasticHistogramToHeatmap, + convertToCards, + removeZeroBuckets, + mergeZeroBuckets, + getMinLog, + getValueBucketBound, + isHeatmapDataEqual, + calculateBucketSize +}; diff --git a/public/app/plugins/panel/heatmap/heatmap_tooltip.ts b/public/app/plugins/panel/heatmap/heatmap_tooltip.ts new file mode 100644 index 00000000000..c78fb35cfba --- /dev/null +++ b/public/app/plugins/panel/heatmap/heatmap_tooltip.ts @@ -0,0 +1,250 @@ +/// + +import d3 from 'd3'; +import $ from 'jquery'; +import _ from 'lodash'; +import kbn from 'app/core/utils/kbn'; +import {getValueBucketBound} from './heatmap_data_converter'; + +let TOOLTIP_PADDING_X = 30; +let TOOLTIP_PADDING_Y = 5; +let HISTOGRAM_WIDTH = 160; +let HISTOGRAM_HEIGHT = 40; + +export class HeatmapTooltip { + tooltip: any; + scope: any; + dashboard: any; + panel: any; + heatmapPanel: any; + mouseOverBucket: boolean; + originalFillColor: any; + + constructor(elem, scope) { + this.scope = scope; + this.dashboard = scope.ctrl.dashboard; + this.panel = scope.ctrl.panel; + this.heatmapPanel = elem; + this.mouseOverBucket = false; + this.originalFillColor = null; + + elem.on("mouseover", this.onMouseOver.bind(this)); + elem.on("mouseleave", this.onMouseLeave.bind(this)); + } + + onMouseOver(e) { + if (!this.panel.tooltip.show || _.isEmpty(this.scope.ctrl.data.buckets)) { return; } + + if (!this.tooltip) { + this.add(); + this.move(e); + } + } + + onMouseLeave() { + this.destroy(); + } + + onMouseMove(e) { + if (!this.panel.tooltip.show) { return; } + + this.move(e); + } + + add() { + this.tooltip = d3.select("body") + .append("div") + .attr("class", "heatmap-tooltip graph-tooltip grafana-tooltip"); + } + + destroy() { + if (this.tooltip) { + this.tooltip.remove(); + } + + this.tooltip = null; + } + + show(pos, data) { + if (!this.panel.tooltip.show || !data) { return; } + + let {xBucketIndex, yBucketIndex} = this.getBucketIndexes(pos, data); + + if (!data.buckets[xBucketIndex] || !this.tooltip) { + this.destroy(); + return; + } + + let boundBottom, boundTop, valuesNumber; + let xData = data.buckets[xBucketIndex]; + let yData = xData.buckets[yBucketIndex]; + + let tooltipTimeFormat = 'YYYY-MM-DD HH:mm:ss'; + let time = this.dashboard.formatDate(xData.x, tooltipTimeFormat); + let decimals = this.panel.tooltipDecimals || 5; + let valueFormatter = this.valueFormatter(decimals); + + let tooltipHtml = `
${time}
+
`; + + if (yData) { + boundBottom = valueFormatter(yData.bounds.bottom); + boundTop = valueFormatter(yData.bounds.top); + valuesNumber = yData.values.length; + tooltipHtml += `
+ bucket: ${boundBottom} - ${boundTop}
+ count: ${valuesNumber}
+
`; + + if (this.panel.tooltip.seriesStat && yData.seriesStat) { + tooltipHtml = this.addSeriesStat(tooltipHtml, yData.seriesStat); + } + } else { + if (!this.panel.tooltip.showHistogram) { + this.destroy(); + return; + } + boundBottom = yBucketIndex; + boundTop = ''; + valuesNumber = 0; + } + + this.tooltip.html(tooltipHtml); + + if (this.panel.tooltip.showHistogram) { + this.addHistogram(xData); + } + + this.move(pos); + } + + getBucketIndexes(pos, data) { + let xBucketIndex, yBucketIndex; + + // if panelRelY is defined another panel wants us to show a tooltip + if (pos.panelRelY) { + xBucketIndex = getValueBucketBound(pos.x, data.xBucketSize, 1); + let y = this.scope.yScale.invert(pos.panelRelY * this.scope.chartHeight); + yBucketIndex = getValueBucketBound(y, data.yBucketSize, this.panel.yAxis.logBase); + pos = this.getSharedTooltipPos(pos); + + if (!this.tooltip) { + // Add shared tooltip for panel + this.add(); + } + } else { + xBucketIndex = this.getXBucketIndex(pos.offsetX, data); + yBucketIndex = this.getYBucketIndex(pos.offsetY, data); + } + + return {xBucketIndex, yBucketIndex}; + } + + getXBucketIndex(offsetX, data) { + let x = this.scope.xScale.invert(offsetX - this.scope.yAxisWidth).valueOf(); + let xBucketIndex = getValueBucketBound(x, data.xBucketSize, 1); + return xBucketIndex; + } + + getYBucketIndex(offsetY, data) { + let y = this.scope.yScale.invert(offsetY - this.scope.chartTop); + let yBucketIndex = getValueBucketBound(y, data.yBucketSize, this.panel.yAxis.logBase); + return yBucketIndex; + } + + getSharedTooltipPos(pos) { + // get pageX from position on x axis and pageY from relative position in original panel + pos.pageX = this.heatmapPanel.offset().left + this.scope.xScale(pos.x); + pos.pageY = this.heatmapPanel.offset().top + this.scope.chartHeight * pos.panelRelY; + return pos; + } + + addSeriesStat(tooltipHtml, seriesStat) { + tooltipHtml += "series:
"; + _.forEach(seriesStat, (values, series) => { + tooltipHtml += `  -  ${series}: ${values}
`; + }); + + return tooltipHtml; + } + + addHistogram(data) { + let xBucket = this.scope.ctrl.data.buckets[data.x]; + let yBucketSize = this.scope.ctrl.data.yBucketSize; + let {min, max, ticks} = this.scope.ctrl.data.yAxis; + let histogramData = _.map(xBucket.buckets, bucket => { + return [bucket.y, bucket.values.length]; + }); + histogramData = _.filter(histogramData, d => { + return d[0] >= min && d[0] <= max; + }); + + let scale = this.scope.yScale.copy(); + let histXScale = scale + .domain([min, max]) + .range([0, HISTOGRAM_WIDTH]); + + let barWidth; + if (this.panel.yAxis.logBase === 1) { + barWidth = Math.floor(HISTOGRAM_WIDTH / (max - min) * yBucketSize * 0.9); + } else { + barWidth = Math.floor(HISTOGRAM_WIDTH / ticks / yBucketSize * 0.9); + } + barWidth = Math.max(barWidth, 1); + + let histYScale = d3.scaleLinear() + .domain([0, _.max(_.map(histogramData, d => d[1]))]) + .range([0, HISTOGRAM_HEIGHT]); + + let histogram = this.tooltip.select(".heatmap-histogram") + .append("svg") + .attr("width", HISTOGRAM_WIDTH) + .attr("height", HISTOGRAM_HEIGHT); + + histogram.selectAll(".bar").data(histogramData) + .enter().append("rect") + .attr("x", d => { + return histXScale(d[0]); + }) + .attr("width", barWidth) + .attr("y", d => { + return HISTOGRAM_HEIGHT - histYScale(d[1]); + }) + .attr("height", d => { + return histYScale(d[1]); + }); + } + + move(pos) { + if (!this.tooltip) { return; } + + let elem = $(this.tooltip.node())[0]; + let tooltipWidth = elem.clientWidth; + let tooltipHeight = elem.clientHeight; + + let left = pos.pageX + TOOLTIP_PADDING_X; + let top = pos.pageY + TOOLTIP_PADDING_Y; + + if (pos.pageX + tooltipWidth + 40 > window.innerWidth) { + left = pos.pageX - tooltipWidth - TOOLTIP_PADDING_X; + } + + if (pos.pageY - window.pageYOffset + tooltipHeight + 20 > window.innerHeight) { + top = pos.pageY - tooltipHeight - TOOLTIP_PADDING_Y; + } + + return this.tooltip + .style("left", left + "px") + .style("top", top + "px"); + } + + valueFormatter(decimals) { + let format = this.panel.yAxis.format; + return function(value) { + if (_.isInteger(value)) { + decimals = 0; + } + return kbn.valueFormats[format](value, decimals); + }; + } +} diff --git a/public/app/plugins/panel/heatmap/img/icn-heatmap-panel.svg b/public/app/plugins/panel/heatmap/img/icn-heatmap-panel.svg new file mode 100644 index 00000000000..29838f76ca6 --- /dev/null +++ b/public/app/plugins/panel/heatmap/img/icn-heatmap-panel.svg @@ -0,0 +1,195 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/public/app/plugins/panel/heatmap/module.html b/public/app/plugins/panel/heatmap/module.html new file mode 100644 index 00000000000..a59092b2687 --- /dev/null +++ b/public/app/plugins/panel/heatmap/module.html @@ -0,0 +1,12 @@ +
+
+ +
+ {{ctrl.dataWarning.title}} +
+ +
+
+ +
+
diff --git a/public/app/plugins/panel/heatmap/module.ts b/public/app/plugins/panel/heatmap/module.ts new file mode 100644 index 00000000000..d6926455563 --- /dev/null +++ b/public/app/plugins/panel/heatmap/module.ts @@ -0,0 +1,7 @@ +/// + +import {HeatmapCtrl} from './heatmap_ctrl'; + +export { + HeatmapCtrl as PanelCtrl +}; diff --git a/public/app/plugins/panel/heatmap/partials/axes_editor.html b/public/app/plugins/panel/heatmap/partials/axes_editor.html new file mode 100644 index 00000000000..161be2dab19 --- /dev/null +++ b/public/app/plugins/panel/heatmap/partials/axes_editor.html @@ -0,0 +1,95 @@ +
+
+
Y Axis
+ + +
+ +
+
+
+
+ +
+ +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+
+
+ + +
+
+ + +
+
+
+
+ + +
+ + +
+
+ +
+
X Axis
+ + +
+ + +
+
+ + +
+
+ +
+
Data format
+
+ +
+ +
+
+
+
diff --git a/public/app/plugins/panel/heatmap/partials/display_editor.html b/public/app/plugins/panel/heatmap/partials/display_editor.html new file mode 100644 index 00000000000..92409b3ad5e --- /dev/null +++ b/public/app/plugins/panel/heatmap/partials/display_editor.html @@ -0,0 +1,87 @@ +
+
+
Colors
+
+ +
+ +
+
+ +
+
+ + + + +
+
+ +
+ +
+
+
+ + +
+
+ +
+
+ +
+
+ +
+ +
+
+
+ +
+ + +
+
+ +
+
Cards
+
+ + +
+
+ + +
+
+ +
+
Tooltip
+ + +
+ + + + + + +
+ + +
+
+
+
diff --git a/public/app/plugins/panel/heatmap/plugin.json b/public/app/plugins/panel/heatmap/plugin.json new file mode 100644 index 00000000000..bee509467a7 --- /dev/null +++ b/public/app/plugins/panel/heatmap/plugin.json @@ -0,0 +1,16 @@ +{ + "type": "panel", + "name": "Heatmap", + "id": "heatmap", + + "info": { + "author": { + "name": "Grafana Project", + "url": "https://grafana.com" + }, + "logos": { + "small": "img/icn-heatmap-panel.svg", + "large": "img/icn-heatmap-panel.svg" + } + } +} diff --git a/public/app/plugins/panel/heatmap/rendering.ts b/public/app/plugins/panel/heatmap/rendering.ts new file mode 100644 index 00000000000..a0f6786baf1 --- /dev/null +++ b/public/app/plugins/panel/heatmap/rendering.ts @@ -0,0 +1,857 @@ +/// + +import _ from 'lodash'; +import $ from 'jquery'; +import moment from 'moment'; +import kbn from 'app/core/utils/kbn'; +import {appEvents, contextSrv} from 'app/core/core'; +import {tickStep} from 'app/core/utils/ticks'; +import d3 from 'd3'; +import {HeatmapTooltip} from './heatmap_tooltip'; +import {convertToCards, mergeZeroBuckets, removeZeroBuckets} from './heatmap_data_converter'; + +let MIN_CARD_SIZE = 1, + CARD_PADDING = 1, + CARD_ROUND = 0, + DATA_RANGE_WIDING_FACTOR = 1.2, + DEFAULT_X_TICK_SIZE_PX = 100, + DEFAULT_Y_TICK_SIZE_PX = 50, + X_AXIS_TICK_PADDING = 10, + Y_AXIS_TICK_PADDING = 5, + MIN_SELECTION_WIDTH = 2; + +export default function link(scope, elem, attrs, ctrl) { + let data, timeRange, panel, heatmap; + + // $heatmap is JQuery object, but heatmap is D3 + let $heatmap = elem.find('.heatmap-panel'); + let tooltip = new HeatmapTooltip($heatmap, scope); + + let width, height, + yScale, xScale, + chartWidth, chartHeight, + chartTop, chartBottom, + yAxisWidth, xAxisHeight, + cardPadding, cardRound, + cardWidth, cardHeight, + colorScale, opacityScale, + mouseUpHandler; + + let selection = { + active: false, + x1: -1, + x2: -1 + }; + + let padding = {left: 0, right: 0, top: 0, bottom: 0}, + margin = {left: 25, right: 15, top: 10, bottom: 20}, + dataRangeWidingFactor = DATA_RANGE_WIDING_FACTOR; + + ctrl.events.on('render', () => { + render(); + ctrl.renderingCompleted(); + }); + + function setElementHeight() { + try { + var height = ctrl.height || panel.height || ctrl.row.height; + if (_.isString(height)) { + height = parseInt(height.replace('px', ''), 10); + } + + height -= 5; // padding + height -= panel.title ? 24 : 9; // subtract panel title bar + + $heatmap.css('height', height + 'px'); + + return true; + } catch (e) { // IE throws errors sometimes + return false; + } + } + + function getYAxisWidth(elem) { + let axis_text = elem.selectAll(".axis-y text").nodes(); + let max_text_width = _.max(_.map(axis_text, text => { + let el = $(text); + // Use JQuery outerWidth() to compute full element width + return el.outerWidth(); + })); + + return max_text_width; + } + + function getXAxisHeight(elem) { + let axis_line = elem.select(".axis-x line"); + if (!axis_line.empty()) { + let axis_line_position = parseFloat(elem.select(".axis-x line").attr("y2")); + let canvas_width = parseFloat(elem.attr("height")); + return canvas_width - axis_line_position; + } else { + // Default height + return 30; + } + } + + function addXAxis() { + xScale = d3.scaleTime() + .domain([timeRange.from, timeRange.to]) + .range([0, chartWidth]); + + let ticks = chartWidth / DEFAULT_X_TICK_SIZE_PX; + let grafanaTimeFormatter = grafanaTimeFormat(ticks, timeRange.from, timeRange.to); + + let xAxis = d3.axisBottom(xScale) + .ticks(ticks) + .tickFormat(d3.timeFormat(grafanaTimeFormatter)) + .tickPadding(X_AXIS_TICK_PADDING) + .tickSize(chartHeight); + + let posY = margin.top; + let posX = yAxisWidth; + heatmap.append("g") + .attr("class", "axis axis-x") + .attr("transform", "translate(" + posX + "," + posY + ")") + .call(xAxis); + + // Remove horizontal line in the top of axis labels (called domain in d3) + heatmap.select(".axis-x").select(".domain").remove(); + } + + function addYAxis() { + let ticks = Math.ceil(chartHeight / DEFAULT_Y_TICK_SIZE_PX); + let tick_interval = tickStep(data.heatmapStats.min, data.heatmapStats.max, ticks); + let {y_min, y_max} = wideYAxisRange(data.heatmapStats.min, data.heatmapStats.max, tick_interval); + + // Rewrite min and max if it have been set explicitly + y_min = panel.yAxis.min !== null ? panel.yAxis.min : y_min; + y_max = panel.yAxis.max !== null ? panel.yAxis.max : y_max; + + // Adjust ticks after Y range widening + tick_interval = tickStep(y_min, y_max, ticks); + ticks = Math.ceil((y_max - y_min) / tick_interval); + + let decimals = panel.yAxis.decimals === null ? getPrecision(tick_interval) : panel.yAxis.decimals; + + // Set default Y min and max if no data + if (_.isEmpty(data.buckets)) { + y_max = 1; + y_min = -1; + ticks = 3; + decimals = 1; + } + + data.yAxis = { + min: y_min, + max: y_max, + ticks: ticks + }; + + yScale = d3.scaleLinear() + .domain([y_min, y_max]) + .range([chartHeight, 0]); + + let yAxis = d3.axisLeft(yScale) + .ticks(ticks) + .tickFormat(tickValueFormatter(decimals)) + .tickSizeInner(0 - width) + .tickSizeOuter(0) + .tickPadding(Y_AXIS_TICK_PADDING); + + heatmap.append("g") + .attr("class", "axis axis-y") + .call(yAxis); + + // Calculate Y axis width first, then move axis into visible area + let posY = margin.top; + let posX = getYAxisWidth(heatmap) + Y_AXIS_TICK_PADDING; + heatmap.select(".axis-y").attr("transform", "translate(" + posX + "," + posY + ")"); + + // Remove vertical line in the right of axis labels (called domain in d3) + heatmap.select(".axis-y").select(".domain").remove(); + } + + // Wide Y values range and anjust to bucket size + function wideYAxisRange(min, max, tickInterval) { + let y_widing = (max * (dataRangeWidingFactor - 1) - min * (dataRangeWidingFactor - 1)) / 2; + let y_min, y_max; + + if (tickInterval === 0) { + y_max = max * dataRangeWidingFactor; + y_min = min - min * (dataRangeWidingFactor - 1); + tickInterval = (y_max - y_min) / 2; + } else { + y_max = Math.ceil((max + y_widing) / tickInterval) * tickInterval; + y_min = Math.floor((min - y_widing) / tickInterval) * tickInterval; + } + + // Don't wide axis below 0 if all values are positive + if (min >= 0 && y_min < 0) { + y_min = 0; + } + + return {y_min, y_max}; + } + + function addLogYAxis() { + let log_base = panel.yAxis.logBase; + let {y_min, y_max} = adjustLogRange(data.heatmapStats.minLog, data.heatmapStats.max, log_base); + + y_min = panel.yAxis.min !== null ? adjustLogMin(panel.yAxis.min, log_base) : y_min; + y_max = panel.yAxis.max !== null ? adjustLogMax(panel.yAxis.max, log_base) : y_max; + + // Set default Y min and max if no data + if (_.isEmpty(data.buckets)) { + y_max = Math.pow(log_base, 2); + y_min = 1; + } + + yScale = d3.scaleLog() + .base(panel.yAxis.logBase) + .domain([y_min, y_max]) + .range([chartHeight, 0]); + + let domain = yScale.domain(); + let tick_values = logScaleTickValues(domain, log_base); + let decimals = panel.yAxis.decimals; + + data.yAxis = { + min: y_min, + max: y_max, + ticks: tick_values.length + }; + + let yAxis = d3.axisLeft(yScale) + .tickValues(tick_values) + .tickFormat(tickValueFormatter(decimals)) + .tickSizeInner(0 - width) + .tickSizeOuter(0) + .tickPadding(Y_AXIS_TICK_PADDING); + + heatmap.append("g") + .attr("class", "axis axis-y") + .call(yAxis); + + // Calculate Y axis width first, then move axis into visible area + let posY = margin.top; + let posX = getYAxisWidth(heatmap) + Y_AXIS_TICK_PADDING; + heatmap.select(".axis-y").attr("transform", "translate(" + posX + "," + posY + ")"); + + // Set first tick as pseudo 0 + if (y_min < 1) { + heatmap.select(".axis-y").select(".tick text").text("0"); + } + + // Remove vertical line in the right of axis labels (called domain in d3) + heatmap.select(".axis-y").select(".domain").remove(); + } + + // Adjust data range to log base + function adjustLogRange(min, max, logBase) { + let y_min, y_max; + + y_min = data.heatmapStats.minLog; + if (data.heatmapStats.minLog > 1 || !data.heatmapStats.minLog) { + y_min = 1; + } else { + y_min = adjustLogMin(data.heatmapStats.minLog, logBase); + } + + // Adjust max Y value to log base + y_max = adjustLogMax(data.heatmapStats.max, logBase); + + return {y_min, y_max}; + } + + function adjustLogMax(max, base) { + return Math.pow(base, Math.ceil(logp(max, base))); + } + + function adjustLogMin(min, base) { + return Math.pow(base, Math.floor(logp(min, base))); + } + + function logScaleTickValues(domain, base) { + let domainMin = domain[0]; + let domainMax = domain[1]; + let tickValues = []; + + if (domainMin < 1) { + let under_one_ticks = Math.floor(logp(domainMin, base)); + for (let i = under_one_ticks; i < 0; i++) { + let tick_value = Math.pow(base, i); + tickValues.push(tick_value); + } + } + + let ticks = Math.ceil(logp(domainMax, base)); + for (let i = 0; i <= ticks; i++) { + let tick_value = Math.pow(base, i); + tickValues.push(tick_value); + } + + return tickValues; + } + + function tickValueFormatter(decimals) { + let format = panel.yAxis.format; + return function(value) { + return kbn.valueFormats[format](value, decimals); + }; + } + + function fixYAxisTickSize() { + heatmap.select(".axis-y") + .selectAll(".tick line") + .attr("x2", chartWidth); + } + + function addAxes() { + chartHeight = height - margin.top - margin.bottom; + chartTop = margin.top; + chartBottom = chartTop + chartHeight; + + if (panel.yAxis.logBase === 1) { + addYAxis(); + } else { + addLogYAxis(); + } + + yAxisWidth = getYAxisWidth(heatmap) + Y_AXIS_TICK_PADDING; + chartWidth = width - yAxisWidth - margin.right; + fixYAxisTickSize(); + + addXAxis(); + xAxisHeight = getXAxisHeight(heatmap); + + if (!panel.yAxis.show) { + heatmap.select(".axis-y").selectAll("line").style("opacity", 0); + } + + if (!panel.xAxis.show) { + heatmap.select(".axis-x").selectAll("line").style("opacity", 0); + } + } + + function addHeatmapCanvas() { + let heatmap_elem = $heatmap[0]; + + width = Math.floor($heatmap.width()) - padding.right; + height = Math.floor($heatmap.height()) - padding.bottom; + + cardPadding = panel.cards.cardPadding !== null ? panel.cards.cardPadding : CARD_PADDING; + cardRound = panel.cards.cardRound !== null ? panel.cards.cardRound : CARD_ROUND; + + if (heatmap) { + heatmap.remove(); + } + + heatmap = d3.select(heatmap_elem) + .append("svg") + .attr("width", width) + .attr("height", height); + } + + function addHeatmap() { + addHeatmapCanvas(); + addAxes(); + + if (panel.yAxis.logBase !== 1) { + if (panel.yAxis.removeZeroValues) { + data.buckets = removeZeroBuckets(data.buckets); + } else { + let log_base = panel.yAxis.logBase; + let domain = yScale.domain(); + let tick_values = logScaleTickValues(domain, log_base); + data.buckets = mergeZeroBuckets(data.buckets, _.min(tick_values)); + } + } + let cardsData = convertToCards(data.buckets); + + let max_value = d3.max(cardsData, card => { + return card.values.length; + }); + + colorScale = getColorScale(max_value); + setOpacityScale(max_value); + setCardSize(); + + if (panel.color.fillBackground && panel.color.mode === 'spectrum') { + fillBackground(heatmap, colorScale(0)); + } + + let cards = heatmap.selectAll(".heatmap-card").data(cardsData); + cards.append("title"); + cards = cards.enter().append("rect") + .attr("x", getCardX) + .attr("width", getCardWidth) + .attr("y", getCardY) + .attr("height", getCardHeight) + .attr("rx", cardRound) + .attr("ry", cardRound) + .attr("class", "bordered heatmap-card") + .style("fill", getCardColor) + .style("stroke", getCardColor) + .style("stroke-width", 0) + .style("opacity", getCardOpacity); + + let $cards = $heatmap.find(".heatmap-card"); + $cards.on("mouseenter", (event) => { + tooltip.mouseOverBucket = true; + highlightCard(event); + }) + .on("mouseleave", (event) => { + tooltip.mouseOverBucket = false; + resetCardHighLight(event); + }); + } + + function highlightCard(event) { + if (panel.highlightCards) { + let color = d3.select(event.target).style("fill"); + let highlightColor = d3.color(color).darker(2); + let strokeColor = d3.color(color).brighter(4); + let current_card = d3.select(event.target); + tooltip.originalFillColor = color; + current_card.style("fill", highlightColor) + .style("stroke", strokeColor) + .style("stroke-width", 1); + } + } + + function resetCardHighLight(event) { + if (panel.highlightCards) { + d3.select(event.target).style("fill", tooltip.originalFillColor) + .style("stroke", tooltip.originalFillColor) + .style("stroke-width", 0); + } + } + + function getColorScale(maxValue) { + let colorScheme = _.find(ctrl.colorSchemes, {value: panel.color.colorScheme}); + let colorInterpolator = d3[colorScheme.value]; + let colorScaleInverted = colorScheme.invert === 'always' || + (colorScheme.invert === 'dark' && !contextSrv.user.lightTheme); + + let start = colorScaleInverted ? maxValue : 0; + let end = colorScaleInverted ? 0 : maxValue; + + return d3.scaleSequential(colorInterpolator).domain([start, end]); + } + + function setOpacityScale(max_value) { + if (panel.color.colorScale === 'linear') { + opacityScale = d3.scaleLinear() + .domain([0, max_value]) + .range([0, 1]); + } else if (panel.color.colorScale === 'sqrt') { + opacityScale = d3.scalePow().exponent(panel.color.exponent) + .domain([0, max_value]) + .range([0, 1]); + } + } + + function setCardSize() { + let xGridSize = Math.floor(xScale(data.xBucketSize) - xScale(0)); + let yGridSize = Math.floor(yScale(yScale.invert(0) - data.yBucketSize)); + + if (panel.yAxis.logBase !== 1) { + let base = panel.yAxis.logBase; + let splitFactor = data.yBucketSize || 1; + yGridSize = Math.floor((yScale(1) - yScale(base)) / splitFactor); + } + + cardWidth = xGridSize - cardPadding * 2; + cardHeight = yGridSize ? yGridSize - cardPadding * 2 : 0; + } + + function getCardX(d) { + let x; + if (xScale(d.x) < 0) { + // Cut card left to prevent overlay + x = yAxisWidth + cardPadding; + } else { + x = xScale(d.x) + yAxisWidth + cardPadding; + } + + return x; + } + + function getCardWidth(d) { + let w; + if (xScale(d.x) < 0) { + // Cut card left to prevent overlay + let cutted_width = xScale(d.x) + cardWidth; + w = cutted_width > 0 ? cutted_width : 0; + } else if (xScale(d.x) + cardWidth > chartWidth) { + // Cut card right to prevent overlay + w = chartWidth - xScale(d.x) - cardPadding; + } else { + w = cardWidth; + } + + // Card width should be MIN_CARD_SIZE at least + w = Math.max(w, MIN_CARD_SIZE); + return w; + } + + function getCardY(d) { + let y = yScale(d.y) + chartTop - cardHeight - cardPadding; + if (panel.yAxis.logBase !== 1 && d.y === 0) { + y = chartBottom - cardHeight - cardPadding; + } else { + if (y < chartTop) { + y = chartTop; + } + } + + return y; + } + + function getCardHeight(d) { + let y = yScale(d.y) + chartTop - cardHeight - cardPadding; + let h = cardHeight; + + if (panel.yAxis.logBase !== 1 && d.y === 0) { + return cardHeight; + } + + // Cut card height to prevent overlay + if (y < chartTop) { + h = yScale(d.y) - cardPadding; + } else if (yScale(d.y) > chartBottom) { + h = chartBottom - y; + } else if (y + cardHeight > chartBottom) { + h = chartBottom - y; + } + + // Height can't be more than chart height + h = Math.min(h, chartHeight); + // Card height should be MIN_CARD_SIZE at least + h = Math.max(h, MIN_CARD_SIZE); + + return h; + } + + function getCardColor(d) { + if (panel.color.mode === 'opacity') { + return panel.color.cardColor; + } else { + return colorScale(d.values.length); + } + } + + function getCardOpacity(d) { + if (panel.color.mode === 'opacity') { + return opacityScale(d.values.length); + } else { + return 1; + } + } + + function fillBackground(heatmap, color) { + heatmap.insert("rect", "g") + .attr("x", yAxisWidth) + .attr("y", margin.top) + .attr("width", chartWidth) + .attr("height", chartHeight) + .attr("fill", color); + } + + ///////////////////////////// + // Selection and crosshair // + ///////////////////////////// + + // Shared crosshair and tooltip + appEvents.on('graph-hover', event => { + drawSharedCrosshair(event.pos); + + // Show shared tooltip + if (ctrl.dashboard.graphTooltip === 2) { + tooltip.show(event.pos, data); + } + }); + + appEvents.on('graph-hover-clear', () => { + clearCrosshair(); + tooltip.destroy(); + }); + + function onMouseDown(event) { + selection.active = true; + selection.x1 = event.offsetX; + + mouseUpHandler = function() { + onMouseUp(); + }; + $(document).one("mouseup", mouseUpHandler); + } + + function onMouseUp() { + $(document).unbind("mouseup", mouseUpHandler); + mouseUpHandler = null; + selection.active = false; + + let selectionRange = Math.abs(selection.x2 - selection.x1); + if (selection.x2 >= 0 && selectionRange > MIN_SELECTION_WIDTH) { + let timeFrom = xScale.invert(Math.min(selection.x1, selection.x2) - yAxisWidth); + let timeTo = xScale.invert(Math.max(selection.x1, selection.x2) - yAxisWidth); + + ctrl.timeSrv.setTime({ + from: moment.utc(timeFrom), + to: moment.utc(timeTo) + }); + } + + clearSelection(); + } + + function onMouseLeave() { + appEvents.emit('graph-hover-clear'); + clearCrosshair(); + } + + function onMouseMove(event) { + if (!heatmap) { return; } + + if (selection.active) { + // Clear crosshair and tooltip + clearCrosshair(); + tooltip.destroy(); + + selection.x2 = limitSelection(event.offsetX); + drawSelection(selection.x1, selection.x2); + } else { + emitGraphHoverEvet(event); + drawCrosshair(event.offsetX); + tooltip.show(event, data); + } + } + + function emitGraphHoverEvet(event) { + let x = xScale.invert(event.offsetX - yAxisWidth).valueOf(); + let y = yScale.invert(event.offsetY); + let pos = { + pageX: event.pageX, + pageY: event.pageY, + x: x, x1: x, + y: y, y1: y, + panelRelY: null + }; + + // Set minimum offset to prevent showing legend from another panel + pos.panelRelY = Math.max(event.offsetY / height, 0.001); + + // broadcast to other graph panels that we are hovering + appEvents.emit('graph-hover', {pos: pos, panel: panel}); + } + + function limitSelection(x2) { + x2 = Math.max(x2, yAxisWidth); + x2 = Math.min(x2, chartWidth + yAxisWidth); + return x2; + } + + function drawSelection(posX1, posX2) { + if (heatmap) { + heatmap.selectAll(".heatmap-selection").remove(); + let selectionX = Math.min(posX1, posX2); + let selectionWidth = Math.abs(posX1 - posX2); + + if (selectionWidth > MIN_SELECTION_WIDTH) { + heatmap.append("rect") + .attr("class", "heatmap-selection") + .attr("x", selectionX) + .attr("width", selectionWidth) + .attr("y", chartTop) + .attr("height", chartHeight); + } + } + } + + function clearSelection() { + selection.x1 = -1; + selection.x2 = -1; + + if (heatmap) { + heatmap.selectAll(".heatmap-selection").remove(); + } + } + + function drawCrosshair(position) { + if (heatmap) { + heatmap.selectAll(".heatmap-crosshair").remove(); + + let posX = position; + posX = Math.max(posX, yAxisWidth); + posX = Math.min(posX, chartWidth + yAxisWidth); + + heatmap.append("g") + .attr("class", "heatmap-crosshair") + .attr("transform", "translate(" + posX + ",0)") + .append("line") + .attr("x1", 1) + .attr("y1", chartTop) + .attr("x2", 1) + .attr("y2", chartBottom) + .attr("stroke-width", 1); + } + } + + function drawSharedCrosshair(pos) { + if (heatmap && ctrl.dashboard.graphTooltip !== 0) { + let posX = xScale(pos.x) + yAxisWidth; + drawCrosshair(posX); + } + } + + function clearCrosshair() { + if (heatmap) { + heatmap.selectAll(".heatmap-crosshair").remove(); + } + } + + function drawColorLegend() { + d3.select("#heatmap-color-legend").selectAll("rect").remove(); + + let legend = d3.select("#heatmap-color-legend"); + let legendWidth = Math.floor($(d3.select("#heatmap-color-legend").node()).outerWidth()); + let legendHeight = d3.select("#heatmap-color-legend").attr("height"); + + let legendColorScale = getColorScale(legendWidth); + + let rangeStep = 2; + let valuesRange = d3.range(0, legendWidth, rangeStep); + var legendRects = legend.selectAll(".heatmap-color-legend-rect").data(valuesRange); + + legendRects.enter().append("rect") + .attr("x", d => d) + .attr("y", 0) + .attr("width", rangeStep + 1) // Overlap rectangles to prevent gaps + .attr("height", legendHeight) + .attr("stroke-width", 0) + .attr("fill", d => { + return legendColorScale(d); + }); + } + + function drawOpacityLegend() { + d3.select("#heatmap-opacity-legend").selectAll("rect").remove(); + + let legend = d3.select("#heatmap-opacity-legend"); + let legendWidth = Math.floor($(d3.select("#heatmap-opacity-legend").node()).outerWidth()); + let legendHeight = d3.select("#heatmap-opacity-legend").attr("height"); + + let legendOpacityScale; + if (panel.color.colorScale === 'linear') { + legendOpacityScale = d3.scaleLinear() + .domain([0, legendWidth]) + .range([0, 1]); + } else if (panel.color.colorScale === 'sqrt') { + legendOpacityScale = d3.scalePow().exponent(panel.color.exponent) + .domain([0, legendWidth]) + .range([0, 1]); + } + + let rangeStep = 1; + let valuesRange = d3.range(0, legendWidth, rangeStep); + var legendRects = legend.selectAll(".heatmap-opacity-legend-rect").data(valuesRange); + + legendRects.enter().append("rect") + .attr("x", d => d) + .attr("y", 0) + .attr("width", rangeStep) + .attr("height", legendHeight) + .attr("stroke-width", 0) + .attr("fill", panel.color.cardColor) + .style("opacity", d => { + return legendOpacityScale(d); + }); + } + + function render() { + data = ctrl.data; + panel = ctrl.panel; + timeRange = ctrl.range; + + if (setElementHeight()) { + + if (data) { + // Draw default axes and return if no data + if (_.isEmpty(data.buckets)) { + addHeatmapCanvas(); + addAxes(); + return; + } + + addHeatmap(); + scope.yScale = yScale; + scope.xScale = xScale; + scope.yAxisWidth = yAxisWidth; + scope.xAxisHeight = xAxisHeight; + scope.chartHeight = chartHeight; + scope.chartWidth = chartWidth; + scope.chartTop = chartTop; + + // Register selection listeners + $heatmap.on("mousedown", onMouseDown); + $heatmap.on("mousemove", onMouseMove); + $heatmap.on("mouseleave", onMouseLeave); + } else { + return; + } + } + + // Draw only if color editor is opened + if (!d3.select("#heatmap-color-legend").empty()) { + drawColorLegend(); + } + if (!d3.select("#heatmap-opacity-legend").empty()) { + drawOpacityLegend(); + } + } +} + +function grafanaTimeFormat(ticks, min, max) { + if (min && max && ticks) { + let range = max - min; + let secPerTick = (range/ticks) / 1000; + let oneDay = 86400000; + let oneYear = 31536000000; + + if (secPerTick <= 45) { + return "%H:%M:%S"; + } + if (secPerTick <= 7200 || range <= oneDay) { + return "%H:%M"; + } + if (secPerTick <= 80000) { + return "%m/%d %H:%M"; + } + if (secPerTick <= 2419200 || range <= oneYear) { + return "%m/%d"; + } + return "%Y-%m"; + } + + return "%H:%M"; +} + +function logp(value, base) { + return Math.log(value) / Math.log(base); +} + +function getPrecision(num) { + let str = num.toString(); + let dot_index = str.indexOf("."); + if (dot_index === -1) { + return 0; + } else { + return str.length - dot_index - 1; + } +} + +function getTicksPrecision(values) { + let precisions = _.map(values, getPrecision); + return _.max(precisions); +} diff --git a/public/app/plugins/panel/heatmap/specs/heatmap_ctrl_specs.ts b/public/app/plugins/panel/heatmap/specs/heatmap_ctrl_specs.ts new file mode 100644 index 00000000000..1d82ba01c23 --- /dev/null +++ b/public/app/plugins/panel/heatmap/specs/heatmap_ctrl_specs.ts @@ -0,0 +1,76 @@ +/// + +import {describe, beforeEach, it, sinon, expect, angularMocks} from '../../../../../test/lib/common'; + +import angular from 'angular'; +import moment from 'moment'; +import {HeatmapCtrl} from '../heatmap_ctrl'; +import helpers from '../../../../../test/specs/helpers'; + +describe('HeatmapCtrl', function() { + var ctx = new helpers.ControllerTestContext(); + + beforeEach(angularMocks.module('grafana.services')); + beforeEach(angularMocks.module('grafana.controllers')); + beforeEach(angularMocks.module(function($compileProvider) { + $compileProvider.preAssignBindingsEnabled(true); + })); + + beforeEach(ctx.providePhase()); + beforeEach(ctx.createPanelController(HeatmapCtrl)); + beforeEach(() => { + ctx.ctrl.annotationsPromise = Promise.resolve({}); + ctx.ctrl.updateTimeRange(); + }); + + describe('when time series are outside range', function() { + + beforeEach(function() { + var data = [ + {target: 'test.cpu1', datapoints: [[45, 1234567890], [60, 1234567899]]}, + ]; + + ctx.ctrl.range = {from: moment().valueOf(), to: moment().valueOf()}; + ctx.ctrl.onDataReceived(data); + }); + + it('should set datapointsOutside', function() { + expect(ctx.ctrl.dataWarning.title).to.be('Data points outside time range'); + }); + }); + + describe('when time series are inside range', function() { + beforeEach(function() { + var range = { + from: moment().subtract(1, 'days').valueOf(), + to: moment().valueOf() + }; + + var data = [ + {target: 'test.cpu1', datapoints: [[45, range.from + 1000], [60, range.from + 10000]]}, + ]; + + ctx.ctrl.range = range; + ctx.ctrl.onDataReceived(data); + }); + + it('should set datapointsOutside', function() { + expect(ctx.ctrl.dataWarning).to.be(null); + }); + }); + + describe('datapointsCount given 2 series', function() { + beforeEach(function() { + var data = [ + {target: 'test.cpu1', datapoints: []}, + {target: 'test.cpu2', datapoints: []}, + ]; + ctx.ctrl.onDataReceived(data); + }); + + it('should set datapointsCount warning', function() { + expect(ctx.ctrl.dataWarning.title).to.be('No data points'); + }); + }); + +}); diff --git a/public/app/plugins/panel/heatmap/specs/heatmap_data_converter_specs.ts b/public/app/plugins/panel/heatmap/specs/heatmap_data_converter_specs.ts new file mode 100644 index 00000000000..b1019b94c56 --- /dev/null +++ b/public/app/plugins/panel/heatmap/specs/heatmap_data_converter_specs.ts @@ -0,0 +1,253 @@ +/// + +import _ from 'lodash'; +import { describe, beforeEach, it, sinon, expect, angularMocks } from '../../../../../test/lib/common'; +import TimeSeries from 'app/core/time_series2'; +import { convertToHeatMap, elasticHistogramToHeatmap, calculateBucketSize, isHeatmapDataEqual } from '../heatmap_data_converter'; + +describe('isHeatmapDataEqual', () => { + let ctx: any = {}; + + beforeEach(() => { + ctx.heatmapA = { + '1422774000000': { + x: 1422774000000, + buckets: { + '1': { y: 1, values: [1, 1.5] }, + '2': { y: 2, values: [1] } + } + } + }; + + ctx.heatmapB = { + '1422774000000': { + x: 1422774000000, + buckets: { + '1': { y: 1, values: [1.5, 1] }, + '2': { y: 2, values: [1] } + } + } + }; + }); + + it('should proper compare objects', () => { + let heatmapC = _.cloneDeep(ctx.heatmapA); + heatmapC['1422774000000'].buckets['1'].values = [1, 1.5]; + + let heatmapD = _.cloneDeep(ctx.heatmapA); + heatmapD['1422774000000'].buckets['1'].values = [1.5, 1, 1.6]; + + let heatmapE = _.cloneDeep(ctx.heatmapA); + heatmapE['1422774000000'].buckets['1'].values = [1, 1.6]; + + let empty = {}; + let emptyValues = _.cloneDeep(ctx.heatmapA); + emptyValues['1422774000000'].buckets['1'].values = []; + + expect(isHeatmapDataEqual(ctx.heatmapA, ctx.heatmapB)).to.be(true); + expect(isHeatmapDataEqual(ctx.heatmapB, ctx.heatmapA)).to.be(true); + + expect(isHeatmapDataEqual(ctx.heatmapA, heatmapC)).to.be(true); + expect(isHeatmapDataEqual(heatmapC, ctx.heatmapA)).to.be(true); + + expect(isHeatmapDataEqual(ctx.heatmapA, heatmapD)).to.be(false); + expect(isHeatmapDataEqual(heatmapD, ctx.heatmapA)).to.be(false); + + expect(isHeatmapDataEqual(ctx.heatmapA, heatmapE)).to.be(false); + expect(isHeatmapDataEqual(heatmapE, ctx.heatmapA)).to.be(false); + + expect(isHeatmapDataEqual(empty, ctx.heatmapA)).to.be(false); + expect(isHeatmapDataEqual(ctx.heatmapA, empty)).to.be(false); + + expect(isHeatmapDataEqual(emptyValues, ctx.heatmapA)).to.be(false); + expect(isHeatmapDataEqual(ctx.heatmapA, emptyValues)).to.be(false); + }); +}); + +describe('calculateBucketSize', () => { + let ctx: any = {}; + + describe('when logBase is 1 (linear scale)', () => { + + beforeEach(() => { + ctx.logBase = 1; + ctx.bounds_set = [ + { bounds: [], size: 0 }, + { bounds: [0], size: 0 }, + { bounds: [4], size: 4 }, + { bounds: [0, 1, 2, 3, 4], size: 1 }, + { bounds: [0, 1, 3, 5, 7], size: 1 }, + { bounds: [0, 3, 7, 9, 15], size: 2 }, + { bounds: [0, 7, 3, 15, 9], size: 2 }, + { bounds: [0, 5, 10, 15, 50], size: 5 } + ]; + }); + + it('should properly calculate bucket size', () => { + _.each(ctx.bounds_set, (b) => { + let bucketSize = calculateBucketSize(b.bounds, ctx.logBase); + expect(bucketSize).to.be(b.size); + }); + }); + }); + + describe('when logBase is 2', () => { + + beforeEach(() => { + ctx.logBase = 2; + ctx.bounds_set = [ + { bounds: [], size: 0 }, + { bounds: [0], size: 0 }, + { bounds: [4], size: 4 }, + { bounds: [1, 2, 4, 8], size: 1 }, + { bounds: [1, Math.SQRT2, 2, 8, 16], size: 0.5 } + ]; + }); + + it('should properly calculate bucket size', () => { + _.each(ctx.bounds_set, (b) => { + let bucketSize = calculateBucketSize(b.bounds, ctx.logBase); + expect(isEqual(bucketSize, b.size)).to.be(true); + }); + }); + }); +}); + +describe('HeatmapDataConverter', () => { + let ctx: any = {}; + + beforeEach(() => { + ctx.series = []; + ctx.series.push(new TimeSeries({ + datapoints: [[1, 1422774000000], [2, 1422774060000]], + alias: 'series1' + })); + ctx.series.push(new TimeSeries({ + datapoints: [[2, 1422774000000], [3, 1422774060000]], + alias: 'series2' + })); + + ctx.xBucketSize = 60000; // 60s + ctx.yBucketSize = 1; + ctx.logBase = 1; + }); + + describe('when logBase is 1 (linear scale)', () => { + + beforeEach(() => { + ctx.logBase = 1; + }); + + it('should build proper heatmap data', () => { + let expectedHeatmap = { + '1422774000000': { + x: 1422774000000, + buckets: { + '1': { y: 1, values: [1] }, + '2': { y: 2, values: [2] } + } + }, + '1422774060000': { + x: 1422774060000, + buckets: { + '2': { y: 2, values: [2] }, + '3': { y: 3, values: [3] } + } + }, + }; + + let heatmap = convertToHeatMap(ctx.series, ctx.yBucketSize, ctx.xBucketSize, ctx.logBase); + expect(isHeatmapDataEqual(heatmap, expectedHeatmap)).to.be(true); + }); + }); + + describe('when logBase is 2', () => { + + beforeEach(() => { + ctx.logBase = 2; + }); + + it('should build proper heatmap data', () => { + let expectedHeatmap = { + '1422774000000': { + x: 1422774000000, + buckets: { + '1': { y: 1, values: [1] }, + '2': { y: 2, values: [2] } + } + }, + '1422774060000': { + x: 1422774060000, + buckets: { + '2': { y: 2, values: [2, 3] } + } + }, + }; + + let heatmap = convertToHeatMap(ctx.series, ctx.yBucketSize, ctx.xBucketSize, ctx.logBase); + expect(isHeatmapDataEqual(heatmap, expectedHeatmap)).to.be(true); + }); + }); +}); + +describe('ES Histogram converter', () => { + let ctx: any = {}; + + beforeEach(() => { + ctx.series = []; + ctx.series.push(new TimeSeries({ + datapoints: [[1, 1422774000000], [0, 1422774060000]], + alias: '1', label: '1' + })); + ctx.series.push(new TimeSeries({ + datapoints: [[1, 1422774000000], [3, 1422774060000]], + alias: '2', label: '2' + })); + ctx.series.push(new TimeSeries({ + datapoints: [[0, 1422774000000], [1, 1422774060000]], + alias: '3', label: '3' + })); + }); + + describe('when converting ES histogram', () => { + + beforeEach(() => { + }); + + it('should build proper heatmap data', () => { + let expectedHeatmap = { + '1422774000000': { + x: 1422774000000, + buckets: { + '1': { y: 1, values: [1] }, + '2': { y: 2, values: [2] } + } + }, + '1422774060000': { + x: 1422774060000, + buckets: { + '2': { y: 2, values: [2, 2, 2] }, + '3': { y: 3, values: [3] } + } + }, + }; + + let heatmap = elasticHistogramToHeatmap(ctx.series); + expect(isHeatmapDataEqual(heatmap, expectedHeatmap)).to.be(true); + }); + }); +}); + +/** + * Compare two numbers with given precision. Suitable for compare float numbers after conversions with precision loss. + * @param a + * @param b + * @param precision + */ +function isEqual(a: number, b: number, precision = 0.000001): boolean { + if (a === b) { + return true; + } else { + return Math.abs(1 - a / b) <= precision; + } +} diff --git a/public/app/plugins/panel/heatmap/specs/renderer_specs.ts b/public/app/plugins/panel/heatmap/specs/renderer_specs.ts new file mode 100644 index 00000000000..3467c5b7542 --- /dev/null +++ b/public/app/plugins/panel/heatmap/specs/renderer_specs.ts @@ -0,0 +1,267 @@ +/// + +import { describe, beforeEach, it, sinon, expect, angularMocks } from '../../../../../test/lib/common'; + +import '../module'; +import angular from 'angular'; +import $ from 'jquery'; +import _ from 'lodash'; +import helpers from 'test/specs/helpers'; +import TimeSeries from 'app/core/time_series2'; +import moment from 'moment'; +import { Emitter } from 'app/core/core'; +import rendering from '../rendering'; +import { convertToHeatMap } from '../heatmap_data_converter'; +// import d3 from 'd3'; + +describe('grafanaHeatmap', function () { + + beforeEach(angularMocks.module('grafana.core')); + + function heatmapScenario(desc, func, elementWidth = 500) { + describe(desc, function () { + var ctx: any = {}; + + ctx.setup = function (setupFunc) { + + beforeEach(angularMocks.module(function ($provide) { + $provide.value("timeSrv", new helpers.TimeSrvStub()); + })); + + beforeEach(angularMocks.inject(function ($rootScope, $compile) { + var ctrl: any = { + colorSchemes: [ + {name: 'Oranges', value: 'interpolateOranges', invert: 'dark'}, + {name: 'Reds', value: 'interpolateReds', invert: 'dark'}, + ], + events: new Emitter(), + height: 200, + panel: { + heatmap: { + }, + cards: { + cardPadding: null, + cardRound: null + }, + color: { + mode: 'spectrum', + cardColor: '#b4ff00', + colorScale: 'linear', + exponent: 0.5, + colorScheme: 'interpolateOranges', + fillBackground: false + }, + xBucketSize: 1000, + xBucketNumber: null, + yBucketSize: 1, + yBucketNumber: null, + xAxis: { + show: true + }, + yAxis: { + show: true, + format: 'short', + decimals: null, + logBase: 1, + splitFactor: null, + min: null, + max: null, + removeZeroValues: false + }, + tooltip: { + show: true, + seriesStat: false, + showHistogram: false + }, + highlightCards: true + }, + renderingCompleted: sinon.spy(), + hiddenSeries: {}, + dashboard: { + getTimezone: sinon.stub().returns('utc') + }, + range: { + from: moment.utc("01 Mar 2017 10:00:00"), + to: moment.utc("01 Mar 2017 11:00:00"), + }, + }; + + var scope = $rootScope.$new(); + scope.ctrl = ctrl; + + ctx.series = []; + ctx.series.push(new TimeSeries({ + datapoints: [[1, 1422774000000], [2, 1422774060000]], + alias: 'series1' + })); + ctx.series.push(new TimeSeries({ + datapoints: [[2, 1422774000000], [3, 1422774060000]], + alias: 'series2' + })); + + ctx.data = { + heatmapStats: { + min: 1, + max: 3, + minLog: 1 + }, + xBucketSize: ctrl.panel.xBucketSize, + yBucketSize: ctrl.panel.yBucketSize + }; + + setupFunc(ctrl, ctx); + + let logBase = ctrl.panel.yAxis.logBase; + let bucketsData = convertToHeatMap(ctx.series, ctx.data.yBucketSize, ctx.data.xBucketSize, logBase); + ctx.data.buckets = bucketsData; + + // console.log("bucketsData", bucketsData); + // console.log("series", ctrl.panel.yAxis.logBase, ctx.series.length); + + let elemHtml = ` +
+
+
+
+
`; + + var element = angular.element(elemHtml); + $compile(element)(scope); + scope.$digest(); + + ctrl.data = ctx.data; + ctx.element = element; + let render = rendering(scope, $(element), [], ctrl); + ctrl.events.emit('render'); + })); + }; + + func(ctx); + }); + } + + heatmapScenario('default options', function (ctx) { + ctx.setup(function (ctrl) { + ctrl.panel.yAxis.logBase = 1; + }); + + it('should draw correct Y axis', function () { + var yTicks = getTicks(ctx.element, ".axis-y"); + expect(yTicks).to.eql(['1', '2', '3']); + }); + + it('should draw correct X axis', function () { + var xTicks = getTicks(ctx.element, ".axis-x"); + let expectedTicks = [ + formatLocalTime("01 Mar 2017 10:00:00"), + formatLocalTime("01 Mar 2017 10:15:00"), + formatLocalTime("01 Mar 2017 10:30:00"), + formatLocalTime("01 Mar 2017 10:45:00"), + formatLocalTime("01 Mar 2017 11:00:00") + ]; + expect(xTicks).to.eql(expectedTicks); + }); + }); + + heatmapScenario('when logBase is 2', function (ctx) { + ctx.setup(function (ctrl) { + ctrl.panel.yAxis.logBase = 2; + }); + + it('should draw correct Y axis', function () { + var yTicks = getTicks(ctx.element, ".axis-y"); + expect(yTicks).to.eql(['1', '2', '4']); + }); + }); + + heatmapScenario('when logBase is 10', function (ctx) { + ctx.setup(function (ctrl, ctx) { + ctrl.panel.yAxis.logBase = 10; + + ctx.series.push(new TimeSeries({ + datapoints: [[10, 1422774000000], [20, 1422774060000]], + alias: 'series3' + })); + ctx.data.heatmapStats.max = 20; + }); + + it('should draw correct Y axis', function () { + var yTicks = getTicks(ctx.element, ".axis-y"); + expect(yTicks).to.eql(['1', '10', '100']); + }); + }); + + heatmapScenario('when logBase is 32', function (ctx) { + ctx.setup(function (ctrl) { + ctrl.panel.yAxis.logBase = 32; + + ctx.series.push(new TimeSeries({ + datapoints: [[10, 1422774000000], [100, 1422774060000]], + alias: 'series3' + })); + ctx.data.heatmapStats.max = 100; + }); + + it('should draw correct Y axis', function () { + var yTicks = getTicks(ctx.element, ".axis-y"); + expect(yTicks).to.eql(['1', '32', '1 K']); + }); + }); + + heatmapScenario('when logBase is 1024', function (ctx) { + ctx.setup(function (ctrl) { + ctrl.panel.yAxis.logBase = 1024; + + ctx.series.push(new TimeSeries({ + datapoints: [[2000, 1422774000000], [300000, 1422774060000]], + alias: 'series3' + })); + ctx.data.heatmapStats.max = 300000; + }); + + it('should draw correct Y axis', function () { + var yTicks = getTicks(ctx.element, ".axis-y"); + expect(yTicks).to.eql(['1', '1 K', '1 Mil']); + }); + }); + + heatmapScenario('when Y axis format set to "none"', function (ctx) { + ctx.setup(function (ctrl) { + ctrl.panel.yAxis.logBase = 1; + ctrl.panel.yAxis.format = "none"; + ctx.data.heatmapStats.max = 10000; + }); + + it('should draw correct Y axis', function () { + var yTicks = getTicks(ctx.element, ".axis-y"); + expect(yTicks).to.eql(['0', '2000', '4000', '6000', '8000', '10000', '12000']); + }); + }); + + heatmapScenario('when Y axis format set to "second"', function (ctx) { + ctx.setup(function (ctrl) { + ctrl.panel.yAxis.logBase = 1; + ctrl.panel.yAxis.format = "s"; + ctx.data.heatmapStats.max = 3600; + }); + + it('should draw correct Y axis', function () { + var yTicks = getTicks(ctx.element, ".axis-y"); + expect(yTicks).to.eql(['0 ns', '17 min', '33 min', '50 min', '1 hour']); + }); + }); + +}); + + +function getTicks(element, axisSelector) { + return element.find(axisSelector).find("text") + .map(function () { + return this.textContent; + }).get(); +} + +function formatLocalTime(timeStr) { + let format = "HH:mm"; + return moment.utc(timeStr).local().format(format); +} diff --git a/public/app/plugins/panel/table/module.html b/public/app/plugins/panel/table/module.html index 9140a182541..e405c6c42e3 100644 --- a/public/app/plugins/panel/table/module.html +++ b/public/app/plugins/panel/table/module.html @@ -1,7 +1,7 @@
-
-
+
+
@@ -21,10 +21,10 @@
-
- - No datapoints No datapoints returned from metric query - +
+ + No data to show Nothing returned by data query +
diff --git a/public/app/system.conf.js b/public/app/system.conf.js index 4574c56742a..f7ed0c5b2d2 100644 --- a/public/app/system.conf.js +++ b/public/app/system.conf.js @@ -30,7 +30,8 @@ System.config({ "jquery.flot.time": "vendor/flot/jquery.flot.time", "jquery.flot.crosshair": "vendor/flot/jquery.flot.crosshair", "jquery.flot.fillbelow": "vendor/flot/jquery.flot.fillbelow", - "jquery.flot.gauge": "vendor/flot/jquery.flot.gauge" + "jquery.flot.gauge": "vendor/flot/jquery.flot.gauge", + "d3": "vendor/d3/d3.js" }, packages: { diff --git a/public/sass/_grafana.scss b/public/sass/_grafana.scss index 370a5db943a..6266bd9c98f 100644 --- a/public/sass/_grafana.scss +++ b/public/sass/_grafana.scss @@ -48,6 +48,7 @@ @import "components/panel_singlestat"; @import "components/panel_table"; @import "components/panel_text"; +@import "components/panel_heatmap"; @import "components/tagsinput"; @import "components/tables_lists"; @import "components/search"; diff --git a/public/sass/components/_panel_heatmap.scss b/public/sass/components/_panel_heatmap.scss new file mode 100644 index 00000000000..cf55c91ab7c --- /dev/null +++ b/public/sass/components/_panel_heatmap.scss @@ -0,0 +1,39 @@ +.heatmap-canvas-wrapper { + // position: relative; + cursor: crosshair; +} + +.heatmap-panel { + position: relative; + + .axis .tick { + text { + fill: $text-color; + color: $text-color; + font-size: $font-size-sm; + } + + line { + opacity: 0.4; + stroke: $text-color-weak; + } + } +} + +.heatmap-tooltip { + white-space: nowrap; + font-size: $font-size-sm; + background-color: $graph-tooltip-bg; + color: $text-color; +} + +.heatmap-histogram rect { + fill: $text-color-weak; +} + +.heatmap-crosshair { + line { + stroke: darken($red,15%); + stroke-width: 1; + } +} diff --git a/public/test/test-main.js b/public/test/test-main.js index 54fbc7d2910..6fba92cc109 100644 --- a/public/test/test-main.js +++ b/public/test/test-main.js @@ -38,7 +38,8 @@ "jquery.flot.time": "vendor/flot/jquery.flot.time", "jquery.flot.crosshair": "vendor/flot/jquery.flot.crosshair", "jquery.flot.fillbelow": "vendor/flot/jquery.flot.fillbelow", - "jquery.flot.gauge": "vendor/flot/jquery.flot.gauge" + "jquery.flot.gauge": "vendor/flot/jquery.flot.gauge", + "d3": "vendor/d3/d3.js", }, packages: { diff --git a/public/vendor/d3/LICENSE b/public/vendor/d3/LICENSE new file mode 100644 index 00000000000..721bd22ece6 --- /dev/null +++ b/public/vendor/d3/LICENSE @@ -0,0 +1,27 @@ +Copyright 2010-2016 Mike Bostock +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the author nor the names of contributors may be used to + endorse or promote products derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/public/vendor/d3/README.md b/public/vendor/d3/README.md new file mode 100644 index 00000000000..32299bfe579 --- /dev/null +++ b/public/vendor/d3/README.md @@ -0,0 +1,57 @@ +# D3: Data-Driven Documents + + + +**D3** (or **D3.js**) is a JavaScript library for visualizing data using web standards. D3 helps you bring data to life using SVG, Canvas and HTML. D3 combines powerful visualization and interaction techniques with a data-driven approach to DOM manipulation, giving you the full capabilities of modern browsers and the freedom to design the right visual interface for your data. + +## Resources + +* [API Reference](https://github.com/d3/d3/blob/master/API.md) +* [Release Notes](https://github.com/d3/d3/releases) +* [Gallery](https://github.com/d3/d3/wiki/Gallery) +* [Examples](http://bl.ocks.org/mbostock) +* [Wiki](https://github.com/d3/d3/wiki) + +## Installing + +If you use npm, `npm install d3`. Otherwise, download the [latest release](https://github.com/d3/d3/releases/latest). The released bundle supports anonymous AMD, CommonJS, and vanilla environments. You can load directly from [d3js.org](https://d3js.org), [CDNJS](https://cdnjs.com/libraries/d3), or [unpkg](https://unpkg.com/d3/). For example: + +```html + +``` + +For the minified version: + +```html + +``` + +You can also use the standalone D3 microlibraries. For example, [d3-selection](https://github.com/d3/d3-selection): + +```html + +``` + +D3 is written using [ES2015 modules](http://www.2ality.com/2014/09/es6-modules-final.html). Create a [custom bundle using Rollup](http://bl.ocks.org/mbostock/bb09af4c39c79cffcde4), Webpack, or your preferred bundler. To import D3 into an ES2015 application, either import specific symbols from specific D3 modules: + +```js +import {scaleLinear} from "d3-scale"; +``` + +Or import everything into a namespace (here, `d3`): + +```js +import * as d3 from "d3"; +``` + +In Node: + +```js +var d3 = require("d3"); +``` + +You can also require individual modules and combine them into a `d3` object using [Object.assign](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign): + +```js +var d3 = Object.assign({}, require("d3-format"), require("d3-geo"), require("d3-geo-projection")); +``` diff --git a/public/vendor/d3/d3-scale-chromatic.min.js b/public/vendor/d3/d3-scale-chromatic.min.js new file mode 100644 index 00000000000..86aed948666 --- /dev/null +++ b/public/vendor/d3/d3-scale-chromatic.min.js @@ -0,0 +1,2 @@ +// https://d3js.org/d3-scale-chromatic/ Version 1.1.1. Copyright 2017 Mike Bostock. +!function(f,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("./d3.v4.min.js")):"function"==typeof define&&define.amd?define(["exports","./d3.v4.min.js"],e):e(f.d3=f.d3||{},f.d3)}(this,function(f,e){"use strict";var d=function(f){for(var e=f.length/6|0,d=new Array(e),c=0;c=Ys?i*=10:o>=Bs?i*=5:o>=js&&(i*=2),n=0&&(e=t.slice(r+1),t=t.slice(0,r)),t&&!n.hasOwnProperty(t))throw new Error("unknown type: "+t);return{type:t,name:e}})}function _(t,n){for(var e,r=0,i=t.length;r=0&&(n=t.slice(e+1),t=t.slice(0,e)),{type:t,name:n}})}function N(t){return function(){var n=this.__on;if(n){for(var e,r=0,i=-1,o=n.length;rn?1:t>=n?0:NaN}function q(t){return function(){this.removeAttribute(t)}}function L(t){return function(){this.removeAttributeNS(t.space,t.local)}}function U(t,n){return function(){this.setAttribute(t,n)}}function D(t,n){return function(){this.setAttributeNS(t.space,t.local,n)}}function O(t,n){return function(){var e=n.apply(this,arguments);null==e?this.removeAttribute(t):this.setAttribute(t,e)}}function F(t,n){return function(){var e=n.apply(this,arguments);null==e?this.removeAttributeNS(t.space,t.local):this.setAttributeNS(t.space,t.local,e)}}function I(t){return function(){this.style.removeProperty(t)}}function Y(t,n,e){return function(){this.style.setProperty(t,n,e)}}function B(t,n,e){return function(){var r=n.apply(this,arguments);null==r?this.style.removeProperty(t):this.style.setProperty(t,r,e)}}function j(t){return function(){delete this[t]}}function H(t,n){return function(){this[t]=n}}function X(t,n){return function(){var e=n.apply(this,arguments);null==e?delete this[t]:this[t]=e}}function V(t){return t.trim().split(/^|\s+/)}function W(t){return t.classList||new $(t)}function $(t){this._node=t,this._names=V(t.getAttribute("class")||"")}function Z(t,n){for(var e=W(t),r=-1,i=n.length;++r>8&15|n>>4&240,n>>4&15|240&n,(15&n)<<4|15&n,1)):(n=El.exec(t))?Tt(parseInt(n[1],16)):(n=Al.exec(t))?new Et(n[1],n[2],n[3],1):(n=Cl.exec(t))?new Et(255*n[1]/100,255*n[2]/100,255*n[3]/100,1):(n=zl.exec(t))?Nt(n[1],n[2],n[3],n[4]):(n=Pl.exec(t))?Nt(255*n[1]/100,255*n[2]/100,255*n[3]/100,n[4]):(n=Rl.exec(t))?At(n[1],n[2]/100,n[3]/100,1):(n=ql.exec(t))?At(n[1],n[2]/100,n[3]/100,n[4]):Ll.hasOwnProperty(t)?Tt(Ll[t]):"transparent"===t?new Et(NaN,NaN,NaN,0):null}function Tt(t){return new Et(t>>16&255,t>>8&255,255&t,1)}function Nt(t,n,e,r){return r<=0&&(t=n=e=NaN),new Et(t,n,e,r)}function kt(t){return t instanceof wt||(t=Mt(t)),t?(t=t.rgb(),new Et(t.r,t.g,t.b,t.opacity)):new Et}function St(t,n,e,r){return 1===arguments.length?kt(t):new Et(t,n,e,null==r?1:r)}function Et(t,n,e,r){this.r=+t,this.g=+n,this.b=+e,this.opacity=+r}function At(t,n,e,r){return r<=0?t=n=e=NaN:e<=0||e>=1?t=n=NaN:n<=0&&(t=NaN),new Pt(t,n,e,r)}function Ct(t){if(t instanceof Pt)return new Pt(t.h,t.s,t.l,t.opacity);if(t instanceof wt||(t=Mt(t)),!t)return new Pt;if(t instanceof Pt)return t;t=t.rgb();var n=t.r/255,e=t.g/255,r=t.b/255,i=Math.min(n,e,r),o=Math.max(n,e,r),u=NaN,a=o-i,c=(o+i)/2;return a?(u=n===o?(e-r)/a+6*(e0&&c<1?0:u,new Pt(u,a,c,t.opacity)}function zt(t,n,e,r){return 1===arguments.length?Ct(t):new Pt(t,n,e,null==r?1:r)}function Pt(t,n,e,r){this.h=+t,this.s=+n,this.l=+e,this.opacity=+r}function Rt(t,n,e){return 255*(t<60?n+(e-n)*t/60:t<180?e:t<240?n+(e-n)*(240-t)/60:n)}function qt(t){if(t instanceof Ut)return new Ut(t.l,t.a,t.b,t.opacity);if(t instanceof jt){var n=t.h*Ul;return new Ut(t.l,Math.cos(n)*t.c,Math.sin(n)*t.c,t.opacity)}t instanceof Et||(t=kt(t));var e=It(t.r),r=It(t.g),i=It(t.b),o=Dt((.4124564*e+.3575761*r+.1804375*i)/Fl),u=Dt((.2126729*e+.7151522*r+.072175*i)/Il),a=Dt((.0193339*e+.119192*r+.9503041*i)/Yl);return new Ut(116*u-16,500*(o-u),200*(u-a),t.opacity)}function Lt(t,n,e,r){return 1===arguments.length?qt(t):new Ut(t,n,e,null==r?1:r)}function Ut(t,n,e,r){this.l=+t,this.a=+n,this.b=+e,this.opacity=+r}function Dt(t){return t>Xl?Math.pow(t,1/3):t/Hl+Bl}function Ot(t){return t>jl?t*t*t:Hl*(t-Bl)}function Ft(t){return 255*(t<=.0031308?12.92*t:1.055*Math.pow(t,1/2.4)-.055)}function It(t){return(t/=255)<=.04045?t/12.92:Math.pow((t+.055)/1.055,2.4)}function Yt(t){if(t instanceof jt)return new jt(t.h,t.c,t.l,t.opacity);t instanceof Ut||(t=qt(t));var n=Math.atan2(t.b,t.a)*Dl;return new jt(n<0?n+360:n,Math.sqrt(t.a*t.a+t.b*t.b),t.l,t.opacity)}function Bt(t,n,e,r){return 1===arguments.length?Yt(t):new jt(t,n,e,null==r?1:r)}function jt(t,n,e,r){this.h=+t,this.c=+n,this.l=+e,this.opacity=+r}function Ht(t){if(t instanceof Vt)return new Vt(t.h,t.s,t.l,t.opacity);t instanceof Et||(t=kt(t));var n=t.r/255,e=t.g/255,r=t.b/255,i=(Kl*r+Jl*n-Ql*e)/(Kl+Jl-Ql),o=r-i,u=(Gl*(e-i)-$l*o)/Zl,a=Math.sqrt(u*u+o*o)/(Gl*i*(1-i)),c=a?Math.atan2(u,o)*Dl-120:NaN;return new Vt(c<0?c+360:c,a,i,t.opacity)}function Xt(t,n,e,r){return 1===arguments.length?Ht(t):new Vt(t,n,e,null==r?1:r)}function Vt(t,n,e,r){this.h=+t,this.s=+n,this.l=+e,this.opacity=+r}function Wt(t,n,e,r,i){var o=t*t,u=o*t;return((1-3*t+3*o-u)*n+(4-6*o+3*u)*e+(1+3*t+3*o-3*u)*r+u*i)/6}function $t(t,n){return function(e){return t+e*n}}function Zt(t,n,e){return t=Math.pow(t,e),n=Math.pow(n,e)-t,e=1/e,function(r){return Math.pow(t+r*n,e)}}function Gt(t,n){var e=n-t;return e?$t(t,e>180||e<-180?e-360*Math.round(e/360):e):ch(isNaN(t)?n:t)}function Jt(t){return 1===(t=+t)?Qt:function(n,e){return e-n?Zt(n,e,t):ch(isNaN(n)?e:n)}}function Qt(t,n){var e=n-t;return e?$t(t,e):ch(isNaN(t)?n:t)}function Kt(t){return function(n){var e,r,i=n.length,o=new Array(i),u=new Array(i),a=new Array(i);for(e=0;e180?n+=360:n-t>180&&(t+=360),o.push({i:e.push(i(e)+"rotate(",null,r)-2,x:dh(t,n)})):n&&e.push(i(e)+"rotate("+n+r)}function a(t,n,e,o){t!==n?o.push({i:e.push(i(e)+"skewX(",null,r)-2,x:dh(t,n)}):n&&e.push(i(e)+"skewX("+n+r)}function c(t,n,e,r,o,u){if(t!==e||n!==r){var a=o.push(i(o)+"scale(",null,",",null,")");u.push({i:a-4,x:dh(t,e)},{i:a-2,x:dh(n,r)})}else 1===e&&1===r||o.push(i(o)+"scale("+e+","+r+")")}return function(n,e){var r=[],i=[];return n=t(n),e=t(e),o(n.translateX,n.translateY,e.translateX,e.translateY,r,i),u(n.rotate,e.rotate,r,i),a(n.skewX,e.skewX,r,i),c(n.scaleX,n.scaleY,e.scaleX,e.scaleY,r,i),n=e=null,function(t){for(var n,e=-1,o=i.length;++e=0&&n._call.call(null,t),n=n._next;--Oh}function gn(){jh=(Bh=Xh.now())+Hh,Oh=Fh=0;try{yn()}finally{Oh=0,xn(),jh=0}}function mn(){var t=Xh.now(),n=t-Bh;n>Yh&&(Hh-=n,Bh=t)}function xn(){for(var t,n,e=ih,r=1/0;e;)e._call?(r>e._time&&(r=e._time),t=e,e=e._next):(n=e._next,e._next=null,e=t?t._next=n:ih=n);oh=t,bn(r)}function bn(t){if(!Oh){Fh&&(Fh=clearTimeout(Fh));var n=t-jh;n>24?(t<1/0&&(Fh=setTimeout(gn,n)),Ih&&(Ih=clearInterval(Ih))):(Ih||(Bh=jh,Ih=setInterval(mn,Yh)),Oh=1,Vh(gn))}}function wn(t,n){var e=t.__transition;if(!e||!(e=e[n])||e.state>Jh)throw new Error("too late");return e}function Mn(t,n){var e=t.__transition;if(!e||!(e=e[n])||e.state>Kh)throw new Error("too late");return e}function Tn(t,n){var e=t.__transition;if(!e||!(e=e[n]))throw new Error("too late");return e}function Nn(t,n,e){function r(t){e.state=Qh,e.timer.restart(i,e.delay,e.time),e.delay<=t&&i(t-e.delay)}function i(r){var s,f,l,h;if(e.state!==Qh)return u();for(s in c)if(h=c[s],h.name===e.name){if(h.state===tp)return Wh(i);h.state===np?(h.state=rp,h.timer.stop(),h.on.call("interrupt",t,t.__data__,h.index,h.group),delete c[s]):+s=0&&(t=t.slice(0,n)),!t||"start"===t})}function jn(t,n,e){var r,i,o=Bn(n)?wn:Mn;return function(){var u=o(this,t),a=u.on;a!==r&&(i=(r=a).copy()).on(n,e),u.on=i}}function Hn(t){return function(){var n=this.parentNode;for(var e in this.__transition)if(+e!==t)return;n&&n.removeChild(this)}}function Xn(t,n){var e,r,i;return function(){var o=Kf(this).getComputedStyle(this,null),u=o.getPropertyValue(t),a=(this.style.removeProperty(t),o.getPropertyValue(t));return u===a?null:u===e&&a===r?i:i=n(e=u,r=a)}}function Vn(t){return function(){this.style.removeProperty(t)}}function Wn(t,n,e){var r,i;return function(){var o=Kf(this).getComputedStyle(this,null).getPropertyValue(t);return o===e?null:o===r?i:i=n(r=o,e)}}function $n(t,n,e){var r,i,o;return function(){var u=Kf(this).getComputedStyle(this,null),a=u.getPropertyValue(t),c=e(this);return null==c&&(this.style.removeProperty(t),c=u.getPropertyValue(t)),a===c?null:a===r&&c===i?o:o=n(r=a,i=c)}}function Zn(t,n,e){function r(){var r=this,i=n.apply(r,arguments);return i&&function(n){r.style.setProperty(t,i(n),e)}}return r._value=n,r}function Gn(t){return function(){this.textContent=t}}function Jn(t){return function(){var n=t(this);this.textContent=null==n?"":n}}function Qn(t,n,e,r){this._groups=t,this._parents=n,this._name=e,this._id=r}function Kn(t){return dt().transition(t)}function te(){return++kp}function ne(t){return+t}function ee(t){return t*t}function re(t){return t*(2-t)}function ie(t){return((t*=2)<=1?t*t:--t*(2-t)+1)/2}function oe(t){return t*t*t}function ue(t){return--t*t*t+1}function ae(t){return((t*=2)<=1?t*t*t:(t-=2)*t*t+2)/2}function ce(t){return 1-Math.cos(t*Rp)}function se(t){return Math.sin(t*Rp)}function fe(t){return(1-Math.cos(Pp*t))/2}function le(t){return Math.pow(2,10*t-10)}function he(t){return 1-Math.pow(2,-10*t)}function pe(t){return((t*=2)<=1?Math.pow(2,10*t-10):2-Math.pow(2,10-10*t))/2}function de(t){return 1-Math.sqrt(1-t*t)}function ve(t){return Math.sqrt(1- --t*t)}function _e(t){return((t*=2)<=1?1-Math.sqrt(1-t*t):Math.sqrt(1-(t-=2)*t)+1)/2}function ye(t){return 1-ge(1-t)}function ge(t){return(t=+t)Math.abs(t[1]-O[1])?M=!0:w=!0),O=t,b=!0,ud(),o()}function o(){var t;switch(m=O[0]-D[0],x=O[1]-D[1],k){case cd:case ad:S&&(m=Math.max(P-l,Math.min(q-v,m)),h=l+m,_=v+m),E&&(x=Math.max(R-p,Math.min(L-y,x)),d=p+x,g=y+x);break;case sd:S<0?(m=Math.max(P-l,Math.min(q-l,m)),h=l+m,_=v):S>0&&(m=Math.max(P-v,Math.min(q-v,m)),h=l,_=v+m),E<0?(x=Math.max(R-p,Math.min(L-p,x)),d=p+x,g=y):E>0&&(x=Math.max(R-y,Math.min(L-y,x)),d=p,g=y+x);break;case fd:S&&(h=Math.max(P,Math.min(q,l-m*S)),_=Math.max(P,Math.min(q,v+m*S))),E&&(d=Math.max(R,Math.min(L,p-x*E)),g=Math.max(R,Math.min(L,y+x*E)))}_0&&(l=h-m),E<0?y=g-x:E>0&&(p=d-x),k=cd,Y.attr("cursor",dd.selection),o());break;default:return}ud()}function s(){switch(t.event.keyCode){case 16:U&&(w=M=U=!1,o());break;case 18:k===fd&&(S<0?v=_:S>0&&(l=h),E<0?y=g:E>0&&(p=d),k=sd,o());break;case 32:k===cd&&(t.event.altKey?(S&&(v=_-m*S,l=h+m*S),E&&(y=g-x*E,p=d+x*E),k=fd):(S<0?v=_:S>0&&(l=h),E<0?y=g:E>0&&(p=d),k=sd),Y.attr("cursor",dd[N]),o());break;default:return}ud()}if(t.event.touches){if(t.event.changedTouches.length=(o=(v+y)/2))?v=o:y=o,(f=e>=(u=(_+g)/2))?_=u:g=u,i=p,!(p=p[l=f<<1|s]))return i[l]=d,t;if(a=+t._x.call(null,p.data),c=+t._y.call(null,p.data),n===a&&e===c)return d.next=p,i?i[l]=d:t._root=d,t;do i=i?i[l]=new Array(4):t._root=new Array(4),(s=n>=(o=(v+y)/2))?v=o:y=o,(f=e>=(u=(_+g)/2))?_=u:g=u;while((l=f<<1|s)===(h=(c>=u)<<1|a>=o));return i[h]=p,i[l]=d,t}function Je(t){var n,e,r,i,o=t.length,u=new Array(o),a=new Array(o),c=1/0,s=1/0,f=-(1/0),l=-(1/0);for(e=0;ef&&(f=r),il&&(l=i));for(f",i=n[3]||"-",o=n[4]||"",u=!!n[5],a=n[6]&&+n[6],c=!!n[7],s=n[8]&&+n[8].slice(1),f=n[9]||"";"n"===f?(c=!0,f="g"):Av[f]||(f=""),(u||"0"===e&&"="===r)&&(u=!0,e="0",r="="),this.fill=e,this.align=r,this.sign=i,this.symbol=o,this.zero=u,this.width=a,this.comma=c,this.precision=s,this.type=f}function lr(t){return t}function hr(n){return Pv=qv(n), +t.format=Pv.format,t.formatPrefix=Pv.formatPrefix,Pv}function pr(){this.reset()}function dr(t,n,e){var r=t.s=n+e,i=r-n,o=r-i;t.t=n-o+(e-i)}function vr(t){return t>1?0:t<-1?m_:Math.acos(t)}function _r(t){return t>1?x_:t<-1?-x_:Math.asin(t)}function yr(t){return(t=R_(t/2))*t}function gr(){}function mr(t,n){t&&O_.hasOwnProperty(t.type)&&O_[t.type](t,n)}function xr(t,n,e){var r,i=-1,o=t.length-e;for(n.lineStart();++i=0?1:-1,i=r*e,o=E_(n),u=R_(n),a=jv*u,c=Bv*o+a*E_(i),s=a*r*R_(i);I_.add(S_(s,c)),Yv=t,Bv=o,jv=u}function kr(t){return[S_(t[1],t[0]),_r(t[2])]}function Sr(t){var n=t[0],e=t[1],r=E_(e);return[r*E_(n),r*R_(n),R_(e)]}function Er(t,n){return t[0]*n[0]+t[1]*n[1]+t[2]*n[2]}function Ar(t,n){return[t[1]*n[2]-t[2]*n[1],t[2]*n[0]-t[0]*n[2],t[0]*n[1]-t[1]*n[0]]}function Cr(t,n){t[0]+=n[0],t[1]+=n[1],t[2]+=n[2]}function zr(t,n){return[t[0]*n,t[1]*n,t[2]*n]}function Pr(t){var n=L_(t[0]*t[0]+t[1]*t[1]+t[2]*t[2]);t[0]/=n,t[1]/=n,t[2]/=n}function Rr(t,n){Qv.push(Kv=[Hv=t,Vv=t]),nWv&&(Wv=n)}function qr(t,n){var e=Sr([t*T_,n*T_]);if(Jv){var r=Ar(Jv,e),i=[r[1],-r[0],0],o=Ar(i,r);Pr(o),o=kr(o);var u,a=t-$v,c=a>0?1:-1,s=o[0]*M_*c,f=N_(a)>180;f^(c*$vWv&&(Wv=u)):(s=(s+360)%360-180,f^(c*$vWv&&(Wv=n))),f?t<$v?Ir(Hv,t)>Ir(Hv,Vv)&&(Vv=t):Ir(t,Vv)>Ir(Hv,Vv)&&(Hv=t):Vv>=Hv?(tVv&&(Vv=t)):t>$v?Ir(Hv,t)>Ir(Hv,Vv)&&(Vv=t):Ir(t,Vv)>Ir(Hv,Vv)&&(Hv=t)}else Rr(t,n);Jv=e,$v=t}function Lr(){X_.point=qr}function Ur(){Kv[0]=Hv,Kv[1]=Vv,X_.point=Rr,Jv=null}function Dr(t,n){if(Jv){var e=t-$v;H_.add(N_(e)>180?e+(e>0?360:-360):e)}else Zv=t,Gv=n;B_.point(t,n),qr(t,n)}function Or(){B_.lineStart()}function Fr(){Dr(Zv,Gv),B_.lineEnd(),N_(H_)>y_&&(Hv=-(Vv=180)),Kv[0]=Hv,Kv[1]=Vv,Jv=null}function Ir(t,n){return(n-=t)<0?n+360:n}function Yr(t,n){return t[0]-n[0]}function Br(t,n){return t[0]<=t[1]?t[0]<=n&&n<=t[1]:nm_?t-w_:t<-m_?t+w_:t,n]}function ti(t,n,e){return(t%=w_)?n||e?G_(ei(t),ri(n,e)):ei(t):n||e?ri(n,e):Kr}function ni(t){return function(n,e){return n+=t,[n>m_?n-w_:n<-m_?n+w_:n,e]}}function ei(t){var n=ni(t);return n.invert=ni(-t),n}function ri(t,n){function e(t,n){var e=E_(n),a=E_(t)*e,c=R_(t)*e,s=R_(n),f=s*r+a*i;return[S_(c*o-f*u,a*r-s*i),_r(f*o+c*u)]}var r=E_(t),i=R_(t),o=E_(n),u=R_(n);return e.invert=function(t,n){var e=E_(n),a=E_(t)*e,c=R_(t)*e,s=R_(n),f=s*o-c*u;return[S_(c*o+s*u,a*r+f*i),_r(f*r-a*i)]},e}function ii(t,n,e,r,i,o){if(e){var u=E_(n),a=R_(n),c=r*e;null==i?(i=n+r*w_,o=n-c/2):(i=oi(u,i),o=oi(u,o),(r>0?io)&&(i+=r*w_));for(var s,f=i;r>0?f>o:f0){do s.point(0===f||3===f?t:e,f>1?r:n);while((f=(f+a+4)%4)!==l)}else s.point(o[0],o[1])}function u(r,i){return N_(r[0]-t)0?0:3:N_(r[0]-e)0?2:1:N_(r[1]-n)0?1:0:i>0?3:2}function a(t,n){return c(t.x,n.x)}function c(t,n){var e=u(t,1),r=u(n,1);return e!==r?e-r:0===e?n[1]-t[1]:1===e?t[0]-n[0]:2===e?t[1]-n[1]:n[0]-t[0]}return function(u){function c(t,n){i(t,n)&&k.point(t,n)}function s(){for(var n=0,e=0,i=_.length;er&&(l-o)*(r-u)>(h-u)*(t-o)&&++n:h<=r&&(l-o)*(r-u)<(h-u)*(t-o)&&--n;return n}function f(){k=S,v=[],_=[],N=!0}function l(){var t=s(),n=N&&t,e=(v=Ks(v)).length;(n||e)&&(u.polygonStart(),n&&(u.lineStart(),o(null,null,1,u),u.lineEnd()),e&&py(v,a,t,o,u),u.polygonEnd()),k=u,v=_=y=null}function h(){E.point=d,_&&_.push(y=[]),T=!0,M=!1,b=w=NaN}function p(){v&&(d(g,m),x&&M&&S.rejoin(),v.push(S.result())),E.point=c,M&&k.lineEnd()}function d(o,u){var a=i(o,u);if(_&&y.push([o,u]),T)g=o,m=u,x=a,T=!1,a&&(k.lineStart(),k.point(o,u));else if(a&&M)k.point(o,u);else{var c=[b=Math.max(vy,Math.min(dy,b)),w=Math.max(vy,Math.min(dy,w))],s=[o=Math.max(vy,Math.min(dy,o)),u=Math.max(vy,Math.min(dy,u))];ly(c,s,t,n,e,r)?(M||(k.lineStart(),k.point(c[0],c[1])),k.point(s[0],s[1]),a||k.lineEnd(),N=!1):a&&(k.lineStart(),k.point(o,u),N=!1)}b=o,w=u,M=a}var v,_,y,g,m,x,b,w,M,T,N,k=u,S=fy(),E={point:c,lineStart:h,lineEnd:p,polygonStart:f,polygonEnd:l};return E}}function si(){gy.point=li,gy.lineEnd=fi}function fi(){gy.point=gy.lineEnd=gr}function li(t,n){t*=T_,n*=T_,J_=t,Q_=R_(n),K_=E_(n),gy.point=hi}function hi(t,n){t*=T_,n*=T_;var e=R_(n),r=E_(n),i=N_(t-J_),o=E_(i),u=R_(i),a=r*u,c=K_*e-Q_*r*o,s=Q_*e+K_*r*o;yy.add(S_(L_(a*a+c*c),s)),J_=t,Q_=e,K_=r}function pi(t,n,e){var r=Is(t,n-y_,e).concat(n);return function(t){return r.map(function(n){return[t,n]})}}function di(t,n,e){var r=Is(t,n-y_,e).concat(n);return function(t){return r.map(function(n){return[n,t]})}}function vi(){function t(){return{type:"MultiLineString",coordinates:n()}}function n(){return Is(A_(o/_)*_,i,_).map(h).concat(Is(A_(s/y)*y,c,y).map(p)).concat(Is(A_(r/d)*d,e,d).filter(function(t){return N_(t%_)>y_}).map(f)).concat(Is(A_(a/v)*v,u,v).filter(function(t){return N_(t%y)>y_}).map(l))}var e,r,i,o,u,a,c,s,f,l,h,p,d=10,v=d,_=90,y=360,g=2.5;return t.lines=function(){return n().map(function(t){return{type:"LineString",coordinates:t}})},t.outline=function(){return{type:"Polygon",coordinates:[h(o).concat(p(c).slice(1),h(i).reverse().slice(1),p(s).reverse().slice(1))]}},t.extent=function(n){return arguments.length?t.extentMajor(n).extentMinor(n):t.extentMinor()},t.extentMajor=function(n){return arguments.length?(o=+n[0][0],i=+n[1][0],s=+n[0][1],c=+n[1][1],o>i&&(n=o,o=i,i=n),s>c&&(n=s,s=c,c=n),t.precision(g)):[[o,s],[i,c]]},t.extentMinor=function(n){return arguments.length?(r=+n[0][0],e=+n[1][0],a=+n[0][1],u=+n[1][1],r>e&&(n=r,r=e,e=n),a>u&&(n=a,a=u,u=n),t.precision(g)):[[r,a],[e,u]]},t.step=function(n){return arguments.length?t.stepMajor(n).stepMinor(n):t.stepMinor()},t.stepMajor=function(n){return arguments.length?(_=+n[0],y=+n[1],t):[_,y]},t.stepMinor=function(n){return arguments.length?(d=+n[0],v=+n[1],t):[d,v]},t.precision=function(n){return arguments.length?(g=+n,f=pi(a,u,90),l=di(r,e,g),h=pi(s,c,90),p=di(o,i,g),t):g},t.extentMajor([[-180,-90+y_],[180,90-y_]]).extentMinor([[-180,-80-y_],[180,80+y_]])}function _i(){return vi()()}function yi(){Sy.point=gi}function gi(t,n){Sy.point=mi,ty=ey=t,ny=ry=n}function mi(t,n){ky.add(ry*t-ey*n),ey=t,ry=n}function xi(){mi(ty,ny)}function bi(t,n){tCy&&(Cy=t),nzy&&(zy=n)}function wi(t,n){Ry+=t,qy+=n,++Ly}function Mi(){By.point=Ti}function Ti(t,n){By.point=Ni,wi(uy=t,ay=n)}function Ni(t,n){var e=t-uy,r=n-ay,i=L_(e*e+r*r);Uy+=i*(uy+t)/2,Dy+=i*(ay+n)/2,Oy+=i,wi(uy=t,ay=n)}function ki(){By.point=wi}function Si(){By.point=Ai}function Ei(){Ci(iy,oy)}function Ai(t,n){By.point=Ci,wi(iy=uy=t,oy=ay=n)}function Ci(t,n){var e=t-uy,r=n-ay,i=L_(e*e+r*r);Uy+=i*(uy+t)/2,Dy+=i*(ay+n)/2,Oy+=i,i=ay*t-uy*n,Fy+=i*(uy+t),Iy+=i*(ay+n),Yy+=3*i,wi(uy=t,ay=n)}function zi(t){this._context=t}function Pi(){this._string=[]}function Ri(t){return"m0,"+t+"a"+t+","+t+" 0 1,1 0,"+-2*t+"a"+t+","+t+" 0 1,1 0,"+2*t+"z"}function qi(t){return t.length>1}function Li(t,n){return((t=t.x)[0]<0?t[1]-x_-y_:x_-t[1])-((n=n.x)[0]<0?n[1]-x_-y_:x_-n[1])}function Ui(t){var n,e=NaN,r=NaN,i=NaN;return{lineStart:function(){t.lineStart(),n=1},point:function(o,u){var a=o>0?m_:-m_,c=N_(o-e);N_(c-m_)0?x_:-x_),t.point(i,r),t.lineEnd(),t.lineStart(),t.point(a,r),t.point(o,r),n=0):i!==a&&c>=m_&&(N_(e-i)y_?k_((R_(n)*(o=E_(r))*R_(e)-R_(r)*(i=E_(n))*R_(t))/(i*o*u)):(n+r)/2}function Oi(t,n,e,r){var i;if(null==t)i=e*x_,r.point(-m_,i),r.point(0,i),r.point(m_,i),r.point(m_,0),r.point(m_,-i),r.point(0,-i),r.point(-m_,-i),r.point(-m_,0),r.point(-m_,i);else if(N_(t[0]-n[0])>y_){var o=t[0]4*n&&v--){var x=u+h,b=a+p,w=c+d,M=L_(x*x+b*b+w*w),T=_r(w/=M),N=N_(N_(w)-1)n||N_((y*A+g*C)/m-.5)>.3||u*h+a*p+c*d2?t[2]%360*T_:0,i()):[b*M_,w*M_,M*M_]},n.precision=function(t){return arguments.length?(A=Qy(r,E=t*t),o()):L_(E)},n.fitExtent=function(t,e){return Yi(n,t,e)},n.fitSize=function(t,e){return Bi(n,t,e)},function(){return u=t.apply(this,arguments),n.invert=u.invert&&e,i()}}function Wi(t){var n=0,e=m_/3,r=Vi(t),i=r(n,e);return i.parallels=function(t){return arguments.length?r(n=t[0]*T_,e=t[1]*T_):[n*M_,e*M_]},i}function $i(t){function n(t,n){return[t*e,R_(n)/e]}var e=E_(t);return n.invert=function(t,n){return[t/e,_r(n*e)]},n}function Zi(t,n){function e(t,n){var e=L_(o-2*i*R_(n))/i;return[e*R_(t*=i),u-e*E_(t)]}var r=R_(t),i=(r+R_(n))/2;if(N_(i)0?n<-x_+y_&&(n=-x_+y_):n>x_-y_&&(n=x_-y_);var e=o/P_(no(n),i);return[e*R_(i*t),o-e*E_(i*t)]}var r=E_(t),i=t===n?R_(t):z_(r/E_(n))/z_(no(n)/no(t)),o=r*P_(no(t),i)/i;return i?(e.invert=function(t,n){var e=o-n,r=q_(i)*L_(t*t+e*e);return[S_(t,N_(e))/i*q_(e),2*k_(P_(o/r,1/i))-x_]},e):Ki}function ro(t,n){return[t,n]}function io(t,n){function e(t,n){var e=o-n,r=i*t;return[e*R_(r),o-e*E_(r)]}var r=E_(t),i=t===n?R_(t):(r-E_(n))/(n-t),o=r/i+t;return N_(i)=0;)n+=e[r].value;else n=1;t.value=n}function mo(t,n){if(t===n)return t;var e=t.ancestors(),r=n.ancestors(),i=null;for(t=e.pop(),n=r.pop();t===n;)i=t,t=e.pop(),n=r.pop();return i}function xo(t,n){var e,r,i,o,u,a=new No(t),c=+t.value&&(a.value=t.value),s=[a];for(null==n&&(n=wo);e=s.pop();)if(c&&(e.value=+e.data.value),(i=n(e.data))&&(u=i.length))for(e.children=new Array(u),o=u-1;o>=0;--o)s.push(r=e.children[o]=new No(i[o])),r.parent=e,r.depth=e.depth+1;return a.eachBefore(To)}function bo(){return xo(this).eachBefore(Mo)}function wo(t){return t.children}function Mo(t){t.data=t.data.data}function To(t){var n=0;do t.height=n;while((t=t.parent)&&t.height<++n)}function No(t){this.data=t,this.depth=this.height=0,this.parent=null}function ko(t){this._=t,this.next=null}function So(t,n){var e=n.x-t.x,r=n.y-t.y,i=t.r-n.r;return i*i+1e-6>e*e+r*r}function Eo(t,n){var e,r,i,o=null,u=t.head;switch(n.length){case 1:e=Ao(n[0]);break;case 2:e=Co(n[0],n[1]);break;case 3:e=zo(n[0],n[1],n[2])}for(;u;)i=u._,r=u.next,e&&So(e,i)?o=u:(o?(t.tail=o,o.next=null):t.head=t.tail=null,n.push(i),e=Eo(t,n),n.pop(),t.head?(u.next=t.head,t.head=u):(u.next=null,t.head=t.tail=u),o=t.tail,o.next=r),u=r;return t.tail=o,e}function Ao(t){return{x:t.x,y:t.y,r:t.r}}function Co(t,n){var e=t.x,r=t.y,i=t.r,o=n.x,u=n.y,a=n.r,c=o-e,s=u-r,f=a-i,l=Math.sqrt(c*c+s*s);return{x:(e+o+c/l*f)/2,y:(r+u+s/l*f)/2,r:(l+i+a)/2}}function zo(t,n,e){var r=t.x,i=t.y,o=t.r,u=n.x,a=n.y,c=n.r,s=e.x,f=e.y,l=e.r,h=2*(r-u),p=2*(i-a),d=2*(c-o),v=r*r+i*i-o*o-u*u-a*a+c*c,_=2*(r-s),y=2*(i-f),g=2*(l-o),m=r*r+i*i-o*o-s*s-f*f+l*l,x=_*p-h*y,b=(p*m-y*v)/x-r,w=(y*d-p*g)/x,M=(_*v-h*m)/x-i,T=(h*g-_*d)/x,N=w*w+T*T-1,k=2*(b*w+M*T+o),S=b*b+M*M-o*o,E=(-k-Math.sqrt(k*k-4*N*S))/(2*N);return{x:b+w*E+r,y:M+T*E+i,r:E}}function Po(t,n,e){var r=t.x,i=t.y,o=n.r+e.r,u=t.r+e.r,a=n.x-r,c=n.y-i,s=a*a+c*c;if(s){var f=.5+((u*=u)-(o*=o))/(2*s),l=Math.sqrt(Math.max(0,2*o*(u+s)-(u-=s)*u-o*o))/(2*s);e.x=r+f*a+l*c,e.y=i+f*c-l*a}else e.x=r+u,e.y=i}function Ro(t,n){var e=n.x-t.x,r=n.y-t.y,i=t.r+n.r;return i*i-1e-6>e*e+r*r}function qo(t,n){for(var e=t._.r;t!==n;)e+=2*(t=t.next)._.r;return e-n._.r}function Lo(t,n,e){var r=t.x-n,i=t.y-e;return r*r+i*i}function Uo(t){this._=t,this.next=null,this.previous=null}function Do(t){if(!(i=t.length))return 0;var n,e,r,i;if(n=t[0],n.x=0,n.y=0,!(i>1))return n.r;if(e=t[1],n.x=-e.r,e.x=n.r,e.y=0,!(i>2))return n.r+e.r;Po(e,n,r=t[2]);var o,u,a,c,s,f,l,h=n.r*n.r,p=e.r*e.r,d=r.r*r.r,v=h+p+d,_=h*n.x+p*e.x+d*r.x,y=h*n.y+p*e.y+d*r.y;n=new Uo(n),e=new Uo(e),r=new Uo(r),n.next=r.previous=e,e.next=n.previous=r,r.next=e.previous=n;t:for(a=3;aqo(c,e)?n=c:e=c,n.next=e,e.previous=n,--a;continue t}f+=c._.r,c=c.next}else{if(Ro(s._,r._)){qo(n,s)>l+n._.r+e._.r?n=s:e=s,n.next=e,e.previous=n,--a;continue t}l+=s._.r,s=s.previous}while(c!==s.next);for(r.previous=n,r.next=e,n.next=e.previous=e=r,v+=d=r._.r*r._.r,_+=d*r._.x,y+=d*r._.y,h=Lo(n._,o=_/v,u=y/v);(r=r.next)!==e;)(d=Lo(r._,o,u))=0;)n=i[o],n.z+=e,n.m+=e,e+=n.s+(r+=n.c)}function Qo(t,n,e){return t.a.parent===n.parent?t.a:e}function Ko(t,n){this._=t,this.parent=null,this.children=null,this.A=null,this.a=this,this.z=0,this.m=0,this.c=0,this.s=0,this.t=null,this.i=n}function tu(t){for(var n,e,r,i,o,u=new Ko(t,0),a=[u];n=a.pop();)if(r=n._.children)for(n.children=new Array(o=r.length),i=o-1;i>=0;--i)a.push(e=n.children[i]=new Ko(r[i],i)),e.parent=n;return(u.parent=new Ko(null,0)).children=[u],u}function nu(t,n,e,r,i,o){for(var u,a,c,s,f,l,h,p,d,v,_,y=[],g=n.children,m=0,x=0,b=g.length,w=n.value;mh&&(h=a),_=f*f*v,p=Math.max(h/_,_/l),p>d){f-=a;break}d=p}y.push(u={value:f,dice:c1&&Gg(t[e[r-2]],t[e[r-1]],t[i])<=0;)--r;e[r++]=i}return e.slice(0,r)}function iu(t){if(!(t>=1))throw new Error;this._size=t,this._call=this._error=null,this._tasks=[],this._data=[],this._waiting=this._active=this._ended=this._start=0}function ou(t){if(!t._start)try{uu(t)}catch(n){if(t._tasks[t._ended+t._active-1])cu(t,n);else if(!t._data)throw n}}function uu(t){for(;t._start=t._waiting&&t._active=0;)if((e=t._tasks[r])&&(t._tasks[r]=null,e.abort))try{e.abort()}catch(t){}t._active=NaN,su(t)}function su(t){if(!t._active&&t._call){var n=t._data;t._data=void 0,t._call(t._error,n)}}function fu(t){return new iu(arguments.length?+t:1/0)}function lu(t){return function(n,e){t(null==n?e:null)}}function hu(t){var n=t.responseType;return n&&"text"!==n?t.response:t.responseText}function pu(t,n){return function(e){return t(e.responseText,n)}}function du(t){function n(n){var o=n+"",u=e.get(o);if(!u){if(i!==xm)return i;e.set(o,u=r.push(n))}return t[(u-1)%t.length]}var e=Ie(),r=[],i=xm;return t=null==t?[]:mm.call(t),n.domain=function(t){if(!arguments.length)return r.slice();r=[],e=Ie();for(var i,o,u=-1,a=t.length;++u=e?1:r(t)}}}function xu(t){return function(n,e){var r=t(n=+n,e=+e);return function(t){return t<=0?n:t>=1?e:r(t)}}}function bu(t,n,e,r){var i=t[0],o=t[1],u=n[0],a=n[1];return o2?wu:bu,o=u=null,r}function r(n){return(o||(o=i(a,c,f?mu(t):t,s)))(+n)}var i,o,u,a=Mm,c=Mm,s=mh,f=!1;return r.invert=function(t){return(u||(u=i(c,a,gu,f?xu(n):n)))(+t)},r.domain=function(t){return arguments.length?(a=gm.call(t,wm),e()):a.slice()},r.range=function(t){return arguments.length?(c=mm.call(t),e()):c.slice()},r.rangeRound=function(t){return c=mm.call(t),s=xh,e()},r.clamp=function(t){return arguments.length?(f=!!t,e()):f},r.interpolate=function(t){return arguments.length?(s=t,e()):s},e()}function Nu(t){var n=t.domain;return t.ticks=function(t){var e=n();return Hs(e[0],e[e.length-1],null==t?10:t)},t.tickFormat=function(t,e){return Tm(n(),t,e)},t.nice=function(r){var i=n(),o=i.length-1,u=null==r?10:r,a=i[0],c=i[o],s=e(a,c,u);return s&&(s=e(Math.floor(a/s)*s,Math.ceil(c/s)*s,u),i[0]=Math.floor(a/s)*s,i[o]=Math.ceil(c/s)*s,n(i)),t},t}function ku(){var t=Tu(gu,dh);return t.copy=function(){return Mu(t,ku())},Nu(t)}function Su(){function t(t){return+t}var n=[0,1];return t.invert=t,t.domain=t.range=function(e){return arguments.length?(n=gm.call(e,wm),t):n.slice()},t.copy=function(){return Su().domain(n)},Nu(t)}function Eu(t,n){return(n=Math.log(n/t))?function(e){return Math.log(e/t)/n}:bm(n)}function Au(t,n){return t<0?function(e){return-Math.pow(-n,e)*Math.pow(-t,1-e)}:function(e){return Math.pow(n,e)*Math.pow(t,1-e)}}function Cu(t){return isFinite(t)?+("1e"+t):t<0?0:t}function zu(t){return 10===t?Cu:t===Math.E?Math.exp:function(n){return Math.pow(t,n)}}function Pu(t){return t===Math.E?Math.log:10===t&&Math.log10||2===t&&Math.log2||(t=Math.log(t),function(n){return Math.log(n)/t})}function Ru(t){return function(n){return-t(-n)}}function qu(){function n(){return o=Pu(i),u=zu(i),r()[0]<0&&(o=Ru(o),u=Ru(u)),e}var e=Tu(Eu,Au).domain([1,10]),r=e.domain,i=10,o=Pu(10),u=zu(10);return e.base=function(t){return arguments.length?(i=+t,n()):i},e.domain=function(t){return arguments.length?(r(t),n()):r()},e.ticks=function(t){var n,e=r(),a=e[0],c=e[e.length-1];(n=c0){for(;hc)break;v.push(l)}}else for(;h=1;--f)if(l=s*f,!(lc)break;v.push(l)}}else v=Hs(h,p,Math.min(p-h,d)).map(u);return n?v.reverse():v},e.tickFormat=function(n,r){if(null==r&&(r=10===i?".0e":","),"function"!=typeof r&&(r=t.format(r)),n===1/0)return r;null==n&&(n=10);var a=Math.max(1,i*n/e.ticks().length);return function(t){var n=t/u(Math.round(o(t)));return n*i0?i[n-1]:e[0],n=i?[o[i-1],r]:[o[n-1],o[n]]},t.copy=function(){return Fu().domain([e,r]).range(u)},Nu(t)}function Iu(){function t(t){if(t<=t)return e[Es(n,t,0,r)]}var n=[.5],e=[0,1],r=1;return t.domain=function(i){return arguments.length?(n=mm.call(i),r=Math.min(n.length,e.length-1),t):n.slice()},t.range=function(i){return arguments.length?(e=mm.call(i),r=Math.min(n.length,e.length-1),t):e.slice()},t.invertExtent=function(t){var r=e.indexOf(t);return[n[r-1],n[r]]},t.copy=function(){return Iu().domain(n).range(e)},t}function Yu(t,n,e,r){function i(n){return t(n=new Date(+n)),n}return i.floor=i,i.ceil=function(e){return t(e=new Date(e-1)),n(e,1),t(e),e},i.round=function(t){var n=i(t),e=i.ceil(t);return t-n0))return u;do u.push(new Date(+e));while(n(e,o),t(e),e=n)for(;t(n),!e(n);)n.setTime(n-1)},function(t,r){if(t>=t)for(;--r>=0;)for(;n(t,1),!e(t););})},e&&(i.count=function(n,r){return km.setTime(+n),Sm.setTime(+r),t(km),t(Sm),Math.floor(e(km,Sm))},i.every=function(t){return t=Math.floor(t),isFinite(t)&&t>0?t>1?i.filter(r?function(n){return r(n)%t===0}:function(n){return i.count(0,n)%t===0}):i:null}),i}function Bu(t){return Yu(function(n){n.setDate(n.getDate()-(n.getDay()+7-t)%7),n.setHours(0,0,0,0)},function(t,n){t.setDate(t.getDate()+7*n)},function(t,n){return(n-t-(n.getTimezoneOffset()-t.getTimezoneOffset())*zm)/qm})}function ju(t){return Yu(function(n){n.setUTCDate(n.getUTCDate()-(n.getUTCDay()+7-t)%7),n.setUTCHours(0,0,0,0)},function(t,n){t.setUTCDate(t.getUTCDate()+7*n)},function(t,n){return(n-t)/qm})}function Hu(t){if(0<=t.y&&t.y<100){var n=new Date(-1,t.m,t.d,t.H,t.M,t.S,t.L);return n.setFullYear(t.y),n}return new Date(t.y,t.m,t.d,t.H,t.M,t.S,t.L)}function Xu(t){if(0<=t.y&&t.y<100){var n=new Date(Date.UTC(-1,t.m,t.d,t.H,t.M,t.S,t.L));return n.setUTCFullYear(t.y),n}return new Date(Date.UTC(t.y,t.m,t.d,t.H,t.M,t.S,t.L))}function Vu(t){return{y:t,m:0,d:1,H:0,M:0,S:0,L:0}}function Wu(t){function n(t,n){return function(e){var r,i,o,u=[],a=-1,c=0,s=t.length;for(e instanceof Date||(e=new Date(+e));++a=c)return-1;if(i=n.charCodeAt(u++),37===i){if(i=n.charAt(u++),o=B[i in Px?n.charAt(u++):i],!o||(r=o(t,e,r))<0)return-1}else if(i!=e.charCodeAt(r++))return-1}return r}function i(t,n,e){var r=C.exec(n.slice(e));return r?(t.p=z[r[0].toLowerCase()],e+r[0].length):-1}function o(t,n,e){var r=q.exec(n.slice(e));return r?(t.w=L[r[0].toLowerCase()],e+r[0].length):-1}function u(t,n,e){var r=P.exec(n.slice(e));return r?(t.w=R[r[0].toLowerCase()],e+r[0].length):-1; +}function a(t,n,e){var r=O.exec(n.slice(e));return r?(t.m=F[r[0].toLowerCase()],e+r[0].length):-1}function c(t,n,e){var r=U.exec(n.slice(e));return r?(t.m=D[r[0].toLowerCase()],e+r[0].length):-1}function s(t,n,e){return r(t,w,n,e)}function f(t,n,e){return r(t,M,n,e)}function l(t,n,e){return r(t,T,n,e)}function h(t){return S[t.getDay()]}function p(t){return k[t.getDay()]}function d(t){return A[t.getMonth()]}function v(t){return E[t.getMonth()]}function _(t){return N[+(t.getHours()>=12)]}function y(t){return S[t.getUTCDay()]}function g(t){return k[t.getUTCDay()]}function m(t){return A[t.getUTCMonth()]}function x(t){return E[t.getUTCMonth()]}function b(t){return N[+(t.getUTCHours()>=12)]}var w=t.dateTime,M=t.date,T=t.time,N=t.periods,k=t.days,S=t.shortDays,E=t.months,A=t.shortMonths,C=Gu(N),z=Ju(N),P=Gu(k),R=Ju(k),q=Gu(S),L=Ju(S),U=Gu(E),D=Ju(E),O=Gu(A),F=Ju(A),I={a:h,A:p,b:d,B:v,c:null,d:ha,e:ha,H:pa,I:da,j:va,L:_a,m:ya,M:ga,p:_,S:ma,U:xa,w:ba,W:wa,x:null,X:null,y:Ma,Y:Ta,Z:Na,"%":Ia},Y={a:y,A:g,b:m,B:x,c:null,d:ka,e:ka,H:Sa,I:Ea,j:Aa,L:Ca,m:za,M:Pa,p:b,S:Ra,U:qa,w:La,W:Ua,x:null,X:null,y:Da,Y:Oa,Z:Fa,"%":Ia},B={a:o,A:u,b:a,B:c,c:s,d:oa,e:oa,H:aa,I:aa,j:ua,L:fa,m:ia,M:ca,p:i,S:sa,U:Ku,w:Qu,W:ta,x:f,X:l,y:ea,Y:na,Z:ra,"%":la};return I.x=n(M,I),I.X=n(T,I),I.c=n(w,I),Y.x=n(M,Y),Y.X=n(T,Y),Y.c=n(w,Y),{format:function(t){var e=n(t+="",I);return e.toString=function(){return t},e},parse:function(t){var n=e(t+="",Hu);return n.toString=function(){return t},n},utcFormat:function(t){var e=n(t+="",Y);return e.toString=function(){return t},e},utcParse:function(t){var n=e(t,Xu);return n.toString=function(){return t},n}}}function $u(t,n,e){var r=t<0?"-":"",i=(r?-t:t)+"",o=i.length;return r+(o68?1900:2e3),e+r[0].length):-1}function ra(t,n,e){var r=/^(Z)|([+-]\d\d)(?:\:?(\d\d))?/.exec(n.slice(e,e+6));return r?(t.Z=r[1]?0:-(r[2]+(r[3]||"00")),e+r[0].length):-1}function ia(t,n,e){var r=Rx.exec(n.slice(e,e+2));return r?(t.m=r[0]-1,e+r[0].length):-1}function oa(t,n,e){var r=Rx.exec(n.slice(e,e+2));return r?(t.d=+r[0],e+r[0].length):-1}function ua(t,n,e){var r=Rx.exec(n.slice(e,e+3));return r?(t.m=0,t.d=+r[0],e+r[0].length):-1}function aa(t,n,e){var r=Rx.exec(n.slice(e,e+2));return r?(t.H=+r[0],e+r[0].length):-1}function ca(t,n,e){var r=Rx.exec(n.slice(e,e+2));return r?(t.M=+r[0],e+r[0].length):-1}function sa(t,n,e){var r=Rx.exec(n.slice(e,e+2));return r?(t.S=+r[0],e+r[0].length):-1}function fa(t,n,e){var r=Rx.exec(n.slice(e,e+3));return r?(t.L=+r[0],e+r[0].length):-1}function la(t,n,e){var r=qx.exec(n.slice(e,e+1));return r?e+r[0].length:-1}function ha(t,n){return $u(t.getDate(),n,2)}function pa(t,n){return $u(t.getHours(),n,2)}function da(t,n){return $u(t.getHours()%12||12,n,2)}function va(t,n){return $u(1+Ym.count(ox(t),t),n,3)}function _a(t,n){return $u(t.getMilliseconds(),n,3)}function ya(t,n){return $u(t.getMonth()+1,n,2)}function ga(t,n){return $u(t.getMinutes(),n,2)}function ma(t,n){return $u(t.getSeconds(),n,2)}function xa(t,n){return $u(jm.count(ox(t),t),n,2)}function ba(t){return t.getDay()}function wa(t,n){return $u(Hm.count(ox(t),t),n,2)}function Ma(t,n){return $u(t.getFullYear()%100,n,2)}function Ta(t,n){return $u(t.getFullYear()%1e4,n,4)}function Na(t){var n=t.getTimezoneOffset();return(n>0?"-":(n*=-1,"+"))+$u(n/60|0,"0",2)+$u(n%60,"0",2)}function ka(t,n){return $u(t.getUTCDate(),n,2)}function Sa(t,n){return $u(t.getUTCHours(),n,2)}function Ea(t,n){return $u(t.getUTCHours()%12||12,n,2)}function Aa(t,n){return $u(1+lx.count(Ax(t),t),n,3)}function Ca(t,n){return $u(t.getUTCMilliseconds(),n,3)}function za(t,n){return $u(t.getUTCMonth()+1,n,2)}function Pa(t,n){return $u(t.getUTCMinutes(),n,2)}function Ra(t,n){return $u(t.getUTCSeconds(),n,2)}function qa(t,n){return $u(px.count(Ax(t),t),n,2)}function La(t){return t.getUTCDay()}function Ua(t,n){return $u(dx.count(Ax(t),t),n,2)}function Da(t,n){return $u(t.getUTCFullYear()%100,n,2)}function Oa(t,n){return $u(t.getUTCFullYear()%1e4,n,4)}function Fa(){return"+0000"}function Ia(){return"%"}function Ya(n){return Cx=Wu(n),t.timeFormat=Cx.format,t.timeParse=Cx.parse,t.utcFormat=Cx.utcFormat,t.utcParse=Cx.utcParse,Cx}function Ba(t){return t.toISOString()}function ja(t){var n=new Date(t);return isNaN(n)?null:n}function Ha(t){return new Date(t)}function Xa(t){return t instanceof Date?+t:+new Date(+t)}function Va(t,n,r,i,o,u,a,c,s){function f(e){return(a(e)=1?lb:t<=-1?-lb:Math.asin(t)}function nc(t,n,e,r,i,o,u,a){var c=e-t,s=r-n,f=u-i,l=a-o,h=(f*(n-o)-l*(t-i))/(l*c-f*s);return[t+h*c,n+h*s]}function ec(t,n,e,r,i,o,u){var a=t-e,c=n-r,s=(u?o:-o)/Math.sqrt(a*a+c*c),f=s*c,l=-s*a,h=t+f,p=n+l,d=e+f,v=r+l,_=(h+d)/2,y=(p+v)/2,g=d-h,m=v-p,x=g*g+m*m,b=i-o,w=h*v-d*p,M=(m<0?-1:1)*Math.sqrt(Math.max(0,b*b*x-w*w)),T=(w*m-g*M)/x,N=(-w*g-m*M)/x,k=(w*m+g*M)/x,S=(-w*g+m*M)/x,E=T-_,A=N-y,C=k-_,z=S-y;return E*E+A*A>C*C+z*z&&(T=k,N=S),{cx:T,cy:N,x01:-f,y01:-l,x11:T*(i/b-1),y11:N*(i/b-1)}}function rc(t){this._context=t}function ic(t){return t[0]}function oc(t){return t[1]}function uc(t){this._curve=t}function ac(t){function n(n){return new uc(t(n))}return n._curve=t,n}function cc(t){var n=t.curve;return t.angle=t.x,delete t.x,t.radius=t.y,delete t.y,t.curve=function(t){return arguments.length?n(ac(t)):n()._curve},t}function sc(t,n,e){t._context.bezierCurveTo((2*t._x0+t._x1)/3,(2*t._y0+t._y1)/3,(t._x0+2*t._x1)/3,(t._y0+2*t._y1)/3,(t._x0+4*t._x1+n)/6,(t._y0+4*t._y1+e)/6)}function fc(t){this._context=t}function lc(t){this._context=t}function hc(t){this._context=t}function pc(t,n){this._basis=new fc(t),this._beta=n}function dc(t,n,e){t._context.bezierCurveTo(t._x1+t._k*(t._x2-t._x0),t._y1+t._k*(t._y2-t._y0),t._x2+t._k*(t._x1-n),t._y2+t._k*(t._y1-e),t._x2,t._y2)}function vc(t,n){this._context=t,this._k=(1-n)/6}function _c(t,n){this._context=t,this._k=(1-n)/6}function yc(t,n){this._context=t,this._k=(1-n)/6}function gc(t,n,e){var r=t._x1,i=t._y1,o=t._x2,u=t._y2;if(t._l01_a>sb){var a=2*t._l01_2a+3*t._l01_a*t._l12_a+t._l12_2a,c=3*t._l01_a*(t._l01_a+t._l12_a);r=(r*a-t._x0*t._l12_2a+t._x2*t._l01_2a)/c,i=(i*a-t._y0*t._l12_2a+t._y2*t._l01_2a)/c}if(t._l23_a>sb){var s=2*t._l23_2a+3*t._l23_a*t._l12_a+t._l12_2a,f=3*t._l23_a*(t._l23_a+t._l12_a);o=(o*s+t._x1*t._l23_2a-n*t._l12_2a)/f,u=(u*s+t._y1*t._l23_2a-e*t._l12_2a)/f}t._context.bezierCurveTo(r,i,o,u,t._x2,t._y2)}function mc(t,n){this._context=t,this._alpha=n}function xc(t,n){this._context=t,this._alpha=n}function bc(t,n){this._context=t,this._alpha=n}function wc(t){this._context=t}function Mc(t){return t<0?-1:1}function Tc(t,n,e){var r=t._x1-t._x0,i=n-t._x1,o=(t._y1-t._y0)/(r||i<0&&-0),u=(e-t._y1)/(i||r<0&&-0),a=(o*i+u*r)/(r+i);return(Mc(o)+Mc(u))*Math.min(Math.abs(o),Math.abs(u),.5*Math.abs(a))||0}function Nc(t,n){var e=t._x1-t._x0;return e?(3*(t._y1-t._y0)/e-n)/2:n}function kc(t,n,e){var r=t._x0,i=t._y0,o=t._x1,u=t._y1,a=(o-r)/3;t._context.bezierCurveTo(r+a,i+a*n,o-a,u-a*e,o,u)}function Sc(t){this._context=t}function Ec(t){this._context=new Ac(t)}function Ac(t){this._context=t}function Cc(t){return new Sc(t)}function zc(t){return new Ec(t)}function Pc(t){this._context=t}function Rc(t){var n,e,r=t.length-1,i=new Array(r),o=new Array(r),u=new Array(r);for(i[0]=0,o[0]=2,u[0]=t[0]+2*t[1],n=1;n=0;--n)i[n]=(u[n]-i[n+1])/o[n];for(o[r-1]=(t[r]+i[r-1])/2,n=0;n0)){if(o/=d,d<0){if(o0){if(o>p)return;o>h&&(h=o)}if(o=r-c,d||!(o<0)){if(o/=d,d<0){if(o>p)return;o>h&&(h=o)}else if(d>0){if(o0)){if(o/=v,v<0){if(o0){if(o>p)return;o>h&&(h=o)}if(o=i-s,v||!(o<0)){if(o/=v,v<0){if(o>p)return;o>h&&(h=o)}else if(v>0){if(o0||p<1)||(h>0&&(t[0]=[c+h*d,s+h*v]),p<1&&(t[1]=[c+p*d,s+p*v]),!0)}}}}}function Gc(t,n,e,r,i){var o=t[1];if(o)return!0;var u,a,c=t[0],s=t.left,f=t.right,l=s[0],h=s[1],p=f[0],d=f[1],v=(l+p)/2,_=(h+d)/2;if(d===h){if(v=r)return;if(l>p){if(c){if(c[1]>=i)return}else c=[v,e];o=[v,i]}else{if(c){if(c[1]1)if(l>p){if(c){if(c[1]>=i)return}else c=[(e-a)/u,e];o=[(i-a)/u,i]}else{if(c){if(c[1]=r)return}else c=[n,u*n+a];o=[r,u*r+a]}else{if(c){if(c[0]ww||Math.abs(i[0][1]-i[1][1])>ww)||delete mw[o]}function Qc(t){return yw[t.index]={site:t,halfedges:[]}}function Kc(t,n){var e=t.site,r=n.left,i=n.right;return e===i&&(i=r,r=e),i?Math.atan2(i[1]-r[1],i[0]-r[0]):(e===r?(r=n[1],i=n[0]):(r=n[0],i=n[1]),Math.atan2(r[0]-i[0],i[1]-r[1]))}function ts(t,n){return n[+(n.left!==t.site)]}function ns(t,n){return n[+(n.left===t.site)]}function es(){for(var t,n,e,r,i=0,o=yw.length;iww||Math.abs(v-h)>ww)&&(c.splice(a,0,mw.push(Wc(u,p,Math.abs(d-t)ww?[t,Math.abs(l-t)ww?[Math.abs(h-r)ww?[e,Math.abs(l-e)ww?[Math.abs(h-n)=-Mw)){var p=c*c+s*s,d=f*f+l*l,v=(l*p-s*d)/h,_=(c*d-f*p)/h,y=xw.pop()||new is;y.arc=t,y.site=i,y.x=v+u,y.y=(y.cy=_+a)+Math.sqrt(v*v+_*_),t.circle=y;for(var g=null,m=gw._;m;)if(y.yww)a=a.L;else{if(i=o-ps(a,u),!(i>ww)){r>-ww?(n=a.P,e=a):i>-ww?(n=a,e=a.N):n=e=a;break}if(!a.R){n=a;break}a=a.R}Qc(t);var c=cs(t);if(_w.insert(n,c),n||e){if(n===e)return us(n),e=cs(n.site),_w.insert(c,e),c.edge=e.edge=Vc(n.site,c.site),os(n),void os(e);if(!e)return void(c.edge=Vc(n.site,c.site));us(n),us(e);var s=n.site,f=s[0],l=s[1],h=t[0]-f,p=t[1]-l,d=e.site,v=d[0]-f,_=d[1]-l,y=2*(h*_-p*v),g=h*h+p*p,m=v*v+_*_,x=[(_*g-p*m)/y+f,(h*m-v*g)/y+l];$c(e.edge,s,d,x),c.edge=Vc(s,t,null,x),e.edge=Vc(t,d,null,x),os(n),os(e)}}function hs(t,n){var e=t.site,r=e[0],i=e[1],o=i-n;if(!o)return r;var u=t.P;if(!u)return-(1/0);e=u.site;var a=e[0],c=e[1],s=c-n;if(!s)return a;var f=a-r,l=1/o-1/s,h=f/s;return l?(-h+Math.sqrt(h*h-2*l*(f*f/(-2*s)-c+s/2+i-o/2)))/l+r:(r+a)/2}function ps(t,n){var e=t.N;if(e)return hs(e,n);var r=t.site;return r[1]===n?r[0]:1/0}function ds(t,n,e){return(t[0]-e[0])*(n[1]-t[1])-(t[0]-n[0])*(e[1]-t[1])}function vs(t,n){return n[1]-t[1]||n[0]-t[0]}function _s(t,n){var e,r,i,o=t.sort(vs).pop();for(mw=[],yw=new Array(t.length),_w=new Yc,gw=new Yc;;)if(i=vw,o&&(!i||o[1]n?1:t>=n?0:NaN},ks=function(t){return 1===t.length&&(t=n(t)),{left:function(n,e,r,i){for(null==r&&(r=0),null==i&&(i=n.length);r>>1;t(n[o],e)<0?r=o+1:i=o}return r},right:function(n,e,r,i){for(null==r&&(r=0),null==i&&(i=n.length);r>>1;t(n[o],e)>0?i=o:r=o+1}return r}}},Ss=ks(Ns),Es=Ss.right,As=Ss.left,Cs=function(t,n){return nt?1:n>=t?0:NaN},zs=function(t){return null===t?NaN:+t},Ps=function(t,n){var e,r,i=t.length,o=0,u=0,a=-1,c=0;if(null==n)for(;++a1)return u/(c-1)},Rs=function(t,n){var e=Ps(t,n);return e?Math.sqrt(e):e},qs=function(t,n){var e,r,i,o=-1,u=t.length;if(null==n){for(;++o=r){e=i=r;break}for(;++or&&(e=r),i=r){e=i=r;break}for(;++or&&(e=r),i=f;)l.pop(),--h;var p,d=new Array(h+1);for(i=0;i<=h;++i)p=d[i]=[],p.x0=i>0?l[i-1]:s,p.x1=i=1)return+e(t[r-1],r-1,t);var r,i=(r-1)*n,o=Math.floor(i),u=+e(t[o],o,t),a=+e(t[o+1],o+1,t);return u+(a-u)*(i-o)}},$s=function(t,n,e){return t=Ds.call(t,zs).sort(Ns),Math.ceil((e-n)/(2*(Ws(t,.75)-Ws(t,.25))*Math.pow(t.length,-1/3)))},Zs=function(t,n,e){return Math.ceil((e-n)/(3.5*Rs(t)*Math.pow(t.length,-1/3)))},Gs=function(t,n){var e,r,i=-1,o=t.length;if(null==n){for(;++i=r){e=r;break}for(;++ie&&(e=r)}else{for(;++i=r){e=r;break}for(;++ie&&(e=r)}return e},Js=function(t,n){var e,r=0,i=t.length,o=-1,u=i;if(null==n)for(;++o=0;)for(r=t[i],n=r.length;--n>=0;)e[--u]=r[n];return e},tf=function(t,n){var e,r,i=-1,o=t.length;if(null==n){for(;++i=r){e=r;break}for(;++ir&&(e=r)}else{for(;++i=r){e=r;break}for(;++ir&&(e=r)}return e},nf=function(t){for(var n=0,e=t.length-1,r=t[0],i=new Array(e<0?0:e);n0)for(var e,r,i=new Array(e),o=0;o=0&&"xmlns"!==(n=t.slice(0,e))&&(t=t.slice(e+1)),gf.hasOwnProperty(n)?{space:gf[n],local:t}:t},xf=function(t){var n=mf(t);return(n.local?m:g)(n)},bf=0;b.prototype=x.prototype={constructor:b,get:function(t){for(var n=this._;!(n in t);)if(!(t=t.parentNode))return;return t[n]},set:function(t,n){return t[this._]=n},remove:function(t){return this._ in t&&delete t[this._]},toString:function(){return this._}};var wf=function(t){return function(){return this.matches(t)}};if("undefined"!=typeof document){var Mf=document.documentElement;if(!Mf.matches){var Tf=Mf.webkitMatchesSelector||Mf.msMatchesSelector||Mf.mozMatchesSelector||Mf.oMatchesSelector;wf=function(t){return function(){return Tf.call(this,t)}}}}var Nf=wf,kf={};if(t.event=null,"undefined"!=typeof document){var Sf=document.documentElement;"onmouseenter"in Sf||(kf={mouseenter:"mouseover",mouseleave:"mouseout"})}var Ef=function(t,n,e){var r,i,o=T(t+""),u=o.length;{if(!(arguments.length<2)){for(a=n?k:N,null==e&&(e=!1),r=0;r=b&&(b=x+1);!(m=_[b])&&++b=0;)(r=i[o])&&(u&&u!==r.nextSibling&&u.parentNode.insertBefore(r,u),u=r);return this},Xf=function(t){function n(n,e){return n&&e?t(n.__data__,e.__data__):!n-!e}t||(t=R);for(var e=this._groups,r=e.length,i=new Array(r),o=0;o1?this.each((null==n?I:"function"==typeof n?B:Y)(t,n,null==e?"":e)):Kf(r=this.node()).getComputedStyle(r,null).getPropertyValue(t)},nl=function(t,n){return arguments.length>1?this.each((null==n?j:"function"==typeof n?X:H)(t,n)):this.node()[t]};$.prototype={add:function(t){var n=this._names.indexOf(t);n<0&&(this._names.push(t),this._node.setAttribute("class",this._names.join(" ")))},remove:function(t){var n=this._names.indexOf(t);n>=0&&(this._names.splice(n,1),this._node.setAttribute("class",this._names.join(" ")))},contains:function(t){return this._names.indexOf(t)>=0}};var el=function(t,n){var e=V(t+"");if(arguments.length<2){for(var r=W(this.node()),i=-1,o=e.length;++i=240?t-240:t+120,i,r),Rt(t,i,r),Rt(t<120?t+240:t-120,i,r),this.opacity)},displayable:function(){return(0<=this.s&&this.s<=1||isNaN(this.s))&&0<=this.l&&this.l<=1&&0<=this.opacity&&this.opacity<=1}}));var Ul=Math.PI/180,Dl=180/Math.PI,Ol=18,Fl=.95047,Il=1,Yl=1.08883,Bl=4/29,jl=6/29,Hl=3*jl*jl,Xl=jl*jl*jl;bl(Ut,Lt,bt(wt,{brighter:function(t){return new Ut(this.l+Ol*(null==t?1:t),this.a,this.b,this.opacity)},darker:function(t){return new Ut(this.l-Ol*(null==t?1:t),this.a,this.b,this.opacity)},rgb:function(){var t=(this.l+16)/116,n=isNaN(this.a)?t:t+this.a/500,e=isNaN(this.b)?t:t-this.b/200;return t=Il*Ot(t),n=Fl*Ot(n),e=Yl*Ot(e),new Et(Ft(3.2404542*n-1.5371385*t-.4985314*e),Ft(-.969266*n+1.8760108*t+.041556*e),Ft(.0556434*n-.2040259*t+1.0572252*e),this.opacity)}})),bl(jt,Bt,bt(wt,{brighter:function(t){return new jt(this.h,this.c,this.l+Ol*(null==t?1:t),this.opacity)},darker:function(t){return new jt(this.h,this.c,this.l-Ol*(null==t?1:t),this.opacity)},rgb:function(){return qt(this).rgb()}}));var Vl=-.14861,Wl=1.78277,$l=-.29227,Zl=-.90649,Gl=1.97294,Jl=Gl*Zl,Ql=Gl*Wl,Kl=Wl*$l-Zl*Vl;bl(Vt,Xt,bt(wt,{brighter:function(t){return t=null==t?Ml:Math.pow(Ml,t),new Vt(this.h,this.s,this.l*t,this.opacity)},darker:function(t){return t=null==t?wl:Math.pow(wl,t),new Vt(this.h,this.s,this.l*t,this.opacity)},rgb:function(){var t=isNaN(this.h)?0:(this.h+120)*Ul,n=+this.l,e=isNaN(this.s)?0:this.s*n*(1-n),r=Math.cos(t),i=Math.sin(t);return new Et(255*(n+e*(Vl*r+Wl*i)),255*(n+e*($l*r+Zl*i)),255*(n+e*(Gl*r)),this.opacity)}}));var th,nh,eh,rh,ih,oh,uh=function(t){var n=t.length-1;return function(e){var r=e<=0?e=0:e>=1?(e=1,n-1):Math.floor(e*n),i=t[r],o=t[r+1],u=r>0?t[r-1]:2*i-o,a=ro&&(i=n.slice(o,i),a[u]?a[u]+=i:a[++u]=i),(e=e[0])===(r=r[0])?a[u]?a[u]+=r:a[++u]=r:(a[++u]=null,c.push({i:u,x:dh(e,r)})),o=yh.lastIndex;return oKh&&e.stateQh&&e.name===n)return new Qn([[t]],ed,n,+r)}return null},id=function(t){return function(){return t}},od=function(t,n,e){this.target=t,this.type=n,this.selection=e},ud=function(){t.event.preventDefault(),t.event.stopImmediatePropagation()},ad={name:"drag"},cd={name:"space"},sd={name:"handle"},fd={name:"center"},ld={name:"x",handles:["e","w"].map(we),input:function(t,n){return t&&[[t[0],n[0][1]],[t[1],n[1][1]]]},output:function(t){return t&&[t[0][0],t[1][0]]}},hd={name:"y",handles:["n","s"].map(we),input:function(t,n){return t&&[[n[0][0],t[0]],[n[1][0],t[1]]]},output:function(t){return t&&[t[0][1],t[1][1]]}},pd={name:"xy",handles:["n","e","s","w","nw","ne","se","sw"].map(we),input:function(t){return t},output:function(t){return t}},dd={overlay:"crosshair",selection:"move",n:"ns-resize",e:"ew-resize",s:"ns-resize",w:"ew-resize",nw:"nwse-resize",ne:"nesw-resize",se:"nwse-resize",sw:"nesw-resize"},vd={e:"w",w:"e",nw:"ne",ne:"nw",se:"sw",sw:"se"},_d={n:"s",s:"n",nw:"sw",ne:"se",se:"ne",sw:"nw"},yd={overlay:1,selection:1,n:null,e:1,s:null,w:-1,nw:-1,ne:1,se:1,sw:-1},gd={overlay:1,selection:1,n:-1,e:null,s:1,w:null,nw:-1,ne:-1,se:1,sw:1},md=function(){return Ce(pd)},xd=Math.cos,bd=Math.sin,wd=Math.PI,Md=wd/2,Td=2*wd,Nd=Math.max,kd=function(){function t(t){var o,u,a,c,s,f,l=t.length,h=[],p=Is(l),d=[],v=[],_=v.groups=new Array(l),y=new Array(l*l);for(o=0,s=-1;++szd)if(Math.abs(f*a-c*s)>zd&&i){var h=e-o,p=r-u,d=a*a+c*c,v=h*h+p*p,_=Math.sqrt(d),y=Math.sqrt(l),g=i*Math.tan((Ad-Math.acos((d+l-v)/(2*_*y)))/2),m=g/y,x=g/_;Math.abs(m-1)>zd&&(this._+="L"+(t+m*s)+","+(n+m*f)),this._+="A"+i+","+i+",0,0,"+ +(f*h>s*p)+","+(this._x1=t+x*a)+","+(this._y1=n+x*c)}else this._+="L"+(this._x1=t)+","+(this._y1=n);else;},arc:function(t,n,e,r,i,o){t=+t,n=+n,e=+e;var u=e*Math.cos(r),a=e*Math.sin(r),c=t+u,s=n+a,f=1^o,l=o?r-i:i-r;if(e<0)throw new Error("negative radius: "+e);null===this._x1?this._+="M"+c+","+s:(Math.abs(this._x1-c)>zd||Math.abs(this._y1-s)>zd)&&(this._+="L"+c+","+s),e&&(l>Pd?this._+="A"+e+","+e+",0,1,"+f+","+(t-u)+","+(n-a)+"A"+e+","+e+",0,1,"+f+","+(this._x1=c)+","+(this._y1=s):(l<0&&(l=l%Cd+Cd),this._+="A"+e+","+e+",0,"+ +(l>=Ad)+","+f+","+(this._x1=t+e*Math.cos(i))+","+(this._y1=n+e*Math.sin(i))))},rect:function(t,n,e,r){this._+="M"+(this._x0=this._x1=+t)+","+(this._y0=this._y1=+n)+"h"+ +e+"v"+ +r+"h"+-e+"Z"},toString:function(){return this._}};var Rd=function(){function t(){var t,a=Sd.call(arguments),c=n.apply(this,a),s=e.apply(this,a),f=+r.apply(this,(a[0]=c,a)),l=i.apply(this,a)-Md,h=o.apply(this,a)-Md,p=f*xd(l),d=f*bd(l),v=+r.apply(this,(a[0]=s,a)),_=i.apply(this,a)-Md,y=o.apply(this,a)-Md;if(u||(u=t=Re()),u.moveTo(p,d),u.arc(0,0,f,l,h),l===_&&h===y||(u.quadraticCurveTo(0,0,v*xd(_),v*bd(_)),u.arc(0,0,v,_,y)),u.quadraticCurveTo(0,0,p,d),u.closePath(),t)return u=null,t+""||null}var n=qe,e=Le,r=Ue,i=De,o=Oe,u=null;return t.radius=function(n){return arguments.length?(r="function"==typeof n?n:Ed(+n),t):r},t.startAngle=function(n){return arguments.length?(i="function"==typeof n?n:Ed(+n),t):i},t.endAngle=function(n){return arguments.length?(o="function"==typeof n?n:Ed(+n),t):o},t.source=function(e){return arguments.length?(n=e,t):n},t.target=function(n){return arguments.length?(e=n,t):e},t.context=function(n){return arguments.length?(u=null==n?null:n,t):u},t},qd="$";Fe.prototype=Ie.prototype={constructor:Fe,has:function(t){return qd+t in this},get:function(t){return this[qd+t]},set:function(t,n){return this[qd+t]=n,this},remove:function(t){var n=qd+t;return n in this&&delete this[n]},clear:function(){for(var t in this)t[0]===qd&&delete this[t]},keys:function(){var t=[];for(var n in this)n[0]===qd&&t.push(n.slice(1));return t},values:function(){var t=[];for(var n in this)n[0]===qd&&t.push(this[n]);return t},entries:function(){var t=[];for(var n in this)n[0]===qd&&t.push({key:n.slice(1),value:this[n]});return t},size:function(){var t=0;for(var n in this)n[0]===qd&&++t;return t},empty:function(){for(var t in this)if(t[0]===qd)return!1;return!0},each:function(t){for(var n in this)n[0]===qd&&t(this[n],n.slice(1),this)}};var Ld=function(){function t(n,i,u,a){if(i>=o.length)return null!=r?r(n):null!=e?n.sort(e):n;for(var c,s,f,l=-1,h=n.length,p=o[i++],d=Ie(),v=u();++lo.length)return t;var i,a=u[e-1];return null!=r&&e>=o.length?i=t.entries():(i=[],t.each(function(t,r){i.push({key:r,values:n(t,e)})})),null!=a?i.sort(function(t,n){return a(t.key,n.key)}):i}var e,r,i,o=[],u=[];return i={object:function(n){return t(n,0,Ye,Be)},map:function(n){return t(n,0,je,He)},entries:function(e){return n(t(e,0,je,He),0)},key:function(t){return o.push(t),i},sortKeys:function(t){return u[o.length-1]=t,i},sortValues:function(t){return e=t,i},rollup:function(t){return r=t,i}}},Ud=Ie.prototype;Xe.prototype=Ve.prototype={constructor:Xe,has:Ud.has,add:function(t){return t+="",this[qd+t]=t,this},remove:Ud.remove,clear:Ud.clear,values:Ud.keys,size:Ud.size,empty:Ud.empty,each:Ud.each};var Dd=function(t){var n=[];for(var e in t)n.push(e);return n},Od=function(t){var n=[];for(var e in t)n.push(t[e]);return n},Fd=function(t){var n=[];for(var e in t)n.push({key:e,value:t[e]});return n},Id=function(t){function n(t,n){var r,i,o=e(t,function(t,e){return r?r(t,e-1):(i=t,void(r=n?$e(t,n):We(t)))});return o.columns=i,o}function e(t,n){function e(){if(f>=s)return u;if(i)return i=!1,o;var n,e=f;if(34===t.charCodeAt(e)){for(var r=e;r++t||t>i||r>n||n>o))return this;var u,a,c=i-e,s=this._root;switch(a=(n<(r+o)/2)<<1|t<(e+i)/2){case 0:do u=new Array(4),u[a]=s,s=u;while(c*=2,i=e+c,o=r+c,t>i||n>o);break;case 1:do u=new Array(4),u[a]=s,s=u;while(c*=2,e=i-c,o=r+c,e>t||n>o);break;case 2:do u=new Array(4),u[a]=s,s=u;while(c*=2,i=e+c,r=o-c,t>i||r>n);break;case 3:do u=new Array(4),u[a]=s,s=u;while(c*=2,e=i-c,r=o-c,e>t||r>n)}this._root&&this._root.length&&(this._root=s)}return this._x0=e,this._y0=r,this._x1=i,this._y1=o,this},ev=function(){var t=[];return this.visit(function(n){if(!n.length)do t.push(n.data);while(n=n.next)}),t},rv=function(t){return arguments.length?this.cover(+t[0][0],+t[0][1]).cover(+t[1][0],+t[1][1]):isNaN(this._x0)?void 0:[[this._x0,this._y0],[this._x1,this._y1]]},iv=function(t,n,e,r,i){this.node=t,this.x0=n,this.y0=e,this.x1=r,this.y1=i},ov=function(t,n,e){var r,i,o,u,a,c,s,f=this._x0,l=this._y0,h=this._x1,p=this._y1,d=[],v=this._root;for(v&&d.push(new iv(v,f,l,h,p)),null==e?e=1/0:(f=t-e,l=n-e,h=t+e,p=n+e,e*=e);c=d.pop();)if(!(!(v=c.node)||(i=c.x0)>h||(o=c.y0)>p||(u=c.x1)=y)<<1|t>=_)&&(c=d[d.length-1],d[d.length-1]=d[d.length-1-s],d[d.length-1-s]=c)}else{var g=t-+this._x.call(null,v.data),m=n-+this._y.call(null,v.data),x=g*g+m*m;if(x=(a=(d+_)/2))?d=a:_=a,(f=u>=(c=(v+y)/2))?v=c:y=c,n=p,!(p=p[l=f<<1|s]))return this;if(!p.length)break;(n[l+1&3]||n[l+2&3]||n[l+3&3])&&(e=n,h=l)}for(;p.data!==t;)if(r=p,!(p=p.next))return this;return(i=p.next)&&delete p.next,r?(i?r.next=i:delete r.next,this):n?(i?n[l]=i:delete n[l],(p=n[0]||n[1]||n[2]||n[3])&&p===(n[3]||n[2]||n[1]||n[0])&&!p.length&&(e?e[h]=p:this._root=p),this):(this._root=i,this)},av=function(){return this._root},cv=function(){var t=0;return this.visit(function(n){if(!n.length)do++t;while(n=n.next)}),t},sv=function(t){var n,e,r,i,o,u,a=[],c=this._root;for(c&&a.push(new iv(c,this._x0,this._y0,this._x1,this._y1));n=a.pop();)if(!t(c=n.node,r=n.x0,i=n.y0,o=n.x1,u=n.y1)&&c.length){var s=(r+o)/2,f=(i+u)/2;(e=c[3])&&a.push(new iv(e,s,f,o,u)),(e=c[2])&&a.push(new iv(e,r,f,s,u)),(e=c[1])&&a.push(new iv(e,s,i,o,f)),(e=c[0])&&a.push(new iv(e,r,i,s,f))}return this},fv=function(t){var n,e=[],r=[];for(this._root&&e.push(new iv(this._root,this._x0,this._y0,this._x1,this._y1));n=e.pop();){var i=n.node;if(i.length){var o,u=n.x0,a=n.y0,c=n.x1,s=n.y1,f=(u+c)/2,l=(a+s)/2;(o=i[0])&&e.push(new iv(o,u,a,f,l)),(o=i[1])&&e.push(new iv(o,f,a,c,l)),(o=i[2])&&e.push(new iv(o,u,l,f,s)),(o=i[3])&&e.push(new iv(o,f,l,c,s))}r.push(n)}for(;n=r.pop();)t(n.node,n.x0,n.y0,n.x1,n.y1);return this},lv=function(t){return arguments.length?(this._x=t,this):this._x},hv=function(t){return arguments.length?(this._y=t,this):this._y},pv=nr.prototype=er.prototype;pv.copy=function(){var t,n,e=new er(this._x,this._y,this._x0,this._y0,this._x1,this._y1),r=this._root;if(!r)return e;if(!r.length)return e._root=rr(r),e;for(t=[{source:r,target:e._root=new Array(4)}];r=t.pop();)for(var i=0;i<4;++i)(n=r.source[i])&&(n.length?t.push({source:n,target:r.target[i]=new Array(4)}):r.target[i]=rr(n));return e},pv.add=tv,pv.addAll=Je,pv.cover=nv,pv.data=ev,pv.extent=rv,pv.find=ov,pv.remove=uv,pv.removeAll=Qe,pv.root=av,pv.size=cv,pv.visit=sv,pv.visitAfter=fv,pv.x=lv,pv.y=hv;var dv,vv=function(t){function n(){function t(t,n,e,r,i){var o=t.data,a=t.r,p=l+a;{if(!o)return n>s+p||rf+p||ic.index){var d=s-o.x-o.vx,v=f-o.y-o.vy,_=d*d+v*v;_t.r&&(t.r=t[n].r)}function r(){if(i){var n,e,r=i.length;for(o=new Array(r),n=0;n1?(null==n?l.remove(t):l.set(t,i(n)),o):l.get(t)},find:function(n,e,r){var i,o,u,a,c,s=0,f=t.length;for(null==r?r=1/0:r*=r,s=0;s1?(d.on(t,n),o):d.on(t)}}},xv=function(){function t(t){var n,a=i.length,c=nr(i,cr,sr).visitAfter(e);for(u=t,n=0;n=f)){(t.data!==o||t.next)&&(0===i&&(i=Kd(),p+=i*i),0===c&&(c=Kd(),p+=c*c),p1?r[0]+r.slice(2):r,+t.slice(e+1)]},Tv=function(t){return t=Mv(Math.abs(t)),t?t[1]:NaN},Nv=function(t,n){return function(e,r){for(var i=e.length,o=[],u=0,a=t[0],c=0;i>0&&a>0&&(c+a+1>r&&(a=Math.max(1,r-c)),o.push(e.substring(i-=a,i+a)),!((c+=a+1)>r));)a=t[u=(u+1)%t.length];return o.reverse().join(n)}},kv=function(t,n){t=t.toPrecision(n);t:for(var e,r=t.length,i=1,o=-1;i0&&(o=0)}return o>0?t.slice(0,o)+t.slice(e+1):t},Sv=function(t,n){var e=Mv(t,n);if(!e)return t+"";var r=e[0],i=e[1],o=i-(dv=3*Math.max(-8,Math.min(8,Math.floor(i/3))))+1,u=r.length;return o===u?r:o>u?r+new Array(o-u+1).join("0"):o>0?r.slice(0,o)+"."+r.slice(o):"0."+new Array(1-o).join("0")+Mv(t,Math.max(0,n+o-1))[0]},Ev=function(t,n){var e=Mv(t,n);if(!e)return t+"";var r=e[0],i=e[1];return i<0?"0."+new Array(-i).join("0")+r:r.length>i+1?r.slice(0,i+1)+"."+r.slice(i+1):r+new Array(i-r.length+2).join("0")},Av={"":kv,"%":function(t,n){return(100*t).toFixed(n)},b:function(t){return Math.round(t).toString(2)},c:function(t){return t+""},d:function(t){return Math.round(t).toString(10)},e:function(t,n){return t.toExponential(n)},f:function(t,n){return t.toFixed(n)},g:function(t,n){return t.toPrecision(n)},o:function(t){return Math.round(t).toString(8)},p:function(t,n){return Ev(100*t,n)},r:Ev,s:Sv,X:function(t){return Math.round(t).toString(16).toUpperCase()},x:function(t){return Math.round(t).toString(16)}},Cv=/^(?:(.)?([<>=^]))?([+\-\( ])?([$#])?(0)?(\d+)?(,)?(\.\d+)?([a-z%])?$/i,zv=function(t){return new fr(t)};fr.prototype.toString=function(){return this.fill+this.align+this.sign+this.symbol+(this.zero?"0":"")+(null==this.width?"":Math.max(1,0|this.width))+(this.comma?",":"")+(null==this.precision?"":"."+Math.max(0,0|this.precision))+this.type};var Pv,Rv=["y","z","a","f","p","n","µ","m","","k","M","G","T","P","E","Z","Y"],qv=function(t){function n(t){function n(t){var n,i,c,g=d,m=v;if("c"===p)m=_(t)+m,t="";else{t=+t;var x=(t<0||1/t<0)&&(t*=-1,!0);if(t=_(t,h),x)for(n=-1,i=t.length,x=!1;++nc||c>57){m=(46===c?o+t.slice(n+1):t.slice(n))+m,t=t.slice(0,n);break}}l&&!s&&(t=r(t,1/0));var b=g.length+t.length+m.length,w=b>1)+g+t+m+w.slice(b)}return w+g+t+m}t=zv(t);var e=t.fill,u=t.align,a=t.sign,c=t.symbol,s=t.zero,f=t.width,l=t.comma,h=t.precision,p=t.type,d="$"===c?i[0]:"#"===c&&/[boxX]/.test(p)?"0"+p.toLowerCase():"",v="$"===c?i[1]:/[%p]/.test(p)?"%":"",_=Av[p],y=!p||/[defgprs%]/.test(p);return h=null==h?p?6:12:/[gprs]/.test(p)?Math.max(1,Math.min(21,h)):Math.max(0,Math.min(20,h)),n.toString=function(){return t+""},n}function e(t,e){var r=n((t=zv(t),t.type="f",t)),i=3*Math.max(-8,Math.min(8,Math.floor(Tv(e)/3))),o=Math.pow(10,-i),u=Rv[8+i/3];return function(t){return r(o*t)+u}}var r=t.grouping&&t.thousands?Nv(t.grouping,t.thousands):lr,i=t.currency,o=t.decimal;return{format:n,formatPrefix:e}};hr({decimal:".",thousands:",",grouping:[3],currency:["$",""]});var Lv=function(t){return Math.max(0,-Tv(Math.abs(t)))},Uv=function(t,n){return Math.max(0,3*Math.max(-8,Math.min(8,Math.floor(Tv(n)/3)))-Tv(Math.abs(t)))},Dv=function(t,n){return t=Math.abs(t),n=Math.abs(n)-t,Math.max(0,Tv(n)-Tv(t))+1},Ov=function(){return new pr};pr.prototype={constructor:pr,reset:function(){this.s=this.t=0},add:function(t){dr(__,t,this.t),dr(this,__.s,this.s),this.s?this.t+=__.t:this.s=__.t},valueOf:function(){return this.s}};var Fv,Iv,Yv,Bv,jv,Hv,Xv,Vv,Wv,$v,Zv,Gv,Jv,Qv,Kv,t_,n_,e_,r_,i_,o_,u_,a_,c_,s_,f_,l_,h_,p_,d_,v_,__=new pr,y_=1e-6,g_=1e-12,m_=Math.PI,x_=m_/2,b_=m_/4,w_=2*m_,M_=180/m_,T_=m_/180,N_=Math.abs,k_=Math.atan,S_=Math.atan2,E_=Math.cos,A_=Math.ceil,C_=Math.exp,z_=Math.log,P_=Math.pow,R_=Math.sin,q_=Math.sign||function(t){return t>0?1:t<0?-1:0},L_=Math.sqrt,U_=Math.tan,D_={Feature:function(t,n){mr(t.geometry,n)},FeatureCollection:function(t,n){for(var e=t.features,r=-1,i=e.length;++ry_?Wv=90:H_<-y_&&(Xv=-90),Kv[0]=Hv,Kv[1]=Vv}},V_=function(t){var n,e,r,i,o,u,a;if(Wv=Vv=-(Hv=Xv=1/0),Qv=[],F_(t,X_),e=Qv.length){for(Qv.sort(Yr),n=1,r=Qv[0],o=[r];nIr(r[0],r[1])&&(r[1]=i[1]),Ir(i[0],r[1])>Ir(r[0],r[1])&&(r[0]=i[0])):o.push(r=i);for(u=-(1/0),e=o.length-1,n=0,r=o[e];n<=e;r=i,++n)i=o[n],(a=Ir(r[1],i[0]))>u&&(u=a,Hv=i[0],Vv=r[1])}return Qv=Kv=null,Hv===1/0||Xv===1/0?[[NaN,NaN],[NaN,NaN]]:[[Hv,Xv],[Vv,Wv]]},W_={sphere:gr,point:jr,lineStart:Xr,lineEnd:$r,polygonStart:function(){W_.lineStart=Zr,W_.lineEnd=Gr},polygonEnd:function(){W_.lineStart=Xr,W_.lineEnd=$r}},$_=function(t){t_=n_=e_=r_=i_=o_=u_=a_=c_=s_=f_=0,F_(t,W_);var n=c_,e=s_,r=f_,i=n*n+e*e+r*r;return i2?t[2]*T_:0),n.invert=function(n){return n=t.invert(n[0]*T_,n[1]*T_),n[0]*=M_,n[1]*=M_,n},n},sy=function(){function t(t,n){e.push(t=r(t,n)),t[0]*=M_,t[1]*=M_}function n(){var t=i.apply(this,arguments),n=o.apply(this,arguments)*T_,c=u.apply(this,arguments)*T_;return e=[],r=ti(-t[0]*T_,-t[1]*T_,0).invert,ii(a,n,c,1),t={type:"Polygon",coordinates:[e]},e=r=null,t}var e,r,i=Z_([0,0]),o=Z_(90),u=Z_(6),a={point:t};return n.center=function(t){return arguments.length?(i="function"==typeof t?t:Z_([+t[0],+t[1]]),n):i},n.radius=function(t){return arguments.length?(o="function"==typeof t?t:Z_(+t),n):o},n.precision=function(t){return arguments.length?(u="function"==typeof t?t:Z_(+t),n):u},n},fy=function(){var t,n=[];return{point:function(n,e){t.push([n,e])},lineStart:function(){n.push(t=[])},lineEnd:gr,rejoin:function(){n.length>1&&n.push(n.pop().concat(n.shift()))},result:function(){var e=n;return n=[],t=null,e}}},ly=function(t,n,e,r,i,o){var u,a=t[0],c=t[1],s=n[0],f=n[1],l=0,h=1,p=s-a,d=f-c;if(u=e-a,p||!(u>0)){if(u/=p,p<0){if(u0){if(u>h)return;u>l&&(l=u)}if(u=i-a,p||!(u<0)){if(u/=p,p<0){if(u>h)return;u>l&&(l=u)}else if(p>0){if(u0)){if(u/=d,d<0){if(u0){if(u>h)return;u>l&&(l=u)}if(u=o-c,d||!(u<0)){if(u/=d,d<0){if(u>h)return;u>l&&(l=u)}else if(d>0){if(u0&&(t[0]=a+l*p,t[1]=c+l*d),h<1&&(n[0]=a+h*p,n[1]=c+h*d),!0}}}}},hy=function(t,n){return N_(t[0]-n[0])=0;--o)i.point((f=s[o])[0],f[1]);else r(h.x,h.p.x,-1,i);h=h.p}h=h.o,s=h.z,p=!p}while(!h.v);i.lineEnd()}}},dy=1e9,vy=-dy,_y=function(){var t,n,e,r=0,i=0,o=960,u=500;return e={stream:function(e){return t&&n===e?t:t=ci(r,i,o,u)(n=e)},extent:function(a){return arguments.length?(r=+a[0][0],i=+a[0][1],o=+a[1][0],u=+a[1][1],t=n=null,e):[[r,i],[o,u]]}}},yy=Ov(),gy={sphere:gr,point:gr,lineStart:si,lineEnd:gr,polygonStart:gr,polygonEnd:gr},my=function(t){return yy.reset(),F_(t,gy),+yy},xy=[null,null],by={type:"LineString",coordinates:xy},wy=function(t,n){return xy[0]=t,xy[1]=n,my(by)},My=function(t,n){var e=t[0]*T_,r=t[1]*T_,i=n[0]*T_,o=n[1]*T_,u=E_(r),a=R_(r),c=E_(o),s=R_(o),f=u*E_(e),l=u*R_(e),h=c*E_(i),p=c*R_(i),d=2*_r(L_(yr(o-r)+u*c*yr(i-e))),v=R_(d),_=d?function(t){var n=R_(t*=d)/v,e=R_(d-t)/v,r=e*f+n*h,i=e*l+n*p,o=e*a+n*s;return[S_(i,r)*M_,S_(o,L_(r*r+i*i))*M_]}:function(){return[e*M_,r*M_]};return _.distance=d,_},Ty=function(t){return t},Ny=Ov(),ky=Ov(),Sy={point:gr,lineStart:gr,lineEnd:gr,polygonStart:function(){Sy.lineStart=yi,Sy.lineEnd=xi},polygonEnd:function(){Sy.lineStart=Sy.lineEnd=Sy.point=gr,Ny.add(N_(ky)),ky.reset()},result:function(){var t=Ny/2;return Ny.reset(),t}},Ey=1/0,Ay=Ey,Cy=-Ey,zy=Cy,Py={point:bi,lineStart:gr,lineEnd:gr,polygonStart:gr,polygonEnd:gr,result:function(){var t=[[Ey,Ay],[Cy,zy]];return Cy=zy=-(Ay=Ey=1/0),t}},Ry=0,qy=0,Ly=0,Uy=0,Dy=0,Oy=0,Fy=0,Iy=0,Yy=0,By={point:wi,lineStart:Mi,lineEnd:ki,polygonStart:function(){By.lineStart=Si,By.lineEnd=Ei},polygonEnd:function(){By.point=wi,By.lineStart=Mi,By.lineEnd=ki},result:function(){var t=Yy?[Fy/Yy,Iy/Yy]:Oy?[Uy/Oy,Dy/Oy]:Ly?[Ry/Ly,qy/Ly]:[NaN,NaN];return Ry=qy=Ly=Uy=Dy=Oy=Fy=Iy=Yy=0,t}};zi.prototype={_radius:4.5,pointRadius:function(t){return this._radius=t,this},polygonStart:function(){this._line=0},polygonEnd:function(){this._line=NaN},lineStart:function(){this._point=0},lineEnd:function(){0===this._line&&this._context.closePath(),this._point=NaN},point:function(t,n){switch(this._point){case 0:this._context.moveTo(t,n),this._point=1;break;case 1:this._context.lineTo(t,n);break;default:this._context.moveTo(t+this._radius,n),this._context.arc(t,n,this._radius,0,w_)}},result:gr},Pi.prototype={_circle:Ri(4.5),pointRadius:function(t){return this._circle=Ri(t),this},polygonStart:function(){this._line=0},polygonEnd:function(){this._line=NaN},lineStart:function(){this._point=0},lineEnd:function(){0===this._line&&this._string.push("Z"),this._point=NaN},point:function(t,n){switch(this._point){case 0:this._string.push("M",t,",",n),this._point=1;break;case 1:this._string.push("L",t,",",n);break;default:this._string.push("M",t,",",n,this._circle)}},result:function(){if(this._string.length){var t=this._string.join("");return this._string=[],t}}};var jy=function(t,n){function e(t){return t&&("function"==typeof o&&i.pointRadius(+o.apply(this,arguments)),F_(t,r(i))),i.result()}var r,i,o=4.5;return e.area=function(t){return F_(t,r(Sy)),Sy.result()},e.bounds=function(t){return F_(t,r(Py)),Py.result()},e.centroid=function(t){return F_(t,r(By)),By.result()},e.projection=function(n){return arguments.length?(r=null==n?(t=null,Ty):(t=n).stream,e):t},e.context=function(t){return arguments.length?(i=null==t?(n=null,new Pi):new zi(n=t),"function"!=typeof o&&i.pointRadius(o),e):n},e.pointRadius=function(t){return arguments.length?(o="function"==typeof t?t:(i.pointRadius(+t),+t),e):o},e.projection(t).context(n)},Hy=Ov(),Xy=function(t,n){var e=n[0],r=n[1],i=[R_(e),-E_(e),0],o=0,u=0;Hy.reset();for(var a=0,c=t.length;a=0?1:-1,T=M*w,N=T>m_,k=d*x;if(Hy.add(S_(k*M*R_(T),v*b+k*E_(T))),o+=N?w+M*w_:w,N^h>=e^g>=e){var S=Ar(Sr(l),Sr(y));Pr(S);var E=Ar(i,S);Pr(E);var A=(N^w>=0?-1:1)*_r(E[2]);(r>A||r===A&&(S[0]||S[1]))&&(u+=N^w>=0?1:-1)}}return(o<-y_||o0){for(x||(o.polygonStart(),x=!0),o.lineStart(),t=0;t1&&2&i&&u.push(u.pop().concat(u.shift())),d.push(u.filter(qi))}var p,d,v,_=n(o),y=i.invert(r[0],r[1]),g=fy(),m=n(g),x=!1,b={point:u,lineStart:c,lineEnd:s,polygonStart:function(){b.point=f,b.lineStart=l,b.lineEnd=h,d=[],p=[]},polygonEnd:function(){b.point=u,b.lineStart=c,b.lineEnd=s,d=Ks(d);var t=Xy(p,y);d.length?(x||(o.polygonStart(),x=!0),py(d,Li,t,e,o)):t&&(x||(o.polygonStart(),x=!0),o.lineStart(),e(null,null,1,o),o.lineEnd()),x&&(o.polygonEnd(),x=!1),d=p=null},sphere:function(){o.polygonStart(),o.lineStart(),e(null,null,1,o),o.lineEnd(),o.polygonEnd()}};return b}},Wy=Vy(function(){return!0},Ui,Oi,[-m_,-x_]),$y=function(t,n){function e(e,r,i,o){ii(o,t,n,i,e,r)}function r(t,n){return E_(t)*E_(n)>a}function i(t){var n,e,i,a,f;return{lineStart:function(){a=i=!1,f=1},point:function(l,h){var p,d=[l,h],v=r(l,h),_=c?v?0:u(l,h):v?u(l+(l<0?m_:-m_),h):0;if(!n&&(a=i=v)&&t.lineStart(),v!==i&&(p=o(n,d),(hy(n,p)||hy(d,p))&&(d[0]+=y_,d[1]+=y_,v=r(d[0],d[1]))),v!==i)f=0,v?(t.lineStart(),p=o(d,n),t.point(p[0],p[1])):(p=o(n,d),t.point(p[0],p[1]),t.lineEnd()),n=p;else if(s&&n&&c^v){var y;_&e||!(y=o(d,n,!0))||(f=0,c?(t.lineStart(),t.point(y[0][0],y[0][1]),t.point(y[1][0],y[1][1]),t.lineEnd()):(t.point(y[1][0],y[1][1]),t.lineEnd(),t.lineStart(),t.point(y[0][0],y[0][1])))}!v||n&&hy(n,d)||t.point(d[0],d[1]),n=d,i=v,e=_},lineEnd:function(){i&&t.lineEnd(),n=null},clean:function(){return f|(a&&i)<<1}}}function o(t,n,e){var r=Sr(t),i=Sr(n),o=[1,0,0],u=Ar(r,i),c=Er(u,u),s=u[0],f=c-s*s;if(!f)return!e&&t;var l=a*c/f,h=-a*s/f,p=Ar(o,u),d=zr(o,l),v=zr(u,h);Cr(d,v);var _=p,y=Er(d,_),g=Er(_,_),m=y*y-g*(Er(d,d)-1);if(!(m<0)){var x=L_(m),b=zr(_,(-y-x)/g);if(Cr(b,d),b=kr(b),!e)return b;var w,M=t[0],T=n[0],N=t[1],k=n[1];T0^b[1]<(N_(b[0]-M)m_^(M<=b[0]&&b[0]<=T)){var C=zr(_,(-y+x)/g);return Cr(C,d),[b,kr(C)]}}}function u(n,e){var r=c?t:m_-t,i=0;return n<-r?i|=1:n>r&&(i|=2),e<-r?i|=4:e>r&&(i|=8),i}var a=E_(t),c=a>0,s=N_(a)>y_;return Vy(r,i,e,c?[0,-t]:[-m_,t-m_])},Zy=function(t){return{stream:Fi(t)}};Ii.prototype={constructor:Ii,point:function(t,n){this.stream.point(t,n)},sphere:function(){this.stream.sphere()},lineStart:function(){this.stream.lineStart()},lineEnd:function(){this.stream.lineEnd()},polygonStart:function(){this.stream.polygonStart()},polygonEnd:function(){this.stream.polygonEnd()}};var Gy=16,Jy=E_(30*T_),Qy=function(t,n){return+n?Hi(t,n):ji(t)},Ky=Fi({point:function(t,n){this.stream.point(t*T_,n*T_)}}),tg=function(){return Wi(Zi).scale(155.424).center([0,33.6442])},ng=function(){return tg().parallels([29.5,45.5]).scale(1070).translate([480,250]).rotate([96,0]).center([-.6,38.7])},eg=function(){function t(t){var n=t[0],e=t[1];return a=null,i.point(n,e),a||(o.point(n,e),a)||(u.point(n,e),a)}function n(){return e=r=null,t}var e,r,i,o,u,a,c=ng(),s=tg().rotate([154,0]).center([-2,58.5]).parallels([55,65]),f=tg().rotate([157,0]).center([-3,19.9]).parallels([8,18]),l={point:function(t,n){a=[t,n]}};return t.invert=function(t){var n=c.scale(),e=c.translate(),r=(t[0]-e[0])/n,i=(t[1]-e[1])/n;return(i>=.12&&i<.234&&r>=-.425&&r<-.214?s:i>=.166&&i<.234&&r>=-.214&&r<-.115?f:c).invert(t)},t.stream=function(t){return e&&r===t?e:e=Gi([c.stream(r=t),s.stream(t),f.stream(t)])},t.precision=function(t){return arguments.length?(c.precision(t),s.precision(t),f.precision(t),n()):c.precision()},t.scale=function(n){return arguments.length?(c.scale(n),s.scale(.35*n),f.scale(n),t.translate(c.translate())):c.scale()},t.translate=function(t){if(!arguments.length)return c.translate();var e=c.scale(),r=+t[0],a=+t[1];return i=c.translate(t).clipExtent([[r-.455*e,a-.238*e],[r+.455*e,a+.238*e]]).stream(l),o=s.translate([r-.307*e,a+.201*e]).clipExtent([[r-.425*e+y_,a+.12*e+y_],[r-.214*e-y_,a+.234*e-y_]]).stream(l),u=f.translate([r-.205*e,a+.212*e]).clipExtent([[r-.214*e+y_,a+.166*e+y_],[r-.115*e-y_,a+.234*e-y_]]).stream(l),n()},t.fitExtent=function(n,e){return Yi(t,n,e)},t.fitSize=function(n,e){return Bi(t,n,e)},t.scale(1070)},rg=Ji(function(t){return L_(2/(1+t))});rg.invert=Qi(function(t){return 2*_r(t/2)});var ig=function(){return Xi(rg).scale(124.75).clipAngle(179.999)},og=Ji(function(t){return(t=vr(t))&&t/R_(t)});og.invert=Qi(function(t){return t});var ug=function(){return Xi(og).scale(79.4188).clipAngle(179.999)};Ki.invert=function(t,n){return[t,2*k_(C_(n))-x_]};var ag=function(){return to(Ki).scale(961/w_)},cg=function(){return Wi(eo).scale(109.5).parallels([30,30])};ro.invert=ro;var sg=function(){return Xi(ro).scale(152.63)},fg=function(){return Wi(io).scale(131.154).center([0,13.9389])};oo.invert=Qi(k_);var lg=function(){return Xi(oo).scale(144.049).clipAngle(60)},hg=function(){function t(){return i=o=null,u}var n,e,r,i,o,u,a=1,c=0,s=0,f=1,l=1,h=Ty,p=null,d=Ty;return u={stream:function(t){return i&&o===t?i:i=h(d(o=t))},clipExtent:function(i){return arguments.length?(d=null==i?(p=n=e=r=null,Ty):ci(p=+i[0][0],n=+i[0][1],e=+i[1][0],r=+i[1][1]),t()):null==p?null:[[p,n],[e,r]]},scale:function(n){return arguments.length?(h=uo((a=+n)*f,a*l,c,s),t()):a},translate:function(n){return arguments.length?(h=uo(a*f,a*l,c=+n[0],s=+n[1]),t()):[c,s]},reflectX:function(n){return arguments.length?(h=uo(a*(f=n?-1:1),a*l,c,s),t()):f<0},reflectY:function(n){return arguments.length?(h=uo(a*f,a*(l=n?-1:1),c,s),t()):l<0},fitExtent:function(t,n){return Yi(u,t,n)},fitSize:function(t,n){return Bi(u,t,n)}}};ao.invert=Qi(_r);var pg=function(){return Xi(ao).scale(249.5).clipAngle(90+y_)};co.invert=Qi(function(t){return 2*k_(t)});var dg=function(){return Xi(co).scale(250).clipAngle(142)};so.invert=function(t,n){return[-n,2*k_(C_(t))-x_]};var vg=function(){var t=to(so),n=t.center,e=t.rotate;return t.center=function(t){return arguments.length?n([-t[1],t[0]]):(t=n(),[t[1],-t[0]])},t.rotate=function(t){return arguments.length?e([t[0],t[1],t.length>2?t[2]+90:90]):(t=e(),[t[0],t[1],t[2]-90])},e([0,0,90]).scale(159.155)},_g=function(){function t(t){var o,u=0;t.eachAfter(function(t){var e=t.children;e?(t.x=lo(e),t.y=po(e)):(t.x=o?u+=n(t,o):0,t.y=0,o=t)});var a=_o(t),c=yo(t),s=a.x-n(a,c)/2,f=c.x+n(c,a)/2;return t.eachAfter(i?function(n){n.x=(n.x-t.x)*e,n.y=(t.y-n.y)*r}:function(n){n.x=(n.x-s)/(f-s)*e,n.y=(1-(t.y?n.y/t.y:1))*r})}var n=fo,e=1,r=1,i=!1;return t.separation=function(e){return arguments.length?(n=e,t):n},t.size=function(n){return arguments.length?(i=!1,e=+n[0],r=+n[1],t):i?null:[e,r]},t.nodeSize=function(n){return arguments.length?(i=!0,e=+n[0],r=+n[1],t):i?[e,r]:null},t},yg=function(){return this.eachAfter(go)},gg=function(t){var n,e,r,i,o=this,u=[o];do for(n=u.reverse(),u=[];o=n.pop();)if(t(o),e=o.children)for(r=0,i=e.length;r=0;--e)i.push(n[e]);return this},xg=function(t){for(var n,e,r,i=this,o=[i],u=[];i=o.pop();)if(u.push(i),n=i.children)for(e=0,r=n.length;e=0;)e+=r[i].value;n.value=e})},wg=function(t){return this.eachBefore(function(n){n.children&&n.children.sort(t)})},Mg=function(t){for(var n=this,e=mo(n,t),r=[n];n!==e;)n=n.parent,r.push(n);for(var i=r.length;t!==e;)r.splice(i,0,t),t=t.parent;return r},Tg=function(){for(var t=this,n=[t];t=t.parent;)n.push(t);return n},Ng=function(){var t=[];return this.each(function(n){t.push(n)}),t},kg=function(){var t=[];return this.eachBefore(function(n){n.children||t.push(n)}),t},Sg=function(){var t=this,n=[];return t.each(function(e){e!==t&&n.push({source:e.parent,target:e})}),n};No.prototype=xo.prototype={constructor:No,count:yg,each:gg,eachAfter:xg,eachBefore:mg,sum:bg,sort:wg,path:Mg,ancestors:Tg,descendants:Ng,leaves:kg,links:Sg,copy:bo};var Eg=function(t){for(var n,e=(t=t.slice()).length,r=null,i=r;e;){var o=new ko(t[e-1]);i=i?i.next=o:r=o,t[n]=t[--e]}return{head:r,tail:i}},Ag=function(t){return Eo(Eg(t),[])},Cg=function(t){return Do(t),t},zg=function(t){return function(){return t}},Pg=function(){function t(t){return t.x=e/2,t.y=r/2,n?t.eachBefore(Bo(n)).eachAfter(jo(i,.5)).eachBefore(Ho(1)):t.eachBefore(Bo(Yo)).eachAfter(jo(Io,1)).eachAfter(jo(i,t.r/Math.min(e,r))).eachBefore(Ho(Math.min(e,r)/(2*t.r))),t}var n=null,e=1,r=1,i=Io;return t.radius=function(e){return arguments.length?(n=Oo(e),t):n},t.size=function(n){return arguments.length?(e=+n[0],r=+n[1],t):[e,r]},t.padding=function(n){return arguments.length?(i="function"==typeof n?n:zg(+n),t):i},t},Rg=function(t){t.x0=Math.round(t.x0),t.y0=Math.round(t.y0),t.x1=Math.round(t.x1),t.y1=Math.round(t.y1)},qg=function(t,n,e,r,i){for(var o,u=t.children,a=-1,c=u.length,s=t.value&&(r-n)/t.value;++a0)throw new Error("cycle");return o}var n=Xo,e=Vo;return t.id=function(e){return arguments.length?(n=Fo(e),t):n},t.parentId=function(n){return arguments.length?(e=Fo(n),t):e},t};Ko.prototype=Object.create(No.prototype);var Ig=function(){function t(t){var r=tu(t);if(r.eachAfter(n),r.parent.m=-r.z,r.eachBefore(e),c)t.eachBefore(i);else{var s=t,f=t,l=t;t.eachBefore(function(t){t.xf.x&&(f=t),t.depth>l.depth&&(l=t)});var h=s===f?1:o(s,f)/2,p=h-s.x,d=u/(f.x+h+p),v=a/(l.depth||1);t.eachBefore(function(t){t.x=(t.x+p)*d,t.y=t.depth*v})}return t}function n(t){var n=t.children,e=t.parent.children,i=t.i?e[t.i-1]:null;if(n){Jo(t);var u=(n[0].z+n[n.length-1].z)/2;i?(t.z=i.z+o(t._,i._),t.m=t.z-u):t.z=u}else i&&(t.z=i.z+o(t._,i._));t.parent.A=r(t,i,t.parent.A||e[0])}function e(t){t._.x=t.z+t.parent.m,t.m+=t.parent.m}function r(t,n,e){if(n){for(var r,i=t,u=t,a=n,c=i.parent.children[0],s=i.m,f=u.m,l=a.m,h=c.m;a=Zo(a),i=$o(i),a&&i;)c=$o(c),u=Zo(u),u.a=t,r=a.z+l-i.z-s+o(a._,i._),r>0&&(Go(Qo(a,t,e),t,r),s+=r,f+=r),l+=a.m,s+=i.m,h+=c.m,f+=u.m;a&&!Zo(u)&&(u.t=a,u.m+=l-f),i&&!$o(c)&&(c.t=i,c.m+=s-h,e=t)}return e}function i(t){t.x*=u,t.y=t.depth*a}var o=Wo,u=1,a=1,c=null;return t.separation=function(n){return arguments.length?(o=n,t):o},t.size=function(n){return arguments.length?(c=!1,u=+n[0],a=+n[1],t):c?null:[u,a]},t.nodeSize=function(n){return arguments.length?(c=!0,u=+n[0],a=+n[1],t):c?[u,a]:null},t},Yg=function(t,n,e,r,i){for(var o,u=t.children,a=-1,c=u.length,s=t.value&&(i-e)/t.value;++a1?n:1)},e}(Bg),Hg=function(){function t(t){return t.x0=t.y0=0,t.x1=i,t.y1=o,t.eachBefore(n),u=[0],r&&t.eachBefore(Rg),t}function n(t){var n=u[t.depth],r=t.x0+n,i=t.y0+n,o=t.x1-n,h=t.y1-n;o=n-1){var s=c[t];return s.x0=r,s.y0=i,s.x1=u,s.y1=a,void 0}for(var l=f[t],h=e/2+l,p=t+1,d=n-1;p>>1;f[v]u-r){var g=(i*y+a*_)/e;o(t,p,_,r,i,u,g),o(p,n,y,r,g,u,a)}else{var m=(r*y+u*_)/e;o(t,p,_,r,i,m,a),o(p,n,y,m,i,u,a)}}var u,a,c=t.children,s=c.length,f=new Array(s+1);for(f[0]=a=u=0;u1?n:1)},e}(Bg),$g=function(t){for(var n,e=-1,r=t.length,i=t[r-1],o=0;++e=0;--n)s.push(t[r[o[n]][2]]);for(n=+a;na!=s>a&&u<(c-e)*(a-r)/(s-r)+e&&(f=!f),c=e,s=r;return f},Kg=function(t){for(var n,e,r=-1,i=t.length,o=t[i-1],u=o[0],a=o[1],c=0;++r1);return t+n*i*Math.sqrt(-2*Math.log(r)/r)}},im=function(){var t=rm.apply(this,arguments);return function(){return Math.exp(t())}},om=function(t){ +return function(){for(var n=0,e=0;e=200&&e<300||304===e){if(o)try{n=o.call(r,s)}catch(t){return void a.call("error",r,t)}else n=s;a.call("load",r,n)}else a.call("error",r,t)}var r,i,o,u,a=p("beforesend","progress","load","error"),c=Ie(),s=new XMLHttpRequest,f=null,l=null,h=0;if("undefined"==typeof XDomainRequest||"withCredentials"in s||!/^(http(s)?:)?\/\//.test(t)||(s=new XDomainRequest),"onload"in s?s.onload=s.onerror=s.ontimeout=e:s.onreadystatechange=function(t){s.readyState>3&&e(t)},s.onprogress=function(t){a.call("progress",r,t)},r={header:function(t,n){return t=(t+"").toLowerCase(),arguments.length<2?c.get(t):(null==n?c.remove(t):c.set(t,n+""),r)},mimeType:function(t){return arguments.length?(i=null==t?null:t+"",r):i},responseType:function(t){return arguments.length?(u=t,r):u},timeout:function(t){return arguments.length?(h=+t,r):h},user:function(t){return arguments.length<1?f:(f=null==t?null:t+"",r)},password:function(t){return arguments.length<1?l:(l=null==t?null:t+"",r)},response:function(t){return o=t,r},get:function(t,n){return r.send("GET",t,n)},post:function(t,n){return r.send("POST",t,n)},send:function(n,e,o){return s.open(n,t,!0,f,l),null==i||c.has("accept")||c.set("accept",i+",*/*"),s.setRequestHeader&&c.each(function(t,n){s.setRequestHeader(n,t)}),null!=i&&s.overrideMimeType&&s.overrideMimeType(i),null!=u&&(s.responseType=u),h>0&&(s.timeout=h),null==o&&"function"==typeof e&&(o=e,e=null),null!=o&&1===o.length&&(o=lu(o)),null!=o&&r.on("error",o).on("load",function(t){o(null,t)}),a.call("beforesend",r,s),s.send(null==e?null:e),r},abort:function(){return s.abort(),r},on:function(){var t=a.on.apply(a,arguments);return t===a?r:t}},null!=n){if("function"!=typeof n)throw new Error("invalid callback: "+n);return r.get(n)}return r},sm=function(t,n){return function(e,r){var i=cm(e).mimeType(t).response(n);if(null!=r){if("function"!=typeof r)throw new Error("invalid callback: "+r);return i.get(r)}return i}},fm=sm("text/html",function(t){return document.createRange().createContextualFragment(t.responseText)}),lm=sm("application/json",function(t){return JSON.parse(t.responseText)}),hm=sm("text/plain",function(t){return t.responseText}),pm=sm("application/xml",function(t){var n=t.responseXML;if(!n)throw new Error("parse error");return n}),dm=function(t,n){return function(e,r,i){arguments.length<3&&(i=r,r=null);var o=cm(e).mimeType(t);return o.row=function(t){return arguments.length?o.response(pu(n,r=t)):r},o.row(r),i?o.get(i):o}},vm=dm("text/csv",Bd),_m=dm("text/tab-separated-values",Wd),ym=Array.prototype,gm=ym.map,mm=ym.slice,xm={name:"implicit"},bm=function(t){return function(){return t}},wm=function(t){return+t},Mm=[0,1],Tm=function(n,r,i){var o,u=n[0],a=n[n.length-1],c=e(u,a,null==r?10:r);switch(i=zv(null==i?",f":i),i.type){case"s":var s=Math.max(Math.abs(u),Math.abs(a));return null!=i.precision||isNaN(o=Uv(c,s))||(i.precision=o),t.formatPrefix(i,s);case"":case"e":case"g":case"p":case"r":null!=i.precision||isNaN(o=Dv(c,Math.max(Math.abs(u),Math.abs(a))))||(i.precision=o-("e"===i.type));break;case"f":case"%":null!=i.precision||isNaN(o=Lv(c))||(i.precision=o-2*("%"===i.type))}return t.format(i)},Nm=function(t,n){t=t.slice();var e,r=0,i=t.length-1,o=t[r],u=t[i];return u0?t>1?Yu(function(n){n.setTime(Math.floor(n/t)*t)},function(n,e){n.setTime(+n+e*t)},function(n,e){return(e-n)/t}):Em:null};var Am=Em.range,Cm=1e3,zm=6e4,Pm=36e5,Rm=864e5,qm=6048e5,Lm=Yu(function(t){t.setTime(Math.floor(t/Cm)*Cm)},function(t,n){t.setTime(+t+n*Cm)},function(t,n){return(n-t)/Cm},function(t){return t.getUTCSeconds()}),Um=Lm.range,Dm=Yu(function(t){t.setTime(Math.floor(t/zm)*zm)},function(t,n){t.setTime(+t+n*zm)},function(t,n){return(n-t)/zm},function(t){return t.getMinutes()}),Om=Dm.range,Fm=Yu(function(t){var n=t.getTimezoneOffset()*zm%Pm;n<0&&(n+=Pm),t.setTime(Math.floor((+t-n)/Pm)*Pm+n)},function(t,n){t.setTime(+t+n*Pm)},function(t,n){return(n-t)/Pm},function(t){return t.getHours()}),Im=Fm.range,Ym=Yu(function(t){t.setHours(0,0,0,0)},function(t,n){t.setDate(t.getDate()+n)},function(t,n){return(n-t-(n.getTimezoneOffset()-t.getTimezoneOffset())*zm)/Rm},function(t){return t.getDate()-1}),Bm=Ym.range,jm=Bu(0),Hm=Bu(1),Xm=Bu(2),Vm=Bu(3),Wm=Bu(4),$m=Bu(5),Zm=Bu(6),Gm=jm.range,Jm=Hm.range,Qm=Xm.range,Km=Vm.range,tx=Wm.range,nx=$m.range,ex=Zm.range,rx=Yu(function(t){t.setDate(1),t.setHours(0,0,0,0)},function(t,n){t.setMonth(t.getMonth()+n)},function(t,n){return n.getMonth()-t.getMonth()+12*(n.getFullYear()-t.getFullYear())},function(t){return t.getMonth()}),ix=rx.range,ox=Yu(function(t){t.setMonth(0,1),t.setHours(0,0,0,0)},function(t,n){t.setFullYear(t.getFullYear()+n)},function(t,n){return n.getFullYear()-t.getFullYear()},function(t){return t.getFullYear()});ox.every=function(t){return isFinite(t=Math.floor(t))&&t>0?Yu(function(n){n.setFullYear(Math.floor(n.getFullYear()/t)*t),n.setMonth(0,1),n.setHours(0,0,0,0)},function(n,e){n.setFullYear(n.getFullYear()+e*t)}):null};var ux=ox.range,ax=Yu(function(t){t.setUTCSeconds(0,0)},function(t,n){t.setTime(+t+n*zm)},function(t,n){return(n-t)/zm},function(t){return t.getUTCMinutes()}),cx=ax.range,sx=Yu(function(t){t.setUTCMinutes(0,0,0)},function(t,n){t.setTime(+t+n*Pm)},function(t,n){return(n-t)/Pm},function(t){return t.getUTCHours()}),fx=sx.range,lx=Yu(function(t){t.setUTCHours(0,0,0,0)},function(t,n){t.setUTCDate(t.getUTCDate()+n)},function(t,n){return(n-t)/Rm},function(t){return t.getUTCDate()-1}),hx=lx.range,px=ju(0),dx=ju(1),vx=ju(2),_x=ju(3),yx=ju(4),gx=ju(5),mx=ju(6),xx=px.range,bx=dx.range,wx=vx.range,Mx=_x.range,Tx=yx.range,Nx=gx.range,kx=mx.range,Sx=Yu(function(t){t.setUTCDate(1),t.setUTCHours(0,0,0,0)},function(t,n){t.setUTCMonth(t.getUTCMonth()+n)},function(t,n){return n.getUTCMonth()-t.getUTCMonth()+12*(n.getUTCFullYear()-t.getUTCFullYear())},function(t){return t.getUTCMonth()}),Ex=Sx.range,Ax=Yu(function(t){t.setUTCMonth(0,1),t.setUTCHours(0,0,0,0)},function(t,n){t.setUTCFullYear(t.getUTCFullYear()+n)},function(t,n){return n.getUTCFullYear()-t.getUTCFullYear()},function(t){return t.getUTCFullYear()});Ax.every=function(t){return isFinite(t=Math.floor(t))&&t>0?Yu(function(n){n.setUTCFullYear(Math.floor(n.getUTCFullYear()/t)*t),n.setUTCMonth(0,1),n.setUTCHours(0,0,0,0)},function(n,e){n.setUTCFullYear(n.getUTCFullYear()+e*t)}):null};var Cx,zx=Ax.range,Px={"-":"",_:" ",0:"0"},Rx=/^\s*\d+/,qx=/^%/,Lx=/[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g;Ya({dateTime:"%x, %X",date:"%-m/%-d/%Y",time:"%-I:%M:%S %p",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]});var Ux="%Y-%m-%dT%H:%M:%S.%LZ",Dx=Date.prototype.toISOString?Ba:t.utcFormat(Ux),Ox=+new Date("2000-01-01T00:00:00.000Z")?ja:t.utcParse(Ux),Fx=1e3,Ix=60*Fx,Yx=60*Ix,Bx=24*Yx,jx=7*Bx,Hx=30*Bx,Xx=365*Bx,Vx=function(){return Va(ox,rx,jm,Ym,Fm,Dm,Lm,Em,t.timeFormat).domain([new Date(2e3,0,1),new Date(2e3,0,2)])},Wx=function(){return Va(Ax,Sx,px,lx,sx,ax,Lm,Em,t.utcFormat).domain([Date.UTC(2e3,0,1),Date.UTC(2e3,0,2)])},$x=function(t){return t.match(/.{6}/g).map(function(t){return"#"+t})},Zx=$x("1f77b4ff7f0e2ca02cd627289467bd8c564be377c27f7f7fbcbd2217becf"),Gx=$x("393b795254a36b6ecf9c9ede6379398ca252b5cf6bcedb9c8c6d31bd9e39e7ba52e7cb94843c39ad494ad6616be7969c7b4173a55194ce6dbdde9ed6"),Jx=$x("3182bd6baed69ecae1c6dbefe6550dfd8d3cfdae6bfdd0a231a35474c476a1d99bc7e9c0756bb19e9ac8bcbddcdadaeb636363969696bdbdbdd9d9d9"),Qx=$x("1f77b4aec7e8ff7f0effbb782ca02c98df8ad62728ff98969467bdc5b0d58c564bc49c94e377c2f7b6d27f7f7fc7c7c7bcbd22dbdb8d17becf9edae5"),Kx=Uh(Xt(300,.5,0),Xt(-240,.5,1)),tb=Uh(Xt(-100,.75,.35),Xt(80,1.5,.8)),nb=Uh(Xt(260,.75,.35),Xt(80,1.5,.8)),eb=Xt(),rb=function(t){(t<0||t>1)&&(t-=Math.floor(t));var n=Math.abs(t-.5);return eb.h=360*t-100,eb.s=1.5-1.5*n,eb.l=.8-.9*n,eb+""},ib=Wa($x("44015444025645045745055946075a46085c460a5d460b5e470d60470e6147106347116447136548146748166848176948186a481a6c481b6d481c6e481d6f481f70482071482173482374482475482576482677482878482979472a7a472c7a472d7b472e7c472f7d46307e46327e46337f463480453581453781453882443983443a83443b84433d84433e85423f854240864241864142874144874045884046883f47883f48893e49893e4a893e4c8a3d4d8a3d4e8a3c4f8a3c508b3b518b3b528b3a538b3a548c39558c39568c38588c38598c375a8c375b8d365c8d365d8d355e8d355f8d34608d34618d33628d33638d32648e32658e31668e31678e31688e30698e306a8e2f6b8e2f6c8e2e6d8e2e6e8e2e6f8e2d708e2d718e2c718e2c728e2c738e2b748e2b758e2a768e2a778e2a788e29798e297a8e297b8e287c8e287d8e277e8e277f8e27808e26818e26828e26828e25838e25848e25858e24868e24878e23888e23898e238a8d228b8d228c8d228d8d218e8d218f8d21908d21918c20928c20928c20938c1f948c1f958b1f968b1f978b1f988b1f998a1f9a8a1e9b8a1e9c891e9d891f9e891f9f881fa0881fa1881fa1871fa28720a38620a48621a58521a68522a78522a88423a98324aa8325ab8225ac8226ad8127ad8128ae8029af7f2ab07f2cb17e2db27d2eb37c2fb47c31b57b32b67a34b67935b77937b87838b9773aba763bbb753dbc743fbc7340bd7242be7144bf7046c06f48c16e4ac16d4cc26c4ec36b50c46a52c56954c56856c66758c7655ac8645cc8635ec96260ca6063cb5f65cb5e67cc5c69cd5b6ccd5a6ece5870cf5773d05675d05477d1537ad1517cd2507fd34e81d34d84d44b86d54989d5488bd6468ed64590d74393d74195d84098d83e9bd93c9dd93ba0da39a2da37a5db36a8db34aadc32addc30b0dd2fb2dd2db5de2bb8de29bade28bddf26c0df25c2df23c5e021c8e020cae11fcde11dd0e11cd2e21bd5e21ad8e219dae319dde318dfe318e2e418e5e419e7e419eae51aece51befe51cf1e51df4e61ef6e620f8e621fbe723fde725")),ob=Wa($x("00000401000501010601010802010902020b02020d03030f03031204041405041606051806051a07061c08071e0907200a08220b09240c09260d0a290e0b2b100b2d110c2f120d31130d34140e36150e38160f3b180f3d19103f1a10421c10441d11471e114920114b21114e22115024125325125527125829115a2a115c2c115f2d11612f116331116533106734106936106b38106c390f6e3b0f703d0f713f0f72400f74420f75440f764510774710784910784a10794c117a4e117b4f127b51127c52137c54137d56147d57157e59157e5a167e5c167f5d177f5f187f601880621980641a80651a80671b80681c816a1c816b1d816d1d816e1e81701f81721f817320817521817621817822817922827b23827c23827e24828025828125818326818426818627818827818928818b29818c29818e2a81902a81912b81932b80942c80962c80982d80992d809b2e7f9c2e7f9e2f7fa02f7fa1307ea3307ea5317ea6317da8327daa337dab337cad347cae347bb0357bb2357bb3367ab5367ab73779b83779ba3878bc3978bd3977bf3a77c03a76c23b75c43c75c53c74c73d73c83e73ca3e72cc3f71cd4071cf4070d0416fd2426fd3436ed5446dd6456cd8456cd9466bdb476adc4869de4968df4a68e04c67e24d66e34e65e44f64e55064e75263e85362e95462ea5661eb5760ec5860ed5a5fee5b5eef5d5ef05f5ef1605df2625df2645cf3655cf4675cf4695cf56b5cf66c5cf66e5cf7705cf7725cf8745cf8765cf9785df9795df97b5dfa7d5efa7f5efa815ffb835ffb8560fb8761fc8961fc8a62fc8c63fc8e64fc9065fd9266fd9467fd9668fd9869fd9a6afd9b6bfe9d6cfe9f6dfea16efea36ffea571fea772fea973feaa74feac76feae77feb078feb27afeb47bfeb67cfeb77efeb97ffebb81febd82febf84fec185fec287fec488fec68afec88cfeca8dfecc8ffecd90fecf92fed194fed395fed597fed799fed89afdda9cfddc9efddea0fde0a1fde2a3fde3a5fde5a7fde7a9fde9aafdebacfcecaefceeb0fcf0b2fcf2b4fcf4b6fcf6b8fcf7b9fcf9bbfcfbbdfcfdbf")),ub=Wa($x("00000401000501010601010802010a02020c02020e03021004031204031405041706041907051b08051d09061f0a07220b07240c08260d08290e092b10092d110a30120a32140b34150b37160b39180c3c190c3e1b0c411c0c431e0c451f0c48210c4a230c4c240c4f260c51280b53290b552b0b572d0b592f0a5b310a5c320a5e340a5f3609613809623909633b09643d09653e0966400a67420a68440a68450a69470b6a490b6a4a0c6b4c0c6b4d0d6c4f0d6c510e6c520e6d540f6d550f6d57106e59106e5a116e5c126e5d126e5f136e61136e62146e64156e65156e67166e69166e6a176e6c186e6d186e6f196e71196e721a6e741a6e751b6e771c6d781c6d7a1d6d7c1d6d7d1e6d7f1e6c801f6c82206c84206b85216b87216b88226a8a226a8c23698d23698f24699025689225689326679526679727669827669a28659b29649d29649f2a63a02a63a22b62a32c61a52c60a62d60a82e5fa92e5eab2f5ead305dae305cb0315bb1325ab3325ab43359b63458b73557b93556ba3655bc3754bd3853bf3952c03a51c13a50c33b4fc43c4ec63d4dc73e4cc83f4bca404acb4149cc4248ce4347cf4446d04545d24644d34743d44842d54a41d74b3fd84c3ed94d3dda4e3cdb503bdd513ade5238df5337e05536e15635e25734e35933e45a31e55c30e65d2fe75e2ee8602de9612bea632aeb6429eb6628ec6726ed6925ee6a24ef6c23ef6e21f06f20f1711ff1731df2741cf3761bf37819f47918f57b17f57d15f67e14f68013f78212f78410f8850ff8870ef8890cf98b0bf98c0af98e09fa9008fa9207fa9407fb9606fb9706fb9906fb9b06fb9d07fc9f07fca108fca309fca50afca60cfca80dfcaa0ffcac11fcae12fcb014fcb216fcb418fbb61afbb81dfbba1ffbbc21fbbe23fac026fac228fac42afac62df9c72ff9c932f9cb35f8cd37f8cf3af7d13df7d340f6d543f6d746f5d949f5db4cf4dd4ff4df53f4e156f3e35af3e55df2e661f2e865f2ea69f1ec6df1ed71f1ef75f1f179f2f27df2f482f3f586f3f68af4f88ef5f992f6fa96f8fb9af9fc9dfafda1fcffa4")),ab=Wa($x("0d088710078813078916078a19068c1b068d1d068e20068f2206902406912605912805922a05932c05942e05952f059631059733059735049837049938049a3a049a3c049b3e049c3f049c41049d43039e44039e46039f48039f4903a04b03a14c02a14e02a25002a25102a35302a35502a45601a45801a45901a55b01a55c01a65e01a66001a66100a76300a76400a76600a76700a86900a86a00a86c00a86e00a86f00a87100a87201a87401a87501a87701a87801a87a02a87b02a87d03a87e03a88004a88104a78305a78405a78606a68707a68808a68a09a58b0aa58d0ba58e0ca48f0da4910ea3920fa39410a29511a19613a19814a099159f9a169f9c179e9d189d9e199da01a9ca11b9ba21d9aa31e9aa51f99a62098a72197a82296aa2395ab2494ac2694ad2793ae2892b02991b12a90b22b8fb32c8eb42e8db52f8cb6308bb7318ab83289ba3388bb3488bc3587bd3786be3885bf3984c03a83c13b82c23c81c33d80c43e7fc5407ec6417dc7427cc8437bc9447aca457acb4679cc4778cc4977cd4a76ce4b75cf4c74d04d73d14e72d24f71d35171d45270d5536fd5546ed6556dd7566cd8576bd9586ada5a6ada5b69db5c68dc5d67dd5e66de5f65de6164df6263e06363e16462e26561e26660e3685fe4695ee56a5de56b5de66c5ce76e5be76f5ae87059e97158e97257ea7457eb7556eb7655ec7754ed7953ed7a52ee7b51ef7c51ef7e50f07f4ff0804ef1814df1834cf2844bf3854bf3874af48849f48948f58b47f58c46f68d45f68f44f79044f79143f79342f89441f89540f9973ff9983ef99a3efa9b3dfa9c3cfa9e3bfb9f3afba139fba238fca338fca537fca636fca835fca934fdab33fdac33fdae32fdaf31fdb130fdb22ffdb42ffdb52efeb72dfeb82cfeba2cfebb2bfebd2afebe2afec029fdc229fdc328fdc527fdc627fdc827fdca26fdcb26fccd25fcce25fcd025fcd225fbd324fbd524fbd724fad824fada24f9dc24f9dd25f8df25f8e125f7e225f7e425f6e626f6e826f5e926f5eb27f4ed27f3ee27f3f027f2f227f1f426f1f525f0f724f0f921")),cb=function(t){return function(){return t}},sb=1e-12,fb=Math.PI,lb=fb/2,hb=2*fb,pb=function(){function t(){var t,s,f=+n.apply(this,arguments),l=+e.apply(this,arguments),h=o.apply(this,arguments)-lb,p=u.apply(this,arguments)-lb,d=Math.abs(p-h),v=p>h;if(c||(c=t=Re()),lsb)if(d>hb-sb)c.moveTo(l*Math.cos(h),l*Math.sin(h)),c.arc(0,0,l,h,p,!v),f>sb&&(c.moveTo(f*Math.cos(p),f*Math.sin(p)),c.arc(0,0,f,p,h,v));else{var _,y,g=h,m=p,x=h,b=p,w=d,M=d,T=a.apply(this,arguments)/2,N=T>sb&&(i?+i.apply(this,arguments):Math.sqrt(f*f+l*l)),k=Math.min(Math.abs(l-f)/2,+r.apply(this,arguments)),S=k,E=k;if(N>sb){var A=tc(N/f*Math.sin(T)),C=tc(N/l*Math.sin(T));(w-=2*A)>sb?(A*=v?1:-1,x+=A,b-=A):(w=0,x=b=(h+p)/2),(M-=2*C)>sb?(C*=v?1:-1,g+=C,m-=C):(M=0,g=m=(h+p)/2)}var z=l*Math.cos(g),P=l*Math.sin(g),R=f*Math.cos(b),q=f*Math.sin(b);if(k>sb){var L=l*Math.cos(m),U=l*Math.sin(m),D=f*Math.cos(x),O=f*Math.sin(x);if(dsb?nc(z,P,D,O,L,U,R,q):[R,q],I=z-F[0],Y=P-F[1],B=L-F[0],j=U-F[1],H=1/Math.sin(Math.acos((I*B+Y*j)/(Math.sqrt(I*I+Y*Y)*Math.sqrt(B*B+j*j)))/2),X=Math.sqrt(F[0]*F[0]+F[1]*F[1]);S=Math.min(k,(f-X)/(H-1)),E=Math.min(k,(l-X)/(H+1))}}M>sb?E>sb?(_=ec(D,O,z,P,l,E,v),y=ec(L,U,R,q,l,E,v),c.moveTo(_.cx+_.x01,_.cy+_.y01),Esb&&w>sb?S>sb?(_=ec(R,q,L,U,f,-S,v),y=ec(z,P,D,O,f,-S,v),c.lineTo(_.cx+_.x01,_.cy+_.y01),S=f;--l)s.point(_[l],y[l]);s.lineEnd(),s.areaEnd()}v&&(_[n]=+e(h,n,t),y[n]=+i(h,n,t),s.point(r?+r(h,n,t):_[n],o?+o(h,n,t):y[n]))}if(p)return s=null,p+""||null}function n(){return vb().defined(u).curve(c).context(a)}var e=ic,r=null,i=cb(0),o=oc,u=cb(!0),a=null,c=db,s=null;return t.x=function(n){return arguments.length?(e="function"==typeof n?n:cb(+n),r=null,t):e},t.x0=function(n){return arguments.length?(e="function"==typeof n?n:cb(+n),t):e},t.x1=function(n){return arguments.length?(r=null==n?null:"function"==typeof n?n:cb(+n),t):r},t.y=function(n){return arguments.length?(i="function"==typeof n?n:cb(+n),o=null,t):i},t.y0=function(n){return arguments.length?(i="function"==typeof n?n:cb(+n),t):i},t.y1=function(n){return arguments.length?(o=null==n?null:"function"==typeof n?n:cb(+n),t):o},t.lineX0=t.lineY0=function(){return n().x(e).y(i)},t.lineY1=function(){return n().x(e).y(o)},t.lineX1=function(){return n().x(r).y(i)},t.defined=function(n){return arguments.length?(u="function"==typeof n?n:cb(!!n),t):u},t.curve=function(n){return arguments.length?(c=n,null!=a&&(s=c(a)),t):c},t.context=function(n){return arguments.length?(null==n?a=s=null:s=c(a=n),t):a},t},yb=function(t,n){return nt?1:n>=t?0:NaN},gb=function(t){return t},mb=function(){function t(t){var a,c,s,f,l,h=t.length,p=0,d=new Array(h),v=new Array(h),_=+i.apply(this,arguments),y=Math.min(hb,Math.max(-hb,o.apply(this,arguments)-_)),g=Math.min(Math.abs(y)/h,u.apply(this,arguments)),m=g*(y<0?-1:1);for(a=0;a0&&(p+=l);for(null!=e?d.sort(function(t,n){return e(v[t],v[n])}):null!=r&&d.sort(function(n,e){return r(t[n],t[e])}),a=0,s=p?(y-h*m)/p:0;a0?l*s:0)+m,v[c]={data:t[c],index:a,value:l,startAngle:_,endAngle:f,padAngle:g};return v}var n=gb,e=yb,r=null,i=cb(0),o=cb(hb),u=cb(0);return t.value=function(e){return arguments.length?(n="function"==typeof e?e:cb(+e),t):n},t.sortValues=function(n){return arguments.length?(e=n,r=null,t):e},t.sort=function(n){return arguments.length?(r=n,e=null,t):r},t.startAngle=function(n){return arguments.length?(i="function"==typeof n?n:cb(+n),t):i},t.endAngle=function(n){return arguments.length?(o="function"==typeof n?n:cb(+n),t):o},t.padAngle=function(n){return arguments.length?(u="function"==typeof n?n:cb(+n),t):u},t},xb=ac(db);uc.prototype={areaStart:function(){this._curve.areaStart()},areaEnd:function(){this._curve.areaEnd()},lineStart:function(){this._curve.lineStart()},lineEnd:function(){this._curve.lineEnd()},point:function(t,n){this._curve.point(n*Math.sin(t),n*-Math.cos(t))}};var bb=function(){return cc(vb().curve(xb))},wb=function(){var t=_b().curve(xb),n=t.curve,e=t.lineX0,r=t.lineX1,i=t.lineY0,o=t.lineY1;return t.angle=t.x,delete t.x,t.startAngle=t.x0,delete t.x0,t.endAngle=t.x1,delete t.x1,t.radius=t.y,delete t.y,t.innerRadius=t.y0,delete t.y0,t.outerRadius=t.y1,delete t.y1,t.lineStartAngle=function(){return cc(e())},delete t.lineX0,t.lineEndAngle=function(){return cc(r())},delete t.lineX1,t.lineInnerRadius=function(){return cc(i())},delete t.lineY0,t.lineOuterRadius=function(){return cc(o())},delete t.lineY1,t.curve=function(t){return arguments.length?n(ac(t)):n()._curve},t},Mb={draw:function(t,n){var e=Math.sqrt(n/fb);t.moveTo(e,0),t.arc(0,0,e,0,hb)}},Tb={draw:function(t,n){var e=Math.sqrt(n/5)/2;t.moveTo(-3*e,-e),t.lineTo(-e,-e),t.lineTo(-e,-3*e),t.lineTo(e,-3*e),t.lineTo(e,-e),t.lineTo(3*e,-e),t.lineTo(3*e,e),t.lineTo(e,e),t.lineTo(e,3*e),t.lineTo(-e,3*e),t.lineTo(-e,e),t.lineTo(-3*e,e),t.closePath()}},Nb=Math.sqrt(1/3),kb=2*Nb,Sb={draw:function(t,n){var e=Math.sqrt(n/kb),r=e*Nb;t.moveTo(0,-e),t.lineTo(r,0),t.lineTo(0,e),t.lineTo(-r,0),t.closePath()}},Eb=.8908130915292852,Ab=Math.sin(fb/10)/Math.sin(7*fb/10),Cb=Math.sin(hb/10)*Ab,zb=-Math.cos(hb/10)*Ab,Pb={draw:function(t,n){var e=Math.sqrt(n*Eb),r=Cb*e,i=zb*e;t.moveTo(0,-e),t.lineTo(r,i);for(var o=1;o<5;++o){var u=hb*o/5,a=Math.cos(u),c=Math.sin(u);t.lineTo(c*e,-a*e),t.lineTo(a*r-c*i,c*r+a*i)}t.closePath()}},Rb={draw:function(t,n){var e=Math.sqrt(n),r=-e/2;t.rect(r,r,e,e)}},qb=Math.sqrt(3),Lb={draw:function(t,n){var e=-Math.sqrt(n/(3*qb));t.moveTo(0,2*e),t.lineTo(-qb*e,-e),t.lineTo(qb*e,-e),t.closePath()}},Ub=-.5,Db=Math.sqrt(3)/2,Ob=1/Math.sqrt(12),Fb=3*(Ob/2+1),Ib={draw:function(t,n){var e=Math.sqrt(n/Fb),r=e/2,i=e*Ob,o=r,u=e*Ob+e,a=-o,c=u;t.moveTo(r,i),t.lineTo(o,u),t.lineTo(a,c),t.lineTo(Ub*r-Db*i,Db*r+Ub*i),t.lineTo(Ub*o-Db*u,Db*o+Ub*u),t.lineTo(Ub*a-Db*c,Db*a+Ub*c),t.lineTo(Ub*r+Db*i,Ub*i-Db*r),t.lineTo(Ub*o+Db*u,Ub*u-Db*o),t.lineTo(Ub*a+Db*c,Ub*c-Db*a),t.closePath()}},Yb=[Mb,Tb,Sb,Rb,Pb,Lb,Ib],Bb=function(){function t(){var t;if(r||(r=t=Re()),n.apply(this,arguments).draw(r,+e.apply(this,arguments)),t)return r=null,t+""||null}var n=cb(Mb),e=cb(64),r=null;return t.type=function(e){return arguments.length?(n="function"==typeof e?e:cb(e),t):n},t.size=function(n){return arguments.length?(e="function"==typeof n?n:cb(+n),t):e},t.context=function(n){return arguments.length?(r=null==n?null:n,t):r},t},jb=function(){};fc.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=NaN,this._point=0},lineEnd:function(){switch(this._point){case 3:sc(this,this._x1,this._y1);case 2:this._context.lineTo(this._x1,this._y1)}(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,n):this._context.moveTo(t,n);break;case 1:this._point=2;break;case 2:this._point=3,this._context.lineTo((5*this._x0+this._x1)/6,(5*this._y0+this._y1)/6);default:sc(this,t,n)}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=n}};var Hb=function(t){return new fc(t)};lc.prototype={areaStart:jb,areaEnd:jb,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._y0=this._y1=this._y2=this._y3=this._y4=NaN,this._point=0},lineEnd:function(){switch(this._point){case 1:this._context.moveTo(this._x2,this._y2),this._context.closePath();break;case 2:this._context.moveTo((this._x2+2*this._x3)/3,(this._y2+2*this._y3)/3),this._context.lineTo((this._x3+2*this._x2)/3,(this._y3+2*this._y2)/3),this._context.closePath();break;case 3:this.point(this._x2,this._y2),this.point(this._x3,this._y3),this.point(this._x4,this._y4)}},point:function(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1,this._x2=t,this._y2=n;break;case 1:this._point=2,this._x3=t,this._y3=n;break;case 2:this._point=3,this._x4=t,this._y4=n,this._context.moveTo((this._x0+4*this._x1+t)/6,(this._y0+4*this._y1+n)/6);break;default:sc(this,t,n)}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=n}};var Xb=function(t){return new lc(t)};hc.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=NaN,this._point=0},lineEnd:function(){(this._line||0!==this._line&&3===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3;var e=(this._x0+4*this._x1+t)/6,r=(this._y0+4*this._y1+n)/6;this._line?this._context.lineTo(e,r):this._context.moveTo(e,r);break;case 3:this._point=4;default:sc(this,t,n)}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=n}};var Vb=function(t){return new hc(t)};pc.prototype={lineStart:function(){this._x=[],this._y=[],this._basis.lineStart()},lineEnd:function(){var t=this._x,n=this._y,e=t.length-1;if(e>0)for(var r,i=t[0],o=n[0],u=t[e]-i,a=n[e]-o,c=-1;++c<=e;)r=c/e,this._basis.point(this._beta*t[c]+(1-this._beta)*(i+r*u),this._beta*n[c]+(1-this._beta)*(o+r*a));this._x=this._y=null,this._basis.lineEnd()},point:function(t,n){this._x.push(+t),this._y.push(+n)}};var Wb=function t(n){function e(t){return 1===n?new fc(t):new pc(t,n)}return e.beta=function(n){return t(+n)},e}(.85);vc.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._point=0},lineEnd:function(){switch(this._point){case 2:this._context.lineTo(this._x2,this._y2);break;case 3:dc(this,this._x1,this._y1)}(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,n):this._context.moveTo(t,n);break;case 1:this._point=2,this._x1=t,this._y1=n;break;case 2:this._point=3;default:dc(this,t,n)}this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=n}};var $b=function t(n){function e(t){return new vc(t,n)}return e.tension=function(n){return t(+n)},e}(0);_c.prototype={areaStart:jb,areaEnd:jb,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._x5=this._y0=this._y1=this._y2=this._y3=this._y4=this._y5=NaN,this._point=0},lineEnd:function(){switch(this._point){case 1:this._context.moveTo(this._x3,this._y3),this._context.closePath();break;case 2:this._context.lineTo(this._x3,this._y3),this._context.closePath();break;case 3:this.point(this._x3,this._y3),this.point(this._x4,this._y4),this.point(this._x5,this._y5)}},point:function(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1,this._x3=t,this._y3=n;break;case 1:this._point=2,this._context.moveTo(this._x4=t,this._y4=n);break;case 2:this._point=3,this._x5=t,this._y5=n;break;default:dc(this,t,n)}this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=n}};var Zb=function t(n){function e(t){return new _c(t,n)}return e.tension=function(n){return t(+n)},e}(0);yc.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._point=0},lineEnd:function(){(this._line||0!==this._line&&3===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3,this._line?this._context.lineTo(this._x2,this._y2):this._context.moveTo(this._x2,this._y2);break;case 3:this._point=4;default:dc(this,t,n)}this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=n}};var Gb=function t(n){function e(t){return new yc(t,n)}return e.tension=function(n){return t(+n)},e}(0);mc.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},lineEnd:function(){switch(this._point){case 2:this._context.lineTo(this._x2,this._y2);break;case 3:this.point(this._x2,this._y2)}(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,n){if(t=+t,n=+n,this._point){var e=this._x2-t,r=this._y2-n;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(e*e+r*r,this._alpha))}switch(this._point){case 0:this._point=1,this._line?this._context.lineTo(t,n):this._context.moveTo(t,n);break;case 1:this._point=2;break;case 2:this._point=3;default:gc(this,t,n)}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=n}};var Jb=function t(n){function e(t){return n?new mc(t,n):new vc(t,0)}return e.alpha=function(n){return t(+n)},e}(.5);xc.prototype={areaStart:jb,areaEnd:jb,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._x5=this._y0=this._y1=this._y2=this._y3=this._y4=this._y5=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},lineEnd:function(){switch(this._point){case 1:this._context.moveTo(this._x3,this._y3),this._context.closePath();break;case 2:this._context.lineTo(this._x3,this._y3),this._context.closePath();break;case 3:this.point(this._x3,this._y3),this.point(this._x4,this._y4),this.point(this._x5,this._y5)}},point:function(t,n){if(t=+t,n=+n,this._point){var e=this._x2-t,r=this._y2-n;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(e*e+r*r,this._alpha))}switch(this._point){case 0:this._point=1,this._x3=t,this._y3=n;break;case 1:this._point=2,this._context.moveTo(this._x4=t,this._y4=n);break;case 2:this._point=3,this._x5=t,this._y5=n;break;default:gc(this,t,n)}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=n}};var Qb=function t(n){function e(t){return n?new xc(t,n):new _c(t,0)}return e.alpha=function(n){return t(+n)},e}(.5);bc.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0; +},lineEnd:function(){(this._line||0!==this._line&&3===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,n){if(t=+t,n=+n,this._point){var e=this._x2-t,r=this._y2-n;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(e*e+r*r,this._alpha))}switch(this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3,this._line?this._context.lineTo(this._x2,this._y2):this._context.moveTo(this._x2,this._y2);break;case 3:this._point=4;default:gc(this,t,n)}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=n}};var Kb=function t(n){function e(t){return n?new bc(t,n):new yc(t,0)}return e.alpha=function(n){return t(+n)},e}(.5);wc.prototype={areaStart:jb,areaEnd:jb,lineStart:function(){this._point=0},lineEnd:function(){this._point&&this._context.closePath()},point:function(t,n){t=+t,n=+n,this._point?this._context.lineTo(t,n):(this._point=1,this._context.moveTo(t,n))}};var tw=function(t){return new wc(t)};Sc.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=this._t0=NaN,this._point=0},lineEnd:function(){switch(this._point){case 2:this._context.lineTo(this._x1,this._y1);break;case 3:kc(this,this._t0,Nc(this,this._t0))}(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,n){var e=NaN;if(t=+t,n=+n,t!==this._x1||n!==this._y1){switch(this._point){case 0:this._point=1,this._line?this._context.lineTo(t,n):this._context.moveTo(t,n);break;case 1:this._point=2;break;case 2:this._point=3,kc(this,Nc(this,e=Tc(this,t,n)),e);break;default:kc(this,this._t0,e=Tc(this,t,n))}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=n,this._t0=e}}},(Ec.prototype=Object.create(Sc.prototype)).point=function(t,n){Sc.prototype.point.call(this,n,t)},Ac.prototype={moveTo:function(t,n){this._context.moveTo(n,t)},closePath:function(){this._context.closePath()},lineTo:function(t,n){this._context.lineTo(n,t)},bezierCurveTo:function(t,n,e,r,i,o){this._context.bezierCurveTo(n,t,r,e,o,i)}},Pc.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x=[],this._y=[]},lineEnd:function(){var t=this._x,n=this._y,e=t.length;if(e)if(this._line?this._context.lineTo(t[0],n[0]):this._context.moveTo(t[0],n[0]),2===e)this._context.lineTo(t[1],n[1]);else for(var r=Rc(t),i=Rc(n),o=0,u=1;u=0&&(this._t=1-this._t,this._line=1-this._line)},point:function(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,n):this._context.moveTo(t,n);break;case 1:this._point=2;default:if(this._t<=0)this._context.lineTo(this._x,n),this._context.lineTo(t,n);else{var e=this._x*(1-this._t)+t*this._t;this._context.lineTo(e,this._y),this._context.lineTo(e,n)}}this._x=t,this._y=n}};var ew=function(t){return new qc(t,.5)},rw=Array.prototype.slice,iw=function(t,n){if((r=t.length)>1)for(var e,r,i=1,o=t[n[0]],u=o.length;i=0;)e[n]=n;return e},uw=function(){function t(t){var o,u,a=n.apply(this,arguments),c=t.length,s=a.length,f=new Array(s);for(o=0;o0){for(var e,r,i,o=0,u=t[0].length;o0){for(var e,r=0,i=t[n[0]],o=i.length;r0&&(r=(e=t[n[0]]).length)>0){for(var e,r,i,o=0,u=1;u=a)return null;var c=t-i.site[0],s=n-i.site[1],f=c*c+s*s;do i=o.cells[r=u],u=null,i.halfedges.forEach(function(e){var r=o.edges[e],a=r.left;if(a!==i.site&&a||(a=r.right)){var c=t-a[0],s=n-a[1],l=c*c+s*s;le?(e+r)/2:Math.min(0,e)||Math.max(0,r),o>i?(i+o)/2:Math.min(0,i)||Math.max(0,o))}function o(t){return[(+t[0][0]+ +t[1][0])/2,(+t[0][1]+ +t[1][1])/2]}function u(t,n,e){t.on("start.zoom",function(){a(this,arguments).start()}).on("interrupt.zoom end.zoom",function(){a(this,arguments).end()}).tween("zoom",function(){var t=this,r=arguments,i=a(t,r),u=m.apply(t,r),c=e||o(u),s=Math.max(u[1][0]-u[0][0],u[1][1]-u[0][1]),f=t.__zoom,l="function"==typeof n?n.apply(t,r):n,h=E(f.invert(c).concat(s/f.k),l.invert(c).concat(s/l.k));return function(t){if(1===t)t=l;else{var n=h(t),e=s/n[2];t=new gs(e,c[0]-n[0]*e,c[1]-n[1]*e)}i.zoom(null,t)}})}function a(t,n){for(var e,r=0,i=A.length;r0?pl(this).transition().duration(k).call(u,f,a):pl(this).call(n.transform,f)}}function h(){if(g.apply(this,arguments)){var n,e,r,i,o=a(this,arguments),u=t.event.changedTouches,c=u.length;for(xs(),e=0;e