I have some monthly data that I need to calculate a specific value for before it gets displayed, using:
.onAppear{
ForEach(monthlyTilt) {item in
item.degrees = NewValue…
}
}
With a Struct/var combination of:
struct MonthlyTilt: Identifiable {
var id = UUID().uuidString
var month: String
var day: Int
var degrees: Double
}
var monthlyTilt = [
MonthlyTilt(month: "Jan", day: 15, degrees: 0.0),
MonthlyTilt(month: "Feb", day: 46, degrees: 0.0),
MonthlyTilt(month: "Mar", day: 74, degrees: 0.0),
MonthlyTilt(month: "Apr", day: 105, degrees: 0.0),
MonthlyTilt(month: "May", day: 135, degrees: 0.0),
MonthlyTilt(month: "Jun", day: 166, degrees: 0.0),
MonthlyTilt(month: "Jul", day: 196, degrees: 0.0),
MonthlyTilt(month: "Aug", day: 227, degrees: 0.0),
MonthlyTilt(month: "Sept", day: 258, degrees: 0.0),
MonthlyTilt(month: "Oct", day: 288, degrees: 0.0),
MonthlyTilt(month: "Nov", day: 319, degrees: 0.0),
MonthlyTilt(month: "Dec", day: 349, degrees: 0.0)
]
The error message reads: Cannot assign to property: 'item' is a 'let' constant. monthlyTilt is set as a var.
How can I update the degree value?
You're trying to update a struct which is, by default, immutable. There are a few different approaches to this. One way is to populate the monthlyTilt var only when you get the data you need to build the structs. You'll probably want to update your datasource to include the month and day as well.
import SwiftUI
public struct FBKChatBotView: View {
#State private var tilts: [MonthlyTilt] = []
public var body: some View {
VStack {
ForEach(tilts) { tilt in
Text("Tilt is \(tilt.degrees) degrees")
}
}
.onAppear {
someDataSource() { items in
items.forEach { item in
let tilt = MonthlyTilt(month: item.month, day: item.day, degrees: item.degrees)
tilts.append(tilt)
}
}
}
}
}
An alternative would be to set up an ObservableObject to handle building that list of an #Published array of tilts and your view would update when that changed. Then you could eliminate the onAppear.
Or, you could create a mutating function in your struct that allows it to set/overwrite its own variables.
struct MonthlyTilt: Identifiable {
var id = UUID().uuidString
var month: String
var day: Int
var degrees: Double
mutating func setDegrees(_ to: Double) {
degrees = to
}
}
I don't know why, but I never really like that last (mutating) option because it opens the door to other things also mutating the struct and then you lose the strictness of an immutable struct. I don't know if that's best practice or personal preference, but I almost always try not to make structs mutable.
Sample code is off the top of my head so excuse any typos
Related
I have a bar chart and i was wondering if there is a way to make all the graphics have the same colour:
https://codepen.io/conormdowney/pen/xmVRjd
var chart = am4core.create("chartdiv", am4charts.XYChart);
chart.data = [{
"category": "Research",
"value1": 450,
"value2": 1200,
"value3": 960,
"value4": 710,
"value5": 900
}, {
"category": "Marketing",
"value1": 1200,
"value2": 450,
"value3": 850,
"value4": 1250,
"value5": 950
}, {
"category": "Distribution",
"value1": 1850,
"value2": 1700,
"value3": 450,
"value4": 870,
"value5": 600
}];
// Create axes
var categoryAxis = chart.xAxes.push(new am4charts.CategoryAxis());
categoryAxis.dataFields.category = "category";
categoryAxis.renderer.grid.template.location = 0;
//categoryAxis.renderer.minGridDistance = 30;
var valueAxis = chart.yAxes.push(new am4charts.ValueAxis());
// Create series
function createSeries(field) {
var series = chart.series.push(new am4charts.ColumnSeries());
series.dataFields.valueY = field;
series.dataFields.categoryX = "category";
return series;
}
createSeries("value1");
createSeries("value2");
createSeries("value3");
createSeries("value4");
createSeries("value5");```
There are two methods:
The obvious method is to manually set each series color to be the same. You can pull from the chart's color list too, if you prefer, e.g.
series.columns.template.fill = chart.colors.getIndex(0);
series.columns.template.stroke = chart.colors.getIndex(0);
Alternatively, you can set reuse to true on the chart's colors object to have it only reuse colors from the list. Setting the list to a single color will essentially be the same as the lines from the first method:
chart.colors.reuse = true;
chart.colors.list = [
am4core.color("#845EC2")
];
As #xorsparks answer, but my bars were a variation of my one colour. I found that I had to set chart.colors.passOptions = {}; to stop that from happening.
I am aiming to make a program in which I am using using SwiftUI buttons to update by SCNView in SceneKit. I have a cylinder as a SCNCylinder in my SCNView inside a frame in SwiftUI. I want my cylinder to rotate about 180° after I press the button. In my current code I have used #State and #Binding to update the view. But somehow the cylinder rotates as soon as I run the code, not waiting for me to touch the button. Not sure why this happens
Here is my code :
struct ContentView: View {
#State var rotationAngle: Float = 180
var body: some View {
VStack{
Button(action: {
// What to perform
self.rotationAngle = 180
}) {
// How the button looks like
Text("180°")
.frame(width: 100, height: 100)
.position(x: 225, y: 500)
}
SceneKitView(angle: self.$rotationAngle)
.frame(width: 300, height: 300)
.position(x: 225, y: 0)
}
}
}
struct SceneKitView: UIViewRepresentable {
#Binding var angle: Float
func degreesToRadians(_ degrees: Float) -> CGFloat {
return CGFloat(degrees * .pi / 180)
}
func makeUIView(context: UIViewRepresentableContext<SceneKitView>) -> SCNView {
let sceneView = SCNView()
sceneView.scene = SCNScene()
sceneView.allowsCameraControl = true
sceneView.autoenablesDefaultLighting = true
sceneView.backgroundColor = UIColor.white
sceneView.frame = CGRect(x: 0, y: 10, width: 0, height: 1)
return sceneView
}
func updateUIView(_ sceneView: SCNView, context: UIViewRepresentableContext<SceneKitView>) {
let cylinder = SCNCylinder(radius: 0.02, height: 2.0)
let cylindernode = SCNNode(geometry: cylinder)
cylindernode.position = SCNVector3(x: 0, y: 0, z: 0)
cylinder.firstMaterial?.diffuse.contents = UIColor.green
cylindernode.pivot = SCNMatrix4MakeTranslation(0, -1, 0)
let rotation = SCNAction.rotate(by: self.degreesToRadians(self.angle), around: SCNVector3(1, 0, 0), duration: 5)
sceneView.scene?.rootNode.addChildNode(cylindernode)
cylindernode.runAction(rotation)
}
typealias UIViewType = SCNView
}
I want the cylinder to rotate after I press the button. Please help me with this problem.
just set startingAngle to 0
#State var rotationAngle: Float = 0
I have a cylinder as SCNCylinder in a SCNScene in SceneKit and want to display it in a frame in SwiftUI. My goal is to rotate the cylinder by a angle of 180° or 90° (as the user chooses). To take the input (of the angle of rotation) i have used Text() and onTapGesture{ .... } property in SwiftUI. After I tap the text, the cylinder rotates but now I have two cylinders, one at the original position and one rotating at an desired angle. I am not sure why this happens. I want the same cylinder to rotate, not a identical copy of that doing it. I have connected the SwiftUI view and SceneKit view by using #State and #Binding.
Here is my code :
struct ContentView: View {
#State var rotationAngle = 0
var body: some View {
VStack{
Text("180°").onTapGesture {
self.rotationAngle = 180
}
Spacer()
Text("90°").onTapGesture {
self.rotationAngle = 90
}
SceneKitView(angle: $rotationAngle)
.position(x: 225.0, y: 200)
.frame(width: 300, height: 300, alignment: .center)
}
}
}
struct SceneKitView: UIViewRepresentable {
#Binding var angle: Int
func degreesToRadians(_ degrees: Float) -> CGFloat {
return CGFloat(degrees * .pi / 180)
}
func makeUIView(context: UIViewRepresentableContext<SceneKitView>) -> SCNView {
let sceneView = SCNView()
sceneView.scene = SCNScene()
sceneView.allowsCameraControl = true
sceneView.autoenablesDefaultLighting = true
sceneView.backgroundColor = UIColor.white
sceneView.frame = CGRect(x: 0, y: 10, width: 0, height: 1)
return sceneView
}
func updateUIView(_ sceneView: SCNView, context: UIViewRepresentableContext<SceneKitView>) {
let cylinder = SCNCylinder(radius: 0.02, height: 2.0)
let cylindernode = SCNNode(geometry: cylinder)
cylindernode.position = SCNVector3(x: 0, y: 0, z: 0)
cylinder.firstMaterial?.diffuse.contents = UIColor.green
cylindernode.pivot = SCNMatrix4MakeTranslation(0, -1, 0)
let inttofloat = Float(self.angle)
let rotation = SCNAction.rotate(by: self.degreesToRadians(inttofloat), around: SCNVector3(1, 0, 0), duration: 5)
cylindernode.runAction(rotation)
sceneView.scene?.rootNode.addChildNode(cylindernode)
}
typealias UIViewType = SCNView
}
I want to have a single cylinder rotation at a given angle.
The problem is, that updateUIView will be called several times. You can check this by adding a debug point there. Because of that your cylinder will be added several times. So you can solve this by many ways...one way would be to delete all nodes in your sceneview before starting your animation like so:
func updateUIView(_ sceneView: SCNView, context: UIViewRepresentableContext<SceneKitView>) {
sceneView.scene?.rootNode.enumerateChildNodes { (node, stop) in
node.removeFromParentNode()
}
I am new to Crossfilter and JS. I can't figure out why this barchart won't render. I've tried several date formats. Link to my jsfiddle is below. Any help, please?
It is basically reading a loan dataset in JSON format and attempting to barchart the count by day over time.
https://jsfiddle.net/5q1dddgt/2/
<div id="time-chart"></div>
/**
* Created on 3/13/17.
*/
var mortgageJson = [{
"index": 0,
"full_fips": "04013",
"defaulted_balance": 0.0,
"original_balance": 148000.0,
"county_name": "Maricopa County",
"state": "AZ",
"oltv": 79,
"dti": 30.0,
"originationMth": "1999-02-01T00:00:00.000Z",
"defaultRate": 0.0
}, {
"index": 1,
"full_fips": "04021",
"defaulted_balance": 0.0,
"original_balance": 148000.0,
"county_name": "Pinal County",
"state": "AZ",
"oltv": 79,
"dti": 30.0,
"originationMth": "1999-02-01T00:00:00.000Z",
"defaultRate": 0.0
}];
var mortgageSummary = mortgageJson;
var dateformat=d3.time.format("%m-%d-%Y");
mortgageSummary.forEach(function(d) {
d.originationMonth = new Date(d.originationMth).toLocaleDateString("en-GB");
});
var ndx = crossfilter(mortgageSummary);
var dateDim = ndx.dimension(function(d) {
return d["originationMonth"];
});
var originationByTime = dateDim.group().reduceCount(function(d) { return d.originationMonth;});
var totalOrigination = ndx.groupAll().reduceSum(function(d) {
return d["original_balance"];
});
var max_county = originationByTime.top(1)[0].value;
var minDate = dateDim.bottom(1)[0]["originationMonth"];
var maxDate = dateDim.top(1)[0]["originationMonth"];
console.log(minDate);
console.log(maxDate);
var timeChart = dc.barChart("#time-chart");
timeChart
.width(600)
.height(160)
.margins({
top: 10,
right: 50,
bottom: 30,
left: 50
})
.dimension(dateDim)
.group(originationByTime)
.transitionDuration(500)
.x(d3.time.scale().domain([minDate, maxDate]))
.xUnits(d3.time.days)
.elasticX(true)
.elasticY(true)
.xAxisLabel("Year")
.yAxis().ticks(4);
dc.renderAll();
Take a look at the browser console and you'll see some of the problems. Your inclusion of firebug-lite is causing lots of errors. I'd recommend you just use the developer tools included in your browser.
Other changes:
You want an actual date object as your dimension rather than a string, so I changed
mortgageSummary.forEach(function(d) {
d.originationMonth = new Date(d.originationMth).toLocaleDateString("en-GB");
});
to
mortgageSummary.forEach(function(d) {
d.originationMonth = new Date(d.originationMth);
});
And group.reduceCount doesn't take any arguments (reduceSum does), so this will work fine:
var originationByTime = dateDim.group().reduceCount();
I also included dc.css as a dependency, which helps a lot with formatting.
Here's the working example: https://jsfiddle.net/5q1dddgt/4/
I am having problems with iOS Charts when I try to put two lines on the same chart with a different number of data points. I've pasted my test code below.
testLineChartView.delegate = self
testLineChartView.xAxis.enabled = true
testLineChartView.xAxis.labelPosition = .Bottom
testLineChartView.rightAxis.drawLabelsEnabled = false
var allLineChartDataSets: [LineChartDataSet] = [LineChartDataSet]()
var dataEntries: [ChartDataEntry] = []
let dataPoints = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul"]
let values = [18.0, 4.0, 6.0, 3.0, 12.0, 16.0, 30]
for i in 0..<dataPoints.count {
let dataEntry = ChartDataEntry(value: values[i], xIndex: i)
dataEntries.append(dataEntry)
println(dataPoints[i])
}
let lineChartDataSet1: LineChartDataSet = LineChartDataSet(yVals: dataEntries, label: "Temperature")
allLineChartDataSets.append(lineChartDataSet1)
var dataEntries2: [ChartDataEntry] = []
let dataPoints2 = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Aug"]
let values2 = [21.0, 5.0, 7.0, 10.0, 11.0, 18.0, 20]
for i in 0..<dataPoints2.count {
let dataEntry2 = ChartDataEntry(value: values2[i], xIndex: i)
dataEntries2.append(dataEntry2)
println(dataPoints2[i])
}
let lineChartDataSet2 = LineChartDataSet(yVals: dataEntries2, label: "Units Sold")
lineChartDataSet2.setColor(UIColor.redColor())
lineChartDataSet2.setCircleColor(UIColor.redColor())
allLineChartDataSets.append(lineChartDataSet2)
let allDataPoints: [String] = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug"]
let lineChartData = LineChartData(xVals: allDataPoints, dataSets: allLineChartDataSets)
testLineChartView.data = lineChartData
Here is the chart that is produced:
As you can see, the August entry for dataset2 is showing as July. I tried to add an extra nil value for dataset2's July value, but that doesn't work. How do I get this to show correctly?
You misunderstand how it works. each xIndex will has its value, called dataEntry. You first create two series of 7 values, but at last you enlarged the xAxis. ios-charts uses xIndex to track where should it draw the value, not the label. Aug and Jul label both are at xIndex:6, in your dataSets. That's why you see they all are drawn on Jul, because it's xIndex is 6.
What you should do is first create the x values like
let allDataPoints: [String] = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug"] to indicate that I have 8 x values, not 7,
And then insert your values with their xIndex value, just ignore if it has no value at specific xIndex and continue the next one. Don't insert nil value, as recommended.
For example, for the data
let values2 = [21.0, 5.0, 7.0, 10.0, 11.0, 18.0, 20] (which has Aug data but no Jul data)
The xIndex should be [0,1,2,3,4,5,7] for each value, not [0,1,2,3,4,5,6] while you are creating dataEntry.