Javascript: how can I populate an array of property values dynamically? - javascript

I have an array of values, which I want to insert into a property of an object, but I'm not sure how. Here's my object. The property is called "values" (located at the very bottom), and as you can see, I'm trying to insert a dynamic list of data (called "result") into it:
var myConfig = {
globals: {
fontFamily: "Roboto"
},
type: "bar",
backgroundColor: "#f4f2f2",
plotarea: {
backgroundColor: "#fff"
},
scaleX: {
lineColor: "#7d7d7d",
tick: {
lineColor: "#7d7d7d"
},
guide: {
visible: false
},
values: [result[0]["Heading"], result[1]["Heading"], result[2]["Heading"], ...],
}};
Is there any way I can set this up to dynamically place this result["Heading"] data into my "values" property?
Thanks

So, assuming results is an array of objects that have the Heading property, you can get an array of only those, using the map function, like this:
values: result.map(function(item){ return item.Heading; })
map is a new-ish function, defined in ECMAScript 5.1, but all major browsers support it. Basically, for every item in the array, it will execute the provided selector function, and return the result. So, you're starting with an array of objects having a Heading property, and ending up with an array of the Heading property values themselves.

Make another function to do that.
It's an Array.
You should traverse it at least once.
function getHeading( arr ) {
var aa = [];
for( var i = 0, size = arr.length ; i < size ; i++ ) {
aa.push( arr[i].Heading );
}
return aa;
}
var myConfig = {
globals: {
fontFamily: "Roboto"
},
type: "bar",
backgroundColor: "#f4f2f2",
plotarea: {
backgroundColor: "#fff"
},
scaleX: {
lineColor: "#7d7d7d",
tick: {
lineColor: "#7d7d7d"
},
guide: {
visible: false
},
values: getHeading( result ),
}};

Related

Initialize several charts in a loop

