CFCHART displaying y-axis numbers in whole - coldfusion

I am trying to use cfchart to display numbers of messages and all works well but when I have a small amount of numbers it breaks it into decimals. Is there a way we can make it always set to whole numbers?
Here is the code:
<cfchart format="#display#" type="bar" show3d="no" showlegend="false" chartHeight="300" chartWidth="220">
<cfchartseries colorlist="##E18014,green,red" type="bar">
<cfchartdata item="Calls Made" value="#voice_messages.TotalMessages#">
<cfchartdata item="Successful" value="#voice_messages.ReceiptsReceived#">
<cfif #voice_messages.DeliveryFailures# neq "0">
<cfchartdata item="Bus/No Ans" value="#voice_messages.DeliveryFailures#">
</cfif>
</cfchartseries>
</cfchart>
And here is a pic of the graph
I looked all over on Stack and the web but didn't find anything. Any help would be greatly appreciated.
Thanks!!

<cfchart> has an attribute called yAxisValues. It accepts an array of values. For this situation, once I had my data, I would do something to produce a suitable array to use for this attribute.

Verify whether the chartdata values voice_messages.TotalMessages, voice_messages.ReceiptsReceived and are infact integers. Use something like:
<cfset displayChart=false>
<cfif (isNumeric(voice_messages.TotalMessages) and voice_messages.TotalMessages gte 0)
and (isNumeric(voice_messages.ReceiptsReceived) and voice_messages.ReceiptsReceived gte 0)
and (isNumeric(voice_messages.DeliveryFailures) and voice_messages.DeliveryFailures gte 0)>
<cfset numberOfMessages=int(voice_messages.TotalMessages)>
<cfset numberOfReceipts=int(voice_messages.ReceiptsReceived)>
<cfset numberOfFailures=int(voice_messages.DeliveryFailures)>
<cfset displayChart=true>
</cfif>
<cfif displayChart>
<!--- You may have to increase the chartWidth to make room for all the labels--->
<cfchart format="png" type="bar" show3d="no" showlegend="false" chartHeight="300" chartWidth="300">
<cfchartseries colorlist="##E18014,green,red" type="bar">
<cfchartdata item="Calls Made" value="#numberOfMessages#">
<cfchartdata item="Successful" value="#numberOfReceipts#">
<cfif numberOfFailures neq 0>
<cfchartdata item="Bus/No Ans" value="#numberOfFailures#">
</cfif>
</cfchartseries>
</cfchart>
<cfelse>
Sorry, chart cannot be displayed because chartdata are not all integers.
</cfif>
In any case, you should realize that the labels in the display you have shown do not correspond to those in the code.

