How to draw from async data in dart? - mobile

Im trying to read a file (async) and return a list of data. Then, I want to draw lines when every given number is line height using CustomPainter (I want to draw multiple lines)
class BarChartPainter extends CustomPainter {
#override
paint(Canvas canvas, Size size) {
final paint = new Paint()
..color = AppColors.primaryColor
..style = PaintingStyle.fill;
var futurePath = run();
futurePath.then((height) {
print('1: $height');
for (var lineHeight in height) {
print('2: $lineHeight');
canvas.drawRect(
new Rect.fromLTWH(1.0, 10.0, 10.0, lineHeight.toDouble()), paint);
}
}).catchError((e) => print(e));
}
#override
bool shouldRepaint(BarChartPainter old) => false;
}
print('1: $height') returns [75, 0, 0, 0, 0, 0, 0, 0, 0, 0, 86, 72, 10, 2, 0, 0, 0, 0, 0, (...), 0, 85, 22, 3, 1]
print('2: $lineHeight') returns 75
drawRect doesn't work. Error is : I/flutter (13751): Object has been disposed.

All of the data needs to be ready by the time the frame is drawn. Using a Future in this case means the data is only available after the canvas has been rendered.
One way to handle this is to move the file loading outside of the CustomPainter and render the painter when the data has been loaded. Here's an example loading from a JSON asset file:
class BarChart extends StatefulWidget {
#override
createState() => new _BarChartState();
}
class _BarChartState extends State<BarChart> {
var barHeights = <int>[];
#override
initState() {
super.initState();
_fetchBarHeights();
}
_fetchBarHeights() async {
final heights = await rootBundle
.loadStructuredData<List<int>>('assets/barchart.json', (jsonStr) async {
final jsonList = json.decode(jsonStr);
return (jsonList as List).map((i) => (i as int)).toList();
});
setState(() => barHeights = heights);
}
#override
Widget build(BuildContext context) {
return new Center(
child: new CustomPaint(
painter: new _BarChartPainter(barHeights),
));
}
}
class _BarChartPainter extends CustomPainter {
_BarChartPainter(this.barHeights);
final List<int> barHeights;
#override
paint(Canvas canvas, Size size) {
final paint = new Paint()
..color = Colors.blue
..style = PaintingStyle.fill;
var pos = 0.0;
barHeights.forEach((barHeight) {
canvas.drawRect(
new Rect.fromLTWH(pos, 10.0, 10.0, barHeight.toDouble()),
paint,
);
pos += 10;
});
}
#override
bool shouldRepaint(BarChartPainter old) =>
!(new ListEquality().equals(old.barHeights, barHeights));
}
}

Related

How to convert List<Asset> Array to List<File> Array in Dart | Flutter?

I have List array which is based on flutter multi_image_picker: ^4.7.14 dependency. How can I convert List Asset Array to List File Array?
This is the code
class _ConvertImageState extends State<ConvertImage> {
List<Asset> images = List<Asset>();
#override
void initState() {
super.initState();
}
Future<void> pickImages() async {
List<Asset> resultList = List<Asset>();
try {
resultList = await MultiImagePicker.pickImages(
maxImages: 20,
enableCamera: true,
selectedAssets: images,
materialOptions: MaterialOptions(
selectCircleStrokeColor: "white",
actionBarTitle: "Select Images",
allViewTitle: "All Images",
actionBarColor: "#3b3b3b",
actionBarTitleColor: "white",
statusBarColor: '#bbbbbb',
selectionLimitReachedText: "You can select minimum 3 and maximum 20 images",
),
);
} on Exception catch (e) {
print(e);
}
setState(() {
images = resultList;
});
}
#override
Widget build(BuildContext context) {......}
}
Can I do it with a loop? Please, anyone can tell me the correct way?
Here I find an answer. This is my working code. I think it is correct.
class _ConvertImageState extends State<ConvertImage> {
List<Asset> images = List<Asset>();
List<File> fileImageArray = [];
#override
void initState() {
super.initState();
}
Future<void> pickImages() async {
List<Asset> resultList = List<Asset>();
try {
resultList = await MultiImagePicker.pickImages(
maxImages: 20,
enableCamera: true,
selectedAssets: images,
materialOptions: MaterialOptions(
selectCircleStrokeColor: "white",
actionBarTitle: "Select Images",
allViewTitle: "All Images",
actionBarColor: "#3b3b3b",
actionBarTitleColor: "white",
statusBarColor: '#bbbbbb',
selectionLimitReachedText: "You can select minimum 3 and maximum 20 images",
),
);
} on Exception catch (e) {
print(e);
}
setState(() {
images = resultList;
putToFileArray();
});
}
void putToFileArray(){
fileImagesArray.clear();
images.forEach((imageAsset) async {
final filePath =
await FlutterAbsolutePath.getAbsolutePath(imageAsset.identifier);
File tempFile = File(filePath);
if (tempFile.existsSync()) {
fileImageArray.add(tempFile);
}
});
}
#override
Widget build(BuildContext context) {......}
}
FlutterAbsolutePathClass (Dependency)
flutter_absolute_path: ^1.0.6
Link: https://pub.dev/packages/flutter_absolute_path
To Print Array
onPressed: () {
print(fileImageArray.toString());
};

