Reading all objects from multidimensional array - javascript

I want to read all objects from multidimensional array which looks like this:
const useMap = ref([
// ====================== ROW 1 ======================
[
{
x: 1,
y: 1,
currentPosition: false,
color: "white",
isWinning: false,
},
{
x: 16,
y: 1,
currentPosition: false,
color: "white",
isWinning: false,
},
],
// ====================== ROW 2 ======================
[
{
x: 1,
y: 2,
currentPosition: false,
color: "white",
isWinning: false,
},
{
x: 16,
y: 2,
currentPosition: false,
color: "white",
isWinning: false,
},
...
Already Im reading values by single rows like this:
<div v-for="tile in map[0]" :key:="tile" :tile="tile">
<div class="tile">{{ tile.y }} | {{ tile.x }}</div>
</div>
<div v-for="tile in map[1]" :key:="tile" :tile="tile">
<div class="tile">{{ tile.y }} | {{ tile.x }}</div>
</div>
...
Is there any option to read all rows once and not by single rows?

Nested arrays can be flattened with flat, and a computed is used to transform data to required shape before it's used:
const tiles = computed(() => useMap.value.flat());
Then it's used like intended:
<div v-for="tile in tiles" :key:="tile" :tile="tile">

You can achieve this requirement by converting the multi dimensional array into a single dimensional array using Array.flat() method and then iterate the values using v-for in the template.
Live Demo :
const { ref, reactive, readonly, computed } = Vue;
const App = {
setup () {
const useMap = ref([
[
{
x: 1,
y: 1,
currentPosition: false,
color: "white",
isWinning: false,
},
{
x: 16,
y: 1,
currentPosition: false,
color: "white",
isWinning: false,
}
],
[
{
x: 1,
y: 2,
currentPosition: false,
color: "white",
isWinning: false,
},
{
x: 16,
y: 2,
currentPosition: false,
color: "white",
isWinning: false,
}
]
])
const tiles = computed(() => useMap.value.flat());
return { tiles }
}
}
Vue.createApp(App).mount("#app");
<script src="https://unpkg.com/vue#next"></script>
<div id="app">
<div v-for="tile in tiles" :key:="tile" :tile="tile">
<div class="tile">{{ tile.y }} | {{ tile.x }}</div>
</div>
</div>

Related

Why array state change is not rendering again?

I was following the React learn section when i encountered this.
I´m drawing three shapes from an array called shapes. I modify the entire array when i press the button and the array do change when i do this.
import { useState } from "react";
export default function MyApp() {
const initialShapes = [
{ name: "circle", x: 50, y: 10, id: 1 },
{ name: "square", x: 100, y: 10, id: 2 },
{ name: "circle", x: 150, y: 10, id: 3 },
];
const [shapes, setShapes] = useState(initialShapes);
console.log(shapes);
return (
<>
<button
onClick={() => {
setShapes([{ name: "square", x: 20, y: 70, id: 2 }]);
}}
>
Just show one shape
</button>
{initialShapes.map((shape) => {
return (
<div
key={shape.id}
style={
shape.name === "circle"
? {
position: "absolute",
backgroundColor: "red",
width: "30px",
height: "30px",
borderRadius: "50px",
translate: `${shape.x}px ${shape.y}px`,
}
: {
position: "absolute",
backgroundColor: "red",
width: "30px",
height: "30px",
translate: `${shape.x}px ${shape.y}px`,
}
}
></div>
);
})}
</>
);
}
I can verify this by the console log at the beginning of the component. The state does uptade indeed but my visual shapes don't uptade with the new state.
const [shapes, setShapes] = useState(initialShapes);
console.log(shapes);
return (..
Console on first state
[
{
"name": "circle",
"x": 50,
"y": 10,
"id": 1
},
{
"name": "square",
"x": 100,
"y": 10,
"id": 2
},
{
"name": "circle",
"x": 150,
"y": 10,
"id": 3
}
]
Console on second state
[
{
"name": "square",
"x": 20,
"y": 70,
"id": 2
}
]
I know that if it was a mutation to the array, re-rendering won't be trigger but i'm actually setting a new state.
Why does this happen, react shouldn't notice the difference between array changes and trigger the render again ?
You are not using the shapes variable, you are mapping initialShapes.

How to write if else statement in reactJS when using useEffect?

Following is the code I'm using to return the Plotly graph, What I need to do in here is I need to return a message or different layout when the graph data is empty or there is nothing to show on the graph. use a default content like no data available if no data for charts. How do I do that?
import React, { useEffect } from "react";
import Plot from "react-plotly.js";
import PropTypes from "prop-types";
let dataArray;
let index;
let obj;
function PlotlyStackedChart({ labels, data, xTitle, yTitle, mainTitle, preHeading }) {
useEffect(() => {
dataArray = [];
for (index = 0; index < data.length; index += 1) {
obj = {
x: labels,
y: data[index].data,
type: "bar",
name: data[index].name,
};
dataArray.push(obj);
}
}, [data]);
return (
<>
<Plot
data={dataArray}
layout={{
barmode: "stack",
autosize: true,
title: {
text: preHeading + mainTitle,
y: 0.9,
},
margin: {
t: 225,
},
xaxis: {
// all "layout.xaxis" attributes: #layout-xaxis
title: {
text: xTitle,
font: {
family: "Arial Black",
},
}, // more about "layout.xaxis.title": #layout-xaxis-title
// dtick: 1,
},
yaxis: {
// all "layout.yaxis" attributes: #layout-yaxis
title: {
text: yTitle,
font: {
family: "Arial Black",
},
}, // more about "layout.yaxis.title": #layout-yaxis-title
},
font: {
// family: "Courier New, monospace",
size: 12,
color: "#7f7f7f",
},
legend: {
bgcolor: "transparent",
x: 0,
y: 1.4,
xanchor: "auto",
traceorder: "normal",
orientation: "h",
},
marker: { size: 40 },
}}
useResizeHandler
style={{ width: "100%", height: 600 }}
/>
</>
);
}
export default PlotlyStackedChart;
PlotlyStackedChart.defaultProps = {
xTitle: "",
yTitle: "",
mainTitle: "",
preHeading: "",
};
// Typechecking props for the MDDatePicker
PlotlyStackedChart.propTypes = {
labels: PropTypes.oneOfType([PropTypes.string, PropTypes.array]).isRequired,
xTitle: PropTypes.string,
yTitle: PropTypes.string,
mainTitle: PropTypes.string,
preHeading: PropTypes.string,
data: PropTypes.oneOfType([PropTypes.string, PropTypes.array, PropTypes.object]).isRequired,
};
explaining further, If there is no data to plot in the graph I want to return the following code else the current one which retrieves the data and plot in the graph.
return {
"layout": {
"xaxis": {
"visible": false
},
"yaxis": {
"visible": false
},
"annotations": [
{
"text": "No matching data found",
"xref": "paper",
"yref": "paper",
"showarrow": false,
"font": {
"size": 28
}
}
]
}
}
You can try returning the empty content template if there is no content.
import React, { useEffect } from "react";
import Plot from "react-plotly.js";
import PropTypes from "prop-types";
let dataArray;
let index;
let obj;
function PlotlyStackedChart({ labels, data, xTitle, yTitle, mainTitle, preHeading }) {
useEffect(() => {
if(data?.length){ // <--- place condition here
dataArray = [];
for (index = 0; index < data.length; index += 1) {
obj = {
x: labels,
y: data[index].data,
type: "bar",
name: data[index].name,
};
dataArray.push(obj);
}
}
}, [data]);
// Place this conditional return
if(!data?.length) {
return <>No data found</>
}
return (
<>
<Plot
data={dataArray}
layout={{
barmode: "stack",
autosize: true,
title: {
text: preHeading + mainTitle,
y: 0.9,
},
margin: {
t: 225,
},
xaxis: {
// all "layout.xaxis" attributes: #layout-xaxis
title: {
text: xTitle,
font: {
family: "Arial Black",
},
}, // more about "layout.xaxis.title": #layout-xaxis-title
// dtick: 1,
},
yaxis: {
// all "layout.yaxis" attributes: #layout-yaxis
title: {
text: yTitle,
font: {
family: "Arial Black",
},
}, // more about "layout.yaxis.title": #layout-yaxis-title
},
font: {
// family: "Courier New, monospace",
size: 12,
color: "#7f7f7f",
},
legend: {
bgcolor: "transparent",
x: 0,
y: 1.4,
xanchor: "auto",
traceorder: "normal",
orientation: "h",
},
marker: { size: 40 },
}}
useResizeHandler
style={{ width: "100%", height: 600 }}
/>
</>
);
}
export default PlotlyStackedChart;
useEffect is entirely irrelevant to the problem you describe.
If you want to render something different when the array is empty, then do it in the render logic, not as an effect.
if (data.length === 0) {
return <Loading />
} else {
return <Plot .../>
}
That said, your effect logic is broken. You can't just assign a new array to a variable and have React render it. You've done nothing to tell React that it needs to do a re-render. Make data a state variable (with useState).

why apexchart not being rendered correctly in vue(spa) when chart type changes?

I have a v-tab items bar with 3 type of graphs, all have to show data from a single variable. variable is being updated upon selecting an option from v-select.
it renders correctly 1st time but then upon changing the option all data renders correctly in all the charts except for the xaxis categories. xaxis values do not appear along the ticks. but are visible in tool tip view.
On resizing the window, the data appears again, cant forcefully render the chart upon change.
need some help.
<v-tabs
center-active
v-model="ctab"
color="#a24187"
slider-color="#a24187"
>
<v-tab >
<v-icon color="#2E93fA">mdi-poll</v-icon> Bar</v-tab
>
<v-tab >
<v-icon color="#2E93fA">mdi-chart-timeline-variant</v-icon>Line
</v-tab>
<v-tab >
<v-icon color="#2E93fA">mdi-scatter-plot</v-icon> Scatter</v-tab
>
</v-tabs>
<v-tabs-items v-model="ctab" class="px-2">
<v-tab-item>
<apexchart
ref="chart"
width="700"
height="260"
type="bar"
:options="chartoptions"
:series="sr"
></apexchart>
</v-tab-item>
<v-tab-item>
<apexchart
ref="chart"
width="700"
height="260"
type="line"
:options="chartoptions"
:series="sr"
></apexchart>
</v-tab-item>
<v-tab-item>
<apexchart
ref="chart"
width="700"
height="260"
type="scatter"
:options="chartoptions"
:series="sr"
></apexchart>
</v-tab-item>
</v-tabs-items>
Variables and properties being used
sr: [
{
name: "",
data: [],
},
],
chartoptions: {
chart: {
id: "UNChart",
},
xaxis: {
categories: [],
axisBorder: {
show: true,
color: "#a24187",
height: 1,
width: "100%",
offsetX: 0,
offsetY: 0,
},
axisTicks: {
show: true,
borderType: "solid",
color: "#a24187",
height: 6,
offsetX: 0,
offsetY: 0,
},
},
yaxis: {
axisBorder: {
show: true,
color: "#a24187",
offsetX: 0,
offsetY: 0,
},
axisTicks: {
show: true,
borderType: "solid",
color: "#a24187",
width: 6,
offsetX: 0,
offsetY: 0,
},
},
title: {
text: undefined,
align: "left",
margin: 10,
offsetX: 0,
offsetY: 0,
floating: false,
style: {
fontSize: "14px",
fontWeight: "bold",
fontFamily: undefined,
color: "#263238",
},
},
},
Method being used to get data
showchart() {
this.tempData = [];
this.tempCategory = [];
this.sr[0].data = [];
this.sr[0].name = "";
this.chartoptions.xaxis.categories = [];
for (let index = 0; index < this.newdata.length; index++) {
if (this.criteria === this.newdata[index].specific_title) {
this.tempData.push(this.newdata[index].current_value);
this.tempCategory.push(this.newdata[index].specific_name);
}
}
// ApexCharts.exec(
// "UNChart",
// "updateOptions",
// {
// xaxis: {
// categories: this.tempCategory,
// },
// },
// false,
// true
// );
// ApexCharts.exec(
// "UNChart",
// "updateSeries",
// [
// {
// data: this.tempData,
// name: this.criteria,
// },
// ],
// true
// );
this.sr = [
{
name: this.criteria,
data: this.tempData,
},
];
this.chartoptions = {
...this.chartoptions,
...{
xaxis: { categories: this.tempCategory },
title: { text: this.filter.theme.name },
},
};
// this.$refs.chart.updateOptions({ xaxis: { categories: this.tempCategory } });
},
1
2
3
4
5

Update tooltip data without reloading whole chart in Canvasjs

I want to update my Tooltip in my canvas js Chart without reloading the whole chart.
let arr = this.graph_arr;
let a = [];
let b = [];
arr.map((val) => {
val.data.map((val2) => {
b.push({
x: new Date(val2.date),
y: val2.revenue,
cn: val2.check_in,
rp: val2.rev_par,
arr: val2.avrr,
adr: val2.avg_daily_rate,
date: moment(val2.date).format('Do, MMMM'),
day: moment(val2.date).format('dddd')
})
})
a.push({
type: "spline",
name: val.channel_img.split('.')[0].toUpperCase(),
markerSize: 1,
showInLegend: true,
dataPoints: b,
label: val.channel_img,
})
b = [];
})
let chart = new CanvasJS.Chart("chartContainer", {
animationEnabled: true,
theme: "light2",
axisY2: {
valueFormatString: "1'%'"
},
axisY: {
suffix: "%"
},
axisX: {
gridThickness: 1,
valueFormatString: "DD/MMM"
},
legend: {
cursor: "pointer",
itemclick: this.toogleDataSeries
},
toolTip: {
shared: false,
content: this.selected == 'arr' ?
`<div style='\"'width: 210px;'\"'>ARR: {arr}, date: {date} </div>` :
this.selected == 'adr' ?
`<div style='\"'width: 210px;'\"'>ARR: {arr}, date: {date] </div>` : null,
cornerRadius: '8'
},
data: a
});
chart.render();
I have this Custom Tooltip. I want to change data in it from a dropdown without reloading. Currently I am using ternary operator and reloading chart. I want to change Tooltip content without reloading when user select from dropdown.
You can programmatically show toolip by using showAtX method. Below is an example:
var chart = new CanvasJS.Chart("chartContainer", {
title:{
text: "Show Tooltip based on dropdown"
},
data: [
{
type: "column",
dataPoints: [
{ x: 10, y: 71 },
{ x: 20, y: 55 },
{ x: 30, y: 50 },
{ x: 40, y: 65 },
{ x: 50, y: 95 },
{ x: 60, y: 68 },
{ x: 70, y: 28 },
{ x: 80, y: 34 },
{ x: 90, y: 14 }
]
}
]
});
chart.render();
var xVal = document.getElementById("xVal");
//Pass dataPoints as option to drop-down
for(var i=0; i<chart.options.data[0].dataPoints.length;i++){
var xValOption = document.createElement("option");
xValOption.text = chart.options.data[0].dataPoints[i].x = chart.options.data[0].dataPoints[i].x;
xVal.appendChild(xValOption);
}
xVal.addEventListener( "change", showTooltip);
//
function showTooltip(e){
if(xVal.value)
chart.toolTip.showAtX(Number(xVal.value));
else
chart.toolTip.hide();
}
<script src="https://canvasjs.com/assets/script/canvasjs.min.js"></script>
<div id="chartContainer" style="height: 360px; width: 80%; float: left"></div>
<div style="float: right; width: 15%">
Show Tooltip for <select id="xVal">
<option>Select</option>
</select>
</div>

Angular How to filter array data by searching in input

How to filter an array object in input text - angular
I'm trying to make an search bar to filter where the user can search the location/description which I name it "sensor".
roomlist.component.ts
validateForm: FormGroup;
rowData: templogRecord[] = [];
option: any = [];
onLoad() {
this.rowData = record.default.records;
this.option = [];
this.rowData.forEach(room => {
this.option.push({
tooltip: {
formatter: "{a} <br/>{b} : {c}°"
},
toolbox: {
show: true,
feature: {
mark: { show: false },
restore: { show: false },
saveAsImage: { show: false }
}
},
series: [
{
name: room.sensor,
type: 'gauge',
center: ['40%', '70%'],
splitNumber: 10,
radius: '70%',
axisLine: {
lineStyle: {
color: [[0.2, '#48b'], [0.8, '#228b22'], [1, '#ff0000']],
width: 8
}
},
axisTick: {
splitNumber: 10,
length: 12,
lineStyle: {
color: 'auto'
}
},
axisLabel: {
textStyle: {
color: 'auto'
}
},
splitLine: {
show: true,
length: 30,
lineStyle: {
color: 'auto'
}
},
pointer: {
width: 5
},
title: {
show: true,
offsetCenter: [0, '65%'],
textStyle: {
fontWeight: 'bolder'
}
},
detail: {
formatter: '{value}°',
textStyle: {
color: 'auto',
fontWeight: 'bolder'
}
},
data: [{ value: this.tempGenerator(), name: "Temperature" }]
},
{
name: '转速',
type: 'gauge',
center: ['70%', '25%'],
splitNumber: 10,
radius: '40%',
axisLine: {
lineStyle: {
width: 8
}
},
axisTick: {
length: 12,
lineStyle: {
color: 'auto'
}
},
splitLine: {
length: 20,
lineStyle: {
color: 'auto'
}
},
pointer: {
width: 5
},
title: {
show: true,
offsetCenter: [0, '80%'],
textStyle: {
fontWeight: 'bolder',
}
},
detail: {
formatter: '{value}%',
offsetCenter: [0, '55%'],
textStyle: {
color: 'auto',
fontSize: 18,
fontWeight: 'bolder'
}
},
data: [{ value: 1.5, name: "Humidity" }]
}
]
});
});
}
tempGenerator() {
var time = 12;
var num = Math.random() * 100;
var tempBase = Math.round(num);
var fluc = [0, 1, 1, 2, 1, 1, 2.5, 3.5, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
return tempBase * fluc[time];
}
searchData(searchValue: any) {
if (searchValue.length >= 3) {
this.rowData = this.rowData.filter((data: templogRecord) => {
console.log(data['sensor']);
});
} else if (searchValue.length < 1) {
console.log('empty')
}
}
}
roomlist.json
{
"records": [
{
"dateandtime": "2018-06-14 02:24:02",
"sensor": "Nine Seal Area",
"temperature": "25.9",
"humidity": "99.9"
},
{
"dateandtime": "2018-06-14 02:24:02",
"sensor": "Ten Line2",
"temperature": "25.9",
"humidity": "99.9"
},
{
"dateandtime": "2018-06-14 02:22:01",
"sensor": "Eight Line1",
"temperature": "25.9",
"humidity": "99.9"
}
]
}
room-list.component.html
<div class="flex-container">
<div class="date-filter">
<nz-input-group [nzSuffix]="suffixIconSearch">
<input type="text"
nz-input placeholder="Search"
[(ngModel)]="filterSearch"
(ngModelChange)="searchData($event)"
/>
</nz-input-group>
<ng-template #suffixIconSearch>
<i nz-icon nzType="search"></i>
</ng-template>
</div>
<ul class="cards">
<li class="cards__item" *ngFor="let data of option">
<div class="card">
<div echarts [options]="data" [autoResize]="true"></div>
<div class="card__content">
<button class="btn btn--block card__btn">Button</button>
</div>
</div>
</li>
</ul>
</div>
In the searchData function, I'm trying to make it filtering while typing based in location/description which I named it "sensor".
Each time you make a search, you're filtering the elements in your array and giving the output to your original array. Consequently, you loose your data.
Why don't you create 2 variables:
The original array which doesn't change ( containing your data ) which should be in a provider but in our example we'll declare it in your component.
The filtered array which you're going to display
searchData(searchValue: any) {
this.filteredData = this.rowData.filter((item: templogRecord) => {
return item.sensor.toLowerCase().includes(searchValue.toLowerCase());
});
}
I would recommend this solution (Based on materials autocomplete: https://stackblitz.com/angular/lndebkoyare?file=app%2Fautocomplete-filter-example.ts)
In your component:
import {Component, OnInit} from '#angular/core';
import {FormControl} from '#angular/forms';
import {Observable} from 'rxjs';
import {map, startWith} from 'rxjs/operators';
/**
* #title Filter autocomplete
*/
#Component({
selector: 'autocomplete-filter-example',
templateUrl: 'autocomplete-filter-example.html',
styleUrls: ['autocomplete-filter-example.css'],
})
export class FilterExample implements OnInit {
// your control for input
searchControl = new FormControl();
// your whole set of options
options: string[] = ['One', 'Two', 'Three'];
// your current result based on filters input
filteredOptions: Observable<string[]>;
ngOnInit() {
this.filteredOptions = this.searchControl.valueChanges
.pipe(
startWith(''),
map(value => this._filter(value))
);
}
private _filter(value: string): string[] {
const filterValue = value.toLowerCase();
return this.options.filter(option => option.toLowerCase().includes(filterValue));
}
}
Your template would look like this:
<div class="flex-container">
<div class="date-filter">
<nz-input-group [nzSuffix]="suffixIconSearch">
<input type="text"
nz-input placeholder="Search"
[formControl]="searchControl"
/>
</nz-input-group>
<ng-template #suffixIconSearch>
<i nz-icon nzType="search"></i>
</ng-template>
</div>
<ul class="cards">
<li class="cards__item" *ngFor="let data of filteredOptions | async">
<div class="card">
<div echarts [options]="data" [autoResize]="true"></div>
<div class="card__content">
<button class="btn btn--block card__btn">Button</button>
</div>
</div>
</li>
</ul>
Every time value on input changes the observable valueChanges on searchControl emits the current value of this input field. This part map(value => this._filter(value)) returns a filtered subset of your options array. Which can be displayd using the async pipe: <li class="cards__item" *ngFor="let data of filteredOptions | async">...</li>
DO NOT USE find or includes, because these are not supported by the Internet Explorer.
How about using javascript's filter function? See an example below.
The following example assumes that you are targeting the sensor element in the object.
Click here for a DEMO
const test = {
"records": [
{
"dateandtime": "2018-06-14 02:24:02",
"sensor": "Nine Seal Area",
"temperature": "25.9",
"humidity": "99.9"
},
{
"dateandtime": "2018-06-14 02:24:02",
"sensor": "Ten Line2",
"temperature": "25.9",
"humidity": "99.9"
},
{
"dateandtime": "2018-06-14 02:22:01",
"sensor": "Eight Line1",
"temperature": "25.9",
"humidity": "99.9"
}
]
};
let found = [];
const searchTerm = 'Eight Line1';
found = test.records.filter(function(element) {
return element.sensor.toLowerCase() == searchTerm.toLowerCase();
});
console.log('found ' , found[0]);
Update
To perform a partial search (searching for a part of the string), you can safely use indexOf. See an example below,
console.log("Eight Line1".indexOf("Ei") !== -1);
Hope this helps,
Thanks.

Categories