What's the easiest way to get my data to show in HighCharts/HighStock? I'm populating the javascript arrays from another source, but I don't know how to assign my javascript array data to the series. I've looked at other examples and posts, but I just don't get it. Any helpful pointers or links to tutorials would be appreciated.
arClosingDates[0] = '2/1/2013'
arClosingDates[1] = '2/2/2013'
arClosingDates[2] = '2/3/2013'
arClosingDates[3] = '2/4/2013'
arClosingDates[4] = '2/5/2013'
arClosingPrices[0] = 3.23
arClosingPrices[1] = 3.28
arClosingPrices[2] = 3.56
arClosingPrices[3] = 3.90
arClosingPrices[4] = 3.23
$('#Chart1').highcharts('StockChart', {
rangeSelector : {
selected : 1
},
title : {
text : 'Test Stock Price'
},
series : [{
name : 'Test',
data : [arClosingDates[],arClosingPrices[]],
tooltip: {
valueDecimals: 2
}
}]
});
You are formatting the data array incorrectly. Try this instead:
var arClosingData = [
[Date.UTC(2013, 1, 1), 3.23],
[Date.UTC(2013, 1, 2), 3.28],
[Date.UTC(2013, 1, 3), 3.56],
[Date.UTC(2013, 1, 4), 3.90],
[Date.UTC(2013, 1, 5), 3.23]
];
...
series: [{
name: 'Test',
data: arClosingData,
tooltip: {
valueDecimals: 2
}
}]
jsFiddle example using your data
EDIT: I forgot that the month parameter in DATE.UTC(year, month, day) expected 0-11 instead of 1-12.
Related
I'm trying to render a chart from a big amount of data (about 1200 entries). The chart takes in an array of objects with text and value properties like the one shown in FIG1. The data that I have coming in though is an object with key value pairs of string and number like the one shown if FIG2.
How could I transform the data from FIG2 format to FIG1 format so that I can use it in the Chart? Any help is much appreciated.
//FIG1
let words = [
{
text: "told",
value: 64,
},
{
text: "great",
value: 11,
},
{
text: "thought",
value: 16,
},
{
text: "clean",
value: 17,
},
];
//FIG2
const data = {
"give it a try!": 97,
"go for 6 months and get 1 month free": 8,
"go for 12 months and get 2 month free": 2,
"go for 12 months and get 2 months free": 6,
"go to url": 1,
};
...
return (
<div>
<h1>Chart</h1>
<ReactWordcloud words={words} />
</div>
);
Easy-Peasy
const transformed = Object.entries(data).map(( [key, value] )=>{
return { text:key , value: value }
})
I've got an example array that I'm trying to reduce by the counts of the occurrence of a key (sentiment in this example):
const sentimentLog = [
{
id: 1,
createdOn: new Date('2020-02-13'),
sentiment: 1
},
{
id: 2,
createdOn: new Date('2020-02-12'),
sentiment: 1
},
{
id: 3,
createdOn: new Date('2020-02-12'),
sentiment: 2
},
{
id: 4,
createdOn: new Date('2020-02-11'),
sentiment: 3
},
{
id: 5,
createdOn: new Date('2020-02-11'),
sentiment: 2
},
{
id: 6,
createdOn: new Date('2020-02-10'),
sentiment: 1
},
{
id: 7,
createdOn: new Date('2020-02-10'),
sentiment: 2
},
{
id: 8,
createdOn: new Date('2020-02-09'),
sentiment: 1
}
]
I'm using:
const sentimentGrouped = (sentiments) => {
return sentiments.reduce((hash, { sentiment }) => {
hash[sentiment] = (hash[sentiment] || 0) + 1
return hash
}, [])
}
And it's nearly there. What I can't figure out is how to replace undefined when there's no sentiment scores of 0 (which is a possibility).
console.log('sentimentGrouped', sentimentGrouped(sentimentLog))
The above produces:
"sentimentGrouped" [undefined, 4, 3, 1]
Whereas I'd like:
"sentimentGrouped" [0, 4, 3, 1]
What am I missing?
Thanks in advance.
Edit: I'll elaborate a bit further, there's 4 scores that will be returned (0 to 3). The data returned will be based on a date range. So there may be instances where there'll be no 1s returned, similarly no 3s returned by a different date range.
The issue is that if you never touch an element of the array, then it stays as a hole in the array, which means it's treated as undefined. Since you know the length of the array i would just prefill the array with zeros. Any sentiment score that does occur will be incremented. Any one that doesn't will stay with its initial value.
return sentiments.reduce((hash, { sentiment }) => {
hash[sentiment] = hash[sentiment] + 1
return hash
}, [0, 0, 0, 0])
How would one organize a dynamic matrix for best fit? So, let say you are attempting to always display the best fit for a display, and need to organize all cells so that there are no gaps between each item. Each item can either have a size from 1 - 12, and the max width of each row is 12. Using the example dataset, how can will dynamic sort and generate a new array that best fits the display?
let matrixExample = [{
size: 10,
type: 'card'
}, {
size: 4,
type: 'card'
}, {
size: 2,
type: 'card'
}, {
size: 11,
type: 'card'
}, {
size: 6,
type: 'card'
}];
let endingResult = [
[{
size: 10,
type: 'card'
}, {
size: 2,
type: 'card'
}],
[{
size: 4,
type: 'card'
}, {
size: 6,
type: 'card'
}],
[{
size: 11,
type: 'card'
}]
];
The user purpose of this?
When generating dynamic data to a UI, and the UI needs to optimize for component space.
This appears to be an example of the bin packing problem.
This isn't a particularly easy problem to solve and more precise fits are likely to be more complicated.
Below is a greedy algorithm that should solve your problem with a rough estimate. It's possible to get better matches but, as you do, you make things more complicated and computationally expensive.
This solution happens to be recursive and somewhat functional, but that's only my preference; it's probably possible to make a neater and less expensive algorithm if you're not interested in making the code functional or recursive.
const matrixExample = [{
size: 10,
type: 'card'
}, {
size: 4,
type: 'card'
}, {
size: 2,
type: 'card'
}, {
size: 11,
type: 'card'
}, {
size: 6,
type: 'card'
}];
const sumCardList = cardList => cardList.reduce((prev, curr) => prev + curr.size, 0);
const packNextToBin = (cards, bins, max) => {
if (cards.length === 0) {
// there are no more cards to pack, use bins as is
return bins;
}
// get the next card to pack into the bins
const cardToPack = cards[0];
// get the indices of bins which can still be filled
const availableBinIndices = bins
.map((bin, i) => ({sum: sumCardList(bin), index: i}))
.filter(binData => binData.sum + cardToPack.size < max)
.map(binData => binData.index);
// if there are no more bins which can fit this card, makea new bin
if (availableBinIndices.length === 0) {
const updatedBins = [
...bins,
[
cardToPack
]
];
return packNextToBin(
cards.slice(1),
updatedBins,
max
);
}
// get the first available bin which can accept the card
const binToPack = availableBinIndices[0];
// get a version of the matched bin with the new card added
const binWithInsertion = [
...bins[binToPack],
cardToPack,
];
// get the bins with the updated bin updated
const updatedBins = bins
.map((bin, i) => i === binToPack ?
binWithInsertion :
bin
);
// pack the next card into the bins
return packNextToBin(
cards.slice(1),
updatedBins,
max
);
}
const results = packNextToBin(matrixExample, [[]], 12)
console.dir(results)
I have five articles with these initial properties:
const articles = [
{ id: 1, views: 92, likes: 0, shares: 2, trendingValue: ? },
{ id: 2, views: 14, likes: 2, shares: 1, trendingValue: ? },
{ id: 3, views: 39, likes: 3, shares: 1, trendingValue: ? },
{ id: 4, views: 87, likes: 0, shares: 1, trendingValue: ? },
{ id: 5, views: 8, likes: 1, shares: 0, trendingValue: ? }
];
I also have a global stats object that should be automatically updated once an article gets new views, likes or shares (or once a week):
const stats = {
totalArticles: 5,
totalViews: 240,
totalLikes: 6,
totalShares: 5,
trendingCriteria: 0
};
So far, I believe there is some sort of formula that can be done with articles' trendingValue and stats' trendingCriteria. Basically the articles that are "trending" need to have an equal or higher number than the criteria. Meaning that whenever an article gets a new view, share or like, trendingValue has to be updated in regards to it's percentage of views, likes and shares, by the global stats' counterparts.
An example (which doesn't exactly work) is:
A user views article 1.
This formula runs for the article to create its trendingValue:
const article = articles[0]; // Article with id 1
article.views++; // Increment the views count
stats.totalViews++ // Increment the total views count
let percentSum = (
(article.views / stats.totalViews) + // = 0.3833
(article.likes / stats.totalLikes) + // = 0
(article.shares / stats.totalShares) // = 0.4
); // = 0.7833
// The trendingValue needs to be a higher value of trendingCriteria
// before refreshing trendingCriteria.
article.trendingValue = (stats.trendingCriteria +
(percentSum / stats.trendingCriteria)
);
Next, trendingCriteria should be refreshed in regards to the updated article. The underlying logic is; if the new trendingCriteria is higher than the article's trendingValue, the article should no longer be "trending".
The third step is where I'm stuck. How do I create this value? Can this value be update for every single new view, like and share? Or do I have to update the value once a week or so?
Update
Thanks for all responses. Unfortunately I could not make any use of them since I'm yet confused what to do with the proposed solutions.
Anyhow, I tried another solution that makes use of an epoch timestamp and the average views, likes and shares. Not sure if it works in practice, so if anyone can confirm I'd be grateful.
function refreshArticleAtIndex(index, props) {
const data = articles[index];
// Increment props
if(props.view) { data.views++; stats.views++; }
else if(props.like) { data.likes++; stats.likes++; }
else if(props.share) { data.shares++; stats.shares++; }
// Refresh trendingRate
data.trendingRate = (() => {
const calcViews = data.views / stats.views;
const calcLikes = data.likes / stats.likes;
const calcShares = data.shares / stats.shares;
let value = Date.now() * (
(isFinite(calcViews) ? calcViews : 0) +
(isFinite(calcLikes) ? calcLikes : 0) +
(isFinite(calcShares) ? calcShares : 0)
);
return Math.round(value);
})();
}
function logArticles() {
const arr = articles.map(article => article);
arr.sort((a, b) => a.trendingRate > b.trendingRate ? -1 : 1);
arr.forEach(a => console.log(a.id +" |", a.trendingRate));
console.log("----------");
}
const stats = { views: 239, likes: 6, shares: 5 };
const articles = [
{ id: 1, views: 91, likes: 0, shares: 2, trendingRate: 0 },
{ id: 2, views: 14, likes: 2, shares: 1, trendingRate: 0 },
{ id: 3, views: 39, likes: 3, shares: 1, trendingRate: 0 },
{ id: 4, views: 87, likes: 0, shares: 1, trendingRate: 0 },
{ id: 5, views: 8, likes: 1, shares: 0, trendingRate: 0 }
];
console.log("ID | trendingRate");
// ================================================
// Add 1 view to article 1
refreshArticleAtIndex(0, { view: true });
// Add nothing to below articles, but refresh their trendingRate
refreshArticleAtIndex(1, {});
refreshArticleAtIndex(2, {});
refreshArticleAtIndex(3, {});
refreshArticleAtIndex(4, {});
logArticles();
// Add 1 like to article 1
refreshArticleAtIndex(0, { like: true });
logArticles();
The first log will show the correct order based on a combination of views, likes and shares for each article. Next, article 1 gets a like, which bumps it to the most trending article.
Question is if this is a working solution?
Trending probably should mean something like "top n% of activity" or "top n articles by activity".
Towards this end, you could simply save the trendingValue to an array like: { article: articleId, trendingValue: trendingValue}
Then when you go to find the most trending articles you would:
let cnt = trendingValues.length * myLimit
let i = 0
let trending = trendingValues.sort((a,b) => a.trendingValue > b.trendingValue).filter(() => i++ < cnt)
To use your language, trendingCriteria could be set to some minimum based on the set of articles. From there it could be fine tuned to ensure a certain minimum activity (as the average article age starts to grow).
I am trying to make my categories span across multiple data points in Highcharts. Here is an example fidde: http://jsfiddle.net/pn9qvz7v/
$(function () {
$('#container').highcharts({
xAxis: {
categories: ['Jan', 'Feb', 'Mar']
},
series: [{
data: [1, 2, 3, 4, 5, 6]
}]
});
});
In my example, I'd like for the "Jan" category to span across the 1 and 2 data points, the "Feb" category to span across the 3 and 4 data points, and the "Mar" category to span across the 5 and 6 data points. Is there an easy way to do this?
Thanks.
You can manipulate the x values of your data to achieve this.
Instead of:
data: [1, 2, 3, 4, 5, 6]
Supply x/y pairs. The x value is the category index, and in this case, you need to provide one x value below the category index, and one above:
data: [
[-0.25,1],
[0.25,2],
[0.75,3],
[1.25,4],
[1.75,5],
[2.25,6]
]
Updated fiddle:
http://jsfiddle.net/pn9qvz7v/1/
Though, if you are using dates, why not just use a datetime x axis type, and provide the actual dates?
http://api.highcharts.com/highcharts#xAxis.type