How to start a counter timer from zero in flutter?

I tried to display a timer ( format dd HH mm ss ) to count the time between each actions (button action for exemple ). And need to work even the app is close and rebuild. Currently I load a string date I saved with sharedpreference when I pressed a button who represent the time when I pressed the button. I format all time decimal to compare and display time difference. I think it's not beautifull, not what I search, and I don't succeded to display clock in the format (dd HH mm ss). If someone have a more simple exemple :)
load_records_pulsion() async{
/*var current_time = DateFormat('yyyy-MM-dd HH').format(DateTime.now());*/
SharedPreferences prefs = await SharedPreferences.getInstance();
setState(() {
RegExp regExp = new RegExp( //Here is the regex time pulsion
r"([12]\d{3})-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])",
);
last_pulsion = (prefs.getString('last_pulsion'))??0;
var match = regExp.firstMatch("$last_pulsion");
annees = match.group(1); // hh:mm
mois = match.group(2); // hh:mm
jours = match.group(3); // hh:mm
int annees_int = int.tryParse("$annees") ;
int mois_int = int.tryParse("$mois") ;
int jours_int = int.tryParse("$jours") ;
print("$annees_int");
print("$mois_int");
print("$jours_int");
final last_pulsion2 = DateTime(annees_int, mois_int, jours_int);
final date_now = DateTime.now();
difference_pulsion = date_now.difference(last_pulsion2).inDays;
if(difference_pulsion==0){
difference_pulsion ="";
prefix_pulsion ="Aujourd'hui";
}else{
prefix_pulsion ="jours";
}
});
}
Also I tried this code, it's OK the timer is increase when I call the function, but I don't want datenow, I just need to start with zero time
int _start = 0;
void startTimer() {
_start=0;
var now = new DateTime.now();
const oneSec = const Duration(seconds: 1);
_timer = new Timer.periodic(
oneSec,
(Timer timer) => setState(() {
{
chrono = now.add(new Duration(seconds: _start));
_start = _start + 1;
}
}));
}
Edit: I found this solution but have some lifecycle error, and if I close the app, I loose the timer.
Stopwatch stopwatch = new Stopwatch();
void rightButtonPressed() {
setState(() {
if (stopwatch.isRunning) {
stopwatch.reset();
} else {
stopwatch.reset();
stopwatch.start();
}
});
}
#override
Widget build(BuildContext context)
{
...
new Container(height: 80.0,
child: new Center(
child: new TimerText(stopwatch: stopwatch),
)),
...
class TimerText extends StatefulWidget {
TimerText({this.stopwatch});
final Stopwatch stopwatch;
TimerTextState createState() => new TimerTextState(stopwatch: stopwatch);
}
class TimerTextState extends State<TimerText> {
Timer timer;
final Stopwatch stopwatch;
TimerTextState({this.stopwatch}) {
timer = new Timer.periodic(new Duration(milliseconds: 30), callback);
}
void callback(Timer timer) {
if (stopwatch.isRunning) {
setState(() {
});
}
}
#override
Widget build(BuildContext context) {
final TextStyle timerTextStyle = const TextStyle(fontSize: 50.0, fontFamily: "Open Sans");
String formattedTime = TimerTextFormatter.format(stopwatch.elapsedMilliseconds);
return new Text(formattedTime, style: timerTextStyle);
}
}
class TimerTextFormatter {
static String format(int milliseconds) {
int seconds = (milliseconds / 1000).truncate();
int minutes = (seconds / 60).truncate();
int hours = (minutes / 60).truncate();
int days = (hours / 24).truncate();
String minutesStr = (minutes % 60).toString().padLeft(2, '0');
String secondsStr = (seconds % 60).toString().padLeft(2, '0');
String hoursStr = (hours % 60).toString().padLeft(2, '0');
String daysStr = (days % 24).toString().padLeft(2, '0');
return "$daysStr:$hoursStr:$minutesStr:$secondsStr";
}
}
If you want the counter to persist after closing the app, there is no way around saving the value somewhere (like shared preferences).
Using dateTime.toIso8601String() and DateTime.parse() will make the saving and loading less ugly.
To calculate the passed time you can use DateTime.now().difference(lastButtonPressed)
There should be a function to format Duration (https://api.flutter.dev/flutter/intl/DateFormat/formatDurationFrom.html) but it's not implemented yet. I found one here: Formatting a Duration like HH:mm:ss
Here is a little example:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutterfly/SharedPrefs.dart';
class TestWidget extends StatefulWidget {
#override
_TestWidgetState createState() => _TestWidgetState();
}
class _TestWidgetState extends State<TestWidget> {
DateTime _lastButtonPress;
String _pressDuration;
Timer _ticker;
#override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("Time since button pressed"),
Text(_pressDuration),
RaisedButton(
child: Text("Press me"),
onPressed: () {
_lastButtonPress = DateTime.now();
_updateTimer();
sharedPreferences.setString("lastButtonPress",_lastButtonPress.toIso8601String());
},
)
],
),
);
}
#override
void initState() {
super.initState();
final lastPressString = sharedPreferences.getString("lastButtonPress");
_lastButtonPress = lastPressString!=null ? DateTime.parse(lastPressString) : DateTime.now();
_updateTimer();
_ticker = Timer.periodic(Duration(seconds:1),(_)=>_updateTimer());
}
#override
void dispose() {
_ticker.cancel();
super.dispose();
}
void _updateTimer() {
final duration = DateTime.now().difference(_lastButtonPress);
final newDuration = _formatDuration(duration);
setState(() {
_pressDuration = newDuration;
});
}
String _formatDuration(Duration duration) {
String twoDigits(int n) {
if (n >= 10) return "$n";
return "0$n";
}
String twoDigitMinutes = twoDigits(duration.inMinutes.remainder(60));
String twoDigitSeconds = twoDigits(duration.inSeconds.remainder(60));
return "${twoDigits(duration.inHours)}:$twoDigitMinutes:$twoDigitSeconds";
}
}
For simplicity i initialized shared preferences in the main method in global scope.

