How can I sort, insert in between and update full dataset in chartjs? - chart.js

I have a few issues with chartjs which simple update method won't solve.
I wonder if there is an option to:
painlessly sort the datasets;
insert some data in between two points;
reload the whole chart without replacing the canvas with a
completely new chart?

There is no option built in, but it is pretty easy to write your own using the addData, removeData methods that Chart.js provides.
var MyBarChartMethods = {
// sort a dataset
sort: function (chart, datasetIndex) {
var data = []
chart.datasets.forEach(function (dataset, i) {
dataset.bars.forEach(function (bar, j) {
if (i === 0) {
data.push({
label: chart.scale.xLabels[j],
values: [bar.value]
})
} else
data[j].values.push(bar.value)
});
})
data.sort(function (a, b) {
if (a.values[datasetIndex] > b.values[datasetIndex])
return -1;
else if (a.values[datasetIndex] < b.values[datasetIndex])
return 1;
else
return 0;
})
chart.datasets.forEach(function (dataset, i) {
dataset.bars.forEach(function (bar, j) {
if (i === 0)
chart.scale.xLabels[j] = data[j].label;
bar.label = data[j].label;
bar.value = data[j].values[i];
})
});
chart.update();
},
// reload data
reload: function (chart, datasetIndex, labels, values) {
var diff = chart.datasets[datasetIndex].bars.length - values.length;
if (diff < 0) {
for (var i = 0; i < -diff; i++)
chart.addData([0], "");
} else if (diff > 0) {
for (var i = 0; i < diff; i++)
chart.removeData();
}
chart.datasets[datasetIndex].bars.forEach(function (bar, i) {
chart.scale.xLabels[i] = labels[i];
bar.value = values[i];
})
chart.update();
}
}
which you call like so (where myBarChart is your chart)
// sort
MyBarChartMethods.sort(myBarChart, 0)
// reload - same number of values
MyBarChartMethods.reload(myBarChart, 0, ["J", "F", "M", "A", "M", "J", "J"], [1, 2, 3, 4, 5, 6, 7])
// reload - more values
MyBarChartMethods.reload(myBarChart, 0, ["J", "F", "M", "A", "M", "J", "J", "A"], [1, 2, 3, 4, 5, 6, 7, 8])
// reload - less values
MyBarChartMethods.reload(myBarChart, 0, ["J", "F", "M", "A", "M"], [1, 2, 3, 4, 5])
Inserting 2 points is special case of reload, so you can use the same function (or write your own based on that easily)
Fiddle - http://jsfiddle.net/Lkdxxkfa/

I came to stackoverflow having the same question so what I have done is created a plugin that allows you to add a sort function to ChartJS
https://github.com/scotthsieh0503/chartjs-plugin-sort
it also allows you to pass in a 'pre-sorted' array as a reference for sorting

Related

How to set tickCount of labels in BarSeries xAxis when appended in Qt QML

I have a BarChart which gets filled by a Button-onClick with Values from a C++ function.
Everything is fine, but there are too much values for the width of my panel. The data contains time-series data in this format:
Date
Value from 0-1
2021-01-01
0.55
2021-01-02
0.35
2021-01-03
0.77
2021-01-04
0.97
When there is to much data, the labels on the x-axis do not get displayed accordingly. I want to show only 3-4 labels.
In a LineSeries, which I used before, I could easily control the amount of labels by defining tickCount in DateTimeAxis.
How to do it in a Barchart? The data is inserted by BarSeries.append()
ChartView {
id: totalChecksChart
BarSeries {
id: barChart
axisX: BarCategoryAxis {
categories: [1, 2, 3, 4, 5, 6, 7];
}
BarSet {
values: [0, 0, 0, 0, 0, 0, 0]
}
}
}
Button{
id: refreshButton
onClicked: {
_frontPageModel.loadAvailability(_db);
_frontPageModel.loadDateList(_db);
graphData = _frontPageModel.getAvailability();
var dateData = _frontPageModel.getDateList();
var xAxis = [];
var yAxis = [];
for (let f=0; f<dateData.length; f++) {
xAxis.push(dateData[f]);
}
for (let k=0; k<graphData.length; k++) {
yAxis.push(graphData[k]);
}
barChart.clear();
barChart.axisX.categories = xAxis;
barChart.axisY.max = 1;
barChart.axisY.min = 0;
barChart.append("Verfügbarkeit", yAxis);
}
}