On my page, I want to show multiple charts that are loaded via ajax. So I get html that is something like this:
<h4>Chart 1</h4>
<canvas data-type="bar" data-labels='["Label 1", "Label 2"]' data-data='[10,20]'></canvas>
<h4>Chart 2</h4>
<canvas data-type="pie" data-labels='["Label 3", "Label 4"]' data-data='[30,40]'></canvas>
As you can see, the charts can be of different types and I have an object holding all the configuration of charts for each type
const chartConfigs = {
bar: {
type: 'bar',
data: {
labels: [],
datasets: [{
data: [],
pointRadius: 2,
backgroundColor: '',
maxBarThickness: 50
}]
},
options: {
legend: {
display: false
},
scales: barScalesOptions
}
},
pie: {
type: 'pie',
data: {
labels: [],
datasets: [{
data: [],
backgroundColor: ['#84c267', '#c9556a'],
borderWidth: 0
}],
},
options: {
legend: {
labels: {
fontColor: '#CCC',
}
},
aspectRatio: 1,
responsive: true
}
}
}
And I loop through all the canvases to initialize them.
container.querySelectorAll('canvas').forEach(canv => {
const chartOptions = chartConfigs[canv.dataset.type];
chartOptions.data.datasets[0].data = JSON.parse(canv.dataset.data);
if(canv.dataset.labels != undefined)
chartOptions.data.labels = JSON.parse(canv.dataset.labels);
console.log(JSON.stringify(chartOptions));
new Chart(canv, chartOptions);
});
But the problem is that all the charts are rendered the same - Labels and Data. I'm assuming its because chartOptions is a copy by reference. Its a pretty difficult task to do a deep copy as this is a nested object and I also need functions in them. But even if I somehow did this task, it would be a memory management nightmare as there are many charts on the page.
If you have done something like this before, please share a better way of doing this.
A quick solution is to clone the needed part of the object, with the handy function(s) JSON.parse and JSON.stringify, it makes sure it breaks all references (as mentioned on mdn).
container.querySelectorAll('canvas').forEach(canv => {
const chartOptions = JSON.parse(JSON.stringify(chartConfigs[canv.dataset.type]));
chartOptions.data.datasets[0].data = JSON.parse(canv.dataset.data);
if(canv.dataset.labels != undefined){
chartOptions.data.labels = JSON.parse(canv.dataset.labels);
console.log(JSON.stringify(chartOptions));
new Chart(canv, chartOptions);
});
Since I can't see any functions in the object chartOptions the serializing and deserializing should be no problem?
Update, for object with functions (for your specific case):
I see two easy options,
just extract the functions from the base object and just pass the current object
Or if you don't want to alter the chartConfigs object, just use the prototype function, call (link to documentation). With other words change the function calls to:
// clone
const chartOptions = JSON.parse(JSON.stringify(chartConfigs[canv.dataset.type]));
...
let id = 1;
let value = 100;
// call the function
chartConfigs[chartOptions.typ].testFunction.call(chartOptions, id, value);
...
(if testFunction would be a function, with 2 parameters ( id, value))
Is not very sexy, but is a fast solution, that will need little code modifications.

Chart.js show negative value in the top half

I am creating a chart.js which has both positive and negative values
but how to make all values be on the top half
(ignore the if it's positive or negative when drawing but keep the label)
var tax_dash = new Chart(ctx_tax_dash, {
type: "bar",
data: {
labels: lable_set,
datasets: [{
label: "Tax in",
data: total_tax_in_t_data__year,
backgroundColor: '#0fd96d',
// borderColor: sales_t_data,
borderWidth: 1,
},
{
label: "Tax out",
data: total_tax_out_t_data__year,
backgroundColor: '#0f81d9',
// borderColor: sales_t_data,
borderWidth: 1,
},
{
label: "Net VAT",
data: total_tax_in_out_t_data__year,
backgroundColor: '#d96a0f',
// borderColor: sales_t_data,
borderWidth: 1,
},
],
},
options: {
legend: {
display: true,
}
},
});
EDIT
what I am trying to do
possible solution: is (multi-axis) dual y axis.multi-axis example
~ issue: how to flip the axis so that the -100 be to the top and 0 be on the bottom
~ issue: how to split the data set base on the (sign)
OR
possible solution 2 : make all variable positive
#Gkiokan> solution: use the popup modifier to the showing values with negative
~ ++ issue: how the function will know if the value is negative
~ issue: the user needs to know that this value is negative in the label
Solution 2
I did it this morning user what Math.abs from #Lawrence comment and "popup modifier" from #Gkiokan comment as well as this jsfiddle
Thank you very much for the help. Chatting with smarter people rubs off on you :)
total_tax_in_t_data_portal_month_year = [Math.abs(12),Math.abs(-234),Math.abs(234)];
total_tax_in_t_data_portal_month_year_sign = [12,-234,234];
var tax_dash_portal = new Chart(ctx_tax_dash_portal, {
type: "bar",
data: {
labels: lable_set,
datasets: [
{
label: "VAT In",
data: total_tax_in_t_data_portal_month_year,
sign: total_tax_in_t_data_portal_month_year_sign,
backgroundColor: "#0fd96d",
// borderColor: sales_t_data,
borderWidth: 1,
},
{
label: "VAT Out",
data: total_tax_out_t_data_portal_month_year,
sign: total_tax_out_t_data_portal_month_year_sign,
backgroundColor: "#0f81d9",
// borderColor: sales_t_data,
borderWidth: 1,
},
{
label: "Net VAT",
data: total_tax_t_data_portal_month_year,
sign: total_tax_t_data_portal_month_year_sign,
backgroundColor: "#d96a0f",
// borderColor: sales_t_data,
borderWidth: 1,
},
],
},
options: {
legend: {
display: true,
},
tooltips: {
enabled: true,
callbacks: {
label: function (tooltipItem, data) {
var label = data.labels[tooltipItem.index];
var val = data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];
var sign = data.datasets[tooltipItem.datasetIndex].sign[tooltipItem.index];
if(sign < 0){
return label + ": -" + val;
}else{
return label + ': ' + val;
}
}
}
}
},
});
total_tax_in_t_data_portal_month_year is an example as the values come from a function
Math.abs is used to remove the negative sign
then I added sign to the datasets for essay access
tooltips callbacks is called on every variable so I added the if statement there
to add - if sign < 0 and do nothing if not
In my opinion you can have a data Set to save the orginal Data and the modified
Data and then use the values as you need. You can not trust the value characters. My solution will work kind cross over as you have control over both values.
I've made a jsfiddle for you which demonstrates the orginal Data vs modified Data usage. Please click first on Modify Data which will then map the data, so you can see the work in progress. In your case you would modify the data before calling the charts.
Actually you will need just a couple of methods as followed:
updateItemValues to modify the negative values and put it to the other object
tooltipCallback callback for the tooltip to use the mapped orginal value
let data = {
modified: false,
orginalData : {
'tax_in' : [10, 20, -30, -40, -100, -50],
'tax_out' : [-10, 10, 20, 10, -40, -70],
'net_vat' : [-50, -9, -40, -20, -10, -90],
},
modifiedData : {
// this modified data will be calculated before putting it in the charts
// for demo purpose we will just copy the values for now.
'tax_in' : [10, 20, -30, -40, -100, -50],
'tax_out' : [-10, 10, 20, 10, -40, -70],
'net_vat' : [-50, -9, -40, -20, -10, -90],
},
updateModifiedData(){
// loop though the orginal Data
Object.keys(this.orginalData).forEach( (item, indexx) => {
console.log('modifying item chart data for: ', item)
this.updateItemValues(item)
})
this.modified = true
document.getElementById('status').innerHTML = 'modified'
},
updateItemValues(dataKey){
let temp = []
this.orginalData[dataKey].forEach( (value, index) => {
console.log('- validating ', dataKey, 'index: ', index, '; value: ', value)
// if we have a negative value, just multiply by -1 so get it positive
if(value <= 0){
value = value * -1
}
// add to the temporary variable
temp.push(value)
})
// put the modified data to some place to have it saved
this.modifiedData[dataKey] = temp
console.log('-- final data modded ', temp)
},
tooltipCallback(tooltipItem, chartData) {
// find reference values
let index = tooltipItem.index
let dataIndex = tooltipItem.datasetIndex
// find the name of dataset
let key = chartData.datasets[dataIndex].name
// validate or whatever with the orginal value
let orginalValueOfItem = data.orginalData[key][index]
let modifiedValueOfItem = data.modifiedData[key][index]
// Modify your final tooltip here
return 'Orginal Value: ' + orginalValueOfItem + ' ; Modified Value: ' + modifiedValueOfItem
}
}
How can you use this solution?
Pretty simple.
Copy that data Object in your code.
Fill the data.orginalData value with your orginal charts data based on key
example data.orginalData.tax_in = [...]
In your datasets add name property with the corresponding key
Extend the Charts options with the tooltipCallback
Call data.updateModifiedData() to get the modified data
Checkout the jsFiddle for reference if you need to.
Have fun.