Flutter, adjust widget in list depending on scrolloffset

How would I set the height of a Widget in a ListView depending on the offset of the underlying scrollview?
Thanks for any hints :)
To know the current scroll offset of ListView, you can just pass a controller and check the offset of controller.
example:
class ScrollOffsetWidget extends StatefulWidget {
#override
_ScrollOffsetWidgetState createState() => new _ScrollOffsetWidgetState();
}
class _ScrollOffsetWidgetState extends State<ScrollOffsetWidget> {
List<double> height = <double>[200.0,200.0,200.0,200.0,200.0];
double scrollOffset = 0.0;
static ScrollController _controller;
_ScrollOffsetWidgetState(){
_controller = new ScrollController(initialScrollOffset: scrollOffset);
_controller.addListener(listen);
}
void listen(){
final List<double> newHeight = height;
if(_controller.offset>100.0){
newHeight[1] = 400.0;
}else{
newHeight[1] = 200.0;
}
setState((){
height = newHeight;
});
}
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
body: new ListView(
controller: _controller,
children: <Widget>[
new AnimatedContainer(duration: const Duration(milliseconds: 300),height: height[0],color: Colors.primaries[0],),
new AnimatedContainer(duration: const Duration(milliseconds: 300),height: height[1],color: Colors.primaries[1],),
new AnimatedContainer(duration: const Duration(milliseconds: 300),height: height[2],color: Colors.primaries[2],),
new AnimatedContainer(duration: const Duration(milliseconds: 300),height: height[3],color: Colors.primaries[3],),
new AnimatedContainer(duration: const Duration(milliseconds: 300),height: height[4],color: Colors.primaries[4],)
],
),
),
);
}
}
Hope that helped!

