diff --git a/CHANGELOG.md b/CHANGELOG.md index 116ab71..143c430 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +1,8 @@ +## 1.4.9 +* Added new formatting option for Category labels "Maximum width" to adjust label width for horizontal oriented bullets. +* Horizontal oriented bullets now renders closer to each other when axis is off. +* Fixed axis tick labels overlapping. +* Visual is updated to API 1.7.0 + ## 1.4.8 * Bars' values respect selection of other visuals now. \ No newline at end of file diff --git a/capabilities.json b/capabilities.json index f286478..75fdf5d 100644 --- a/capabilities.json +++ b/capabilities.json @@ -108,6 +108,11 @@ "displayName": "Text Size", "displayNameKey": "Visual_TextSize", "type": { "formatting": { "fontSize": true } } + }, + "maxWidth": { + "displayName": "Maximum width", + "displayNameKey": "Visual_MaxWidth", + "type": { "numeric": true } } } }, @@ -146,7 +151,7 @@ } }, "colors": { - "displayName": "colors", + "displayName": "Colors", "displayNameKey": "Visual_Colors", "properties": { "minColor": { diff --git a/karma.conf.js b/karma.conf.js index d5bca75..d166570 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -60,8 +60,9 @@ module.exports = (config) => { files: [ srcCssRecursivePath, srcRecursivePath, + 'node_modules/jquery/dist/jquery.min.js', 'node_modules/powerbi-visuals-utils-testutils/lib/index.js', - 'node_modules/jasmine-jquery/lib/jasmine-jquery.js', + 'node_modules/jasmine-jquery/lib/jasmine-jquery.js', recursivePathToTests, { pattern: srcOriginalRecursivePath, diff --git a/package.json b/package.json index 3e1f954..d1fb826 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,13 @@ { "name": "powerbi-visuals-bulletchart", "description": "Bullet chart", - "version": "1.4.8", + "version": "1.4.9", "author": { "name": "Microsoft", "email": "pbicvsupport@microsoft.com" }, "scripts": { - "postinstall": "pbiviz update 1.6.0", + "postinstall": "pbiviz update 1.7.0", "pbiviz": "pbiviz", "start": "pbiviz start", "package": "pbiviz package", @@ -41,11 +41,12 @@ "karma-typescript-preprocessor": "0.3.0", "lodash": "4.16.2", "moment": "2.15.1", - "powerbi-visuals-tools": "1.6.3", - "powerbi-visuals-utils-chartutils": "^0.3.0", - "powerbi-visuals-utils-dataviewutils": "^1.0.1", + "powerbi-visuals-utils-chartutils": "^1.2.0", + "powerbi-visuals-utils-dataviewutils": "^1.2.0", + "powerbi-visuals-tools": "1.7.0", "powerbi-visuals-utils-testutils": "^0.2.2", - "powerbi-visuals-utils-tooltiputils": "^0.3.0", + "powerbi-visuals-utils-tooltiputils": "^1.0.0", + "powerbi-visuals-utils-formattingutils": "^2.1.0", "tslint": "^4.4.2", "tslint-microsoft-contrib": "^4.0.0", "typescript": "2.1.4" diff --git a/pbiviz.json b/pbiviz.json index 1e25d40..4b769b7 100644 --- a/pbiviz.json +++ b/pbiviz.json @@ -4,12 +4,12 @@ "displayName": "Bullet Chart", "guid": "BulletChart1443347686880", "visualClassName": "BulletChart", - "version": "1.4.8", + "version": "1.4.9", "description": "A bullet chart that includes four orientations and a few customization options. Use to feature a single measure against a qualitative range.", "supportUrl": "http://community.powerbi.com", "gitHubUrl": "https://github.com/Microsoft/powerbi-visuals-bulletchart" }, - "apiVersion": "1.6.0", + "apiVersion": "1.7.0", "author": { "name": "Microsoft", "email": "pbicvsupport@microsoft.com" @@ -18,7 +18,6 @@ "icon": "assets/icon.png" }, "externalJS": [ - "node_modules/jquery/dist/jquery.min.js", "node_modules/lodash/lodash.min.js", "node_modules/d3/d3.js", "node_modules/powerbi-visuals-utils-typeutils/lib/index.js", diff --git a/src/columns.ts b/src/columns.ts index cb49717..18d24fa 100644 --- a/src/columns.ts +++ b/src/columns.ts @@ -73,7 +73,7 @@ module powerbi.extensibility.visual { }); } - return useHighlightAsValue ? (x).highlights : x.values; + return useHighlightAsValue ? (x).highlights : (x).values; })[0] || values.source && values.source.roles && values.source.roles[i] && series); } diff --git a/src/settings.ts b/src/settings.ts index fb2056f..4de7cdf 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -50,6 +50,7 @@ module powerbi.extensibility.visual { public show: boolean = true; public labelColor: string = "Black"; public fontSize: number = 11; + public maxWidth: number = 80; } export enum BulletChartOrientation { diff --git a/src/visual.ts b/src/visual.ts index 3949349..05bd771 100644 --- a/src/visual.ts +++ b/src/visual.ts @@ -69,7 +69,7 @@ module powerbi.extensibility.visual { private static SpaceRequiredForBarVertically: number = 100; private static XMarginHorizontalLeft: number = 20; private static XMarginHorizontalRight: number = 55; - private static YMarginHorizontal: number = 30; + private static YMarginHorizontal: number = 20; private static XMarginVertical: number = 70; private static YMarginVertical: number = 10; private static BulletSize: number = 25; @@ -80,7 +80,8 @@ module powerbi.extensibility.visual { private static SubtitleMargin: number = 10; private static AxisFontSizeInPt: number = 8; private static SecondTargetLineSize: number = 7; - private static MarkerMarginHorizontal: number = BulletChart.BulletSize / 3; + private static MarkerMarginHorizontal: number = BulletChart.BulletSize / 6; + private static MarkerMarginHorizontalEnd: number = 5 * BulletChart.MarkerMarginHorizontal; private static MarkerMarginVertical: number = BulletChart.BulletSize / 4; private static FontFamily: string = "Segoe UI"; private baselineDelta: number = 0; @@ -131,8 +132,11 @@ module powerbi.extensibility.visual { }; } private static value1dot4: number = 1.4; + private static categoryLabelModifier: number = 1.25; private static value2: number = 2; + private static value15: number = 15; private static value20: number = 20; + private static value28: number = 28; private static value60: number = 60; private static emptyString: string = ""; // Convert a DataView into a view model @@ -165,10 +169,10 @@ module powerbi.extensibility.visual { bulletModel.labelHeight = (settings.labels.show || BulletChart.zeroValue) && parseFloat(PixelConverter.fromPoint(settings.labels.fontSize)); bulletModel.labelHeightTop = (settings.labels.show || BulletChart.zeroValue) && parseFloat(PixelConverter.fromPoint(settings.labels.fontSize)) / BulletChart.value1dot4; - bulletModel.spaceRequiredForBarHorizontally = Math.max(BulletChart.value60, bulletModel.labelHeight + BulletChart.value20); + bulletModel.spaceRequiredForBarHorizontally = Math.max(settings.axis.axis ? BulletChart.value60 : BulletChart.value28, bulletModel.labelHeight * BulletChart.categoryLabelModifier); bulletModel.viewportLength = Math.max(0, (verticalOrientation ? (options.viewport.height - bulletModel.labelHeightTop - BulletChart.SubtitleMargin - BulletChart.value20 - BulletChart.YMarginVertical * BulletChart.value2) - : (options.viewport.width - BulletChart.MaxLabelWidth - BulletChart.XMarginHorizontalLeft - BulletChart.XMarginHorizontalRight)) - BulletChart.ScrollBarSize); + : (options.viewport.width - (settings.labels.show ? settings.labels.maxWidth : 0) - BulletChart.XMarginHorizontalLeft - BulletChart.XMarginHorizontalRight)) - BulletChart.ScrollBarSize); bulletModel.hasHighlights = !!(categorical.Value[0].values.length > BulletChart.zeroValue && categorical.Value[0].highlights); let valueFormatString: string = valueFormatter.getFormatStringByColumn(categorical.Value[0].source, true); @@ -180,7 +184,7 @@ module powerbi.extensibility.visual { category = valueFormatter.format(categoricalValues.Category[idx], categoryFormatString); category = TextMeasurementService.getTailoredTextOrDefault( BulletChart.getTextProperties(category, settings.labels.fontSize), - BulletChart.MaxLabelWidth); + verticalOrientation ? this.MaxLabelWidth : settings.labels.maxWidth); } let toolTipItems: BulletChartTooltipItem[] = [], @@ -342,7 +346,6 @@ module powerbi.extensibility.visual { }); let xAxisProperties: IAxisProperties = null; - if (settings.axis.axis) { xAxisProperties = AxisHelper.createAxis({ pixelSpan: bulletModel.viewportLength, @@ -354,7 +357,8 @@ module powerbi.extensibility.visual { isVertical: verticalOrientation, isCategoryAxis: false, scaleType: axisScale.linear, - disableNice: true + forcedTickCount: BulletChart.getFitTicksCount(bulletModel.viewportLength), + disableNiceOnlyForScale: true }); } @@ -382,6 +386,10 @@ module powerbi.extensibility.visual { if (settings.values.minimumPercent > settings.values.maximumPercent) { settings.values.maximumPercent = settings.values.minimumPercent; } + + if (settings.labels.maxWidth <= 0) { + settings.labels.maxWidth = this.MaxLabelWidth; + } } private get settings(): BulletchartSettings { @@ -398,6 +406,18 @@ module powerbi.extensibility.visual { options); } + public static getFitTicksCount(viewportLength: number): number { + if (viewportLength < 35) { + return 1; + } else if (viewportLength < 150) { + return 3; + } else if (viewportLength < 300) { + return 5; + } + + return 12; + } + private static addItemToBarArray( collection: BarRect[], barIndex: number, @@ -513,7 +533,7 @@ module powerbi.extensibility.visual { else { this.scrollContainer.attr({ - height: (this.data.bars.length * (this.data.spaceRequiredForBarHorizontally || BulletChart.zeroValue)) + "px", + height: (this.data.bars.length * (this.data.spaceRequiredForBarHorizontally || BulletChart.zeroValue) + BulletChart.YMarginHorizontal) + "px", width: PixelConverter.toString(this.viewportScroll.width) }); } @@ -550,7 +570,7 @@ module powerbi.extensibility.visual { private calculateLabelWidth(barData: BarData, bar?: BarRect, reversed?: boolean) { return (reversed ? BulletChart.XMarginHorizontalRight - : barData.x + BulletChart.MaxLabelWidth + BulletChart.XMarginHorizontalLeft) + : barData.x + (this.settings.labels.show ? this.settings.labels.maxWidth : 0) + BulletChart.XMarginHorizontalLeft) + (bar ? bar.start : BulletChart.zeroValue); } private static value5: number = 5; @@ -563,6 +583,7 @@ module powerbi.extensibility.visual { private static value1: number = 1; private static value4: number = 4; private static value12: number = 12; + private static bulletMiddlePosition: number = (1 / BulletChart.value8 + 1 / BulletChart.value4) * BulletChart.BulletSize; private setUpBulletsHorizontally(bulletBody: d3.Selection, model: BulletChartModel, reveresed: boolean): void { let bars: BarData[] = model.bars; let rects: BarRect[] = model.barRects; @@ -573,7 +594,7 @@ module powerbi.extensibility.visual { // Draw bullets let bullets: d3.Selection = rectSelection.enter().append("rect").attr({ "x": ((d: BarRect) => Math.max(BulletChart.zeroValue, this.calculateLabelWidth(bars[d.barIndex], d, reveresed))), - "y": ((d: BarRect) => Math.max(BulletChart.zeroValue, bars[d.barIndex].y - BulletChart.BulletSize / BulletChart.value2)), + "y": ((d: BarRect) => Math.max(BulletChart.zeroValue, bars[d.barIndex].y)), "width": ((d: BarRect) => Math.max(BulletChart.zeroValue, d.end - d.start)), "height": BulletChart.BulletSize, }).classed("range", true).style({ @@ -586,7 +607,7 @@ module powerbi.extensibility.visual { let valueSelection: any = this.bulletGraphicsContext.selectAll("rect.value").data(valueRects, (d: BarValueRect) => d.key); valueSelection.enter().append("rect").attr({ "x": ((d: BarValueRect) => Math.max(BulletChart.zeroValue, this.calculateLabelWidth(bars[d.barIndex], d, reveresed))), - "y": ((d: BarValueRect) => Math.max(BulletChart.zeroValue, bars[d.barIndex].y - BulletChart.BulletSize / BulletChart.value8)), + "y": ((d: BarValueRect) => Math.max(BulletChart.zeroValue, bars[d.barIndex].y + BulletChart.bulletMiddlePosition)), "width": ((d: BarValueRect) => Math.max(BulletChart.zeroValue, d.end - d.start)), "height": BulletChart.BulletSize * BulletChart.value1 / BulletChart.value4, }).classed("value", true).style({ @@ -598,13 +619,13 @@ module powerbi.extensibility.visual { this.drawFirstTargets(targetValues, (d: TargetValue) => this.calculateLabelWidth(bars[d.barIndex], null, reveresed) + d.value, (d: TargetValue) => this.calculateLabelWidth(bars[d.barIndex], null, reveresed) + d.value, - (d: TargetValue) => bars[d.barIndex].y - BulletChart.MarkerMarginHorizontal, - (d: TargetValue) => bars[d.barIndex].y + BulletChart.MarkerMarginHorizontal); + (d: TargetValue) => bars[d.barIndex].y + BulletChart.MarkerMarginHorizontal, + (d: TargetValue) => bars[d.barIndex].y + BulletChart.MarkerMarginHorizontalEnd); this.drawSecondTargets( targetValues, (d: TargetValue) => this.calculateLabelWidth(bars[d.barIndex], null, reveresed) + d.value2, - (d: TargetValue) => bars[d.barIndex].y); + (d: TargetValue) => bars[d.barIndex].y + BulletChart.BulletSize / BulletChart.value2); // Draw axes if (model.settings.axis.axis) { @@ -617,7 +638,7 @@ module powerbi.extensibility.visual { barGroup.append("g").attr({ "transform": () => { let xLocation: number = this.calculateLabelWidth(bar, null, reveresed); - let yLocation: number = bar.y + BulletChart.BulletSize / BulletChart.value2; + let yLocation: number = bar.y + BulletChart.BulletSize; return "translate(" + xLocation + "," + yLocation + ")"; }, @@ -643,7 +664,7 @@ module powerbi.extensibility.visual { return BulletChart.XMarginHorizontalLeft + BulletChart.XMarginHorizontalRight + model.viewportLength; return d.x; }), - "y": ((d: BarData) => d.y + this.baselineDelta), + "y": ((d: BarData) => d.y + this.baselineDelta + BulletChart.BulletSize / 2), "fill": model.settings.labels.labelColor, "font-size": PixelConverter.fromPoint(model.settings.labels.fontSize), }).text((d: BarData) => d.categoryLabel); @@ -716,7 +737,7 @@ module powerbi.extensibility.visual { // Draw value rects let valueSelection: any = this.bulletGraphicsContext.selectAll("rect.value").data(valueRects, (d: BarValueRect) => d.key); valueSelection.enter().append("rect").attr({ - "x": ((d: BarValueRect) => Math.max(BulletChart.zeroValue, bars[d.barIndex].x + BulletChart.BulletSize / BulletChart.value3)), + "x": ((d: BarValueRect) => Math.max(BulletChart.zeroValue, bars[d.barIndex].x + BulletChart.bulletMiddlePosition)), "y": ((d: BarValueRect) => Math.max(BulletChart.zeroValue, this.calculateLabelHeight(bars[d.barIndex], d, reveresed))), "height": ((d: BarValueRect) => Math.max(BulletChart.zeroValue, d.start - d.end)), "width": BulletChart.BulletSize * BulletChart.value1 / BulletChart.value4, @@ -735,7 +756,7 @@ module powerbi.extensibility.visual { (d: TargetValue) => this.calculateLabelHeight(bars[d.barIndex], null, reveresed) + d.value); this.drawSecondTargets(targetValues, - (d: TargetValue) => bars[d.barIndex].x + BulletChart.BulletSize / BulletChart.value3 + BulletChart.BulletSize / BulletChart.value8, + (d: TargetValue) => bars[d.barIndex].x + BulletChart.BulletSize / BulletChart.value2, (d: TargetValue) => this.calculateLabelHeight(bars[d.barIndex], null, reveresed) + d.value2); // // Draw axes @@ -911,10 +932,9 @@ module powerbi.extensibility.visual { if (spanElement) return; - spanElement = $(""); - $("body").append(spanElement); + d3.select("body").append("span"); // The style hides the svg element from the canvas, preventing canvas from scrolling down to show svg black square. - svgTextElement = d3.select($("body").get(0)) + svgTextElement = d3.select(d3.select("body")[0][0]) .append("svg") .style({ "height": "0px", @@ -922,7 +942,8 @@ module powerbi.extensibility.visual { "position": "absolute" }) .append("text"); - canvasCtx = ($("").get(0)).getContext("2d"); + + canvasCtx = (document.createElement("canvas")).getContext("2d"); } function measureSvgTextRect(textProperties: TextProperties): SVGRect { diff --git a/stringResources/en-US/resources.resjson b/stringResources/en-US/resources.resjson index db75d91..3ee5008 100644 --- a/stringResources/en-US/resources.resjson +++ b/stringResources/en-US/resources.resjson @@ -27,5 +27,6 @@ "Visual_Axis": "Axis", "Visual_AxisColor": "Axis color", "Visual_MeasureUnits": "Measure Units", - "Visual_UnitsColor": "Units color" + "Visual_UnitsColor": "Units color", + "Visual_MaxWidth": "Maximum width" } \ No newline at end of file diff --git a/test/visualTest.ts b/test/visualTest.ts index ef72991..44f9035 100644 --- a/test/visualTest.ts +++ b/test/visualTest.ts @@ -124,6 +124,30 @@ module powerbi.extensibility.visual.test { expect(visualBuilder.mainElement[0].getBoundingClientRect().width).toBe(0); }); + it("should be smaller gap between bullets if axis is not rendered", () => { + visualBuilder.updateFlushAllD3Transitions(dataView); + + let rangeRects: any = visualBuilder.rangeRects; + let yArray: number[] = rangeRects.map((i, e) => { + return parseFloat($(e).attr("y")); + }); + + dataView.metadata.objects = { + axis: { + axis: false + } + }; + + visualBuilder.updateFlushAllD3Transitions(dataView); + + rangeRects = visualBuilder.rangeRects; + let yArrayWithNoAxis: any = rangeRects.map((i, e) => { + return parseFloat($(e).attr("y")); + }); + + expect(yArray[yArray.length - 1]).toBeGreaterThan(yArrayWithNoAxis[yArrayWithNoAxis.length - 1]); + }); + it("only defined ranges should be visible", (done) => { dataView = defaultDataViewBuilder.getDataView([ BulletChartData.ColumnCategory, @@ -459,6 +483,39 @@ module powerbi.extensibility.visual.test { }); }); }); + + describe("tick count tests", () => { + it("should calculate fit count of ticks using viewport length", () => { + const tinyViewportLength: number = 10, + smallViewportLength: number = 100, + mediumViewportLength: number = 200, + bigViewportLength: number = 500, + lengthArray: number[] = [tinyViewportLength, smallViewportLength, mediumViewportLength, bigViewportLength]; + + lengthArray.forEach((x) => { + expect(VisualClass.getFitTicksCount(x)).toBeGreaterThan(0); + }); + }); + }); + + describe("formatting option limitation test", () => { + it("should limit values correctly", () => { + dataView.metadata.objects = { + values: { + minimumPercent: 100, + maximumPercent: 0 + }, + labels: { + maxWidth: 0 + } + }; + + visualBuilder.updateFlushAllD3Transitions(dataView); + let sett = visualBuilder.getSettings(); + expect(sett.values.minimumPercent).not.toBeGreaterThan(sett.values.maximumPercent); + expect(sett.labels.maxWidth).not.toBe(0); + }); + }); }); function doColorsEqual(firstColor: string, secondColor: string): boolean { diff --git a/tsconfig.json b/tsconfig.json index b7c1668..04bdec2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,7 +10,7 @@ "declaration": true }, "files": [ - ".api/v1.6.0/PowerBI-visuals.d.ts", + ".api/v1.7.0/PowerBI-visuals.d.ts", "node_modules/powerbi-visuals-utils-formattingutils/lib/index.d.ts", "node_modules/powerbi-visuals-utils-interactivityutils/lib/index.d.ts", "node_modules/powerbi-visuals-utils-typeutils/lib/index.d.ts",