Related
I'm getting data from an API and need to format it differently. I have a car_array that consists of an array of hashes. However, sometimes there will be a sub-array as one of the hash values that contains more than 1 element. In this case, there should be a loop so that each element in the array gets mapped correctly as separate entries.
An example of data, note how prices and options_package are arrays with multiple elements.
[{
dealer_id: 1,
dealer_name: "dealership 1",
car_make: "jeep",
prices: ['30', '32', '35'],
options_package: ['A', 'B', 'C']
}, {
dealer_id: 2,
dealer_name: "dealership 2",
car_make: "ford",
prices: ['50', '55'],
options_package: ['X', 'Y']
}, {
dealer_id: 3,
dealer_name: "dealership 3",
car_make: "dodge",
prices: ['70'],
options_package: ['A']
}]
I would like to create multiple entries when there are multiple array elements
for example, the data above should be broken out and mapped as:
some_array = [
{ dealer_id: 1, dealer_name: "dealership 1", car_make: "jeep", price: '30', options_package: 'A' },
{ dealer_id: 1, dealer_name: "dealership 1", car_make: "jeep", price: '32', options_package: 'B' },
{ dealer_id: 1, dealer_name: "dealership 1", car_make: "jeep", price: '35', options_package: 'C' },
{ dealer_id: 2, dealer_name: "dealership 2", car_make: "ford", price: '50', options_package: 'X' },
{ dealer_id: 2, dealer_name: "dealership 2", car_make: "ford", price: '55', options_package: 'Y' },
{ dealer_id: 3, dealer_name: "dealership 3", car_make: "dodge", price: '70', options_package: 'A' }
]
Here's what I've got so far:
car_arr.each do |car|
if car['Prices'].length > 1
# if there are multiple prices/options loop through each one and create a new car
car.each do |key, value|
if key == 'Prices'
value.each do |price|
formatted_car_array << {
dealer_id: car['dealer_id'],
dealer_name: car['dealer_name'],
car_make: car['make'],
options_package: ???????,
price: price,
}
end
end
end
else
# there's only element for price and options_package
formatted_car_array << {
dealer_id: car['dealer_id'],
dealer_name: car['dealer_name'],
car_make: car['make'],
options_package: car['options_package'],
price: car['prices']
}
end
end
Consider just one hash to start with, and how this problem can be solved for this simpler problem.
h = {
dealer_id: 1,
dealer_name: "dealership 1",
car_make: "jeep",
prices: ['30', '32', '35'],
options_package: ['A', 'B', 'C']
}
Let's get combinations of prices and options packages using #zip.
h[:prices].zip(h[:options_package])
# => [["30", "A"], ["32", "B"], ["35", "C"]]
The length of 3 for this array corresponds with how many hashes we expect to get from it, so let's map over those, building a new hash each time.
h[:prices].zip(h[:options_package]).map do |price, pkg|
{
dealer_id: h[:dealer_id],
dealer_name: h[:dealer_name],
car_make: h[:car_make],
price: price,
options_package: pkg,
}
end
# => [{:price=>"30", :options_package=>"A", :dealership=>nil, :dealer_id=>1, :car_make=>"jeep"},
# {:price=>"32", :options_package=>"B", :dealership=>nil, :dealer_id=>1, :car_make=>"jeep"},
# {:price=>"35", :options_package=>"C", :dealership=>nil, :dealer_id=>1, :car_make=>"jeep"}]
Now you just need to #flat_map this over your array.
car_arr.flat_map do |h|
h[:prices].zip(h[:options_package]).map do |price, pkg|
{
dealer_id: h[:dealer_id],
dealer_name: h[:dealer_name],
car_make: h[:car_make],
price: price,
options_package: pkg,
}
end
end
# => [{:price=>"30", :options_package=>"A", :dealership=>nil, :dealer_id=>1, :car_make=>"jeep"},
# {:price=>"32", :options_package=>"B", :dealership=>nil, :dealer_id=>1, :car_make=>"jeep"},
# {:price=>"35", :options_package=>"C", :dealership=>nil, :dealer_id=>1, :car_make=>"jeep"},
# {:price=>"50", :options_package=>"X", :dealership=>nil, :dealer_id=>2, :car_make=>"ford"},
# {:price=>"55", :options_package=>"Y", :dealership=>nil, :dealer_id=>2, :car_make=>"ford"},
# {:price=>"70", :options_package=>"A", :dealership=>nil, :dealer_id=>3, :car_make=>"dodge"}]
If arr is the array of hashes given in the question one may write the following.
arr.flat_map do |h|
h[:prices].zip(h[:options_package]).map do |p,o|
h.reject { |k,_| k == :prices }.merge(price: p, options_package: o)
end
end
#=> [{:dealer_id=>1, :dealer_name=>"dealership 1", :car_make=>"jeep",
# :options_package=>"A", :price=>"30"},
# {:dealer_id=>1, :dealer_name=>"dealership 1", :car_make=>"jeep",
# :options_package=>"B", :price=>"32"},
# {:dealer_id=>1, :dealer_name=>"dealership 1", :car_make=>"jeep",
# :options_package=>"C", :price=>"35"},
# {:dealer_id=>2, :dealer_name=>"dealership 2", :car_make=>"ford",
# :options_package=>"X", :price=>"50"},
# {:dealer_id=>2, :dealer_name=>"dealership 2", :car_make=>"ford",
# :options_package=>"Y", :price=>"55"},
# {:dealer_id=>3, :dealer_name=>"dealership 3", :car_make=>"dodge",
# :options_package=>"A", :price=>"70"}]
Note that this code need not be changed if key-value pairs are added to the hash or if the keys :dealer_id and :dealer_name are deleted or renamed.
My answer is an extension upon the answer of Chris.
car_arr.flat_map do |h|
h[:prices].zip(h[:options_package]).map do |price, pkg|
{
dealer_id: h[:dealer_id],
dealer_name: h[:dealer_name],
car_make: h[:car_make],
price: price,
options_package: pkg,
}
end
end
This answer could be shortened if you're using the newest Ruby version.
Ruby 3.0 introduced Hash#except, which lets you easily create a copy of a hash without the specified key(s). This allows us to reduce the answer to:
cars.flat_map do |car|
car[:prices].zip(car[:options_package]).map do |price, pkg|
car.except(:prices).merge(price: price, options_package: pkg)
end
end
car.except(:prices) will create a new hash without the :prices key/value-pair, we don't need to remove :options_package, since merge will overwrite the the old :options_package value with a new value.
Ruby 3.1 introduced { x:, y: } as syntax sugar for { x: x, y: y }. This allows us to "reduce" the answer further to:
cars.flat_map do |car|
car[:prices].zip(car[:options_package]).map do |price, options_package|
car.except(:prices).merge(price:, options_package:)
end
end
Input
input = [{
dealer_id: 1,
dealer_name: "dealership 1",
car_make: "jeep",
prices: ['30', '32', '35'],
options_package: ['A', 'B', 'C']
}, {
dealer_id: 2,
dealer_name: "dealership 2",
car_make: "ford",
prices: ['50', '55'],
options_package: ['X', 'Y']
}, {
dealer_id: 3,
dealer_name: "dealership 3",
car_make: "dodge",
prices: ['70'],
options_package: ['A']
}]
Code
result = input.map do |h|
prices = h[:prices]
options_package = h[:options_package]
h[:options_package].count.times.map do
h[:prices] = prices.shift
h[:options_package] = options_package.shift
h.dup
end
end
p result.flatten
Output
[{:dealer_id=>1, :dealer_name=>"dealership 1", :car_make=>"jeep", :prices=>"30", :options_package=>"A"},
{:dealer_id=>1, :dealer_name=>"dealership 1", :car_make=>"jeep", :prices=>"32", :options_package=>"B"},
{:dealer_id=>1, :dealer_name=>"dealership 1", :car_make=>"jeep", :prices=>"35", :options_package=>"C"},
{:dealer_id=>2, :dealer_name=>"dealership 2", :car_make=>"ford", :prices=>"50", :options_package=>"X"},
{:dealer_id=>2, :dealer_name=>"dealership 2", :car_make=>"ford", :prices=>"55", :options_package=>"Y"},
{:dealer_id=>3, :dealer_name=>"dealership 3", :car_make=>"dodge", :prices=>"70", :options_package=>"A"}]
I have created a bar horizontal chart in chartjs and want to add a vertical scrollbar when more than 5 data, then add scroll in chart automatically. it is showing scroll but i want scroll within the chart, and only need to show 5 records, rest records in scroll.
I have tried so far...
$(document).ready(function(){
let data1 = [{
label: 'Notebook',
backgroundColor: '#0360b8',
data: [399,309,278,239,235, 203,182, 157, 149, 144, 143, 137, 134, 138, 118, 107, 127, 137]
}, {
label: 'Pen',
backgroundColor: '#579ee2',
data: [112, 211, 232, 232, 203,182, 157, 149, 144, 143, 231, 286, 234, 345, 321, 306, 399, 403]
}, {
label: 'Papers',
backgroundColor: '#97c5f5',
data: [399,309,278,239,235, 203,182, 157, 149, 144, 143, 137, 134, 138, 118, 107, 127, 137]
}];
let myChart = new Chart(document.getElementById('divchart'), {
type: 'horizontalBar',
data: {
labels: ["Division 1", "Division 2", "Division 3", "Division 4", "Division 5", "Division 6", "Division 7", "Division 8", "Division 9", "Division 10", "Division 11", "Division 12", "Division 13", "Division 14", "Division 15", "Division 16", "Division 17", "Division 18"],
datasets: data1
},
options: {
responsive: true,
scales: {
xAxes: [{
ticks: {
beginAtZero: true
},
gridLines: {
display:false
}
}],
yAxes: [{
gridLines: {
display:false,
}
}],
},
legend: {
display: true,
position: 'bottom',
},
maintainAspectRatio: false,
}
});
});
.chartheight{height:250px; overflow-y:auto;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.8.0/Chart.min.js"></script>
<div class="chartheight">
<canvas class="" id="divchart"></canvas>
</div><!-- end xol-xs-12 div -->
I have an array of hashes called events:
events = [
{:name => "Event 1", :date => "2019-02-21 08:00:00", :area => "South", :micro_area => "A"},
{:name => "Event 2", :date => "2019-02-21 08:00:00", :area => "South", :micro_area => "A"},
{:name => "Event 3", :date => "2019-02-21 08:00:00", :area => "South", :micro_area => "B"},
{:name => "Event 4", :date => "2019-02-21 08:00:00", :area => "South", :micro_area => "B"},
{:name => "Event 5", :date => "2019-02-21 08:00:00", :area => "North", :micro_area => "A"},
{:name => "Event 6", :date => "2019-02-21 08:00:00", :area => "North", :micro_area => "A"},
{:name => "Event 7", :date => "2019-02-21 08:00:00", :area => "North", :micro_area => "B"},
{:name => "Event 8", :date => "2019-02-21 08:00:00", :area => "North", :micro_area => "B"}
]
I want to know how to group_by first date, then area then micro_area to end up with a single array of hashes for example:
[
{
"2019-02-21 08:00:00": {
"South": {
"A": [
{:name=>"Event 1", :date=>"2019-02-21 08:00:00", :area=>"South", :micro_area=>"A" },
{:name=>"Event 2", :date=>"2019-02-21 08:00:00", :area=>"South", :micro_area=>"A" }
],
"B": [
{:name=>"Event 3", :date=>"2019-02-21 08:00:00", :area=>"South", :micro_area=>"B" },
{:name=>"Event 4", :date=>"2019-02-21 08:00:00", :area=>"South", :micro_area=>"B" }
]
},
"North": {
"A": [
{:name=>"Event 5", :date=>"2019-02-21 08:00:00", :area=>"North", :micro_area=>"A" },
{:name=>"Event 6", :date=>"2019-02-21 08:00:00", :area=>"North", :micro_area=>"A" }
],
"B": [
{:name=>"Event 7", :date=>"2019-02-21 08:00:00", :area=>"North", :micro_area=>"B" },
{:name=>"Event 8", :date=>"2019-02-21 08:00:00", :area=>"North", :micro_area=>"B" }
]
}
}
}
]
Trying events.group_by { |r| [r[:date], r[:area], r[:micro_area]] } doesn't seem too work the way I want it to.
I think following will work for you,
events = [
{ name: 'Event 1', date: '2019-02-21 08:00:00', area: 'South', micro_area: 'A' }
]
events.group_by { |x| x[:date] }.transform_values do |v1|
v1.group_by { |y| y[:area] }.transform_values do |v2|
v2.group_by { |z| z[:micro_area] }
end
end
# {
# "2019-02-21 08:00:00"=>{
# "South"=>{
# "A"=>[
# {:name=>"Event 1", :date=>"2019-02-21 08:00:00", :area=>"South", :micro_area=>"A"}
# ]
# }
# }
# }
Another option is to build the nested structure as you traverse your hash:
events.each_with_object({}) do |event, result|
d, a, m = event.values_at(:date, :area, :micro_area)
result[d] ||= {}
result[d][a] ||= {}
result[d][a][m] ||= []
result[d][a][m] << event
end
Another option is grouping them like you did in the question. Then build the nested structure from the array used as key.
# build an endless nested structure
nested = Hash.new { |hash, key| hash[key] = Hash.new(&hash.default_proc) }
# group by the different criteria and place them in the above nested structure
events.group_by { |event| event.values_at(:date, :area, :micro_area) }
.each { |(*path, last), events| nested.dig(*path)[last] = events }
# optional - reset all default procs
reset_default_proc = ->(hash) { hash.each_value(&reset_default_proc).default = nil if hash.is_a?(Hash) }
reset_default_proc.call(nested)
The above leaves the answer in the nested variable.
References:
Hash::new to create the nested hash.
Hash#default_proc to get the default proc of a hash.
Hash#default= to reset the hash default back to nil.
Hash#dig to traverse the nested structure until the last node.
Hash#[]= to set the last node equal to the grouped events.
Array decomposition and array to argument conversion to capture all but the last node into path and call #dig with the contents of path as arguments.
Here is a recursive solution that will handle arbitrary levels of nesting and arbitrary grouping objects.
def hashify(events, grouping_keys)
return events if grouping_keys.empty?
first_key, *remaining_keys = grouping_keys
events.group_by { |h| h[first_key] }.
transform_values { |a|
hashify(a.map { |h|
h.reject { |k,_| k == first_key } },
remaining_keys) }
end
Before executing this with the sample data from the questions let's add a hash with a different date to events.
events <<
{ :name=>"Event 8", :date=>"2018-12-31 08:00:00",
:area=>"North", :micro_area=>"B" }
grouping_keys = [:date, :area, :micro_area]
hashify(events, grouping_keys)
#=> {"2019-02-21 08:00:00"=>{
# "South"=>{
# "A"=>[{:name=>"Event 1"}, {:name=>"Event 2"}],
# "B"=>[{:name=>"Event 3"}, {:name=>"Event 4"}]
# },
# "North"=>{
# "A"=>[{:name=>"Event 5"}, {:name=>"Event 6"}],
# "B"=>[{:name=>"Event 7"}, {:name=>"Event 8"}]
# }
# },
# "2018-12-31 08:00:00"=>{
# "North"=>{
# "B"=>[{:name=>"Event 8"}]
# }
# }
# }
hashify(events, [:date, :area])
#=> {"2019-02-21 08:00:00"=>{
# "South"=>[
# {:name=>"Event 1", :micro_area=>"A"},
# {:name=>"Event 2", :micro_area=>"A"},
# {:name=>"Event 3", :micro_area=>"B"},
# {:name=>"Event 4", :micro_area=>"B"}
# ],
# "North"=>[
# {:name=>"Event 5", :micro_area=>"A"},
# {:name=>"Event 6", :micro_area=>"A"},
# {:name=>"Event 7", :micro_area=>"B"},
# {:name=>"Event 8", :micro_area=>"B"}
# ]
# },
# "2018-12-31 08:00:00"=>{
# "North"=>[
# {:name=>"Event 8", :micro_area=>"B"}
# ]
# }
# }
See Enumerable#group_by, Hash#transform_values and Hash#reject.
hi everyone hi do have a problemi with a chart that just do not appears.
i think the problem is related with the number of colums that i have to show because with a little number of data is working.
than when i put all the data in the chart it stopped working. i do not think is a limit on the amchart itself.
i post here my code
<script>
var chart = AmCharts.makeChart( "confrontogas", {
"type": "serial",
"theme": "light",
"dataProvider": [ {
"offerta": "Energia Italia 150€",
"costo": 150,
"color": "#0C3B54",
"labelcolor": "#FFFFFF"
},{
"offerta": "",
"costo": 229,
"color": "#CACACA",
"labelcolor": "#FFFFFF"
},{
"offerta": "",
"costo": 418,
"color": "#CACACA",
"labelcolor": "#FFFFFF"
},{
"offerta": "",
"costo": 419,
"color": "#CACACA",
"labelcolor": "#FFFFFF"
},{
"offerta": "",
"costo": 420,
"color": "#CACACA",
"labelcolor": "#FFFFFF"
},{
"offerta": "",
"costo": 425,
"color": "#CACACA",
"labelcolor": "#FFFFFF"
},{
"offerta": "",
"costo": 1,460,
"color": "#CACACA",
"labelcolor": "#FFFFFF"
} ],
"valueAxes": [ {
"gridColor": "#FFFFFF",
"gridAlpha": 0,
"dashLength": 0,
"axisAlpha": 0,
"minimum": 0,
"labelsEnabled": false
} ],
"gridAboveGraphs": true,
"startDuration": 1,
"graphs": [ {
"balloonText": "<b>[[value]]</b> €",
"fillAlphas": 0.8,
"lineAlpha": 0.2,
"type": "column",
"colorField": "color",
"valueField": "costo"
} ],
"chartCursor": {
"categoryBalloonEnabled": false,
"cursorAlpha": 0,
"zoomable": false
},
"categoryField": "offerta",
"categoryAxis": {
"gridPosition": "start",
"gridAlpha": 0,
"tickPosition": "start",
"tickLength": 0,
"labelRotation": 90,
"autoGridCount": false,
"gridCount": 548,
"equalSpacing": true,
"inside": true,
"labelFrequency": 1,
"labelColorField": "labelcolor",
"forceShowField": "true"
},
"export": {
"enabled": true
}
} );
as you can see
"gridCount": 548,
i posted less data just to keep it easy
Going by your last datapoint in the fiddle, your valueField costo has numeric values with commas, which isn't valid JavaScript:
{
"offerta": "",
"costo": 1,460, //should be 1460 or 1.460 depending on what ',' means in your region
"color": "#CACACA",
"labelcolor": "#FFFFFF"
}
Make sure your numeric data only contains numbers or dots (.) for decimals. AmCharts will automatically use commas for thouand separators and dots for decimals to format your values, but your numeric values must be valid in JavaScript first. If you need to change the separators in the output once your values are valid, use thousandsSeparator and decimalSeparator
I've had about 3 goes at trying to learn the jqGrid. Every time has failed. So this time I tried to start with the basics. All I wanted to do was replicate the json data example at http://trirand.com/blog/jqgrid/jqgrid.html .
My best efforts have resulted in a table with rows and columns by every cell has a non-breaking space in it.
With my code, I only departed from the code in that example in the slightest way to account for the fact that I wasn't getting the data from the same data service. But I was getting the exact same data as I was able to extract the json using fiddler.
The code is:
function getData() {
var obj =
{
"page": "1",
"total": 2,
"records": "13",
"rows": [{
"id": "13",
"cell": ["13",
"2007-10-06",
"Client 3",
"1000.00",
"0.00",
"1000.00",
null]
},
{
"id": "12",
"cell": ["12",
"2007-10-06",
"Client 2",
"700.00",
"140.00",
"840.00",
null]
},
{
"id": "11",
"cell": ["11",
"2007-10-06",
"Client 1",
"600.00",
"120.00",
"720.00",
null]
},
{
"id": "10",
"cell": ["10",
"2007-10-06",
"Client 2",
"100.00",
"20.00",
"120.00",
null]
},
{
"id": "9",
"cell": ["9",
"2007-10-06",
"Client 1",
"200.00",
"40.00",
"240.00",
null]
},
{
"id": "8",
"cell": ["8",
"2007-10-06",
"Client 3",
"200.00",
"0.00",
"200.00",
null]
},
{
"id": "7",
"cell": ["7",
"2007-10-05",
"Client 2",
"120.00",
"12.00",
"134.00",
null]
},
{
"id": "6",
"cell": ["6",
"2007-10-05",
"Client 1",
"50.00",
"10.00",
"60.00",
""]
},
{
"id": "5",
"cell": ["5",
"2007-10-05",
"Client 3",
"100.00",
"0.00",
"100.00",
"no tax at all"]
},
{
"id": "4",
"cell": ["4",
"2007-10-04",
"Client 3",
"150.00",
"0.00",
"150.00",
"no tax"]
}],
"userdata": {
"amount": 3220,
"tax": 342,
"total": 3564,
"name": "Totals:"
}
}
return obj;
}
$(function () {
$("#list2").jqGrid({
data: getData()['rows'],
datatype: "local",
colNames: ['Inv No', 'Date', 'Client', 'Amount', 'Tax', 'Total', 'Notes'],
colModel: [
{ name: 'id', index: 'id', width: 55 },
{ name: 'invdate', index: 'invdate', width: 90 },
{ name: 'name', index: 'name asc, invdate', width: 100 },
{ name: 'amount', index: 'amount', width: 80, align: "right" },
{ name: 'tax', index: 'tax', width: 80, align: "right" },
{ name: 'total', index: 'total', width: 80, align: "right" },
{ name: 'note', index: 'note', width: 150, sortable: false }
],
rowNum: 10,
rowList: [10, 20, 30],
pager: '#pager2',
sortname: 'id',
viewrecords: true,
sortorder: "desc",
caption: "JSON Example"
});
$("#list2").jqGrid('navGrid', '#pager2', { edit: false, add: false, del: false });
});
Can anyone please help me break my really bad run with this popular javascript library?
I don't find anything wrong with your grid. There is something wrong with your data. I hope your data need to be checked. Also if you are using local as your datatype your data should like below,
var mydata = [
{ id: "11", invdate: "2007-10-06", name: "Client 1", amount: "600.00"
, tax:"120.00", total:"720.00", note: null },
{ id: "12", invdate: "2007-10-06", name: "Client 2", amount: "700.00",
tax:"140.00", total:"840.00", note: null },
{ id: "13", invdate: "2007-10-06", name: "Client 3", amount: "1000.00",
tax:"0.00", total:"1000.00", note: null }
];
This Demo will give you the good start with jQgrid. I have used your grid definition. Hope this helps.