-
Notifications
You must be signed in to change notification settings - Fork 8.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Update Vislib Axis #7961
Update Vislib Axis #7961
Changes from 15 commits
dd4e733
9bde535
3c3a843
a57f99c
736cbec
b3b7826
ad15255
41eb555
06042ac
9864f49
9aed759
137d289
b6ff697
e7e8961
d7c8386
a31083a
cebe795
2c0a3da
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,6 +9,31 @@ module.directive('pointSeriesOptions', function ($parse, $compile) { | |
return { | ||
restrict: 'E', | ||
template: pointSeriesOptionsTemplate, | ||
replace: true | ||
replace: true, | ||
link: function ($scope, $el) { | ||
$scope.showSeperateYAxisOption = () => { | ||
const result = (() => { | ||
if ($scope.vis.params.setYExtents || $scope.vis.params.defaultYExtents) return false; | ||
if ($scope.vis.type.name === 'histogram' && $scope.vis.params.mode !== 'grouped') return false; | ||
if ($scope.vis.type.name === 'area') return false; | ||
|
||
let metrics = 0; | ||
let buckets = 0; | ||
_.map($scope.vis.aggs, agg => { | ||
if (agg.type && agg.type.type === 'metrics') metrics++; | ||
else buckets++; | ||
}); | ||
|
||
if (metrics < 2 || buckets !== 1) return false; | ||
return true; | ||
})(); | ||
|
||
if (result === false) { | ||
$scope.vis.params.splitYAxis = false; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems like a weird place to put this. Can you explain why it's here? |
||
} | ||
|
||
return result; | ||
}; | ||
} | ||
}; | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,264 @@ | ||
import d3 from 'd3'; | ||
import _ from 'lodash'; | ||
import $ from 'jquery'; | ||
import VislibErrorHandlerProvider from 'ui/vislib/lib/_error_handler'; | ||
import VislibAxisTitleProvider from 'ui/vislib/lib/axis_title'; | ||
import VislibAxisLabelsProvider from 'ui/vislib/lib/axis_labels'; | ||
import VislibAxisScaleProvider from 'ui/vislib/lib/axis_scale'; | ||
|
||
export default function AxisFactory(Private) { | ||
const ErrorHandler = Private(VislibErrorHandlerProvider); | ||
const AxisTitle = Private(VislibAxisTitleProvider); | ||
const AxisLabels = Private(VislibAxisLabelsProvider); | ||
const AxisScale = Private(VislibAxisScaleProvider); | ||
const defaults = { | ||
show: true, | ||
type: 'value', | ||
elSelector: '.axis-wrapper-{pos} .axis-div', | ||
position: 'left', | ||
axisFormatter: null, // TODO: create default axis formatter | ||
scale: 'linear', | ||
expandLastBucket: true, //TODO: rename ... bucket has nothing to do with vis | ||
inverted: false, | ||
style: { | ||
color: '#ddd', | ||
lineWidth: '1px', | ||
opacity: 1, | ||
tickColor: '#ddd', | ||
tickWidth: '1px', | ||
tickLength: '6px' | ||
} | ||
}; | ||
|
||
const categoryDefaults = { | ||
type: 'category', | ||
position: 'bottom', | ||
labels: { | ||
rotate: 0, | ||
rotateAnchor: 'end', | ||
filter: true | ||
} | ||
}; | ||
/** | ||
* Appends y axis to the visualization | ||
* | ||
* @class Axis | ||
* @constructor | ||
* @param args {{el: (HTMLElement), yMax: (Number), _attr: (Object|*)}} | ||
*/ | ||
_.class(Axis).inherits(ErrorHandler); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about you convert these classes to be es6 syntax |
||
function Axis(args) { | ||
if (args.type === 'category') { | ||
_.extend(this, defaults, categoryDefaults, args); | ||
} else { | ||
_.extend(this, defaults, args); | ||
} | ||
|
||
this._attr = args.vis._attr; | ||
this.elSelector = this.elSelector.replace('{pos}', this.position); | ||
this.scale = new AxisScale(this, { scale: this.scale }); | ||
this.axisTitle = new AxisTitle(this, this.axisTitle); | ||
this.axisLabels = new AxisLabels(this, this.labels); | ||
} | ||
|
||
/** | ||
* Renders the y axis | ||
* | ||
* @method render | ||
* @return {D3.UpdateSelection} Renders y axis to visualization | ||
*/ | ||
Axis.prototype.render = function () { | ||
d3.select(this.vis.el).selectAll(this.elSelector).call(this.draw()); | ||
}; | ||
|
||
Axis.prototype.isHorizontal = function () { | ||
return (this.position === 'top' || this.position === 'bottom'); | ||
}; | ||
|
||
/** | ||
* Creates the d3 y axis function | ||
* | ||
* @method getAxis | ||
* @param length {Number} DOM Element height | ||
* @returns {D3.Svg.Axis|*} D3 axis function | ||
*/ | ||
Axis.prototype.getAxis = function (length) { | ||
const scale = this.scale.getScale(length); | ||
|
||
// Create the d3 axis function | ||
return d3.svg.axis() | ||
.scale(scale) | ||
.tickFormat(this.tickFormat(this.domain)) | ||
.ticks(this.tickScale(length)) | ||
.orient(this.position); | ||
}; | ||
|
||
Axis.prototype.getScale = function () { | ||
return this.scale.scale; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this defined before There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why split this function in two? |
||
}; | ||
|
||
Axis.prototype.addInterval = function (interval) { | ||
return this.scale.addInterval(interval); | ||
}; | ||
|
||
Axis.prototype.substractInterval = function (interval) { | ||
return this.scale.substractInterval(interval); | ||
}; | ||
|
||
/** | ||
* Create a tick scale for the y axis that modifies the number of ticks | ||
* based on the height of the wrapping DOM element | ||
* Avoid using even numbers in the yTickScale.range | ||
* Causes the top most tickValue in the chart to be missing | ||
* | ||
* @method tickScale | ||
* @param height {Number} DOM element height | ||
* @returns {number} Number of y axis ticks | ||
*/ | ||
Axis.prototype.tickScale = function (length) { | ||
// TODO: should accept size and decide based on position which one to use (width, height) | ||
const yTickScale = d3.scale.linear() | ||
.clamp(true) | ||
.domain([20, 40, 1000]) | ||
.range([0, 3, 11]); | ||
|
||
return Math.ceil(yTickScale(length)); | ||
}; | ||
|
||
Axis.prototype.tickFormat = function () { | ||
if (this.axisFormatter) return this.axisFormatter; | ||
if (this.isPercentage()) return d3.format('%'); | ||
return d3.format('n'); | ||
}; | ||
|
||
Axis.prototype.getLength = function (el, n) { | ||
if (this.isHorizontal()) { | ||
return $(el).parent().width() / n - this._attr.margin.left - this._attr.margin.right - 50; | ||
} | ||
return $(el).parent().height() / n - this._attr.margin.top - this._attr.margin.bottom; | ||
}; | ||
/** | ||
* Appends div to make .y-axis-spacer-block | ||
* match height of .x-axis-wrapper | ||
* | ||
* @method updateXaxisHeight | ||
*/ | ||
Axis.prototype.updateXaxisHeight = function () { | ||
const self = this; | ||
const selection = d3.select(this.vis.el).selectAll('.vis-wrapper'); | ||
|
||
|
||
selection.each(function () { | ||
const visEl = d3.select(this); | ||
|
||
if (visEl.select('.inner-spacer-block').node() === null) { | ||
visEl.selectAll('.y-axis-spacer-block') | ||
.append('div') | ||
.attr('class', 'inner-spacer-block'); | ||
} | ||
|
||
const height = visEl.select(`.axis-wrapper-${self.position}`).style('height'); | ||
visEl.selectAll(`.y-axis-spacer-block-${self.position} .inner-spacer-block`).style('height', height); | ||
}); | ||
|
||
}; | ||
|
||
Axis.prototype.adjustSize = function () { | ||
const self = this; | ||
const xAxisPadding = 15; | ||
|
||
return function (selection) { | ||
const text = selection.selectAll('.tick text'); | ||
const lengths = []; | ||
|
||
text.each(function textWidths() { | ||
lengths.push((() => { | ||
if (self.isHorizontal()) { | ||
return d3.select(this.parentNode).node().getBBox().height; | ||
} else { | ||
return d3.select(this.parentNode).node().getBBox().width; | ||
} | ||
})()); | ||
}); | ||
const length = _.max(lengths); | ||
|
||
if (self.isHorizontal()) { | ||
selection.attr('height', length); | ||
self.updateXaxisHeight(); | ||
if (self.position === 'top') { | ||
selection.select('g') | ||
.attr('transform', `translate(0, ${length - parseInt(self.style.lineWidth)})`); | ||
selection.select('path') | ||
.attr('transform', 'translate(1,0)'); | ||
} | ||
} else { | ||
selection.attr('width', length + xAxisPadding); | ||
if (self.position === 'left') { | ||
selection.select('g') | ||
.attr('transform', `translate(${length + xAxisPadding - 2 - parseInt(self.style.lineWidth)},${self._attr.margin.top})`); | ||
} | ||
} | ||
}; | ||
}; | ||
|
||
|
||
/** | ||
* Renders the y axis to the visualization | ||
* | ||
* @method draw | ||
* @returns {Function} Renders y axis to visualization | ||
*/ | ||
Axis.prototype.draw = function () { | ||
const self = this; | ||
const margin = this.vis._attr.margin; | ||
const mode = this._attr.mode; | ||
|
||
return function (selection) { | ||
const n = selection[0].length; | ||
if (self.axisTitle) { | ||
self.axisTitle.render(selection); | ||
} | ||
selection.each(function () { | ||
const el = this; | ||
const div = d3.select(el); | ||
const width = $(el).parent().width(); | ||
const height = $(el).height(); | ||
const length = self.getLength(el, n); | ||
|
||
// Validate whether width and height are not 0 or `NaN` | ||
self.validateWidthandHeight(width, height); | ||
|
||
const axis = self.getAxis(length); | ||
|
||
// The axis should not appear if mode is set to 'wiggle' or 'silhouette' | ||
if (self.show) { | ||
// Append svg and y axis | ||
const svg = div.append('svg') | ||
.attr('width', width) | ||
.attr('height', height); | ||
|
||
|
||
svg.append('g') | ||
.attr('class', `axis ${self.id}`) | ||
.call(axis); | ||
|
||
const container = svg.select('g.axis').node(); | ||
if (container) { | ||
svg.select('path') | ||
.attr('style', `stroke: ${self.style.color}; stroke-width: ${self.style.lineWidth}; stroke-opacity: ${self.style.opacity}`); | ||
svg.selectAll('line') | ||
.attr('style', `stroke: ${self.style.tickColor}; stroke-width: ${self.style.tickWidth}; stroke-opacity: ${self.style.opacity}`); | ||
// TODO: update to be depenent on position ... | ||
//.attr('x1', -parseInt(self.style.lineWidth) / 2) | ||
//.attr('x2', -parseInt(self.style.lineWidth) / 2 - parseInt(self.style.tickLength)); | ||
|
||
if (self.axisLabels) self.axisLabels.render(svg); | ||
svg.call(self.adjustSize()); | ||
} | ||
} | ||
}); | ||
}; | ||
}; | ||
|
||
return Axis; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
only show this option if we have 1 bucket only (doesn't make much sense with multiple buckets ...)