Combine chart data if category name is the same - JavaFX - charts

I have an observable list of chart data which is created automatically. Is there any way to automatically combine the data which has the same category name in to one category?
The reason I need this is since the observable list of data is created directly form a table in a SQL database, I can not know if the user actually wants to show a combined view or each category and value separately in the chart.
Say I have the following data:
Instead of Bordeau-1 appearing this many times, I want all the values combined in to one pie called Bordeau-1, the same goes for Boredeau-2 and 3.
This is the code I use to automatically create data from the ObservableList which represents a table:
ObservableList<PieChart.Data> pieChartData
=
FXCollections.observableArrayList(EasyBind.map(observableListData, rowData -> {
String name = (String) rowData.get(0);
Double value = Double.parseDouble(rowData.get(1));
return new PieChart.Data(name, value);
}));

When you load the data, store it in a Map<String, Double>, and use the Map.merge(...) method to add or combine new values.
So in the code where you are loading the data from the database, you do this:
public Map<String, Double> loadDataFromDatabase() {
Map<String, Double> data = new HashMap<>();
// for each row in database:
// get name and value from row
data.merge(name, value, Double::sum);
// end for...
return data ;
}
Once you have loaded all the data into the map, you need to convert it to an ObservableList<PieChart.Data>. I don't think there's a cute way to do this in EasyBind, and if you're loading all the data at the start anyway, then you don't need the bound list. You can do something like:
ObservableList<PieChart.Data> pieChartData =
data.entrySet().stream()
.map(entry -> new PieChart.Data(entry.getKey(), entry.getValue()))
.collect(Collectors.toCollection(() -> FXCollections.observableArrayList()));
If the original map may get modified after the pie chart is created, you will need to make it an ObservableMap, register a listener with it, and update the pie chart data accordingly if the map changes. If you need this, it might be worth requesting the appropriate functionality in the EasyBind framework. Something like a method
public static <K, V, T> ObservableList<T> mapToList(ObservableMap<K, V> map, Function<Map.Entry<K, V>, T> mapping);

Related

Spring batchUpdate with IN clause

Given a simple table
create table car (
make varchar
model varchar
)
And the following DAO code
NamedParameterJdbcTemplate template;
String SQL = "delete from car where make = :make and model in (:model)";
void batchDelete(final Map<String, Collection<String>> map) {
SqlParameterSource[] params = map.entrySet().stream()
.map(entry -> toParams(entry.getKey(), entry.getValue()))
.toArray(SqlParameterSource[]::new);
template.batchUpdate(SQL, params);
}
void delete(final Map<String, Collection<String>> map) {
map.forEach((make, models) -> {
SqlParameterSource params = toParams(make, models);
template.update(SQL, params);
});
}
SqlParameterSource toParams(final String make, final Collection<String> models) {
return new MapSqlParameterSource("make", make)
.addValue("model", new ArrayList<>(models));
}
The batch delete function fails when the maps has 2 keys with different number of values for the IN clause in a batch. Assume Map.of creates and ordered Map.
// runs fine - 2 values for each key
batchDelete(Map.of("VW", Arrays.asList("Polo", "Golf"), "Toyota", Arrays.asList("Yaris", "Camry")));
// fails - first key has 1 value, second key has 2 values
batchDelete(Map.of("Toyota", Arrays.asList("Yaris"), "VW", Arrays.asList("Polo", "Golf")));
// runs fine - key with bigger list comes first
batchDelete(Map.of("VW", Arrays.asList("Polo", "Golf"), "Toyota", Arrays.asList("Yaris")));
// non batch delete runs fine either way
delete(Map.of("Toyota", Arrays.asList("Yaris"), "VW", Arrays.asList("Polo", "Golf")));
Spring documentation sort of alludes to that
https://docs.spring.io/spring/docs/current/spring-framework-reference/data-access.html#jdbc-in-clause
The SQL standard allows for selecting rows based on an expression that includes a variable list of values. A typical example would be select * from T_ACTOR where id in (1, 2, 3). This variable list is not directly supported for prepared statements by the JDBC standard; you cannot declare a variable number of placeholders. You need a number of variations with the desired number of placeholders prepared, or you need to generate the SQL string dynamically once you know how many placeholders are required. The named parameter support provided in the NamedParameterJdbcTemplate and JdbcTemplate takes the latter approach.
The error message is
The column index is out of range: 3, number of columns: 2.; nested exception is org.postgresql.util.PSQLException: The column index is out of range: 3, number of columns: 2.
What happens is the following line in NamedParameterJdbcTemplate # batchUpdate:
PreparedStatementCreatorFactory pscf = getPreparedStatementCreatorFactory(parsedSql, batchArgs[0]);
will create a dynamic sql out of the first batch arg length:
delete from car where make = ? and model in (?)
So the 2nd batch item which has 2 models will fail as there is only 1 placeholder.
What would be a workaround ? (other than grouping map entries by number of values)
Solution
Went back to plain old PreparedStatement
SQL - use ANY instead of IN
delete from car where make = ? and model = any (?)
DAO
Connection con;
PreparedStatement ps = con.prepareStatement("SQL");
map.forEach((make, models) -> {
int col = 0;
ps.setString(++col, make);
ps.setArray(++col, con.createArrayOf("text", models));
ps.addBatch();
});
ps.executeBatch();
I would recommend changing the SQL to look something more like this:
String SQL = "DELETE FROM car WHERE (make, model) IN (:ids)";
If you do it this way then you can use something similar to the answer I gave on this question: NamedJDBCTemplate Parameters is list of lists. Doing it this way means you can use NamedParameterJdbcTemplate.update(String sql, Map<String, ?> paramMap). Where in your paramMap the key would be "ids" and the value would be an instance of Collection<Object[]> where each entry in the collection is an array containing the value pairs you want to delete:
List<Object[]> params = new ArrayList<>();//you can make this any instance of collection you want
for (Car car : cars) {
params.add(new Object[] { car.getMake(), car.getModel() });
//this is just to provide an example of what I mean, obviously this will probably be different in your app.
}