How to wrap this behavior in a plugin?

Currently I have a request to have a Bullet Chart with two targets (min and max).
To do it I am simply using a normal Bullet Chart with a Scatter series to draw the other target. I would like to wrap this behavior inside the bullet chart, so it would have something like the following options:
series: [{
data: [{
y: 275,
target: 250,
minTarget: 100
}]
},
And then, on the wrap, I would get this minTarget and make a scatter plot automatically. How can I do it?
Here's the fiddle I have so far: http://jsfiddle.net/gwkxd02p/
I do not think that render is a good method to add another series - anyway, you can try to do it like this:
Highcharts.wrap(Highcharts.seriesTypes.bullet.prototype, 'render', function(p) {
if (!this.hasRendered) {
const scatterData = this.points
.map(({ x, y, options }) => ({
x,
y: options.minTarget !== undefined ? options.minTarget : null
}))
if (scatterData.length) {
const scatter = this.chart.addSeries({
type: 'scatter',
data: scatterData,
marker: {
symbol: 'line',
lineWidth: 3,
radius: 8,
lineColor: '#000'
}
}, false)
scatter.translate()
scatter.render()
}
}
p.call(this)
})
And data for bullet:
series: [{
data: [{
y: 275,
target: 250,
minTarget: 100
}, {
y: 100,
target: 50
}, {
y: 500,
target: 600,
minTarget: 20
}]
live example: http://jsfiddle.net/n4p0ezzw/
I think that the better place is bullet's init method but in that method the points do not exist yet - so you would have to match the x values (if it is needed) on your own.
My suggestion is - do not wrap Highcharts if you don't have to. A better (simpler, safer, cleaner, easier to debug, it does not change Highcharts internal code) practice would be to wrap the Highcharts constructor in a function and parse the options inside it and then call the chart constructor with new options, like this:
function customBullet(container, options) {
const newOptions = {} // parse options, check for minTarget, etc. and create new options
return Highcharts.chart(container, newOptions)
}

Where to add and register the custom attributes in flot-pie chart js?

Although this question has answer at: Adding custom attributes to flot data
but I have tried every possible way but my custom attributes are not showing on click event.
So far I tried this:
html:
<div id="audit_status" class="chart"></div>
JS:
var audit_status = [
{label: "Pending", data: 2, location_value="Majiwada,Pune"},
{ label: "Ongoing", data: 1 location_value="Mumbai"},
];
var options = {
series: {
pie: {
show: true,
label: {
show: true,
radius: 120,
formatter: function (label, series) {
return '<div style="border:1px solid grey;font-size:8pt;text-align:center;padding:5px;color:white;background-color: #90bdce;">' +
label + ' : ' +
series.data[0][1] +
' ('+Math.round(series.percent)+' %)</div>';
},
background: {
opacity: 0.8,
color: '#000'
}
}
}
},
legend: {
show: true
},
grid: {
hoverable: true,
clickable: true
}
};
$("#audit_status").bind("plotclick", function(event, pos, obj) {
console.log(obj);
//alert(obj.series.value);
if (obj) {
alert(obj.series.location_value);
}
});
$(document).ready(function () {
$.plot($("#audit_status"), audit_status, options);
});
The problem is: whenever I click the pie segment I want to alert "location_value"
but i am getting [Object Object]
I see two issues with the code as it is now. First, the audit_status JSON object isn't quite defined right. The location_value properties need to use colons, not equal signs:
var audit_status = [
{ label: "Pending", data: 2, location_value: "Majiwada,Pune" },
{ label: "Ongoing", data: 1, location_value: "Mumbai" }
];
Second, in your plotclick function, the extra properties defined in your data object don't make it over to the series object passed into the callback. You need to reference the original data object, using the obj.seriesIndex provided to the callback. This JSFiddle provides an example of the code below.
var data = [{
label: "Yes",
data: 50,
location_value: 'Majiwada,Pune'
}, {
label: "No",
data: 150,
location_value: 'Mumbai'
}];
// plot initialization code here
$("#pie").bind("plotclick", function(event, pos, obj) {
if (obj) {
// use the obj.seriesIndex to grab the original data object,
// then you can use any property you defined on that object
alert(data[obj.seriesIndex].location_value);
}
});

Turn jQuery array of objects into a new array

Currently my Rails controller is returning an array of objects:
var _data = [];
$.getJSON(my_url, function(data) {
$.map(data, function(v) {
_data.push([v.occurrences, v.period])
});
});
console.log(_data) => []
Which when expanded looks like this:
Array[4]
0:Object
1:Object
2:Object
3:Object
And individual objects when expanded look like this:
0:Object
occurrences:1
period:1488499200000
I'm having trouble mapping the initial array of objects in such a way that my final result will be an array of arrays that is comprised of each objects' occurrences value and period value.
The end result should like like:
[[1488499200000, 1],[.., ..],[.., ..],[.., ..]]
So that I can use each array in a chart as an x and y axis point.
I've tried using .map, .each, (for i in ..), etc. with no luck.
EDIT:
This is my chart data:
var line_data2 = {
data: _data,
color: "#00c0ef"
};
$.plot("#myGraph", [line_data2], {
grid: {
hoverable: true,
borderColor: "#f3f3f3",
borderWidth: 1,
tickColor: "#f3f3f3"
},
series: {
shadowSize: 0,
lines: {
show: true
},
points: {
show: true
}
},
lines: {
fill: false,
color: ["#3c8dbc", "#f56954"]
},
yaxis: {
show: true,
},
xaxis: {
show: true,
mode: "time",
timeformat: "%m/%d/%Y"
}
});
There is likely a more elegant solution than this, but this will get you the shaped array I think you are asking for.
let _data = [
{occurrences: 1, period: 200},
{occurrences: 3, period: 300},
{occurrences: 6, period: 400}
];
let newData = _data.map((x)=>{
let arr = [];
arr.push(x.occurrences);
arr.push(x.period);
return arr;
});
console.log(newData);
You could take it a step further and simply loop through the object rather than hard coding the names.
let newData = _data.map((x)=>{
let arr = [];
for(let i in x){
arr.push(x[i]);
}
return arr;
});

Categories

Resources