SciChart® the market leader in Fast WPF Charts, WPF 3D Charts, iOS Chart, Android Chart and JavaScript Chart Components
SciChart Android ships with ~90Â Android Chart Examples which you can browse, play with, view the source-code and even export each SciChart Android Chart Example to a stand-alone Android Studio project. All of this is possible with the new and improved SciChart Android Examples Suite, which ships as part of our Android Charts SDK.
Demonstrates the speed and power of SciChart in a real-time example. Creates a threadpool timer and pushes 1000 points every 10ms to three series on the chart. The point count quickly rises to the millions of points, and SciChart is still rendering!
Tip!
For best performance use append override which accepts IValues as arguments. This allows to avoid boxing/unboxing overhead when adding large amount of data to draw
The full source code for the Android Chart Realtime Performance example is included below (Scroll down!).
Did you know you can also view the source code from one of the following sources as well?
//******************************************************************************
// SCICHART® Copyright SciChart Ltd. 2011-2021. All rights reserved.
//
// Web: http://www.scichart.com
// Support: support@scichart.com
// Sales: sales@scichart.com
//
// PerformanceDemoFragment.kt is part of SCICHART®, High Performance Scientific Charts
// For full terms and conditions of the license, see http://www.scichart.com/scichart-eula/
//
// This source code is protected by international copyright law. Unauthorized
// reproduction, reverse-engineering, or distribution of all or any portion of
// this source code is strictly prohibited.
//
// This source code contains confidential and proprietary trade secrets of
// SciChart Ltd., and should at no time be copied, transferred, sold,
// distributed or made available without express written permission.
//******************************************************************************
package com.scichart.examples.fragments.examples2d.createRealtimeCharts.kt
import android.os.Bundle
import android.os.Handler
import android.view.View
import android.widget.AdapterView
import android.widget.Spinner
import android.widget.TextView
import com.scichart.charting.model.dataSeries.IXyDataSeries
import com.scichart.charting.visuals.SciChartSurface
import com.scichart.charting.visuals.annotations.AnnotationCoordinateMode
import com.scichart.charting.visuals.axes.AutoRange
import com.scichart.charting.visuals.pointmarkers.CrossPointMarker
import com.scichart.charting.visuals.renderableSeries.FastLineRenderableSeries
import com.scichart.charting.visuals.renderableSeries.FastMountainRenderableSeries
import com.scichart.charting.visuals.renderableSeries.IRenderableSeries
import com.scichart.charting.visuals.renderableSeries.XyScatterRenderableSeries
import com.scichart.core.model.FloatValues
import com.scichart.core.model.IntegerValues
import com.scichart.core.utility.Dispatcher
import com.scichart.data.model.ISciList
import com.scichart.data.numerics.ResamplingMode
import com.scichart.examples.R
import com.scichart.examples.components.SpinnerStringAdapter
import com.scichart.examples.data.MovingAverage
import com.scichart.examples.fragments.base.ExampleSingleChartBaseFragment
import com.scichart.examples.utils.*
import com.scichart.examples.utils.scichartExtensions.*
import com.scichart.examples.utils.widgetgeneration.ImageViewWidget
import com.scichart.examples.utils.widgetgeneration.Widget
import java.util.*
import java.util.concurrent.Executors
import java.util.concurrent.ScheduledFuture
import java.util.concurrent.TimeUnit
import kotlin.math.roundToInt
class PerformanceDemoFragment : ExampleSingleChartBaseFragment() {
private val pointCounts: List<Int> = ArrayList<Int>().apply {
add(10)
add(100)
add(1000)
}
private var selectedSeriesType: String? = null
private var selectedStrokeThickness = 1
private var pointsCount = 100
private var selectedResamplingMode = ResamplingMode.Auto
private val random = Random()
private var maLow = MovingAverage(MA_LOW)
private var maHigh = MovingAverage(MA_HIGH)
private val scheduledExecutorService = Executors.newSingleThreadScheduledExecutor()
private lateinit var schedule: ScheduledFuture<*>
@Volatile
private var isRunning = true
private var textView: TextView? = null
override fun initExample(surface: SciChartSurface) {
selectedSeriesType = resources.getStringArray(R.array.series_types)[0]
initChart()
// disable modifiers when updating in real-time
updateModifiers(false)
schedule = scheduledExecutorService.scheduleWithFixedDelay(appendDataRunnable, 0, TIME_INTERVAL, TimeUnit.MILLISECONDS)
}
private fun initChart() {
textView = TextView(activity).apply { setPadding(20, 20, 20, 20) }
binding.surface.suspendUpdates {
xAxes { numericAxis { autoRange = AutoRange.Always } }
yAxes { numericAxis { autoRange = AutoRange.Always } }
renderableSeries {
fastLineRenderableSeries { dataSeries = XyDataSeries<Int, Float>(); strokeStyle = SolidPenStyle(0xFF4083B7) }
fastLineRenderableSeries { dataSeries = XyDataSeries<Int, Float>(); strokeStyle = SolidPenStyle(0xFFFFA500) }
fastLineRenderableSeries { dataSeries = XyDataSeries<Int, Float>(); strokeStyle = SolidPenStyle(0xFFE13219) }
}
chartModifiers { defaultModifiers() }
annotations {
customAnnotation {
x1 = 0; y1 = 0
coordinateMode = AnnotationCoordinateMode.Relative
setContentView(textView)
zIndex = -1
}
}
}
}
private val appendDataRunnable: Runnable = object : Runnable {
private val xValues = IntegerValues(pointsCount)
private val firstYValues = FloatValues(pointsCount)
private val secondYValues = FloatValues(pointsCount)
private val thirdYValues = FloatValues(pointsCount)
override fun run() {
val maxPointsCount = calcMaxPointCountToDisplay()
if (!isRunning || getPointsCount() > maxPointsCount) return
xValues.clear()
firstYValues.clear()
secondYValues.clear()
thirdYValues.clear()
binding.surface.suspendUpdates {
val mainSeries = renderableSeries[0].dataSeries as IXyDataSeries<Int, Float>
val maLowSeries = renderableSeries[1].dataSeries as IXyDataSeries<Int, Float>
val maHighSeries = renderableSeries[2].dataSeries as IXyDataSeries<Int, Float>
var xValue = if (mainSeries.count > 0) mainSeries.xValues[mainSeries.count - 1] else 0
var yValue: Float = if (mainSeries.count > 0) mainSeries.yValues[mainSeries.count - 1] else 10f
for (i in 0 until pointsCount) {
xValue++
yValue += random.nextFloat() - 0.5f
xValues.add(xValue)
firstYValues.add(yValue)
secondYValues.add(maLow.push(yValue.toDouble()).current.toFloat())
thirdYValues.add(maHigh.push(yValue.toDouble()).current.toFloat())
}
mainSeries.append(xValues, firstYValues)
maLowSeries.append(xValues, secondYValues)
maHighSeries.append(xValues, thirdYValues)
val count = (mainSeries.count + maLowSeries.count + maHighSeries.count).toLong()
val text = "Amount of points: $count"
Dispatcher.postOnUiThread { textView!!.text = text }
}
}
}
override fun getToolbarItems(): List<Widget> = ArrayList<Widget>().apply {
add(ImageViewWidget.Builder().setId(R.drawable.example_toolbar_play).setListener { updateRunningState(true) }.build() )
add(ImageViewWidget.Builder().setId(R.drawable.example_toolbar_pause).setListener { updateRunningState(false) }.build() )
add(ImageViewWidget.Builder().setId(R.drawable.example_toolbar_stop).setListener {
updateRunningState(false)
resetChart()
}.build())
add(ImageViewWidget.Builder().setId(R.drawable.example_toolbar_settings).setListener { openSettingsDialog() }.build() )
}
private fun updateRunningState(isRunning: Boolean) {
this.isRunning = isRunning
updateAutoRangeBehavior(isRunning)
updateModifiers(!isRunning)
}
private fun updateAutoRangeBehavior(isEnabled: Boolean) {
binding.surface.run {
val autoRangeMode = if (isEnabled) AutoRange.Always else AutoRange.Never
xAxes.first().autoRange = autoRangeMode
yAxes.first().autoRange = autoRangeMode
}
}
private fun updateModifiers(isEnabled: Boolean) {
binding.surface.chartModifiers.forEach { it.isEnabled = isEnabled }
}
private fun openSettingsDialog() {
val dialog = ViewSettingsUtil.createSettingsPopup(activity, R.layout.example_performance_demo_popup_layout)
val context = dialog.context
with(dialog.findViewById<View>(R.id.series_types_spinner) as Spinner) {
val seriesTypesAdapter = SpinnerStringAdapter(context, R.array.series_types)
adapter = seriesTypesAdapter
setSelection(seriesTypesAdapter.getPosition(selectedSeriesType))
onItemSelectedListener = object : ItemSelectedListenerBase() {
override fun onItemSelected(parent: AdapterView<*>?, view: View, position: Int, id: Long) {
selectedSeriesType = seriesTypesAdapter.getItem(position)
onChangeSeriesType()
}
}
}
with(dialog.findViewById<View>(R.id.stroke_spinner) as Spinner) {
val strokeAdapter = SpinnerStringAdapter(context, R.array.stroke)
adapter = strokeAdapter
setSelection(strokeAdapter.getPosition(selectedStrokeThickness.toString()))
onItemSelectedListener = object : ItemSelectedListenerBase() {
override fun onItemSelected(parent: AdapterView<*>?, view: View, position: Int, id: Long) {
selectedStrokeThickness = strokeAdapter.getItem(position)!!.toInt()
onChangeStroke()
}
}
}
with(dialog.findViewById<View>(R.id.points_spinner) as Spinner) {
val pointsAdapter = SpinnerStringAdapter(context, R.array.points)
adapter = pointsAdapter
setSelection(pointsAdapter.getPosition(resources.getStringArray(R.array.points)[pointCounts.indexOf(pointsCount)]))
onItemSelectedListener = object : ItemSelectedListenerBase() {
override fun onItemSelected(parent: AdapterView<*>?, view: View, position: Int, id: Long) {
pointsCount = pointCounts[position]
onChangeStroke()
}
}
}
with(dialog.findViewById<View>(R.id.resampling_mode_spinner) as Spinner) {
val resamplingModeAdapter = SpinnerStringAdapter(context, R.array.resampling_mode)
adapter = resamplingModeAdapter
setSelection(resamplingModeAdapter.getPosition(selectedResamplingMode.toString()))
onItemSelectedListener = object : ItemSelectedListenerBase() {
override fun onItemSelected(parent: AdapterView<*>?, view: View, position: Int, id: Long) {
selectedResamplingMode = ResamplingMode.valueOf(resamplingModeAdapter.getItem(position)!!)
onResamplingMode()
}
}
}
dialog.show()
}
private fun onChangeSeriesType() {
binding.surface.suspendUpdates {
for (i in renderableSeries.indices) {
renderableSeries[i] = changeSeriesType(selectedSeriesType!!, renderableSeries[i])
}
}
}
private fun changeSeriesType(param: String, initSeries: IRenderableSeries): IRenderableSeries? {
when (param) {
resources.getStringArray(R.array.series_types)[0] -> {
return FastLineRenderableSeries().apply {
dataSeries = initSeries.dataSeries
strokeStyle = initSeries.strokeStyle
resamplingMode = initSeries.resamplingMode
}
}
resources.getStringArray(R.array.series_types)[1] -> {
return FastMountainRenderableSeries().apply {
dataSeries = initSeries.dataSeries
strokeStyle = initSeries.strokeStyle
resamplingMode = initSeries.resamplingMode
areaStyle = SolidBrushStyle(initSeries.strokeStyle.color)
}
}
resources.getStringArray(R.array.series_types)[2] -> {
return XyScatterRenderableSeries().apply {
dataSeries = initSeries.dataSeries
strokeStyle = initSeries.strokeStyle
resamplingMode = initSeries.resamplingMode
crossPointMarker {
setSize(20)
strokeStyle = initSeries.strokeStyle
}
}
}
else -> return null
}
}
private fun onChangeStroke() {
binding.surface.suspendUpdates {
renderableSeries.forEach {
val currentStyle = it.strokeStyle
it.strokeStyle = SolidPenStyle(
currentStyle.color,
currentStyle.thickness,
currentStyle.antiAliasing
)
}
}
}
private fun onResamplingMode() {
binding.surface.suspendUpdates {
renderableSeries.forEach { it.resamplingMode = selectedResamplingMode }
}
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
savedInstanceState?.run {
val surface = binding.surface
val seriesCount: Int = getInt("seriesCount")
for (i in 0 until seriesCount) {
surface.renderableSeries[i].dataSeries = XyDataSeries<Int, Float>().apply {
val xValues: ISciList<Int> = getParcelable("xValues$i")!!
val yValues: ISciList<Float> = getParcelable("yValues$i")!!
append(xValues, yValues)
}
}
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
isRunning = false
outState.run {
val surface = binding.surface
val size = surface.renderableSeries.size
putInt("seriesCount", size)
for (i in 0 until size) {
val series = surface.renderableSeries[i].dataSeries as IXyDataSeries<Int, Float>
putParcelable("xValues$i", series.xValues)
putParcelable("yValues$i", series.yValues)
}
}
}
override fun onDestroyView() {
super.onDestroyView()
schedule.cancel(true)
}
private fun resetChart() {
Handler().postDelayed({
binding.surface.suspendUpdates {
for (i in renderableSeries.indices) {
renderableSeries[i].dataSeries.clear()
}
}
}, 100)
maLow = MovingAverage(MA_LOW)
maHigh = MovingAverage(MA_HIGH)
}
private fun calcMaxPointCountToDisplay(): Int {
// for resampling None need
// 8 mb for data series (float, int)
// 16 mb for resampling (double, double)
// 4 mb for data indices (int)
// 8 mb for coordinates (float, float)
val oneMlnPointsRequirement = (8 + 16 + 4 + 8).toFloat()
// need to reserve some memory for other needs ( 40 mb should be enough )
val memorySize = (getMaxMemorySize() - 40).toFloat()
// max amount of point on screen
val maxPointCount = memorySize / oneMlnPointsRequirement * 1000000
// we have 3 series in example
return (maxPointCount / 3).roundToInt()
}
private fun getMaxMemorySize(): Int {
// max memory size in megabytes
return (Runtime.getRuntime().maxMemory() / 1024L / 1024L).toInt()
}
private fun getPointsCount(): Int {
var result = 0
binding.surface.renderableSeries.forEach { result += it.dataSeries.count }
return result
}
companion object {
private const val MA_LOW = 200
private const val MA_HIGH = 1000
private const val TIME_INTERVAL: Long = 10
}
}
//******************************************************************************
// SCICHART® Copyright SciChart Ltd. 2011-2021. All rights reserved.
//
// Web: http://www.scichart.com
// Support: support@scichart.com
// Sales: sales@scichart.com
//
// PerformanceDemoFragment.java is part of SCICHART®, High Performance Scientific Charts
// For full terms and conditions of the license, see http://www.scichart.com/scichart-eula/
//
// This source code is protected by international copyright law. Unauthorized
// reproduction, reverse-engineering, or distribution of all or any portion of
// this source code is strictly prohibited.
//
// This source code contains confidential and proprietary trade secrets of
// SciChart Ltd., and should at no time be copied, transferred, sold,
// distributed or made available without express written permission.
//******************************************************************************
package com.scichart.examples.fragments.examples2d.createRealtimeCharts;
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Spinner;
import android.widget.TextView;
import androidx.annotation.NonNull;
import com.scichart.charting.model.ChartModifierCollection;
import com.scichart.charting.model.RenderableSeriesCollection;
import com.scichart.charting.model.dataSeries.IXyDataSeries;
import com.scichart.charting.model.dataSeries.XyDataSeries;
import com.scichart.charting.modifiers.IChartModifier;
import com.scichart.charting.visuals.SciChartSurface;
import com.scichart.charting.visuals.annotations.AnnotationCoordinateMode;
import com.scichart.charting.visuals.annotations.CustomAnnotation;
import com.scichart.charting.visuals.axes.AutoRange;
import com.scichart.charting.visuals.axes.IAxis;
import com.scichart.charting.visuals.pointmarkers.CrossPointMarker;
import com.scichart.charting.visuals.renderableSeries.IRenderableSeries;
import com.scichart.core.framework.UpdateSuspender;
import com.scichart.core.model.FloatValues;
import com.scichart.core.model.IntegerValues;
import com.scichart.core.utility.Dispatcher;
import com.scichart.data.model.ISciList;
import com.scichart.data.numerics.ResamplingMode;
import com.scichart.drawing.common.PenStyle;
import com.scichart.examples.R;
import com.scichart.examples.components.SpinnerStringAdapter;
import com.scichart.examples.data.MovingAverage;
import com.scichart.examples.fragments.base.ExampleSingleChartBaseFragment;
import com.scichart.examples.utils.ItemSelectedListenerBase;
import com.scichart.examples.utils.ViewSettingsUtil;
import com.scichart.examples.utils.widgetgeneration.ImageViewWidget;
import com.scichart.examples.utils.widgetgeneration.Widget;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
public class PerformanceDemoFragment extends ExampleSingleChartBaseFragment {
private final List<Integer> pointCounts = new ArrayList<Integer>() {{
add(10);
add(100);
add(1000);
}};
private final static int MA_LOW = 200;
private final static int MA_HIGH = 1000;
private final static long TIME_INTERVAL = 10;
private String selectedSeriesType;
private int selectedStrokeThickness = 1;
private int pointsCount = 100;
private ResamplingMode selectedResamplingMode = ResamplingMode.Auto;
private final Random random = new Random();
private MovingAverage maLow = new MovingAverage(MA_LOW);
private MovingAverage maHigh = new MovingAverage(MA_HIGH);
private ScheduledFuture<?> schedule;
private final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
private volatile boolean isRunning = true;
private TextView textView;
@Override
protected void initExample(@NonNull SciChartSurface surface) {
selectedSeriesType = getResources().getStringArray(R.array.series_types)[0];
initChart();
// disable modifiers when updating in real-time
updateModifiers(false);
final int maxPointsCount = calcMaxPointCountToDisplay();
schedule = scheduledExecutorService.scheduleWithFixedDelay(() -> {
if (!isRunning || getPointsCount() > maxPointsCount) {
return;
}
UpdateSuspender.using(surface, appendDataRunnable);
}, 0, TIME_INTERVAL, TimeUnit.MILLISECONDS);
}
private void initChart() {
final IAxis xAxis = sciChartBuilder.newNumericAxis().withAutoRangeMode(AutoRange.Always).build();
final IAxis yAxis = sciChartBuilder.newNumericAxis().withAutoRangeMode(AutoRange.Always).build();
final IRenderableSeries rs1 = sciChartBuilder.newLineSeries().withStrokeStyle(0xFF4083B7).withDataSeries(new XyDataSeries<>(Integer.class, Float.class)).build();
final IRenderableSeries rs2 = sciChartBuilder.newLineSeries().withStrokeStyle(0xFFFFA500).withDataSeries(new XyDataSeries<>(Integer.class, Float.class)).build();
final IRenderableSeries rs3 = sciChartBuilder.newLineSeries().withStrokeStyle(0xFFE13219).withDataSeries(new XyDataSeries<>(Integer.class, Float.class)).build();
textView = new TextView(getActivity());
textView.setPadding(20, 20, 20, 20);
final CustomAnnotation annotation = sciChartBuilder.newCustomAnnotation()
.withCoordinateMode(AnnotationCoordinateMode.Relative)
.withContent(textView)
.withZIndex(-1)
.withX1(0)
.withY1(0)
.build();
final SciChartSurface surface = binding.surface;
UpdateSuspender.using(surface, () -> {
Collections.addAll(surface.getXAxes(), xAxis);
Collections.addAll(surface.getYAxes(), yAxis);
Collections.addAll(surface.getRenderableSeries(), rs1, rs2, rs3);
Collections.addAll(surface.getChartModifiers(), sciChartBuilder.newModifierGroupWithDefaultModifiers().build());
Collections.addAll(surface.getAnnotations(), annotation);
});
}
private final Runnable appendDataRunnable = new Runnable() {
private final IntegerValues xValues = new IntegerValues(pointsCount);
private final FloatValues firstYValues = new FloatValues(pointsCount);
private final FloatValues secondYValues = new FloatValues(pointsCount);
private final FloatValues thirdYValues = new FloatValues(pointsCount);
@Override
public void run() {
xValues.clear();
firstYValues.clear();
secondYValues.clear();
thirdYValues.clear();
final SciChartSurface surface = binding.surface;
final IXyDataSeries<Integer, Float> mainSeries = (IXyDataSeries<Integer, Float>) surface.getRenderableSeries().get(0).getDataSeries();
final IXyDataSeries<Integer, Float> maLowSeries = (IXyDataSeries<Integer, Float>) surface.getRenderableSeries().get(1).getDataSeries();
final IXyDataSeries<Integer, Float> maHighSeries = (IXyDataSeries<Integer, Float>) surface.getRenderableSeries().get(2).getDataSeries();
int xValue = mainSeries.getCount() > 0 ? mainSeries.getXValues().get(mainSeries.getCount() - 1) : 0;
float yValue = mainSeries.getCount() > 0 ? (float) mainSeries.getYValues().get(mainSeries.getCount() - 1) : 10;
for (int i = 0; i < pointsCount; i++) {
xValue++;
yValue += random.nextFloat() - 0.5f;
xValues.add(xValue);
firstYValues.add(yValue);
secondYValues.add((float) maLow.push(yValue).getCurrent());
thirdYValues.add((float) maHigh.push(yValue).getCurrent());
}
mainSeries.append(xValues, firstYValues);
maLowSeries.append(xValues, secondYValues);
maHighSeries.append(xValues, thirdYValues);
final long count = mainSeries.getCount() + maLowSeries.getCount() + maHighSeries.getCount();
final String text = "Amount of points: " + count;
Dispatcher.postOnUiThread(() -> textView.setText(text));
}
};
@NonNull
@Override
public List<Widget> getToolbarItems() {
return new ArrayList<Widget>() {
{
add(new ImageViewWidget.Builder().setId(R.drawable.example_toolbar_play).setListener(v -> updateRunningState(true)).build());
add(new ImageViewWidget.Builder().setId(R.drawable.example_toolbar_pause).setListener(v -> updateRunningState(false)).build());
add(new ImageViewWidget.Builder().setId(R.drawable.example_toolbar_stop).setListener(v -> {
updateRunningState(false);
resetChart();
}).build());
add(new ImageViewWidget.Builder().setId(R.drawable.example_toolbar_settings).setListener(v -> openSettingsDialog()).build());
}
};
}
private void updateRunningState(boolean isRunning) {
this.isRunning = isRunning;
updateAutoRangeBehavior(isRunning);
updateModifiers(isRunning);
}
private void updateAutoRangeBehavior(boolean isEnabled) {
final SciChartSurface surface = binding.surface;
AutoRange autoRangeMode = isEnabled ? AutoRange.Always : AutoRange.Never;
surface.getXAxes().get(0).setAutoRange(autoRangeMode);
surface.getYAxes().get(0).setAutoRange(autoRangeMode);
}
private void updateModifiers(boolean isEnabled) {
ChartModifierCollection modifiers = binding.surface.getChartModifiers();
for (IChartModifier modifier : modifiers) {
modifier.setIsEnabled(isEnabled);
}
}
private void openSettingsDialog() {
final Dialog dialog = ViewSettingsUtil.createSettingsPopup(getActivity(), R.layout.example_performance_demo_popup_layout);
Context context = dialog.getContext();
final SpinnerStringAdapter seriesTypesAdapter = new SpinnerStringAdapter(context, R.array.series_types);
final Spinner seriesSpinner = (Spinner) dialog.findViewById(R.id.series_types_spinner);
seriesSpinner.setAdapter(seriesTypesAdapter);
seriesSpinner.setSelection(seriesTypesAdapter.getPosition(selectedSeriesType));
seriesSpinner.setOnItemSelectedListener(new ItemSelectedListenerBase() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
selectedSeriesType = seriesTypesAdapter.getItem(position);
onChangeSeriesType();
}
});
final SpinnerStringAdapter strokeAdapter = new SpinnerStringAdapter(context, R.array.stroke);
final Spinner strokeSpinner = (Spinner) dialog.findViewById(R.id.stroke_spinner);
strokeSpinner.setAdapter(strokeAdapter);
strokeSpinner.setSelection(strokeAdapter.getPosition(String.valueOf(selectedStrokeThickness)));
strokeSpinner.setOnItemSelectedListener(new ItemSelectedListenerBase() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
selectedStrokeThickness = Integer.parseInt(strokeAdapter.getItem(position));
onChangeStroke();
}
});
final SpinnerStringAdapter pointsAdapter = new SpinnerStringAdapter(context, R.array.points);
final Spinner pointsSpinner = (Spinner) dialog.findViewById(R.id.points_spinner);
pointsSpinner.setAdapter(pointsAdapter);
pointsSpinner.setSelection(pointsAdapter.getPosition(getResources().getStringArray(R.array.points)[pointCounts.indexOf(pointsCount)]));
pointsSpinner.setOnItemSelectedListener(new ItemSelectedListenerBase() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
pointsCount = pointCounts.get(position);
onChangeStroke();
}
});
final SpinnerStringAdapter resamplingModeAdapter = new SpinnerStringAdapter(context, R.array.resampling_mode);
final Spinner resamplingSpinner = (Spinner) dialog.findViewById(R.id.resampling_mode_spinner);
resamplingSpinner.setAdapter(resamplingModeAdapter);
resamplingSpinner.setSelection(resamplingModeAdapter.getPosition(selectedResamplingMode.toString()));
resamplingSpinner.setOnItemSelectedListener(new ItemSelectedListenerBase() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
selectedResamplingMode = ResamplingMode.valueOf(resamplingModeAdapter.getItem(position));
onResamplingMode();
}
});
dialog.show();
}
private void onChangeSeriesType() {
final SciChartSurface surface = binding.surface;
UpdateSuspender.using(surface, new Runnable() {
@Override
public void run() {
for (int i = 0; i < surface.getRenderableSeries().size(); i++) {
IRenderableSeries oldRenderableSeries = surface.getRenderableSeries().get(i);
IRenderableSeries newRenderableSeries = changeSeriesType(selectedSeriesType, oldRenderableSeries);
surface.getRenderableSeries().set(i, newRenderableSeries);
}
}
});
}
private IRenderableSeries changeSeriesType(String param, IRenderableSeries initSeries) {
if (getResources().getStringArray(R.array.series_types)[0].equals(param)) {
return sciChartBuilder.newLineSeries()
.withDataSeries(initSeries.getDataSeries())
.withStrokeStyle(initSeries.getStrokeStyle())
.withResamplingMode(initSeries.getResamplingMode())
.build();
} else if (getResources().getStringArray(R.array.series_types)[1].equals(param)) {
return sciChartBuilder.newMountainSeries()
.withDataSeries(initSeries.getDataSeries())
.withStrokeStyle(initSeries.getStrokeStyle())
.withResamplingMode(initSeries.getResamplingMode())
.withAreaFillColor(initSeries.getStrokeStyle().getColor())
.build();
} else if (getResources().getStringArray(R.array.series_types)[2].equals(param)) {
CrossPointMarker pointMarker = new CrossPointMarker();
pointMarker.setSize(20, 20);
pointMarker.setStrokeStyle(initSeries.getStrokeStyle());
return sciChartBuilder.newScatterSeries()
.withDataSeries(initSeries.getDataSeries())
.withStrokeStyle(initSeries.getStrokeStyle())
.withResamplingMode(initSeries.getResamplingMode())
.withPointMarker(pointMarker)
.build();
}
return null;
}
private void onChangeStroke() {
final SciChartSurface surface = binding.surface;
UpdateSuspender.using(surface, new Runnable() {
@Override
public void run() {
for (int i = 0; i < surface.getRenderableSeries().size(); i++) {
IRenderableSeries renderableSeries = surface.getRenderableSeries().get(i);
final PenStyle currentStyle = renderableSeries.getStrokeStyle();
renderableSeries.setStrokeStyle(sciChartBuilder.newPen()
.withColor(currentStyle.getColor())
.withAntiAliasing(currentStyle.antiAliasing)
.withThickness(selectedStrokeThickness)
.build());
}
}
});
}
private void onResamplingMode() {
final SciChartSurface surface = binding.surface;
UpdateSuspender.using(surface, new Runnable() {
@Override
public void run() {
for (int i = 0; i < surface.getRenderableSeries().size(); i++) {
IRenderableSeries rSeries = surface.getRenderableSeries().get(i);
rSeries.setResamplingMode(selectedResamplingMode);
}
}
});
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (savedInstanceState != null) {
final SciChartSurface surface = binding.surface;
final int seriesCount = savedInstanceState.getInt("seriesCount");
for (int i = 0; i < seriesCount; i++) {
final ISciList<Integer> xValues = savedInstanceState.getParcelable("xValues" + i);
final ISciList<Float> yValues = savedInstanceState.getParcelable("yValues" + i);
final IXyDataSeries<Integer, Float> series = new XyDataSeries<>(Integer.class, Float.class);
series.append(xValues, yValues);
surface.getRenderableSeries().get(i).setDataSeries(series);
}
}
}
@Override
public void onSaveInstanceState(final Bundle outState) {
super.onSaveInstanceState(outState);
isRunning = false;
final SciChartSurface surface = binding.surface;
UpdateSuspender.using(surface, () -> {
final int size = surface.getRenderableSeries().size();
outState.putInt("seriesCount", size);
for (int i = 0; i < size; i++) {
final IRenderableSeries renderableSeries = surface.getRenderableSeries().get(i);
IXyDataSeries<Integer, Float> series = (IXyDataSeries<Integer, Float>) renderableSeries.getDataSeries();
outState.putParcelable("xValues" + i, series.getXValues());
outState.putParcelable("yValues" + i, series.getYValues());
}
});
}
@Override
public void onDestroyView() {
super.onDestroyView();
schedule.cancel(true);
}
private void resetChart() {
new Handler().postDelayed(() -> {
final SciChartSurface surface = binding.surface;
UpdateSuspender.using(surface, () -> {
for (int i = 0; i < surface.getRenderableSeries().size(); i++) {
surface.getRenderableSeries().get(i).getDataSeries().clear();
}
});
}, 100);
maLow = new MovingAverage(MA_LOW);
maHigh = new MovingAverage(MA_HIGH);
}
private static int calcMaxPointCountToDisplay() {
// for resampling None need
// 8 mb for data series (float, int)
// 16 mb for resampling (double, double)
// 4 mb for data indices (int)
// 8 mb for coordinates (float, float)
final float oneMlnPointsRequirement = 8 + 16 + 4 + 8;
// need to reserve some memory for other needs ( 40 mb should be enough )
final float memorySize = getMaxMemorySize() - 40;
// max amount of point on screen
final float maxPointCount = memorySize / oneMlnPointsRequirement * 1_000_000;
// we have 3 series in example
return Math.round(maxPointCount / 3);
}
private static int getMaxMemorySize() {
// max memory size in megabytes
return (int) (Runtime.getRuntime().maxMemory() / 1024L / 1024L);
}
private int getPointsCount() {
int result = 0;
RenderableSeriesCollection rsCollection = binding.surface.getRenderableSeries();
for (int i = 0; i < rsCollection.size(); i++) {
IRenderableSeries renderableSeries = rsCollection.get(i);
result += renderableSeries.getDataSeries().getCount();
}
return result;
}
}