I shall now give you another answer. It is an answer of last resort, because it involves a changing ColdFusion server settings. Perhaps the reason you cannot easily find it on the web. Again, please be warned beforehand: it is a change of server setting. So back up your files!
ColdFusion 2018 (my version) uses the settings for ZingCharts to configure style in cfcharts. You will find the relevant files at C:\ColdFusion2018\cfusion\charting\styles. You want to edit the style file for bar charts.
Open in a text editor the file C:\ColdFusion2018\cfusion\charting\styles\bar. It has JSON format.
Locate the element "scale-y". It begins with:
"scale-y" : {
"format" : "%v",
"font-size" : 16,
... and so on.
Prepend to it the key-value pair "decimals" : 0. The result should be:
"scale-y" : {
"decimals" : 0,
"format" : "%v",
"font-size" : 16,
... and so on.
This will ensure that the y-axis will have 0 decimal places.
Save the file and restart ColdFusion for the changes to take effect.
You have now configured the y-axis in cfchart's bar charts to no longer have decimals.
You will have noticed that the element "scale-y" is a sub-element of "graphset". Should you be interested in knowing more about their settings, then go to https://www.zingchart.com/docs/api/json-configuration/graphset

Related

Split the session variable into an array using recreating individual session variables

The typical way government offices make you record mileage is to enter one number per input box like the example below:
So what I am trying to do is create one input box that can split the session variable into an array. Then when that session variable is split into an array I would like it to set each value in the array to its own session variable.
Mileage Input:
123456 -<cfoutput>#session.checkout.vehicle.mileage#</cfoutput>
array [1,2,3,4,5,6]
6 -<cfoutput>#session.checkout.vehicle.mileage1#</cfoutput>
5 -<cfoutput>#session.checkout.vehicle.mileage2#</cfoutput>
4 -<cfoutput>#session.checkout.vehicle.mileage3#</cfoutput>
3 -<cfoutput>#session.checkout.vehicle.mileage4#</cfoutput>
2 -<cfoutput>#session.checkout.vehicle.mileage5#</cfoutput>
1 -<cfoutput>#session.checkout.vehicle.mileage6#</cfoutput>
So then I will be able to prefill in an already created form that has the boxes split for only one per box.
Where I am super confused and trying to comprehend is that there will not always be 6 variables. Let's say the mileage is 2344. I am assuming it will need to know to start backwards, counting from the right to the left. That's why I started 6 at #session.checkout.vehicle.mileage1#
Hopefully I have not super confused anyone with what I am trying to do. And any help would be greatly appreciated!!
<cfparam name="form.mileage" default="#session.checkout.vehicle.mileage#">
...
<label for="mileage">Mileage:</label>
<input type="text" name="mileage"
id="mileage"
value="<cfoutput>#form.mileage#</cfoutput>">
Edit:
The issue I am having with this is let's say the mileage is 9000 all 0's will not show. (which is great for the first two zero's in (009000) but after the 9 those 0's would still need to show.) Do you any ideas for that issue? Or should this be a new question?
<cfset Mileage = "9000" />
<cfif mileage is not "Exempt">
<cfset Mileage = NumberFormat(trim(Mileage),"000000") />
<cfset MilArray = ReMatch("\d",Mileage) />
<cfelse>
<cfset MilArray = ["E","x","e","m","p","t"]>
</cfif>
<cfdump var="#MilArray#">
<cfif MilArray[1] is not "0">
<!---Section6 First box Odometer Reading--->
<cfpdfformparam name="E" value="#MilArray[1]#">
<cfelse>
<cfpdfformparam name="E" value="">
</cfif>
If I'm understanding, you want to divide the string into six easy to work with variables, or whatever the length of the variable is.
<cfset Mileage = "123456" />
<cfset MilArray = ReMatch("\d",Mileage) />
<cfdump var="#MilArray#" />
You can actually stick a Reverse() in there to reverse the string, this may be handy because you can have [1] at the ones place, [2] at tens, [3] at hundreds.
<cfset Mileage = "123456" />
<cfset MileageR = Reverse(Mileage) />
<cfset MilArray = ReMatch("\d",MileageR) />
<cfdump var="#MilArray#" />
\d by itself in regular expressions just means "one digit". It's the same as [0-9].
As the CFDUMP will show, ReMatch will split your mileage into an easy to work with array. If you use the reverse as above, you can say "The last digit of your mileage is #MilArray[1]#.", as an example.
Edit:
you know the \d ? is there a way to have it be either \d or only the word Exempt? is it possible to create both of those?
There are a few ways.
You can say
<cfif mileage is not "Exempt">
...
<cfelse>
<cfset MilArray = ["Exempt"]>
</cfif>
which creates a one dimensional array populated with "Exempt" as the only element, which might be useful later in your code so you know MilArray is always an array, or you can simply always work with the <cfif mileage is not "Exempt">.
A regex to accomplish the same thing is possible but it achieves the same as the above cfif, and you'd have to write exempt backwards if you're using reverse, like this
<cfset MilArray = ReMatchNoCase("\d|^Exempt$|^tpmexE$)",trim(Mileage)) />
<cfif MilArray[1] is "tpmexE"><cfset milArray = ["Exempt"] /></cfif>
Edit #2:
<cfif isDefined("session") and structKeyExists(session, 'checkout') and structKeyExists(session.checkout, 'info') and structKeyExists(session.checkout.info, 'oreading')>
<cfif isDefined("#MilArray[6]#") eq "">
<cfpdfformparam name="E" value="">
<!---Section6 First box Odometer Reading--->
<cfelse>
<cfpdfformparam name="E" value="#MilArray[6]#">
</cfif>
</cfif>
This is a task for ArrayIsDefined() (link)
<cfif isDefined("session") and structKeyExists(session, 'checkout') and structKeyExists(session.checkout, 'info') and structKeyExists(session.checkout.info, 'oreading')>
<cfset MilArray = ReMatch("\d",session.checkout.info.oreading) />
<cfif not ArrayIsDefined(MilArray,6)>
<cfpdfformparam name="E" value="">
<!---Section6 First box Odometer Reading--->
<cfelse>
<cfpdfformparam name="E" value="#MilArray[6]#">
</cfif>
.... I assume that it continues on down from here... <cfif not ArrayIsDefined(MilArray,5)>........</cfif>
</cfif>
Finally, while there's contention here on whether to use StructKeyExists() over IsDefined(), there's a narrow field where isDefined() fails.
(Don't put structures in the top level and in the variables scope. Cold Fusion gets confused--IE, don't create an object called "variables.form" or "variables.url"). Beyond that, It's mostly just semantics.
Anyway. once you have the above code working (because it's your code and your familiar with it), you might find it useful to switch to the easier to read IsDefined() version, because isDefined can check several levels deep in one condition.
<cfif isDefined("session.checkout.info.oreading')>
<cfset MilArray = ReMatch("\d",session.checkout.info.oreading) />
<cfif not ArrayIsDefined(MilArray,6)>
<cfpdfformparam name="E" value="">
<!---Section6 First box Odometer Reading--->
<cfelse>
<cfpdfformparam name="E" value="#MilArray[6]#">
</cfif>
</cfif>
Edit 3:
Leigh points out
Why so complicated? Can't you just left pad the value with spaces or zeroes? Then change the regex to check for either a digit or space? Then the array will always have six elements
This can be achieved like this:
<cfset Mileage = "exempt" />
<cfif mileage is not "Exempt">
<cfset Mileage = NumberFormat(trim(Mileage),"000000") />
<cfset MilArray = ReMatch("\d",Mileage) />
<cfelse>
<cfset MilArray = ["E","x","e","m","p","t"]>
</cfif>
<cfdump var="#MilArray#">
Which would conveniently drop Exempt into place (handy that it's 6 characters).
You need to do some prechecking before you start generating the pdf to make sure that mileage variable is Exempt or or numeric.
<cfif len((trim(mileage)) gt 6 or not ((isNumeric(trim(mileage))
or mileage is "exempt")>
<!--- The 6 above is a len-check, you may need to update that number to
something else later, but you'll have to put the same number of 0s
in the NumberFormat function.
If you change that number, and the 0s, you'll need to pad the
"Exempt array"... ie ["E","x","e","m","p","t"," "] --->
....raise a flag...
</cfif>
Here is a simpler way.
originalNumber = "123";
sixDigitNumber = right(("000000" & originalNumber), 6);
<cfoutput>
<cfloop from="1" to = "6" index="position">
do something useful with #Mid(sixDigitNumber, position, 1)#
</cfloop>
<cfoutput>

ColdFusion 9 CFCHART CFCHARTSERIES Issue

I'm displaying a chart with one or more line series. The data comes from a query and works correctly if there is more than on series on the chart. However, if only one series is returned it is not displaying correctly.
Here's the code used:
<cfchart format="flash" tipstyle="mouseover" showlegend="yes" xaxistitle="Date" yaxistitle="Hits" chartwidth="1200" chartheight="300">
<cfoutput query="qryReport" group="APP_SYS_NR">
<cfchartseries serieslabel="#qryReport.APP_NA#" type="line">
<cfoutput>
<cfchartdata item="#DateFormat(qryReport.CDR_DT, "mm/dd/yyyy")#" value="#qryReport.TOT_HIT_CNT#">
</cfoutput>
</cfchartseries>
</cfoutput>
</cfchart>
The blacked out area at the top of this chart lists the keys for what the two lines represent:
In this chart (when there is only one APP_SYS_NR returned), instead of only having a single label, all the dates are turned into labels. Obviously not what I want:
Edit: I've traced this to the showlegend attribute of cfchart. According to Adobe, it's whether to display the legend if the chart contains more than one data series. I guess when it contains only one data series, it completely craps itself and does the data points in the legend. I tested on ColdFusion 9 and ColdFusion 10.
The solution here is to set showlegend to no when there is only a single series to display. Instead you should use a chart title in that instance. See the following modified code:
<cfset VARIABLES.blnShowLegend = "no">
<cfset VARIABLES.strChartTitle = "#qryReport.APP_NA#">
<cfif ListLen(URL.lstApps) GT 1>
<cfset VARIABLES.blnShowLegend = "yes">
<cfset VARIABLES.strChartTitle = "">
</cfif>
<cfchart format="flash" title="#VARIABLES.strChartTitle#" tipstyle="mouseover" style="appstats" showlegend="#VARIABLES.blnShowLegend#" xaxistitle="Date" yaxistitle="Hits" chartwidth="1200" chartheight="300">
<cfoutput query="qryReport" group="APP_SYS_NR">
<cfchartseries serieslabel="#qryReport.APP_NA#" type="line">
<cfoutput>
<cfchartdata item="#DateFormat(qryReport.CDR_DT, "mm/dd/yyyy")#" value="#qryReport.TOT_HIT_CNT#">
</cfoutput>
</cfchartseries>
</cfoutput>
</cfchart>

How to create a cfspreadsheet with protected cells

I am creating a spreadsheet object using cfspreadsheet. Would like to make some of the individual cells as protected (read-only). Please let me know if anybody has tried this before.
I did try putting cell format as locked but it did not seems to work. Here is the sample code:
<cfset a = spreadsheetnew()>
<cfset format1 = structNew()>
<cfset format1.locked=true>
<cfset SpreadsheetFormatCell(a,format1,1,1)>
<cfspreadsheet action="write" filename="#expandpath('.')#/test.xls" name="a" overwrite="true">
Thanks.
Locking a cell does nothing unless the sheet is protected ie using cfspreadsheet's password attribute. But doing so has some negative side effects ...
Protecting the sheet locks all cells. That means you essentially have to "unlock" everything else by applying a format. In theory you could just unlock the entire sheet:
<cfset SpreadsheetFormatCellRange (sheet, {locked=false}, 1, 1, maxRow, maxCol)>
However, that has the nasty effect of populating every single cell in the sheet. So if you read the file into a query, the query would contain ~65,536 rows and 256 columns. Even if you only populated a few cells explicitly.
The lock feature is better suited to cases where you want everything to be locked except a few cells (not the reverse). Unless that is what you are doing, I probably would not bother with it, given all the negative side effects.
Side effect example
<cfset testFile = "c:/test.xls">
<cfset sheet = spreadsheetNew()>
<!--- only unlocking 100 rows to demonstrate --->
<cfset SpreadsheetFormatCellRange (sheet, {locked=false}, 1, 1, 100, 10)>
<!--- populate two cells --->
<cfset SpreadsheetSetCellValue(sheet,"LOCKED",1,1)>
<cfset SpreadsheetSetCellValue(sheet,"UNLOCKED",2,1)>
<!--- make one cell locked --->
<cfset SpreadsheetFormatCell(sheet, {locked=true}, 1, 1)>
<cfspreadsheet action="write"
name="sheet"
fileName="#testFile#"
password=""
overwrite="true" >
<!--- now see it is filled with empty cells --->
<cfspreadsheet action="read"
query="sheetData"
src="#testFile#" >
<cfdump var="#sheetData#" label="Lots of empty cells" />

limit x axis for cfchart

I would like to limit the x-axis of my chart that is created by cfchart. I see the attribute scaleFrom and scaleTo that limit the y-axis, but I see nothing that limits the x-axis.
Also, I saw a similar question here:
ColdFusion Chart x-axis label limits
but neither of the answers were appropriate. ScaleMin and ScaleMax do not exists as far as I can tell and the other answer is more complicated than what I'd like to do.
The scaleMin and scaleMax attributes Saul mentioned are only available when using a custom style. Note, using type "scale" means your xAxis values must be numeric. If you want to use strings, you will probably need to use Ben's approach instead.
Here is a quick example that creates a chart with 24 points on the xAxis. Even though the query only contains the first six (6) points.
<!--- bare bones style --->
<cfsavecontent variable="style">
<?xml version="1.0" encoding="UTF-8"?>
<frameChart is3D="false" isInterpolated="true">
<frame xDepth="3" yDepth="1" />
<xAxis type="Scale" scaleMin="0" scaleMax="24" labelCount="25" isBucketed="false" />
</frameChart>
</cfsavecontent>
<!--- sample query --->
<cfset qry = queryNew("")>
<cfset queryAddColumn(qry, "xValue", listToArray("1,2,3,4,5,6"))>
<cfset queryAddColumn(qry, "yValue", listToArray("30,15,22,14,45,5"))>
<!--- chart code --->
<cfchart format="jpg" style="#style#" width="600">
<cfchartseries type="line"
markerstyle="circle"
query="qry"
itemColumn="xValue"
valueColumn="yValue" />
</cfchart>

CFCHART: reverse Y-axis ordering

I mean that MIN should be on top and MAX on X-axis.
This needed to make chart more intuitive to read and related to object ranking, when 1st place is "better" than 3rd. That's why it'd be better to look this way:
(source: xantea.net) =>
(source: xantea.net)
This can be done using custom XML. I ran the Chart Designer (cfinstall/charting/webcharts.bar (or .sh) and simply worked with the YAxis setting. There is an isReversed setting which does what you want. Consider this code:
<cfset q2 = queryNew("year,employees","integer,integer")>
<!--- generate random sales data --->
<cfloop index="y" from="1994" to="1998">
<cfscript>
queryAddRow(q2);
querySetCell(q2, "year", y);
querySetCell(q2, "employees", randRange(2,8));
</cfscript>
</cfloop>
<cfsavecontent variable="chartxml">
<?xml version="1.0" encoding="UTF-8"?>
<frameChart is3D="false">
<yAxis isReversed="true">
</yAxis>
</frameChart>
</cfsavecontent>
<cfchart chartWidth="400" chartHeight="400" title="Sales" font="arial" style="#chartxml#">
<cfchartseries type="line" query="q2" itemColumn="year" valueColumn="employees" seriesLabel="Employees" />
</cfchart>
The query on top was just used for testing. The XML came from the chart designer. I removed everything but the yAxis stuff that used isReverse. Lastly, note how I specify style in the chart tag.