JFreeChart: How to plot an array of 100000 samples using JFreeChart in a dynamic fashion [duplicate]

I have an array of 100,000 samples all of double type. I want to display or plot this array so that I get a moving chart/ plot (dynamic) instead of displaying it at once. Can anyone help me out. In plot ee[] and y[] is obtained after some processing.
private byte[] FileR(String filename) {
byte[] data = null;
AudioInputStream ais;
try {
File fileIn = new File(filename);
if (fileIn.exists()) {
ais = AudioSystem.getAudioInputStream(fileIn);
data = new byte[ais.available()];
ais.read(data);
}
} catch (UnsupportedAudioFileException | IOException e) {
System.out.println(e.getMessage());
throw new RuntimeException("Could not read " + filename);
}
return data;
}
private byte[] Capture(double t) throws LineUnavailableException {
AudioFormat format = new AudioFormat(48000, 16, 2, true, false);
DataLine.Info info = new DataLine.Info(TargetDataLine.class, format);
line = (TargetDataLine) AudioSystem.getLine(info);
line.open(format);
line.open();
int size = (int) (line.getBufferSize() * t);
byte[] b = new byte[size];
line.start();
line.read(b, 0, size);
return b;
}
private void plot(double[] ee, double[] y) {
XYSeries see = new XYSeries("Filtered");
for (int i = 0; i < ee.length; i++) {
see.add(i, ee[i]);
}
XYSeriesCollection cee = new XYSeriesCollection();
cee.addSeries(see);
XYItemRenderer ree = new StandardXYItemRenderer();
NumberAxis rangeAxisee = new NumberAxis("Filtered");
XYPlot subplot1 = new XYPlot(cee, null, rangeAxisee, ree);
subplot1.setRangeAxisLocation(AxisLocation.BOTTOM_OR_LEFT);
XYSeries sy = new XYSeries("Noisy");
for (int i = 0; i < y.length; i++) {
sy.add(i, y[i]);
}
XYSeriesCollection cy = new XYSeriesCollection();
cy.addSeries(sy);
XYItemRenderer ry = new StandardXYItemRenderer();
NumberAxis rangeAxisy = new NumberAxis("Noisy");
XYPlot subplot2 = new XYPlot(cy, null, rangeAxisy, ry);
subplot2.setRangeAxisLocation(AxisLocation.BOTTOM_OR_LEFT);
CombinedDomainXYPlot plot = new CombinedDomainXYPlot(new NumberAxis("Domain"));
plot.setGap(10.0);
plot.add(subplot1);
plot.add(subplot2);
plot.setOrientation(PlotOrientation.VERTICAL);
JFreeChart chart = new JFreeChart("Adaptive Filter", JFreeChart.DEFAULT_TITLE_FONT, plot, true);
panel = new ChartPanel(chart, true, true, true, false, true);
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(750, 500);
frame.add(panel, BorderLayout.CENTER);
frame.setVisible(true);
}
You need to have a thread where all this data is coming from. For example from your backend. Then, every time there is a new set of data for the chart you will need to update the chart via the Event Dispatch Thread. If your chart data is coming in regular intervals it is fairly easy (ie. pull), however if it is push (ie. the data is more random), and can get a little more tricky.
Remove all the GUI creation out of the plot method :
JFreeChart chart = new JFreeChart("Adaptive Filter", JFreeChart.DEFAULT_TITLE_FONT, plot, true);
panel = new ChartPanel(chart, true, true, true, false, true);
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(750, 500);
frame.add(panel, BorderLayout.CENTER);
frame.setVisible(true);
This only needs to be called once. The plot method will be called every time new data comes.
Here is a simple approach :
public void startCharting() {
final MySoundCard card = new MySoundCard();
final MyJFreeChart chart = new MyJFreeChart();
Runnable r = new Runnable() {
#Override
public void run() {
while(true) {
int[] i = card.FileR();
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
chart.plot();
}
});
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
Thread t = new Thread(r);
t.start();
}
A thread calls your datasource every second and then updates the chart. The updates are invoked in the Event Dispatch Thread.

