{"id":26,"date":"2017-07-29T10:07:00","date_gmt":"2017-07-29T10:07:00","guid":{"rendered":"\/lisa\/post\/2017\/07\/29\/Walkthrough-SQL-not-Range-Bars-is-the-Secret-Sauce-to-SSRS-Gantt-Charts.aspx"},"modified":"2023-02-02T07:34:44","modified_gmt":"2023-02-02T15:34:44","slug":"walkthrough-sql-not-range-bars-is-the-secret-sauce-to-ssrs-gantt-charts","status":"publish","type":"post","link":"https:\/\/spacefold.com\/lisa\/2017\/07\/29\/walkthrough-sql-not-range-bars-is-the-secret-sauce-to-ssrs-gantt-charts\/","title":{"rendered":"Walkthrough: SQL, not Range Bars, is the Secret Sauce to SSRS Gantt Charts"},"content":{"rendered":"<p>I&#8217;ve seen numerous people advocate the use of Range Bars for Gantt charts in SSRS. \u00a0Well, yes, of the natively supplied widgets, it does best fit the bill. \u00a0But it&#8217;s not great.<\/p>\n<p>It&#8217;s probably fine for some types of Gantt, but it doesn&#8217;t work if you want to show resources mapped to a calendar grid. MS Project, at least at one time, called this activity <strong>Team Planning<\/strong>\u00a0, but you might want to do it for additional resources such as room or equipment use. \u00a0Here&#8217;s an example, from\u00a0<a title=\"MS project guru talks about Team Planning resource view\" href=\"https:\/\/www.mpug.com\/articles\/the-best-resource-workload-views-in-microsoft-project-2010\/\" target=\"_blank\" rel=\"noopener\">https:\/\/www.mpug.com\/articles\/the-best-resource-workload-views-in-microsoft-project-2010\/<\/a>:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-623 aligncenter\" src=\"https:\/\/spacefold.com\/lisa\/wp-content\/uploads\/2017\/07\/GanttExample-1-300x169.png\" alt=\"\" width=\"350\" height=\"197\" srcset=\"https:\/\/spacefold.com\/lisa\/wp-content\/uploads\/2017\/07\/GanttExample-1-300x169.png 300w, https:\/\/spacefold.com\/lisa\/wp-content\/uploads\/2017\/07\/GanttExample-1-1024x576.png 1024w, https:\/\/spacefold.com\/lisa\/wp-content\/uploads\/2017\/07\/GanttExample-1-768x432.png 768w, https:\/\/spacefold.com\/lisa\/wp-content\/uploads\/2017\/07\/GanttExample-1.png 1140w\" sizes=\"auto, (max-width: 350px) 100vw, 350px\" \/><\/p>\n<p>The problem is getting the bars that belong to one resource horizontally on the same line. \u00a0Yes, I know you can use the Range Bar Custom Attribute DrawSideBySide to this for some extent &#8212; but that only works if the two tasks are contiguous. \u00a0It doesn&#8217;t seem to work where you have a task, and then some empty space representing an unallocated period of time, followed by another task.<\/p>\n<h2>SQL to the Rescue<\/h2>\n<p>It turns out you can do this with a garden variety matrix in SSRS, with a little more work on the query side. \u00a0I&#8217;ve now done this particular trick in a bunch of different contexts, with variations, and it works a treat.<\/p>\n<p>As usual, you have some choices you can make about where you want to put the complexity: do you want to code more in the query? \u00a0Or more in the RDL design code? \u00a0In this walkthrough, I&#8217;m going to put all the complexity on the SQL side; in fact, I&#8217;ll use the Report Wizard for dashing the matrix together at the end, and just adjust a couple of default attributes on objects in the RDL afterwards. \u00a0There&#8217;s hardly an IIF() expression in site. \u00a0But once you see the trick to it, you can adjust the balance of coding and do more in the Designer, if you prefer.<\/p>\n<h4>Limitations?<\/h4>\n<p>The only one I can think of is this: \u00a0there is a width minimum for a column in an RDL matrix, so you&#8217;ll find &#8212; even if you minimize padding and borders &#8212; you can&#8217;t use this trick if your durations are in the minute or second range, because 60 * the column width and you go off into hyperspace on the right pretty quickly. \u00a0I found this out trying to capture some sub-minute device readings for <a title=\"Poseidon - my not-during-the-day gig\" href=\"http:\/\/www.poseidonsaltwatersystems.com\" target=\"_blank\" rel=\"noopener\">Poseidon<\/a> &#8212; in the end, we&#8217;ve settled for not having the proper horizontal orientation rather than have very, very wide reports.<\/p>\n<p>n this walkthrough I&#8217;ll use a half-hour as the lowest level duration that we can display, and that seems to work fine.<\/p>\n<h2>Setup<\/h2>\n<p>I&#8217;ve gotten a little tired of using the <a href=\"\/wp-content\/downloads\/MSWorld.zip\">Sql World database<\/a> for examples lately, and besides it doesn&#8217;t have interesting datetimes, so I&#8217;ve finally succumbed to<a title=\"Go on, read Brent -- you'll be glad you did\" href=\"http:\/\/www.brentozar.com\" target=\"_blank\" rel=\"noopener\"> Brent Ozar<\/a>&#8216;s blandishments and downloaded <a title=\"Stack Overflow data files\" href=\"https:\/\/archive.org\/download\/stackexchange\" target=\"_blank\" rel=\"noopener\">a small subset of the Stack Overflow data dump<\/a>\u00a0&#8212; I used the dba subset, natch. \u00a0I used the <a href=\"https:\/\/github.com\/BrentOzarULTD\/soddi\">SODDI importer tool <\/a>to load it, which worked fine.<\/p>\n<p>You&#8217;ll need to add a Date Dimension table of some sort to this, if you don&#8217;t already have one. \u00a0Everybody has their favorite way of doing this; I used <a title=\"Calendar table code\" href=\"https:\/\/www.mssqltips.com\/sqlservertip\/4054\/creating-a-date-dimension-or-calendar-table-in-sql-server\/\" target=\"_blank\" rel=\"noopener\">this one<\/a>\u00a0because I always find its holiday and other extra data is handy, especially for <a title=\"Dominican University of California, my day gig\" href=\"http:\/\/www.dominican.edu\" target=\"_blank\" rel=\"noopener\">Dominican<\/a> work.<\/p>\n<p>What we&#8217;re going to do, with this data, is look at the Posts or threads available for various Locations as provided by users when they login to add content. We&#8217;ll treat the original poster&#8217;s Location as a Resource, the date the Post was created as the Start of the thread, and the max of the Comments in this thread during the current query period as the End of the thread. \u00a0 This is fairly contrived, but grouping people together by Location gives us a reasonable number of &#8220;resources&#8221; and a critical mass of data per-resource.<\/p>\n<h4>User parameters you&#8217;ll probably want to add<\/h4>\n<p>I&#8217;ve included some parameter values directly in the query as variables &#8212; for your purposes, these are the ones that I thought would be useful to expose in a real report:<\/p>\n<pre class=\"code\">-- parameters\r\nDECLARE @HoursBottom AS TINYINT = 8;\r\nDECLARE @HoursTop AS TINYINT = 18;\r\nDECLARE @DateBottom AS DATE = '1\/10\/2017';\r\nDECLARE @DateTop AS DATE = '1\/13\/2017';\r\nDECLARE @maxColors AS TINYINT = 7;\r\nDECLARE @minItems AS TINYINT = 2;\r\nDECLARE @nullReplacement as VARCHAR(3) = 'Unknown' ;\r\n---- end parameters<\/pre>\n<ul>\n<li><em>time period of interest (bottom and top hours). <\/em>These are expressed as hours, from 0 to 24, and would normally be a dropdown type parameter if you wanted to highlight a significant slice of the day, rather than all 24 hours.<br \/>\n<table border=\"0\">\n<tbody>\n<tr>\n<td>Note that, while StackOverflow obviously operates 24&#215;7, in my example parameter values,<br \/>\nI&#8217;ve chosen a reasonably busy slice of its hours across all Locations,<br \/>\nto show sufficient contrasting activity in the &#8220;graph&#8221;. \u00a0(All times are stored in UTC.)<\/td>\n<td><a title=\"click to see fullsize\" href=\"\/lisa\/wp-non\/migrated\/2017\/7\/HoursTopBottom.png\" target=\"_blank\" rel=\"noopener\"><img decoding=\"async\" class=\"null\" src=\"\/lisa\/wp-non\/migrated\/2017\/7\/HoursTopBottom.png\" alt=\"\" width=\"20%\" \/><\/a><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/li>\n<li><em>bottom and top dates<\/em>. \u00a0There&#8217;s almost always a time period you want to use.<\/li>\n<li><em>maxColors<\/em>. \u00a0while there may be many resources displayed, this represents the set of colors you&#8217;d like to cycle through. \u00a0In my example, there is a set of 7 colors in a CTE, so the 8th row on the &#8220;graph&#8221; is going to start with the first color again, and this number is known. \u00a0But you might want to expose this to users, or derive it in code, if you&#8217;re data-driving your colors, or generating the values.<\/li>\n<li><em>minItems<\/em>. \u00a0There are so many Locations registered by users on StackOverflow, I chose not to display Locations that have less than 2 Posts in my chosen time period. \u00a0You might want to show all activity for your resources, and this number would be 0 for you.<\/li>\n<li><em>null resource ID replacement.<\/em>\u00a0 This may not be useful for you either, but if it&#8217;s possible for you to have (say) unassigned tasks, and if you want to include them on the &#8220;graph&#8221;, you need to provide a value representing the &#8220;null resource ID&#8221; for your grouped rows. \u00a0In my example, many, many people don&#8217;t provide their Locations at all, and since this is the most numerous set by far, I didn&#8217;t want to leave it out of the picture. If you need this, it&#8217;s likely a hidden parameter. See next item why you might want to declare it outside the query in a hidden parameter.<\/li>\n<li><em>not included above, but possibly useful:<\/em>\u00a0 you may want to provide a multi-select dropdown of feasible resources, so that the user can pick which rows they want to see. \u00a0In my case, the query supporting this multi-select parameter might look something like this (untested!):\n<pre class=\"code\">SELECT isnull(Location,@nullReplacement) as Value,\r\nisnull(Location,@nullReplacement) + \r\n    ' (' + cast(count(*) as varchar(10)) + ') ' as Label\r\nFROM Users u\r\nJOIN Comments c \u00a0ON u.ID = c.UserId\r\nWHERE c.CreationDate BETWEEN @DateBottom AND @DateTop\r\nGROUP BY Location\r\nHAVING COUNT(*) &gt;= @minItems\r\nORDER BY COUNT(*) DESC ;<\/pre>\n<\/li>\n<\/ul>\n<h2>Let&#8217;s get started!<\/h2>\n<p>The first part of the query is a set of 3 CTEs that serves to identify a set of resource rows and assign sorting and colors to them.<\/p>\n<p class=\"NB\">Note that I&#8217;ve used CTEs at the beginning of this query, and switched to temp tables for the latter part of it. \u00a0This is purely a matter of taste, in many cases, but here I&#8217;m using CTEs for some very inconsequential data and then switching over to temp tables for large datasets.<\/p>\n<pre class=\"code\">with resourceColors as (\r\nselect 0 as resourceColorNumber, 'PaleVioletRed' as resourceColor\r\nunion all\r\nselect 1, 'LightSalmon' as resourceColor\r\nunion all\r\nselect 2, 'Tan' as resourceColor\r\nunion all\r\nselect 3, 'DarkSeaGreen' as resourceColor\r\nunion all\r\nselect 4, 'LightSteelBlue' as resourceColor\r\nunion all\r\nselect 5, 'CornflowerBlue' as resourceColor\r\nunion all\r\nselect 6, 'Plum' as resourceColor ) ,\r\nresources as (\r\nselect row_number() over (order by count(*) desc ) as resourceNumber,\r\nisnull(Location, @nullReplacement) as resourceID\r\nfrom Users u\r\njoin Posts p on u.id = p.OwnerUserId\r\njoin (\r\nselect comments.ID, PostID\r\nfrom Comments\r\njoin Posts on Comments.PostId = Posts.ID\r\nwhere Posts.CreationDate between @DateBottom and @DateTop ) c\r\non p.ID = c.PostId\r\nwhere p.CreationDate between @DateBottom and @DateTop\r\ngroup by isnull(Location, @nullReplacement)\r\nhaving count(*) &gt;= @minItems ) ,\r\nresourceInfo as (\r\nselect resourceID, resourceColor, resourceNumber\r\nfrom resources m\r\njoin resourceColors rc\r\non m.resourceNumber % @maxColors = rc.resourceColorNumber\r\n)<\/pre>\n<p>The first CTE, <em>resourceColors<\/em>, just picks up the colors available for different rows in the &#8220;graph&#8221;. \u00a0 (Note: \u00a0in some cases you might want to show these as a key on top against each resource, or against the resource names or IDs). \u00a0As explained earlier, you might have many ways of doing this, but however you do it, you&#8217;re going to want to know what the number of available colors (@maxColors, in my example). \u00a0Each color is assigned a consecutive number, starting with 0.<\/p>\n<p>The second CTE, <em>resources<\/em>, picks up the list of resources we are going to display in rows. \u00a0Each resource is assigned a unique sorting value &#8212; here, I&#8217;ve chosen to sort by the highest to lowest thread activity, but you might do it alphabetically, or however you like,<\/p>\n<p>The third CTE, <em>resourceInfo<\/em>, puts this information together, assigning a resourceColor to each row based on its expected sorting according to its unique resourceNumber plus a modulus expression using resourceNumber and @maxColors. (That&#8217;s why we numbered the available colors starting with 0 in the first CTE.)<\/p>\n<h2>Get a set of appropriate events that fit your selected resources and datetimes<\/h2>\n<p>We&#8217;ll create a temp table that uses this information and gets data for the start and end times, as well as specified colors, for our resources, based on the final CTE:<\/p>\n<pre class=\"code\">select\r\nr.resourceNumber,\r\nr.resourceID,\r\nresourceColor,\r\np.CreationDate as EventStart,\r\nc.AnsweredDate as EventEnd,\r\np.Title\r\ninto #resourceEvents\r\nfrom resourceInfo r\r\njoin Users u\r\non isnull(Location,@nullReplacement) = r.resourceID\r\njoin Posts p\r\non u.Id = p.OwnerUserId\r\njoin\r\n(select\r\nPostID,\r\nmax(comments.CreationDate) as AnsweredDate\r\nfrom Comments\r\njoin Posts on Comments.PostId = Posts.ID\r\nwhere Posts.CreationDate between @DateBottom and @DateTop group by PostID\r\n) c\r\non p.ID = c.PostId\r\nwhere p.CreationDate between @DateBottom and @DateTop;<\/pre>\n<p>&#8230; there&#8217;s probably a better way to get a relevant sample data set from StackOverflow data, but, hey, it&#8217;s my first time, and it will serve for this example.<\/p>\n<h2>Now to derive our calendar grid<\/h2>\n<p>Here&#8217;s where the DateDimension table comes in. \u00a0Whether there was activity in it or not, we need a cell for each row for each possible time period. \u00a0We create a temp table for this purpose, mostly because we happen to want half-hour increments and our DateDimension table stores only to-the-hour granularity:<\/p>\n<pre class=\"code\">-- we'll create a \"half hour dimension\":\r\nselect\r\nDatePart(month,d.Date) as MonthNo,\r\nDatePart(week,d.Date) as Week,\r\nd.Date, DatePart(Day,d.Date) as Day,\r\nd.IsWeekend, Hour,\r\ncast(cast(Hour as varchar(2)) + ':' + \r\n     case when BottomHalfHour = 0 then '00' else '30' end as DateTime) \r\n     as StartTime,\r\ncast(cast(Hour as varchar(2)) + ':' + \r\n     case when BottomHalfHour = 0 then '29' else '59' end as DateTime) \r\n     as EndTime,\r\ncase\r\nwhen Hour = 0 and BottomHalfHour = 0 then 'mid nt'\r\nwhen Hour = 12 and BottomHalfHour = 0 then 'noon'\r\nelse cast(case when Hour &lt; 13 then Hour else Hour - 12\r\nend as varchar(2)) +\r\ncase when BottomHalfHour = 0 then ':00' else ':30' end +\r\ncase when Hour &lt; 12 then ' am' else ' pm' end end\r\nas halfHourLabel\r\ninto #halfHours\r\nfrom \u00a0dbo.DateDimension d join\r\n( select top 24 Day-1 as Hour, 0 as BottomHalfHour from \u00a0dbo.DateDimension\r\nunion all\r\nselect top 24 Day-1 as Hour, 1 as BottomHalfHour from dbo.DateDimension ) h\r\non 1=1\r\nwhere d.Date\u00a0between @DateBottom and @DateTop\r\nand h.Hour between @HoursBottom and @HoursTop\r\norder by 1,2<\/pre>\n<p>&#8230; Notice that I include both a MonthName for display and a Month numeric value to ensure correct sorting. \u00a0If your data may span more than one calendar year, you&#8217;ll want to include Year too, of course. I&#8217;ve also included a &#8220;Week&#8221; value, which you could use for both sorting and grouping as an interim level between Month and Day, should this suit your purposes, although I am not using it here.<\/p>\n<h2>Now put it together for the RDL dataset<\/h2>\n<p>&#8230; and of course, drop the temp tables for neatness&#8217; sake:<\/p>\n<pre class=\"code\">select h.MonthNo,\r\n-- add Week here too if you want\r\nh.Date,\r\nh.Day,\r\nh.Hour,\r\nh.StartTime,\r\nh.halfHourLabel,\r\ne.resourceID,\r\ncase when h.IsWeekend = 1 then 'Gainsboro' else 'WhiteSmoke' end as dayColor,\r\nDateName(month,h.Date) as MonthName, isnull( e.Title, 'No Title') as Title,\r\nisnull(e.resourceColor,'White') as resourceColor,\r\ne.resourceNumber from #halfHours h\r\nleft join #resourceEvents e\r\non cast(eventStart as date) = h.date and\r\neventStart= cast(h.Date as DateTime)+ h.EndTime\r\norder by 1,2,3,4,5,isnull(e.Title,'ZZZZZ');\r\n\r\ndrop table #halfHours ;\r\ndrop table #resourceEvents ;<\/pre>\n<p>&#8230; \u00a0the final SELECT is really pretty simple, and has everything we&#8217;ll need based on the previous queries. \u00a0The only thing it&#8217;s adding in is a <em>dayColor <\/em>value, for use in the headers, so that weekends are clearly distinguished from weekdays, and this is certainly not a critical requirement.<\/p>\n<p>What&#8217;s really going on here? \u00a0We&#8217;re talking our calendar data and creating a left join with our event data. \u00a0So, some matrix cells will be empty (NULLs) because nothing is going on for a given resource for that time period.<\/p>\n<h2>Enough SQL? \u00a0Ready to have some Designer fun?<\/h2>\n<p>OK, here you go.<\/p>\n<table border=\"0\">\n<tbody>\n<tr>\n<td><a title=\"click to see fullsize\" href=\"\/lisa\/wp-non\/migrated\/2017\/7\/Wizard.PNG\" target=\"_blank\" rel=\"noopener\"><img loading=\"lazy\" decoding=\"async\" class=\"null alignnone\" src=\"\/lisa\/wp-non\/migrated\/2017\/7\/Wizard.PNG\" alt=\"\" width=\"660\" height=\"563\" \/><\/a><\/td>\n<td>As promised, having dumped the query into the RDL, I can use the Report Wizard to group things the way we would expect for a calendar grid &#8212; it&#8217;s straightforward enough that it&#8217;s easier to show you what I grabbed in the Wizard than explain in words (fullsize if you click), but the jist is:<\/p>\n<ul>\n<li>row group on resources<\/li>\n<li>column groups on time periods, largest on the outside descending to the smallest on the inside<\/li>\n<li>detail cell contents on&#8230; anything, really. \u00a0You&#8217;ll see why.<\/li>\n<\/ul>\n<p>And now we have very little left to do.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h4>Stupid Designer pet tricks<\/h4>\n<p>Here&#8217;s all that&#8217;s left to do in the Designer to make this work:<\/p>\n<ol>\n<li>Set the row group to be ordered by resourceNumber, because by default it&#8217;s going to add a sort by the value you provided in the grouping. \u00a0We want our specified sorting to reflect our expected cycling through color values.<\/li>\n<li>Similarly, the outermost column group needs to sort by MonthNo, not MonthName. \u00a0For clarity, if you prefer, group on MonthNo too. \u00a0I just tend to pick up on the display items when I&#8217;m working in the Wizard, which I do very rarely. The innermost column group, which will display our halfHourLabel, should sort by StartTime and, for clarity, it might as well group by StartTime too. \u00a0Again, the halfHourLabel was for display purposes.<\/li>\n<li>Set the Fill (backcolor) of the \u00a0Day and halfHour header cells to dayColor, remember we&#8217;ll just do this for fun to distinguish weekends from weekdays::<br \/>\n<a title=\"click to see fullsize\" href=\"\/lisa\/wp-non\/migrated\/2017\/7\/BackcolorDayHeader.PNG\" target=\"_blank\" rel=\"noopener\"><img decoding=\"async\" class=\"null\" src=\"\/lisa\/wp-non\/migrated\/2017\/7\/BackcolorDayHeader.PNG\" alt=\"\" width=\"200px\" \/><\/a><\/li>\n<li>and finally &#8212;<strong> this is the critical trick &#8212; change two properties of the matrix detail cells<\/strong>\n<ol>\n<li>the Fill (backcolor) of the detail cells should be\u00a0<strong><em>=Fields!resourceColor.Value<\/em><\/strong>, just like header cells&#8217; Fill or backcolor you changed in the last step<\/li>\n<li>the Expression for the textbox in the detail cells should be <strong>blank<\/strong> (I use <strong><em>=space(0)<\/em><\/strong>).<\/li>\n<\/ol>\n<\/li>\n<\/ol>\n<p>That&#8217;s really it! \u00a0This works natively &#8212; again, without any iif() to check to see if you have a resource event for a cell, although if you like you can complicate things by adding one &#8212; \u00a0because the null cells don&#8217;t get any fill at all, they stay blank. \u00a0In every version of SSRS I&#8217;ve tried.<\/p>\n<h4>A few more non-essentials<\/h4>\n<ol>\n<li>If you want, for extra credit, you can gussy things up with a tooltip on the detail level, showing what&#8217;s really going on in that cell or cells. \u00a0In my example data set, I used this expression in the tooltip, but it would be different in every case:\n<p class=\"code\">=iif(countDistinct(Fields!Title.Value) = 0,&#8221;&#8221;,<br \/>\nFirst(Fields!resourceID.Value) &amp; &#8220;: &#8221; &amp; First(Fields!Title.Value)) &amp;<br \/>\niif(countDistinct(Fields!Title.Value) &gt; 1,<br \/>\n&#8220;&#8230; (&#8221; &amp; countDistinct(Fields!Title.Value) &amp; &#8221; threads in this time pd)&#8221;,&#8221;&#8221;)<\/p>\n<\/li>\n<li>Typically, in non-example datasets, there&#8217;s also a drilldown from the detail cell, that will show the resource task or event details pertinent to the day and resource row. \u00a0I suppose I could have done that here by bringing up a set of post titles and answers\/comments for each. \u00a0You can see immediately that you have the information you need to parameterize the drilldown report with the data to hand in the current cell.<\/li>\n<li>The native collapse-expand behavior also lends itself to gussying up a report like this, again because it tends to be very wide, and starting off the months or days or weeks as collapsed allows the viewer to quickly zero in on areas of interest. \u00a0I almost always use it in reports using this technique.<\/li>\n<li>\u00a0While bare Day numbers are used in the column headers to facilitate &#8220;skinniness&#8221; of columns, depending on how you do your collapse-expand, a tooltip on, say, <strong>=FormatDateTime(Fields!Date.Value, DateFormat.LongDate)<\/strong> can be very informative here.<\/li>\n<\/ol>\n<h2>And that&#8217;s all she wrote&#8230; Wait.<\/h2>\n<p>Wait. \u00a0You want to see what it looks like? \u00a0Oh, that&#8217;s right, eye candy was the whole point&#8230; so here you go:<\/p>\n<p><a title=\"click to see fullsize\" href=\"\/lisa\/wp-non\/migrated\/2017\/7\/GanttResult.PNG\" target=\"_blank\" rel=\"noopener\"><img decoding=\"async\" class=\"null\" style=\"display: block; border: 1px solid black; margin-left: auto; margin-right: auto;\" src=\"\/lisa\/wp-non\/migrated\/2017\/7\/GanttResult.PNG\" alt=\"\" width=\"80%\" \/><\/a><\/p>\n<p>&#8230; and here&#8217;s the RDL, not that it&#8217;s anything special, but it does contain all the SqL.\u00a0<a href=\"\/lisa\/wp-non\/migrated\/2017\/7\/Gantt.rdl\">Gantt.rdl (21.76 kb)<\/a><\/p>\n<p>You&#8217;ll notice a &#8220;hard stop&#8221; at the beginning of the day; there are no horizontal bars that span this time. \u00a0This is due to the @HoursBottom and @HoursTop values picking up a limited slice of the 24-hour period, and is not a limitation of the technique.<\/p>\n<p>This simple technique has come in handy many times, and really it&#8217;s hardly anything from a reporting perspective. \u00a0Have fun!<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I&#8217;ve seen numerous people advocate the use of Range Bars for Gantt charts in SSRS. \u00a0Well, yes, of the natively supplied widgets, it does best fit the bill. \u00a0But it&#8217;s not great. It&#8217;s probably fine for some types of Gantt, but it doesn&#8217;t work if you want to show resources mapped to a calendar grid.<a class=\"more-link\" href=\"https:\/\/spacefold.com\/lisa\/2017\/07\/29\/walkthrough-sql-not-range-bars-is-the-secret-sauce-to-ssrs-gantt-charts\/\">Read more<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5,6],"tags":[],"class_list":["post-26","post","type-post","status-publish","format-standard","hentry","category-reporting","category-sql-server"],"_links":{"self":[{"href":"https:\/\/spacefold.com\/lisa\/wp-json\/wp\/v2\/posts\/26","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/spacefold.com\/lisa\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/spacefold.com\/lisa\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/spacefold.com\/lisa\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/spacefold.com\/lisa\/wp-json\/wp\/v2\/comments?post=26"}],"version-history":[{"count":4,"href":"https:\/\/spacefold.com\/lisa\/wp-json\/wp\/v2\/posts\/26\/revisions"}],"predecessor-version":[{"id":625,"href":"https:\/\/spacefold.com\/lisa\/wp-json\/wp\/v2\/posts\/26\/revisions\/625"}],"wp:attachment":[{"href":"https:\/\/spacefold.com\/lisa\/wp-json\/wp\/v2\/media?parent=26"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/spacefold.com\/lisa\/wp-json\/wp\/v2\/categories?post=26"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/spacefold.com\/lisa\/wp-json\/wp\/v2\/tags?post=26"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}