I have the following code:
public data = [
{
value: 61,
color: 'orange',
},
{
value: 29,
color: 'white',
},
{
value: 10,
color: 'blue',
},
];
public pie = d3
.pie()
.padAngle(0)
.value((d: any) => d.value);
const arcs = this.pie(this.data);
Which is basically the outcome of some of the various tutorials about building a donut chart with d3js.
Now I would like to add a custom interface for the items in the data array and also properly type the d parameter in the .value() function.
The problem is, that the #types/d3 package defines the expected data array as number[] and the d parameter as number.
Which means that I cannot use a custom interface for the data items. The typings package for D3 seems to be wrong in this case because all the tutorials I've read do it this way and the code works just fine.
What are my options in this case? Are there any workarounds? Can I override the typings that get into my way?
d3's pie accepts a generic just for that.
Here's how to solve this:
interface IData {
value: number;
color: string;
}
const data: IData[] = [
{
value: 61,
color: 'orange',
},
{
value: 29,
color: 'white',
},
{
value: 10,
color: 'blue',
},
];
const pie = d3
.pie<IData>()
.padAngle(0)
.value(d => d.value);
const arcs = pie(data);
Related
Here is the code (it fails to compile at the sentence that builds the state2, i.e. at the second spread):
let line_id = 6;
let state = {
invoice: {
id: 1015,
description: 'web order',
},
lines: [
{id: 5, description: 'phone', color: 'black'},
{id: 6, description: 'tablet', color: 'blue'},
{id: 7, description: 'computer', color: 'gray'},
]
};
//this alert and this access pattern works, so, I would like to use
//.find... to access element in spread... structure as well
//alert(state['lines'].find(line=>line['id']==line_id)['description']);
let state2 = {
...state,
['lines']: { ...state['lines'],
find(line=>line['id']==line_id): { ...state['lines'].find(line=>line['id']==line_id),
['description']: 'TV',
},
},
};
alert(state2['lines'].find(line=>line['id']==line_id)['description']);
I have state structure, I access lines array, I access the specific line by name-value pair id=6 and I would like to change the value of the field description. This effort is the continuation of https://stackoverflow.com/a/64116308/1375882 in which I am trying to create the general procedure, that use the spread... syntax and the access-by-name strategy for updating the complex object/array tree. In fact - this complex tree is the state of the Redux reducer and that update happend in the action that process the valueSetter function of the AgGrid. But - this is generally the interesting exercise by itself to better understand spread... and JavaScript and JSON structure in JavaScript.
So - the only question is: how to write line
find(line=>line['id']==line_id): { ...state['lines'].find(line=>line['id']==line_id),
so that the code compiles? How can I access the certain element of the array by name-value pair in this setting:
Note, that I am trying to build general code:
find(line=>line[keyFieldName]==keyFieldValue): { ...state['lines'].find(line=>line[keyFieldName]==keyFieldValue),
that uses arbitrary field names and field values - so that such handler can update the any field of the any record of arbitrary 2D AgGrid in React/Redux setting.
The desired result of my code: 1) it should compile; 2) the second alert should return 'TV'.
If I understood correctly what you want to achieve, this should work:
let line_id = 6;
let state = {
invoice: {
id: 1015,
description: 'web order',
},
lines: [{
id: 5,
description: 'phone',
color: 'black'
},
{
id: 6,
description: 'tablet',
color: 'blue'
},
{
id: 7,
description: 'computer',
color: 'gray'
},
]
};
const stateKeyId = 'lines';
const itemKeyId = 'id';
const itemAttr = 'description'
let state2 = {
...state,
[stateKeyId]: state[stateKeyId].map(item => {
if (item[itemKeyId] == line_id) {
return ({
...item,
[itemAttr]: 'TV'
});
}
return item
})
}
console.log(state2);
find(line=>line['id']==line_id) should become [find(line=>line['id']==line_id)], since just like the string it must be between square brackets for js to work properly.
Also, if you are using find from lodash, it will return the object, therefore if you need to use the id as key you can do something like:
[get(find(line => line['id'] === line_id]), 'id')]: whatever
a few observations though:
always please always use === over == in js
avoid snake_case, use camelCase with js, since it's standard
your code is not actually handling missing items correclty, if you need to do so split it in multiple lines since it would be more comprehensible
You can use the map method from arrays to return different elements based on the original one.
Here's how you could use it:
line_id = 6;
state = {
invoice: {
id: 1015,
description: 'web order',
},
lines: [
{id: 5, description: 'phone', color: 'black'},
{id: 6, description: 'tablet', color: 'blue'},
{id: 7, description: 'computer', color: 'gray'},
]
};
state2 = {
...state,
lines: state.lines.map(line => {
if (line.id === line_id)
return { ...line, description: 'YT' }
return { ...line }
})
};
alert(state2['lines'].find(line=>line['id']==line_id)['description']);
I'm working on a line chart of ChartJS library.
I have this object and I would like to transform it in an array, because I want to use its data on it.
Searching on the net I found that map it's the easiest way to do that but I can't actually use it on Object, but only on arrays. How can i convert it in an array or simply use the json data on the line chart? That's the json object i have right now.
{labels: Array(2), values: Array(2)}
labels: (2) ["sales-be", "sales-mw"]
values: (2) [48, 8]
i tried like this but it gives me errors:
var labels = this.errorsList.map(function (e) {
return e.labels;
});
var datas = this.errorsList.map(function (e) {
return e.values;
});
TypeError: this.errorsList.map is not a function
The properties are already arrays. So you could access them directly.
Try the following
export class AppComponent {
errorsList = {
labels: ["sales-be", "sales-mw"],
values: [48, 8]
};
ngOnInit() {
this.chartData = [{
data: this.errorsList.values,
label: 'Sales',
fill: false
}];
this.chartLabels = this.errorsList.labels;
this.chartColors = [{
backgroundColor: 'rgba(0, 0, 0, 0)',
borderColor: 'rgba(255, 15, 15, 1)'
}];
};
}
Template
<div id="chart-container">
<canvas #myCanvas id="canvas" baseChart [chartType]="chartType" [datasets]="chartData" [labels]="chartLabels" [colors]="chartColors" [options]="chartOptions"></canvas>
</div>
Working example: Stackblitz
Here the errorsList is defined with hard values only for illustration. You don't have to define it in your code.
Again this code is an example and it's only here to illustrate how to use the properties from your object, nothing more.
How would I modify this code to dynamically create dataset objects inside of the 'datasets' array for each y-value data array that already exists? This is executed in the same function block as the function adding data to the arrays (where both fall inside of export class AppComponent implements OnInit { ngOnInit() { } }), answered previously here: https://stackoverflow.com/a/56710201/5067233 (as to provide proper source code credit).
My current method is obviously hard-coded and not efficient nor dynamic.
My generic code:
one = [];
two = [];
three = [];
...code that adds data to the arrays
// The actual graph formatting
this.chart = new Chart('canvas', {
type: 'line',
data: {
labels: this.one,
datasets: [
{
data: this.two,
label: 'two',
yAxisID: 'two',
borderColor: '#3CBA9F',
fill: false,
},
{
data: this.three,
label: 'three',
yAxisID: 'three',
scaleOverride: true,
borderColor: '#51b7ed',
fill: false,
},
...n amount of datasets here
]
},
});
A multi-line chart will have three important object arrays that need to be created, assigned to:
chart.data.labels - for x-axis labeling (ex. an independent variable like time)
chart.data.datasets - for y-axis labeling (including multiple lines)
chart.options.scales.yAxes - for the properties of the line(s)
If you have a pre-existing set of data (in this particular case objectArray) that contains pre-existing data associated with particular line names, you can create a constant array that contains strings of the same names (in this case NAME_ARRAY), as well as temporary arrays for each dataset (name1 = []; name2 = []; etc...) acquired by iterating through a for loop.
This can be seen with:
// 'N' PRE-DEFINED CONSTANTS
// Names of the lines you are graphing
const NAME_ARRAY = [
'name1',
'name2',
'name3'
];
// Colors of lines in same order as aforementioned names
const HEX_ARRAY = [
'#3CBA9F',
'#51b7ed',
'#FF0000'
];
// GRAPHING LINE PROPERTIES SHARED BY 'N' LINES
// Doesn't require arguments so can utilize .map() effectively
const datasetLineArray = NAME_ARRAY.map((value,index,array) => {
return {
id: value,
display: false,
ticks: {
// INSERT YOUR PROPERTIES HERE (although none required)
// max: 1000,
// min: 100000,
// reverse: true,
// stepSize: 10000,
// suggestedMax: 500,
// etc...
},
};
});
// Utilizes two arguments so needs object creation upon call rather than pre-defined array mapping (as in .map())
function addDataToDataset(dataArray, index) {
const tempObject = {
data: dataArray,
label: NAME_ARRAY[index],
yAxisID: NAME_ARRAY[index],
borderColor: HEX_ARRAY[index],
fill: false,
scaleOverride: true,
};
return tempObject;
}
// CHART.JS GRAPH CREATION
export class AppComponent implements OnInit {
// Store dataset objects
Dataset_Object_Array = [];
// Store data with same name as incoming objects
name1 = [];
name2 = [];
name3 = [];
// Essentially main()
ngOnInit() {
for (const categoryArray in objectArray) {
const categoryObject = objectArray[categoryArray];
// these names are arbitrary, just note that categoryObject is an object that contains
// the property 'category' which is the equivalent of the name from the constant array
// 'NAME_ARRAY'; however, this object may contain data that we need to parse
// IMPORTANT BIT
this.Dataset_Object_Array.push(addDataToDataset(this[categoryObject.category], categoryArray));
}
// The actual graph formatting
this.chart = new Chart('canvas', {
type: 'line',
data: {
// **IMPORTANT BIT**
labels: this.one, // x-axis labeling
// **IMPORTANT BIT**
datasets: this.Dataset_Object_Array // multi-line y-axis labeling as well as data
},
options: {
responsive: true,
maintainAspectRatio: true,
legend: {
display: true
},
scales: {
xAxes: [{
display: true,
ticks: {
callback: function(value, index, values) {
return parseFloat(value).toFixed(3);
},
}
}],
// **IMPORTANT BIT**
yAxes: datasetLineArray, // property meta-data for the lines
}
}
});
}
}
I've been stuck on this for hours.
I have a list of objects:
const myCompanyList = [
{name: 'coca-cola', size: 'big', color: 'red'},
{name: 'my-cola', size: 'small', color: 'purple'},
{name: 'pepsi', size: 'big', color: 'blue'}
];
I need to get it into this format:
myJson = {
companies: {
big: {
coca-cola: {color: 'red'},
pepsi: {color: 'blue'}
},
small: {
my-cola: {color: 'purple'}
}
}
}
I've tried doing this:
wrapperObject = {};
innerObject = {};
for each object in myCompanyList {
innerObject[object.size] = { object.name : {color: object.color}}
}
but the object[name] bit overwrites. How can I write this so I can dynamically get names on each object/map level? Do I need to make another inner object/map to dynamically write the names?
I've tried writing this in Java but I ended up with 5 dimensional maps and it didn't work, so I just wondered if there was something simple I'm missing - answers in Java, javascript or pseudocode thanks.
You can use Array.reduce() to create a map, Try the following :
let myCompanyList = [{name: "coca-cola", size: "big", color: "red"}, {name: "my-cola", size: "small", color: "purple"},{name: "pepsi", size: "big", color:"blue"}];
let result = {};
result.companies = myCompanyList.reduce((a,curr)=>{
a[curr.size] = a[curr.size] || {};
a[curr.size][curr.name] = {"color" : curr.color};
return a;
},{});
console.log(result);
I am trying to use NVD3 http://nvd3.org/livecode/#codemirrorNav a pie chart. But i want to change the default color. How do i change the color. i am not able to do it.
If you want to use specific color for pie
chart.color(function(d){
return d.data.color
});
Then, organize your data as:
[
{
key: "Cumulative Return",
values: [
{
"label": "One",
"value" : 29.765957771107,
"color" : "#8c564b"
} ,
{
"label": "Three",
"value" : 32.807804682612,
"color" : "#e377c2"
}
]
}
]
You can add colors by passing an array to the 'color()' option. So just add:
.color(['blue', 'green', 'yellow'])
If you want to use these colors for 3 elements.
Note: if you have more than 3 elements, then some colors will be used multiple times.
To use your own colours you will have to override the existing colours, I prefer not to tinker around with the original code.
So this is what I did.
var myColors = ["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf"];
d3.scale.myColors = function() {
return d3.scale.ordinal().range(myColors);
};
nv.addGraph(function() {
var chart = nv.models.pieChart()
.x(function(d) { return d.label })
.y(function(d) { return d.value })
.showLabels(true).color(d3.scale.myColors().range());
d3.select("#chart svg")
.datum(data)
.transition().duration(1200)
.call(chart);
return chart;
});
All I did was add .color(d3.scale.myColors().range())
UPDATE :
Check answer by Christopher Chiche, for the perfect solution.
.color(['blue', 'green', 'yellow'])
Hope this helps.
Here is a note that got me, I was setting chart.color inside of nv.addGraph, this was not working correctly and would instead use the defaults. I then set it outside of this and it worked fine.
I use .color(function(d) {return [d.value > 0 ? 'blue' : 'red']}); to make it change color depending on data value.
Hope this help someone.
Here is the Typescript solution which is similar to the JS version. Let's say you want a Top 10 of your results with 10 different colors :
Here i'll give you the example that work for my PieChart or MultiBarChart
Create a global array of color like colors = ["#D9D9D9", "#4B11A6", ....];
The data has to be formatted in an Object : myGraph { key: "Title", values : { streams to push }}
Then when creating your data table just push the color for each stream :
let stream = {}; let data = []; let i=0;
dataTable.forEach((period) => {
stream = { 'label': period.key, 'value': period.value, 'color': this.colors[i] };
if(period.value !== 0) {
data.push(stream);}
i++;
});
this.myGraph = data;
The if condition is just there to prevent empty piece in the graph
I have used below concept to add custom color:
// for define variable of custom colors
var colors = ["red", "green", "blue"];
...
chart {
color: function(d,i){
return (d.data && d.data.color) || colors[i % colors.length]
}
}
For dynamic color based on JSON Data just add New json obj color such as below concept..
// or set custom colors directly in the data
data = [{
key: "One",
y: 5,
color: "yellow"
},{
key: "Two",
y: 2,
color: "gray"
},{
key: "Three",
y: 9
},
...
];