how to change pie chart colors of JFreeChart?

how to customize the colors of JFreeChart graphic.
lets see my java code :
private StreamedContent chartImage ;
public void init(){
JFreeChart jfreechart = ChartFactory.createPieChart("title", createDataset(), true, true, false);
File chartFile = new File("dynamichart");
ChartUtilities.saveChartAsPNG(chartFile, jfreechart, 375, 300);
chartImage = new DefaultStreamedContent(new FileInputStream( chartFile), "image/png");
}
public PieDataset createDataset() {
DefaultPieDataset dataset = new DefaultPieDataset();
dataset.setValue("J-2", 10);
dataset.setValue("J-1", 15);
dataset.setValue("J", 50);
dataset.setValue("J+1", 20);
dataset.setValue("J+2", 15);
return dataset;
}
html page :
<p:graphicImage id="MyImage" value="#{beanCreateImage.chartImage}" />
You can change the color of single pieces like this:
JFreeChart chart = ChartFactory.createPieChart("title", createDataset(), true, true, false);
PiePlot plot = (PiePlot) chart.getPlot();
plot.setSectionPaint("J+1", Color.black);
plot.setSectionPaint("J-1", new Color(120, 0, 120));
// or do this, if you are using an older version of JFreeChart:
//plot.setSectionPaint(1, Color.black);
//plot.setSectionPaint(3, new Color(120, 0, 120));
So with your code, all the pies are colored automatically, after my code changes, the J-1 and J+1 have a fixed color, the rest gets automatically colored.
To set the colous for a chart you can implement the DrawingSupplier inferface in this case I've used DefaultDrawingSupplier:
public class ChartDrawingSupplier extends DefaultDrawingSupplier {
public Paint[] paintSequence;
public int paintIndex;
public int fillPaintIndex;
{
paintSequence = new Paint[] {
new Color(227, 26, 28),
new Color(000,102, 204),
new Color(102,051,153),
new Color(102,51,0),
new Color(156,136,48),
new Color(153,204,102),
new Color(153,51,51),
new Color(102,51,0),
new Color(204,153,51),
new Color(0,51,0),
};
}
#Override
public Paint getNextPaint() {
Paint result
= paintSequence[paintIndex % paintSequence.length];
paintIndex++;
return result;
}
#Override
public Paint getNextFillPaint() {
Paint result
= paintSequence[fillPaintIndex % paintSequence.length];
fillPaintIndex++;
return result;
}
}
Then include this code in your `init()' method
JFreeChart jfreechart = ChartFactory.createPieChart("title", createDataset(), true, true, false);
Plot plot = jfreechart.getPlot();
plot.setDrawingSupplier(new ChartDrawingSupplier());
...
You can customize the colors according to the labels while getting the data from the dataset:
// Add custom colors
PiePlot plot = (PiePlot) chart.getPlot();
for (int i = 0; i < dataset.getItemCount(); i++) {
if(dataset.getKey(i).equals("J+1")){
plot.setSectionPaint(i, Color.black);
}
}
You can also use a switch-case statement or the one you prefer.

Resources