diff --git a/CHANGELOG.md b/CHANGELOG.md index d079114..93ec907 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,6 @@ +## 1.1.0 + * Added ability to use hours, minutes and seconds in a 'duration' ## 1.0.2 * Fixed tooltip date format not respected ## 1.0.1 -* Fixed start date calculation \ No newline at end of file + * Fixed start date calculation \ No newline at end of file diff --git a/capabilities.json b/capabilities.json index 6717572..f5016c0 100644 --- a/capabilities.json +++ b/capabilities.json @@ -186,6 +186,34 @@ "type": { "bool": true } + }, + "durationUnit": { + "displayName": "Duration unit", + "displayNameKey": "Visual_DurationUnit", + "type": { + "enumeration": [ + { + "value": "day", + "displayName": "Days", + "displayNameKey": "Visual_DurationUnit_Days" + }, + { + "value": "hour", + "displayName": "Hours", + "displayNameKey": "Visual_DurationUnit_Hours" + }, + { + "value": "minute", + "displayName": "Minutes", + "displayNameKey": "Visual_DurationUnit_Minutes" + }, + { + "value": "second", + "displayName": "Seconds", + "displayNameKey": "Visual_DurationUnit_Seconds" + } + ] + } } } }, diff --git a/package.json b/package.json index 4ad3b27..93b7445 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "powerbi-visuals-gantt", - "version": "1.0.2", + "version": "1.1.0", "description": "A Gantt chart is a type of bar chart which illustrates a project timeline or schedule. The Gantt Chart visual shows the Tasks, Start Dates, Durations, % Complete, and Resources for a project. The Gantt Chart visual can be used to show current schedule status using percent-complete shadings and a vertical \"TODAY\" line. The Legend may be used to group or filter tasks based upon data values.", "repository": { "type": "git", diff --git a/pbiviz.json b/pbiviz.json index ba150bb..359c42a 100644 --- a/pbiviz.json +++ b/pbiviz.json @@ -4,7 +4,7 @@ "displayName": "Gantt", "guid": "Gantt1448688115699", "visualClassName": "Gantt", - "version": "1.0.2", + "version": "1.1.0", "description": "A Gantt chart is a type of bar chart which illustrates a project timeline or schedule. The Gantt Chart visual shows the Tasks, Start Dates, Durations, % Complete, and Resources for a project. The Gantt Chart visual can be used to show current schedule status using percent-complete shadings and a vertical \"TODAY\" line. The Legend may be used to group or filter tasks based upon data values.", "supportUrl": "http://community.powerbi.com", "gitHubUrl": "https://github.com/Microsoft/powerbi-visuals-gantt" diff --git a/src/gantt.ts b/src/gantt.ts index 2dc7359..0e3f908 100644 --- a/src/gantt.ts +++ b/src/gantt.ts @@ -106,7 +106,13 @@ module powerbi.extensibility.visual { const MillisecondsInAYear: number = 365 * MillisecondsInADay; const ChartLineHeight: number = 40; const PaddingTasks: number = 5; - const numberFormat = "#"; + + const GanttDurationUnitType = [ + "day", + "hour", + "minute", + "second" + ]; export interface Task extends SelectableDataPoint { id: number; @@ -131,7 +137,6 @@ module powerbi.extensibility.visual { export interface GanttChartFormatters { startDateFormatter: IValueFormatter; completionFormatter: IValueFormatter; - durationFormatter: IValueFormatter; } export interface GanttViewModel { @@ -428,7 +433,7 @@ module powerbi.extensibility.visual { * @param task All task attributes. * @param formatters Formatting options for gantt attributes. */ - private static getTooltipInfo(task: Task, locale: string, formatters: GanttChartFormatters, timeInterval: string = "Days"): VisualTooltipDataItem[] { + private static getTooltipInfo(task: Task, locale: string, formatters: GanttChartFormatters, durationUnit: string): VisualTooltipDataItem[] { let tooltipDataArray: VisualTooltipDataItem[] = []; if (task.taskType) { @@ -440,7 +445,9 @@ module powerbi.extensibility.visual { tooltipDataArray.push({ displayName: "Start Date", value: formatters.startDateFormatter.format(task.start) }); } - tooltipDataArray.push({ displayName: "Duration", value: `${formatters.durationFormatter.format(task.duration)} ${timeInterval}` }); + const durationLabel: string = this.generateLabelForDuration(task.duration, durationUnit); + + tooltipDataArray.push({ displayName: "Duration", value: durationLabel }); tooltipDataArray.push({ displayName: "Completion", value: formatters.completionFormatter.format(task.completion) }); if (task.resource) { @@ -491,7 +498,6 @@ module powerbi.extensibility.visual { return { startDateFormatter: ValueFormatter.create({ format: dateFormat, cultureSelector }), - durationFormatter: ValueFormatter.create({ format: numberFormat }), completionFormatter: ValueFormatter.create({ format: PercentFormat, value: 1, allowFormatBeautification: true }) }; } @@ -530,7 +536,8 @@ module powerbi.extensibility.visual { taskTypes: TaskTypes, host: IVisualHost, formatters: GanttChartFormatters, - colors: IColorPalette + colors: IColorPalette, + settings: GanttSettings ): Task[] { const tasks: Task[] = []; const colorHelper: ColorHelper = new ColorHelper( @@ -589,8 +596,11 @@ module powerbi.extensibility.visual { identity: selectionId }; - task.end = d3.time.day.offset(task.start, task.duration); - task.tooltipInfo = Gantt.getTooltipInfo(task, host.locale, formatters); + let durationUnit = settings.general.durationUnit; + durationUnit = (GanttDurationUnitType.indexOf(durationUnit) !== -1 && durationUnit) || "day"; + + task.end = d3.time[durationUnit].offset(task.start, task.duration); + task.tooltipInfo = Gantt.getTooltipInfo(task, host.locale, formatters, settings.general.durationUnit); tasks.push(task); } @@ -601,6 +611,45 @@ module powerbi.extensibility.visual { return tasks; } + /** + * Generate 'Duration' label for tooltip + * @param duration The duration of task + * @param durationUnit The duration unit for chart + */ + private static generateLabelForDuration(duration: number, durationUnit: string): string { + let label: string = ""; + + const days: number = Math.floor(duration / 24); + label += days ? `${days} Days ` : ``; + if (durationUnit === "day") { + return `${duration} Days `; + } + + const hours: number = duration - (days * 24); + label += hours ? `${hours} Hours ` : ``; + if (durationUnit === "hour") { + return duration >= 24 + ? label + : `${duration} Hours`; + } + + const minutes: number = duration - ((days * 24) + (hours * 60)); + label += minutes ? `${minutes} Minutes ` : ``; + if (durationUnit === "minute") { + return duration >= 60 + ? label + : `${duration} Minutes `; + } + + const seconds: number = duration - (days * 24 + hours * 60 + minutes * 60); + label += seconds ? `${seconds} Seconds ` : ``; + if (durationUnit === "second") { + return duration >= 60 + ? label + : `${duration} Seconds `; + } + } + /** * Convert the dataView to view model * @param dataView The data Model @@ -617,7 +666,7 @@ module powerbi.extensibility.visual { const taskTypes: TaskTypes = Gantt.getAllTasksTypes(dataView) , formatters: GanttChartFormatters = this.getFormatters(dataView, host.locale || null) - , tasks: Task[] = Gantt.createTasks(dataView, taskTypes, host, formatters, colors); + , tasks: Task[] = Gantt.createTasks(dataView, taskTypes, host, formatters, colors, settings); return { dataView, diff --git a/src/settings.ts b/src/settings.ts index ca1c881..86255d1 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -40,6 +40,7 @@ module powerbi.extensibility.visual { export class GeneralSettings { groupTasks: boolean = false; + durationUnit: string = "day"; } export class LegendSettings { diff --git a/test/visualData.ts b/test/visualData.ts index 5ac9d3c..e16ee9c 100644 --- a/test/visualData.ts +++ b/test/visualData.ts @@ -86,7 +86,7 @@ module powerbi.extensibility.visual.test { public static getRandomUniqueNumbers(count: number, min: number = 0, max: number = 1): number[] { let result: number[] = []; for (let i: number = 0; i < count; i++) { - result.push(getRandomNumber(min, max, result)); + result.push(Math.floor(getRandomNumber(min, max, result))); } return result; diff --git a/test/visualTest.ts b/test/visualTest.ts index 0f1b712..d95a449 100644 --- a/test/visualTest.ts +++ b/test/visualTest.ts @@ -313,6 +313,74 @@ module powerbi.extensibility.visual.test { }); describe("Format settings test", () => { + describe("General", () => { + describe("Duration units", () => { + + function checkDurationUnit(durationUnit: string) { + const tasks: Task[] = d3 + .select(visualBuilder.element.get(0)) + .selectAll(".task") + .data(); + + tasks.forEach(task => { + const dates: Date[] = d3 + .time[durationUnit] + .range(task.start, task.end); + expect(dates.length).toEqual(task.duration); + }); + } + + function setDurationUnit(durationUnit) { + dataView.metadata.objects = { + general: { + durationUnit: durationUnit + } + }; + } + + it("days", (done) => { + let durationUnit: string = "day"; + setDurationUnit(durationUnit); + + visualBuilder.updateRenderTimeout(dataView, () => { + checkDurationUnit(durationUnit); + done(); + }); + }); + + it("hours", (done) => { + let durationUnit: string = "hour"; + setDurationUnit(durationUnit); + + visualBuilder.updateRenderTimeout(dataView, () => { + checkDurationUnit(durationUnit); + done(); + }); + }); + + it("minutes", (done) => { + let durationUnit: string = "minute"; + setDurationUnit(durationUnit); + + visualBuilder.updateRenderTimeout(dataView, () => { + checkDurationUnit(durationUnit); + done(); + }); + }); + + it("seconds", (done) => { + let durationUnit: string = "second"; + setDurationUnit(durationUnit); + + visualBuilder.updateRenderTimeout(dataView, () => { + checkDurationUnit(durationUnit); + done(); + }); + }); + + }); + }); + describe("Data labels", () => { beforeEach(() => { dataView.metadata.objects = {