Adding elements to TableViewer from a collection?

So I'm trying to make a simple program that takes in some basic information about a loan, and turns it into an amortization table.
I am using WindowBuilder for this, and thus I have a Tableviewer that is supposed to have a column for Month, interest, payment, ect.
Unfortunately, I can't figure out how to get the table to actually display information. I have a Loan object that generates an ArrayList of month objects (each month being one row of the table), but from what I've read online, it's not as simple as telling the Tableviewer to populate each column with a variable from that arraylist.
I have set my Content Provider to be a Loan object, which contains an Arraylist of Month Objects, and each Month object has a getter for each column.
How do I go about displaying the text in each column?
Thanks.
EDIT:
So, here is the snippet of code from the example given to me below:
colFirstName.setLabelProvider(new ColumnLabelProvider() {
#Override
public String getText(Object element) {
Person p = (Person) element;
return p.getFirstName();
}});
I don't quite understand what is going on here. It's overriding the getText method, and casting the element to the type of element that has their data?
balanceCol.setLabelProvider(new ColumnLabelProvider(){
#Override
public String getText(Object element) {
Loan m = (Loan) element;
return m.getMonthObj().get(0).getMonth();
}
});
When I run this, it almost sorta works. It fills in about half the months, and then I get this error:
java.lang.ClassCastException: model.Month cannot be cast to model.Loan

MVC how do i populate dropdownlist without using a model

