How to refactor this code so only some properties are shared between variables in Swift - ios

I only just began learning swift and wanted to create a simple chart displaying some data.
I am creating a line chart using AnyChart library and there is a series of lines I am plotting to the chart. I noticed that I am repeating pretty much all of the properties. How can I make the below code less dry.
I am creating a line chart using AnyChart library and there is a series of lines I am plotting to the chart. I noticed that I am repeating pretty much all of the properties, the only thing that is changing is the initial variable name.
How can I make produce less code that will take into account the names of the variables intact (series 1, series 2)?
let series1Mapping = set.mapAs(mapping: "{x: 'x', value: 'value'}")
let series2Mapping = set.mapAs(mapping: "{x: 'x', value: 'value2'}")
let series1 = chart.line(data: series1Mapping)
let series2 = chart.line(data: series2Mapping)
series1.name(name: data.seriesNames[0])
series1.hovered().markers().enabled(enabled: true)
series1.hovered().markers()
.type(type: anychart.enums.MarkerType.CIRCLE)
.size(size: 4)
series1.tooltip()
.position(position: data.position)
.anchor(anchor: anychart.enums.Anchor.LEFT_CENTER)
.offsetX(offset: 5)
.offsetY(offset: 5)
series2.name(name: data.seriesNames[1])
series2.hovered().markers().enabled(enabled: true)
series2.hovered().markers()
.type(type: anychart.enums.MarkerType.CIRCLE)
.size(size: 4)
series2.tooltip()
.position(position: data.position)
.anchor(anchor: anychart.enums.Anchor.LEFT_CENTER)
.offsetX(offset: 5)
.offsetY(offset: 5)

You can do it like this.. Its just a rough idea ...i don't know what is Series object and mapping that you are doing ... But you can have one function that return series and get parameters to create that series
func getSeries(number:Int, mapping:String) -> Series {
let series = chart.line(data: set.mapAs(mapping: mapping))
series.name(name: data.seriesNames[number])
series.hovered().markers().enabled(enabled: true)
series.hovered().markers()
.type(type: anychart.enums.MarkerType.CIRCLE)
.size(size: 4)
series.tooltip()
.position(position: data.position)
.anchor(anchor: anychart.enums.Anchor.LEFT_CENTER)
.offsetX(offset: 5)
.offsetY(offset: 5)
return series
}
And then Create Series
let series1 = getSeries(number:0 , mapping:"{x: 'x', value: 'value'}")
let series2 = getSeries(number:1 , mapping:"{x: 'x', value: 'value2'}")
if you want to make it more simpler ... you can create mapping string from the number as well
Thanks

Related

Swift Charts: How to show only values and labels for values in array?

I have a Chart with WeatherKit.HourWeather objects spanning over multiple days on the x axis. However, I want to exclude the nighttime hours. It looks like I can do this with the chartXScale modifier like this:
let myDataSeperatedByHours = arrayWithAllDates.filter { ... }.sorted(...) // Array of WeatherKit.HourWeather objects filtered by isDaylight = true and sorted by date
let allDaytimeDates = myDataSeperatedByHours.map { $0.date } //only the Date objects
Chart {
ForEach(myDataSeperatedByHours, id: \.date) { hourData in
LineMark(
x: .value("hour", hourData.date),
y: .value("value", hourData.value)
)
}
}
.chartXAxis {
AxisMarks(position: .bottom, values: allDaytimeDates) { axisValue in
if let date = axisValue.as(Date.self) {
AxisValueLabel(
"\(Self.shortTimeFormatter.calendar.component(.hour, from: date))"
)
}
}
}
.chartXScale(domain: allDaytimeDates, type: .category)
However the Chart still displays part where there is no value. (the nighttime)
I want everything removed when there is night. I've marked it green on the image below. Maybe I have to use two Charts next to each other. One for every day, but I can't believe that there's no way to do it with one Chart only.
I've created an example app that you can download and test here: https://github.com/Iomegan/DateChartIssue
As per chart scale modifier documentation for domain parameter:
The possible data values along the x axis in the chart. You can define the domain with a ClosedRange for number or Date values (e.g., 0 ... 500), and with an array for categorical values (e.g., ["A", "B", "C"])
It seems for date type values this function is expecting a range but since you are specifying an array the method invocation traps.
Instead of providing the domain directly, you can provide an automatic scale domain modifying the inferred domain. To set the domain to your calculated allDaytimeDates use:
.chartXScale(domain: .automatic(dataType: Date.self) { dates in
dates = allDaytimeDates
})
Update 1
There are multiple approaches you can try to ignore night time date scale on X-axis. The simpler and not recommended approach is to provided X-axis value in your line mark as a String instead of a Date.
The issue with specifying X-axis value as Date is you can only supply a range for the axis scale and you can't just pick multiple ranges as scale for your axis as of now and similarly you can't specify your scale to ignore certain range or values (i.e. night time). With specifying X-axis value as string you will be able to just ignore night time values:
LineMark(
x: .value("hour", "\(hourData.date)"),
y: .value("value", hourData.value)
)
The demerit with this approach is temprature variations as obtained from this graph is wrong as all your data points will be just separated equally regardless of their date value.
The preferred approach is to manually adjust the X-axis position for next day's data points. For your scenario you can create a DayHourWeather type with custom X-position value:
struct DayHourWeather: Plottable {
let position: TimeInterval // Manually calculated X-axis position
let date: Date
let temp: Double
let series: String // The day this data belongs to
var primitivePlottable: TimeInterval { position }
init?(primitivePlottable: TimeInterval) { nil }
init(position: TimeInterval, date: Date, temp: Double, series: String) {
self.position = position
self.date = date
self.temp = temp
self.series = series
}
}
You can customize the position data to move daytime plots closer together ignoring night time values. Then you can create DayHourWeathers from your HourWeathers:
/// assumes `hourWeathers` are filtered containing only day time data and already sorted
func getDayHourWeathers(from hourWeathers: [HourWeather]) -> [DayHourWeather] {
let padding: TimeInterval = 10000 // distance between lat day's last data point and next day's first data point
var translation: TimeInterval = 0 // The negetive translation required on X-axis for certain day
var series: Int = 0 // Current day series
var result: [DayHourWeather] = []
result.reserveCapacity(hourWeathers.count)
for (index, hourWeather) in hourWeathers.enumerated() {
defer {
result.append(
.init(
position: hourWeather.date.timeIntervalSince1970 - translation,
date: hourWeather.date,
temp: hourWeather.temp,
series: "Day \(series + 1)"
)
)
}
guard
index > 0,
case let lastWeather = hourWeathers[index - 1],
!Calendar.current.isDate(lastWeather.date, inSameDayAs: hourWeather.date)
else { continue }
// move next day graph to left occupying previous day's night scale
translation = hourWeather.date.timeIntervalSince1970 - (result.last!.position + padding)
series += 1
}
return result
}
Now to plot your chart you can use the newly created DayHourWeather values:
var body: some View {
let dayWeathers = getDayHourWeathers(from: myDataSeperatedByHours)
Chart {
ForEach(dayWeathers, id: \.date) { hourData in
LineMark(
x: .value("hour", hourData.position), // custom X-axis position calculated
y: .value("value", hourData.temp)
)
.foregroundStyle(by: .value("Day", hourData.series))
}
}
.chartXScale(domain: dayWeathers.first!.position...dayWeathers.last!.position) // provide scale range for calculated custom X-axis positions
}
Note that with above changes your X-axis marker will display your custom X-axis positions. To change it back to the actual date label you want to display you can specify custom X-axis label:
.chartXAxis {
AxisMarks(position: .bottom, values: dayWeathers) {
AxisValueLabel(
"\(Self.shortTimeFormatter.calendar.component(.hour, from: dayWeathers[$0.index].date))"
)
}
}
The values argument for AxisMarks only accepts an array of Plottable items, this is why confirming DayHourWeather to Plottable is needed. After above changes the chart obtained will look similar to this:
Note that I have created a different series for each day data. Although you can combine them into a single series, I will advise against doing so as the resulting chart is misleading to viewer since you are removing part of the X-axis scale.

highlightValue not working in chart with CombinedChartView :: Display marker programmatically is not working :: ChartIssue :: iOS Swift :: DanielGindi

Here is what i am doing!
chart.highlightValue(x: timeStampValue, dataSetIndex: totalCount)
==> In the above line,
timeStampValue is x axis value which i have set while filling up the array.
totalCount is total count of array of data which i am displaying in chart.
What i need to achieve is
When chart screen comes up, i need to display marker by default and for that, i am using "highlightValue" method of chart which is not working.
Please let me know the solution to show marker by default programatically.
NOTE: I am using marker whose UI is custom which is working fine when i tap manually at point in chart:
let marker = CustomMarkerView.viewFromXib()!
marker.chartView = chart
chart.marker = marker
chart.drawMarkers = true
Library used : https://github.com/danielgindi/Charts
Chart Data set :
let data = CombinedChartData()
data.lineData = LineChartData(dataSets:[viewModel.lineChartDataSet, viewModel.emptylineChartDataSet])
data.lineData.highlightEnabled = true
viewModel.lineChartDataSet.highlightColor = AssetsColor.highlightedColor.color
viewModel.lineChartDataSet.drawHorizontalHighlightIndicatorEnabled = false
viewModel.lineChartDataSet.highlightLineDashPhase = 2
viewModel.lineChartDataSet.highlightLineDashLengths = [5, 2.5]
you are using the wrong value for dataSetIndex param
based on your code, the datasets only contains 2 data
data.lineData = LineChartData(dataSets:[viewModel.lineChartDataSet, viewModel.emptylineChartDataSet])
dataSetIndex is not data count, in linechart dataset represent a line that has many data (x,y), so dataSetIndex is more like which line
so your code should be something like this
chart.highlightValue(x: timeStampValue, dataSetIndex: 0)
chart.highlightValue(x: timeStampValue, dataSetIndex: 0, dataIndex: 0)
When i added 1 more parameter which is dataIndex as 0 and it worked.
Here, dataSetIndex is set to 0 because it is CombinedChartView where i have merged 2 data set.

