I have data in the form shown below. I want to display the data in such a way that time (the "No column name" column in the figure) should be along the Y-axis and testiD and queryId in the X-axis. I need it in such a way that items with same testId should be grouped (in the X-axis) and the corresponding time should be displayed (Y-axis)
here is the code I use, it does not work as I expected.
protected internal Chart GenerateChart(DataTable dtChartDataSource,Chart chart,int intType )
{
ChartArea chartArea = new ChartArea() { Name = "ChartArea" };
chart.ChartAreas.Add(chartArea);
chart.Palette = ChartColorPalette.BrightPastel;
string series = string.Empty;
if (dtChartDataSource != null)
{
foreach (DataColumn dc in dtChartDataSource.Columns)
{
if (chart.Series.FindByName(dc.ColumnName) == null)
{
series = dc.ColumnName[1].ToString();
chart.Series.Add(series);
chart.Series[series].ChartType = (SeriesChartType)intType;
}
foreach (DataRow dr in dtChartDataSource.Rows)
{
double dataPoint = 0;
double.TryParse(dr[dc.ColumnName].ToString(), out dataPoint);
DataPoint objDataPoint = new DataPoint() { AxisLabel = "series", YValues = new double[] { dataPoint } };
chart.Series[series].Points.Add(dataPoint);
}
}
}
return chart;
}
I could get my requirement done by below code.
public Chart fnTestChart(Chart chart, DataTable dt)
{
DataTable dtUniqueCols = dt.DefaultView.ToTable(true, "Test ID");
chart.ChartAreas.Add("area");
chart.ChartAreas["area"].AxisX.Minimum = 0;
//chart.ChartAreas["area"].AxisX.Interval = 1;
chart.ChartAreas["area"].AxisY.Minimum = 0;
//chart.ChartAreas["area"].AxisY.Interval = 1;
foreach (DataRow dr in dtUniqueCols.Rows)
{
chart.Series.Add(dr[0].ToString());
}
foreach (DataRow dr in dt.Rows)
{
chart.Series[dr[0].ToString()].Points.AddXY(dr[1].ToString(), dr[2].ToString());
}
return chart;
}
You can use many different libraries, to achieve this goal.
Ex: (Visblox)
http://csharp-source.net/open-source/charting-and-reporting
Related
I am trying to fill a DataTable with values from dynamically created controls. The idea is to have three columns in the DataTable, and fill them one after the other. First fill column 1, then 2, then 3. However, I am struggling to do this effectively.
What is happening is that the DataTable is being filled, but the Data in columns 1, 2 and 3 is not side by side, but instead being filled in different rows. I know this is because I am adding a row with each loop, but I have tried some other things that did not work. Could you help me out?
DataTable tvpIngredients = new DataTable();
tvpIngredients.Columns.Add("Quantity", typeof(string));
tvpIngredients.Columns.Add("Measure", typeof(string));
tvpIngredients.Columns.Add("Ingredient", typeof(string));
foreach (Control ctrlQtt in quantity.Controls)
{
if (ctrlQtt is TextBox)
{
DataRow drNew = tvpIngredients.NewRow();
TextBox quantity = (TextBox)ctrlQtt;
drNew["Quantity"] = quantity.Text;
tvpIngredients.Rows.Add(drNew);
}
}
foreach (Control ctrlMsr in measure.Controls)
{
if (ctrlMsr is DropDownList)
{
DataRow drNew = tvpIngredients.NewRow();
DropDownList measure = (DropDownList)ctrlMsr;
drNew["Measure"] = measure.SelectedValue.ToString();
tvpIngredients.Rows.Add(drNew);
}
}
foreach (Control ctrlIng in ingredient.Controls)
{
if (ctrlIng is TextBox)
{
DataRow drNew = tvpIngredients.NewRow();
TextBox ingredient = (TextBox)ctrlIng;
drNew["Ingredient"] = ingredient.Text;
tvpIngredients.Rows.Add(drNew);
}
}
Thank you!
You already added rows. You need to modify the row column instead.
DataTable tvpIngredients = new DataTable();
tvpIngredients.Columns.Add("Quantity", typeof(string));
tvpIngredients.Columns.Add("Measure", typeof(string));
tvpIngredients.Columns.Add("Ingredient", typeof(string));
foreach (Control ctrlQtt in quantity.Controls)
{
if (ctrlQtt is TextBox)
{
DataRow drNew = tvpIngredients.NewRow();
TextBox quantity = (TextBox)ctrlQtt;
drNew["Quantity"] = quantity.Text;
tvpIngredients.Rows.Add(drNew);
}
}
for (int i = 1; i < tvpIngredients.Rows.Count; i++)
{
if (ctrlMsr is DropDownList)
{
DataRow drNew = tvpIngredients.NewRow();
DropDownList measure = (DropDownList)ctrlMsr;
drNew["Measure"] = measure.SelectedValue.ToString();
tvpIngredients.Rows[i][1].ToString() = drNew;
}
}
for (int i = 1; i < tvpIngredients.Rows.Count; i++)
{
if (ctrlIng is TextBox)
{
DataRow drNew = tvpIngredients.NewRow();
TextBox ingredient = (TextBox)ctrlIng;
drNew["Ingredient"] = ingredient.Text;
tvpIngredients.Rows[i][2].ToString() = drNew;
}
}
To solve a problem like this you need to think about the way you model your data.
Looking at the code here I can guess that you have a Form let's call it Recipe that has at least 3 controls quantity, measure, and ingredients. These controls can contain an arbitrary number of items. It looks like they would be of equal length given that they are each related to one item in your recipe.
You could do this:
if(quantity.Controls.Count != measure.Controls.Count
|| quantity.Controls.Count != ingredient.Controls.Count)
{
throw new Exception("Invalid ingredients list");
}
var quantities = new Control[quantity.Controls.Count];
var measurements = new Control[measure.Controls.Count];
var ingredients = new Control[ingredient.Controls.Count];
quantity.Controls.CopyTo(quantities, 0);
measure.Controls.CopyTo(measurements , 0);
ingredient.Controls.CopyTo(ingredients , 0);
for(var i = 0; i < quantity.Length; ++i)
{
// Make your new row
// index the arrays and fill the row data in
}
There is probably a more elegant solution to the whole problem if you spend some time thinking through how you model it.
I am trying to scrape data from the webpage. However, I am having a trouble scraping all of data in the table. I need to switch pages to get all the data and I am willing to get an output with DataGridTable. I am having a trouble figuring out how to do this even though there is a change with number of pages they have in the website. I would like to add information automatically on a data grid table pages by pages. My input(Website) is only showing 25 items. Thats why I have 25 items in DataGridTable. I would like to justify a "number of pages" from "go to end page button"'s element. So that my program knows how many pages are there to scrape from the website. but, if there's a different way, I wanna know thank you.
This is my code for now.
DataTable dt = new DataTable();
var header = driver.FindElement(By.CssSelector("#gridComponent > div.k-grid-header"));
foreach (var row in header.FindElements(By.TagName("tr")))
{
//Configure Number of Col and row
int cellIndex = 0;
string[] arr = new string[32];
//Get Cell Data
foreach (var cell in row.FindElements(By.TagName("th")))
{
// Check the header cell for a checkbox child. If no
// such child exists, add the column.
var headerCheckboxes = cell.FindElements(By.CssSelector("input[type='checkbox']"));
if (headerCheckboxes.Count == 0)
{
//Number of Col Data Load
if (cellIndex <= 29)
{
arr[cellIndex] = cell.Text;
dt.Columns.Add(cell.Text);
}
else
cellIndex++;
}
}
Console.WriteLine(arr);
}
var table = driver.FindElement(By.CssSelector("#gridComponent"));
//Get Row value
foreach (var row in table.FindElements(By.TagName("tr")))
{
//Configure Number of Col and row
int cellIndex = 0;
// Use a list instead of an array
List<string> arr = new List<string>();
//Get Cell Data
foreach (var cell in row.FindElements(By.TagName("td")))
{
// Skip the first column in the row by checking
// if the cell index is 0.
if (cellIndex != 0)
{
string cellValue = "";
Console.WriteLine(cell);
var checkboxes = cell.FindElements(By.CssSelector("input[type='checkbox']"));
if (checkboxes.Count > 0)
{
bool isChecked = false;
isChecked = checkboxes[0].Selected;
cellValue = isChecked.ToString();
}
else
{
cellValue = cell.Text;
}
arr.Add(cellValue);
}
cellIndex++;
}
dt.Rows.Add(arr.ToArray());
}
dataGridView1.DataSource = dt;
driver.FindElement(By.CssSelector("#gridComponent > div.k-pager-wrap.k-grid-pager.k-widget.k-floatwrap > ul > li:nth-child(3)")).Click();
}
This is the table that I am trying to scrape from.
This is the code for the following element that is shown picture above.
<span class="k-icon k-i-arrow-end-right"></span>
Thank you so much.
You may want to consider the index information "1 - 25 out of 64 items", since it is a good indicator of the total number of pages.
Batch = 1 - 25 i.e. 25 items per page
Total items = 64
No. of pages = roundup (64 / 25)
PS: A better option, without any computations, maybe to get the "data-page" attribute of the last page button.
I Finally got the answer for this.
private List<List<string>> GetRecords(IWebElement table)
{
List<List<string>> rows = new List<List<string>>(); ;
//Get Row value
foreach (var row in table.FindElements(By.TagName("tr")))
{
//Configure Number of Col and row
int cellIndex = 0;
// Use a list instead of an array
List<string> cols = new List<string>();
//Get Cell Data
foreach (var cell in row.FindElements(By.TagName("td")))
{
// Skip the first column in the row by checking
// if the cell index is 0.
if (cellIndex != 0)
{
string cellValue = "";
Console.WriteLine(cell);
var checkboxes = cell.FindElements(By.CssSelector("input[type='checkbox']"));
if (checkboxes.Count > 0)
{
bool isChecked = false;
isChecked = checkboxes[0].Selected;
cellValue = isChecked.ToString();
}
else
{
cellValue = cell.Text;
}
cols.Add(cellValue);
}
cellIndex++;
}
rows.Add(cols);
}
return rows;
}
private void button1_Click(object sender, EventArgs e)
{
//Configure to Hide CMD
var chromeDriverService = ChromeDriverService.CreateDefaultService();
chromeDriverService.HideCommandPromptWindow = true;
//Configure to Hide Chrome
ChromeOptions option = new ChromeOptions();
option.AddArgument("--headless");
//HIDING CHROME UN-COMMNET THE SECOND ONE TO SHOW
//IWebDriver driver = new ChromeDriver(chromeDriverService, option);
IWebDriver driver = new ChromeDriver();
driver.Url = "**************";
driver.Manage().Window.Maximize();
driver.SwitchTo().DefaultContent();
//Log-in
driver.FindElement(By.Id("username")).SendKeys("*****");
driver.FindElement(By.Id("password")).SendKeys("******" + OpenQA.Selenium.Keys.Enter);
//Entering Access Code
driver.FindElement(By.Id("password")).SendKeys("*******");
driver.FindElement(By.Id("accesscode")).SendKeys("********" + OpenQA.Selenium.Keys.Enter);
//go to CustomerList
driver.Navigate().GoToUrl("***********");
driver.Navigate().GoToUrl("*****************");
//Wait till load 3 seconds
waitOnPage(2);
DataTable dt = new DataTable();
var header = driver.FindElement(By.CssSelector("#gridComponent > div.k-grid-header"));
foreach (var row in header.FindElements(By.TagName("tr")))
{
//Configure Number of Col and row
int cellIndex = 0;
string[] arr = new string[32];
//Get Cell Data
foreach (var cell in row.FindElements(By.TagName("th")))
{
// Check the header cell for a checkbox child. If no
// such child exists, add the column.
var headerCheckboxes = cell.FindElements(By.CssSelector("input[type='checkbox']"));
if (headerCheckboxes.Count == 0)
{
//Number of Col Data Load
if (cellIndex <= 29)
{
arr[cellIndex] = cell.Text;
dt.Columns.Add(cell.Text);
}
else
cellIndex++;
}
}
Console.WriteLine(arr);
}
var table = driver.FindElement(By.CssSelector("#gridComponent"));
List<List<string>> records = GetRecords(table);
// Supposing you want the footer information
var lastPageStr = table.FindElement(By.ClassName("k-pager-last")).GetAttribute("data-page");
var lastPage = Convert.ToInt16(lastPageStr);
// You can select other info lik this
// class="k-link k-pager-nav" data-page="1"
driver.FindElement(By.CssSelector("#gridComponent > div.k-pager-wrap.k-grid-pager.k-widget.k-floatwrap > ul > li:nth-child(3)")).Click();
// Cycle over the pages
for (int p = 0; p < (lastPage - 1); p++)
{
driver.FindElement(By.CssSelector("#gridComponent > div.k-pager-wrap.k-grid-pager.k-widget.k-floatwrap > a:nth-child(4) > span")).Click();
waitOnPage(2);
var rows = GetRecords(table);
records.AddRange(rows);
}
// Add all rows to DT
//dt.Rows.Add(records[4].ToArray());
foreach(var row in records)
{
dt.Rows.Add(row.ToArray());
}
dataGridView1.DataSource = dt;
}
I am making a custom report using Google Visualization API.
It will have 6 sections with each section having tables on either side and a chart in the middle.
Since the formats differ slightly I was spending a lot of time defining classes for each one-off case.
I decided to try Google.DataTable.Net.Wrapper 3.1.0.0.
I created a stored procedure that returns a DataSet and then walk through the DataSet in my Controller and pass each table that I need.
The Data looks something like this
rownum charttypeid charttypename
----------- ----------- ------------------
1 1 Membership Sales
rownum chartareaid chartareaname
----------- ----------- -------------------------
1 1 Membership Sales Overview
2 2 Membership Sales Chart
title value display
------------------------- ----------- ----------
# of Walk-ins 25 25
# of Tours 17 17
# of New Members 35 35
Tour Conversion 78 78%
Percent to Goal 87 87%
Month value display goalvalue goaldisplay
----- ----------- ---------- ----------- -----------
Sep 3125 $3,125.00 1500 $1,500.00
Oct 4500 $4,500.00 1500 $1,500.00
Sometimes the charts will have money formats or other display formats, sometimes dates etc.
I can't figure out how to add/modify the "f" part of the cell which provides a string format for display.
My Controller code looks like this
[ResponseType(typeof(List<ChartPanel>))]
public IHttpActionResult GetChart(int gym, string dateCategory, string iso8601date, int id = -1)
{
if (!String.IsNullOrWhiteSpace(dateCategory))
{
dateCategory = dateCategory.ToLower();
string strConnString = ConfigurationManager.ConnectionStrings["PrimaryDBConnection"].ConnectionString;
// return DataSet From USP
DataSet dashBoardDataSet = GetDataSQL(strConnString, gym, dateCategory, iso8601date, 0);
if (dashBoardDataSet != null)
{
int chartPanelCount = dashBoardDataSet.Tables[0].Rows.Count;
List<ChartPanel> chartTypeList = new List<ChartPanel>(); // list for all the panels
// first table describes the Chart Panels
int tableCount = 0;
for (int chartPanelLoop = 0; chartPanelLoop < chartPanelCount; chartPanelLoop++)
{ // for every panel
tableCount++;
ChartPanel chartPanel = new ChartPanel();
chartPanel.name = dashBoardDataSet.Tables[0].Rows[chartPanelLoop][2].ToString();
// second table describes the following chart areas for the panel
int panelAreaCount = dashBoardDataSet.Tables[1].Rows.Count;
List<ChartArea> chartAreaList = new List<ChartArea>();
int areaTableCount = tableCount;
for (int panelAreaLoop = 0; panelAreaLoop < panelAreaCount; panelAreaLoop++)
{ // for every area
int areaTable = areaTableCount;
ChartArea chartArea = new ChartArea();
chartArea.name = dashBoardDataSet.Tables[areaTable].Rows[panelAreaLoop][2].ToString();
int chartAreaRowNum = panelAreaLoop + 1;
System.Data.DataTable systDT = new System.Data.DataTable();
systDT = dashBoardDataSet.Tables[areaTable + chartAreaRowNum];
var dt = systDT.ToGoogleDataTable(); //convert with wrapper
//issue ==> //dt = RemoveColumnsWithTitleLikeDisplayAndPassCellContentsAsFormattedStringToPreviousCell(dt);
chartArea.table = JsonConvert.DeserializeObject(dt.GetJson());
chartAreaList.Add(chartArea);
//}
if (chartAreaList.Count() > 0) chartPanel.areas = chartAreaList;
tableCount++;
}
if (chartPanel.areas != null && chartPanel.areas.Count() > 0) chartTypeList.Add(chartPanel);
}
return Ok(chartTypeList);
}
else { return NotFound(); }
}
else { return NotFound(); }
}
Is there a better way to do this?
Figured it out. Here is my working code with a hack to look for any column where (colName.Contains("_display")) and make it be the formatted ("f") data for the previous column.
To map the column to the formatting column I made a custom class.
Custom Class
class ColumnDisplayMap
{
public int columnToFormat { get; set; }
public int formatColumn { get; set; }
}
Method For Building Charts
[ResponseType(typeof(List<ChartPanel>))]
public IHttpActionResult GetChart(int gym, string dateCategory, string iso8601date, int id = -1)
{
if (!String.IsNullOrWhiteSpace(dateCategory))
{
dateCategory = dateCategory.ToLower();
string strConnString = ConfigurationManager.ConnectionStrings["PrimaryDBConnection"].ConnectionString;
// return DataSet From USP
DataSet dashBoardDataSet = GetDataSQL(strConnString, gym, dateCategory, iso8601date, 0);
if (dashBoardDataSet != null)
{
int chartPanelCount = dashBoardDataSet.Tables[0].Rows.Count;
List<ChartPanel> chartTypeList = new List<ChartPanel>(); // list for all the panels
// first table describes the Chart Panels
int tableCount = 0;
for (int chartPanelLoop = 0; chartPanelLoop < chartPanelCount; chartPanelLoop++)
{ // for every panel
ChartPanel chartPanel = new ChartPanel();
chartPanel.name = dashBoardDataSet.Tables[0].Rows[chartPanelLoop][2].ToString();
// second table describes the following chart areas for the panel
DataRow[] areaTableRows = dashBoardDataSet.Tables[1].Select("charttype = " + (chartPanelLoop + 1).ToString());
int panelAreaCount = areaTableRows.Count();
List<ChartArea> chartAreaList = new List<ChartArea>();
for (int panelAreaLoop = 0; panelAreaLoop < panelAreaCount; panelAreaLoop++)
{ // for every area
int areaTable = 1;
ChartArea chartArea = new ChartArea();
chartArea.name = areaTableRows[panelAreaLoop][3].ToString(); // dashBoardDataSet.Tables[areaTable].Rows[panelAreaLoop][3].ToString();
DataColumnCollection columns = dashBoardDataSet.Tables[areaTable + tableCount + 1].Columns;
DataRowCollection rows = dashBoardDataSet.Tables[areaTable + tableCount + 1].Rows;
Google.DataTable.Net.Wrapper.DataTable gdt = new Google.DataTable.Net.Wrapper.DataTable();
List<ColumnDisplayMap> cMap = new List<ColumnDisplayMap>();
foreach (DataColumn col in columns)
{
string colName = col.ToString();
if (!colName.Contains("_display"))
{
ColumnType type = ColumnType.Number;
if (!col.IsNumeric()) type = ColumnType.String;
gdt.AddColumn(new Column(type, col.ToString(), col.ToString()));
}else
{
ColumnDisplayMap cdm = new ColumnDisplayMap(){columnToFormat = col.Ordinal - 1, formatColumn = col.Ordinal};
cMap.Add(cdm);
}
}
foreach (DataRow row in rows)
{
var r = gdt.NewRow();
for (int cellItem = 0; cellItem < row.ItemArray.Count(); cellItem++)
{
if (cMap.Any(c => c.columnToFormat.Equals(cellItem)))
{
r.AddCell(new Cell(row.ItemArray[cellItem], row.ItemArray[cellItem + 1].ToString()));
}
else if (cMap.Any(c => c.formatColumn.Equals(cellItem)))
{
// do nothing
}
else
{
r.AddCell(new Cell(row.ItemArray[cellItem], row.ItemArray[cellItem].ToString()));
}
}
gdt.AddRow(r);
}
chartArea.table = JsonConvert.DeserializeObject(gdt.GetJson());
chartAreaList.Add(chartArea);
//}
if (chartAreaList.Count() > 0) chartPanel.areas = chartAreaList;
tableCount++;
}
if (chartPanel.areas != null && chartPanel.areas.Count() > 0) chartTypeList.Add(chartPanel);
}
return Ok(chartTypeList);
}
else { return NotFound(); }
}
else { return NotFound(); }
}
I have the 3d bar chart below:
Was wondering if there's a way that I can exclude the 'Closed' and 'TOTALS' row in the chart?
Here's my code that generates this:
chtOverview.DataSource = dt;
var headercountries = new List<string>();
foreach (DataColumn dc in dt.Columns)
{
if (!(dc.ColumnName.ToLower().Contains("total") || dc.ColumnName.ToLower().Contains("status")))
{
headercountries.Add(dc.ColumnName);
chtOverview.Series.Add(new Series(dc.ColumnName));
}
}
chtOverview.Legends.Add(new Legend("Legend1"));
for (int i = 0; i < headercountries.Count; i++)
{
chtOverview.Series[i].ChartArea = "ChartArea1";
chtOverview.Series[i].ChartType = SeriesChartType.Column;
chtOverview.Series[i].IsValueShownAsLabel = true;
chtOverview.Series[i].Legend = "Legend1";
chtOverview.Series[i].XValueMember = "Case Status";
chtOverview.Series[i].YAxisType = AxisType.Primary;
chtOverview.Series[i].YValueMembers = headercountries[i];
}
chtOverview.DataBind();
Thanks for the help. :)
In the line where you declare the datasource:
chtOverview.DataSource = dt.Select("[Case Status] NOT IN ('Closed', 'TOTALS')");
I have the following data in sql that needs to be represented in Stacked Column chart.
Name Type Amount
Paul T1 100
John T2 200
John T3 300
The name represents the X-axis and the Type as Series. The issue I am facing is duplicate X-axis value with the same name. This is the code example I was following before getting the duplicate Name but now doesn't make sense.
SectionData data = GetSectionData(sectionId);
List<double[]> yValues= new List<double[]>();
if (data != null && data.LineItems.Count() > 0)
{
List<string> xValues = new List<string>();
List<string> typeNames = new List<string>();
int index=0;
foreach (var yval in data.LineItems)
{
xValues.Add(yval.Name);
typeNames.Add(yval.TypeName);
double[] temp = new double[data.LineItems.Count()];
temp.SetValue(yval.Amont, index);
yValues.Add(temp);
index++;
}
foreach (string name in typeNames)
{
StackedColumnChart.Series.Add(
new Series
{
Name = name,
ChartType = SeriesChartType.StackedColumn,
Font= new Font("Segoe UI", 8),
CustomProperties="DrawingStyle=Cylinder",
Legend = "Default"
}
);
}
for (int counter = 0; counter < typeNames.Count; counter++)
{
try
{
StackedColumnChart.Series[counter].Points.DataBindXY(xValues, yValues.Select(i => i[counter]).ToList());
}
catch (Exception ex)
{
//throw ex
}
}
}
Any help.