From 087b34add69673150823ec68aa25f6d400e1bf00 Mon Sep 17 00:00:00 2001 From: ciscoff Date: Sat, 7 Dec 2019 22:26:36 +0300 Subject: [PATCH 1/5] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB?= =?UTF-8?q?=20=D0=BA=D0=B0=D1=81=D1=82=D0=BE=D0=BC=D0=BD=D1=8B=D0=B9=20?= =?UTF-8?q?=D1=8D=D0=BB=D0=B5=D0=BC=D0=B5=D0=BD=D1=82=20GraphView=20=D0=B4?= =?UTF-8?q?=D0=BB=D1=8F=20=D0=BE=D1=82=D1=80=D0=B8=D1=81=D0=BE=D0=B2=D0=BA?= =?UTF-8?q?=D0=B8=20=D0=B3=D1=80=D0=B0=D1=84=D0=B8=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fixdataproto/application/TradingApp.kt | 3 +- .../data/BarMarketDataRepoImpl.kt | 1 + ...etDataImpl.kt => FooMarketDataRepoImpl.kt} | 0 .../{domain => data}/MarketDataHub.kt | 5 +- .../MarketDataProviderImpl.kt | 0 .../presentation/graph/GraphView.kt | 65 +++++++++++++++++++ app/src/main/res/layout/activity_main.xml | 14 +++- app/src/main/res/values/colors.xml | 3 + 8 files changed, 87 insertions(+), 4 deletions(-) rename app/src/main/java/s/yarlykov/fixdataproto/data/{FooMarketDataImpl.kt => FooMarketDataRepoImpl.kt} (100%) rename app/src/main/java/s/yarlykov/fixdataproto/{domain => data}/MarketDataHub.kt (71%) rename app/src/main/java/s/yarlykov/fixdataproto/{domain => data}/MarketDataProviderImpl.kt (100%) create mode 100644 app/src/main/java/s/yarlykov/fixdataproto/presentation/graph/GraphView.kt diff --git a/app/src/main/java/s/yarlykov/fixdataproto/application/TradingApp.kt b/app/src/main/java/s/yarlykov/fixdataproto/application/TradingApp.kt index 4f24893..a27d13a 100644 --- a/app/src/main/java/s/yarlykov/fixdataproto/application/TradingApp.kt +++ b/app/src/main/java/s/yarlykov/fixdataproto/application/TradingApp.kt @@ -4,10 +4,9 @@ import android.app.Application import s.yarlykov.fixdataproto.R import s.yarlykov.fixdataproto.data.BarMarketDataRepoImpl import s.yarlykov.fixdataproto.data.FooMarketDataRepoImpl -import s.yarlykov.fixdataproto.domain.MarketDataHub +import s.yarlykov.fixdataproto.data.MarketDataHub import s.yarlykov.fixdataproto.domain.MarketDataProvider import s.yarlykov.fixdataproto.domain.MarketDataProviderImpl -import s.yarlykov.fixdataproto.domain.MarketDataRepo class TradingApp : Application() { diff --git a/app/src/main/java/s/yarlykov/fixdataproto/data/BarMarketDataRepoImpl.kt b/app/src/main/java/s/yarlykov/fixdataproto/data/BarMarketDataRepoImpl.kt index e7e7e3c..c2e357c 100644 --- a/app/src/main/java/s/yarlykov/fixdataproto/data/BarMarketDataRepoImpl.kt +++ b/app/src/main/java/s/yarlykov/fixdataproto/data/BarMarketDataRepoImpl.kt @@ -12,6 +12,7 @@ private const val PRICE_MIN = 50 private const val PRICE_MAX = 70 class BarMarketDataRepoImpl : MarketDataRepo { + override fun connect(): Observable = Observable .interval(1, TimeUnit.SECONDS, Schedulers.newThread()) diff --git a/app/src/main/java/s/yarlykov/fixdataproto/data/FooMarketDataImpl.kt b/app/src/main/java/s/yarlykov/fixdataproto/data/FooMarketDataRepoImpl.kt similarity index 100% rename from app/src/main/java/s/yarlykov/fixdataproto/data/FooMarketDataImpl.kt rename to app/src/main/java/s/yarlykov/fixdataproto/data/FooMarketDataRepoImpl.kt diff --git a/app/src/main/java/s/yarlykov/fixdataproto/domain/MarketDataHub.kt b/app/src/main/java/s/yarlykov/fixdataproto/data/MarketDataHub.kt similarity index 71% rename from app/src/main/java/s/yarlykov/fixdataproto/domain/MarketDataHub.kt rename to app/src/main/java/s/yarlykov/fixdataproto/data/MarketDataHub.kt index 3fc027b..14ff8f7 100644 --- a/app/src/main/java/s/yarlykov/fixdataproto/domain/MarketDataHub.kt +++ b/app/src/main/java/s/yarlykov/fixdataproto/data/MarketDataHub.kt @@ -1,6 +1,9 @@ -package s.yarlykov.fixdataproto.domain +package s.yarlykov.fixdataproto.data import io.reactivex.Observable +import s.yarlykov.fixdataproto.domain.Granularity +import s.yarlykov.fixdataproto.domain.MarketData +import s.yarlykov.fixdataproto.domain.MarketDataProvider class MarketDataHub(providers: List) { diff --git a/app/src/main/java/s/yarlykov/fixdataproto/domain/MarketDataProviderImpl.kt b/app/src/main/java/s/yarlykov/fixdataproto/data/MarketDataProviderImpl.kt similarity index 100% rename from app/src/main/java/s/yarlykov/fixdataproto/domain/MarketDataProviderImpl.kt rename to app/src/main/java/s/yarlykov/fixdataproto/data/MarketDataProviderImpl.kt diff --git a/app/src/main/java/s/yarlykov/fixdataproto/presentation/graph/GraphView.kt b/app/src/main/java/s/yarlykov/fixdataproto/presentation/graph/GraphView.kt new file mode 100644 index 0000000..588c5f2 --- /dev/null +++ b/app/src/main/java/s/yarlykov/fixdataproto/presentation/graph/GraphView.kt @@ -0,0 +1,65 @@ +package s.yarlykov.fixdataproto.presentation.graph + +import android.content.Context +import android.graphics.* +import android.util.AttributeSet +import android.view.View +import androidx.core.content.res.ResourcesCompat +import s.yarlykov.fixdataproto.R + +class GraphView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : View(context, attrs, defStyleAttr) { + /** + * Все рисование делаем в отдельной битмапе. Потом в onDraw() + * копируем её контент в битмапу нашей View. + * @cacheBitmap + * @cacheCanvas + */ + private lateinit var cacheBitmap: Bitmap + private lateinit var cacheCanvas: Canvas + private var pathAxis = Path() + + private val colorBackground = ResourcesCompat.getColor(resources, R.color.colorBackground, null) + + // Кисть для осей координат и надписей на них + private val paintAxis = Paint(Paint.ANTI_ALIAS_FLAG).apply { + style = Paint.Style.STROKE + textAlign = Paint.Align.CENTER + textSize = 55.0f + typeface = Typeface.create("", Typeface.NORMAL) + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + canvas.drawBitmap(cacheBitmap, 0f, 0f, null) + } + + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { + super.onSizeChanged(w, h, oldw, oldh) + + // Чтобы не было утечки памяти, удалить старую битмапу перед созданием новой + if (::cacheBitmap.isInitialized) cacheBitmap.recycle() + + pathAxis = createAxisPath(w, h) + + cacheBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) + cacheCanvas = Canvas(cacheBitmap) + cacheCanvas.drawColor(colorBackground) + cacheCanvas.drawPath(pathAxis, paintAxis) + } + + private fun createAxisPath(w: Int, h: Int): Path { + + val paddingH = w.toFloat() / 20f + val paddingV = h.toFloat() / 20f + + return Path().apply { + moveTo(paddingH, paddingV) + lineTo(paddingH, h.toFloat() - paddingV) + lineTo(w - paddingH, h.toFloat() - paddingV) + } + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index c878861..0223e50 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -7,6 +7,18 @@ android:layout_height="match_parent" tools:context=".presentation.MainActivity"> + + + + app:layout_constraintTop_toBottomOf="@id/graph" > #008577 #00574B #D81B60 + + #FFC5C5C5 + From 9fae8db2a3868e0e4f2624abe337c4708e98eaa6 Mon Sep 17 00:00:00 2001 From: ciscoff Date: Thu, 12 Dec 2019 08:19:08 +0300 Subject: [PATCH 2/5] =?UTF-8?q?=D0=97=D0=B0=D0=BF=D1=83=D1=81=D1=82=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=B3=D1=80=D0=B0=D1=84=D0=B8=D0=BA.=20=D0=9F=D0=BE?= =?UTF-8?q?=D0=BA=D0=B0=20=D0=BA=D1=80=D0=B8=D0=B2=D0=B5=D0=BD=D1=8C=D0=BA?= =?UTF-8?q?=D0=BE,=20=D0=BD=D0=BE=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0?= =?UTF-8?q?=D0=B5=D1=82=20))?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fixdataproto/application/TradingApp.kt | 2 +- .../data/BarMarketDataRepoImpl.kt | 6 +-- .../data/FooMarketDataRepoImpl.kt | 6 +-- .../data/MarketDataProviderImpl.kt | 8 +++- .../fixdataproto/domain/ChartOptions.kt | 8 ++++ .../fixdataproto/presentation/MainActivity.kt | 13 +++++- .../presentation/graph/ChartView.kt | 9 ++++ .../graph/{GraphView.kt => LineChartView.kt} | 44 +++++++++++++++++-- app/src/main/res/layout/activity_main.xml | 4 +- 9 files changed, 85 insertions(+), 15 deletions(-) create mode 100644 app/src/main/java/s/yarlykov/fixdataproto/domain/ChartOptions.kt create mode 100644 app/src/main/java/s/yarlykov/fixdataproto/presentation/graph/ChartView.kt rename app/src/main/java/s/yarlykov/fixdataproto/presentation/graph/{GraphView.kt => LineChartView.kt} (65%) diff --git a/app/src/main/java/s/yarlykov/fixdataproto/application/TradingApp.kt b/app/src/main/java/s/yarlykov/fixdataproto/application/TradingApp.kt index a27d13a..40024de 100644 --- a/app/src/main/java/s/yarlykov/fixdataproto/application/TradingApp.kt +++ b/app/src/main/java/s/yarlykov/fixdataproto/application/TradingApp.kt @@ -6,7 +6,7 @@ import s.yarlykov.fixdataproto.data.BarMarketDataRepoImpl import s.yarlykov.fixdataproto.data.FooMarketDataRepoImpl import s.yarlykov.fixdataproto.data.MarketDataHub import s.yarlykov.fixdataproto.domain.MarketDataProvider -import s.yarlykov.fixdataproto.domain.MarketDataProviderImpl +import s.yarlykov.fixdataproto.data.MarketDataProviderImpl class TradingApp : Application() { diff --git a/app/src/main/java/s/yarlykov/fixdataproto/data/BarMarketDataRepoImpl.kt b/app/src/main/java/s/yarlykov/fixdataproto/data/BarMarketDataRepoImpl.kt index c2e357c..255a476 100644 --- a/app/src/main/java/s/yarlykov/fixdataproto/data/BarMarketDataRepoImpl.kt +++ b/app/src/main/java/s/yarlykov/fixdataproto/data/BarMarketDataRepoImpl.kt @@ -8,8 +8,8 @@ import s.yarlykov.fixdataproto.logIt import java.util.concurrent.TimeUnit import kotlin.random.Random -private const val PRICE_MIN = 50 -private const val PRICE_MAX = 70 +const val BAR_PRICE_MIN = 50 +const val BAR_PRICE_MAX = 70 class BarMarketDataRepoImpl : MarketDataRepo { @@ -17,7 +17,7 @@ class BarMarketDataRepoImpl : MarketDataRepo { Observable .interval(1, TimeUnit.SECONDS, Schedulers.newThread()) .map { - MarketData(Random.nextInt(PRICE_MIN, PRICE_MAX)) + MarketData(Random.nextInt(BAR_PRICE_MIN, BAR_PRICE_MAX)) } .doOnNext { logIt("${it.value} in ${it.time}") diff --git a/app/src/main/java/s/yarlykov/fixdataproto/data/FooMarketDataRepoImpl.kt b/app/src/main/java/s/yarlykov/fixdataproto/data/FooMarketDataRepoImpl.kt index 5a7a34f..3c2c9fd 100644 --- a/app/src/main/java/s/yarlykov/fixdataproto/data/FooMarketDataRepoImpl.kt +++ b/app/src/main/java/s/yarlykov/fixdataproto/data/FooMarketDataRepoImpl.kt @@ -8,8 +8,8 @@ import s.yarlykov.fixdataproto.logIt import java.util.concurrent.TimeUnit import kotlin.random.Random -private const val PRICE_MIN = 1 -private const val PRICE_MAX = 20 +const val FOO_PRICE_MIN = 1 +const val FOO_PRICE_MAX = 20 class FooMarketDataRepoImpl : MarketDataRepo { @@ -17,7 +17,7 @@ class FooMarketDataRepoImpl : MarketDataRepo { Observable .interval(1, TimeUnit.SECONDS, Schedulers.newThread()) .map { - MarketData(Random.nextInt(PRICE_MIN, PRICE_MAX)) + MarketData(Random.nextInt(FOO_PRICE_MIN, FOO_PRICE_MAX)) } .doOnNext { logIt("${it.value} in ${it.time}") diff --git a/app/src/main/java/s/yarlykov/fixdataproto/data/MarketDataProviderImpl.kt b/app/src/main/java/s/yarlykov/fixdataproto/data/MarketDataProviderImpl.kt index a622b49..a3e6b49 100644 --- a/app/src/main/java/s/yarlykov/fixdataproto/data/MarketDataProviderImpl.kt +++ b/app/src/main/java/s/yarlykov/fixdataproto/data/MarketDataProviderImpl.kt @@ -1,9 +1,13 @@ -package s.yarlykov.fixdataproto.domain +package s.yarlykov.fixdataproto.data import io.reactivex.Observable import io.reactivex.Observer import io.reactivex.disposables.Disposable import io.reactivex.subjects.BehaviorSubject +import s.yarlykov.fixdataproto.domain.Granularity +import s.yarlykov.fixdataproto.domain.MarketData +import s.yarlykov.fixdataproto.domain.MarketDataProvider +import s.yarlykov.fixdataproto.domain.MarketDataRepo /** * Класс реализует двусвязный список на массиве. Массив удобен для первичной инииализации. @@ -38,7 +42,7 @@ class MarketDataProviderImpl( override fun onNext(fixData: MarketData) { head.marketData = fixData head = head.next!! - aggregatedDataStream.onNext(collectAscent()) + aggregatedDataStream.onNext(collectDescent()) } override fun onError(e: Throwable) { diff --git a/app/src/main/java/s/yarlykov/fixdataproto/domain/ChartOptions.kt b/app/src/main/java/s/yarlykov/fixdataproto/domain/ChartOptions.kt new file mode 100644 index 0000000..a70be3e --- /dev/null +++ b/app/src/main/java/s/yarlykov/fixdataproto/domain/ChartOptions.kt @@ -0,0 +1,8 @@ +package s.yarlykov.fixdataproto.domain + +data class ChartOptions( + val title : String, + val axisX : String, + val axisY: String, + val min : Int, + val max : Int) \ No newline at end of file diff --git a/app/src/main/java/s/yarlykov/fixdataproto/presentation/MainActivity.kt b/app/src/main/java/s/yarlykov/fixdataproto/presentation/MainActivity.kt index f128a9e..b0fcb83 100644 --- a/app/src/main/java/s/yarlykov/fixdataproto/presentation/MainActivity.kt +++ b/app/src/main/java/s/yarlykov/fixdataproto/presentation/MainActivity.kt @@ -7,6 +7,9 @@ import io.reactivex.disposables.CompositeDisposable import kotlinx.android.synthetic.main.activity_main.* import s.yarlykov.fixdataproto.R import s.yarlykov.fixdataproto.application.TradingApp +import s.yarlykov.fixdataproto.data.FOO_PRICE_MAX +import s.yarlykov.fixdataproto.data.FOO_PRICE_MIN +import s.yarlykov.fixdataproto.domain.ChartOptions import s.yarlykov.fixdataproto.domain.MarketData import java.text.SimpleDateFormat import java.util.* @@ -25,6 +28,14 @@ class MainActivity : AppCompatActivity() { val hub = (application as TradingApp).getHub() + graph.setChartOptions(ChartOptions( + getString(R.string.foo), + "x", + "y", + FOO_PRICE_MIN, + FOO_PRICE_MAX + )) + disposable.add( hub .marketDataStream(getString(R.string.foo)) @@ -32,6 +43,7 @@ class MainActivity : AppCompatActivity() { .subscribe { val message = it.print() tvFoo.text = message + graph.update(it) } ) @@ -55,7 +67,6 @@ class MainActivity : AppCompatActivity() { val sdf = SimpleDateFormat("ss", Locale.getDefault()) - val li = mutableListOf() this.forEach {md -> if(md.value > 0) { diff --git a/app/src/main/java/s/yarlykov/fixdataproto/presentation/graph/ChartView.kt b/app/src/main/java/s/yarlykov/fixdataproto/presentation/graph/ChartView.kt new file mode 100644 index 0000000..56f8468 --- /dev/null +++ b/app/src/main/java/s/yarlykov/fixdataproto/presentation/graph/ChartView.kt @@ -0,0 +1,9 @@ +package s.yarlykov.fixdataproto.presentation.graph + +import s.yarlykov.fixdataproto.domain.ChartOptions +import s.yarlykov.fixdataproto.domain.MarketData + +interface ChartView { + fun setChartOptions(options : ChartOptions) + fun update(data : List) +} \ No newline at end of file diff --git a/app/src/main/java/s/yarlykov/fixdataproto/presentation/graph/GraphView.kt b/app/src/main/java/s/yarlykov/fixdataproto/presentation/graph/LineChartView.kt similarity index 65% rename from app/src/main/java/s/yarlykov/fixdataproto/presentation/graph/GraphView.kt rename to app/src/main/java/s/yarlykov/fixdataproto/presentation/graph/LineChartView.kt index 588c5f2..f6520f9 100644 --- a/app/src/main/java/s/yarlykov/fixdataproto/presentation/graph/GraphView.kt +++ b/app/src/main/java/s/yarlykov/fixdataproto/presentation/graph/LineChartView.kt @@ -6,12 +6,14 @@ import android.util.AttributeSet import android.view.View import androidx.core.content.res.ResourcesCompat import s.yarlykov.fixdataproto.R +import s.yarlykov.fixdataproto.domain.ChartOptions +import s.yarlykov.fixdataproto.domain.MarketData -class GraphView @JvmOverloads constructor( +class LineChartView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 -) : View(context, attrs, defStyleAttr) { +) : View(context, attrs, defStyleAttr), ChartView { /** * Все рисование делаем в отдельной битмапе. Потом в onDraw() * копируем её контент в битмапу нашей View. @@ -20,6 +22,7 @@ class GraphView @JvmOverloads constructor( */ private lateinit var cacheBitmap: Bitmap private lateinit var cacheCanvas: Canvas + private lateinit var options: ChartOptions private var pathAxis = Path() private val colorBackground = ResourcesCompat.getColor(resources, R.color.colorBackground, null) @@ -48,7 +51,42 @@ class GraphView @JvmOverloads constructor( cacheBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) cacheCanvas = Canvas(cacheBitmap) cacheCanvas.drawColor(colorBackground) - cacheCanvas.drawPath(pathAxis, paintAxis) +// cacheCanvas.drawPath(pathAxis, paintAxis) + + if (::options.isInitialized) { + decorateChart() + } + } + + override fun setChartOptions(options: ChartOptions) { + this.options = options + } + + override fun update(data: List) { + + val xStep = width / data.size + val yStep = height / (options.max - options.min) + val yBase = options.min + + val path = Path() + path.moveTo(0f, 0f) + + data.withIndex().forEach { d -> + path.lineTo((xStep * d.index).toFloat(), height - ((d.value.value - yBase) * yStep).toFloat()) + } + + cacheCanvas.drawColor(colorBackground) + cacheCanvas.drawPath(path, paintAxis) + + path.reset() + invalidate() + } + + /** + * Надписи вдоль осей координат + */ + private fun decorateChart() { + } private fun createAxisPath(w: Int, h: Int): Path { diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 0223e50..24b1691 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -7,7 +7,7 @@ android:layout_height="match_parent" tools:context=".presentation.MainActivity"> - - + Date: Thu, 12 Dec 2019 16:38:15 +0300 Subject: [PATCH 3/5] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB?= =?UTF-8?q?=20=D0=BC=D0=B0=D1=80=D0=BA=D0=B5=D1=80=20=D0=BA=20=D0=BA=D0=B0?= =?UTF-8?q?=D0=B6=D0=B4=D0=BE=D0=B9=20=D0=BA=D0=BE=D1=82=D0=B8=D1=80=D0=BE?= =?UTF-8?q?=D0=B2=D0=BA=D0=B5,=20=D1=87=D1=82=D0=BE=D0=B1=D1=8B=20=D0=B8?= =?UTF-8?q?=D0=BC=D0=B5=D1=82=D1=8C=20=D0=B2=D0=BE=D0=B7=D0=BC=D0=BE=D0=B6?= =?UTF-8?q?=D0=BD=D0=BE=D1=81=D1=82=D1=8C=20=D0=BC=D0=B0=D1=81=D1=88=D1=82?= =?UTF-8?q?=D0=B0=D0=B1=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D1=82=D1=8C=20=D1=80?= =?UTF-8?q?=D0=B8=D1=81=D1=83=D0=BD=D0=BE=D0=BA=20=D0=BD=D0=B0=20=D0=B3?= =?UTF-8?q?=D1=80=D0=B0=D1=84=D0=B8=D0=BA=D0=B5=20=D1=81=20=D1=88=D0=B0?= =?UTF-8?q?=D0=B3=D0=BE=D0=BC=20=D0=BC=D0=B8=D0=BD=D1=83=D1=82=D0=B0,=20?= =?UTF-8?q?=D1=87=D0=B0=D1=81=20=D0=B4=D0=B5=D0=BD=D1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/s/yarlykov/fixdataproto/Utils.kt | 3 +- .../fixdataproto/application/TradingApp.kt | 6 +- .../data/BarMarketDataRepoImpl.kt | 11 ++-- .../data/FooMarketDataRepoImpl.kt | 7 ++- .../data/MarketDataProviderImpl.kt | 15 +++-- .../fixdataproto/domain/MarketData.kt | 7 ++- .../fixdataproto/domain/MarketDataRepo.kt | 8 ++- .../fixdataproto/domain/time/TimeEvent.kt | 8 +++ .../domain/time/TimeLineHandler.kt | 57 +++++++++++++++++++ .../domain/time/TimeLineMarker.kt | 3 + .../fixdataproto/presentation/MainActivity.kt | 2 +- .../{graph => chart}/ChartView.kt | 2 +- .../{graph => chart}/LineChartView.kt | 17 +++++- app/src/main/res/layout/activity_main.xml | 4 +- 14 files changed, 124 insertions(+), 26 deletions(-) create mode 100644 app/src/main/java/s/yarlykov/fixdataproto/domain/time/TimeEvent.kt create mode 100644 app/src/main/java/s/yarlykov/fixdataproto/domain/time/TimeLineHandler.kt create mode 100644 app/src/main/java/s/yarlykov/fixdataproto/domain/time/TimeLineMarker.kt rename app/src/main/java/s/yarlykov/fixdataproto/presentation/{graph => chart}/ChartView.kt (80%) rename app/src/main/java/s/yarlykov/fixdataproto/presentation/{graph => chart}/LineChartView.kt (87%) diff --git a/app/src/main/java/s/yarlykov/fixdataproto/Utils.kt b/app/src/main/java/s/yarlykov/fixdataproto/Utils.kt index d15f8da..c66378b 100644 --- a/app/src/main/java/s/yarlykov/fixdataproto/Utils.kt +++ b/app/src/main/java/s/yarlykov/fixdataproto/Utils.kt @@ -5,4 +5,5 @@ import android.util.Log fun logIt(message: String, tag: String = "APP_TAG") { Log.i(tag, message) System.out.println("$tag: $message") -} \ No newline at end of file +} + diff --git a/app/src/main/java/s/yarlykov/fixdataproto/application/TradingApp.kt b/app/src/main/java/s/yarlykov/fixdataproto/application/TradingApp.kt index 40024de..c955804 100644 --- a/app/src/main/java/s/yarlykov/fixdataproto/application/TradingApp.kt +++ b/app/src/main/java/s/yarlykov/fixdataproto/application/TradingApp.kt @@ -15,9 +15,11 @@ class TradingApp : Application() { override fun onCreate() { super.onCreate() + val capacity = 30 + val list = listOf( - MarketDataProviderImpl(getString(R.string.foo), FooMarketDataRepoImpl()), - MarketDataProviderImpl(getString(R.string.bar), BarMarketDataRepoImpl()) + MarketDataProviderImpl(getString(R.string.foo), FooMarketDataRepoImpl(), capacity), + MarketDataProviderImpl(getString(R.string.bar), BarMarketDataRepoImpl(), capacity) ) marketDataHub = MarketDataHub(list) diff --git a/app/src/main/java/s/yarlykov/fixdataproto/data/BarMarketDataRepoImpl.kt b/app/src/main/java/s/yarlykov/fixdataproto/data/BarMarketDataRepoImpl.kt index 255a476..022093e 100644 --- a/app/src/main/java/s/yarlykov/fixdataproto/data/BarMarketDataRepoImpl.kt +++ b/app/src/main/java/s/yarlykov/fixdataproto/data/BarMarketDataRepoImpl.kt @@ -4,23 +4,22 @@ import io.reactivex.Observable import io.reactivex.schedulers.Schedulers import s.yarlykov.fixdataproto.domain.MarketData import s.yarlykov.fixdataproto.domain.MarketDataRepo -import s.yarlykov.fixdataproto.logIt import java.util.concurrent.TimeUnit import kotlin.random.Random const val BAR_PRICE_MIN = 50 const val BAR_PRICE_MAX = 70 -class BarMarketDataRepoImpl : MarketDataRepo { +class BarMarketDataRepoImpl : MarketDataRepo() { override fun connect(): Observable = Observable .interval(1, TimeUnit.SECONDS, Schedulers.newThread()) .map { - MarketData(Random.nextInt(BAR_PRICE_MIN, BAR_PRICE_MAX)) - } - .doOnNext { - logIt("${it.value} in ${it.time}") + MarketData( + Random.nextInt(BAR_PRICE_MIN, BAR_PRICE_MAX), + timeLineHandler.getMarker(System.currentTimeMillis()) + ) } .publish() .refCount() diff --git a/app/src/main/java/s/yarlykov/fixdataproto/data/FooMarketDataRepoImpl.kt b/app/src/main/java/s/yarlykov/fixdataproto/data/FooMarketDataRepoImpl.kt index 3c2c9fd..a23cf2d 100644 --- a/app/src/main/java/s/yarlykov/fixdataproto/data/FooMarketDataRepoImpl.kt +++ b/app/src/main/java/s/yarlykov/fixdataproto/data/FooMarketDataRepoImpl.kt @@ -11,16 +11,17 @@ import kotlin.random.Random const val FOO_PRICE_MIN = 1 const val FOO_PRICE_MAX = 20 -class FooMarketDataRepoImpl : MarketDataRepo { +class FooMarketDataRepoImpl : MarketDataRepo() { override fun connect(): Observable = Observable .interval(1, TimeUnit.SECONDS, Schedulers.newThread()) .map { - MarketData(Random.nextInt(FOO_PRICE_MIN, FOO_PRICE_MAX)) + MarketData(Random.nextInt(FOO_PRICE_MIN, FOO_PRICE_MAX), + timeLineHandler.getMarker(System.currentTimeMillis())) } .doOnNext { - logIt("${it.value} in ${it.time}") + logIt("${it.value} in ${it.marker.time}") } .publish() .refCount() diff --git a/app/src/main/java/s/yarlykov/fixdataproto/data/MarketDataProviderImpl.kt b/app/src/main/java/s/yarlykov/fixdataproto/data/MarketDataProviderImpl.kt index a3e6b49..f16dd85 100644 --- a/app/src/main/java/s/yarlykov/fixdataproto/data/MarketDataProviderImpl.kt +++ b/app/src/main/java/s/yarlykov/fixdataproto/data/MarketDataProviderImpl.kt @@ -8,6 +8,8 @@ import s.yarlykov.fixdataproto.domain.Granularity import s.yarlykov.fixdataproto.domain.MarketData import s.yarlykov.fixdataproto.domain.MarketDataProvider import s.yarlykov.fixdataproto.domain.MarketDataRepo +import s.yarlykov.fixdataproto.domain.time.TimeEvent +import s.yarlykov.fixdataproto.domain.time.TimeLineMarker /** * Класс реализует двусвязный список на массиве. Массив удобен для первичной инииализации. @@ -23,7 +25,12 @@ class MarketDataProviderImpl( // Массив для хранения котировок private var history: Array = Array(capacity) { index -> - Link(index, MarketData(0), null, null) + Link( + index, + MarketData(0, TimeLineMarker(0, TimeEvent.SECOND)), + null, + null + ) } // Этот указатель будет передвигаться по кругу и указывать @@ -42,7 +49,7 @@ class MarketDataProviderImpl( override fun onNext(fixData: MarketData) { head.marketData = fixData head = head.next!! - aggregatedDataStream.onNext(collectDescent()) + aggregatedDataStream.onNext(headIsPastTailIsNow()) } override fun onError(e: Throwable) { @@ -82,7 +89,7 @@ class MarketDataProviderImpl( } // Список котировок по убывающей дате - private fun collectDescent(): List { + private fun headIsNowTailIsPast(): List { val list = mutableListOf() @@ -98,7 +105,7 @@ class MarketDataProviderImpl( } // Список котировок по возрастающей дате - private fun collectAscent() : List { + private fun headIsPastTailIsNow(): List { val list = mutableListOf() var item = head diff --git a/app/src/main/java/s/yarlykov/fixdataproto/domain/MarketData.kt b/app/src/main/java/s/yarlykov/fixdataproto/domain/MarketData.kt index 2a9f10e..0254575 100644 --- a/app/src/main/java/s/yarlykov/fixdataproto/domain/MarketData.kt +++ b/app/src/main/java/s/yarlykov/fixdataproto/domain/MarketData.kt @@ -1,3 +1,8 @@ package s.yarlykov.fixdataproto.domain -data class MarketData(val value : Int, val time : Long = System.currentTimeMillis()) \ No newline at end of file +import s.yarlykov.fixdataproto.domain.time.TimeLineMarker + +data class MarketData( + val value: Int, + val marker: TimeLineMarker +) \ No newline at end of file diff --git a/app/src/main/java/s/yarlykov/fixdataproto/domain/MarketDataRepo.kt b/app/src/main/java/s/yarlykov/fixdataproto/domain/MarketDataRepo.kt index ad2eacf..8063532 100644 --- a/app/src/main/java/s/yarlykov/fixdataproto/domain/MarketDataRepo.kt +++ b/app/src/main/java/s/yarlykov/fixdataproto/domain/MarketDataRepo.kt @@ -1,7 +1,11 @@ package s.yarlykov.fixdataproto.domain import io.reactivex.Observable +import s.yarlykov.fixdataproto.domain.time.TimeLineHandler -interface MarketDataRepo { - fun connect() : Observable +abstract class MarketDataRepo { + + val timeLineHandler = TimeLineHandler() + + abstract fun connect(): Observable } \ No newline at end of file diff --git a/app/src/main/java/s/yarlykov/fixdataproto/domain/time/TimeEvent.kt b/app/src/main/java/s/yarlykov/fixdataproto/domain/time/TimeEvent.kt new file mode 100644 index 0000000..1aa4004 --- /dev/null +++ b/app/src/main/java/s/yarlykov/fixdataproto/domain/time/TimeEvent.kt @@ -0,0 +1,8 @@ +package s.yarlykov.fixdataproto.domain.time + +enum class TimeEvent(val value : Int) { + SECOND(1), + MINUTE(60), + HOUR(3600), + DAY(24 * 60 * 60) +} \ No newline at end of file diff --git a/app/src/main/java/s/yarlykov/fixdataproto/domain/time/TimeLineHandler.kt b/app/src/main/java/s/yarlykov/fixdataproto/domain/time/TimeLineHandler.kt new file mode 100644 index 0000000..face7b4 --- /dev/null +++ b/app/src/main/java/s/yarlykov/fixdataproto/domain/time/TimeLineHandler.kt @@ -0,0 +1,57 @@ +package s.yarlykov.fixdataproto.domain.time + +import java.text.SimpleDateFormat +import java.util.* + +class TimeLineHandler(startTime: Long = System.currentTimeMillis()) { + + private val timeFormat = "dd-HH-mm-ss" + + private val day = 0 + private val hour = 1 + private val minute = 2 + + private val parsedStartTime = parseTime(startTime) + + private var lastDay: Int = parsedStartTime[day] + private var lastHour: Int = parsedStartTime[hour] + private var lastMinute: Int = parsedStartTime[minute] + + /** + * Вернуть маркер, который клеится к маркет дате + */ + fun getMarker(time: Long): TimeLineMarker = + TimeLineMarker(time, getEvent(time)) + + /** + * Определить произошел ли переход дня/часа/минуты + * Переход дня подразумевает также переход часа и минуты, + * а переход часа - переход минуты. + */ + private fun getEvent(time: Long): TimeEvent { + val currentTime = parseTime(time) + + return if (currentTime[day] != lastDay) { + lastDay = currentTime[day] + TimeEvent.DAY + } else if (currentTime[hour] != lastHour) { + lastHour = currentTime[hour] + TimeEvent.HOUR + } else if (currentTime[minute] != lastMinute) { + lastMinute = currentTime[minute] + TimeEvent.MINUTE + } else { + TimeEvent.SECOND + } + } + + private fun parseTime(time: Long): List { + val sdf = SimpleDateFormat(timeFormat, Locale.getDefault()) + return sdf + .format(time) + .split("-".toRegex()) + .map { + it.toInt() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/s/yarlykov/fixdataproto/domain/time/TimeLineMarker.kt b/app/src/main/java/s/yarlykov/fixdataproto/domain/time/TimeLineMarker.kt new file mode 100644 index 0000000..057ea40 --- /dev/null +++ b/app/src/main/java/s/yarlykov/fixdataproto/domain/time/TimeLineMarker.kt @@ -0,0 +1,3 @@ +package s.yarlykov.fixdataproto.domain.time + +data class TimeLineMarker(val time: Long, val timeEvent: TimeEvent) \ No newline at end of file diff --git a/app/src/main/java/s/yarlykov/fixdataproto/presentation/MainActivity.kt b/app/src/main/java/s/yarlykov/fixdataproto/presentation/MainActivity.kt index b0fcb83..bc42e8c 100644 --- a/app/src/main/java/s/yarlykov/fixdataproto/presentation/MainActivity.kt +++ b/app/src/main/java/s/yarlykov/fixdataproto/presentation/MainActivity.kt @@ -70,7 +70,7 @@ class MainActivity : AppCompatActivity() { val li = mutableListOf() this.forEach {md -> if(md.value > 0) { - val s = "${"%02d".format(md.value)}: ${sdf.format(md.time)}s" + val s = "${"%02d".format(md.value)}: ${sdf.format(md.marker.time)}s" li.add(s) } } diff --git a/app/src/main/java/s/yarlykov/fixdataproto/presentation/graph/ChartView.kt b/app/src/main/java/s/yarlykov/fixdataproto/presentation/chart/ChartView.kt similarity index 80% rename from app/src/main/java/s/yarlykov/fixdataproto/presentation/graph/ChartView.kt rename to app/src/main/java/s/yarlykov/fixdataproto/presentation/chart/ChartView.kt index 56f8468..3bbbfd8 100644 --- a/app/src/main/java/s/yarlykov/fixdataproto/presentation/graph/ChartView.kt +++ b/app/src/main/java/s/yarlykov/fixdataproto/presentation/chart/ChartView.kt @@ -1,4 +1,4 @@ -package s.yarlykov.fixdataproto.presentation.graph +package s.yarlykov.fixdataproto.presentation.chart import s.yarlykov.fixdataproto.domain.ChartOptions import s.yarlykov.fixdataproto.domain.MarketData diff --git a/app/src/main/java/s/yarlykov/fixdataproto/presentation/graph/LineChartView.kt b/app/src/main/java/s/yarlykov/fixdataproto/presentation/chart/LineChartView.kt similarity index 87% rename from app/src/main/java/s/yarlykov/fixdataproto/presentation/graph/LineChartView.kt rename to app/src/main/java/s/yarlykov/fixdataproto/presentation/chart/LineChartView.kt index f6520f9..0dd902a 100644 --- a/app/src/main/java/s/yarlykov/fixdataproto/presentation/graph/LineChartView.kt +++ b/app/src/main/java/s/yarlykov/fixdataproto/presentation/chart/LineChartView.kt @@ -1,4 +1,4 @@ -package s.yarlykov.fixdataproto.presentation.graph +package s.yarlykov.fixdataproto.presentation.chart import android.content.Context import android.graphics.* @@ -8,6 +8,7 @@ import androidx.core.content.res.ResourcesCompat import s.yarlykov.fixdataproto.R import s.yarlykov.fixdataproto.domain.ChartOptions import s.yarlykov.fixdataproto.domain.MarketData +import s.yarlykov.fixdataproto.domain.time.TimeEvent class LineChartView @JvmOverloads constructor( context: Context, @@ -71,11 +72,21 @@ class LineChartView @JvmOverloads constructor( val path = Path() path.moveTo(0f, 0f) + cacheCanvas.drawColor(colorBackground) + data.withIndex().forEach { d -> - path.lineTo((xStep * d.index).toFloat(), height - ((d.value.value - yBase) * yStep).toFloat()) + + val x = (xStep * d.index).toFloat() + val y = height - ((d.value.value - yBase) * yStep).toFloat() + + path.lineTo(x, y) + + if(d.value.marker.timeEvent == TimeEvent.MINUTE) { + cacheCanvas.drawText("m", x, y, paintAxis) + System.out.println("APP_TAG: minute") + } } - cacheCanvas.drawColor(colorBackground) cacheCanvas.drawPath(path, paintAxis) path.reset() diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 24b1691..e42970b 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -7,7 +7,7 @@ android:layout_height="match_parent" tools:context=".presentation.MainActivity"> - - + Date: Thu, 12 Dec 2019 19:39:37 +0300 Subject: [PATCH 4/5] =?UTF-8?q?=D0=A3=D1=81=D1=82=D0=B0=D0=BD=D0=BE=D0=B2?= =?UTF-8?q?=D0=B8=D0=BB=20=D0=BE=D1=82=D0=B4=D0=B5=D0=BB=D1=8C=D0=BD=D1=83?= =?UTF-8?q?=D1=8E=20=D0=BE=D0=B1=D0=BB=D0=B0=D1=81=D1=82=D1=8C=20=D0=BE?= =?UTF-8?q?=D1=82=D1=80=D0=B8=D1=81=D0=BE=D0=B2=D0=BA=D0=B8=20=D0=B3=D1=80?= =?UTF-8?q?=D0=B0=D1=84=D0=B8=D0=BA=D0=B0=20=D0=B2=D0=BD=D1=83=D1=82=D1=80?= =?UTF-8?q?=D0=B8=20ChartView?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/chart/LineChartView.kt | 80 +++++++++++++++---- app/src/main/res/values/colors.xml | 3 +- 2 files changed, 68 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/s/yarlykov/fixdataproto/presentation/chart/LineChartView.kt b/app/src/main/java/s/yarlykov/fixdataproto/presentation/chart/LineChartView.kt index 0dd902a..4064278 100644 --- a/app/src/main/java/s/yarlykov/fixdataproto/presentation/chart/LineChartView.kt +++ b/app/src/main/java/s/yarlykov/fixdataproto/presentation/chart/LineChartView.kt @@ -15,6 +15,7 @@ class LineChartView @JvmOverloads constructor( attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : View(context, attrs, defStyleAttr), ChartView { + /** * Все рисование делаем в отдельной битмапе. Потом в onDraw() * копируем её контент в битмапу нашей View. @@ -26,7 +27,26 @@ class LineChartView @JvmOverloads constructor( private lateinit var options: ChartOptions private var pathAxis = Path() - private val colorBackground = ResourcesCompat.getColor(resources, R.color.colorBackground, null) + /** + * Paddind'и для области рисования и прямоугольник для рисования, который + * содержит координаты (L,T R,B) + */ + private var chartPaddings = Rect() + private var chartArea = Rect() + + /** + * Цвета рамки и области рисования + */ + private val colorChartFrame = ResourcesCompat.getColor(resources, R.color.colorFrame, null) + private val colorChartArea = ResourcesCompat.getColor(resources, R.color.colorBackground, null) + + /** + * Кисти + */ + private val paintChartArea = Paint(Paint.ANTI_ALIAS_FLAG).apply { + style = Paint.Style.FILL + color = colorChartArea + } // Кисть для осей координат и надписей на них private val paintAxis = Paint(Paint.ANTI_ALIAS_FLAG).apply { @@ -47,11 +67,29 @@ class LineChartView @JvmOverloads constructor( // Чтобы не было утечки памяти, удалить старую битмапу перед созданием новой if (::cacheBitmap.isInitialized) cacheBitmap.recycle() - pathAxis = createAxisPath(w, h) + // Пересчитать отступы области рисования + with(chartPaddings) { + left = w / 10 + top = h / 20 + bottom = h / 10 + right = w / 20 + } + + // Пересчитать координаты области рисования + with(chartArea) { + left = chartPaddings.left + top = chartPaddings.top + bottom = h - chartPaddings.bottom + right = w - chartPaddings.right + } + +// pathAxis = createAxisPath(w, h) - cacheBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) + cacheBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888) cacheCanvas = Canvas(cacheBitmap) - cacheCanvas.drawColor(colorBackground) + + cacheCanvas.drawColor(colorChartFrame) + cacheCanvas.drawRect(chartArea, paintChartArea) // cacheCanvas.drawPath(pathAxis, paintAxis) if (::options.isInitialized) { @@ -65,28 +103,42 @@ class LineChartView @JvmOverloads constructor( override fun update(data: List) { - val xStep = width / data.size - val yStep = height / (options.max - options.min) + val w = chartArea.width() + val h = chartArea.height() + val shiftX = chartArea.left.toFloat() + val shiftY = chartArea.top.toFloat() + + val xStep = w / data.size + val yStep = h / (options.max - options.min) val yBase = options.min val path = Path() - path.moveTo(0f, 0f) + cacheCanvas.drawColor(colorChartArea) - cacheCanvas.drawColor(colorBackground) + var xPrev = 0f + var yPrev = 0f data.withIndex().forEach { d -> - val x = (xStep * d.index).toFloat() - val y = height - ((d.value.value - yBase) * yStep).toFloat() + val x = (xStep * d.index).toFloat() + shiftX + val y = h - ((d.value.value - yBase) * yStep).toFloat() + shiftY - path.lineTo(x, y) + if(d.index != 0) { + path.quadTo(x, y, (x + xPrev)/2, (y + yPrev)/2) - if(d.value.marker.timeEvent == TimeEvent.MINUTE) { - cacheCanvas.drawText("m", x, y, paintAxis) - System.out.println("APP_TAG: minute") + if (d.value.marker.timeEvent == TimeEvent.MINUTE) { + cacheCanvas.drawText("m", x, y, paintAxis) + } + } else { + path.moveTo(x, y) } + + xPrev = x + yPrev = y } + cacheCanvas.drawColor(colorChartFrame) + cacheCanvas.drawRect(chartArea, paintChartArea) cacheCanvas.drawPath(path, paintAxis) path.reset() diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 31f9cf9..bfc02aa 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -4,6 +4,7 @@ #00574B #D81B60 - #FFC5C5C5 + #FF858585 + #FFe5e5e5 From 561c45c6297843c81f73d63f67b0d623abbdb40c Mon Sep 17 00:00:00 2001 From: yagray Date: Thu, 12 Dec 2019 20:08:33 +0300 Subject: [PATCH 5/5] =?UTF-8?q?=D0=93=D1=80=D0=B0=D1=84=D0=B8=D0=BA=20?= =?UTF-8?q?=D0=9E=D0=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/chart/LineChartView.kt | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/s/yarlykov/fixdataproto/presentation/chart/LineChartView.kt b/app/src/main/java/s/yarlykov/fixdataproto/presentation/chart/LineChartView.kt index 4064278..94308a3 100644 --- a/app/src/main/java/s/yarlykov/fixdataproto/presentation/chart/LineChartView.kt +++ b/app/src/main/java/s/yarlykov/fixdataproto/presentation/chart/LineChartView.kt @@ -103,34 +103,37 @@ class LineChartView @JvmOverloads constructor( override fun update(data: List) { + // Предыдущие координаты для отрисовки более плавного перехода + // к новым координатам с помощью path.quadTo() + var xPrev = 0f + var yPrev = 0f + + // Область рисования и её смещения внутри View val w = chartArea.width() val h = chartArea.height() val shiftX = chartArea.left.toFloat() val shiftY = chartArea.top.toFloat() + // Размер "единицы измерения по каждой из осей val xStep = w / data.size val yStep = h / (options.max - options.min) val yBase = options.min - val path = Path() - cacheCanvas.drawColor(colorChartArea) - - var xPrev = 0f - var yPrev = 0f + val pathChart = Path() data.withIndex().forEach { d -> val x = (xStep * d.index).toFloat() + shiftX val y = h - ((d.value.value - yBase) * yStep).toFloat() + shiftY - if(d.index != 0) { - path.quadTo(x, y, (x + xPrev)/2, (y + yPrev)/2) + if (d.index != 0) { + pathChart.quadTo(xPrev, yPrev, (x + xPrev) / 2, (y + yPrev) / 2) if (d.value.marker.timeEvent == TimeEvent.MINUTE) { cacheCanvas.drawText("m", x, y, paintAxis) } } else { - path.moveTo(x, y) + pathChart.moveTo(x, y) } xPrev = x @@ -139,9 +142,9 @@ class LineChartView @JvmOverloads constructor( cacheCanvas.drawColor(colorChartFrame) cacheCanvas.drawRect(chartArea, paintChartArea) - cacheCanvas.drawPath(path, paintAxis) + cacheCanvas.drawPath(pathChart, paintAxis) - path.reset() + pathChart.reset() invalidate() }