angular-chart.js custom color based on chart label values

I'm integrating Doughnut Chart using angular-chart.js and I want to use custom color based on chart label value.
//Controller
vm.labels = ["A", "B", "C"];
vm.data = [1, 2, 3];
//HTML
<canvas id="doughnut" class="chart chart-doughnut"
chart-data="vm.data"
chart-labels="vm.labels"
chart-options="options"
chart-colors = "colours"
chart-legend = "true" >
</canvas>
and i'm setting the color like below
ChartJsProvider.setOptions({
colours: ['#DD1C2C', '#E03E2D','#E35B2B'],
responsive: true
});
It works perfect only if all A,B and C are there (A => #DD1C2C B=> #E03E2D and C=>#E35B2B)
But for the following case
$scope.labels = ["A", "C"];
$scope.data = [1, 3];
A => #DD1C2C and C=>#E03E2D
Expected : A => #DD1C2C and C=>#E35B2B
Did you try to fill the missing parts with empty string and 0 like
$scope.labels = ["A", "", "C"];
$scope.data = [1, 0, 3];

Couchbase custom reduce function

I have some documents in my Couchbase with the following template:
{
"id": 102750,
"status": 5,
"updatedAt": "2014-09-10T10:50:39.297Z",
"points1": 1,
"points2": -3,
"user1": {
"id": 26522,
...
},
"user2": {
"id": 38383,
...
},
....
}
What I want to do is to group the documents on the user and sum the points for each user and then show the top 100 users in the last week. I have been circling around but I haven't come with any solution.
I have started with the following map function:
function (doc, meta) {
if (doc.user1 && doc.user2) {
emit(doc.user1.id, doc.points1);
emit(doc.user2.id, doc.points2);
}
}
and then tried the sum to reduce the results but clearly I was wrong because I wasn't able to sort on the points and I couldn't also include the date parameter
you need to see my exemple I was able to group by date and show the values with reduce. but calculate the sum I did it in my program.
see the response How can I groupBy and change content of the value in couchbase?
I have solved this issue by the help of a server side script.
What I have done is I changed my map function to be like this:
function (doc, meta) {
if (doc.user1 && doc.user2) {
emit(dateToArray(doc.createdAt), { 'userId': doc.user1.id, 'points': doc.points1});
emit(dateToArray(doc.createdAt), { 'userId': doc.user2.id, 'points': doc.points2});
}
}
And in the script I query the view with the desired parameters and then I group and sort them then send the top 100 users.
I am using Node JS so my script is like this: (the results are what I read from couchbase view)
function filterResults(results) {
debug('filtering ' + results.length + ' entries..');
// get the values
var values = _.pluck(results, 'value');
var groupedUsers = {};
// grouping users and sum their points in the games
// groupedUsers will be like the follwoing:
// {
// '443322': 33,
// '667788': 55,
// ...
// }
for (var val in values) {
var userId = values[val].userId;
var points = values[val].points;
if (_.has(groupedUsers, userId)) {
groupedUsers[userId] += points;
}
else
groupedUsers[userId] = points;
}
// changing the groupedUsers to array form so it can be sorted by points:
// [['443322', 33], ['667788', 55], ...]
var topUsers = _.pairs(groupedUsers);
// sort descending
topUsers.sort(function(a, b) {
return b[1] - a[1];
});
debug('Number of users: ' + topUsers.length + '. Returning top 100 users');
return _.first(topUsers, 100);
}

How to clip a view in Famo.us?

I'm trying to build a countdown with Famous Timer.
As a first step, I want to make a scrolling digit, so I made 3 digits which are doing the needed animation and now I need to show only the middle one.
I saw the clipSize option, but couldn't understand how to use it.
If there are some other way to do it, that's great too.
My app is here: http://slexy.org/view/s2R8VNhgEO
Thanks, Alex A.
Rather than fix your code, I wrote an example of how you can create the effect you are looking for with the Lightbox render controller and clipping the view to only show the current count down index. Of course, this can be improved as needed.
Example of the working code in jsBin: Just click to start the counter.
Main Context
var mainContext = Engine.createContext();
var cview = new CountdownView({
start: 10,
size: [50, 50]
});
var counter = 0;
var started = false;
var funcRef;
cview.on('click', function () {
if (started) {
Timer.clear(funcRef);
started = false;
} else {
started = true;
funcRef = Timer.setInterval(function(){
console.log('setNext ' + cview.nextIndex);
cview.setNext();
},1000);
}
});
var container = new ContainerSurface({
size: [100, 100],
properties: {
overflow: 'hidden'
}
});
container.add(cview);
mainContext.add(container);
CountdownView
function CountdownView(options) {
View.apply(this, arguments);
this.options.start = options.start || 10;
this.surfaces = [];
for (var i = 0; i <= this.options.start; i++) {
var surface = new Surface({
size: this.options.size,
content: i.toString(),
properties: {
backgroundColor: "hsl(" + (i * 360 / 40) + ", 100%, 50%)",
lineHeight: this.options.size[1]+"px",
textAlign: "center",
fontSize: "30px",
cursor:'pointer'
}
});
this.surfaces.push(surface);
surface.pipe(this._eventOutput);
}
this.renderer = new Lightbox({
inOpacity: 0,
outOpacity: 0,
inOrigin: [1, 1],
inAlign: [1, 1],
showOrigin: [0, 0],
inTransform: Transform.translate(0,0,0.0002),
outTransform: Transform.translate(0,this.options.size[1],0.0001),
outOrigin: [1,1],
outAlign: [1,1],
inTransition: { duration: 600, curve: Easing.inCirc },
outTransition: { duration: 1000, curve: Easing.outCirc },
overlap: true
});
this.add(this.renderer);
this.renderer.show(this.surfaces[this.options.start]);
this.nextIndex = this.options.start - 1;
}
CountdownView.prototype = Object.create(View.prototype);
CountdownView.prototype.constructor = CountdownView;
CountdownView.prototype.setNext = function setNext() {
this.renderer.show(this.surfaces[this.nextIndex]);
this.nextIndex = (this.nextIndex -1 < 0) ? this.options.start : this.nextIndex - 1;
};
CountdownView.prototype.setIndex = function setIndex(newIndex) {
if (newIndex < 0 || newIndex > this.countStart) return;
this.renderer.show(this.surfaces[newIndex]);
};
CountdownView.prototype.getLength = function getLength() {
return this.surfaces.length;
};

How can I delete duplicates in a Dart List? list.distinct()?

How do I delete duplicates from a list without fooling around with a set? Is there something like list.distinct()? or list.unique()?
void main() {
print("Hello, World!");
List<String> list = ['abc',"abc",'def'];
list.forEach((f) => print("this is list $f"));
Set<String> set = new Set<String>.from(list);
print("this is #0 ${list[0]}");
set.forEach((f) => print("set: $f"));
List<String> l2= new List<String>.from(set);
l2.forEach((f) => print("This is new $f"));
}
Hello, World!
this is list abc
this is list abc
this is list def
this is #0 abc
set: abc
set: def
This is new abc
This is new def
Set seems to be way faster!! But it loses the order of the items :/
Use toSet and then toList
var ids = [1, 4, 4, 4, 5, 6, 6];
var distinctIds = ids.toSet().toList();
Result: [1, 4, 5, 6]
Or with spread operators:
var distinctIds = [...{...ids}];
I didn't find any of the provided answers very helpful.
Here is what I generally do:
final ids = Set();
myList.retainWhere((x) => ids.add(x.id));
Of course you can use any attribute which uniquely identifies your objects. It doesn't have to be an id field.
Benefits over other approaches:
Preserves the original order of the list
Works for rich objects not just primitives/hashable types
Doesn't have to copy the entire list to a set and back to a list
Update 09/12/21
You can also declare an extension method once for lists:
extension Unique<E, Id> on List<E> {
List<E> unique([Id Function(E element)? id, bool inplace = true]) {
final ids = Set();
var list = inplace ? this : List<E>.from(this);
list.retainWhere((x) => ids.add(id != null ? id(x) : x as Id));
return list;
}
}
This extension method does the same as my original answer. Usage:
// Use a lambda to map an object to its unique identifier.
myRichObjectList.unique((x) => x.id);
// Don't use a lambda for primitive/hashable types.
hashableValueList.unique();
Set works okay, but it doesn't preserve the order. Here's another way using LinkedHashSet:
import "dart:collection";
void main() {
List<String> arr = ["a", "a", "b", "c", "b", "d"];
List<String> result = LinkedHashSet<String>.from(arr).toList();
print(result); // => ["a", "b", "c", "d"]
}
https://api.dart.dev/stable/2.4.0/dart-collection/LinkedHashSet/LinkedHashSet.from.html
Try the following:
List<String> duplicates = ["a", "c", "a"];
duplicates = duplicates.toSet().toList();
Check this code on Dartpad.
If you want to keep ordering or are dealing with more complex objects than primitive types, store seen ids to the Set and filter away those ones that are already in the set.
final list = ['a', 'a', 'b'];
final seen = Set<String>();
final unique = list.where((str) => seen.add(str)).toList();
print(unique); // => ['a', 'b']
//This easy way works fine
List<String> myArray = [];
myArray = ['x', 'w', 'x', 'y', 'o', 'x', 'y', 'y', 'r', 'a'];
myArray = myArray.toSet().toList();
print(myArray);
// result => myArray =['x','w','y','o','r', 'a']
I am adding this to atreeon's answer. For anyone that want use this with Object:
class MyObject{
int id;
MyObject(this.id);
#override
bool operator ==(Object other) {
return other != null && other is MyObject && hashCode == other.hashCode;
}
#override
int get hashCode => id;
}
main(){
List<MyObject> list = [MyObject(1),MyObject(2),MyObject(1)];
// The new list will be [MyObject(1),MyObject(2)]
List<MyObject> newList = list.toSet().toList();
}
Remove duplicates from a list of objects:
class Stock {
String? documentID; //key
Make? make;
Model? model;
String? year;
Stock({
this.documentID,
this.make,
this.model,
this.year,
});
}
List of stock, from where we want to remove duplicate stocks
List<Stock> stockList = [stock1, stock2, stock3];
Remove duplicates
final ids = stockList.map((e) => e.documentID).toSet();
stockList.retainWhere((x) => ids.remove(x.documentID));
Using Dart 2.3+, you can use the spread operators to do this:
final ids = [1, 4, 4, 4, 5, 6, 6];
final distinctIds = [...{...ids}];
Whether this is more or less readable than ids.toSet().toList() I'll let the reader decide :)
For distinct list of objects you can use Equatable package.
Example:
// ignore: must_be_immutable
class User extends Equatable {
int id;
String name;
User({this.id, this.name});
#override
List<Object> get props => [id];
}
List<User> items = [
User(
id: 1,
name: "Omid",
),
User(
id: 2,
name: "Raha",
),
User(
id: 1,
name: "Omid",
),
User(
id: 2,
name: "Raha",
),
];
print(items.toSet().toList());
Output:
[User(1), User(2)]
Here it is, a working solution:
var sampleList = ['1', '2', '3', '3', '4', '4'];
//print('original: $sampleList');
sampleList = Set.of(sampleList).toList();
//print('processed: $sampleList');
Output:
original: [1, 2, 3, 3, 4, 4]
processed: [1, 2, 3, 4]
Using the fast_immutable_collections package:
[1, 2, 3, 2].distinct();
Or
[1, 2, 3, 2].removeDuplicates().toList();
Note: While distinct() returns a new list, removeDuplicates() does it lazily by returning an Iterable. This means it is much more efficient when you are doing some extra processing. For example, suppose you have a list with a million items, and you want to remove duplicates and get the first five:
// This will process five items:
List<String> newList = list.removeDuplicates().take(5).toList();
// This will process a million items:
List<String> newList = list.distinct().sublist(0, 5);
// This will also process a million items:
List<String> newList = [...{...list}].sublist(0, 5);
Both methods also accept a by parameter. For example:
// Returns ["a", "yk", "xyz"]
["a", "yk", "xyz", "b", "xm"].removeDuplicates(by: (item) => item.length);
If you don't want to include a package into your project but needs the lazy code, here it is a simplified removeDuplicates():
Iterable<T> removeDuplicates<T>(Iterable<T> iterable) sync* {
Set<T> items = {};
for (T item in iterable) {
if (!items.contains(item)) yield item;
items.add(item);
}
}
Note: I am one of the authors of the fast_immutable_collections package.
void uniqifyList(List<Dynamic> list) {
for (int i = 0; i < list.length; i++) {
Dynamic o = list[i];
int index;
// Remove duplicates
do {
index = list.indexOf(o, i+1);
if (index != -1) {
list.removeRange(index, 1);
}
} while (index != -1);
}
}
void main() {
List<String> list = ['abc', "abc", 'def'];
print('$list');
uniqifyList(list);
print('$list');
}
Gives output:
[abc, abc, def]
[abc, def]
As for me, one of the best practices is sort the array, and then deduplicate it. The idea is stolen from low-level languages. So, first make the sort by your own, and then deduplicate equal values that are going after each other.
// Easy example
void dedup<T>(List<T> list, {removeLast: true}) {
int shift = removeLast ? 1 : 0;
T compareItem;
for (int i = list.length - 1; i >= 0; i--) {
if (compareItem == (compareItem = list[i])) {
list.removeAt(i + shift);
}
}
}
// Harder example
void dedupBy<T, I>(List<T> list, I Function(T) compare, {removeLast: true}) {
int shift = removeLast ? 1 : 0;
I compareItem;
for (int i = list.length - 1; i >= 0; i--) {
if (compareItem == (compareItem = compare(list[i]))) {
list.removeAt(i + shift);
}
}
}
void main() {
List<List<int>> list = [[1], [1], [2, 1], [2, 2]];
print('$list');
dedupBy(list, (innerList) => innerList[0]);
print('$list');
print('\n removeLast: false');
List<List<int>> list2 = [[1], [1], [2, 1], [2, 2]];
print('$list2');
dedupBy(list2, (innerList) => innerList[0], removeLast: false);
print('$list2');
}
Output:
[[1], [1], [2, 1], [2, 2]]
[[1], [2, 1]]
removeLast: false
[[1], [1], [2, 1], [2, 2]]
[[1], [2, 2]]
This is another way...
final reducedList = [];
list.reduce((value, element) {
if (value != element)
reducedList.add(value);
return element;
});
reducedList.add(list.last);
print(reducedList);
It works for me.
var list = [
{"id": 1, "name": "Joshua"},
{"id": 2, "name": "Joshua"},
{"id": 3, "name": "Shinta"},
{"id": 4, "name": "Shinta"},
{"id": 5, "name": "Zaidan"}
];
list.removeWhere((element) => element.name == element.name.codeUnitAt(1));
list.sort((a, b) => a.name.compareTo(b.name));
Output:
[{"id": 1, "name": "Joshua"},
{"id": 3, "name": "Shinta"},
{"id": 5, "name": "Zaidan"}]
List<Model> bigList = [];
List<ModelNew> newList = [];
for (var element in bigList) {
var list = newList.where((i) => i.type == element.type).toList();
if(list.isEmpty){
newList.add(element);
}
}
Create method to remove duplicates from Array and return Array of unique elements.
class Utilities {
static List<String> uniqueArray(List<String> arr) {
List<String> newArr = [];
for (var obj in arr) {
if (newArr.contains(obj)) {
continue;
}
newArr.add(obj);
}
return newArr;
}
}
You can use the following way:
void main(List <String> args){
List<int> nums = [1, 2, 2, 2, 3, 4, 5, 5];
List<int> nums2 = nums.toSet().toList();
}
NOTE: This will not work if the items in the list are objects of class and have the same attributes. So, to solve this, you can use the following way:
void main() {
List<Medicine> objets = [Medicine("Paracetamol"),Medicine("Paracetamol"), Medicine("Benylin")];
List <String> atributs = [];
objets.forEach((element){
atributs.add(element.name);
});
List<String> noDuplicates = atributs.toSet().toList();
print(noDuplicates);
}
class Medicine{
final String name;
Medicine(this.name);
}
This is my solution
List<T> removeDuplicates<T>(List<T> list, IsEqual isEqual) {
List<T> output = [];
for(var i = 0; i < list.length; i++) {
bool found = false;
for(var j = 0; j < output.length; j++) {
if (isEqual(list[i], output[j])) {
found = true;
}
}
if (found) {
output.add(list[i]);
}
}
return output;
}
Use it like this:
var theList = removeDuplicates(myOriginalList, (item1, item2) => item1.documentID == item2.documentID);
or...
var theList = removeDuplicates(myOriginalList, (item1, item2) => item1.equals(item2));
or...
I have a library called Reactive-Dart that contains many composable operators for terminating and non-terminating sequences. For your scenario it would look something like this:
final newList = [];
Observable
.fromList(['abc', 'abc', 'def'])
.distinct()
.observe((next) => newList.add(next), () => print(newList));
Yielding:
[abc, def]
I should add that there are other libraries out there with similar features. Check around on GitHub and I'm sure you'll find something suitable.