diff --git a/.gitignore b/.gitignore
index 92f3e46fb40a..d8a90ba458ed 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,6 @@
.DS_Store
node_modules
trash
-src/bower_components/K4D3
src/bower_components/elasticsearch
build
-target
\ No newline at end of file
+target
diff --git a/src/kibana/apps/discover/directives/timechart.js b/src/kibana/apps/discover/directives/timechart.js
index b96632b259ad..562a55f36712 100644
--- a/src/kibana/apps/discover/directives/timechart.js
+++ b/src/kibana/apps/discover/directives/timechart.js
@@ -1,7 +1,7 @@
define(function (require) {
var _ = require('lodash');
var $ = require('jquery');
- var k4 = require('components/k4d3/index');
+ var vislib = require('components/vislib/index');
var app = require('modules').get('apps/discover');
@@ -15,7 +15,7 @@ define(function (require) {
var init = function () {
// This elem should already have a height/width
- var myChart = new k4.Chart(elem[0], {
+ var myChart = new vislib.Chart(elem[0], {
});
diff --git a/src/kibana/apps/visualize/directives/visualize.js b/src/kibana/apps/visualize/directives/visualize.js
index d51d17448b5a..0e4742b43b24 100644
--- a/src/kibana/apps/visualize/directives/visualize.js
+++ b/src/kibana/apps/visualize/directives/visualize.js
@@ -1,5 +1,5 @@
define(function (require) {
- var k4d3 = require('components/k4d3/index');
+ var vislib = require('components/vislib/index');
var $ = require('jquery');
var _ = require('lodash');
var typeDefs = require('apps/visualize/saved_visualizations/_type_defs');
@@ -90,7 +90,7 @@ define(function (require) {
_.merge(params, vis.params);
_.defaults(params, typeDefinition.params);
- chart = new k4d3.Chart($visualize[0], params);
+ chart = new vislib.Chart($visualize[0], params);
// For each type of interaction, assign the the handler if the vis object has it
// otherwise use the typeDef, otherwise, do nothing.
diff --git a/src/kibana/components/K4D3 b/src/kibana/components/K4D3
deleted file mode 160000
index 65ca81b5f0f4..000000000000
--- a/src/kibana/components/K4D3
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 65ca81b5f0f4a905918d0a96a9ec554578231c52
diff --git a/src/kibana/components/vislib/core.js b/src/kibana/components/vislib/core.js
new file mode 100644
index 000000000000..89482086c476
--- /dev/null
+++ b/src/kibana/components/vislib/core.js
@@ -0,0 +1,24 @@
+/*
+ * Main module
+ * Accepts an html element and a config object.
+ * Calls all other K4 modules.
+ * Returns the charting function.
+ */
+
+define(function(require) {
+
+ return function(elem, args) {
+ var type = args.type,
+ charts = {
+ 'histogram': require('src/modules/histogram'),
+ 'line': require('src/modules/lineChart'),
+ 'area': require('src/modules/areaChart'),
+ };
+
+ if (typeof(charts[type]) !== 'function') { throw type + " is not a supported k4 function."; }
+
+ var chartFunc = charts[type](elem, args);
+
+ return chartFunc;
+ };
+});
diff --git a/src/kibana/components/vislib/index.js b/src/kibana/components/vislib/index.js
new file mode 100644
index 000000000000..1565409dcaca
--- /dev/null
+++ b/src/kibana/components/vislib/index.js
@@ -0,0 +1,13 @@
+define(function(require) {
+
+ var k4 = {
+ version: '0.0.0',
+ legend: require('src/modules/legend'),
+ Chart: require('src/core'),
+ histogram: require('src/modules/histogram'),
+ line: require('src/modules/lineChart'),
+ area: require('src/modules/areaChart'),
+ };
+
+ return k4;
+});
diff --git a/src/kibana/components/vislib/modules/areaChart.js b/src/kibana/components/vislib/modules/areaChart.js
new file mode 100644
index 000000000000..5f753508d252
--- /dev/null
+++ b/src/kibana/components/vislib/modules/areaChart.js
@@ -0,0 +1,574 @@
+define(function(require) {
+ 'use strict';
+
+ var d3 = require('lib/d3/d3'),
+ selectionFn = require('src/utils/selection'),
+ zeroInjection = require('src/utils/zeroInjection'),
+ legendFn = require('src/modules/legend'),
+ colorFn = require('src/utils/colorspace');
+
+ return function area(elem, args) {
+ if (typeof args === 'undefined') { args = {}; }
+
+ var chart = {},
+
+ /* ***** Chart Options ***** */
+ addLegend = args.addLegend || true,
+ addTooltip = args.addTooltip || true,
+ shareYAxis = args.shareYAxis || false,
+ /* ************************* */
+
+ /* ***** Chart Flags ******* */
+ destroyFlag = false,
+ /* ************************* */
+
+ /* ***** Chart Globals ******* */
+ dispatch = d3.dispatch('hover', 'click', 'mouseenter', 'mouseleave', 'mouseout', 'mouseover', 'brush'),
+ margin = args.margin || { top: 35, right: 15, bottom: 35, left: 50 },
+ $elem = $(elem), // cached jquery version of element
+ vis = d3.select(elem),
+ height,
+ width,
+ latestData,
+ chartwrapper,
+ selection,
+ selectors,
+ colDomain,
+ getColors,
+ legend,
+ tip,
+ allLayers,
+ color = d3.scale.category10(),
+ mousemove,
+ scrolltop,
+ allItms = false,
+ xValue = function(d, i) { return d.x; },
+ yValue = function(d, i) { return d.y; };
+ /* ************************* */
+
+ chart.render = function(data) {
+ // removes elements to redraw the chart on subsequent calls
+ d3.select(elem).selectAll('*').remove();
+
+ // store a copy of the data sent to render, so that it can be resent with .resize()
+ latestData = data;
+
+ if (destroyFlag || !elem) {
+ if (destroyFlag) { throw new Error('you destroyed this chart and then tried to use it again'); }
+ else { throw new Error('there is no element present'); }
+ }
+
+ // HTML div in which charts are contained
+ chartwrapper = d3.select(elem).append('div')
+ .attr('class', 'chartwrapper')
+ .style('height', $(elem).height() + 'px');
+
+ // Selection function - returns an array of DOM elements with data bound
+ selectors = selectionFn(chartwrapper[0][0], latestData);
+
+ try { selection = d3.selectAll(selectors); }
+ catch(error) { return; }
+
+ // get colDomain from chart obj
+ colDomain = chart.colorDomain(selection);
+
+ // color domain
+ getColors = colorFn(colDomain.length);
+ chart.colorObj = _.zipObject(colDomain, getColors);
+
+ // Chart options
+ if (addLegend) {
+ legend = legendFn(elem, selection, chart);
+ allItms = d3.select('.legendwrapper').selectAll('li.legends');
+ }
+
+ if (addTooltip) { tip = d3.select(elem).append('div').attr('class', 'k4tip'); }
+
+ try {
+ selection.each(function(d, i) {
+ var that = this;
+ chart.createAreaChart({'data': d, 'index': i, 'this': that});
+ });
+ } catch (err) {
+ if (err.message === 'chart too small') { chart.error(); }
+ console.group(err);
+ }
+
+ return chart;
+ };
+
+
+ /* Color domain */
+ chart.colorDomain = function(selection) {
+ var items = [];
+ selection.each(function(d) {
+ d.series.forEach(function(label) {
+ if (label.label) { items.push(label.label); }
+ else { items.push(d.yAxisLabel); }
+ });
+ });
+
+ items = _.uniq(items);
+ return items;
+ };
+
+ /* Function for global yAxis */
+ chart.globalYAxisFn = function(selection) {
+ var yArray = [];
+
+ selection.each(function(d) {
+ return d3.max(d.series, function(layer) {
+ return d3.max(layer.values, function(d) {
+ yArray.push(d.y);
+ });
+ });
+ });
+
+ return d3.max(yArray);
+ };
+
+ chart.getBounds = function(data) {
+ var bounds = [];
+
+ data.series.map(function(series) {
+ series.values.map(function(d, i) {
+ bounds.push({
+ x: xValue.call(series, d, i),
+ y: yValue.call(series, d, i)
+ });
+ });
+ });
+
+ return bounds;
+ };
+
+ chart.createAreaChart= function(args) {
+ if (typeof args === 'undefined') { args = {}; }
+
+ var data = args.data,
+ that = args.this,
+
+ // width, height, margins
+ elemWidth = parseInt(d3.select(that).style('width'), 10),
+ elemHeight = parseInt(d3.select(that).style('height'), 10),
+ width = elemWidth - margin.left - margin.right, // width of the parent element ???
+ height = elemHeight - margin.top - margin.bottom, // height of the parent element ???
+ seriesData = [],
+ xAxisLabel = data.xAxisLabel,
+ yAxisLabel = data.yAxisLabel,
+ label = data.label,
+ xAxisFormatter = data.xAxisFormatter,
+ yAxisFormatter = data.yAxisFormatter,
+ tooltipFormatter = data.tooltipFormatter,
+ brush;
+
+ data.series.map(function(series) {
+ seriesData.push(series);
+ });
+
+ var interpolate = "linear",
+ xTickScale = d3.scale.linear().clamp(true).domain([80, 300, 800]).range([0, 2, 4]),
+ xTickN = Math.floor(xTickScale(width)),
+ ytickScale = d3.scale.linear().clamp(true).domain([20, 40, 1000]).range([0, 1, 10]),
+ ytickN = Math.floor(ytickScale(height)),
+ xScale = d3.time.scale()
+ .domain(d3.extent(chart.getBounds(data), function(d) { return d.x; }))
+ .range([0, width]),
+ yScale = d3.scale.linear()
+ .domain([0, d3.max(chart.getBounds(data), function(d) { return d.y; })])
+ .range([height, 0]),
+ xAxis = d3.svg.axis().scale(xScale).ticks(xTickN).tickPadding(5).tickFormat(xAxisFormatter).orient("bottom"),
+ yAxis = d3.svg.axis().scale(yScale).ticks(ytickN).tickPadding(4).tickFormat(yAxisFormatter).orient("left"),
+ area = d3.svg.area().x(X).y0(height).y1(Y),
+ line = d3.svg.line().interpolate("linear").x(X).y(Y),
+ globalYAxis = chart.globalYAxisFn(selection);
+
+ // setting the y scale domain
+ if (shareYAxis) { yScale.domain([0, globalYAxis]).nice(ytickN); }
+ else {
+ yScale.domain([0, d3.max(chart.getBounds(data), function(d) { return d.y; })]).nice(ytickN);
+ }
+
+ var svg = d3.select(that).append("svg")
+ .attr("class", "canvas")
+ .attr("width", "100%")
+ .attr("height", "100%");
+
+ var g = svg.append("g")
+ .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
+
+ // background rect
+ g.append('rect')
+ .attr('class', 'chart-bkgd')
+ .attr('width', width)
+ .attr('height', height);
+
+ g.append("g")
+ .attr("class", "x axis")
+ .attr("transform", "translate(0," + height + ")")
+ .call(xAxis)
+ .selectAll('text')
+ .call(chart.tickText, width)
+ .on('mouseover', function(d) {
+ var hh = tip[0][0].scrollHeight;
+
+ mousemove = { left: d3.event.pageX, top: d3.event.pageY};
+ scrolltop = document.body.scrollTop;
+
+ d3.select(that).style('cursor', 'default');
+ return tip.datum(d).text(d)
+ .style('top', mousemove.top - scrolltop - hh/2 + 'px')
+ .style('left', mousemove.left + 20 + 'px')
+ .style('visibility', 'visible');
+ })
+ .on('mouseout', function() {
+ d3.select(that).classed('hover', false).style('stroke', null);
+ tip.style('visibility', 'hidden');
+ });
+
+ g.append("g")
+ .attr("class", "y axis")
+ .call(yAxis);
+
+ // Axis labels
+ g.append('text')
+ .attr('class', 'x-axis-label')
+ .attr('text-anchor', 'middle')
+ .attr('x', width / 2)
+ .attr('y', height + 30)
+ .text(xAxisLabel);
+
+ g.append('text')
+ .attr('class', 'y-axis-label')
+ .attr('text-anchor', 'middle')
+ .attr('x', -height / 2)
+ .attr('y', -40)
+ .attr('dy', '.75em')
+ .attr('transform', 'rotate(-90)')
+ .text(yAxisLabel);
+
+ // Chart title
+ g.append('text')
+ .attr('class', 'charts-label')
+ .attr('text-anchor', 'middle')
+ .attr('x', width / 2)
+ .attr('y', -10)
+ .text(label)
+ .call(chart.tickText, width)
+ .on('mouseover', function(d) {
+ var hh = tip[0][0].scrollHeight;
+
+ mousemove = { left: d3.event.pageX, top: d3.event.pageY};
+ scrolltop = document.body.scrollTop;
+
+ d3.select(that).style('cursor', 'default');
+ return tip.text(d.label)
+ .style('top', mousemove.top - scrolltop - hh/2 + 'px')
+ .style('left', mousemove.left + 20 + 'px')
+ .style('visibility', 'visible');
+ })
+ .on('mouseout', function() {
+ d3.select(that).classed('hover', false).style('stroke', null);
+ tip.style('visibility', 'hidden');
+ });
+
+ var lines = g.selectAll(".lines")
+ .data(seriesData)
+ .enter().append("g")
+ .attr("class", "lines");
+
+ lines.append("path")
+ .attr("d", function(d) { return line(d.values); })
+ .attr("fill", "none")
+ .style("stroke", function(d) { return d.label ? chart.colorObj[d.label] : chart.colorObj[yAxisLabel]; })
+ .style("stroke-width", "3px");
+
+ lines.append("path")
+ .attr("d", function(d) { return area(d.values); })
+ .style("fill", function(d) {
+ return d.label ? chart.colorObj[d.label] : chart.colorObj[yAxisLabel];
+ })
+ .style("stroke", function(d) { return d.label ? chart.colorObj[d.label] : chart.colorObj[yAxisLabel]; })
+ .style("stroke-width", "3px")
+ .style("opacity", 0.5);
+
+ var layer = g.selectAll(".layer")
+ .data(seriesData)
+ .enter().append("g")
+ .attr("class", function(d) { return "r" + d.label; })
+ .attr("stroke", function(d) { return d.label ? chart.colorObj[d.label] : chart.colorObj[yAxisLabel]; });
+
+ var circle = layer.selectAll(".points")
+ .data(function(d) { return d.values; })
+ .enter().append("circle")
+ .attr("class", "points")
+ .attr("cx", function(d) { return xScale(d.x); })
+ .attr("cy", function(d) { return yScale(d.y); })
+ /* css styling */
+ .attr("r", 8)
+ .attr("fill", "#ffffff")
+ .attr("stroke-width", 3.5)
+ .attr("opacity", 0)
+ .on("mouseover", function(d, i) {
+ var point = d3.select(this);
+ point.attr("opacity", 0.5);
+
+ d3.select(this)
+ .classed('hover', true)
+ .style('stroke', '#333')
+ .style('cursor', 'pointer');
+
+ dispatch.hover({
+ value: yValue(d, i),
+ point: d,
+ pointIndex: i,
+ series: data.series,
+ config: args,
+ data: latestData,
+ e: d3.event
+ });
+ d3.event.stopPropagation();
+ })
+ .on("mousemove", function(d) {
+ var datum, hh = tip[0][0].scrollHeight;
+
+ mousemove = { left: d3.event.pageX, top: d3.event.pageY};
+ scrolltop = document.body.scrollTop;
+
+ if (typeof d.label !== 'undefined') {
+ datum = { label: d.label, x: d.x, y: d.y };
+ } else {
+ datum = { x: d.x, y: d.y };
+ }
+
+ tip.datum(datum)
+ .text(tooltipFormatter)
+ .style('top', mousemove.top - scrolltop - hh/2 + 'px')
+ .style('left', mousemove.left + 20 + 'px')
+ .style('visibility', 'visible');
+ })
+ .on('click', function (d, i) {
+ dispatch.click({
+ value: yValue(d, i),
+ point: d,
+ pointIndex: i,
+ series: data.series,
+ config: args,
+ data: latestData,
+ e: d3.event
+ });
+ d3.event.stopPropagation();
+ })
+ .on("mouseout", function() {
+ var point = d3.select(this);
+ point.attr("opacity", 0);
+ tip.style('visibility', 'hidden');
+ });
+
+ if (addTooltip) {
+ // **** hilite series on hover
+ allLayers = vis.select('path');
+ //var allLayers = svg.selectAll('.rect');
+ lines.on(
+ 'mouseover', function (d, i) {
+
+ // hilite chart layer
+ allLayers.style('opacity', 0.3);
+ var layerClass = '.' + d3.select(this).node().classList[2],
+ mylayer = vis.selectAll(layerClass).style('opacity', 1);
+
+ // stroke this rect
+ d3.select(this)
+ .classed('hover', true)
+ .style('stroke', '#333')
+ .style('cursor', 'pointer');
+
+ // hilite legend item
+ if (allItms) {
+ allItms.style('opacity', 0.3);
+ var itm = d3.select('.legendwrapper').select('li.legends' + layerClass).style('opacity', 1);
+ }
+ });
+ }
+
+ /* Event Selection: BRUSH */
+ brush = d3.svg.brush()
+ .x(xScale)
+ .on('brushend', function brushend() {
+ var selected, start, lastVal, end, selectedRange;
+
+ // selected is used to determine the range for ordinal scales
+ selected = xScale.domain().filter(function(d) {
+ return (brush.extent()[0] <= xScale(d)) && (xScale(d) <= brush.extent()[1]);
+ });
+
+ start = selected[0];
+ lastVal = selected.length - 1;
+ end = selected[lastVal];
+ selectedRange = [start, end];
+
+ return brush.extent()[0] instanceof Date ?
+ dispatch.brush({
+ range: brush.extent(),
+ config: args,
+ e: d3.event,
+ data: latestData
+ }) :
+ dispatch.brush({
+ range: selectedRange,
+ config: args,
+ e: d3.event,
+ data: latestData
+ });
+ });
+
+ if (dispatch.on('brush')) {
+ g.append('g')
+ .attr('class', 'brush')
+ .call(brush)
+ .selectAll('rect')
+ .attr('height', height);
+ }
+ /* ************************** */
+
+ lines.on("mouseout", function() {
+ allLayers.style('opacity', 1);
+ allItms.style('opacity', 1);
+ });
+
+ function X(d) { return xScale(d.x); }
+ function Y(d) { return yScale(d.y); }
+
+ return svg;
+ };
+
+ // getters / setters
+ chart.resize = _.debounce(function() {
+ if (latestData) {
+ chart.render(latestData);
+ }
+ }, 200);
+
+ // enable auto-resize
+ var prevSize;
+ (function checkSize() {
+ var size = $elem.width() + ':' + $elem.height();
+ if (prevSize !== size) {
+ chart.resize();
+ }
+ prevSize = size;
+ setTimeout(checkSize, 250);
+ }());
+
+ /* Function for truncating x axis tick labels */
+ chart.tickText = function (text, width) {
+ var n = text[0].length,
+ maxw = width / n * 0.9,
+ tickn = Math.floor(maxw);
+ text.each(function () {
+ var text = d3.select(this),
+ length = this.getComputedTextLength(),
+ tspan = text.text();
+ if ( length > maxw ) {
+ var str = text.text(),
+ avg = length / str.length,
+ end = Math.floor(maxw / avg);
+ str = str.substr(0, end) + '...';
+ tspan = text.text(str);
+ }
+ });
+ };
+
+ chart.margin = function(_) {
+ if (!arguments.length) { return margin; }
+
+ margin.top = typeof _.top !== 'undefined' ? _.top : margin.top;
+ margin.right = typeof _.right !== 'undefined' ? _.right : margin.right;
+ margin.bottom = typeof _.bottom !== 'undefined' ? _.bottom : margin.bottom;
+ margin.left = typeof _.left !== 'undefined' ? _.left : margin.left;
+
+ return chart;
+ };
+
+ chart.width = function(_) {
+ if (!arguments.length) { return width; }
+ width = _;
+ return chart;
+ };
+
+ chart.height = function(_) {
+ if (!arguments.length) { return height; }
+ height = _;
+ return chart;
+ };
+
+ chart.x = function(_) {
+ if (!arguments.length) { return xValue; }
+ xValue = _;
+ return chart;
+ };
+
+ chart.y = function(_) {
+ if (!arguments.length) { return yValue; }
+ yValue = _;
+ return chart;
+ };
+
+ chart.interpolate = function (_) {
+ if (!arguments.length) { return interpolate; }
+ interpolate = _;
+ return chart;
+ };
+
+ chart.error = function() {
+ // Removes the legend container
+ d3.select(elem).selectAll('*').remove();
+
+ var errorWrapper = d3.select(elem).append('div')
+ .attr('class', 'errorWrapper')
+ .style('height', function() { return $(elem).height() + 'px'; })
+ .style('text-align', 'center');
+
+ errorWrapper.append('p')
+ .style('font-size', '18px')
+ .style('margin-top', function() { return $(elem).height() / 3 + 'px'; })
+ .style('line-height', '18px')
+ .text('The container is too small for this chart.');
+ return chart;
+ };
+
+ chart.off = function(event) {
+ dispatch.on(event, null);
+ return chart;
+ };
+
+ chart.destroy = function(_) {
+ /*
+ Destroys all charts associated with the parent element
+ if the argument passed is true. By default the argument
+ is true.
+ */
+ if (!arguments.length || _) {
+ destroyFlag = _ || true;
+
+ // Removing chart and all elements associated with it
+ d3.select(elem).selectAll('*').remove();
+
+ // Cleaning up event listeners
+ chart.off('click');
+ chart.off('hover');
+ chart.off('brush');
+ d3.select(window).on('resize', null);
+ }
+ destroyFlag = _;
+ return chart;
+ };
+
+ chart.dispatch = dispatch;
+
+ d3.rebind(chart, dispatch, 'on');
+ d3.select(window).on('resize', chart.resize);
+
+ return chart;
+ };
+});
diff --git a/src/kibana/components/vislib/modules/histogram.js b/src/kibana/components/vislib/modules/histogram.js
new file mode 100644
index 000000000000..c2520d79500b
--- /dev/null
+++ b/src/kibana/components/vislib/modules/histogram.js
@@ -0,0 +1,717 @@
+define(function(require) {
+ 'use strict';
+
+ var d3 = require('lib/d3/d3'),
+ _ = require('lib/lodash/dist/lodash'),
+ selectionFn = require('src/utils/selection'),
+ zeroInjection = require('src/utils/zeroInjection'),
+ legendFn = require('src/modules/legend'),
+ getColor = require('src/utils/colorspace');
+
+ // Dynamically adds css file
+ require('lib/require-css/css!../css/k4.d3');
+
+ return function histogram(elem, args) {
+ if (typeof args === 'undefined') { args = {}; }
+
+ var chart = {},
+
+ /* ******* Error Handling ******** */
+ destroyFlag = false,
+ /* ***************************** */
+
+ /* ******* Chart options ******* */
+ shareYAxis = args.shareYAxis || false,
+ addLegend = args.addLegend || false,
+ addTooltip = args.addTooltip || false,
+ /* ***************************** */
+
+ /* ******** Chart methods ******* */
+ margin = args.margin || { top: 35, right: 15, bottom: 35, left: 60 },
+ offset = args.offset || 'zero',
+ //color = args.color ? d3.scale.linear().range(args.color) : d3.scale.ordinal().range(['#e24700', '#f9e593']),
+ dispatch = d3.dispatch('hover', 'click', 'mouseenter', 'mouseleave', 'mouseout', 'mouseover', 'brush'),
+ getY = function(d, i) { return d.y; },
+ getX = function(d, i) { return d.x; },
+ /* ***************************** */
+ colDomain,
+
+ /* Parameters defined within the chart.render function */
+ stack = d3.layout.stack().values(function(d) { return d.values; }),
+ $elem = $(elem), // cached jquery version of element
+ vis = d3.select(elem), allLayers,
+ globalYAxis, mousemove, scrolltop,
+ selection, elemWidth, elemHeight, width, height,
+ layers, n, yGroupMax, yStackMax, keys,
+ xScale, yScale, xAxis, yAxis,
+ layer, bars, legend, tip,
+ xAxisFormatter, yAxisFormatter, tooltipFormatter, dataLength, latestData, chartwrapper,
+ allItms = false;
+
+ /* ***************************** */
+
+ chart.getColors = function(selection) {
+ try {
+ if (!selection) { throw new Error("No valid selection"); }
+
+ var colorDomain = chart.getColorDomain(selection),
+ lengthOfColorDomain = colorDomain.length,
+ colorArray = getColor(lengthOfColorDomain),
+ colorDict;
+
+ colorDict = chart.getColorDict(colorDomain, colorArray);
+
+ return colorDict;
+ }
+ catch(error) {
+ console.group("chart.getColors: " + error);
+ }
+ };
+
+ chart.getColorDict = function(colorDomain, colorArray) {
+ try {
+ if (!colorDomain) { throw new Error("No valid colorDomain"); }
+ if (!colorArray) { throw new Error("No valid colorArray"); }
+
+ var colorDict = _.zipObject(colorDomain, colorArray);
+
+ return colorDict;
+ }
+ catch(error) {
+ console.group("chart.getColorDict" + error);
+ }
+ };
+
+ /* Color domain */
+ chart.getColorDomain = function(selection) {
+ try {
+ if (!selection) { throw new Error("No valid selection"); }
+
+ var items = [];
+
+ selection.each(function(d) {
+ d.series.forEach(function(label) {
+ if (label.label) { items.push(label.label); }
+ else { items.push(d.yAxisLabel); }
+ });
+ });
+
+ items = _.uniq(items);
+ return items;
+ }
+ catch(error) {
+ console.group("chart.getColorDomain: " + error);
+ }
+ };
+
+ chart.render = function render(data) {
+ // store a copy of the data sent to render, so that it can be resent with .resize()
+ latestData = data;
+
+ // removes elements to redraw the chart on subsequent calls
+ d3.select(elem).selectAll('*').remove();
+
+ if (destroyFlag || !elem) {
+ if (destroyFlag) { throw new Error('you destroyed this chart and then tried to use it again'); }
+ else { throw new Error('there is no element present'); }
+ }
+
+ // HTML div in which charts are contained
+ chartwrapper = d3.select(elem).append('div')
+ .attr('class', 'chartwrapper')
+ .style('height', $(elem).height() + 'px');
+
+ // Selection function - returns an array of DOM elements with data bound
+ try { selection = d3.selectAll(selectionFn(chartwrapper[0][0], latestData)); }
+ catch(error) { return; }
+
+ // get colDomain from chart obj
+
+ var colors = chart.getColors(selection);
+
+ // Chart options
+ if (addLegend) {
+ legend = legendFn(elem, colors, chart);
+ allItms = d3.select('.legendwrapper').selectAll('li.legends');
+ }
+ if (addTooltip) { tip = d3.select(elem).append('div').attr('class','k4tip'); }
+
+ try {
+ selection.each(function (d, i) {
+ var that = this;
+ chart.iterateSelection({
+ 'data': d,
+ 'index': i,
+ 'this': that,
+ 'colors': colors
+ });
+ });
+ } catch (err) {
+ if (err.message === 'chart too small') { chart.error(); }
+ console.group(err);
+ }
+
+ return chart;
+ };
+
+ /* Color domain */
+ chart.colorDomain = function(selection) {
+ var items = [];
+ selection.each(function(d) {
+ d.series.forEach(function(label) {
+ if (label.label) { items.push(label.label); }
+ else { items.push(d.yAxisLabel); }
+ });
+ });
+
+ items = _.uniq(items);
+ return items;
+ };
+
+ /* Function for global yAxis */
+ chart.globalYAxisFn = function(selection) {
+ var yArray = [];
+ selection.each(function(d) {
+ d = zeroInjection(d);
+ return d3.max(stack(d.series), function(layer) {
+ return d3.max(layer.values, function(d) {
+ if (offset === 'group') { yArray.push(d.y); }
+ else { yArray.push(d.y0 + d.y); }
+ });
+ });
+ });
+ return d3.max(yArray);
+ };
+
+ chart.iterateSelection = function(args) {
+ if (typeof args === 'undefined') { args = {}; }
+
+ var data = zeroInjection(args.data),
+ that = args.this,
+ colors = args.colors,
+ brush;
+
+ // Data formatters
+ xAxisFormatter = data.xAxisFormatter || function(v) { return v; };
+ yAxisFormatter = data.yAxisFormatter;
+ tooltipFormatter = data.tooltipFormatter;
+
+ // adds the label value to each data point
+ // within the values array for displaying in the tooltip
+ data.series.forEach(function(d) {
+ d.values.forEach(function(e) {
+ var label = d.label ? d.label : data.yAxisLabel;
+ e.label = label;
+ });
+ });
+
+ // width, height, margins
+ elemWidth = parseInt(d3.select(that).style('width'), 10);
+ elemHeight = parseInt(d3.select(that).style('height'), 10);
+ width = elemWidth - margin.left - margin.right; // width of the parent element ???
+ height = elemHeight - margin.top - margin.bottom; // height of the parent element ???
+
+ // preparing the data and scales
+ dataLength = data.series[0].values.length;
+ stack.offset(offset);
+ layers = stack(data.series);
+ n = layers.length; // number of layers
+ globalYAxis = chart.globalYAxisFn(selection);
+ yGroupMax = d3.max(layers, function(layer) {
+ return d3.max(layer.values, function(d) { return d.y; });
+ });
+ yStackMax = d3.max(layers, function(layer) {
+ return d3.max(layer.values, function(d) { return d.y0 + d.y; });
+ });
+ keys = d3.set(layers[0].values.map(function(d) { return d.x; })).values();
+
+ /* Error Handler that prevents a chart from being rendered when
+ there are too many data points for the width of the container. */
+ if (width/dataLength <= 4) { throw new Error('chart too small'); }
+
+ /* ************************** DATE FORMAT *************************************************** */
+ if ( data.ordered !== undefined && data.ordered.date !== undefined) {
+ var milsInterval = data.ordered.interval,
+ testInterval, dateoffset;
+
+ if (milsInterval < 2419200000) { testInterval = 'week'; dateoffset = (milsInterval / 604800000); }
+ if (milsInterval < 604800000) { testInterval = 'day'; dateoffset = (milsInterval / 86400000); }
+ if (milsInterval < 86400000) { testInterval = 'hour'; dateoffset = (milsInterval / 3600000); }
+ if (milsInterval < 3600000) { testInterval = 'minute'; dateoffset = (milsInterval / 60000); }
+ if (milsInterval < 60000) { testInterval = 'second'; dateoffset = (milsInterval / 1000); }
+
+ // apply interval to last date in keys
+ var maxIntervalOffset = d3.time[testInterval].offset( new Date(data.ordered.max), dateoffset );
+ var minIntervalOffset = d3.time[testInterval].offset( new Date(data.ordered.min), -dateoffset );
+
+ xScale = d3.time.scale().domain([minIntervalOffset, maxIntervalOffset]).range([0, width]);
+ } else {
+ xScale = d3.scale.ordinal().domain(keys).rangeRoundBands([0, width], 0.1);
+ }
+ /* ******************************************************************************************** */
+
+ yScale = d3.scale.linear().range([height, 0]);
+ var xTickScale = d3.scale.linear().clamp(true).domain([80, 300, 800]).range([0, 2, 4]);
+ var xTicks = Math.floor(xTickScale(width));
+
+ xAxis = d3.svg.axis()
+ .scale(xScale)
+ .ticks(xTicks)
+ .tickPadding(5)
+ .tickFormat(xAxisFormatter)
+ .orient('bottom');
+
+ // tickScale uses height to get tickN value for ticks() and nice()
+ var tickScale = d3.scale.linear().clamp(true).domain([20, 40, 1000]).range([0, 1, 10]),
+ tickN = Math.floor(tickScale(height));
+
+ yAxis = d3.svg.axis()
+ .scale(yScale)
+ .ticks(tickN)
+ .tickPadding(4)
+ .tickFormat(yAxisFormatter)
+ .orient('left');
+
+ // setting the y scale domain
+ if (shareYAxis) { yScale.domain([0, globalYAxis]).nice(tickN); }
+ else {
+ if (offset === 'group') { yScale.domain([0, yGroupMax]).nice(tickN); }
+ else { yScale.domain([0, yStackMax]).nice(tickN); }
+ }
+
+ // maps color domain to range
+ //color.domain([0, colDomain.length - 1]);
+
+ // canvas
+ var svg = d3.select(that).append('svg')
+ .attr('class', 'canvas')
+ .attr('width', '100%')
+ .attr('height', '100%')
+ .append('g')
+ .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
+
+ // background rect
+ svg.append('rect')
+ .attr('class', 'chart-bkgd')
+ .attr('width', width)
+ .attr('height', height);
+
+ // x axis
+ svg.append('g')
+ .attr('class', 'x axis')
+ .attr('transform', 'translate(0,' + height + ')')
+ .call(xAxis)
+ .selectAll('text')
+ .call(chart.tickText, width)
+ .on('mouseover', function(d) {
+ var hh = tip[0][0].scrollHeight;
+
+ mousemove = { left: d3.event.pageX, top: d3.event.pageY};
+ scrolltop = document.body.scrollTop;
+
+ d3.select(that).style('cursor', 'default');
+ return tip.datum(d).text(d)
+ .style('top', mousemove.top - scrolltop - hh/2 + 'px')
+ .style('left', mousemove.left + 20 + 'px')
+ .style('visibility', 'visible');
+ })
+ .on('mouseout', function() {
+ d3.select(that).classed('hover', false).style('stroke', null);
+ tip.style('visibility', 'hidden');
+ });
+
+ // y axis
+ svg.append('g')
+ .attr('class', 'y axis')
+ .call(yAxis);
+
+ // Axis labels
+ svg.append('text')
+ .attr('class', 'x-axis-label')
+ .attr('text-anchor', 'middle')
+ .attr('x', width / 2)
+ .attr('y', height + 30)
+ .text(data.xAxisLabel);
+
+ svg.append('text')
+ .attr('class', 'y-axis-label')
+ .attr('text-anchor', 'middle')
+ .attr('x', -height / 2)
+ .attr('y', function() {
+ // get width of y axis group for label offset
+ var ww = svg.select('.y.axis').node().getBBox();
+ return -1 * ww.width - 14;
+ })
+ .attr('dy', '.75em')
+ .attr('transform', 'rotate(-90)')
+ .text(data.yAxisLabel);
+
+ // Chart title
+ svg.append('text')
+ .attr('class', 'charts-label')
+ .attr('text-anchor', 'middle')
+ .attr('x', width / 2)
+ .attr('y', -10)
+ .text(data.label)
+ .call(chart.tickText, width)
+ .on('mouseover', function(d) {
+ var hh = tip[0][0].scrollHeight;
+
+ mousemove = { left: d3.event.pageX, top: d3.event.pageY};
+ scrolltop = document.body.scrollTop;
+
+ d3.select(that).style('cursor', 'default');
+ return tip.text(d.label)
+ .style('top', mousemove.top - scrolltop - hh/2 + 'px')
+ .style('left', mousemove.left + 20 + 'px')
+ .style('visibility', 'visible');
+ })
+ .on('mouseout', function() {
+ d3.select(that).classed('hover', false).style('stroke', null);
+ tip.style('visibility', 'hidden');
+ });
+
+ /* Event Selection: BRUSH */
+ brush = d3.svg.brush()
+ .x(xScale)
+ .on('brushend', function brushend() {
+ var selected, start, lastVal, end, selectedRange;
+
+ // selected is used to determine the range for ordinal scales
+ selected = xScale.domain().filter(function(d) {
+ return (brush.extent()[0] <= xScale(d)) && (xScale(d) <= brush.extent()[1]);
+ });
+
+ start = selected[0];
+ lastVal = selected.length - 1;
+ end = selected[lastVal];
+ selectedRange = [start, end];
+
+ return brush.extent()[0] instanceof Date ?
+ dispatch.brush({
+ range: brush.extent(),
+ config: args,
+ e: d3.event,
+ data: latestData
+ }) :
+ dispatch.brush({
+ range: selectedRange,
+ config: args,
+ e: d3.event,
+ data: latestData
+ });
+ });
+
+ if (dispatch.on('brush')) {
+ svg.append('g')
+ .attr('class', 'brush')
+ .call(brush)
+ .selectAll('rect')
+ .attr('height', height);
+ }
+ /* ************************** */
+
+ // layers
+ layer = svg.selectAll('.layer')
+ .data(function (d) { return d.series; })
+ .enter().append('g')
+ .attr('class', function (d) {
+ if (!d.label) {
+ return colors[data.yAxisLabel];
+ } else {
+ return colors[d.label];
+ }
+
+ })
+ .style('fill', function (d) {
+ if (!d.label) {
+ return colors[data.yAxisLabel];
+ } else {
+ return colors[d.label];
+ }
+
+ });
+
+ bars = layer.selectAll('rect')
+ .data(function (d) { return d.values; });
+
+ // enter
+ bars.enter().append('rect')
+ .attr('class', function (d, i) {
+ // Regex to remove ., /, white space, *, ;, (, ), :, , from labels.
+ var label = d.label !== undefined ?
+ d.label.replace(/[.]+|[/]+|[\s]+|[*]+|[;]+|[(]+|[)]+|[:]+|[,]+/g, '') :
+ data.yAxisLabel.replace(/[.]+|[/]+|[\s]+|[*]+|[;]+|[(]+|[)]+|[:]+|[,]+/g, '');
+ return 'rl rl-' + label;
+ })
+ .on('mouseover', function(d, i) {
+ d3.select(this)
+ .classed('hover', true)
+ .style('stroke', '#333')
+ .style('cursor', 'pointer');
+
+ dispatch.hover({
+ value: getY(d, i),
+ point: d,
+ pointIndex: i,
+ series: data.series,
+ config: args,
+ data: latestData,
+ e: d3.event
+ });
+
+ d3.event.stopPropagation();
+ })
+ .on('click', function (d, i) {
+ dispatch.click({
+ value: getY(d, i),
+ point: d,
+ pointIndex: i,
+ series: data.series,
+ config: args,
+ data: latestData,
+ e: d3.event
+ });
+
+ d3.event.stopPropagation();
+ });
+
+ if (addTooltip) {
+ // **** hilite series on hover
+ allLayers = vis.selectAll('rect');
+ var itm, itmRect, ht, ot, legendwrap = d3.select('.legendwrapper');
+ //var allLayers = svg.selectAll('.rect');
+ bars.on('mouseover', function (d) {
+
+ // hilite chart layer
+ allLayers.style('opacity', 0.3);
+ var layerClass = '.rl-' + d.label.replace(/[.]+|[/]+|[\s]+|[*]+|[;]+|[(]+|[)]+|[:]+|[,]+/g, ''),
+ mylayer = vis.selectAll(layerClass).style('opacity', 1);
+
+ // stroke this rect
+ d3.select(this)
+ .classed('hover', true)
+ .style('stroke', '#333');
+// .style('cursor', 'pointer');
+
+ // hilite legend item
+ if ( allItms ) {
+ allItms.style('opacity', 0.3);
+ var itm = d3.select('.legendwrapper')
+ .select(layerClass)
+ .style('opacity', 1);
+ }
+
+ // scroll legend
+ if ( chart.headerOpen === true ) {
+ ht = legendwrap.node().getBoundingClientRect().height;
+ if ( itm.node() ) {
+ ot = itm.node().offsetTop;
+ legendwrap.node().scrollTop = 35+ot-ht;
+ } else {
+ legendwrap.node().scrollTop = 0;
+ }
+ }
+
+ });
+
+ bars.on('mousemove', function(d) {
+ var datum, hh = tip[0][0].scrollHeight;
+
+ mousemove = { left: d3.event.pageX, top: d3.event.pageY};
+ scrolltop = document.body.scrollTop;
+
+ if (typeof d.label !== 'undefined') {
+ datum = { label: d.label, x: d.x, y: d.y };
+ } else {
+ datum = { x: d.x, y: d.y };
+ }
+
+ tip.datum(datum)
+ .text(tooltipFormatter)
+ .style('top', mousemove.top - scrolltop - hh/2 + 'px')
+ .style('left', mousemove.left + 20 + 'px')
+ .style('visibility', 'visible');
+ });
+
+ bars.on('mouseout', function() {
+ d3.select(this).classed('hover', false).style('stroke', null);
+ tip.style('visibility', 'hidden');
+ allLayers.style('opacity', 1);
+ allItms.style('opacity', 1);
+ });
+
+ }
+
+ // update
+ switch (offset) {
+ case 'group':
+ bars
+ // ****** DATE FORMAT *******
+ .attr('x', function (d, i, j) {
+ return data.ordered === undefined || !data.ordered.date ?
+ xScale(d.x) + xScale.rangeBand() / n * j :
+ xScale(d.x) - (width / (keys.length + 1) / 2);
+ })
+ .attr('width', function() {
+ return data.ordered === undefined || !data.ordered.date ?
+ xScale.rangeBand() / n : width / (keys.length + 1) / n;
+ })
+ .attr('y', function (d) { return yScale(d.y); })
+ .attr('height', function (d) { return height - yScale(d.y); });
+ break;
+
+ default:
+ bars
+ .attr('x', function (d) { return xScale(d.x); })
+ .attr('width', function() {
+ return data.ordered === undefined || !data.ordered.date ?
+ xScale.rangeBand() :
+ xScale(data.ordered.min + data.ordered.interval) - xScale(data.ordered.min) - 2;
+ })
+ .attr('y', function (d) { return yScale(d.y0 + d.y); })
+ .attr('height', function (d) { return yScale(d.y0) - yScale(d.y0 + d.y); });
+ break;
+ }
+
+ // exit
+ bars.exit().remove();
+
+ return svg;
+ };
+
+
+ /* Function for truncating x axis tick labels */
+ chart.tickText = function (text, width) {
+ var n = text[0].length,
+ maxw = width / n * 0.9,
+ tickn = Math.floor(maxw);
+ text.each(function () {
+ var text = d3.select(this),
+ length = this.getComputedTextLength(),
+ tspan = text.text();
+ if ( length > maxw ) {
+ var str = text.text(),
+ avg = length / str.length,
+ end = Math.floor(maxw / avg);
+ str = str.substr(0, end) + '...';
+ tspan = text.text(str);
+ }
+ });
+ };
+
+ // getters / setters
+ chart.resize = _.debounce(function() {
+ if (latestData) {
+ chart.render(latestData);
+ }
+ }, 200);
+
+ // enable auto-resize
+ var prevSize;
+ (function checkSize() {
+ var size = $elem.width() + ':' + $elem.height();
+ if (prevSize !== size) {
+ chart.resize();
+ }
+ prevSize = size;
+ setTimeout(checkSize, 250);
+ }());
+
+ chart.dispatch = dispatch;
+
+ chart.margin = function(_) {
+ if (!arguments.length) { return margin; }
+ margin.top = typeof _.top !== 'undefined' ? _.top : margin.top;
+ margin.right = typeof _.right !== 'undefined' ? _.right : margin.right;
+ margin.bottom = typeof _.bottom !== 'undefined' ? _.bottom : margin.bottom;
+ margin.left = typeof _.left !== 'undefined' ? _.left : margin.left;
+ return chart;
+ };
+
+ chart.y = function(_) {
+ if (!arguments.length) { return getY; }
+ getY = _;
+ return chart;
+ };
+
+ chart.x = function(_) {
+ if (!arguments.length) { return getX; }
+ getX = _;
+ return chart;
+ };
+
+ chart.offset = function(_) {
+ if (!arguments.length) { return offset; }
+ offset = _;
+ return chart;
+ };
+
+ chart.width = function(_) {
+ if (!arguments.length) { return elemWidth; }
+ elemWidth = _;
+ return chart;
+ };
+
+ chart.height = function(_) {
+ if (!arguments.length) { return elemHeight; }
+ elemHeight = _;
+ return chart;
+ };
+
+ //chart.color = function(_) {
+ // if (!arguments.length) { return color; }
+ // color = d3.scale.linear().range(_);
+ // return chart;
+ //};
+
+ chart.off = function(event) {
+ dispatch.on(event, null);
+ return chart;
+ };
+
+ chart.destroy = function(_) {
+ /*
+ Destroys all charts associated with the parent element
+ if the argument passed is true. By default the argument
+ is true.
+ */
+ if (!arguments.length || _) {
+ destroyFlag = _ || true;
+
+ // Removing chart and all elements associated with it
+ d3.select(elem).selectAll('*').remove();
+
+ // Cleaning up event listeners
+ chart.off('click');
+ chart.off('hover');
+ chart.off('brush');
+ d3.select(window).on('resize', null);
+ }
+ destroyFlag = _;
+ return chart;
+ };
+
+ chart.error = function() {
+ // Removes the legend container
+ d3.select(elem).selectAll('*').remove();
+
+ var errorWrapper = d3.select(elem).append('div')
+ .attr('class', 'errorWrapper')
+ .style('height', function() { return $(elem).height() + 'px'; })
+ .style('text-align', 'center');
+
+ errorWrapper.append('p')
+ .style('font-size', '18px')
+ .style('margin-top', function() { return $(elem).height() / 3 + 'px'; })
+ .style('line-height', '18px')
+ .text('The container is too small for this chart.');
+ return chart;
+ };
+
+ d3.rebind(chart, dispatch, 'on');
+ d3.select(window).on('resize', chart.resize);
+
+ return chart;
+ };
+});
diff --git a/src/kibana/components/vislib/modules/legend.js b/src/kibana/components/vislib/modules/legend.js
new file mode 100644
index 000000000000..977e559d02d1
--- /dev/null
+++ b/src/kibana/components/vislib/modules/legend.js
@@ -0,0 +1,135 @@
+define(function(require) {
+ 'use strict';
+
+ var d3 = require('lib/d3/d3');
+
+ function legend (elem, colors, chart) {
+
+ var vis = d3.select(elem),
+ chartWrap = vis.select('.chartwrapper'),
+ legendWrap = vis.append('div').attr('class', 'legendwrapper'),
+ allLayers = chartWrap.selectAll('rect'),
+ header = legendWrap.append('div').attr('class', 'header'),
+ list,
+ itm,
+ allItms;
+
+ header
+ .append('div')
+ .attr('class', 'column-labels')
+ .html(';');
+
+ list = legendWrap.append('ul')
+ .attr('class', 'legend-ul')
+ .selectAll('li')
+ .data(d3.keys(colors))
+ .enter().append('li')
+ .attr('class', function (d) {
+ var label = d !== undefined ?
+ d.replace(/[.]+|[/]+|[\s]+|[#]+|[*]+|[;]+|[(]+|[)]+|[:]+|[,]+/g, '') : undefined;
+
+ return 'legends rl rl-' + label;
+ })
+ .html(function(d) {
+ var str = '' + d + '';
+
+ return str;
+ });
+
+ allItms = vis.selectAll('li.legends').style('cursor', 'pointer');
+
+ list
+ .on('mouseover', function (d) {
+ // chart layer
+ // Regex to remove ., /, white space, *, ;, (, ), :, , from labels.
+ var label = d !== undefined ?
+ d.replace(/[.]+|[/]+|[\s]+|[#]+|[*]+|[;]+|[(]+|[)]+|[:]+|[,]+/g, '') : undefined,
+ layerClass = '.rl-' + label;
+
+ allLayers = vis.selectAll('.rl').style('opacity', 0.3);
+
+ vis.selectAll(layerClass).style('opacity', 1);
+
+ // legend list
+ allItms.style('opacity', 0.3);
+ itm = d3.select(this).style('opacity', 1);
+ })
+ .on('mouseout', function () {
+ allLayers = vis.selectAll('.rl').style('opacity', 1);
+ allItms.style('opacity', 1);
+ });
+
+ // toggle header
+ function closeheader(e) {
+ var vwidth = vis.style('width'),
+ legwidth = +vwidth.substr(0,vwidth.length-2);
+
+ chartWrap.style('width', function() {
+ return legwidth - 30 + 'px';
+ });
+
+ legendWrap
+ .classed('legend-open', false)
+ .style('width', '30px')
+ .style('height', '30px');
+
+ header
+ .select('.column-labels')
+ .html(' ');
+
+ header
+ .select('.legend-toggle')
+ .on( 'click', function (e) {
+ var sel = d3.select(this)[0].parentNode;
+ toggleheader(e);
+ });
+ }
+
+ function openheader(e) {
+ var vheight = vis.style('height'),
+ vwidth = vis.style('width'),
+ legheight = +vheight.substr(0,vheight.length-2) - 68,
+ legwidth = +vwidth.substr(0,vwidth.length-2);
+
+ chartWrap.style('width', function() {
+ return legwidth - 180 + 'px';
+ });
+
+ legendWrap
+ .classed('legend-open', true)
+ .style('width', '170px')
+ .style('height', legheight + 'px');
+
+ header
+ .select('.column-labels')
+ .html('');
+
+ header
+ .select('.legend-toggle')
+ .on( 'click', function (e) {
+ var sel = d3.select(this)[0].parentNode;
+ toggleheader(e);
+ });
+ }
+
+ function toggleheader (e) {
+ if (chart.headerOpen === undefined || chart.headerOpen === false) {
+ chart.headerOpen = true;
+ openheader(e);
+ } else {
+ chart.headerOpen = false;
+ closeheader(e);
+ }
+
+ chart.resize();
+ }
+
+ // check for last state
+ if ( chart.headerOpen === true ) { openheader(); }
+ else { closeheader(); }
+ }
+
+ return function (elem, colors, chart) {
+ return legend (elem, colors, chart);
+ };
+});
\ No newline at end of file
diff --git a/src/kibana/components/vislib/modules/lineChart.js b/src/kibana/components/vislib/modules/lineChart.js
new file mode 100644
index 000000000000..9291429c7153
--- /dev/null
+++ b/src/kibana/components/vislib/modules/lineChart.js
@@ -0,0 +1,770 @@
+define(function(require) {
+ 'use strict';
+
+ var d3 = require('lib/d3/d3'),
+ getSelection = require('src/utils/selection'),
+ getLegend = require('src/modules/legend'),
+ getColor = require('src/utils/colorspace');
+
+ return function getLineChart(elem, config) {
+ if (typeof config === 'undefined') { config = {}; }
+
+ var chart = {},
+
+ /* ***** Chart Options ***** */
+ addLegend = config.addLegend || false,
+ addTooltip = config.addTooltip || false,
+ shareYAxis = config.shareYAxis || false,
+ /* ************************* */
+
+ /* ***** Chart Flags ******* */
+ destroyFlag = false,
+ /* ************************* */
+
+ /* ***** Chart Globals ******* */
+ dispatch = d3.dispatch('hover', 'click', 'mouseenter', 'mouseleave', 'mouseout', 'mouseover', 'brush'),
+ $elem = $(elem), // cached jquery version of element
+ latestData,
+ prevSize,
+ xValue = function(d, i) { return d.x; },
+ yValue = function(d, i) { return d.y; };
+ /* ************************* */
+
+ /*
+ Renders the chart to the HTML element
+ */
+ chart.render = function(data) {
+ try {
+ if (!data) { throw new Error("No valid data"); }
+ if (!elem) { throw new Error("No elem provided"); }
+
+ // store a copy of the data sent to render, so that it can be resent with .resize()
+ latestData = data;
+
+ // removes elements to redraw the chart on subsequent calls
+ d3.select(elem).selectAll('*').remove();
+
+ var chartWrapper = chart.getChartWrapper(elem)[0][0],
+ selection = chart.getSelection(chartWrapper, latestData);
+
+ return chart.getVisualization(selection);
+ }
+ catch(error) {
+ console.group("chart.render: " + error);
+ }
+ };
+
+ /*
+ Creates the d3 visualization
+ */
+ chart.getVisualization = function(selection) {
+ try {
+ if (!selection) { throw new Error("No valid selection"); }
+
+ if (destroyFlag) { throw new Error('You destroyed the chart and tried to use it again'); }
+
+ var colors = chart.getColors(selection);
+
+ // Calculates the max Y axis value for all Charts
+ if (shareYAxis) { var yAxisMax = chart.getYAxisMax(selection); }
+
+ // Adds the legend
+ if (addLegend) { var legend = getLegend(elem, colors, chart); }
+
+ // Adds tooltips
+ if (addTooltip) { var tip = chart.getTooltip(elem); }
+
+ return selection.each(function(d, i) {
+ var that = this;
+
+ chart.createLineChart({
+ 'data': d,
+ 'index': i,
+ 'this': that,
+ 'colors': colors,
+ 'tip': tip,
+ 'yAxisMax': yAxisMax
+ });
+ });
+ }
+ catch(error) {
+ console.group("chart.getVisualization: " + error);
+ }
+ };
+
+ chart.getTooltip = function(elem) {
+ try {
+ if (!elem) { throw new Error("No valid elem"); }
+
+ var tooltipDiv;
+
+ tooltipDiv = d3.select(elem)
+ .append('div')
+ .attr('class', 'k4tip');
+
+ return tooltipDiv;
+ }
+ catch(error) {
+ console.group("chart.getTooltip: " + error);
+ }
+ };
+
+ chart.getChartWrapper = function(elem) {
+ try {
+ if (!elem) { throw new Error("No valid elem"); }
+
+ var chartWrapper = d3.select(elem).append('div');
+
+ chartWrapper
+ .attr('class', 'chartwrapper')
+ .style('height', $(elem).height() + 'px');
+
+ return chartWrapper;
+ }
+ catch(error) {
+ console.group("chart.getChartWrapper: " + error);
+ }
+ };
+
+ chart.getSelection = function(elem, data) {
+ try {
+ if (!elem) { throw new Error("No valid elem"); }
+ if (!data) { throw new Error("No valid data"); }
+
+ var selection = d3.selectAll(getSelection(elem, data));
+
+ return selection;
+ }
+ catch(error) {
+ console.group("chart.getSelection: " + error);
+ }
+ };
+
+ chart.getColors = function(selection) {
+ try {
+ if (!selection) { throw new Error("No valid selection"); }
+
+ var colorDomain = chart.getColorDomain(selection),
+ lengthOfColorDomain = colorDomain.length,
+ colorArray = getColor(lengthOfColorDomain),
+ colorDict;
+
+ colorDict = chart.getColorDict(colorDomain, colorArray);
+
+ return colorDict;
+ }
+ catch(error) {
+ console.group("chart.getColors: " + error);
+ }
+ };
+
+ chart.getColorDict = function(colorDomain, colorArray) {
+ try {
+ if (!colorDomain) { throw new Error("No valid colorDomain"); }
+ if (!colorArray) { throw new Error("No valid colorArray"); }
+
+ var colorDict;
+
+ colorDict = _.zipObject(colorDomain, colorArray);
+
+ return colorDict;
+ }
+ catch(error) {
+ console.group("chart.getColorDict" + error);
+ }
+ };
+
+ /* Color domain */
+ chart.getColorDomain = function(selection) {
+ try {
+ if (!selection) { throw new Error("No valid selection"); }
+
+ var items = [];
+
+ selection.each(function(d) {
+ d.series.forEach(function(label) {
+ if (label.label) { items.push(label.label); }
+ else { items.push(d.yAxisLabel); }
+ });
+ });
+
+ items = _.uniq(items);
+ return items;
+ }
+ catch(error) {
+ console.group("chart.getColorDomain: " + error);
+ }
+ };
+
+ /* Function for global yAxis */
+ chart.getYAxisMax = function(selection) {
+ try {
+ if (!selection) { throw new Error("No valid selection"); }
+
+ var yArray = [];
+
+ selection.each(function(d) {
+ return d3.max(d.series, function(layer) {
+ return d3.max(layer.values, function(d) {
+ yArray.push(d.y);
+ });
+ });
+ });
+
+ return d3.max(yArray);
+ }
+ catch(error) {
+ console.group("chart.getYAxisMax: " + error);
+ }
+ };
+
+ chart.getBounds = function(data) {
+ try {
+ if (!data) { throw new Error("No valid data"); }
+
+ var bounds = [];
+
+ data.series.map(function(series) {
+ series.values.map(function(d, i) {
+ bounds.push({
+ x: xValue.call(series, d, i),
+ y: yValue.call(series, d, i)
+ });
+ });
+ });
+
+ return bounds;
+ }
+ catch(error) {
+ console.group("chart.getBounds: " + error);
+ }
+ };
+
+ chart.createLineChart = function(args) {
+ try {
+ if (typeof args === 'undefined') { args = {}; }
+
+ var data = args.data,
+ that = args.this,
+ colors = args.colors,
+ tip = args.tip,
+ yAxisMax = args.yAxisMax,
+ xAxisLabel = data.xAxisLabel,
+ yAxisLabel = data.yAxisLabel,
+ chartLabel = data.label,
+ xAxisFormatter = data.xAxisFormatter,
+ yAxisFormatter = data.yAxisFormatter,
+ tooltipFormatter = data.tooltipFormatter;
+
+ var elemWidth = parseInt(d3.select(that).style('width'), 10),
+ elemHeight = parseInt(d3.select(that).style('height'), 10);
+
+ if (!elemWidth) { throw new Error("The visualization element has no width"); }
+ if (!elemHeight) { throw new Error("The visualization element has no height"); }
+
+ var margin = { top: 35, right: 15, bottom: 35, left: 50 },
+ width = elemWidth - margin.left - margin.right,
+ height = elemHeight - margin.top - margin.bottom;
+
+ var xTickScale = d3.scale.linear()
+ .clamp(true)
+ .domain([80, 300, 800])
+ .range([0, 2, 4]);
+
+ var yTickScale = d3.scale.linear()
+ .clamp(true)
+ .domain([20, 40, 1000])
+ .range([0, 1, 10]);
+
+ var xTickN = Math.floor(xTickScale(width)),
+ yTickN = Math.floor(yTickScale(height));
+
+ var xScale = d3.time.scale()
+ .range([0, width]);
+
+ var yScale = d3.scale.linear()
+ .range([height, 0]);
+
+ var xAxis = d3.svg.axis()
+ .scale(xScale)
+ .ticks(xTickN)
+ .tickPadding(5)
+ .tickFormat(xAxisFormatter)
+ .orient('bottom');
+
+ var yAxis = d3.svg.axis()
+ .scale(yScale)
+ .ticks(yTickN)
+ .tickPadding(4)
+ .tickFormat(yAxisFormatter)
+ .orient('left');
+
+ var interpolate = 'linear';
+
+ var line = d3.svg.line()
+ .interpolate(interpolate)
+ .x(X)
+ .y(Y);
+
+ var vis = d3.select(elem),
+ allLayers = vis.selectAll('path'),
+ allItms = d3.select('.legendwrapper').selectAll('li.legends'),
+ scrolltop = document.body.scrollTop,
+ mousemove;
+
+ /* *** Data Manipulation *** */
+ var seriesData = [];
+
+ data.series.map(function(series) {
+ seriesData.push(series);
+ });
+
+ // adds the label value to each data point
+ // within the values array for displaying in the tooltip
+ data.series.forEach(function(d) {
+ d.values.forEach(function(e) {
+ e.label = d.label;
+ });
+ });
+
+ xScale.domain(d3.extent(chart.getBounds(data), function(d) { return d.x; }));
+
+ // setting the y scale domain
+ if (shareYAxis) {
+ yScale
+ .domain([0, yAxisMax])
+ .nice(yTickN);
+ } else {
+ yScale
+ .domain([0, d3.max(chart.getBounds(data), function(d) { return d.y; })])
+ .nice(yTickN);
+ }
+ /* ************************** */
+
+ var svg = d3.select(that).append('svg')
+ .attr('class', 'canvas')
+ .attr('width', '100%')
+ .attr('height', '100%');
+
+ var g = svg.append('g')
+ .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
+
+ // background rect
+ g.append('rect')
+ .attr('class', 'chart-bkgd')
+ .attr('width', width)
+ .attr('height', height);
+
+ g.append('g')
+ .attr('class', 'x axis')
+ .attr('transform', 'translate(0,' + height + ')')
+ .call(xAxis)
+ .selectAll('text')
+ .call(chart.tickText, width)
+ .on('mouseover', function(d) {
+ if (addTooltip) {
+ var hh = tip[0][0].scrollHeight;
+
+ mousemove = { left: d3.event.pageX, top: d3.event.pageY};
+
+ d3.select(that).style('cursor', 'default');
+
+ return tip.datum(d).text(d)
+ .style('top', mousemove.top - scrolltop - hh/2 + 'px')
+ .style('left', mousemove.left + 20 + 'px')
+ .style('visibility', 'visible');
+ }
+ })
+ .on('mouseout', function() {
+ d3.select(that).classed('hover', false).style('stroke', null);
+ if (addTooltip) { tip.style('visibility', 'hidden'); }
+ });
+
+ g.append('g')
+ .attr('class', 'y axis')
+ .call(yAxis);
+
+ // Axis labels
+ g.append('text')
+ .attr('class', 'x-axis-label')
+ .attr('text-anchor', 'middle')
+ .attr('x', width / 2)
+ .attr('y', height + 30)
+ .text(xAxisLabel);
+
+ g.append('text')
+ .attr('class', 'y-axis-label')
+ .attr('text-anchor', 'middle')
+ .attr('x', -height / 2)
+ .attr('y', -40)
+ .attr('dy', '.75em')
+ .attr('transform', 'rotate(-90)')
+ .text(yAxisLabel);
+
+ // Chart title
+ g.append('text')
+ .attr('class', 'charts-label')
+ .attr('text-anchor', 'middle')
+ .attr('x', width / 2)
+ .attr('y', -10)
+ .text(chartLabel)
+ .call(chart.tickText, width)
+ .on('mouseover', function(d) {
+ if (addTooltip) {
+ var hh = tip[0][0].scrollHeight;
+
+ mousemove = { left: d3.event.pageX, top: d3.event.pageY};
+
+ d3.select(that).style('cursor', 'default');
+
+ return tip.text(d.label)
+ .style('top', mousemove.top - scrolltop - hh/2 + 'px')
+ .style('left', mousemove.left + 20 + 'px')
+ .style('visibility', 'visible');
+ }
+ })
+ .on('mouseout', function() {
+ d3.select(that).classed('hover', false).style('stroke', null);
+ if (addTooltip) { tip.style('visibility', 'hidden'); }
+ });
+
+ var lines = g.selectAll('.lines')
+ .data(seriesData)
+ .enter().append('g')
+ .attr('class', 'lines');
+
+ lines.append('path')
+ .attr('class', function(d) { return 'rl rl-' + chart.getClassName(d.label, yAxisLabel); })
+ .attr('d', function(d) { return line(d.values); })
+ .style('fill', 'none')
+ .style('stroke', function(d) { return d.label ? colors[d.label] : colors[yAxisLabel]; })
+ .style('stroke-width', '3px');
+
+ var layer = g.selectAll('.layer')
+ .data(seriesData)
+ .enter().append('g')
+ .attr('class', function(d) { return 'rl rl-' + chart.getClassName(d.label, yAxisLabel); })
+ .attr('stroke', function(d) { return d.label ? colors[d.label] : colors[yAxisLabel]; });
+
+ var circle = layer.selectAll('.points')
+ .data(function(d) { return d.values; })
+ .enter().append('circle')
+ .attr('class', 'points')
+ .attr('cx', function(d) { return xScale(d.x); })
+ .attr('cy', function(d) { return yScale(d.y); })
+ .attr('r', 8);
+
+ circle
+ .attr('fill', '#ffffff')
+ .attr('stroke', function(d) { return d.label ? colors[d.label] : colors[yAxisLabel]; })
+ .attr('stroke-width', 3.5)
+ .attr('opacity', 0);
+
+ circle
+ .on('mouseover', function(d, i) {
+ var point = d3.select(this),
+ layerClass = '.rl-' + chart.getClassName(d.label, yAxisLabel);
+
+ point.attr('opacity', 1)
+ .classed('hover', true)
+ .style('cursor', 'pointer');
+
+ // highlight chart layer
+ allLayers = vis.selectAll('path');
+ allLayers.style('opacity', 0.3);
+
+ vis.selectAll(layerClass).style('opacity', 1);
+
+ // highlight legend item
+ if (allItms) {
+ allItms.style('opacity', 0.3);
+
+ var itm = d3.select('.legendwrapper')
+ .select(layerClass)
+ .style('opacity', 1);
+ }
+
+ dispatch.hover({
+ value: yValue(d, i),
+ point: d,
+ pointIndex: i,
+ series: data.series,
+ config: config,
+ data: latestData,
+ e: d3.event
+ });
+
+ d3.event.stopPropagation();
+ })
+ .on('mousemove', function(d) {
+ if (addTooltip) {
+ var hh = tip[0][0].scrollHeight,
+ datum;
+
+ mousemove = { left: d3.event.pageX, top: d3.event.pageY};
+
+ if (typeof d.label !== 'undefined') {
+ datum = { label: d.label, x: d.x, y: d.y };
+ } else {
+ datum = { x: d.x, y: d.y };
+ }
+
+ tip.datum(datum)
+ .text(tooltipFormatter)
+ .style('top', mousemove.top - scrolltop - hh/2 + 'px')
+ .style('left', mousemove.left + 20 + 'px')
+ .style('visibility', 'visible');
+ }
+ })
+ .on('click', function (d, i) {
+ dispatch.click({
+ value: yValue(d, i),
+ point: d,
+ pointIndex: i,
+ series: data.series,
+ config: config,
+ data: latestData,
+ e: d3.event
+ });
+ d3.event.stopPropagation();
+ })
+ .on('mouseout', function() {
+ var point = d3.select(this);
+ point.attr('opacity', 0);
+ if (addTooltip) { tip.style('visibility', 'hidden'); }
+ allLayers.style('opacity', 1);
+ allItms.style('opacity', 1);
+ });
+
+ if (addTooltip) {
+ // **** hilite series on hover
+ allLayers = vis.selectAll('path');
+ lines.on('mouseover', function (d) {
+ // highlight chart layer
+ allLayers.style('opacity', 0.3);
+ var layerClass = '.rl-' + chart.getClassName(d.label, yAxisLabel),
+ mylayer = vis.selectAll(layerClass).style('opacity', 1);
+
+ // stroke this rect
+ d3.select(this)
+ .classed('hover', true)
+ .style('stroke', '#333')
+ .style('cursor', 'pointer');
+
+ // hilite legend item
+ if (allItms) {
+ allItms.style('opacity', 0.3);
+ var itm = d3.select('.legendwrapper')
+ .select(layerClass)
+ .style('opacity', 1);
+ }
+ });
+ }
+
+ /* Event Selection: BRUSH */
+ var brush = d3.svg.brush()
+ .x(xScale)
+ .on('brushend', brushend);
+
+ if (dispatch.on('brush')) {
+ g.append('g')
+ .attr('class', 'brush')
+ .call(brush)
+ .selectAll('rect')
+ .attr('height', height);
+ }
+ /* ************************** */
+
+ lines.on('mouseout', function() {
+ allLayers.style('opacity', 1);
+ allItms.style('opacity', 1);
+ });
+
+ function X(d) { return xScale(d.x); }
+ function Y(d) { return yScale(d.y); }
+
+ function brushend() {
+ var selected,
+ start,
+ lastVal,
+ end,
+ selectedRange;
+
+ // selected is used to determine the range for ordinal scales
+ selected = xScale.domain().filter(function(d) {
+ return (brush.extent()[0] <= xScale(d)) && (xScale(d) <= brush.extent()[1]);
+ });
+
+ start = selected[0];
+ lastVal = selected.length - 1;
+ end = selected[lastVal];
+ selectedRange = [start, end];
+
+ return brush.extent()[0] instanceof Date ?
+ dispatch.brush({
+ range: brush.extent(),
+ config: config,
+ e: d3.event,
+ data: latestData
+ }) :
+ dispatch.brush({
+ range: selectedRange,
+ config: config,
+ e: d3.event,
+ data: latestData
+ });
+ }
+
+ return svg;
+
+ }
+ catch(error) {
+ console.group("chart.createLineChart: " + error);
+ }
+ };
+
+ // getters / setters
+ chart.resize = _.debounce(function () {
+ try {
+ if (!latestData) { throw new Error("No valid data"); }
+ chart.render(latestData);
+ }
+ catch(error) {
+ console.group("chart.resize: " + error);
+ }
+ }, 200);
+
+ // enable auto-resize
+ chart.checkSize = function checkSize() {
+ try {
+ var size = $elem.width() + ':' + $elem.height();
+
+ if (prevSize !== size) { chart.resize(); }
+ prevSize = size;
+
+ setTimeout(checkSize, 250);
+ }
+ catch (error) {
+ console.group("chart.checkSize: " + error);
+ }
+ };
+
+ /* Function for truncating x axis tick labels */
+ chart.tickText = function (text, width) {
+ try {
+ if (!text) { throw new Error("No text was given"); }
+ if (!width) { throw new Error("No width was given"); }
+
+ var n = text[0].length,
+ maxw = width / n * 0.9,
+ tickn = Math.floor(maxw);
+
+ text.each(function () {
+ var text = d3.select(this),
+ length = this.getComputedTextLength(),
+ tspan = text.text();
+
+ if ( length > maxw ) {
+ var str = text.text(),
+ avg = length / str.length,
+ end = Math.floor(maxw / avg);
+ str = str.substr(0, end) + '...';
+ tspan = text.text(str);
+ }
+ });
+
+ }
+ catch(error) {
+ console.group("chart.tickText: " + error);
+ }
+ };
+
+ chart.classifyString = function(string) {
+ try {
+ return string.replace(/[.]+|[/]+|[\s]+|[*]+|[;]+|[(]+|[)]+|[:]+|[,]+/g, '');
+ }
+ catch(error) {
+ console.group("chart.classifyString: " + error);
+ }
+ };
+
+ chart.getClassName = function(label, yAxisLabel) {
+ try {
+ return label ? chart.classifyString(label) : chart.classifyString(yAxisLabel);
+ }
+ catch(error) {
+ console.group("chart.getClassName: " + error);
+ }
+ };
+
+ chart.error = function() {
+ try {
+ // Removes the legend container
+ d3.select(elem).selectAll('*').remove();
+
+ var errorWrapper = d3.select(elem).append('div')
+ .attr('class', 'errorWrapper')
+ .style('height', function() { return $(elem).height() + 'px'; })
+ .style('text-align', 'center');
+
+ errorWrapper.append('p')
+ .style('font-size', '18px')
+ .style('margin-top', function() { return $(elem).height() / 3 + 'px'; })
+ .style('line-height', '18px')
+ .text('The container is too small for this chart.');
+
+ return chart;
+ }
+ catch(error) {
+ console.group("chart.error: " + error);
+ }
+ };
+
+ chart.off = function(event) {
+ try {
+ dispatch.on(event, null);
+ return chart;
+ }
+ catch(error) {
+ console.group("chart.off: " + error);
+ }
+ };
+
+ /*
+ * Destroys all charts associated with the parent element
+ * if the argument passed is true. By default the argument
+ * is true.
+ */
+ chart.destroy = function(_) {
+ try {
+ if (!arguments.length || _) {
+ destroyFlag = _ || true;
+
+ // Removing chart and all elements associated with it
+ d3.select(elem).selectAll('*').remove();
+
+ // Cleaning up event listeners
+ chart.off('click');
+ chart.off('hover');
+ chart.off('brush');
+ d3.select(window).on('resize', null);
+ }
+
+ destroyFlag = _;
+ return chart;
+ }
+ catch(error) {
+ console.group("chart.destroy: " + error);
+ }
+ };
+
+ chart.dispatch = dispatch;
+
+ d3.rebind(chart, dispatch, 'on');
+ d3.select(window).on('resize', chart.resize);
+
+ chart.checkSize();
+
+ return chart;
+ };
+});
\ No newline at end of file
diff --git a/src/kibana/components/vislib/styles/k4.d3.css b/src/kibana/components/vislib/styles/k4.d3.css
new file mode 100644
index 000000000000..e8b9d914b91c
--- /dev/null
+++ b/src/kibana/components/vislib/styles/k4.d3.css
@@ -0,0 +1,203 @@
+/* Stylings that will blow your mind! */
+
+.arc path {
+ stroke: #fff;
+ /* stroke-width: 3px; */
+}
+
+.arc path {
+ stroke: #fff;
+ /* stroke-width: 3px; */
+}
+
+div.col {
+ margin: 0;
+ padding: 0;
+ display: inline-block;
+}
+
+div.rows {
+ margin: 0;
+ padding: 0;
+}
+.row-labels, .column-labels {
+ margin: 0;
+ padding: 10;
+}
+
+visualize {
+ margin:0;
+ padding:0;
+}
+
+.chartwrapper {
+ margin: 0;
+ padding: 0;
+ overflow: hidden;
+}
+
+/* legends */
+.legendwrapper {
+ margin: 0;
+ padding: 0;
+ /*border: 1px solid #ddd;*/
+ position: absolute;
+ float: right;
+ width: 25px;
+ height: 25px;
+ top: 34px;
+ right: 10px;
+ z-index: 10;
+ overflow: hidden;
+}
+.header {
+ width: 100%;
+ height: 26px;
+ margin: 0 0 6px 0;
+}
+.legend-ul {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ width: 150px;
+
+}
+.legend-ul li {
+ display: block;
+ float: left;
+ width: 150px;
+ min-height: 22px;
+ margin: 0 18px 0 18px;
+ padding: 4px 0 4px 0;
+ text-align: left;
+ word-wrap: break-word;
+ font-size: 12px;
+ line-height: 13px;
+ list-style: none;
+ color: #666;
+
+}
+.dots {
+ width: 10px;
+ height: 10px;
+ border-radius: 50%;
+ display: block;
+ float: left;
+ margin: 2px 0 0 -16px;
+ padding: 0;
+}
+.legend-toggle {
+ position: relative;
+ float: right;
+ right: 2px;
+ top: 1px;
+}
+
+.legend-open {
+ overflow: auto;
+}
+
+
+.column-labels {
+ color: #777;
+ font-size: 10pt;
+ font-weight: normal;
+ display: block;
+ margin: 0;
+ padding: 0;
+ padding-left: 1.0em;
+ line-height: 2.0em;
+}
+
+/* histogram axis and label styles */
+.vis-canvas {
+ /* background-color: #fff; */
+}
+
+.chart-bkgd {
+ fill: #ffffff;
+ /*fill: #eff3f4;*/
+ /*stroke: #ddd;*/
+ /*shape-rendering: crispEdges;*/
+}
+
+/*.rows-label, .columns-label {
+ font-size: 10pt;
+ fill: #46525d;
+ text-align: center;
+ line-height: 1.5em;
+}*/
+
+p.rows-label, p.columns-label {
+ display: block;
+ background: #eff3f4;
+ padding: 0;
+ margin: 0;
+ font-size: 9pt;
+ fill: #46525d;
+ text-align: center;
+ line-height: 1.9em;
+}
+
+.charts-label {
+ font-size: 8pt;
+ fill: #848e96;
+}
+
+.x-axis-label, .y-axis-label {
+ font-size: 7pt;
+ fill: #848e96;
+}
+
+.tick text {
+ font-size: 7pt;
+ fill: #848e96;
+ /*cursor: pointer;*/
+}
+
+.axis {
+ shape-rendering: crispEdges;
+ stroke-width: 1px;
+}
+
+.axis line, .axis path {
+ stroke: #ddd;
+ fill: none;
+}
+
+.legend-box {
+ fill: #ffffff;
+}
+
+.brush .extent {
+ stroke: #fff;
+ fill-opacity: .125;
+ shape-rendering: crispEdges;
+}
+
+.layer .rect:hover {
+ stroke: 3px;
+}
+
+.k4tip {
+ line-height: 1;
+ font-size: 12px;
+ font-weight: normal;
+ padding: 8px;
+ background: rgba(70, 82, 93, 0.95);
+ color: #fff;
+ border-radius: 4px;
+ position: fixed;
+ z-index: 20;
+ visibility: hidden;
+}
+
+.rect {
+ /*shape-rendering: crispEdges;*/
+ stroke: transparent;
+ stroke-width: 2;
+}
+.rect.hover {
+ /*shape-rendering: crispEdges;*/
+ stroke: #333;
+}
diff --git a/src/kibana/components/vislib/utils/colorspace.js b/src/kibana/components/vislib/utils/colorspace.js
new file mode 100644
index 000000000000..18d247e90fa2
--- /dev/null
+++ b/src/kibana/components/vislib/utils/colorspace.js
@@ -0,0 +1,45 @@
+define(function (require) {
+ 'use strict';
+
+ var _ = require('lodash'),
+ d3 = require('d3');
+
+ /* Returns an array of usedColors. The length of usedColors
+ is trimmed or extended with generated colors to match length of colDom.
+ */
+
+ // 72 seed colors
+ var seedColors = [
+ '#57c17b','#006e8a','#6f87d8','#663db8','#bc52bc','#9e3533','#daa05d','#967b17',
+ '#a0caae','#73a4b0','#acb5d8','#9b8bbb','#c19fc1','#b88484','#e0cbb2','#bfb282',
+ '#336c46','#00455c','#394e93','#422c6d','#783678','#6a2424','#936734','#60521f',
+ '#b3d5bf','#85adb7','#bdc5e0','#aa9dc3','#c9acc9','#c39898','#e8d7c5','#cbc09a',
+ '#2c593b','#003252','#34457f','#352456','#673267','#591d1d','#7f592f','#443b17',
+ '#c7e0cf','#a8c5cc','#d2d7ea','#c2b9d4','#dbc7db','#d5b9b9','#f2e9de','#d9d1b5',
+ '#254b32','#002c47','#2b3969','#30214f','#562956','#491818','#654625','#393114',
+ '#dbebe0','#bcd2d7','#d0d6eb','#cdc4de','#e8d9e8','#e0cccc','#f5eee5','#e4dec9',
+ '#20412b','#001f33','#232f57','#281b41','#482348','#3e1414','#563c20','#2e2810'
+ ],
+ usedColors = [];
+
+ return function (colDom) {
+
+ // check if more colors needed
+ var dif = colDom - seedColors.length;
+ if ( dif > 0 ) {
+ // generate more colors
+ usedColors = _.clone(seedColors);
+ for (var newcol, i = 0; i < dif; i++) {
+ newcol = d3.rgb( usedColors[i] ).darker(1.3).toString();
+ usedColors.push(newcol);
+ }
+
+ } else {
+ // trim to length of colDomain labels
+ usedColors = _.first(seedColors, colDom);
+ }
+
+ return usedColors;
+ };
+
+});
\ No newline at end of file
diff --git a/src/kibana/components/vislib/utils/selection.js b/src/kibana/components/vislib/utils/selection.js
new file mode 100644
index 000000000000..86dabb03afd6
--- /dev/null
+++ b/src/kibana/components/vislib/utils/selection.js
@@ -0,0 +1,84 @@
+define(function(require) {
+ 'use strict';
+
+ var d3 = require('lib/d3/d3');
+
+ // Dynamically adds css file
+ require('lib/require-css/css!../css/k4.d3');
+
+ // adds an array to another array
+ function addTo(to, array) {
+ [].push.apply(to, array);
+ }
+
+ /*
+ Accepts a DOM element(s) and data.
+ Returns an array of DOM elements on which charts
+ will be rendered.
+ */
+ function placeChart(elem, data) {
+ var $el = elem instanceof Array ? elem : d3.select(elem).datum(data),
+ charts = [];
+
+ if (data.rows) { addTo(charts, split($el, 'height', 'width', data.rows, 'rows')); }
+ else if (data.columns) { addTo(charts, split($el, 'width', 'height', data.columns, 'columns')); }
+ else {
+ if (!data.series || data.series.length === 0) { throw new Error('No valid data'); }
+
+ addTo(charts, $el.append('div')
+ .attr('class', 'chart')
+ .style('width', '100%')
+ .style('height', '100%')[0]);
+ }
+
+ return charts;
+ }
+
+ /*
+ Accepts a DOM element(s), 'width' and 'height', data and class name.
+ Returns a DOM element array that has been split by class name,
+ i.e. rows or columns.
+ */
+ function split (elem, by, inherit, data, name) {
+ var charts = [],
+ $el = elem instanceof Array ? elem : d3.select(elem),
+ node = elem instanceof Array ? $(elem[0]) : $(elem),
+ // need to refactor
+ size = ($(node).parent()[by]() / data.length) / $(node).parent()[by]() * 100,
+ inheritedSize = node[inherit]() / node[inherit]() * 100;
+
+ if (!size || !inheritedSize || size === 0 || inheritedSize === 0) {
+ if (!size || size === 0) {
+ throw new Error('Chart cannot be rendered because ' + by + ' is ' + size + '.');
+ } else {
+ throw new Error('Chart cannot be rendered because ' + inherit + ' is ' + inheritedSize + '.');
+ }
+ }
+
+ $el.selectAll('div')
+ .data(data)
+ .enter().append('div')
+ .attr('class', name)
+ .style('width', function() { return by === 'width' ? size + '%': inheritedSize + '%'; })
+ .style('height', function() { return by === 'height' ? size + '%' : inheritedSize + '%'; })
+ .style('display', function() { return name === 'rows' ? 'block' : 'inline-block'; })
+ .each(function(d) {
+ var selection = d3.select(this);
+
+ if (!d.series) {
+ selection.append('div')
+ .attr('class', name + '-label')
+ .attr('height', '10%')
+ .text(d.label);
+ }
+
+ addTo(charts, placeChart(selection, d));
+ });
+
+ return charts;
+ }
+
+ return function(elem, data) {
+ return placeChart(elem, data);
+ };
+});
diff --git a/src/kibana/components/vislib/utils/zeroInjection.js b/src/kibana/components/vislib/utils/zeroInjection.js
new file mode 100644
index 000000000000..7956be48f5f9
--- /dev/null
+++ b/src/kibana/components/vislib/utils/zeroInjection.js
@@ -0,0 +1,73 @@
+define(function(require) {
+ 'use strict';
+
+ var _ = require('lib/lodash/dist/lodash');
+
+ /* Returns an array of ordered keys for a data series
+ * or several arrays of data values.
+ */
+
+ function orderKeys(data) {
+ var uniqueXObjs = {}, orderedKeys;
+
+ data.forEach(function (series) {
+ series.values.forEach(function (d, i) {
+ var key = d.x;
+ uniqueXObjs[key] = uniqueXObjs[key] === void 0 ? i : Math.max(i, uniqueXObjs[key]);
+ });
+ });
+
+ orderedKeys = _.chain(uniqueXObjs).pairs().sortBy(1).pluck(0).value();
+ return orderedKeys;
+ }
+
+ /* Returns the indexed position of a value in an array. */
+ function getIndexOf(val, arr) {
+ var i, max = arr.length;
+ for (i = 0; i < max; i++) {
+ if (val == arr[i].x) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ function createZeroFilledArray(orderedKeys) {
+ var max = orderedKeys.length,
+ i, arr = [];
+
+ for (i = 0; i < max; i++) {
+ var val = orderedKeys[i];
+ arr.push({ x: val, y: 0});
+ }
+
+ return arr;
+ }
+
+ function modifyZeroFilledArray(zeroArray, dataArray) {
+ var i, max = dataArray.length;
+
+ for (i = 0; i < max; i++) {
+ var val = dataArray[i],
+ index = getIndexOf(val.x, zeroArray);
+
+ zeroArray.splice(index, 1);
+ zeroArray.splice(index, 0, val);
+ }
+
+ return zeroArray;
+ }
+
+ return function (data) {
+ var i, max = data.series.length,
+ orderedKeys = orderKeys(data.series);
+
+ for (i = 0; i < max; i ++) {
+ var zeroArray = createZeroFilledArray(orderedKeys),
+ dataArray = data.series[i].values;
+ data.series[i].values = modifyZeroFilledArray(zeroArray, dataArray);
+ }
+
+ return data;
+ };
+});
\ No newline at end of file
diff --git a/src/kibana/require.config.js b/src/kibana/require.config.js
index e95a5506f76a..4a20e1ca0375 100644
--- a/src/kibana/require.config.js
+++ b/src/kibana/require.config.js
@@ -45,7 +45,6 @@ require.config({
'elasticsearch': ['angular'],
'angular-bootstrap': ['angular'],
'angular-bindonce': ['angular'],
- k4d3: ['jquery', 'lodash'],
'angular-ui-ace': ['angular', 'ace'],
inflection: {
exports: 'inflection'
diff --git a/tasks/config/clean.js b/tasks/config/clean.js
index fad9ea446dab..da58c836a433 100644
--- a/tasks/config/clean.js
+++ b/tasks/config/clean.js
@@ -1,5 +1,5 @@
module.exports = function (grunt) {
- var notIncludedComponents = '{K4D3,font-awesome,requirejs}';
+ var notIncludedComponents = '{font-awesome,requirejs}';
return {
build: '<%= build %>',
target: '<%= target %>',
@@ -12,14 +12,9 @@ module.exports = function (grunt) {
// remove the contents of K4D3, font-awesome, and requirejs except necessary files
'<%= build %>/bower_components/' + notIncludedComponents + '/*',
- '!<%= build %>/bower_components/K4D3/build',
'!<%= build %>/bower_components/requirejs/require.js',
'!<%= build %>/bower_components/font-awesome/fonts',
- // remove extra builds from K4D3
- '<%= build %>/bower_components/K4D3/build/*',
- '!<%= build %>/bower_components/K4D3/build/k4.d3.js',
-
'<%= build %>/**/_empty_',
'<%= build %>/**/*.less',
'<%= appBuild %>/{css-builder,normalize}.js',
diff --git a/tasks/config/requirejs.js b/tasks/config/requirejs.js
index f697ccfabf2a..93cd92dda941 100644
--- a/tasks/config/requirejs.js
+++ b/tasks/config/requirejs.js
@@ -10,7 +10,6 @@ module.exports = function (grunt) {
name: 'kibana',
excludeShallow: [
'../config',
- 'k4d3'
],
include: [
'controllers/kibana'
diff --git a/tasks/update.js b/tasks/update.js
index 7bebc7013894..e2ec16560755 100644
--- a/tasks/update.js
+++ b/tasks/update.js
@@ -3,7 +3,7 @@ module.exports = function (grunt) {
var spawn = require('./utils/spawn');
var installOrUpdateRepo = require('./utils/install_or_update_repo');
- // bower update elasticsearch && bower update k4d3 && npm run rebuild-esjs"
+ // bower update elasticsearch && npm run rebuild-esjs"
grunt.registerTask('update', [
'update-esjs',
]);