I am using mvc 4, i have a form in a view (not binded to a model), its using standard html elements.
I want to populate a dropdownlist from a list value (i.e return from controller action)
also based on the selection of a value from the first dropdownlist i want to populate a second dropdownlist
can someone please guide
for the first dropdownlist you loop thru all the available options and add in <option> tags inside <select> for the second dropdownlist you need to either make bunch drop downs and hide/show them, or you create one big list and remove invalid entries base on the first list's selection. You would definitely need to do javascript for 2nd list.
If you don't want to use a Model (you should though), you will have to add the items to ViewData
I'll stub something out for you and you can complete the rest.
Inside your controller, create a list object of what you need. If you are using EntityFrameWork this will look familiar.
var list = context.Table.ToList();
List<System.Web.Mvc.SelectListItem> ddlItems = new List<System.Web.Mvc.SelectListItem>();
foreach (var item in list)
{
ddlItems.Add(new SelectListItem(){ Text = item.Text, Value = item.Value.ToString()});
}
ViewData["DDLItems"] = ddlItems;
#Html.DropDownListFor(x => x.LeagueId,
new SelectList((System.Collections.IEnumerable)ViewData["DDLItems"], "Value", "Text")
, "--Select League--", new { id = "league" })
You can define your second dropdownlist with just a placeholder until the cascade effect happens.
#Html.DropDownListFor(x => x.DivisionId, Enumerable.Empty<SelectListItem>(),
"--Select Division--", new { id = "ddlDivision" })
Your going to need to use JQuery and fire and event when the dropdown changes, and then use Ajax to make a call back to the controller. Theres 2348239 examples online about making Ajax calls, know how to do that because it's done all the time in MVC.
I'll let you figure that part out. One hint, inside the Ajax call you can pass data to the controller. something like this data: { leagueId: value } where value is the value of the dropdownlist you want to cascade off of. leagueId must match type and name of the parameter your controller will expect.
Return a Json object from your controller like so...
public JsonResult GetDivisions(int leagueId)
{
var division = //similar to before, fill a list.
return Json(divisions, JsonRequestBehavior.AllowGet);
}
And then in the success function of your Ajax call, you will populate the Second dropdownlist.
success: function (data) {
$.each(data, function (index, item)
$('#ddlDivision')
.append($('<option></option>')
.val(item.Value)
.html(item.Text))
item.Value and item.Text can be anything, just as long as the Json you return as the properties of Text and Value
IE...
var divisions = (from x in context.Division
select new
{
Text = league + " " + x.Region,
Value = x.DivisionId
}).ToList();

Viewing a data matrix in Eclipse (RCP)

This is how I update my TableViewer that displays a list:
public void view(MyListClass list) {
ViewerSupport.bind(
this,
new WritableList(list, controller.theClass()),
BeanProperties.values(controller.theClass(), controller.strings())
);
}
controller is an instance of a class that encapsulates my glue code (it is a different class for the two listings; as is controller.theClass()). strings() is an array of property names. MyListClass descends from ArrayList<MyListEntryObject>. That works fine. But, I want to display a matrix of data. MyMatrixClass is a HashMap<Point, MyMatrixEntryObject>. This is what I've tried:
private void view(MyMatrixClass matrix) {
columns(matrix.columns());
List<WritableList> lists = new ArrayList<WritableList>(matrix.rows());
for (MyEntityClass list : matrix.children())
if (list instanceof MyListClass)
lists.add(new WritableList((MyListClass) list, controller.theClass()));
WritableList[] alists = lists.toArray(new WritableList[0]);
MultiList mlist = new MultiList(alists);
ViewerSupport.bind(
this,
mlist,
BeanProperties.value(controller.theClass(), "value")
);
}
This doesn't work. I get null argument popup errors. (Every data model class implements MyEntityClass. Class names have been altered due to this being a proprietary program I'm being employed to develop.)
Long story short, how do I use ViewerSupport and BeanProperties to display a matrix of data in a TableViewer?
As the JFace table viewer is line-based, you have to provide your matrix in a line-based way. You have to create a collection of the lines of the matrix, and then set this list as the input of the viewer. After that, you could create BeanProperties, that display the columns of the selected rows.

ADO.NET Mapping From SQLDataReader to Domain Object?

I have a very simple mapping function called "BuildEntity" that does the usual boring "left/right" coding required to dump my reader data into my domain object. (shown below) My question is this - If I don't bring back every column in this mapping as is, I get the "System.IndexOutOfRangeException" exception and wanted to know if ado.net had anything to correct this so I don't need to bring back every column with each call into SQL ...
What I'm really looking for is something like "IsValidColumn" so I can keep this 1 mapping function throughout my DataAccess class with all the left/right mappings defined - and have it work even when a sproc doesn't return every column listed ...
Using reader As SqlDataReader = cmd.ExecuteReader()
Dim product As Product
While reader.Read()
product = New Product()
product.ID = Convert.ToInt32(reader("ProductID"))
product.SupplierID = Convert.ToInt32(reader("SupplierID"))
product.CategoryID = Convert.ToInt32(reader("CategoryID"))
product.ProductName = Convert.ToString(reader("ProductName"))
product.QuantityPerUnit = Convert.ToString(reader("QuantityPerUnit"))
product.UnitPrice = Convert.ToDouble(reader("UnitPrice"))
product.UnitsInStock = Convert.ToInt32(reader("UnitsInStock"))
product.UnitsOnOrder = Convert.ToInt32(reader("UnitsOnOrder"))
product.ReorderLevel = Convert.ToInt32(reader("ReorderLevel"))
productList.Add(product)
End While
Also check out this extension method I wrote for use on data commands:
public static void Fill<T>(this IDbCommand cmd,
IList<T> list, Func<IDataReader, T> rowConverter)
{
using (var rdr = cmd.ExecuteReader())
{
while (rdr.Read())
{
list.Add(rowConverter(rdr));
}
}
}
You can use it like this:
cmd.Fill(products, r => r.GetProduct());
Where "products" is the IList<Product> you want to populate, and "GetProduct" contains the logic to create a Product instance from a data reader. It won't help with this specific problem of not having all the fields present, but if you're doing a lot of old-fashioned ADO.NET like this it can be quite handy.
Although connection.GetSchema("Tables") does return meta data about the tables in your database, it won't return everything in your sproc if you define any custom columns.
For example, if you throw in some random ad-hoc column like *SELECT ProductName,'Testing' As ProductTestName FROM dbo.Products" you won't see 'ProductTestName' as a column because it's not in the Schema of the Products table. To solve this, and ask for every column available in the returned data, leverage a method on the SqlDataReader object "GetSchemaTable()"
If I add this to the existing code sample you listed in your original question, you will notice just after the reader is declared I add a data table to capture the meta data from the reader itself. Next I loop through this meta data and add each column to another table that I use in the left-right code to check if each column exists.
Updated Source Code
Using reader As SqlDataReader = cmd.ExecuteReader()
Dim table As DataTable = reader.GetSchemaTable()
Dim colNames As New DataTable()
For Each row As DataRow In table.Rows
colNames.Columns.Add(row.ItemArray(0))
Next
Dim product As Product While reader.Read()
product = New Product()
If Not colNames.Columns("ProductID") Is Nothing Then
product.ID = Convert.ToInt32(reader("ProductID"))
End If
product.SupplierID = Convert.ToInt32(reader("SupplierID"))
product.CategoryID = Convert.ToInt32(reader("CategoryID"))
product.ProductName = Convert.ToString(reader("ProductName"))
product.QuantityPerUnit = Convert.ToString(reader("QuantityPerUnit"))
product.UnitPrice = Convert.ToDouble(reader("UnitPrice"))
product.UnitsInStock = Convert.ToInt32(reader("UnitsInStock"))
product.UnitsOnOrder = Convert.ToInt32(reader("UnitsOnOrder"))
product.ReorderLevel = Convert.ToInt32(reader("ReorderLevel"))
productList.Add(product)
End While
This is a hack to be honest, as you should return every column to hydrate your object correctly. But I thought to include this reader method as it would actually grab all the columns, even if they are not defined in your table schema.
This approach to mapping your relational data into your domain model might cause some issues when you get into a lazy loading scenario.
Why not just have each sproc return complete column set, using null, -1, or acceptable values where you don't have the data. Avoids having to catch IndexOutOfRangeException or re-writing everything in LinqToSql.
Use the GetSchemaTable() method to retrieve the metadata of the DataReader. The DataTable that is returned can be used to check if a specific column is present or not.
Why don't you use LinqToSql - everything you need is done automatically. For the sake of being general you can use any other ORM tool for .NET
If you don't want to use an ORM you can also use reflection for things like this (though in this case because ProductID is not named the same on both sides, you couldn't do it in the simplistic fashion demonstrated here):
List Provider in C#
I would call reader.GetOrdinal for each field name before starting the while loop. Unfortunately GetOrdinal throws an IndexOutOfRangeException if the field doesn't exist, so it won't be very performant.
You could probably store the results in a Dictionary<string, int> and use its ContainsKey method to determine if the field was supplied.
I ended up writing my own, but this mapper is pretty good (and simple): https://code.google.com/p/dapper-dot-net/