diff --git a/example/ios/Flutter/flutter_export_environment.sh b/example/ios/Flutter/flutter_export_environment.sh index 3a81732..16ecc30 100755 --- a/example/ios/Flutter/flutter_export_environment.sh +++ b/example/ios/Flutter/flutter_export_environment.sh @@ -1,13 +1,15 @@ #!/bin/sh # This is a generated file; do not edit or check into version control. -export "FLUTTER_ROOT=/Users/serdarcoskun/Development/flutter" -export "FLUTTER_APPLICATION_PATH=/Users/serdarcoskun/Projects/dakik/oscilloscope/example" -export "FLUTTER_TARGET=/Users/serdarcoskun/Projects/dakik/oscilloscope/example/lib/main.dart" +export "FLUTTER_ROOT=/home/sezerbudak/flutter" +export "FLUTTER_APPLICATION_PATH=/home/sezerbudak/apps/sb_oscilloscope/oscilloscope/example" +export "FLUTTER_TARGET=lib/main.dart" export "FLUTTER_BUILD_DIR=build" export "SYMROOT=${SOURCE_ROOT}/../build/ios" export "OTHER_LDFLAGS=$(inherited) -framework Flutter" -export "FLUTTER_FRAMEWORK_DIR=/Users/serdarcoskun/Development/flutter/bin/cache/artifacts/engine/ios" +export "FLUTTER_FRAMEWORK_DIR=/home/sezerbudak/flutter/bin/cache/artifacts/engine/ios" export "FLUTTER_BUILD_NAME=1.0.0" export "FLUTTER_BUILD_NUMBER=1" -export "TRACK_WIDGET_CREATION=true" -export "DART_DEFINES=flutter.inspector.structuredErrors=true" +export "DART_OBFUSCATION=false" +export "TRACK_WIDGET_CREATION=false" +export "TREE_SHAKE_ICONS=false" +export "PACKAGE_CONFIG=.packages" diff --git a/example/lib/main.dart b/example/lib/main.dart index 9d1481b..c95da8c 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -189,8 +189,8 @@ class _ShellState extends State { children: [ Expanded( child: Oscilloscope( - showYAxis: false, - yAxisColor: Colors.lightBlue, + // showYAxis: false, + // yAxisColor: Colors.lightBlue, padding: 0.0, backgroundColor: Colors.white60, traceColor: Colors.black, @@ -198,6 +198,7 @@ class _ShellState extends State { yAxisMin: ecgMin, xScale: 1, dataSet: ecgBuffer, + centerPoint: ecgBuffer.isNotEmpty ? ecgBuffer.first : null, isZoomable: true, isScrollable: false, strokeWidth: 1, @@ -224,8 +225,8 @@ class _ShellState extends State { SizedBox( height: 44, child: Oscilloscope( - showYAxis: false, - yAxisColor: Colors.lightBlue, + // showYAxis: false, + // yAxisColor: Colors.lightBlue, padding: 0.0, backgroundColor: Colors.white60, traceColor: Colors.black.withAlpha(100), @@ -233,6 +234,7 @@ class _ShellState extends State { yAxisMin: ecgMin, xScale: 1, dataSet: ecgPreviewData, + centerPoint: ecgPreviewData.isNotEmpty ? ecgPreviewData.first : null, isZoomable: false, isScrollable: true, strokeWidth: 0.5, diff --git a/example/pubspec.lock b/example/pubspec.lock index 6a66284..06a4a99 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -8,6 +8,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.0" + characters: + dependency: transitive + description: + name: characters + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0-nullsafety.3" charcode: dependency: transitive description: @@ -21,7 +28,7 @@ packages: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.14.12" + version: "1.15.0-nullsafety.3" cupertino_icons: dependency: "direct main" description: @@ -61,14 +68,14 @@ packages: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.1.8" + version: "1.3.0-nullsafety.3" oscilloscope: dependency: "direct dev" description: path: ".." relative: true source: path - version: "0.0.3" + version: "0.4.0" path: dependency: transitive description: @@ -122,14 +129,14 @@ packages: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.1.6" + version: "1.3.0-nullsafety.3" vector_math: dependency: transitive description: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.0.8" + version: "2.1.0-nullsafety.3" xml_parser: dependency: "direct main" description: @@ -138,5 +145,5 @@ packages: source: hosted version: "0.1.2" sdks: - dart: ">=2.6.0 <3.0.0" + dart: ">=2.10.0-110 <2.11.0" flutter: ">=0.1.4 <2.0.0" diff --git a/lib/oscilloscope.dart b/lib/oscilloscope.dart index cc8865b..50c183d 100644 --- a/lib/oscilloscope.dart +++ b/lib/oscilloscope.dart @@ -3,11 +3,10 @@ library oscilloscope; import 'dart:math' as Math; -import 'dart:typed_data'; import 'dart:ui'; import 'package:flutter/material.dart'; -import 'dart:math'; + /// A widget that defines a customisable Oscilloscope type display that can be used to graph out data /// /// The [dataSet] arguments MUST be a List - this is the data that is used by the display to generate a trace @@ -35,35 +34,41 @@ class Oscilloscope extends StatefulWidget { final double padding; final Color backgroundColor; final Color traceColor; - final Color yAxisColor; - final bool showYAxis; final double xScale; final bool isScrollable; final bool isZoomable; final bool isAdaptiveRange; final bool willNormalizeData; - final GridDrawingSetting gridDrawingSetting; + final double centerPoint; final double strokeWidth; - Function(double x, double y) onScaleChange; - - Oscilloscope( - {this.traceColor = Colors.white, - this.backgroundColor = Colors.black, - this.yAxisColor = Colors.white, - this.padding = 10.0, - this.yAxisMax = 1.0, - this.yAxisMin = 0.0, - this.showYAxis = false, - this.xScale = 1.0, - this.isScrollable = false, - this.isZoomable = false, - this.isAdaptiveRange = false, - this.willNormalizeData = false, - this.gridDrawingSetting, - this.strokeWidth = 2.0, - @required this.dataSet, - this.onScaleChange - }); + final double dataMultiplier; + final double yScaleFactor; + final double yTranslateFactor; + final GridDrawingSetting gridDrawingSetting; + final ReverseSetting reverseSetting; + final Function(double x, double y) onScaleChange; + + Oscilloscope({ + this.traceColor = Colors.white, + this.backgroundColor = Colors.black, + this.padding = 10.0, + this.yAxisMax = 1.0, + this.yAxisMin = 0.0, + this.xScale = 1.0, + this.isScrollable = false, + this.isZoomable = false, + this.isAdaptiveRange = false, + this.willNormalizeData = false, + this.gridDrawingSetting, + this.strokeWidth = 2.0, + this.reverseSetting, + this.dataMultiplier = 1.0, + @required this.dataSet, + @required this.centerPoint, + this.onScaleChange, + this.yScaleFactor = 0.03, + this.yTranslateFactor = 0.8, + }); @override _OscilloscopeState createState() => _OscilloscopeState(); @@ -83,10 +88,19 @@ class _OscilloscopeState extends State { double yMin; double yMax; List normaliedDataSet; - ScrollController _scrollController = ScrollController(keepScrollOffset: true); + ScrollController _scrollController = + ScrollController(keepScrollOffset: true); GlobalKey _widgetKey = GlobalKey(); + bool reverse = false; + double yOffset = 0.0; + double centerPoint; + + ReverseSetting get reverseSetting => + widget.reverseSetting ?? ReverseSetting(); + bool get isReversable => reverseSetting.reversable == true; + @override void dispose() { _scrollController.dispose(); @@ -96,22 +110,48 @@ class _OscilloscopeState extends State { @override void initState() { super.initState(); + centerPoint = widget.centerPoint; if (widget.willNormalizeData) { yMax = 1; yMin = 0; yRange = 1; - }else{ + } else { yMax = widget.yAxisMax; yMin = widget.yAxisMin; yRange = yMax - yMin; } + if (reverseSetting.initialReversed) { + notifyReverseChangeIfNeeded(false); + } } - void notifyScaleChangeIfNeeded(){ - if(widget.onScaleChange == null){ - return; + void notifyReverseChangeIfNeeded(bool notifyChange) { + double yMinTemp = yMin; + yMin = (-1) * yMax; + yMax = (-1) * yMinTemp; + centerPoint = (-1) * centerPoint; + reverse = !reverse; + if (notifyChange && reverseSetting.onReverseChange != null) { + reverseSetting.onReverseChange(reverse); + } + } + + void notifyScaleChangeIfNeeded() { + if (widget.onScaleChange != null) { + widget.onScaleChange(xZoomFactor, yZoomFactor); } - widget.onScaleChange(xZoomFactor,yZoomFactor); + } + + Offset lastOffset; + bool yDeltaCheck(Offset off) { + bool delta; + if (((lastOffset?.dy ?? 0.0) - off.dy) != 0) { + delta = ((lastOffset?.dy ?? 0.0) - off.dy) > 0 ? true : false; + } + setState(() { + lastOffset = off; + }); + return delta; } @override @@ -120,141 +160,199 @@ class _OscilloscopeState extends State { normalizeDataIfNeeded(); updateYRangeIfNeeded(); return GestureDetector( - onVerticalDragUpdate: (details){ + onLongPressMoveUpdate: (details) { + setState(() { + bool delta = yDeltaCheck(details.offsetFromOrigin); + double tranlateValue = 0.0; + if (delta != null) { + tranlateValue = delta + ? widget.yTranslateFactor + : -widget.yTranslateFactor; + } + yOffset = yOffset + tranlateValue; + }); + notifyScaleChangeIfNeeded(); + }, + onVerticalDragUpdate: (details) { if (widget.isZoomable) { setState(() { - yZoomFactor = yZoomFactor + ((details.primaryDelta >= 0) ? 0.05 : -0.05); + yZoomFactor = yZoomFactor + + ((details.primaryDelta >= 0) + ? widget.yScaleFactor + : -widget.yScaleFactor); + yZoomFactor = + yZoomFactor <= 0 ? widget.yScaleFactor : yZoomFactor; }); notifyScaleChangeIfNeeded(); } }, - onScaleStart: (state){ + onScaleStart: (state) { prevXValue = xZoomFactor; -// prevYValue = yZoomFactor; }, - onScaleUpdate: (state){ + onScaleUpdate: (ScaleUpdateDetails state) { if (widget.isZoomable) { setState(() { xZoomFactor = prevXValue * state.horizontalScale; -// yZoomFactor = prevYValue * state.verticalScale; + if (xZoomFactor <= 0) xZoomFactor = 0.0000001; }); notifyScaleChangeIfNeeded(); } }, - onScaleEnd: (_){ + onScaleEnd: (_) { prevXValue = null; prevYValue = null; }, child: Stack( - children: [SingleChildScrollView( - key: _widgetKey, - physics: ClampingScrollPhysics(), - controller: _scrollController, - scrollDirection: Axis.horizontal, - child: SizedBox( - width: getWidth(context), - child: Container( - padding: EdgeInsets.all(widget.padding), - width: double.infinity, - height: double.infinity, - color: widget.backgroundColor, - child: ClipRect( - child: CustomPaint( - painter: _TracePainter( - showYAxis: widget.showYAxis, - yAxisColor: widget.yAxisColor, + children: [ + SingleChildScrollView( + key: _widgetKey, + physics: ClampingScrollPhysics(), + controller: _scrollController, + scrollDirection: Axis.horizontal, + child: SizedBox( + width: getWidth(context), + child: Container( + padding: EdgeInsets.all(widget.padding), + width: double.infinity, + height: double.infinity, + color: widget.backgroundColor, + child: ClipRect( + child: CustomPaint( + painter: _TracePainter( dataSet: normaliedDataSet, traceColor: widget.traceColor, yMin: yMin, + yMax: yMax, yRange: yRange, + centerValue: centerPoint, xScale: widget.xScale * xZoomFactor, isScrollable: widget.isScrollable, gridDrawingSetting: widget.gridDrawingSetting, - strokeWidth: widget.strokeWidth + strokeWidth: widget.strokeWidth, + yOffsetValue: yOffset, + yZoomFactor: yZoomFactor, + multiplier: widget.dataMultiplier ?? 1.0, + ), ), ), ), ), ), - ), Positioned( bottom: 0, left: 0, - child: (yZoomFactor != 1.0 || xZoomFactor != 1.0) ? IconButton( - icon: Icon(Icons.refresh, color: widget.traceColor,), - onPressed: (){ - setState(() { - yZoomFactor = 1.0; - xZoomFactor = 1.0; - notifyScaleChangeIfNeeded(); - }); - }, - ) : Container(), - ) + child: (yZoomFactor != 1.0 || xZoomFactor != 1.0) + ? IconButton( + icon: Icon( + Icons.refresh, + color: widget.traceColor, + ), + onPressed: () { + setState(() { + yZoomFactor = 1.0; + xZoomFactor = 1.0; + yOffset = 0.0; + notifyScaleChangeIfNeeded(); + }); + }, + ) + : Container(), + ), + Positioned( + bottom: 0, + right: 0, + child: isReversable + ? IconButton( + icon: Icon(reverseSetting.icon), + onPressed: () { + setState(() { + notifyReverseChangeIfNeeded(true); + normalizeDataIfNeeded(); + }); + }, + ) + : Container(), + ), ], ), ); } - void normalizeDataIfNeeded(){ + void normalizeDataIfNeeded() { if (widget.willNormalizeData) { - normaliedDataSet = getNormalizedData(widget.dataSet); - }else{ - normaliedDataSet = widget.dataSet; + normaliedDataSet = + reverseData(getNormalizedData(widget.dataSet)); + } else { + normaliedDataSet = reverseData(widget.dataSet); } } - List getNormalizedData(List dataSet){ + List reverseData(List dataSet) { + if (reverse && dataSet.length > 0) { + List dataArray = []; + dataSet.forEach((element) { + dataArray.add(element * (-1)); + }); + return dataArray; + } else { + return dataSet; + } + } + + List getNormalizedData(List dataSet) { if (dataSet.length > 0) { List dataArray = []; - double min = dataSet.reduce(Math.min); - double max = dataSet.reduce(Math.max); - for(int i = 0;i 0) { yMin = normaliedDataSet.reduce(Math.min) * 1.1; yMax = normaliedDataSet.reduce(Math.max) * 1.1; - }else{ + } else { yMin = widget.yAxisMin; yMax = widget.yAxisMax; } yMin = yMin; yMax = yMax; - yRange = (yMax - yMin)*yZoomFactor; + yRange = (yMax - yMin) * yZoomFactor; } - void scrollToEndIfNeeded(BuildContext context){ + void scrollToEndIfNeeded(BuildContext context) { if (!widget.isScrollable || context == null) { return; } - WidgetsBinding.instance.addPostFrameCallback((_){ - try{ + WidgetsBinding.instance.addPostFrameCallback((_) { + try { double width = MediaQuery.of(context).size.width; - if (widget.dataSet.length*widget.xScale > width) { - if (!_scrollController.position.isScrollingNotifier.value && _scrollController.offset > _scrollController.position.maxScrollExtent - 100 ) { - _scrollController.jumpTo(_scrollController.position.maxScrollExtent); + if (widget.dataSet.length * widget.xScale > width) { + if (!_scrollController.position.isScrollingNotifier.value && + _scrollController.offset > + _scrollController.position.maxScrollExtent - 100) { + _scrollController + .jumpTo(_scrollController.position.maxScrollExtent); } } - }catch(exception){ + } catch (exception) { print("Got Error from osciloscope: $exception"); } }); } - double getWidth(BuildContext context){ + double getWidth(BuildContext context) { double mediaQueryWidth = MediaQuery.of(context).size.width; double width = mediaQueryWidth * xZoomFactor; if (!widget.isScrollable) { @@ -263,7 +361,9 @@ class _OscilloscopeState extends State { if (widget.dataSet.length == 0) { return width; } - double calculatedWidth = widget.dataSet.length.toDouble()*widget.xScale*xZoomFactor; + double calculatedWidth = widget.dataSet.length.toDouble() * + widget.xScale * + xZoomFactor; if (calculatedWidth <= mediaQueryWidth) { calculatedWidth = mediaQueryWidth; } @@ -276,26 +376,32 @@ class _TracePainter extends CustomPainter { final List dataSet; final double xScale; final double yMin; + final double yMax; final Color traceColor; - final Color yAxisColor; - final bool showYAxis; final double yRange; final bool isScrollable; final GridDrawingSetting gridDrawingSetting; final double strokeWidth; - - _TracePainter( - {this.showYAxis, - this.yAxisColor, - this.yRange, - this.yMin, - this.dataSet, - this.xScale = 1.0, - this.traceColor = Colors.white, - this.isScrollable = false, - this.gridDrawingSetting, - this.strokeWidth = 2.0 - }); + final double yOffsetValue; + final double centerValue; + final double yZoomFactor; + final double multiplier; + + _TracePainter({ + this.yRange, + this.yMin, + this.yMax, + this.dataSet, + this.xScale = 1.0, + this.traceColor = Colors.white, + this.isScrollable = false, + this.gridDrawingSetting, + this.strokeWidth = 2.0, + this.yOffsetValue = 0.0, + this.yZoomFactor = 0.0, + this.centerValue = 0.75, + this.multiplier = 1.0, + }); @override void paint(Canvas canvas, Size size) { @@ -307,11 +413,7 @@ class _TracePainter extends CustomPainter { final axisPaint = Paint() ..strokeWidth = 1.0 - ..color = yAxisColor; - - double yScale = (size.height / yRange); - // only start plot if dataset has data - int length = dataSet.length; + ..color = Colors.blue; //If GRid Drawing is enabled if (gridDrawingSetting != null) { @@ -320,27 +422,28 @@ class _TracePainter extends CustomPainter { ..strokeWidth = gridDrawingSetting.strokeWidth; if (gridDrawingSetting.drawXAxisGrid) { - for (int i = 0;i 0) { - // transform data set to just what we need if bigger than the width(otherwise this would be a memory hog) + double baseY = size.height * 0.5; + double yScale = (size.height / yRange) * multiplier; + if (!isScrollable) { int maxSize = (size.width.toDouble() ~/ xScale) + 1; if (length > maxSize) { @@ -349,21 +452,15 @@ class _TracePainter extends CustomPainter { } } - - // Create Path and set Origin to first data point Path trace = Path(); - trace.moveTo(0.0, size.height - (dataSet[0].toDouble() - yMin) * yScale); - - // generate trace path - for (int p = 0; p < length; p++) { - double plotPoint = - size.height - ((dataSet[p].toDouble() - yMin) * yScale); - if (p == 0) { - } - trace.lineTo(p.toDouble() * (xScale), plotPoint); + trace.moveTo(0, baseY * 0.5); + double x = 0, y = 0; + for (int i = 0; i < length; i++) { + x = i * xScale; + y = (baseY - (dataSet[i].toDouble() - centerValue) * yScale) - + yOffsetValue; + trace.lineTo(x, y); } - - // display the trace canvas.drawPath(trace, tracePaint); } } @@ -372,7 +469,7 @@ class _TracePainter extends CustomPainter { bool shouldRepaint(_TracePainter old) => true; } -class GridDrawingSetting{ +class GridDrawingSetting { final bool drawXAxisGrid; final bool drawYAxisGrid; final int xAxisGridSpace; @@ -380,6 +477,25 @@ class GridDrawingSetting{ final Color gridColor; final double strokeWidth; - GridDrawingSetting(this.drawXAxisGrid, this.drawYAxisGrid, - {this.xAxisGridSpace = 10, this.yAxisGridSpace = 10,this.gridColor = Colors.grey,this.strokeWidth = 0.5}); -} \ No newline at end of file + GridDrawingSetting( + this.drawXAxisGrid, + this.drawYAxisGrid, { + this.xAxisGridSpace = 10, + this.yAxisGridSpace = 10, + this.gridColor = Colors.grey, + this.strokeWidth = 0.5, + }); +} + +class ReverseSetting { + final bool reversable; + final IconData icon; + final bool initialReversed; + final Function(bool value) onReverseChange; + ReverseSetting({ + this.reversable = false, + this.icon = Icons.import_export, + this.initialReversed = false, + this.onReverseChange, + }); +} diff --git a/pubspec.lock b/pubspec.lock index f1918b0..3159873 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,62 +1,55 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: - archive: - dependency: transitive - description: - name: archive - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.13" - args: - dependency: transitive - description: - name: args - url: "https://pub.dartlang.org" - source: hosted - version: "1.6.0" async: dependency: transitive description: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.4.1" + version: "2.5.0-nullsafety.1" boolean_selector: dependency: transitive description: name: boolean_selector url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.1.0-nullsafety.1" + characters: + dependency: transitive + description: + name: characters + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0-nullsafety.3" charcode: dependency: transitive description: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.1.3" - collection: + version: "1.2.0-nullsafety.1" + clock: dependency: transitive description: - name: collection + name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.14.12" - convert: + version: "1.1.0-nullsafety.1" + collection: dependency: transitive description: - name: convert + name: collection url: "https://pub.dartlang.org" source: hosted - version: "2.1.1" - crypto: + version: "1.15.0-nullsafety.3" + fake_async: dependency: transitive description: - name: crypto + name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "2.1.4" + version: "1.2.0-nullsafety.1" flutter: dependency: "direct main" description: flutter @@ -67,48 +60,27 @@ packages: description: flutter source: sdk version: "0.0.0" - image: - dependency: transitive - description: - name: image - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.12" matcher: dependency: transitive description: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.6" + version: "0.12.10-nullsafety.1" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.1.8" + version: "1.3.0-nullsafety.3" path: dependency: transitive description: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.6.4" - petitparser: - dependency: transitive - description: - name: petitparser - url: "https://pub.dartlang.org" - source: hosted - version: "2.4.0" - quiver: - dependency: transitive - description: - name: quiver - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.3" + version: "1.8.0-nullsafety.1" sky_engine: dependency: transitive description: flutter @@ -120,63 +92,56 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.8.0-nullsafety.2" stack_trace: dependency: transitive description: name: stack_trace url: "https://pub.dartlang.org" source: hosted - version: "1.9.3" + version: "1.10.0-nullsafety.1" stream_channel: dependency: transitive description: name: stream_channel url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.1.0-nullsafety.1" string_scanner: dependency: transitive description: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.0.5" + version: "1.1.0-nullsafety.1" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.2.0-nullsafety.1" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.15" + version: "0.2.19-nullsafety.2" typed_data: dependency: transitive description: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.1.6" + version: "1.3.0-nullsafety.3" vector_math: dependency: transitive description: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.0.8" - xml: - dependency: transitive - description: - name: xml - url: "https://pub.dartlang.org" - source: hosted - version: "3.6.1" + version: "2.1.0-nullsafety.3" sdks: - dart: ">=2.6.0 <3.0.0" + dart: ">=2.10.0-110 <2.11.0" flutter: ">=0.1.4 <2.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index 945c724..7db5c2d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: oscilloscope description: A graphical widget that can be used to display a dataset similar to an oscilloscope display -version: 0.4 +version: 0.4.0 author: Steve Rogers homepage: https://github.com/magnatronus/oscilloscope