UNPKG

kibana-riya

Version:

Kibana is an open source (Apache Licensed), browser based analytics and search dashboard for Elasticsearch. Kibana is a snap to setup and start using. Kibana strives to be easy to get started with, while also being flexible and powerful, just like Elastic

280 lines (245 loc) 9.05 kB
import _ from 'lodash'; import d3 from 'd3'; import $ from 'jquery'; import moment from 'moment'; import VislibVisualizationsPointSeriesProvider from './_point_series'; import getColor from 'ui/vislib/components/color/heatmap_color'; export default function HeatmapChartFactory(Private) { const PointSeries = Private(VislibVisualizationsPointSeriesProvider); const defaults = { color: undefined, // todo fillColor: undefined // todo }; /** * Line Chart Visualization * * @class HeatmapChart * @constructor * @extends Chart * @param handler {Object} Reference to the Handler Class Constructor * @param el {HTMLElement} HTML element to which the chart will be appended * @param chartData {Object} Elasticsearch query results for this specific chart */ class HeatmapChart extends PointSeries { constructor(handler, chartEl, chartData, seriesConfigArgs) { super(handler, chartEl, chartData, seriesConfigArgs); this.seriesConfig = _.defaults(seriesConfigArgs || {}, defaults); this.handler.visConfig.set('legend', { labels: this.getHeatmapLabels(this.handler.visConfig), colors: this.getHeatmapColors(this.handler.visConfig) }); const colors = this.handler.visConfig.get('legend.colors', null); if (colors) { this.handler.vis.uiState.setSilent('vis.defaultColors', null); this.handler.vis.uiState.setSilent('vis.defaultColors', colors); } } getHeatmapLabels(cfg) { const percentageMode = cfg.get('percentageMode'); const colorsNumber = cfg.get('colorsNumber'); const colorsRange = cfg.get('colorsRange'); const zScale = this.getValueAxis().getScale(); const [min, max] = zScale.domain(); const labels = []; if (cfg.get('setColorRange')) { colorsRange.forEach(range => { const from = range.from; const to = range.to; labels.push(`${from} - ${to}`); }); } else { for (let i = 0; i < colorsNumber; i++) { let label; let val = i / colorsNumber; let nextVal = (i + 1) / colorsNumber; if (percentageMode) { val = Math.ceil(val * 100); nextVal = Math.ceil(nextVal * 100); label = `${val}% - ${nextVal}%`; } else { val = val * (max - min) + min; nextVal = nextVal * (max - min) + min; if (max > 1) { val = Math.ceil(val); nextVal = Math.ceil(nextVal); } label = `${val} - ${nextVal}`; } labels.push(label); } } return labels; } getHeatmapColors(cfg) { const colorsNumber = cfg.get('colorsNumber'); const invertColors = cfg.get('invertColors'); const colorSchema = cfg.get('colorSchema'); const labels = this.getHeatmapLabels(cfg); const colors = {}; for (const i in labels) { if (labels[i]) { const val = invertColors ? 1 - i / colorsNumber : i / colorsNumber; colors[labels[i]] = getColor(val, colorSchema); } } return colors; } addSquares(svg, data) { const xScale = this.getCategoryAxis().getScale(); const yScale = this.handler.valueAxes[1].getScale(); const zScale = this.getValueAxis().getScale(); const ordered = this.handler.data.get('ordered'); const tooltip = this.baseChart.tooltip; const isTooltip = this.handler.visConfig.get('tooltip.show'); const isHorizontal = this.getCategoryAxis().axisConfig.isHorizontal(); const colorsNumber = this.handler.visConfig.get('colorsNumber'); const setColorRange = this.handler.visConfig.get('setColorRange'); const colorsRange = this.handler.visConfig.get('colorsRange'); const color = this.handler.data.getColorFunc(); const labels = this.handler.visConfig.get('legend.labels'); const zAxisConfig = this.getValueAxis().axisConfig; const zAxisFormatter = zAxisConfig.get('labels.axisFormatter'); const showLabels = zAxisConfig.get('labels.show'); const layer = svg.append('g') .attr('class', 'series'); const squares = layer .selectAll('g.square') .data(data.values); squares .exit() .remove(); let barWidth; if (this.getCategoryAxis().axisConfig.isTimeDomain()) { const { min, interval } = this.handler.data.get('ordered'); const start = min; const end = moment(min).add(interval).valueOf(); barWidth = xScale(end) - xScale(start); if (!isHorizontal) barWidth *= -1; } function x(d) { return xScale(d.x); } function y(d) { return yScale(d.series); } const [min, max] = zScale.domain(); function getColorBucket(d) { let val = 0; if (setColorRange && colorsRange.length) { const bucket = _.find(colorsRange, range => { return range.from <= d.y && range.to > d.y; }); return bucket ? colorsRange.indexOf(bucket) : -1; } else { if (isNaN(min) || isNaN(max)) { val = colorsNumber - 1; } else { val = (d.y - min) / (max - min); /* get val from 0 - 1 */ val = Math.min(colorsNumber - 1, Math.floor(val * colorsNumber)); } } return val; } function label(d) { const colorBucket = getColorBucket(d); if (colorBucket === -1) d.hide = true; return labels[colorBucket]; } function z(d) { if (label(d) === '') return 'transparent'; return color(label(d)); } const squareWidth = barWidth || xScale.rangeBand(); const squareHeight = yScale.rangeBand(); squares .enter() .append('g') .attr('class', 'square'); squares.append('rect') .attr('x', x) .attr('width', squareWidth) .attr('y', y) .attr('height', squareHeight) .attr('data-label', label) .attr('fill', z) .attr('style', 'cursor: pointer; stroke: black; stroke-width: 0.1px') .style('display', d => { return d.hide ? 'none' : 'initial'; }); // todo: verify that longest label is not longer than the barwidth // or barwidth is not smaller than textheight (and vice versa) // if (showLabels) { const rotate = zAxisConfig.get('labels.rotate'); const rotateRad = rotate * Math.PI / 180; const cellPadding = 5; const maxLength = Math.min( Math.abs(squareWidth / Math.cos(rotateRad)), Math.abs(squareHeight / Math.sin(rotateRad)) ) - cellPadding; const maxHeight = Math.min( Math.abs(squareWidth / Math.sin(rotateRad)), Math.abs(squareHeight / Math.cos(rotateRad)) ) - cellPadding; let hiddenLabels = false; squares.append('text') .text(d => zAxisFormatter(d.y)) .style('display', function (d) { const textLength = this.getBBox().width; const textHeight = this.getBBox().height; const textTooLong = textLength > maxLength; const textTooWide = textHeight > maxHeight; if (!d.hide && (textTooLong || textTooWide)) { hiddenLabels = true; } return d.hide || textTooLong || textTooWide ? 'none' : 'initial'; }) .style('dominant-baseline', 'central') .style('text-anchor', 'middle') .style('fill', zAxisConfig.get('labels.color')) .attr('x', function (d) { const center = x(d) + squareWidth / 2; return center; }) .attr('y', function (d) { const center = y(d) + squareHeight / 2; return center; }) .attr('transform', function (d) { const horizontalCenter = x(d) + squareWidth / 2; const verticalCenter = y(d) + squareHeight / 2; return `rotate(${rotate},${horizontalCenter},${verticalCenter})`; }); if (hiddenLabels) { this.baseChart.handler.alerts.show('Some labels were hidden due to size constrains'); } } if (isTooltip) { squares.call(tooltip.render()); } return squares.selectAll('rect'); } /** * Renders d3 visualization * * @method draw * @returns {Function} Creates the line chart */ draw() { const self = this; return function (selection) { selection.each(function () { const svg = self.chartEl.append('g'); svg.data([self.chartData]); const squares = self.addSquares(svg, self.chartData); self.addCircleEvents(squares); self.events.emit('rendered', { chart: self.chartData }); return svg; }); }; } } return HeatmapChart; }