Skip to content
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

Closed
wants to merge 18 commits into from
Closed
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,11 @@
Scale Y-Axis to Data Bounds
</label>
</div>
<div class="vis-option-item" ng-if="showSeperateYAxisOption()">
<label>
<input type="checkbox" ng-model="vis.params.splitYAxis">
Seperate Y Axises
</label>
</div>
</div>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Copy link
Member Author

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 ...)

return true;
})();

if (result === false) {
$scope.vis.params.splitYAxis = false;
Copy link
Contributor

Choose a reason for hiding this comment

The 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;
};
}
};
});
264 changes: 264 additions & 0 deletions src/ui/public/vislib/lib/axis.js
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);
Copy link
Contributor

Choose a reason for hiding this comment

The 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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this defined before this.createScale() is called?

Copy link
Contributor

Choose a reason for hiding this comment

The 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;
};
Loading