UNPKG

kibana-123

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

206 lines (168 loc) 6.15 kB
import d3 from 'd3'; import _ from 'lodash'; import moment from 'moment'; import errors from 'ui/errors'; export default function AxisScaleFactory(Private) { class AxisScale { constructor(axisConfig, visConfig) { this.axisConfig = axisConfig; this.visConfig = visConfig; if (this.axisConfig.get('type') === 'category') { this.values = this.axisConfig.values; this.ordered = this.axisConfig.ordered; } }; getScaleType() { return this.axisConfig.getScaleType(); }; validateUserExtents(domain) { const config = this.axisConfig; return domain.map((val) => { val = parseFloat(val); if (isNaN(val)) throw new Error(val + ' is not a valid number'); if (config.isPercentage() && config.isUserDefined()) return val / 100; return val; }); }; getTimeDomain(data) { return [this.minExtent(data), this.maxExtent(data)]; }; minExtent(data) { return this.calculateExtent(data || this.values, 'min'); }; maxExtent(data) { return this.calculateExtent(data || this.values, 'max'); }; calculateExtent(data, extent) { const ordered = this.ordered; const opts = [ordered[extent]]; let point = d3[extent](data); if (this.axisConfig.get('scale.expandLastBucket') && extent === 'max') { point = this.addInterval(point); } opts.push(point); return d3[extent](opts.reduce(function (opts, v) { if (!_.isNumber(v)) v = +v; if (!isNaN(v)) opts.push(v); return opts; }, [])); }; addInterval(x) { return this.modByInterval(x, +1); }; subtractInterval(x) { return this.modByInterval(x, -1); }; modByInterval(x, n) { const ordered = this.ordered; if (!ordered) return x; const interval = ordered.interval; if (!interval) return x; if (!ordered.date) { return x += (ordered.interval * n); } const y = moment(x); const method = n > 0 ? 'add' : 'subtract'; _.times(Math.abs(n), function () { y[method](interval); }); return y.valueOf(); }; getAllPoints() { const config = this.axisConfig; const data = this.visConfig.data.chartData(); const chartPoints = _.reduce(data, (chartPoints, chart, chartIndex) => { const points = chart.series.reduce((points, seri, seriIndex) => { const seriConfig = this.visConfig.get(`charts[${chartIndex}].series[${seriIndex}]`); const matchingValueAxis = !!seriConfig.valueAxis && seriConfig.valueAxis === config.get('id'); const isFirstAxis = config.get('id') === this.visConfig.get('valueAxes[0].id'); if (matchingValueAxis || (!seriConfig.valueAxis && isFirstAxis)) { const axisPoints = seri.values.map(val => { if (val.y0) { return val.y0 + val.y; } return val.y; }); return points.concat(axisPoints); } return points; }, []); return chartPoints.concat(points); }, []); return chartPoints; }; getYMin() { return d3.min(this.getAllPoints()); }; getYMax() { return d3.max(this.getAllPoints()); }; getExtents() { if (this.axisConfig.get('type') === 'category') { if (this.axisConfig.isTimeDomain()) return this.getTimeDomain(this.values); if (this.axisConfig.isOrdinal()) return this.values; } const min = this.axisConfig.get('scale.min') || this.getYMin(); const max = this.axisConfig.get('scale.max') || this.getYMax(); const domain = [min, max]; if (this.axisConfig.isUserDefined()) return this.validateUserExtents(domain); if (this.axisConfig.isLogScale()) return this.logDomain(min, max); if (this.axisConfig.isYExtents()) return domain; return [Math.min(0, min), Math.max(0, max)]; }; getRange(length) { if (this.axisConfig.isHorizontal()) { return !this.axisConfig.get('scale.inverted') ? [0, length] : [length, 0]; } else { return this.axisConfig.get('scale.inverted') ? [0, length] : [length, 0]; } }; throwCustomError(message) { throw new Error(message); }; throwLogScaleValuesError() { throw new errors.InvalidLogScaleValues(); }; logDomain(min, max) { if (min < 0 || max < 0) return this.throwLogScaleValuesError(); return [1, max]; }; getD3Scale(scaleTypeArg) { let scaleType = scaleTypeArg || 'linear'; if (scaleType === 'square root') scaleType = 'sqrt'; if (this.axisConfig.isTimeDomain()) return d3.time.scale.utc(); // allow time scale if (this.axisConfig.isOrdinal()) return d3.scale.ordinal(); if (typeof d3.scale[scaleType] !== 'function') { return this.throwCustomError(`Axis.getScaleType: ${scaleType} is not a function`); } return d3.scale[scaleType](); }; canApplyNice() { const config = this.axisConfig; return (!config.isUserDefined() && !config.isYExtents() && !config.isOrdinal() && !config.isTimeDomain()); } getScale(length) { const config = this.axisConfig; const scale = this.getD3Scale(config.getScaleType()); const domain = this.getExtents(); const range = this.getRange(length); const padding = config.get('style.rangePadding'); const outerPadding = config.get('style.rangeOuterPadding'); this.scale = scale.domain(domain); if (config.isOrdinal()) { this.scale.rangeBands(range, padding, outerPadding); } else { this.scale.range(range); } if (this.canApplyNice()) this.scale.nice(); // Prevents bars from going off the chart when the y extents are within the domain range if (this.scale.clamp) this.scale.clamp(true); this.validateScale(this.scale); return this.scale; }; validateScale(scale) { if (!scale || _.isNaN(scale)) throw new Error('scale is ' + scale); }; } return AxisScale; };