In Swift Charts the signature for chartForegroundStyleScale to set the ShapeStyle for each data series is:
func chartForegroundStyleScale<DataValue, S>(_ mapping: KeyValuePairs<DataValue, S>) -> some View where DataValue : Plottable, S : ShapeStyle
The KeyValuePairs initialiser (init(dictionaryLiteral: (Key, Value)...)) only takes a variadic parameter so any attempt to initialise a foreground style from an array (in my case <String, Color>) results in the error:
Cannot pass array of type '[(String, Color)]' as variadic arguments of type '(String, Color)'
In my application the names of the chart series are set dynamically from the data so although I can generate a [String : Color] dictionary or an array of (String, Color) tuples I can't see that it's possible to pass either of these into chartForegroundStyleScale? Unless I'm missing something this seems like a odd limitation in Swift charts that the series names need to be hard coded for this modifier?
OK I've found an approach that works as long as an arbitrary limitation to the number of entries is acceptable (example below with max size of 4:
func keyValuePairs<S, T>(_ from: [(S, T)]) -> KeyValuePairs<S, T> {
switch from.count {
case 1: return [ from[0].0 : from[0].1 ]
case 2: return [ from[0].0 : from[0].1, from[1].0 : from[1].1 ]
case 3: return [ from[0].0 : from[0].1, from[1].0 : from[1].1, from[2].0 : from[2].1 ]
default: return [ from[0].0 : from[0].1, from[1].0 : from[1].1, from[2].0 : from[2].1, from[3].0 : from[3].1 ]
}
In my case I know that there won't be more than 20 mappings so this func can just be extended to accommodate that number.
Not ideal, but it works...
You could also pass an array of colors to .chartForegroundStyleScale(range:). As long as you add the colors to the array in the same order you add your graph marks it should work fine.
Not incredibly elegant either, but this approach works with an arbitrary number or entries.
struct GraphItem: Identifiable {
var id = UUID()
var label: String
var value: Double
var color: Color
}
struct ContentView: View {
let data = [
GraphItem(label: "Apples", value: 2, color: .red),
GraphItem(label: "Pears", value: 3, color: .yellow),
GraphItem(label: "Melons", value: 5, color: .green)
]
var body: some View {
Chart {
ForEach(data, id: \.label) { item in
BarMark(
x: .value("Count", item.value),
y: .value("Fruit", item.label)
)
.foregroundStyle(by: .value("Fruit", item.label))
}
}
.chartForegroundStyleScale(range: graphColors(for: data))
}
func graphColors(for input: [GraphItem]) -> [Color] {
var returnColors = [Color]()
for item in input {
returnColors.append(item.color)
}
return returnColors
}
}
Related
How can I loop from last item to first in a SwiftUi view?
I've tried the following
let myarray = ["1", "2", "3", "4"]
ForEach(stride(from: myarray.count, to: 1, by: -1), id: \.self) { i in
print(myarray[i])
}
//desired output: 4 3 2 1
Thanks!!
You can use reversed:
ForEach(myarray.reversed(), id: \.self) { item in
Text("\(item)")
}
Note that I'm using Text, not print -- this is SwiftUI, so you should have View code inside a ForEach.
Also note that \.self is generally dangerous in ForEach, but I'm assuming this is just sample code. Generally, you want something truly uniquely Identifiable
I want to change dataLabel color for specific value in my bar chart.
documentation says:
Also, if you are rendering a bar/pie/donut/radialBar chart, you can pass a function which returns color based on the value.
I know this is for bar colors but I tried to use it in dataLabel colors. of course it didn't work. any idea how to do it?
my codepen: https://codepen.io/osmanyasircankaya/pen/gOXELmB
style: {
colors: [
function ({ w }) {
if (w.config.series[0].data[4] > 3) {
return "#ff0014";
} else {
return "#1f52b0";
}
},
],
},
docs:
https://apexcharts.com/docs/options/colors/
https://apexcharts.com/docs/options/datalabels/
In your function you checking value of single dataPoint over and over data[4]. What you need to do is checking current series and dataPoint like this:
function ({ seriesIndex,dataPointIndex, w }) {
if (w.config.series[seriesIndex].data[dataPointIndex] > 3) {
return "#ff0014";
} else {
return "#1f52b0";
}
},
I want to merge same "startime" to one (step, distance and calorie) in the list, how can I to do this.
var listNewStepData = arrayListOf<NewStepData>()
data class
data class NewStepData (
val startTime: String?,
val endTime: String?,
val step: Int? = 0,
val distance: Int? = 0,
val calorie: Int? = 0
)
this is sample
NewStepData(startTime=2020-04-14T00:00:00.000Z, endTime=2020-04-14T00:00:00.000Z, step=4433, distance=0, calorie=0)
NewStepData(startTime=2020-04-14T00:00:00.000Z, endTime=2020-04-15T00:00:00.000Z, step=0, distance=0, calorie=1697)
NewStepData(startTime=2020-04-14T00:00:00.000Z, endTime=2020-04-14T00:00:00.000Z, step=0, distance=2436, calorie=0)
NewStepData(startTime=2020-04-15T00:00:00.000Z, endTime=2020-04-15T00:00:00.000Z, step=5423, distance=0, calorie=0)
NewStepData(startTime=2020-04-15T00:00:00.000Z, endTime=2020-04-16T00:00:00.000Z, step=0, distance=0, calorie=1715)
NewStepData(startTime=2020-04-15T00:00:00.000Z, endTime=2020-04-15T00:00:00.000Z, step=0, distance=3196, calorie=0)
I want to get this
NewStepData(startTime=2020-04-14T00:00:00.000Z, endTime=2020-04-15T00:00:00.000Z, step=4433, distance=2436, calorie=1697)
NewStepData(startTime=2020-04-15T00:00:00.000Z, endTime=2020-04-16T00:00:00.000Z, step=5423, distance=3196, calorie=1715)
thanks
You can use groupBy { } for your list. It will return a map of your grouping variable type to lists of your original type. And then, use flatMap to aggregate your data.
I assume that you take maximum end date which is maxBy and sum of distances, and steps which you need sumBy for, and calories which sumByDouble is the best choice.
Here's the sample code:
var grouped = listNewStepData.groupBy { it.startTime }.flatMap { entry -> NewStepData(startTime = entry.key,
endTime = entry.value.maxBy { item -> item.endTime },
step = entry.value.sumBy { item -> item.step },
distance = entry.value.sumBy { item -> item.distance },
calorie = entry.value.sumByDouble { item -> item.calorie })
}
I have a row structure like this
c:[
{ v: 'somevalue'},
{ v: 'somevalue'},
{
v: 'somevalue',
link: 'abc.com'
}
]
now I need all the rows which has link property present in 3rd column, is it possible using getFillteredRows function ?
first, to use cell properties correctly, the structure would resemble the following...
c:[
{ v: 'somevalue'},
{ v: 'somevalue'},
{
v: 'somevalue',
p: {
link: 'abc.com'
}
}
]
to get or set the properties, use the following methods...
getProperty(rowIndex, columnIndex, name)
setProperty(rowIndex, columnIndex, name, value)
adding in getFilteredRows (spelling - one L in filter)...
use the test function, to find all the rows which has link property present in 3rd column
var rowsFound = data.getFilteredRows([{
column: 2,
test: function (value, row, column, table) {
return (table.getProperty(row, column, 'link') !== null);
}
}]);
The code I need is here:
chart.js bar chart color change based on value
Dola changes the color of the bars based on the values of the datasets using myObjBar.datasets[0].bars
I want to do the same thing but with the labels values (good, average, bad) e.g.
var barChartData = {
labels: ["good", "average", "bad"],
datasets: [
{
data: [1, 3, 10]
}
]
};
var ctx = document.getElementById("mycanvas").getContext("2d");
window.myObjBar = new Chart(ctx).Bar(barChartData, {
responsive : true
});
var bars = myObjBar.labels[0]; //I need this line
for(i=0;i<bars.length;i++){
var color="green";
if(bars[i].value=="bad"){
color="red";
}
else if(bars[i].value=="average"){
color="orange"
}
else{
color="green"
}
bars[i].fillColor = color;
}
myObjBar.update();
Instead of using bars[i].value property, you can use bars[i].label which gives you the label of the xAxe.
So in your loop, change to this :
for(i=0;i<bars.length;i++){
var color="green";
if(bars[i].label == "bad"){
color="red";
}
else if(bars[i].label == "average"){
color="orange"
}
else{
color="green"
}
bars[i].fillColor = color;
}
You can find the full code in this jsFiddle and here is its result :