// Spludlow Software // Copyright © Samuel P. Ludlow 2020 All Rights Reserved // Distributed under the terms of the GNU General Public License version 3 // Distributed WITHOUT ANY WARRANTY; without implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE // https://www.spludlow.co.uk/LICENCE.TXT // The Spludlow logo is a registered trademark of Samuel P. Ludlow and may not be used without permission // v1.14 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Data; using System.Drawing; namespace Spludlow.Drawing { public class BarChart { private Spludlow.Printing.PrintDoc Doc; private string FontHead = "Arial, 18, Bold, Italic"; // Not "Arial Bold, 18" private string FontSmall = "Arial, 7"; public BarChart(Spludlow.Printing.PrintDoc doc) { this.Doc = doc; } public void Plot(float x, float y, float width, float height, DataTable table, bool seriesColumns) { RectangleF rectangle = new RectangleF(x, y, width, height); this.Plot(rectangle, table, seriesColumns, "c0"); } public void Plot(RectangleF rectangle, DataTable table, bool seriesColumns, string format) { float lineWeight = 0.25F; float left = 20; float right = 5; float top = 10; float bottom = 10; RectangleF plotRectangle = new RectangleF(rectangle.X + left, rectangle.Y + top, rectangle.Width - left - right, rectangle.Height - bottom - top); this.Doc.Rectangle(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height, lineWeight, Color.Black, Color.Empty); this.Doc.Text(table.TableName, this.FontHead, rectangle.X, rectangle.Y); List columnNames = new List(); List rowNames = new List(); for (int index = 1; index < table.Columns.Count; ++index) columnNames.Add(table.Columns[index].ColumnName); foreach (DataRow row in table.Rows) { //if (row[0].GetType() == typeof(string)) // rowNames.Add((string)row[0]); //else // rowNames.Add(((int)row[0]).ToString()); rowNames.Add(Convert.ToString(row[0])); } List barList = columnNames; List seriesList = rowNames; if (seriesColumns == true) { barList = rowNames; seriesList = columnNames; } int barCount = barList.Count; int seriesCount = seriesList.Count; if (barCount == 0 || seriesCount == 0) { throw new ApplicationException("Bar Chart; No data"); } Color[] colourRange = Spludlow.Drawing.Colour.Range(seriesCount < 9 ? 9 : seriesCount); float plotWidth = plotRectangle.Width; float barWidth = plotWidth / barCount; int barGap = 2; decimal minValue = Decimal.MaxValue; decimal maxValue = Decimal.MinValue; foreach (DataRow row in table.Rows) { for (int index = 1; index < table.Columns.Count; ++index) { if (row.IsNull(index) == false) { decimal value = this.GetValue(row, index); minValue = Math.Min(minValue, value); maxValue = Math.Max(maxValue, value); } } } if (minValue == Decimal.MaxValue || maxValue == 0) // || minValue == maxValue use when pushing up { Spludlow.Log.Warning("Bar Chart; Bad limits data, min:" + minValue + ", max:" + maxValue, table); return; } //Spludlow.Log.Warning("barList", barList.ToArray()); //Spludlow.Log.Warning("seriesList", seriesList.ToArray()); int scaleFactor = (int)Math.Pow(10, ((int)maxValue).ToString().Length - 1); for (decimal scaleValue = 0; scaleValue <= maxValue; scaleValue += scaleFactor) { float y = plotRectangle.Y + plotRectangle.Height - ((float)plotRectangle.Height / (float)maxValue) * (float)scaleValue; //this.Pdf.Line(rectangle.X, y, plotRectangle.X + plotRectangle.Width, y, lineWeight); this.Doc.Line(rectangle.X, y, plotRectangle.X + plotRectangle.Width, y, lineWeight, Color.Black); //this.Pdf.Text(rectangle.X, y, scaleValue.ToString(format), "small"); this.Doc.Text(scaleValue.ToString(), this.FontSmall, rectangle.X, y); } for (int barIndex = 0; barIndex < barCount; ++barIndex) { RectangleF barRectangle = new RectangleF(plotRectangle.X + (barIndex * barWidth) + barGap, plotRectangle.Y, barWidth - (2 * barGap), plotRectangle.Height); string barName = null; if (seriesColumns == false) barName = columnNames[barIndex]; else barName = rowNames[barIndex]; float x1 = plotRectangle.X + (barIndex * barWidth) + barGap; float y1 = plotRectangle.Y + plotRectangle.Height; float x2 = x1 + barWidth; float y2 = y1 + 10; this.Doc.Text(barName, this.FontSmall, x1, y1); float seriesBarWidth = (float)barRectangle.Width / seriesCount; for (int seriesIndex = 0; seriesIndex < seriesCount; ++seriesIndex) { int columnIndex = 0; DataRow row = null; if (seriesColumns == false) { row = table.Rows[seriesIndex]; columnIndex = barIndex + 1; } else { row = table.Rows[barIndex]; columnIndex = seriesIndex + 1; } Color colour = colourRange[seriesIndex]; // this.ParseColour(colours[seriesIndex % colours.Length]); decimal value = this.GetValue(row, columnIndex); int barHeight = (int)Math.Round(((decimal)barRectangle.Height / maxValue) * value, 0); x1 = (float)barRectangle.X + ((float)seriesIndex * seriesBarWidth); y1 = (float)barRectangle.Y + ((float)barRectangle.Height - barHeight); x2 = x1 + seriesBarWidth; y2 = barRectangle.Y + barRectangle.Height; //this.Pdf.Rectangle(x1, y1, x2, y2, lineWeight, colour); this.Doc.Rectangle(x1, y1, seriesBarWidth, y2 - y1, lineWeight, Color.Black, colour); string barText = value.ToString(); if (seriesColumns == false) barText += " (" + SeriesText(row) + ")"; else barText += " (" + table.Columns[columnIndex].ColumnName + ")"; //this.Pdf.Text(x1 + (seriesBarWidth / 2), barRectangle.Y + barRectangle.Height - 5, barText, "small", 90); this.Doc.Text(barText, this.FontSmall, x1 + (seriesBarWidth / 2), barRectangle.Y + barRectangle.Height - 10, Color.Black, -90, StringAlignment.Center); } } } private string SeriesText(DataRow row) { if (row.Table.Columns[0].DataType == typeof(string)) return (string)row[0]; if (row.Table.Columns[0].DataType == typeof(int)) return ((int)row[0]).ToString(); throw new ApplicationException("Unexpected Column type getting Series text:\t" + row.Table.Columns[0].DataType.Name); } private Color ParseColour(string colourText) { int r = Int32.Parse(colourText.Substring(0, 2), System.Globalization.NumberStyles.HexNumber); int g = Int32.Parse(colourText.Substring(2, 2), System.Globalization.NumberStyles.HexNumber); int b = Int32.Parse(colourText.Substring(4, 2), System.Globalization.NumberStyles.HexNumber); int tint = 80; r = this.Tint(r, tint); g = this.Tint(g, tint); b = this.Tint(b, tint); return Color.FromArgb(r, g, b); } private int Tint(int value, int tint) { value += tint; if (value < 0) value = 0; if (value > 255) value = 255; return value; } private decimal GetValue(DataRow row, int index) { if (row.IsNull(index) == true) return 0; return Convert.ToDecimal(row[index]); //string data = ((string)row[index]).Trim(); //if (data == "") // return 0; //if (Char.IsDigit(data[0]) == true) // return Decimal.Parse(data); //return Decimal.Parse(data, System.Globalization.NumberStyles.AllowCurrencySymbol | System.Globalization.NumberStyles.AllowThousands | System.Globalization.NumberStyles.AllowLeadingSign); } } }