mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
pie chart labels (#12174)
* pie labels * add simple unit test * fixing dashboard test * fixing basedo on review * simplifying conflict resolution * removing unused code * cleanup code * minor changes based on review * updating option templates to match new design * updating truncate_labels to work with chars instead pixels
This commit is contained in:
parent
1d8d835ab7
commit
5793410a35
10 changed files with 421 additions and 172 deletions
|
@ -1,20 +1,25 @@
|
|||
<div>
|
||||
<div class="vis-option-item">
|
||||
<label for="visualizeBasicSettingsLegendPosition">
|
||||
<div class="form-group">
|
||||
<div class="kuiSideBarFormRow">
|
||||
<label class="kuiSideBarFormRow__label" for="visualizeBasicSettingsLegendPosition">
|
||||
Legend Position
|
||||
</label>
|
||||
<select
|
||||
id="visualizeBasicSettingsLegendPosition"
|
||||
class="form-control"
|
||||
ng-model="vis.params.legendPosition"
|
||||
ng-options="position.value as position.text for position in vis.type.editorConfig.collections.legendPositions"
|
||||
>
|
||||
</select>
|
||||
<div class="kuiSideBarFormRow__control">
|
||||
<select
|
||||
id="visualizeBasicSettingsLegendPosition"
|
||||
class="form-control"
|
||||
ng-model="vis.params.legendPosition"
|
||||
ng-options="position.value as position.text for position in vis.type.editorConfig.collections.legendPositions"
|
||||
>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="vis-option-item">
|
||||
<label id="visualizeBasicSettingsShowTooltip">
|
||||
<input type="checkbox" ng-model="vis.params.addTooltip">
|
||||
|
||||
<div class="kuiSideBarFormRow">
|
||||
<label class="kuiSideBarFormRow__label" for="showTooltip">
|
||||
Show Tooltip
|
||||
</label>
|
||||
<div class="kuiSideBarFormRow__control">
|
||||
<input id="showTooltip" type="checkbox" ng-model="vis.params.addTooltip">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,8 +1,75 @@
|
|||
<!-- vis type specific options -->
|
||||
<div class="kuiSideBarSection">
|
||||
<label>
|
||||
<input type="checkbox" value="{{isDonut}}" ng-model="vis.params.isDonut" name="isDonut" ng-checked="vis.params.isDonut">
|
||||
Donut
|
||||
</label>
|
||||
<div class="form-group">
|
||||
<div class="kuiSideBarSectionTitle">
|
||||
<div class="kuiSideBarSectionTitle__text">
|
||||
Pie Settings
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="kuiSideBarFormRow">
|
||||
<label class="kuiSideBarFormRow__label" for="isDonut">
|
||||
Donut
|
||||
</label>
|
||||
<div class="kuiSideBarFormRow__control">
|
||||
<input id="isDonut" name="isDonut" type="checkbox" value="{{isDonut}}"
|
||||
ng-checked="vis.params.isDonut"
|
||||
ng-model="vis.params.isDonut"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<vislib-basic-options></vislib-basic-options>
|
||||
</div>
|
||||
|
||||
<!-- Labels -->
|
||||
<div class="kuiSideBarSection">
|
||||
<div class="form-group">
|
||||
<div class="kuiSideBarSectionTitle">
|
||||
<div class="kuiSideBarSectionTitle__text">
|
||||
Labels Settings
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="kuiSideBarFormRow">
|
||||
<label class="kuiSideBarFormRow__label" for="showLabels">
|
||||
Show Labels
|
||||
</label>
|
||||
<div class="kuiSideBarFormRow__control">
|
||||
<input class="kuiCheckBox" id="showLabels" type="checkbox" ng-model="vis.params.labels.show">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="kuiSideBarFormRow">
|
||||
<label class="kuiSideBarFormRow__label" for="showLastLevel">
|
||||
Show Top Level Only
|
||||
</label>
|
||||
<div class="kuiSideBarFormRow__control">
|
||||
<input class="kuiCheckBox" id="showLastLevel" type="checkbox" ng-model="vis.params.labels.last_level">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="kuiSideBarFormRow">
|
||||
<label class="kuiSideBarFormRow__label" for="showValues">
|
||||
Show Values
|
||||
</label>
|
||||
<div class="kuiSideBarFormRow__control">
|
||||
<input class="kuiCheckBox" id="showValues" type="checkbox" ng-model="vis.params.labels.values">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="kuiSideBarFormRow">
|
||||
<label class="kuiSideBarFormRow__label" for="truncateLabels">
|
||||
Truncate
|
||||
</label>
|
||||
<div class="kuiSideBarFormRow__control">
|
||||
<input
|
||||
id="truncateLabels"
|
||||
class="kuiInput kuiSideBarInput"
|
||||
type="number"
|
||||
ng-model="vis.params.labels.truncate"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -20,7 +20,13 @@ export default function HistogramVisType(Private) {
|
|||
addTooltip: true,
|
||||
addLegend: true,
|
||||
legendPosition: 'right',
|
||||
isDonut: true
|
||||
isDonut: true,
|
||||
labels: {
|
||||
show: false,
|
||||
values: true,
|
||||
last_level: true,
|
||||
truncate: 100
|
||||
}
|
||||
},
|
||||
},
|
||||
editorConfig: {
|
||||
|
|
|
@ -154,144 +154,157 @@ describe('No global chart settings', function () {
|
|||
});
|
||||
});
|
||||
|
||||
aggArray.forEach(function (dataAgg, i) {
|
||||
describe('Vislib PieChart Class Test Suite for ' + names[i] + ' data', function () {
|
||||
const visLibParams = {
|
||||
type: 'pie',
|
||||
addLegend: true,
|
||||
addTooltip: true
|
||||
};
|
||||
let vis;
|
||||
let Vis;
|
||||
let persistedState;
|
||||
let indexPattern;
|
||||
let buildHierarchicalData;
|
||||
let data;
|
||||
|
||||
beforeEach(ngMock.module('kibana'));
|
||||
beforeEach(ngMock.inject(function (Private, $injector) {
|
||||
vis = Private(FixturesVislibVisFixtureProvider)(visLibParams);
|
||||
Vis = Private(VisProvider);
|
||||
persistedState = new ($injector.get('PersistedState'))();
|
||||
indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
|
||||
buildHierarchicalData = Private(BuildHierarchicalDataProvider);
|
||||
|
||||
let id = 1;
|
||||
const stubVis = new Vis(indexPattern, {
|
||||
describe('Vislib PieChart Class Test Suite', function () {
|
||||
aggArray.forEach(function (dataAgg, i) {
|
||||
describe('Vislib PieChart Class Test Suite for ' + names[i] + ' data', function () {
|
||||
const visLibParams = {
|
||||
type: 'pie',
|
||||
aggs: dataAgg
|
||||
});
|
||||
addLegend: true,
|
||||
addTooltip: true
|
||||
};
|
||||
let vis;
|
||||
let Vis;
|
||||
let persistedState;
|
||||
let indexPattern;
|
||||
let buildHierarchicalData;
|
||||
let data;
|
||||
|
||||
// We need to set the aggs to a known value.
|
||||
_.each(stubVis.aggs, function (agg) { agg.id = 'agg_' + id++; });
|
||||
beforeEach(ngMock.module('kibana'));
|
||||
beforeEach(ngMock.inject(function (Private, $injector) {
|
||||
vis = Private(FixturesVislibVisFixtureProvider)(visLibParams);
|
||||
Vis = Private(VisProvider);
|
||||
persistedState = new ($injector.get('PersistedState'))();
|
||||
indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
|
||||
buildHierarchicalData = Private(BuildHierarchicalDataProvider);
|
||||
|
||||
data = buildHierarchicalData(stubVis, fixtures.threeTermBuckets);
|
||||
|
||||
vis.render(data, persistedState);
|
||||
}));
|
||||
|
||||
afterEach(function () {
|
||||
vis.destroy();
|
||||
});
|
||||
|
||||
describe('addPathEvents method', function () {
|
||||
let path;
|
||||
let d3selectedPath;
|
||||
let onClick;
|
||||
let onMouseOver;
|
||||
|
||||
beforeEach(function () {
|
||||
vis.handler.charts.forEach(function (chart) {
|
||||
path = $(chart.chartEl).find('path')[0];
|
||||
d3selectedPath = d3.select(path)[0][0];
|
||||
|
||||
// d3 instance of click and hover
|
||||
onClick = (!!d3selectedPath.__onclick);
|
||||
onMouseOver = (!!d3selectedPath.__onmouseover);
|
||||
let id = 1;
|
||||
const stubVis = new Vis(indexPattern, {
|
||||
type: 'pie',
|
||||
aggs: dataAgg
|
||||
});
|
||||
});
|
||||
|
||||
it('should attach a click event', function () {
|
||||
vis.handler.charts.forEach(function () {
|
||||
expect(onClick).to.be(true);
|
||||
});
|
||||
});
|
||||
// We need to set the aggs to a known value.
|
||||
_.each(stubVis.aggs, function (agg) { agg.id = 'agg_' + id++; });
|
||||
|
||||
it('should attach a hover event', function () {
|
||||
vis.handler.charts.forEach(function () {
|
||||
expect(onMouseOver).to.be(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
data = buildHierarchicalData(stubVis, fixtures.threeTermBuckets);
|
||||
|
||||
describe('addPath method', function () {
|
||||
let width;
|
||||
let height;
|
||||
let svg;
|
||||
let slices;
|
||||
|
||||
beforeEach(ngMock.inject(function () {
|
||||
vis.handler.charts.forEach(function (chart) {
|
||||
width = $(chart.chartEl).width();
|
||||
height = $(chart.chartEl).height();
|
||||
svg = d3.select($(chart.chartEl).find('svg')[0]);
|
||||
slices = chart.chartData.slices;
|
||||
});
|
||||
vis.render(data, persistedState);
|
||||
}));
|
||||
|
||||
it('should return an SVG object', function () {
|
||||
vis.handler.charts.forEach(function (chart) {
|
||||
expect(_.isObject(chart.addPath(width, height, svg, slices))).to.be(true);
|
||||
});
|
||||
afterEach(function () {
|
||||
vis.destroy();
|
||||
});
|
||||
|
||||
it('should draw path elements', function () {
|
||||
vis.handler.charts.forEach(function (chart) {
|
||||
describe('addPathEvents method', function () {
|
||||
let path;
|
||||
let d3selectedPath;
|
||||
let onClick;
|
||||
let onMouseOver;
|
||||
|
||||
// test whether path elements are drawn
|
||||
expect($(chart.chartEl).find('path').length).to.be.greaterThan(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('draw method', function () {
|
||||
it('should return a function', function () {
|
||||
vis.handler.charts.forEach(function (chart) {
|
||||
expect(_.isFunction(chart.draw())).to.be(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
sizes.forEach(function (size) {
|
||||
describe('containerTooSmall error', function () {
|
||||
it('should throw an error', function () {
|
||||
// 20px is the minimum height and width
|
||||
beforeEach(function () {
|
||||
vis.handler.charts.forEach(function (chart) {
|
||||
$(chart.chartEl).height(size);
|
||||
$(chart.chartEl).width(size);
|
||||
path = $(chart.chartEl).find('path')[0];
|
||||
d3selectedPath = d3.select(path)[0][0];
|
||||
|
||||
if (size < 20) {
|
||||
expect(function () {
|
||||
chart.render();
|
||||
}).to.throwError();
|
||||
}
|
||||
// d3 instance of click and hover
|
||||
onClick = (!!d3selectedPath.__onclick);
|
||||
onMouseOver = (!!d3selectedPath.__onmouseover);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not throw an error', function () {
|
||||
vis.handler.charts.forEach(function (chart) {
|
||||
$(chart.chartEl).height(size);
|
||||
$(chart.chartEl).width(size);
|
||||
it('should attach a click event', function () {
|
||||
vis.handler.charts.forEach(function () {
|
||||
expect(onClick).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
if (size > 20) {
|
||||
expect(function () {
|
||||
chart.render();
|
||||
}).to.not.throwError();
|
||||
}
|
||||
it('should attach a hover event', function () {
|
||||
vis.handler.charts.forEach(function () {
|
||||
expect(onMouseOver).to.be(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('addPath method', function () {
|
||||
let width;
|
||||
let height;
|
||||
let svg;
|
||||
let slices;
|
||||
|
||||
|
||||
it('should return an SVG object', function () {
|
||||
vis.handler.charts.forEach(function (chart) {
|
||||
$(chart.chartEl).find('svg').empty();
|
||||
width = $(chart.chartEl).width();
|
||||
height = $(chart.chartEl).height();
|
||||
svg = d3.select($(chart.chartEl).find('svg')[0]);
|
||||
slices = chart.chartData.slices;
|
||||
expect(_.isObject(chart.addPath(width, height, svg, slices))).to.be(true);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('should draw path elements', function () {
|
||||
vis.handler.charts.forEach(function (chart) {
|
||||
|
||||
// test whether path elements are drawn
|
||||
expect($(chart.chartEl).find('path').length).to.be.greaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
it ('should draw labels', function () {
|
||||
vis.handler.charts.forEach(function (chart) {
|
||||
$(chart.chartEl).find('svg').empty();
|
||||
width = $(chart.chartEl).width();
|
||||
height = $(chart.chartEl).height();
|
||||
svg = d3.select($(chart.chartEl).find('svg')[0]);
|
||||
slices = chart.chartData.slices;
|
||||
chart._attr.labels.show = true;
|
||||
chart.addPath(width, height, svg, slices);
|
||||
expect($(chart.chartEl).find('text.label-text').length).to.be.greaterThan(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('draw method', function () {
|
||||
it('should return a function', function () {
|
||||
vis.handler.charts.forEach(function (chart) {
|
||||
expect(_.isFunction(chart.draw())).to.be(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
sizes.forEach(function (size) {
|
||||
describe('containerTooSmall error', function () {
|
||||
it('should throw an error', function () {
|
||||
// 20px is the minimum height and width
|
||||
vis.handler.charts.forEach(function (chart) {
|
||||
$(chart.chartEl).height(size);
|
||||
$(chart.chartEl).width(size);
|
||||
|
||||
if (size < 20) {
|
||||
expect(function () {
|
||||
chart.render();
|
||||
}).to.throwError();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should not throw an error', function () {
|
||||
vis.handler.charts.forEach(function (chart) {
|
||||
$(chart.chartEl).height(size);
|
||||
$(chart.chartEl).width(size);
|
||||
|
||||
if (size > 20) {
|
||||
expect(function () {
|
||||
chart.render();
|
||||
}).to.not.throwError();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
18
src/ui/public/vislib/components/labels/truncate_labels.js
Normal file
18
src/ui/public/vislib/components/labels/truncate_labels.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
import $ from 'jquery';
|
||||
import d3 from 'd3';
|
||||
|
||||
/***
|
||||
*
|
||||
* @param text (d3 node containing text)
|
||||
* @param size (number of characters to leave)
|
||||
* @returns {text} the updated text
|
||||
*/
|
||||
const truncateLabel = function (text, size) {
|
||||
const node = d3.select(text).node();
|
||||
const str = $(node).text();
|
||||
if (size === 0) return str;
|
||||
if (size >= str.length) return str;
|
||||
return str.substr(0, size) + '…';
|
||||
};
|
||||
|
||||
export { truncateLabel };
|
|
@ -1,5 +1,6 @@
|
|||
import d3 from 'd3';
|
||||
import $ from 'jquery';
|
||||
import { truncateLabel } from '../../components/labels/truncate_labels';
|
||||
|
||||
export function VislibAxisLabelsProvider() {
|
||||
class AxisLabels {
|
||||
|
@ -56,34 +57,14 @@ export function VislibAxisLabelsProvider() {
|
|||
};
|
||||
}
|
||||
|
||||
truncateLabel(text, size) {
|
||||
const node = d3.select(text).node();
|
||||
let str = $(node).text();
|
||||
const width = node.getBBox().width;
|
||||
const chars = str.length;
|
||||
const pxPerChar = width / chars;
|
||||
let endChar = 0;
|
||||
const ellipsesPad = 4;
|
||||
|
||||
if (width > size) {
|
||||
endChar = Math.floor((size / pxPerChar) - ellipsesPad);
|
||||
while (str[endChar - 1] === ' ' || str[endChar - 1] === '-' || str[endChar - 1] === ',') {
|
||||
endChar = endChar - 1;
|
||||
}
|
||||
str = str.substr(0, endChar) + '...';
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
truncateLabels() {
|
||||
const self = this;
|
||||
const config = this.axisConfig;
|
||||
return function (selection) {
|
||||
if (!config.get('labels.truncate')) return;
|
||||
|
||||
selection.selectAll('.tick text')
|
||||
.text(function () {
|
||||
return self.truncateLabel(this, config.get('labels.truncate'));
|
||||
return truncateLabel(this, config.get('labels.truncate'));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
@ -5,7 +5,11 @@ export function VislibPieConfigProvider() {
|
|||
return function (config) {
|
||||
if (!config.chart) {
|
||||
config.chart = _.defaults({}, config, {
|
||||
type: 'pie'
|
||||
type: 'pie',
|
||||
labels: {
|
||||
show: false,
|
||||
truncate: 100
|
||||
}
|
||||
});
|
||||
}
|
||||
return config;
|
||||
|
|
|
@ -199,3 +199,15 @@
|
|||
margin-bottom: 0px;
|
||||
margin-right: 0px;
|
||||
}
|
||||
|
||||
.label-line {
|
||||
opacity: .3;
|
||||
stroke: black;
|
||||
stroke-width: 2px;
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.label-text {
|
||||
font-size: 130%;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import d3 from 'd3';
|
||||
import _ from 'lodash';
|
||||
import $ from 'jquery';
|
||||
import numeral from 'numeral';
|
||||
import { PieContainsAllZeros, ContainerTooSmall } from 'ui/errors';
|
||||
import { VislibVisualizationsChartProvider } from './_chart';
|
||||
import { truncateLabel } from '../components/labels/truncate_labels';
|
||||
|
||||
export function VislibVisualizationsPieChartProvider(Private) {
|
||||
|
||||
|
@ -111,25 +113,37 @@ export function VislibVisualizationsPieChartProvider(Private) {
|
|||
const tooltip = self.tooltip;
|
||||
const isTooltip = self._attr.addTooltip;
|
||||
|
||||
const arcs = svg.append('g').attr('class', 'arcs');
|
||||
const labels = svg.append('g').attr('class', 'labels');
|
||||
|
||||
const showLabels = self._attr.labels.show;
|
||||
const showValues = self._attr.labels.values;
|
||||
const truncateLabelLength = self._attr.labels.truncate;
|
||||
const showOnlyOnLastLevel = self._attr.labels.last_level;
|
||||
|
||||
const partition = d3.layout.partition()
|
||||
.sort(null)
|
||||
.value(function (d) {
|
||||
return d.percentOfParent * 100;
|
||||
});
|
||||
const x = d3.scale.linear()
|
||||
.range([0, 2 * Math.PI]);
|
||||
const y = d3.scale.sqrt()
|
||||
.range([0, radius]);
|
||||
|
||||
const x = d3.scale.linear().range([0, 2 * Math.PI]);
|
||||
const y = d3.scale.sqrt().range([0, radius * 0.7]);
|
||||
|
||||
const startAngle = function (d) {
|
||||
return Math.max(0, Math.min(2 * Math.PI, x(d.x)));
|
||||
};
|
||||
|
||||
const endAngle = function (d) {
|
||||
if (d.dx < 1e-8) return x(d.x);
|
||||
return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx)));
|
||||
};
|
||||
|
||||
const arc = d3.svg.arc()
|
||||
.startAngle(function (d) {
|
||||
return Math.max(0, Math.min(2 * Math.PI, x(d.x)));
|
||||
})
|
||||
.endAngle(function (d) {
|
||||
if (d.dx < 1e-8) return x(d.x);
|
||||
return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx)));
|
||||
})
|
||||
.startAngle(startAngle)
|
||||
.endAngle(endAngle)
|
||||
.innerRadius(function (d) {
|
||||
// option for a single layer, i.e pie chart
|
||||
// option for a single layer, i.e pie chart
|
||||
if (d.depth === 1 && !isDonut) {
|
||||
// return no inner radius
|
||||
return 0;
|
||||
|
@ -141,7 +155,14 @@ export function VislibVisualizationsPieChartProvider(Private) {
|
|||
return Math.max(0, y(d.y + d.dy));
|
||||
});
|
||||
|
||||
const path = svg
|
||||
const outerArc = d3.svg.arc()
|
||||
.startAngle(startAngle)
|
||||
.endAngle(endAngle)
|
||||
.innerRadius(radius * 0.8)
|
||||
.outerRadius(radius * 0.8);
|
||||
|
||||
let maxDepth = 0;
|
||||
const path = arcs
|
||||
.datum(slices)
|
||||
.selectAll('path')
|
||||
.data(partition.nodes)
|
||||
|
@ -152,6 +173,7 @@ export function VislibVisualizationsPieChartProvider(Private) {
|
|||
if (d.depth === 0) {
|
||||
return;
|
||||
}
|
||||
if (d.depth > maxDepth) maxDepth = d.depth;
|
||||
return 'slice';
|
||||
})
|
||||
.call(self._addIdentifier, 'name')
|
||||
|
@ -162,6 +184,127 @@ export function VislibVisualizationsPieChartProvider(Private) {
|
|||
return color(d.name);
|
||||
});
|
||||
|
||||
// add labels
|
||||
if (showLabels) {
|
||||
const labelGroups = labels
|
||||
.datum(slices)
|
||||
.selectAll('.label')
|
||||
.data(partition.nodes);
|
||||
|
||||
// create an empty quadtree to hold label positions
|
||||
const svgBBox = {
|
||||
width: svg.node().parentElement.clientWidth,
|
||||
height: svg.node().parentElement.clientHeight
|
||||
};
|
||||
|
||||
const labelLayout = d3.geom.quadtree()
|
||||
.extent([[-svgBBox.width, -svgBBox.height], [svgBBox.width, svgBBox.height] ])
|
||||
.x(function (d) { return d.position.x; })
|
||||
.y(function (d) { return d.position.y; })
|
||||
([]);
|
||||
|
||||
labelGroups
|
||||
.enter()
|
||||
.append('g')
|
||||
.attr('class', 'label')
|
||||
.append('text')
|
||||
.text(function (d) {
|
||||
if (d.depth === 0) {
|
||||
this.parentElement.remove();
|
||||
return;
|
||||
}
|
||||
if (showValues) {
|
||||
const value = numeral(d.value / 100).format('0.[00]%');
|
||||
return `${d.name} (${value})`;
|
||||
}
|
||||
return d.name;
|
||||
}).text(function () {
|
||||
return truncateLabel(this, truncateLabelLength);
|
||||
}).attr('text-anchor', function (d) {
|
||||
const midAngle = startAngle(d) + (endAngle(d) - startAngle(d)) / 2;
|
||||
return (midAngle < Math.PI) ? 'start' : 'end';
|
||||
})
|
||||
.attr('class', 'label-text')
|
||||
.each(function resolveConflicts(d) {
|
||||
if (d.depth === 0) return;
|
||||
|
||||
const parentElement = this.parentElement;
|
||||
if (showOnlyOnLastLevel && maxDepth !== d.depth) {
|
||||
parentElement.remove();
|
||||
return;
|
||||
}
|
||||
|
||||
const bbox = this.getBBox();
|
||||
const pos = outerArc.centroid(d);
|
||||
const midAngle = startAngle(d) + (endAngle(d) - startAngle(d)) / 2;
|
||||
pos[1] += 4;
|
||||
pos[0] = (0.7 + d.depth / 10) * radius * (midAngle < Math.PI ? 1 : -1);
|
||||
d.position = {
|
||||
x: pos[0],
|
||||
y: pos[1],
|
||||
left: midAngle < Math.PI ? pos[0] : pos[0] - bbox.width,
|
||||
right: midAngle > Math.PI ? pos[0] + bbox.width : pos[0],
|
||||
bottom: pos[1] + 5,
|
||||
top: pos[1] - bbox.height - 5,
|
||||
};
|
||||
|
||||
const conflicts = [];
|
||||
labelLayout.visit(function (node) {
|
||||
if (!node.point) return;
|
||||
if (conflicts.length) return true;
|
||||
|
||||
const point = node.point.position;
|
||||
const current = d.position;
|
||||
if (point) {
|
||||
const horizontalConflict = (point.left < 0 && current.left < 0) || (point.left > 0 && current.left > 0);
|
||||
const verticalConflict = (point.top > current.top && point.top <= current.bottom) ||
|
||||
(point.top < current.top && point.bottom >= current.top);
|
||||
|
||||
if (horizontalConflict && verticalConflict) {
|
||||
point.point = node.point;
|
||||
conflicts.push(point);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
if (conflicts.length) {
|
||||
parentElement.remove();
|
||||
return;
|
||||
}
|
||||
|
||||
labelLayout.add(d);
|
||||
})
|
||||
.attr('x', function (d) {
|
||||
if (d.depth === 0 || !d.position) {
|
||||
return;
|
||||
}
|
||||
return d.position.x;
|
||||
})
|
||||
.attr('y', function (d) {
|
||||
if (d.depth === 0 || !d.position) {
|
||||
return;
|
||||
}
|
||||
return d.position.y;
|
||||
});
|
||||
|
||||
labelGroups
|
||||
.append('polyline')
|
||||
.attr('points', function (d) {
|
||||
if (d.depth === 0 || !d.position) {
|
||||
return;
|
||||
}
|
||||
const pos1 = outerArc.centroid(d);
|
||||
const x2 = d.position.x > 0 ? d.position.x - 10 : d.position.x + 10;
|
||||
const pos2 = [x2, d.position.y - 4];
|
||||
pos1[1] = pos2[1];
|
||||
return [arc.centroid(d), pos1, pos2];
|
||||
})
|
||||
.attr('class', 'label-line');
|
||||
|
||||
}
|
||||
|
||||
if (isTooltip) {
|
||||
path.call(tooltip.render());
|
||||
}
|
||||
|
|
|
@ -515,7 +515,7 @@ export function DashboardPageProvider({ getService, getPageObjects }) {
|
|||
async filterOnPieSlice() {
|
||||
log.debug('Filtering on a pie slice');
|
||||
await retry.try(async () => {
|
||||
const slices = await find.allByCssSelector('svg > g > path.slice');
|
||||
const slices = await find.allByCssSelector('svg > g > g.arcs > path.slice');
|
||||
log.debug('Slices found:' + slices.length);
|
||||
return slices[0].click();
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue