swift use TabularData and Charts together - swift5

I tried to use specified columns from a DataFrame from TabularData in a Charts LineMark, but couldn't subtract the .value's.
ForEach(theXYdata, id: \.x) { //item in
LineMark(
x: .value("x", $0.x),
y: .value("y", $0.y)
)
.interpolationMethod(.catmullRom)
.symbol(Circle())
.foregroundStyle(.orange)
}
}

Related

How to Add Labels on a LineMark Chart in SwiftUI

I'm creating a chart in SwiftUI. It's weather related and I have the data successfully displaying. Now, I'm attempting to display the temperature on each symbol in the chart, but it only displays on the first symbol.
Here's what I've done:
Chart {
ForEach(temperatures, id: \.tempType) { series in
ForEach(series.data) { element in
LineMark(x: .value("Day", element.day), y: .value("Temps", element.temp))
.interpolationMethod(.catmullRom)
.foregroundStyle(by: .value("TemperatureType", series.tempType))
.symbol {
Circle()
.fill(.yellow)
.frame(width: 10)
.shadow(radius: 2)
}
.lineStyle(.init(lineWidth: 5))
} // end for each
} // end for each
}
This works. Then, I attempt to add text using this modifier on the LineMark:
.annotation(position: .overlay, alignment: .bottom) {
let text = "\(element.temp)"
Text(text)
}
It only displays the text on the first symbol's data point:
Since the annotation modifier is within the ForEach loop, I thought it would display the temperature at each data point, but it doesn't. What's the best way to have the temperature displayed at each symbol instead of only the first?
The short answer is that the .annotation refers to the type of "Mark" that you attach it to - and you are attaching it to a LineMark, so it is the entire line you are "annotating", not the individual points.
Had you used BarMarks or PointMarks, the annotation will attach to the individual bar or point. So try this instead:
Chart {
ForEach(Array(zip(numbers, numbers.indices)), id: \.0) { number, index in
LineMark(
x: .value("Index", index),
y: .value("Value", number)
)
.lineStyle(.init(lineWidth: 5))
PointMark(
x: .value("Index", index),
y: .value("Value", number)
)
.annotation(position: .overlay,
alignment: .bottom,
spacing: 10) {
Text("\(number)")
.font(.largeTitle)
}
}
}
To make it compatible with your nice symbols, we need to add a couple of extra steps:
Chart {
ForEach(Array(zip(numbers, numbers.indices)), id: \.0) { number, index in
LineMark(
x: .value("Index", index),
y: .value("Value", number)
)
.interpolationMethod(.catmullRom)
.lineStyle(.init(lineWidth: 5))
.symbol {
// This still needs to be associated
// with the LineMark
Circle()
.fill(.yellow)
.frame(width: 10)
.shadow(radius: 2)
}
PointMark(
x: .value("Index", index),
y: .value("Value", number)
)
// We need .opacity(0) or it will
// overlay your `.symbol`
.opacity(0)
.annotation(position: .overlay,
alignment: .bottom,
spacing: 10) {
Text("\(number)")
.font(.largeTitle)
}
}
}

How to add a placeholder for a Swift Chart that doesn't have any data yet?

Imagine this chart doesn't have any data.
What api do i use to display a placeholder like "no data yet"?
I've searched WWDC and apple docs.
Maybe it's just a case of hiding the chart and showing some text.
import SwiftUI
import Charts
struct TopStyleChart: View {
let data = [
(name: "Cachapa", sales: 916),
(name: "Injera", sales: 850),
(name: "Crêpe", sales: 802),
(name: "Jian Bing", sales: 753),
(name: "Dosa", sales: 654),
(name: "American", sales: 618)
]
var body: some View {
Chart(data, id: \.name) {
BarMark(
x: .value("Sales", $0.sales),
y: .value("Name", $0.name)
)
// Set the foreground style of the bars.
.foregroundStyle(.pink)
// Customize the accessibility label and value.
.accessibilityLabel($0.name)
.accessibilityValue("\($0.sales) sold")
}
}
}
A simple if clause should do the trick here:
struct TopStyleChart: View {
let data: [(name: String, sales: Int)]?
var body: some View {
if let data = data{
Chart(data, id: \.name) {
BarMark(
x: .value("Sales", $0.sales),
y: .value("Name", $0.name)
)
// Set the foreground style of the bars.
.foregroundStyle(.pink)
// Customize the accessibility label and value.
.accessibilityLabel($0.name)
.accessibilityValue("\($0.sales) sold")
}
} else{
Text("no data yet")
}
}
}

Create a SwiftUI line graph using arrays and the Swift Charts framework

In a SwiftUI app, I'm trying to pass an array of Y values to the Swift Charts framework to draw a line graph. I would like to create the X values based on the length or count of the Y data. I tried the approach shown below but it doesn't work because Charts wants the data as an array of structs. Is there a way to pass arrays directly to the Charts framework?
import SwiftUI
import Charts
struct ContentView: View {
let ydata = [1, 4.5, 3, 6, 7, 5.2, 9, 12.5]
let xdata = Array(0..<ydata.count)
let data = [xdata, ydata]
var body: some View {
Chart(data) {
LineMark(
x: .value("X values", $0),
y: .value("Y values", $1)
)
}
}
}
You can use an array of tuples, here I am using a computed property for the data
var data: [(Int, Double)] {
Array(zip(Array(0..<ydata.count), ydata))
}
When using a tuple we need to tell Chart what the id is so the use of data is a little different
var body: some View {
Chart(data, id: \.0) { tuple in
LineMark(
x: .value("X values", tuple.0),
y: .value("Y values", tuple.1)
)
}
}

Apex Charts tooltip formatter: how to convert number to string

I am trying to show y value of a line graph in tooltips (apexcharts). The original data is set in string format in series {'8:50 AM', '11:00 AM', '9:02 AM'...}, but they appear as '8', '11', '9'... in tooltips, rounded up in number format. I do not understand why the string data is converted and rounded by itself. I used 'toString' to reveal the full text ('8:50 AM', '11:00 AM', '9:02 AM'...) in the tooltip formatter, but it did not work. Any suggestion?
var spark1 = {
chart: {
id: 'sparkline1',
type: 'line',
height: 200,
sparkline: {
enabled: true
},
group: 'sparklines'
},
series: [{
name: 'wake-up time',
data: [{x: '05/06/2014', y:'8:50 AM'}, {x: '05/07/2014', y:'11:00 AM'}, {x: '05/08/2014', y:'9:02 AM'}, {x: '05/09/2014', y:'10:47 AM'}]
}],
tooltip: {
x: {
show: false
},
y: {
formatter: function(value){
return value.toString();
}
}
}
}
}
the chart expects a number to be provided for y.
if a string is provided, it will try to parse it to a number using parseFloat, or something similar.
this is why the numbers are rounded.
parseFloat('8:50 AM') = 8
so if you have something like '1:00 PM', you will not get the desired line.
instead, convert the values to a true date, then extract the time value.
we can do this be creating a new date with both the date and time.
then create another date with just the date portion and subtract it from the previous.
we'll save the chart data in a variable.
var chartData = [{x: '05/06/2014', y: '8:50 AM'}, {x: '05/07/2014', y: '11:00 AM'}, {x: '05/08/2014', y: '9:02 AM'}, {x: '05/09/2014', y: '10:47 AM'}];
then map it for the chart.
data: chartData.map(function (row) {
// create full date time
var rowDate = new Date(row.x + ' ' + row.y);
// subtract date only from full date time
var rowTime = rowDate.getTime() - (new Date(row.x)).getTime();
// return new data point
return {x: row.x, y: rowTime};
})
as for the tooltip, we can use the series argument to pull the index from the original data...
formatter: function(value, series) {
// use series argument to pull original string from chart data
return chartData[series.dataPointIndex].y;
}
see following working snippet...
$(document).ready(function() {
var chartData = [{x: '05/06/2014', y: '8:50 AM'}, {x: '05/07/2014', y: '11:00 AM'}, {x: '05/08/2014', y: '9:02 AM'}, {x: '05/09/2014', y: '10:47 AM'}];
var spark1 = {
chart: {
id: 'sparkline1',
type: 'line',
height: 200,
sparkline: {
enabled: true
},
group: 'sparklines'
},
series: [{
name: 'wake-up time',
data: chartData.map(function (row) {
// create full date time
var rowDate = new Date(row.x + ' ' + row.y);
// subtract date only from full date time
var rowTime = rowDate.getTime() - (new Date(row.x)).getTime();
// return new data point
return {x: row.x, y: rowTime};
})
}],
tooltip: {
x: {
show: false
},
y: {
formatter: function(value, series) {
// use series argument to pull original string from chart data
return chartData[series.dataPointIndex].y;
}
}
}
};
var chart = new ApexCharts(
document.getElementById('sparkline1'),
spark1
);
chart.render();
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/apexcharts"></script>
<div id="sparkline1"></div>
You can also customise the title and the value with the below format
tooltip: {
y: {
formatter: function (val) {
return "Count: " + val
},
title: {
formatter: function (seriesName) {
return seriesName + "<br/>-------------------<br/>"
},
},
},
},

Swift create object matrix

I'm new to Swift and I am trying to create a array of arrays of a custom object. I did some research and what mostly came up online is:
Array(repeating: Array(repeating: [Object](), count: y), count: x)
or similar but I haven't been able to get them working for me. ( Deprecated because different swift versions,etc.. ) Right now I have
class ChessPiece {
// class definition...
}
class ChessBoard {
var board: [[ChessPiece]] = []
init() {
board = [[ChessPiece(),ChessPiece(),ChessPiece()],
[ChessPiece(),ChessPiece(),ChessPiece()],
[ChessPiece(),ChessPiece(),ChessPiece()]]
}
}
But what if I had 100 rows or columns? Isn't there a more efficient and direct way to create a matrix with x rows and y columns?
I just do with normal for-in loop
class ChessPiece {
// class definition...
}
class ChessBoard {
var board: [[ChessPiece]] = []
init(row: Int, column: Int) {
for _ in 1...row {
var innerArray: [ChessPiece] = []
for _ in 1...column {
innerArray.append(ChessPiece())
}
board.append(innerArray)
}
}
}
let chessBoard = ChessBoard(row: 8, column: 8)
The function you mentioned is fine Array(repeating:count:).
This works on my playground:
struct ChessPiece {}
func makeChessPlate(dimension: Int) -> [[ChessPiece]] {
return Array(repeating: Array(repeating: ChessPiece(), count: dimension), count: dimension)
}
print(makeChessPlate(dimension: 2)) // Result: [[ChessPiece, ChessPiece],[ChessPiece, ChessPiece]]
EDIT: Notice that my example works only because I used a struct instead of a class. On the contrary to classes, structs are copied by values, then this results in an array of unique objects.
You can use this loop to create a multidimensional array.
class ChessPiece{
}
var numColumns = 27
var numRows = 52
var array = [[ChessPiece]]()
for column in 0...numColumns {
array.append(Array(repeating: ChessPiece(), count:numRows))
}
This would create an array of array of ChessPieces.