Structure Data timeintervals in Highcharts

I'd like to visualize the amount of steps taken over a day. Each datapoint looks simplified like this:
{
startDate: 1481029440000,
endDate: 1481029920000,
steps: 31
}
I'd like to plot it over an entire day and illustrate the duration but also the grow of step increase. Each datapoint is a separate series as I didn't want to have points connected to each other.
The result looks like what I want except for the styling which I have change. However the performance and zoom into the chart is extremely slow. Might there be a better way to use it?
Highcharts is optimised for managing many points, not many series (the work has been start on optimising series, though - as far as I know).
You can use one series with the null points as separators. By default connecting nulls is disabled.
data: (function (data) {
var d = [], i = 0, len = data.length, point;
for (; i < len; i++) {
point = data[i];
d.push([point.startDate, point.steps], [point.endDate, point.steps]);
if (i < len - 1) {d.push([point.endDate, null]);}
}
return d;
})(data)
example: http://jsfiddle.net/7vtd4fzm/

Stock High Charts with Custom points on X-axis

I have a requirement where i have to show custom points on x-axis instead of dates values. Also same custom data points needs to be shown on navigator as well. In the below Js fiddle, i am converting data (Per13/2016 etc) into equivalent date values and then binding the chart using converted date values.
Below is the link of the JS fiddle:- Fiddle link
In the Js fiddle, i am showing Per1,Per2 etc.on x-axis and same has to be shown on navigator as well.
Now i am facing problem with the navigator,when i changes the range using slider ,the x-axis labels changes but not according to the range selected.Also tool-tip formatting is getting changed.
Can you please let me know how to handle this scenario and best way to do the same.
//few code lines to post fiddle link
xAxis: {
labels: {
formatter: function () {
if(fiscal13){
var perDate = new Date(this.value);
return 'Per' + (perDate.getMonth() + 1);
}
}
}
}
I am not sure if I am right, but I think you are overdoing this.
Let's keep original data, so remove fiscal13Data.Data.forEach(function(item) { .. }); function. And When creating data, use simply index of the point as x-value:
var cost = [],
usage = [],
dataLength = fiscal13Data.Data.length
i = 0;
for (i; i < dataLength; i += 1) {
// need to sum costs
cost.push([
i, // the index
fiscal13Data.Data[i]['Cost'] // cost
]);
usage.push([
i, // the index
fiscal13Data.Data[i]['Usage'] // Usage
]);
}
Now you can get to the "Per13/2016" strings in a simple way in xAxis labels' formatters:
var str = fiscal13Data.Data[this.value].Date;
In tooltip formatter, it is almost exactly the same:
var str = fiscal13Data.Data[this.x].Date;
And here is working demo: http://jsfiddle.net/qneuh4Ld/3/
Note: You data looks a bit strange - don't you want to sort it first? Also, you have twice every date (e.g. "Per13/2016" - once for "water" and once for "electric").

MS Chart Control Range Bar

I am trying to somehow replicate the range bar chart here.
I've found this reference but I don't fully grasp the code.
What I have is a series of task (sometimes accomplished in different periods).
let d = [("task1", DateTime.Parse("11/01/2014 08:30"), DateTime.Parse("12/01/2014 10:30"));
("task2", DateTime.Parse("15/01/2014 09:30"), DateTime.Parse("16/01/2014 10:30"));
("task3", DateTime.Parse("11/01/2014 08:30"), DateTime.Parse("16/01/2014 10:30"))]
let chart = d |> FSharp.Charting.Chart.RangeBar
chart.ShowChart()
I am struggling to understand the logic of the API.
I have also tried:
let chart = new Windows.Forms.DataVisualization.Charting.Chart(Dock = DockStyle.Fill)
let area = new ChartArea("Main")
chart.ChartAreas.Add(area)
let mainForm = new Form(Visible = true, TopMost = true, Width = 700, Height = 500)
mainForm.Controls.Add(chart)
let seriesColumns = new Series("NameOfTheSerie")
seriesColumns.ChartType <- SeriesChartType.RangeBar
type SupportToChart(serieVals: Series) =
member this.addPointXY(lbl, [<ParamArray>] yVals: Object[]) =
serieVals.Points.AddXY(lbl, yVals) |> ignore
let supporter = SupportToChart(seriesColumns)
supporter.addPointXY("AAA", DateTime.Parse("11/01/2014 08:30"), DateTime.Parse("12/01/2014 10:30") )
which results in
System.ArgumentOutOfRangeException: You can only set 1 Y values for
this data point.
Has something changed in the API since then?
I'm not entirely sure that F# Charting is currently powerful enough to be able to reconstruct the above chart. However, one of the problems seems to be that it treats dates as float values (for some reason) and incorrectly guesses the ranges. You can at least see the chart if you use:
Chart.RangeBar(d)
|> Chart.WithYAxis(Min=41650.0, Max=41660.0)
Please submit this as an issue on GitHub. If you want to dig deeper into how F# Charting works and help us get this fixed, that would be amazing :-)
The trick is initializing the Series with
let serie = new Series("Range", yValues)
where yValues defines the max number of "Y-values".

Resources