Merge branch 'master' into develop

This commit is contained in:
Torkel Ödegaard
2017-10-24 09:06:09 +02:00
69 changed files with 1284 additions and 654 deletions

View File

@@ -27,7 +27,7 @@ _.move = function (array, fromIndex, toIndex) {
return array;
};
import {coreModule} from './core/core';
import {coreModule, registerAngularDirectives} from './core/core';
export class GrafanaApp {
registerFunctions: any;
@@ -109,6 +109,9 @@ export class GrafanaApp {
// makes it possible to add dynamic stuff
this.useModule(coreModule);
// register react angular wrappers
registerAngularDirectives();
var preBootRequires = [System.import('app/features/all')];
Promise.all(preBootRequires).then(() => {

View File

@@ -1,5 +1,5 @@
import { react2AngularDirective } from 'app/core/utils/react2angular';
import { PasswordStrength } from './ui/PasswordStrength';
import { PasswordStrength } from './components/PasswordStrength';
export function registerAngularDirectives() {

View File

@@ -6,7 +6,7 @@ export interface IProps {
onColorSelect: (c: string) => void;
}
export class GfColorPalette extends React.Component<IProps, any> {
export class ColorPalette extends React.Component<IProps, any> {
paletteColors: string[];
constructor(props) {

View File

@@ -1,8 +1,8 @@
import React from 'react';
import $ from 'jquery';
import tinycolor from 'tinycolor2';
import { GfColorPalette } from './ColorPalette';
import { GfSpectrumPicker } from './SpectrumPicker';
import { ColorPalette } from './ColorPalette';
import { SpectrumPicker } from './SpectrumPicker';
const DEFAULT_COLOR = '#000000';
@@ -82,12 +82,12 @@ export class ColorPickerPopover extends React.Component<IProps, any> {
render() {
const paletteTab = (
<div id="palette">
<GfColorPalette color={this.state.color} onColorSelect={this.sampleColorSelected.bind(this)} />
<ColorPalette color={this.state.color} onColorSelect={this.sampleColorSelected.bind(this)} />
</div>
);
const spectrumTab = (
<div id="spectrum">
<GfSpectrumPicker color={this.state.color} onColorSelect={this.spectrumColorSelected.bind(this)} options={{}} />
<SpectrumPicker color={this.state.color} onColorSelect={this.spectrumColorSelected.bind(this)} options={{}} />
</div>
);
const currentTab = this.state.tab === 'palette' ? paletteTab : spectrumTab;

View File

@@ -9,7 +9,7 @@ export interface IProps {
onColorSelect: (c: string) => void;
}
export class GfSpectrumPicker extends React.Component<IProps, any> {
export class SpectrumPicker extends React.Component<IProps, any> {
elem: any;
isMoving: boolean;

View File

@@ -53,10 +53,9 @@ import {orgSwitcher} from './components/org_switcher';
import {profiler} from './profiler';
import {registerAngularDirectives} from './angular_wrappers';
registerAngularDirectives();
export {
profiler,
registerAngularDirectives,
arrayJoin,
coreModule,
grafanaAppDirective,

View File

@@ -0,0 +1,10 @@
import React from 'react';
import renderer from 'react-test-renderer';
import { ColorPalette } from '../components/colorpicker/ColorPalette';
describe('CollorPalette', () => {
it('renders correctly', () => {
const tree = renderer.create(<ColorPalette color="#EAB839" onColorSelect={jest.fn()} />).toJSON();
expect(tree).toMatchSnapshot();
});
});

View File

@@ -1,7 +1,7 @@
import React from 'react';
import {shallow} from 'enzyme';
import {PasswordStrength} from '../ui/PasswordStrength';
import {PasswordStrength} from '../components/PasswordStrength';
describe('PasswordStrength', () => {

View File

@@ -0,0 +1,628 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CollorPalette renders correctly 1`] = `
<div
className="graph-legend-popover"
>
<p
className="m-b-0"
>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#890f02",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#58140c",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#99440a",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#c15c17",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#967302",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#cca300",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#3f6833",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#2f575e",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#64b0c8",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#052b51",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#0a50a1",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#584477",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#3f2b5b",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#511749",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#e24d42",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#bf1b00",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#ef843c",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#f4d598",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#e5ac0e",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#9ac48a",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#508642",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#6ed0e0",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#65c5db",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#0a437c",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#447ebc",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#614d93",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#d683ce",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#6d1f62",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#ea6460",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#e0752d",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#f9934e",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#fceaca",
}
}
>
 
</i>
<i
className="pointer fa fa-circle-o"
onClick={[Function]}
style={
Object {
"color": "#eab839",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#b7dbab",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#629e51",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#70dbed",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#82b5d8",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#1f78c1",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#aea2e0",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#705da0",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#e5a8e2",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#962d82",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#f29191",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#fce2de",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#f9ba8f",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#f9e2d2",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#f2c96d",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#e0f9d7",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#7eb26d",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#cffaff",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#badff4",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#5195ce",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#dedaf7",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#806eb7",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#f9d9f9",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#ba43a9",
}
}
>
 
</i>
</p>
</div>
`;

View File

@@ -1,5 +1,3 @@
///<reference path="../headers/common.d.ts" />
import kbn from 'app/core/utils/kbn';
import _ from 'lodash';
@@ -23,6 +21,7 @@ export default class TimeSeries {
id: string;
label: string;
alias: string;
aliasEscaped: string;
color: string;
valueFormater: any;
stats: any;
@@ -52,6 +51,7 @@ export default class TimeSeries {
this.label = opts.alias;
this.id = opts.alias;
this.alias = opts.alias;
this.aliasEscaped = _.escape(opts.alias);
this.color = opts.color;
this.valueFormater = kbn.valueFormats.none;
this.stats = {};

View File

@@ -3,6 +3,7 @@ import './editor_ctrl';
import angular from 'angular';
import _ from 'lodash';
import coreModule from 'app/core/core_module';
import {makeRegions, dedupAnnotations} from './events_processing';
export class AnnotationsSrv {
globalAnnotationsPromise: any;
@@ -161,83 +162,4 @@ export class AnnotationsSrv {
}
}
/**
* This function converts annotation events into set
* of single events and regions (event consist of two)
* @param annotations
* @param options
*/
function makeRegions(annotations, options) {
let [regionEvents, singleEvents] = _.partition(annotations, 'regionId');
let regions = getRegions(regionEvents, options.range);
annotations = _.concat(regions, singleEvents);
return annotations;
}
function getRegions(events, range) {
let region_events = _.filter(events, event => {
return event.regionId;
});
let regions = _.groupBy(region_events, 'regionId');
regions = _.compact(
_.map(regions, region_events => {
let region_obj = _.head(region_events);
if (region_events && region_events.length > 1) {
region_obj.timeEnd = region_events[1].time;
region_obj.isRegion = true;
return region_obj;
} else {
if (region_events && region_events.length) {
// Don't change proper region object
if (!region_obj.time || !region_obj.timeEnd) {
// This is cut region
if (isStartOfRegion(region_obj)) {
region_obj.timeEnd = range.to.valueOf() - 1;
} else {
// Start time = null
region_obj.timeEnd = region_obj.time;
region_obj.time = range.from.valueOf() + 1;
}
region_obj.isRegion = true;
}
return region_obj;
}
}
}),
);
return regions;
}
function isStartOfRegion(event): boolean {
return event.id && event.id === event.regionId;
}
function dedupAnnotations(annotations) {
let dedup = [];
// Split events by annotationId property existance
let events = _.partition(annotations, 'id');
let eventsById = _.groupBy(events[0], 'id');
dedup = _.map(eventsById, eventGroup => {
if (eventGroup.length > 1 && !_.every(eventGroup, isPanelAlert)) {
// Get first non-panel alert
return _.find(eventGroup, event => {
return event.eventType !== 'panel-alert';
});
} else {
return _.head(eventGroup);
}
});
dedup = _.concat(dedup, events[1]);
return dedup;
}
function isPanelAlert(event) {
return event.eventType === 'panel-alert';
}
coreModule.service('annotationsSrv', AnnotationsSrv);

View File

@@ -0,0 +1,80 @@
import _ from 'lodash';
/**
* This function converts annotation events into set
* of single events and regions (event consist of two)
* @param annotations
* @param options
*/
export function makeRegions(annotations, options) {
let [regionEvents, singleEvents] = _.partition(annotations, 'regionId');
let regions = getRegions(regionEvents, options.range);
annotations = _.concat(regions, singleEvents);
return annotations;
}
function getRegions(events, range) {
let region_events = _.filter(events, event => {
return event.regionId;
});
let regions = _.groupBy(region_events, 'regionId');
regions = _.compact(
_.map(regions, region_events => {
let region_obj = _.head(region_events);
if (region_events && region_events.length > 1) {
region_obj.timeEnd = region_events[1].time;
region_obj.isRegion = true;
return region_obj;
} else {
if (region_events && region_events.length) {
// Don't change proper region object
if (!region_obj.time || !region_obj.timeEnd) {
// This is cut region
if (isStartOfRegion(region_obj)) {
region_obj.timeEnd = range.to.valueOf() - 1;
} else {
// Start time = null
region_obj.timeEnd = region_obj.time;
region_obj.time = range.from.valueOf() + 1;
}
region_obj.isRegion = true;
}
return region_obj;
}
}
}),
);
return regions;
}
function isStartOfRegion(event): boolean {
return event.id && event.id === event.regionId;
}
export function dedupAnnotations(annotations) {
let dedup = [];
// Split events by annotationId property existance
let events = _.partition(annotations, 'id');
let eventsById = _.groupBy(events[0], 'id');
dedup = _.map(eventsById, eventGroup => {
if (eventGroup.length > 1 && !_.every(eventGroup, isPanelAlert)) {
// Get first non-panel alert
return _.find(eventGroup, event => {
return event.eventType !== 'panel-alert';
});
} else {
return _.head(eventGroup);
}
});
dedup = _.concat(dedup, events[1]);
return dedup;
}
function isPanelAlert(event) {
return event.eventType === 'panel-alert';
}

View File

@@ -0,0 +1,83 @@
import {makeRegions, dedupAnnotations} from '../events_processing';
describe('Annotations', () => {
describe('Annotations regions', () => {
let testAnnotations: any[];
beforeEach(() => {
testAnnotations = [
{id: 1, time: 1},
{id: 2, time: 2},
{id: 3, time: 3, regionId: 3},
{id: 4, time: 5, regionId: 3},
{id: 5, time: 4, regionId: 5},
{id: 6, time: 8, regionId: 5}
];
});
it('should convert single region events to regions', () => {
const range = {from: 0, to: 10};
const expectedAnnotations = [
{id: 3, regionId: 3, isRegion: true, time: 3, timeEnd: 5},
{id: 5, regionId: 5, isRegion: true, time: 4, timeEnd: 8},
{id: 1, time: 1},
{id: 2, time: 2}
];
let regions = makeRegions(testAnnotations, {range: range});
expect(regions).toEqual(expectedAnnotations);
});
it('should cut regions to current time range', () => {
const range = {from: 0, to: 8};
testAnnotations = [
{id: 5, time: 4, regionId: 5}
];
const expectedAnnotations = [
{id: 5, regionId: 5, isRegion: true, time: 4, timeEnd: 7}
];
let regions = makeRegions(testAnnotations, {range: range});
expect(regions).toEqual(expectedAnnotations);
});
});
describe('Annotations deduplication', () => {
it('should remove duplicated annotations', () => {
const testAnnotations = [
{id: 1, time: 1},
{id: 2, time: 2},
{id: 2, time: 2},
{id: 5, time: 5},
{id: 5, time: 5}
];
const expectedAnnotations = [
{id: 1, time: 1},
{id: 2, time: 2},
{id: 5, time: 5}
];
let deduplicated = dedupAnnotations(testAnnotations);
expect(deduplicated).toEqual(expectedAnnotations);
});
it('should leave non "panel-alert" event if present', () => {
const testAnnotations = [
{id: 1, time: 1},
{id: 2, time: 2},
{id: 2, time: 2, eventType: 'panel-alert'},
{id: 5, time: 5},
{id: 5, time: 5}
];
const expectedAnnotations = [
{id: 1, time: 1},
{id: 2, time: 2},
{id: 5, time: 5}
];
let deduplicated = dedupAnnotations(testAnnotations);
expect(deduplicated).toEqual(expectedAnnotations);
});
});
});

View File

@@ -7,6 +7,7 @@ import {coreModule, JsonExplorer} from 'app/core/core';
const template = `
<div class="query-troubleshooter" ng-if="ctrl.isOpen">
<div class="query-troubleshooter__header">
<a class="pointer" ng-click="ctrl.toggleMocking()">Mock Response</a>
<a class="pointer" ng-click="ctrl.toggleExpand()" ng-hide="ctrl.allNodesExpanded">
<i class="fa fa-plus-square-o"></i> Expand All
</a>
@@ -15,10 +16,15 @@ const template = `
</a>
<a class="pointer" clipboard-button="ctrl.getClipboardText()"><i class="fa fa-clipboard"></i> Copy to Clipboard</a>
</div>
<div class="query-troubleshooter__body">
<div class="query-troubleshooter__body" ng-hide="ctrl.isMocking">
<i class="fa fa-spinner fa-spin" ng-show="ctrl.isLoading"></i>
<div class="query-troubleshooter-json"></div>
</div>
<div class="query-troubleshooter__body" ng-show="ctrl.isMocking">
<div class="gf-form p-l-1 gf-form--v-stretch">
<textarea class="gf-form-input" style="width: 95%" rows="10" ng-model="ctrl.mockedResponse" placeholder="JSON"></textarea>
</div>
</div>
</div>
`;
@@ -32,6 +38,8 @@ export class QueryTroubleshooterCtrl {
onRequestResponseEventListener: any;
hasError: boolean;
allNodesExpanded: boolean;
isMocking: boolean;
mockedResponse: string;
jsonExplorer: JsonExplorer;
/** @ngInject **/
@@ -51,6 +59,10 @@ export class QueryTroubleshooterCtrl {
appEvents.off('ds-request-error', this.onRequestErrorEventListener);
}
toggleMocking() {
this.isMocking = !this.isMocking;
}
onRequestError(err) {
// ignore if closed
if (!this.isOpen) {
@@ -76,12 +88,29 @@ export class QueryTroubleshooterCtrl {
return '';
}
handleMocking(data) {
var mockedData;
try {
mockedData = JSON.parse(this.mockedResponse);
} catch (err) {
appEvents.emit('alert-error', ['Failed to parse mocked response']);
return;
}
data.data = mockedData;
}
onRequestResponse(data) {
// ignore if closed
if (!this.isOpen) {
return;
}
if (this.isMocking) {
this.handleMocking(data);
return;
}
this.isLoading = false;
data = _.cloneDeep(data);

View File

@@ -15,7 +15,7 @@ import * as flatten from 'app/core/utils/flatten';
import * as ticks from 'app/core/utils/ticks';
import {impressions} from 'app/features/dashboard/impression_store';
import builtInPlugins from './built_in_plugins';
import d3 from 'vendor/d3/d3';
import * as d3 from 'd3';
// rxjs
import {Observable} from 'rxjs/Observable';

View File

@@ -1,5 +1,3 @@
import {describe, it, expect} from 'test/lib/common';
import {containsVariable, assignModelProperties} from '../variable';
describe('containsVariable', function() {
@@ -8,37 +6,37 @@ describe('containsVariable', function() {
it('should find it with $var syntax', function() {
var contains = containsVariable('this.$test.filters', 'test');
expect(contains).to.be(true);
expect(contains).toBe(true);
});
it('should not find it if only part matches with $var syntax', function() {
var contains = containsVariable('this.$serverDomain.filters', 'server');
expect(contains).to.be(false);
expect(contains).toBe(false);
});
it('should find it if it ends with variable and passing multiple test strings', function() {
var contains = containsVariable('show field keys from $pgmetric', 'test string2', 'pgmetric');
expect(contains).to.be(true);
expect(contains).toBe(true);
});
it('should find it with [[var]] syntax', function() {
var contains = containsVariable('this.[[test]].filters', 'test');
expect(contains).to.be(true);
expect(contains).toBe(true);
});
it('should find it when part of segment', function() {
var contains = containsVariable('metrics.$env.$group-*', 'group');
expect(contains).to.be(true);
expect(contains).toBe(true);
});
it('should find it its the only thing', function() {
var contains = containsVariable('$env', 'env');
expect(contains).to.be(true);
expect(contains).toBe(true);
});
it('should be able to pass in multiple test strings', function() {
var contains = containsVariable('asd','asd2.$env', 'env');
expect(contains).to.be(true);
expect(contains).toBe(true);
});
});
@@ -50,14 +48,14 @@ describe('assignModelProperties', function() {
it('only set properties defined in defaults', function() {
var target: any = {test: 'asd'};
assignModelProperties(target, {propA: 1, propB: 2}, {propB: 0});
expect(target.propB).to.be(2);
expect(target.test).to.be('asd');
expect(target.propB).toBe(2);
expect(target.test).toBe('asd');
});
it('use default value if not found on source', function() {
var target: any = {test: 'asd'};
assignModelProperties(target, {propA: 1, propB: 2}, {propC: 10});
expect(target.propC).to.be(10);
expect(target.propC).toBe(10);
});
});

View File

@@ -1,8 +1,6 @@
///<reference path="../../../headers/common.d.ts" />
import _ from 'lodash';
import TimeSeries from 'app/core/time_series2';
import {colors} from 'app/core/core';
import colors from 'app/core/utils/colors';
export class DataProcessor {

View File

@@ -381,7 +381,7 @@ function graphDirective($rootScope, timeSrv, popoverSrv, contextSrv) {
var haveSortOrder = sortOrder !== null || sortOrder !== undefined;
if (panel.stack && haveSortBy && haveSortOrder) {
var desc = desc = panel.legend.sortDesc === true ? 1 : -1;
var desc = desc = panel.legend.sortDesc === true ? -1 : 1;
series.sort((x, y) => {
if (x.stats[sortBy] > y.stats[sortBy]) {
return 1 * desc;

View File

@@ -127,7 +127,7 @@ function ($, core) {
value: value,
hoverIndex: hoverIndex,
color: series.color,
label: series.label,
label: series.aliasEscaped,
time: pointTime,
distance: hoverDistance,
index: i
@@ -264,7 +264,7 @@ function ($, core) {
else if (item) {
series = seriesList[item.seriesIndex];
group = '<div class="graph-tooltip-list-item"><div class="graph-tooltip-series-name">';
group += '<i class="fa fa-minus" style="color:' + item.series.color +';"></i> ' + series.label + ':</div>';
group += '<i class="fa fa-minus" style="color:' + item.series.color +';"></i> ' + series.aliasEscaped + ':</div>';
if (panel.stack && panel.tooltip.value_type === 'individual') {
value = item.datapoint[1] - item.datapoint[2];

View File

@@ -169,7 +169,7 @@ function (angular, _, $) {
html += '<i class="fa fa-minus pointer" style="color:' + series.color + '"></i>';
html += '</div>';
html += '<a class="graph-legend-alias pointer" title="' + _.escape(series.label) + '">' + _.escape(series.label) + '</a>';
html += '<a class="graph-legend-alias pointer" title="' + series.aliasEscaped + '">' + series.aliasEscaped + '</a>';
if (panel.legend.values) {
var avg = series.formatValue(series.stats.avg);

View File

@@ -1,7 +1,3 @@
///<reference path="../../../../headers/common.d.ts" />
import {describe, beforeEach, it, expect} from '../../../../../test/lib/common';
import {DataProcessor} from '../data_processor';
describe('Graph DataProcessor', function() {
@@ -29,7 +25,7 @@ describe('Graph DataProcessor', function() {
});
it('Should automatically set xaxis mode to field', () => {
expect(panel.xaxis.mode).to.be('field');
expect(panel.xaxis.mode).toBe('field');
});
});
@@ -48,16 +44,16 @@ describe('Graph DataProcessor', function() {
it('Should return all field names', () => {
var fields = processor.getDataFieldNames(dataList, false);
expect(fields).to.contain('hostname');
expect(fields).to.contain('valueField');
expect(fields).to.contain('nested.prop1');
expect(fields).to.contain('nested.value2');
expect(fields).toContain('hostname');
expect(fields).toContain('valueField');
expect(fields).toContain('nested.prop1');
expect(fields).toContain('nested.value2');
});
it('Should return all number fields', () => {
var fields = processor.getDataFieldNames(dataList, true);
expect(fields).to.contain('valueField');
expect(fields).to.contain('nested.value2');
expect(fields).toContain('valueField');
expect(fields).toContain('nested.value2');
});
});
});

View File

@@ -75,7 +75,7 @@ describe('grafanaGraph', function() {
alias: 'series1'
}));
ctx.data.push(new TimeSeries({
datapoints: [[1,10],[2,20]],
datapoints: [[10,1],[20,2]],
alias: 'series2'
}));
@@ -112,52 +112,67 @@ describe('grafanaGraph', function() {
});
});
graphScenario('sort series as legend', (ctx) => {
describe("with sort as legend undefined", () => {
ctx.setup((ctrl) => {
ctrl.panel.legend.sort = undefined;
});
it("should not modify order of time series", () => {
expect(ctx.plotData[0].alias).to.be('series1');
expect(ctx.plotData[1].alias).to.be('series2');
});
graphScenario('sorting stacked series as legend. disabled', (ctx) => {
ctx.setup((ctrl) => {
ctrl.panel.legend.sort = undefined;
ctrl.panel.stack = false;
});
describe("with sort as legend set to min. descending order", () => {
ctx.setup((ctrl) => {
ctrl.panel.legend.sort = 'min';
ctrl.panel.legend.sortDesc = true;
});
it("should not modify order of time series", () => {
expect(ctx.plotData[0].alias).to.be('series1');
expect(ctx.plotData[1].alias).to.be('series2');
});
});
it("highest value should be first", () => {
expect(ctx.plotData[1].alias).to.be('series2');
expect(ctx.plotData[0].alias).to.be('series1');
});
graphScenario("sorting stacked series as legend. min descending order", (ctx) => {
ctx.setup(ctrl => {
ctrl.panel.legend.sort = 'min';
ctrl.panel.legend.sortDesc = true;
ctrl.panel.stack = true;
});
describe("with sort as legend set to min. ascending order", () => {
ctx.setup((ctrl) => {
ctrl.panel.legend.sort = 'min';
ctrl.panel.legend.sortDesc = true;
});
it("highest value should be first", () => {
expect(ctx.plotData[0].alias).to.be('series2');
expect(ctx.plotData[1].alias).to.be('series1');
});
});
it("lowest value should be first", () => {
expect(ctx.plotData[0].alias).to.be('series1');
expect(ctx.plotData[1].alias).to.be('series2');
});
graphScenario("sorting stacked series as legend. min ascending order", (ctx) => {
ctx.setup((ctrl, data) => {
ctrl.panel.legend.sort = 'min';
ctrl.panel.legend.sortDesc = false;
ctrl.panel.stack = true;
});
describe("with sort as legend set to current. ascending order", () => {
ctx.setup((ctrl) => {
ctrl.panel.legend.sort = 'current';
ctrl.panel.legend.sortDesc = false;
});
it("lowest value should be first", () => {
expect(ctx.plotData[0].alias).to.be('series1');
expect(ctx.plotData[1].alias).to.be('series2');
});
});
it("highest last value should be first", () => {
expect(ctx.plotData[1].alias).to.be('series2');
expect(ctx.plotData[0].alias).to.be('series1');
});
graphScenario("sorting stacked series as legend. stacking disabled", (ctx) => {
ctx.setup((ctrl) => {
ctrl.panel.legend.sort = 'min';
ctrl.panel.legend.sortDesc = true;
ctrl.panel.stack = false;
});
it("highest value should be first", () => {
expect(ctx.plotData[0].alias).to.be('series1');
expect(ctx.plotData[1].alias).to.be('series2');
});
});
graphScenario("sorting stacked series as legend. current descending order", (ctx) => {
ctx.setup((ctrl) => {
ctrl.panel.legend.sort = 'current';
ctrl.panel.legend.sortDesc = true;
ctrl.panel.stack = true;
});
it("highest last value should be first", () => {
expect(ctx.plotData[0].alias).to.be('series2');
expect(ctx.plotData[1].alias).to.be('series1');
});
});
@@ -300,7 +315,7 @@ describe('grafanaGraph', function() {
});
it('should set barWidth', function() {
expect(ctx.plotOptions.series.bars.barWidth).to.be(10/1.5);
expect(ctx.plotOptions.series.bars.barWidth).to.be(1/1.5);
});
});
@@ -383,146 +398,4 @@ describe('grafanaGraph', function() {
});
}, 10);
// graphScenario('when using flexible Y-Min and Y-Max settings', function(ctx) {
// describe('and Y-Min is <100 and Y-Max is >200 and values within range', function() {
// ctx.setup(function(ctrl, data) {
// ctrl.panel.yaxes[0].min = '<100';
// ctrl.panel.yaxes[0].max = '>200';
// data[0] = new TimeSeries({
// datapoints: [[120,10],[160,20]],
// alias: 'series1',
// });
// });
//
// it('should set min to 100 and max to 200', function() {
// expect(ctx.plotOptions.yaxes[0].min).to.be(100);
// expect(ctx.plotOptions.yaxes[0].max).to.be(200);
// });
// });
// describe('and Y-Min is <100 and Y-Max is >200 and values outside range', function() {
// ctx.setup(function(ctrl, data) {
// ctrl.panel.yaxes[0].min = '<100';
// ctrl.panel.yaxes[0].max = '>200';
// data[0] = new TimeSeries({
// datapoints: [[99,10],[201,20]],
// alias: 'series1',
// });
// });
//
// it('should set min to auto and max to auto', function() {
// expect(ctx.plotOptions.yaxes[0].min).to.be(null);
// expect(ctx.plotOptions.yaxes[0].max).to.be(null);
// });
// });
// describe('and Y-Min is =10.5 and Y-Max is =10.5', function() {
// ctx.setup(function(ctrl, data) {
// ctrl.panel.yaxes[0].min = '=10.5';
// ctrl.panel.yaxes[0].max = '=10.5';
// data[0] = new TimeSeries({
// datapoints: [[100,10],[120,20], [110,30]],
// alias: 'series1',
// });
// });
//
// it('should set min to last value + 10.5 and max to last value + 10.5', function() {
// expect(ctx.plotOptions.yaxes[0].min).to.be(99.5);
// expect(ctx.plotOptions.yaxes[0].max).to.be(120.5);
// });
// });
// describe('and Y-Min is ~10.5 and Y-Max is ~10.5', function() {
// ctx.setup(function(ctrl, data) {
// ctrl.panel.yaxes[0].min = '~10.5';
// ctrl.panel.yaxes[0].max = '~10.5';
// data[0] = new TimeSeries({
// datapoints: [[102,10],[104,20], [110,30]], //Also checks precision
// alias: 'series1',
// });
// });
//
// it('should set min to average value + 10.5 and max to average value + 10.5', function() {
// expect(ctx.plotOptions.yaxes[0].min).to.be(94.8);
// expect(ctx.plotOptions.yaxes[0].max).to.be(115.8);
// });
// });
// });
// graphScenario('when using regular Y-Min and Y-Max settings', function(ctx) {
// describe('and Y-Min is 100 and Y-Max is 200', function() {
// ctx.setup(function(ctrl, data) {
// ctrl.panel.yaxes[0].min = '100';
// ctrl.panel.yaxes[0].max = '200';
// data[0] = new TimeSeries({
// datapoints: [[120,10],[160,20]],
// alias: 'series1',
// });
// });
//
// it('should set min to 100 and max to 200', function() {
// expect(ctx.plotOptions.yaxes[0].min).to.be(100);
// expect(ctx.plotOptions.yaxes[0].max).to.be(200);
// });
// });
// describe('and Y-Min is 0 and Y-Max is 0', function() {
// ctx.setup(function(ctrl, data) {
// ctrl.panel.yaxes[0].min = '0';
// ctrl.panel.yaxes[0].max = '0';
// data[0] = new TimeSeries({
// datapoints: [[120,10],[160,20]],
// alias: 'series1',
// });
// });
//
// it('should set min to 0 and max to 0', function() {
// expect(ctx.plotOptions.yaxes[0].min).to.be(0);
// expect(ctx.plotOptions.yaxes[0].max).to.be(0);
// });
// });
// describe('and negative values used', function() {
// ctx.setup(function(ctrl, data) {
// ctrl.panel.yaxes[0].min = '-10';
// ctrl.panel.yaxes[0].max = '-13.14';
// data[0] = new TimeSeries({
// datapoints: [[120,10],[160,20]],
// alias: 'series1',
// });
// });
//
// it('should set min and max to negative', function() {
// expect(ctx.plotOptions.yaxes[0].min).to.be(-10);
// expect(ctx.plotOptions.yaxes[0].max).to.be(-13.14);
// });
// });
// });
// graphScenario('when using Y-Min and Y-Max settings stored as number', function(ctx) {
// describe('and Y-Min is 0 and Y-Max is 100', function() {
// ctx.setup(function(ctrl, data) {
// ctrl.panel.yaxes[0].min = 0;
// ctrl.panel.yaxes[0].max = 100;
// data[0] = new TimeSeries({
// datapoints: [[120,10],[160,20]],
// alias: 'series1',
// });
// });
//
// it('should set min to 0 and max to 100', function() {
// expect(ctx.plotOptions.yaxes[0].min).to.be(0);
// expect(ctx.plotOptions.yaxes[0].max).to.be(100);
// });
// });
// describe('and Y-Min is -100 and Y-Max is -10.5', function() {
// ctx.setup(function(ctrl, data) {
// ctrl.panel.yaxes[0].min = -100;
// ctrl.panel.yaxes[0].max = -10.5;
// data[0] = new TimeSeries({
// datapoints: [[120,10],[160,20]],
// alias: 'series1',
// });
// });
//
// it('should set min to -100 and max to -10.5', function() {
// expect(ctx.plotOptions.yaxes[0].min).to.be(-100);
// expect(ctx.plotOptions.yaxes[0].max).to.be(-10.5);
// });
// });
// });
});

View File

@@ -1,7 +1,3 @@
///<reference path="../../../../headers/common.d.ts" />
import { describe, beforeEach, it, expect } from '../../../../../test/lib/common';
import { convertValuesToHistogram, getSeriesValues } from '../histogram';
describe('Graph Histogam Converter', function () {
@@ -21,7 +17,7 @@ describe('Graph Histogam Converter', function () {
];
let histogram = convertValuesToHistogram(values, bucketSize);
expect(histogram).to.eql(expected);
expect(histogram).toMatchObject(expected);
});
it('Should not add empty buckets', () => {
@@ -31,7 +27,7 @@ describe('Graph Histogam Converter', function () {
];
let histogram = convertValuesToHistogram(values, bucketSize);
expect(histogram).to.eql(expected);
expect(histogram).toMatchObject(expected);
});
});
@@ -50,7 +46,7 @@ describe('Graph Histogam Converter', function () {
let expected = [1, 2, 10, 11, 17, 20, 29];
let values = getSeriesValues(data);
expect(values).to.eql(expected);
expect(values).toMatchObject(expected);
});
it('Should skip null values', () => {
@@ -59,7 +55,7 @@ describe('Graph Histogam Converter', function () {
let expected = [1, 2, 10, 11, 17, 20, 29];
let values = getSeriesValues(data);
expect(values).to.eql(expected);
expect(values).toMatchObject(expected);
});
});
});

View File

@@ -1,9 +1,10 @@
import angular from 'angular';
import _ from 'lodash';
import $ from 'jquery';
import d3 from 'vendor/d3/d3';
import * as d3 from 'd3';
import {contextSrv} from 'app/core/core';
import {tickStep} from 'app/core/utils/ticks';
import {getColorScale, getOpacityScale} from './color_scale';
let module = angular.module('grafana.directives');
@@ -30,7 +31,7 @@ module.directive('colorLegend', function() {
if (panel.color.mode === 'spectrum') {
let colorScheme = _.find(ctrl.colorSchemes, {value: panel.color.colorScheme});
let colorScale = getColorScale(colorScheme, legendWidth);
let colorScale = getColorScale(colorScheme, contextSrv.user.lightTheme, legendWidth);
drawSimpleColorLegend(elem, colorScale);
} else if (panel.color.mode === 'opacity') {
let colorOptions = panel.color;
@@ -93,7 +94,7 @@ function drawColorLegend(elem, colorScheme, rangeFrom, rangeTo, maxValue, minVal
let widthFactor = legendWidth / (rangeTo - rangeFrom);
let valuesRange = d3.range(rangeFrom, rangeTo, rangeStep);
let colorScale = getColorScale(colorScheme, maxValue, minValue);
let colorScale = getColorScale(colorScheme, contextSrv.user.lightTheme, maxValue, minValue);
legend.selectAll(".heatmap-color-legend-rect")
.data(valuesRange)
.enter().append("rect")
@@ -115,7 +116,10 @@ function drawOpacityLegend(elem, options, rangeFrom, rangeTo, maxValue, minValue
let legendWidth = Math.floor(legendElem.outerWidth()) - 30;
let legendHeight = legendElem.attr("height");
let rangeStep = 10;
let rangeStep = 1;
if (rangeTo - rangeFrom > legendWidth) {
rangeStep = Math.floor((rangeTo - rangeFrom) / legendWidth);
}
let widthFactor = legendWidth / (rangeTo - rangeFrom);
let valuesRange = d3.range(rangeFrom, rangeTo, rangeStep);
@@ -228,31 +232,6 @@ function clearLegend(elem) {
legendElem.empty();
}
function getColorScale(colorScheme, maxValue, minValue = 0) {
let colorInterpolator = d3[colorScheme.value];
let colorScaleInverted = colorScheme.invert === 'always' ||
(colorScheme.invert === 'dark' && !contextSrv.user.lightTheme);
let start = colorScaleInverted ? maxValue : minValue;
let end = colorScaleInverted ? minValue : maxValue;
return d3.scaleSequential(colorInterpolator).domain([start, end]);
}
function getOpacityScale(options, maxValue, minValue = 0) {
let legendOpacityScale;
if (options.colorScale === 'linear') {
legendOpacityScale = d3.scaleLinear()
.domain([minValue, maxValue])
.range([0, 1]);
} else if (options.colorScale === 'sqrt') {
legendOpacityScale = d3.scalePow().exponent(options.exponent)
.domain([minValue, maxValue])
.range([0, 1]);
}
return legendOpacityScale;
}
function getSvgElemX(elem) {
let svgElem = elem.get(0);
if (svgElem && svgElem.x && svgElem.x.baseVal) {

View File

@@ -0,0 +1,27 @@
import * as d3 from 'd3';
import * as d3ScaleChromatic from 'd3-scale-chromatic';
export function getColorScale(colorScheme: any, lightTheme: boolean, maxValue: number, minValue = 0): (d: any) => any {
let colorInterpolator = d3ScaleChromatic[colorScheme.value];
let colorScaleInverted = colorScheme.invert === 'always' ||
(colorScheme.invert === 'dark' && !lightTheme);
let start = colorScaleInverted ? maxValue : minValue;
let end = colorScaleInverted ? minValue : maxValue;
return d3.scaleSequential(colorInterpolator).domain([start, end]);
}
export function getOpacityScale(options, maxValue, minValue = 0) {
let legendOpacityScale;
if (options.colorScale === 'linear') {
legendOpacityScale = d3.scaleLinear()
.domain([minValue, maxValue])
.range([0, 1]);
} else if (options.colorScale === 'sqrt') {
legendOpacityScale = d3.scalePow().exponent(options.exponent)
.domain([minValue, maxValue])
.range([0, 1]);
}
return legendOpacityScale;
}

View File

@@ -1,4 +1,4 @@
import d3 from 'vendor/d3/d3';
import * as d3 from 'd3';
import $ from 'jquery';
import _ from 'lodash';
import kbn from 'app/core/utils/kbn';

View File

@@ -1,12 +1,13 @@
import _ from 'lodash';
import $ from 'jquery';
import moment from 'moment';
import * as d3 from 'd3';
import kbn from 'app/core/utils/kbn';
import {appEvents, contextSrv} from 'app/core/core';
import {tickStep, getScaledDecimals, getFlotTickSize} from 'app/core/utils/ticks';
import d3 from 'vendor/d3/d3';
import {HeatmapTooltip} from './heatmap_tooltip';
import {mergeZeroBuckets} from './heatmap_data_converter';
import {getColorScale, getOpacityScale} from './color_scale';
let MIN_CARD_SIZE = 1,
CARD_PADDING = 1,
@@ -386,8 +387,9 @@ export default function link(scope, elem, attrs, ctrl) {
let maxValue = panel.color.max || maxValueAuto;
let minValue = panel.color.min || 0;
colorScale = getColorScale(maxValue, minValue);
setOpacityScale(maxValue);
let colorScheme = _.find(ctrl.colorSchemes, {value: panel.color.colorScheme});
colorScale = getColorScale(colorScheme, contextSrv.user.lightTheme, maxValue, minValue);
opacityScale = getOpacityScale(panel.color, maxValue);
setCardSize();
let cards = heatmap.selectAll(".heatmap-card").data(cardsData);
@@ -422,8 +424,8 @@ export default function link(scope, elem, attrs, ctrl) {
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)
current_card.style("fill", highlightColor.toString())
.style("stroke", strokeColor.toString())
.style("stroke-width", 1);
}
@@ -433,30 +435,6 @@ export default function link(scope, elem, attrs, ctrl) {
.style("stroke-width", 0);
}
function getColorScale(maxValue, minValue = 0) {
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 : minValue;
let end = colorScaleInverted ? minValue : maxValue;
return d3.scaleSequential(colorInterpolator).domain([start, end]);
}
function setOpacityScale(maxValue) {
if (panel.color.colorScale === 'linear') {
opacityScale = d3.scaleLinear()
.domain([0, maxValue])
.range([0, 1]);
} else if (panel.color.colorScale === 'sqrt') {
opacityScale = d3.scalePow().exponent(panel.color.exponent)
.domain([0, maxValue])
.range([0, 1]);
}
}
function setCardSize() {
let xGridSize = Math.floor(xScale(data.xBucketSize) - xScale(0));
let yGridSize = Math.floor(yScale(yScale.invert(0) - data.yBucketSize));

View File

@@ -1,83 +0,0 @@
System.config({
defaultJSExtenions: true,
baseURL: 'public',
paths: {
'virtual-scroll': 'vendor/npm/virtual-scroll/src/index.js',
'mousetrap': 'vendor/npm/mousetrap/mousetrap.js',
'remarkable': 'vendor/npm/remarkable/dist/remarkable.js',
'tether': 'vendor/npm/tether/dist/js/tether.js',
'eventemitter3': 'vendor/npm/eventemitter3/index.js',
'tether-drop': 'vendor/npm/tether-drop/dist/js/drop.js',
'moment': 'vendor/moment.js',
"jquery": "vendor/jquery/dist/jquery.js",
'lodash-src': 'vendor/lodash/dist/lodash.js',
"lodash": 'app/core/lodash_extended.js',
"angular": "vendor/angular/angular.js",
"bootstrap": "vendor/bootstrap/bootstrap.js",
'angular-route': 'vendor/angular-route/angular-route.js',
'angular-sanitize': 'vendor/angular-sanitize/angular-sanitize.js',
"angular-ui": "vendor/angular-ui/ui-bootstrap-tpls.js",
"angular-strap": "vendor/angular-other/angular-strap.js",
"angular-dragdrop": "vendor/angular-native-dragdrop/draganddrop.js",
"angular-bindonce": "vendor/angular-bindonce/bindonce.js",
"spectrum": "vendor/spectrum.js",
"bootstrap-tagsinput": "vendor/tagsinput/bootstrap-tagsinput.js",
"jquery.flot": "vendor/flot/jquery.flot",
"jquery.flot.pie": "vendor/flot/jquery.flot.pie",
"jquery.flot.selection": "vendor/flot/jquery.flot.selection",
"jquery.flot.stack": "vendor/flot/jquery.flot.stack",
"jquery.flot.stackpercent": "vendor/flot/jquery.flot.stackpercent",
"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",
"d3": "vendor/d3/d3.js",
"jquery.flot.dashes": "vendor/flot/jquery.flot.dashes",
"twemoji": "vendor/npm/twemoji/2/twemoji.amd.js",
"ace": "vendor/npm/ace-builds/src-noconflict/ace",
},
packages: {
app: {
defaultExtension: 'js',
},
vendor: {
defaultExtension: 'js',
},
plugins: {
defaultExtension: 'js',
},
test: {
defaultExtension: 'js',
},
},
map: {
text: 'vendor/plugin-text/text.js',
css: 'app/core/utils/css_loader.js'
},
meta: {
'vendor/npm/virtual-scroll/src/indx.js': {
format: 'cjs',
exports: 'VirtualScroll',
},
'vendor/angular/angular.js': {
format: 'global',
deps: ['jquery'],
exports: 'angular',
},
'vendor/npm/eventemitter3/index.js': {
format: 'cjs',
exports: 'EventEmitter'
},
'vendor/npm/mousetrap/mousetrap.js': {
format: 'global',
exports: 'Mousetrap'
},
'vendor/npm/ace-builds/src-noconflict/ace.js': {
format: 'global',
exports: 'ace'
}
}
});

View File

@@ -1,27 +0,0 @@
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.

View File

@@ -1,57 +0,0 @@
# D3: Data-Driven Documents
<a href="https://d3js.org"><img src="https://d3js.org/logo.svg" align="left" hspace="10" vspace="6"></a>
**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
<script src="https://d3js.org/d3.v4.js"></script>
```
For the minified version:
```html
<script src="https://d3js.org/d3.v4.min.js"></script>
```
You can also use the standalone D3 microlibraries. For example, [d3-selection](https://github.com/d3/d3-selection):
```html
<script src="https://d3js.org/d3-selection.v1.js"></script>
```
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"));
```

File diff suppressed because one or more lines are too long

View File

@@ -1,3 +0,0 @@
// Import main D3.js module and combine it with another
var d3 = Object.assign({}, require('./d3.v4.min.js'), require('./d3-scale-chromatic.min.js'));
module.exports = d3;

File diff suppressed because one or more lines are too long