const renderItem = ({ item }: {item : siteDataCard}) => {
return (
<SiteCard key={item.key} siteName = {item.siteName} status={item.status} alarmCount ={item.alarmCount} siteCapacity ={item.siteCapacity}
performanceRatio = {item.performanceRatio} dailyEnergy = {item.energyToday} outputActivePower = {item.outputActivePower}
></SiteCard>
)
};
const AccountScreen = ({sites, ...props} : {sites :Array<site>}) => {
let [skip, setSkip] = useState(true);
let [dataLoaded, setDataLoaded] = useState(false);
let [page, setPage] = useState(1);
let [siteDataList, setSiteDataList] = useState<Array<siteDataCard>>([]);
let [visible, setVisible] = useState(false);
let [errors, setErrors] = useState('');
let [dailyEnergy, setDailyEnergy] = useState({});
let [siteKeys,setSiteKeys] = useState<Array<string>>([]);
let [search, setSearch] = useState('');
let {data : sitesData, isSuccess : sitesQuerySuccess} = useLoggedInUserSitesQuery({per : 10, page : page, search : search});
let [trigger, result] = useLazySiteAlarmCountDataQuery();
let [sitesTrigger, {data : sitesResult, isSuccess : isLazySiteQuerySuccess}] = useLazyLoggedInUserSitesQuery();
let [siteLevelDataTrigger, {data : siteLevelResult, isSuccess : isLazySiteLevelQuerySuccess}] = useLazySiteLevelDataForSiteQuery();
let [latestDataTrigger, {data : latestDataResult}] = useLazyLatestEventDataQuery();
const onChange = (search : string) => {
setSearch(search);
setSiteDataList([]);
}
if(sites && sites.length > 0 && dataLoaded === false) {
let siteKeys: Array<string> = [];
sites.forEach(site => {
siteKeys.push(site.site_key);
})
trigger({siteKeys});
let siteLevelDataPayloadForEnergyAndPR : siteDataQuery = {
startTime : moment().startOf('day').valueOf(),
endTime : moment().endOf('day').valueOf(),
timeGrouping : 'DAY',
cumulate : false,
provideBufferData : false,
bufferInterval : null,
suppressErrors : true,
limit : null,
siteParameterAggregationType : {}
}
if(siteKeys.length > 0) {
let paramAggregation : {[key : string] : any} = siteLevelDataPayloadForEnergyAndPR.siteParameterAggregationType;
siteKeys.forEach(siteKey => {
let option : Array<ParameterDataQueryOption> = [{ parameterName: "Daily Energy", dataQueryOperation: null }, { parameterName: "Total Energy", dataQueryOperation: null },
{parameterName : 'Performance Ratio', dataQueryOperation : null}];
let currentSiteKey : string = siteKey;
paramAggregation[currentSiteKey] = option;
})
siteLevelDataPayloadForEnergyAndPR.siteParameterAggregationType = paramAggregation
}
siteLevelDataTrigger(siteLevelDataPayloadForEnergyAndPR);
let latestDataPayload : siteLatestDataRequest = {
startTime : moment().startOf('day').valueOf(),
endTime : moment().endOf('day').valueOf(),
validateParameterPresence : true,
suppressErrors : true,
siteParameterList : {}
}
if(siteKeys.length > 0) {
let siteParameterList : {[key : string] : any} = latestDataPayload.siteParameterList;
siteKeys.forEach(siteKey => {
let option : Array<string> = ['Output Active Power'];
let currentSiteKey : string = siteKey;
siteParameterList[currentSiteKey] = option;
})
latestDataPayload.siteParameterList = siteParameterList;
}
latestDataTrigger(latestDataPayload);
setDataLoaded(true)
}
return (
<AccountContainer>
<Header ></Header>
<ScrollView style = {accountStyles.accountBar}>
<AccountBar></AccountBar>
</ScrollView>
{sitesData ? sitesData.elements.map(site => {
let alarmData = result ? result.data ? result.data : null : null;
let totalAlarmCount = 0;
let prValue : number | string = '-';
let dailyEnergyValue : number | string = '-';
let oapValue : number | string = '-';
let allDataLoaded = false;
if(alarmData !== null) {
alarmData.forEach(alarm => {
if(alarm.siteKey === site.site_key) {
totalAlarmCount += alarm.noDataInverterAlarmCount;
totalAlarmCount += alarm.noDataRuleEvaluationLogsCount;
totalAlarmCount += alarm.openCommunicationLogsCount;
totalAlarmCount += alarm.openInverterAlarmCount;
totalAlarmCount += alarm.openRuleEvaluationLogsCount;
}
})
}
if(siteLevelResult) {
let originalResult = siteLevelResult.result;
originalResult.forEach(siteResult => {
if(siteResult.site_key === site.site_key) {
let valueData = siteResult.data;
valueData.forEach(siteData => {
console.log(siteData)
if(siteData.parameter_name === "Daily Energy") {
dailyEnergyValue = siteData.value;
}
if(siteData.parameter_name === 'Performance Ratio') {
prValue = siteData.value;
}
})
}
})
}
if(latestDataResult) {
let originalResult = latestDataResult.result;
originalResult.forEach(siteResult => {
if(siteResult.site_key === site.site_key) {
let valueData = siteResult.data;
valueData.forEach(siteData => {
console.log(siteData)
if(siteData.parameter_name === "Output Active Power") {
oapValue = siteData.value;
}
})
}
})
allDataLoaded = true;
}
if(allDataLoaded) {
let sitePresent : boolean = false;
siteDataList.forEach(siteCard => {
if(siteCard.key === site.site_key) {
sitePresent = true;
}
});
if(!sitePresent)
siteDataList.push({key : site.site_key, siteName : site.name, status : site.status, alarmCount : totalAlarmCount, siteCapacity : site.site_capacity, performanceRatio : prValue, energyToday : dailyEnergyValue, outputActivePower : oapValue});
}
})
: null}
<View style={accountStyles.searchBar}>
<SearchTextBar
label="Search"
value={search}
onChangeText={text => onChange(text)}
autoCapitalize="none"
></SearchTextBar>
</View>
<View style = {accountStyles.flatListView}>
<FlatList data = {siteDataList} renderItem = {renderItem} />
</View>
<View style = {accountStyles.actionButtons}>
<Button mode="contained" style = {{backgroundColor : 'white'}} disabled = {page === 1? true : false} onPress={() => {
setPage(page - 1);
setSiteDataList([]);
let siteKeys: Array<string> = [];
sites.forEach(site => {
siteKeys.push(site.site_key);
})
trigger({siteKeys});
let siteLevelDataPayloadForEnergyAndPR : siteDataQuery = {
startTime : moment().startOf('day').valueOf(),
endTime : moment().endOf('day').valueOf(),
timeGrouping : 'DAY',
cumulate : false,
provideBufferData : false,
bufferInterval : null,
suppressErrors : true,
limit : null,
siteParameterAggregationType : {}
}
if(siteKeys.length > 0) {
let paramAggregation : {[key : string] : any} = siteLevelDataPayloadForEnergyAndPR.siteParameterAggregationType;
siteKeys.forEach(siteKey => {
let option : Array<ParameterDataQueryOption> = [{ parameterName: "Daily Energy", dataQueryOperation: null }, { parameterName: "Total Energy", dataQueryOperation: null },
{parameterName : 'Performance Ratio', dataQueryOperation : null}];
let currentSiteKey : string = siteKey;
paramAggregation[currentSiteKey] = option;
})
siteLevelDataPayloadForEnergyAndPR.siteParameterAggregationType = paramAggregation
}
siteLevelDataTrigger(siteLevelDataPayloadForEnergyAndPR);
let latestDataPayload : siteLatestDataRequest = {
startTime : moment().startOf('day').valueOf(),
endTime : moment().endOf('day').valueOf(),
validateParameterPresence : true,
suppressErrors : true,
siteParameterList : {}
}
if(siteKeys.length > 0) {
let siteParameterList : {[key : string] : any} = latestDataPayload.siteParameterList;
siteKeys.forEach(siteKey => {
let option : Array<string> = ['Output Active Power'];
let currentSiteKey : string = siteKey;
siteParameterList[currentSiteKey] = option;
})
latestDataPayload.siteParameterList = siteParameterList;
}
latestDataTrigger(latestDataPayload);
sitesData ? sitesData.elements.map(site => {
let alarmData = result ? result.data ? result.data : null : null;
let totalAlarmCount = 0;
let prValue : number | string = '-';
let dailyEnergyValue : number | string = '-';
let oapValue : number | string = '-';
let allDataLoaded = false;
if(alarmData !== null) {
alarmData.forEach(alarm => {
if(alarm.siteKey === site.site_key) {
totalAlarmCount += alarm.noDataInverterAlarmCount;
totalAlarmCount += alarm.noDataRuleEvaluationLogsCount;
totalAlarmCount += alarm.openCommunicationLogsCount;
totalAlarmCount += alarm.openInverterAlarmCount;
totalAlarmCount += alarm.openRuleEvaluationLogsCount;
}
})
}
if(siteLevelResult) {
let originalResult = siteLevelResult.result;
originalResult.forEach(siteResult => {
if(siteResult.site_key === site.site_key) {
let valueData = siteResult.data;
valueData.forEach(siteData => {
console.log(siteData)
if(siteData.parameter_name === "Daily Energy") {
dailyEnergyValue = siteData.value;
}
if(siteData.parameter_name === 'Performance Ratio') {
prValue = siteData.value;
}
})
}
})
}
if(latestDataResult) {
let originalResult = latestDataResult.result;
originalResult.forEach(siteResult => {
if(siteResult.site_key === site.site_key) {
let valueData = siteResult.data;
valueData.forEach(siteData => {
console.log(siteData)
if(siteData.parameter_name === "Output Active Power") {
oapValue = siteData.value;
}
})
}
})
allDataLoaded = true;
}
if(allDataLoaded) {
let sitePresent : boolean = false;
siteDataList.forEach(siteCard => {
if(siteCard.key === site.site_key) {
sitePresent = true;
}
});
if(!sitePresent)
siteDataList.push({key : site.site_key, siteName : site.name, status : site.status, alarmCount : totalAlarmCount, siteCapacity : site.site_capacity, performanceRatio : prValue, energyToday : dailyEnergyValue, outputActivePower : oapValue});
}
}): null
}}>
<Text style = {{color : 'black'}}>Back</Text>
</Button>
<Button mode="contained" style = {{backgroundColor : 'white'}} onPress={() => {
setPage(page +1);
setSiteDataList([]);
let siteKeys: Array<string> = [];
sites.forEach(site => {
siteKeys.push(site.site_key);
})
trigger({siteKeys});
let siteLevelDataPayloadForEnergyAndPR : siteDataQuery = {
startTime : moment().startOf('day').valueOf(),
endTime : moment().endOf('day').valueOf(),
timeGrouping : 'DAY',
cumulate : false,
provideBufferData : false,
bufferInterval : null,
suppressErrors : true,
limit : null,
siteParameterAggregationType : {}
}
if(siteKeys.length > 0) {
let paramAggregation : {[key : string] : any} = siteLevelDataPayloadForEnergyAndPR.siteParameterAggregationType;
siteKeys.forEach(siteKey => {
let option : Array<ParameterDataQueryOption> = [{ parameterName: "Daily Energy", dataQueryOperation: null }, { parameterName: "Total Energy", dataQueryOperation: null },
{parameterName : 'Performance Ratio', dataQueryOperation : null}];
let currentSiteKey : string = siteKey;
paramAggregation[currentSiteKey] = option;
})
siteLevelDataPayloadForEnergyAndPR.siteParameterAggregationType = paramAggregation
}
siteLevelDataTrigger(siteLevelDataPayloadForEnergyAndPR);
let latestDataPayload : siteLatestDataRequest = {
startTime : moment().startOf('day').valueOf(),
endTime : moment().endOf('day').valueOf(),
validateParameterPresence : true,
suppressErrors : true,
siteParameterList : {}
}
if(siteKeys.length > 0) {
let siteParameterList : {[key : string] : any} = latestDataPayload.siteParameterList;
siteKeys.forEach(siteKey => {
let option : Array<string> = ['Output Active Power'];
let currentSiteKey : string = siteKey;
siteParameterList[currentSiteKey] = option;
})
latestDataPayload.siteParameterList = siteParameterList;
}
latestDataTrigger(latestDataPayload);
sitesData ? sitesData.elements.map(site => {
let alarmData = result ? result.data ? result.data : null : null;
let totalAlarmCount = 0;
let prValue : number | string = '-';
let dailyEnergyValue : number | string = '-';
let oapValue : number | string = '-';
let allDataLoaded = false;
if(alarmData !== null) {
alarmData.forEach(alarm => {
if(alarm.siteKey === site.site_key) {
totalAlarmCount += alarm.noDataInverterAlarmCount;
totalAlarmCount += alarm.noDataRuleEvaluationLogsCount;
totalAlarmCount += alarm.openCommunicationLogsCount;
totalAlarmCount += alarm.openInverterAlarmCount;
totalAlarmCount += alarm.openRuleEvaluationLogsCount;
}
})
}
if(siteLevelResult) {
let originalResult = siteLevelResult.result;
originalResult.forEach(siteResult => {
if(siteResult.site_key === site.site_key) {
let valueData = siteResult.data;
valueData.forEach(siteData => {
console.log(siteData)
if(siteData.parameter_name === "Daily Energy") {
dailyEnergyValue = siteData.value;
}
if(siteData.parameter_name === 'Performance Ratio') {
prValue = siteData.value;
}
})
}
})
}
if(latestDataResult) {
let originalResult = latestDataResult.result;
originalResult.forEach(siteResult => {
if(siteResult.site_key === site.site_key) {
let valueData = siteResult.data;
valueData.forEach(siteData => {
console.log(siteData)
if(siteData.parameter_name === "Output Active Power") {
oapValue = siteData.value;
}
})
}
})
allDataLoaded = true;
}
if(allDataLoaded) {
let sitePresent : boolean = false;
siteDataList.forEach(siteCard => {
if(siteCard.key === site.site_key) {
sitePresent = true;
}
});
if(!sitePresent)
siteDataList.push({key : site.site_key, siteName : site.name, status : site.status, alarmCount : totalAlarmCount, siteCapacity : site.site_capacity, performanceRatio : prValue, energyToday : dailyEnergyValue, outputActivePower : oapValue});
}
}): null;
setSiteDataList(siteDataList);
}}>
<Text style = {{color : 'black'}}>Next</Text>
</Button>
</View>
</AccountContainer>
)
}
const accountStyles = StyleSheet.create({
accountBar : {
width : '100%',
backgroundColor : '#313644',
paddingLeft : 20,
paddingRight : 20,
paddingTop : 30,
flex : 1
},
pageView : {
width : '100%',
},
searchBar : {
alignItems : 'center',
padding : 10,
},
flatListView : {
flex : 3,
},
actionButtons : {
display : 'flex',
flexDirection : 'row',
marginVertical : 10,
alignItems : 'center',
justifyContent : 'space-around'
}
})
function mapStateToProps(state : RootState) {
return {
sites : state.sitesSlice.sites
};
}
export default connect(mapStateToProps)(AccountScreen)
I am using Stack RTK Query, React Native for Mobile App Development. I am absolute beginner to using these 2 stacks. I am looking into using a flatlist and adding pagination to it using a back and a next button provided just at the end of the list. Now the data for the list is fetched using 4 different queries, First problem is I am looking at a way to combine these queries and execute them parallely, Second I am looking at how to use the query trigger manually, I am using a lazy use query hook but where should I trigger the same, should I change the state and the hook should trigger automatically or should I compute all the data on press of next/back and then rerender the component?
This is how I would approach it
In your createApi
getUsers: builder.query({
query: (page) => `users?page=${page}&perPage=25`,
}),
In your component
const [noMoreResults, setNoMoreResults] = useState(false);
const [page, setPage] = useState(1)
const [users, setUsers] = useState([]);
const { data = [] } = useGetUsersQuery(page, {
skip: noMoreResults
});
useEffect(() => {
if(data.length) {
setUsers([...users, ...data]);
} else if(page > 1) {
setNoMoreResults(true);
}
}, [data]);
return (
<>
{users.map((user) => (
<>
User: {user.id} <br />
<>
))}
<input type="button" onClick={() => setPage(page+1)}>Next page</input>
</>
)
This should work.
Related
I am trying to implement a custom select editor with dynamic options updating in ag-grid.
Saw an example of the same in vue js
AG Grid Vue example
How can to render the dynamic options in table cell?
//custom dropdown for cell editor
export const CustomDropDownEditor = forwardRef((props: any, ref) => {
const createInitialState = () => {
let startValue = props.value;
const filter = props?.values?.filter((item) => item.isSelected);
if (filter?.length) {
startValue = filter[0].dataFieldLookupId;
} else {
startValue = -1;
}
return {
value: startValue,
};
};
const [value, setValue] = useState(-1);
const refSelect = useRef<HTMLSelectElement>(null);
const dispatch = useDispatch<any>();
useEffect(() => {
if (props?.values?.length) {
const state: any = createInitialState();
setValue(state.value);
}
}, [props]);
useImperativeHandle(ref, () => {
return {
getValue() {
return value;
},
isCancelBeforeStart() {
},
isCancelAfterEnd() {
},
};
});
const onselectionchange = (e) => {console.log(props)
if (props?.properties?.dataFieldRelation) {
let service: any = props?.properties?.dataFieldRelation?.subSetValueService;
if (service !== undefined) {
service = service.replace("{dataFieldLookupId}", e?.target?.value);
dispatch(getDatafieldValues(service, props?.properties?.dataFieldRelation?.childDataFieldId, props.body))
}
const index = props?.datafields?.findIndex((item) => item?.uniqueDataFieldId == props?.properties?.uniqueDataFieldId);
props?.datafields[index]?.validatedValues?.forEach(element => {
if (element?.dataFieldLookupId == e.target.value)
element.isSelected = true;
else
element.isSelected = false;
});
props?.setFields(props?.datafields);
} else {
if (props?.properties?.dataFieldName === "Test Method") {
} else if (props?.properties?.dataFieldName === "Test Result") {
} else {
const index = props?.datafields?.findIndex((item) => item?.uniqueDataFieldId == props?.properties?.uniqueDataFieldId);
props?.datafields[index]?.validatedValues?.forEach(element => {
if (element?.dataFieldLookupId == e.target.value)
element.isSelected = true;
else
element.isSelected = false;
});
props?.setFields(props?.datafields);
}
}
setValue(e.target.value);
}
return (
<select
ref={refSelect}
name={props.properties.uniqueDataFieldId}
id={props.properties.uniqueDataFieldId}
value={value}
onChange={onselectionchange}>
<option value={-1}>--Select--</option>
{props?.values?.map((item, key) => (
<option key={key} value={item.dataFieldLookupId}>{item.dataFieldLookupValue}</option>
))
}
</select>
)
})
//get column definition in dynamically
const getEquipmentColumn = (generalProperties, datafields, isEditMode) => {
const columnInfo: any = [];
if (generalProperties?.length) {
for (var i = 0; i < generalProperties?.length; i++) {
if (generalProperties[i]?.name === "EquipmentId") {
let data: any = {
headerName: "action",
minWidth: 150,
cellRenderer: EditButtom,
editable: false,
colId: "action",
pinned: "left",
lockPinned: true
}
columnInfo.push(data);
}
if (generalProperties[i]?.propertyType !== "INTERNAL") {
let data: any = {
headerName: generalProperties[i]?.alias,
field: generalProperties[i]?.name,
comparator: comparator,
}
if (datafields?.length && isEditMode) {
const filterData = datafields?.filter((item) => item?.uniqueDataFieldId === generalProperties[i]?.uniqueDataFieldId);
if (filterData?.length) {
if (["Text Box", "Text Area"].includes(filterData[0]?.dataEntryControl?.dataEntryControlName)) {
if (filterData[0].disabled === false) {
data.editable = true;
data.cellEditor = "customeTextBox"
data.cellEditorParams = {
properties: filterData[0],
datafields: datafields,
}
} else {
data.editable = false;
}
}
else if (filterData[0]?.dataEntryControl?.dataEntryControlName === "Date Picker") {
data.editable = false;
} else if (filterData[0]?.dataEntryControl?.dataEntryControlName === "Date Time Picker") {
data.editable = false;
} else if (filterData[0]?.dataEntryControl?.dataEntryControlName === "Drop-down list") {
if (filterData[0].disabled === false) {
data.editable = true;
data.cellEditor = "customDropDown"
data.cellEditorParams = {
values: filterData[0].validatedValues,
properties: filterData[0],
datafields: datafields,
body: body,
setFields: (props) => setDatafields([...props]),
}
} else {
data.editable = false;
}
}
} else {
data.editable = false;
}
}
columnInfo.push(data);
}
}
}
return [columnInfo, isEditMode];
}
//getting additional values for dependency dropdown
useLayoutEffect(() => {
if (additionalFieldValues) {
let fieldValues = JSON.parse(JSON.stringify(additionalFieldValues));
const findIndex = datafields?.findIndex((item) => item.uniqueDataFieldId === fieldValues[1]);
if (findIndex >= 0) {
datafields[findIndex].validatedValues = fieldValues[0];
}
agGridRef.current.api.refreshCells({ columns: [`${datafields[findIndex].dataFieldName}`], force: true });
setDatafields(datafields);
dispatch(setAdditionalDatafieldValues(""));
}
}, [additionalFieldValues])
I'm using this react-beautiful-dnd library to be able to reorder lists. However, even though I'm able to drag and drop and re-order, there is a flicker when I try to move a card from one list to another list I call API when a card is dragged to the destination list
const onDragEnd = (result: any) => {
if (!result.destination) {
return;
}
const listCopy: any = { ...elements };
const sourceList = listCopy[result.source.droppableId];
const [removedElement, newSourceList] = removeFromList(
sourceList,
result.source.index
);
listCopy[result.source.droppableId] = newSourceList;
const destinationList = listCopy[result.destination.droppableId];
listCopy[result.destination.droppableId] = addToList(
result.destination.droppableId,
destinationList || [],
result.destination.index,
removedElement,
result.source.droppableId
);
setElements(listCopy)};
and in addToList function I am calling API to update order on server
const addToList = (
changedList: string,
list: any[],
index: number,
element: any,
currentListId: string
) => {
let cardOrder;
const result = Array.from(list);
result.splice(index, 0, element);
const cardCurrentIndex = result.findIndex((item) => item.id === element.id);
if (list.length === 0) {
cardOrder = DEFAULT_PIPELINE_ORDER;
} else if (cardCurrentIndex === 0 && result.length !== 0) {
const nextCardOrder = result[1];
cardOrder = nextCardOrder.current_stage_order - STAGE_INCREMENT_AMOUNT;
} else if (cardCurrentIndex === result.length - 1) {
const nextCardOrder = result[result.length - 2];
cardOrder = nextCardOrder.current_stage_order + STAGE_INCREMENT_AMOUNT;
} else if (
Boolean(result[cardCurrentIndex - 1]) &&
Boolean(result[cardCurrentIndex + 1])
) {
cardOrder = Math.round(
(result[cardCurrentIndex - 1].current_stage_order +
result[cardCurrentIndex + 1].current_stage_order) /
2
);
}
let candidatesData: any = elements;
if (candidatesData) {
if (currentListId === changedList) {
candidatesData[changedList as any] = result as unknown as elementsType;
setElements([...candidatesData]);
} else {
candidatesData[currentListId as any] = candidatesData[
currentListId as any
]?.filter((item: any) => item.id !== element.id);
candidatesData[changedList as any] = result as unknown as elementsType;
setElements([...candidatesData]);
console.log("[...candidatesData]", [...candidatesData]);
}
}
const stageId = stagePipeLineLanes?.find(
(item) => item.id.toString() === changedList.toLowerCase()
)?.id;
if (
changedList === "applied" ||
changedList === "sourcing" ||
changedList === "interviewing"
) {
const changedDestination = changedList;
const destinationStages = positionDetails?.candidate_stages.filter(
(item) =>
item.pipeline.toLowerCase() === changedDestination.toLowerCase()
);
const stage = destinationStages.find((item) => item.is_default === true);
mutate(
{
candidateId: element.id.toString(),
data: compressObject({
stage: stage?.id.toString(),
}),
},
{
onSuccess: (response) => {
if (response) {
toast.success(
`Candidate moved to ${capitalizeFirstLetter(
changedDestination
)}`
);
}
},
}
);
} else {
mutate({
candidateId: element.id.toString(),
data: compressObject({
stage: stageId?.toString() || "",
current_stage_order: cardOrder?.toString() || "",
}),
});
}
return result;
};
I'm both new to d3 and React, and I'm trying to draw a linechart with d3 with data I get from my backend through a async rest call.
When I used static data from a .csv file, it worked. But when I try to use the backend data, neither the xScale is on the correct position nor the lines are shown.
As far I can see from my console outputs, the data is formatted and fetched correctly when I'm trying to draw the lines.
Since neither the xScale or my lines are visible, my guess is that the zoom also doesn't work anymore.
Edit: I also noticed that the xAxis is displayed on top even when I use d3.axisBottom
Hope someone can help me.
type ChartDataType = {
timestamp: Date,
value: number
}
const LineChart = ({
width, height, top, right, bottom, left, fill, url, equipmentUUID
}: ZoomBasicLineChartProps) => {
const dispatch = useDispatch<AppDispatch>()
const [data, setData] = useState<ChartDataType[]>()
const title = useRef<string>('Default title, use label')
const containerRef = useRef<HTMLDivElement>()
let container: Selection<SVGGElement, unknown, HTMLElement, unknown>;
let zoom: d3.ZoomBehavior<HTMLDivElement, unknown>;
const chartWidth = width - left - right
const chartHeight = height - top - bottom
let xAxis: Selection<SVGGElement, unknown, HTMLElement, unknown>;
let yAxis: Selection<SVGGElement, unknown, HTMLElement, unknown>;
let path: any;
const diagramDTO = useSelector(LineChartDiagramSelectors.strippedLineChartDiagramData)
const loadStrippedLineChartDiagramData = useCallback(() => {
dispatch(LineChartDiagramDataThunks.getLineChartDiagramDataArray("services/performancemanagement/api/diagram/line-chart/7f5a2e69-0a51-4131-a77f-601ae9de24c6/0/SchlagstatistikGes_VR1?since=148"))
}, [dispatch])
const containerSelection = useMemo(() => (
d3.select<HTMLDivElement, unknown>('#ZoomLineChart')
), [containerRef.current])
const xScale = useMemo(() => {
const domain = data ? d3.extent(data, (d) => d.timestamp) : [new Date(), new Date()]
console.log("domain")
console.log(domain)
return d3
.scaleTime()
.domain([domain[0] ?? new Date(), domain[1] ?? new Date()])
.range([0, width])
}, [data])
const yScale = useMemo(() => {
const domain = data ? d3.extent(data, (d) => d.value) : [0, 0]
return d3
.scaleLinear()
.domain([domain[0] ?? 0, domain[1] ?? 0])
.range([height, 0])
}, [data])
const initSvg = (): SelectionType => (
containerSelection
.append('svg')
.attr('width', chartWidth + left + right)
.attr('height', chartHeight + top + bottom + 75)
.append('g')
.attr('transform', `translate(${left},${top})`)
)
const drawAxes = (g: SelectionType): void => {
xAxis = g.append('g')
.call(d3.axisBottom(xScale))
yAxis = g.append('g')
.call(d3.axisLeft(yScale))
}
const drawLabel = (g: SelectionType): void => {
g.append('text')
.attr('text-anchor', 'start')
.attr('y', height + 40)
.attr('x', 0)
.text(title.current)
}
const drawLines = (g: SelectionType): void => {
if (data) {
const lines = d3.line<ChartDataType>()
.x(d => {
// console.log(d.timestamp)
return xScale(d.timestamp)
})
.y(d => {
// console.log(d.value)
return yScale(d.value)
})
console.log("data")
console.log(data)
path = g
.append('g')
.attr('clip-path', "url(#clip)")
.append('path')
.datum(data)
.attr('class', 'line')
.attr('fill', 'none')
.attr('stroke', fill)
.attr('stroke-width', 1.5)
.attr('d', lines)
}
}
function updateChart(event: any) {
const {transform} = event;
const newX = transform.rescaleX(xScale);
xAxis.call(d3.axisBottom(newX));
path.attr("d", d3.line<ChartDataType>()
.x(d => newX(d.timestamp))
.y(d => yScale(d.value)));
}
const clip = (g: SelectionType): void => {
g.append("defs")
.append("SVG:clipPath")
.attr("id", "clip")
.append("SVG:rect")
.attr("width", width)
.attr("height", height)
.attr("x", 0)
.attr("y", 0);
}
const initZoom = (g: SelectionType): void => {
zoom = d3.zoom<HTMLDivElement, unknown>()
.scaleExtent([0.5, 5])
.on('zoom', updateChart)
containerSelection.call(zoom)
}
// Parse into data object
useEffect(() => {
if (diagramDTO) {
const updatedData = diagramDTO.payload?.map(dataPoint => {
const value = +dataPoint.value
return {timestamp: dataPoint.timestamp, value}
})
setData(updatedData)
} else {
loadStrippedLineChartDiagramData()
}
}, [diagramDTO])
useEffect(() => {
container = initSvg();
initZoom(container)
clip(container)
drawAxes(container)
drawLines(container)
drawLabel(container)
}, [data])
return (
<>
<Box id='ZoomLineChart' ref={containerRef}/>
</>
)
}
Example data playload:
{
"unit" : "unit.schlagstatistikges_vr1",
"label" : "label.schlagstatistikges_vr1",
"payload" : [ {
"timestamp" : "2022-06-08T03:22:00Z",
"value" : "10676"
}, {
"timestamp" : "2022-06-08T03:23:00Z",
"value" : "10583"
}, {
"timestamp" : "2022-06-08T03:24:00Z",
"value" : "10647"
}, {
"timestamp" : "2022-06-08T03:25:00Z",
"value" : "10585"
}, {
"timestamp" : "2022-06-08T03:26:00Z",
"value" : "10644"
}, {
"timestamp" : "2022-06-08T03:27:00Z",
"value" : "10227"
}, {
"timestamp" : "2022-06-08T03:28:00Z",
"value" : "10620"
}, {
"timestamp" : "2022-06-08T03:29:00Z",
"value" : "10635"
}, {
"timestamp" : "2022-06-08T03:30:00Z",
"value" : "10432"
}, {
"timestamp" : "2022-06-08T03:31:00Z",
"value" : "10295"
}, {
"timestamp" : "2022-06-08T03:32:00Z",
"value" : "10674"
}, {
"timestamp" : "2022-06-08T03:33:00Z",
"value" : "10715"
}, {
"timestamp" : "2022-06-08T03:34:00Z",
"value" : "10068"
}, {
"timestamp" : "2022-06-08T03:35:00Z",
"value" : "10262"
}, {
"timestamp" : "2022-06-08T03:36:00Z",
"value" : "10926"
}, {
"timestamp" : "2022-06-08T03:37:00Z",
"value" : "10271"
}, {
"timestamp" : "2022-06-08T03:38:00Z",
"value" : "10870"
} ],
"color" : "#80BEBF"
}
I found the problem: my IDE recognized the date object, but d3 required an explicit timeparse:
const updatedData = diagramDTO.payload?.map(dataPoint => {
const parser = d3.timeParse('%Y-%m-%dT%H:%M:%S.%LZ')
const timestamp = parser(String(dataPoint.timestamp))!
const value = +dataPoint.value
return {timestamp, value}
})
async function saveBlob() {
const doc = <TableDocument/>;
const asPdf = pdf([]);
await asPdf.updateContainer(doc);
const blob = await asPdf.toBlob();
saveAs(blob, 'instructions.pdf');
}
return (
<IconButton
onClick={() => saveBlob()}>
</IconButton>
);
This is how I am trying to generate my pdf doc by getting blob and then download it. I am doing all things at client side only using reactJS.
On Chrome and Edge browsers it takes 3-4 seconds only but on IE it takes around 30-40 seconds. Its a 3 page pdf doc with simple table (For single page IE takes around 8-9 seconds).
And even after using async await it blocks the UI.
Any help to reduce time to generate pdf on IE will be highly appreciated.
I found pdfmake(https://pdfmake.github.io/docs/0.1/) library which is very fast in generating pdf from client side. on IE also it takes merely 2 seconds to generate pdf.
function TablePDF(t, headerGroups, rows, pageName, messageTitle, messageBody, fontLocale, fontMargin, rowFlag) {
const moreRows = [...rows];
const createTableHeader = col => {
const val = t(col.Header);
return { text: val && val.replace(/<\/br>|<br>/g, '\n'), style: 'tableHeader' };
};
const createTableRow = row => {
const rowElement = [];
Object.entries(row).map(([key, val]) => {
if (key === 'notes' || (rowFlag && key === 'fileNameFileReference')) return null;
const rowCellFormatted =
key === 'instructionStatus'
? mapInstructionStatus(val)
: key === 'instructionType'
? t(mapInstructionTypeStoreData(val))
: val;
const cellText =
(rowCellFormatted &&
(typeof rowCellFormatted === 'string' && rowCellFormatted.replace(/<\/br>|<br>/g, '\n'))) ||
rowCellFormatted ||
'';
rowElement.push({
text: cellText,
margin: [5, fontMargin(getLanguageFont(cellText)), 5, 5],
font: getLanguageFont(cellText)
});
return null;
});
return rowElement;
};
const getTableHeader = () => {
const arrOfHeader =
headerGroups &&
headerGroups.length &&
headerGroups[0].headers
.filter(col => {
if (
col.render('Header') === 'ila.notes' ||
(rowFlag && col.render('Header') === 'ila.fileNameFileReference')
) {
return false;
}
return true;
})
.map((col, index) => createTableHeader(col, index));
return arrOfHeader;
};
const arrOfHeader = getTableHeader();
const getTableRows = () => moreRows.map(row => createTableRow(row.values));
const pdfNote = t('ila.pdfNote');
const mapInstructionStatus = status => {
let statusToReturn = '';
const INSTRUCTION_STATUS = ROW_INSTRUCTION_STATUS;
switch (status) {
case 'PA':
statusToReturn = INSTRUCTION_STATUS[0].label;
break;
case 'RA':
statusToReturn = INSTRUCTION_STATUS[1].label;
break;
case 'P2':
statusToReturn = INSTRUCTION_STATUS[2].label;
break;
case 'R2':
statusToReturn = INSTRUCTION_STATUS[3].label;
break;
case 'P3':
statusToReturn = INSTRUCTION_STATUS[4].label;
break;
case 'R3':
statusToReturn = INSTRUCTION_STATUS[5].label;
break;
case 'P4':
statusToReturn = INSTRUCTION_STATUS[6].label;
break;
case 'P5':
statusToReturn = INSTRUCTION_STATUS[7].label;
break;
case 'RB':
statusToReturn = 'ila.receivedByBank';
break;
case 'JB':
statusToReturn = 'ila.rejectedByBank';
break;
case 'JC':
statusToReturn = 'ila.rejectedByCustomer';
break;
case 'RF':
statusToReturn = 'ila.receivedFwdInstr';
break;
default:
statusToReturn = t(`ila.instructionStatus_${status}`) ? `ila.instructionStatus_${status}` : '';
break;
}
return t(statusToReturn);
};
const mapInstructionTypeStoreData = type => {
switch (type) {
case 'C0':
case 'C1':
case 'PP':
return 'ila.pp';
case 'CS':
case 'COS':
return 'ila.cos';
case 'AP':
case 'ACH':
case 'APC':
return 'ila.achCredit';
case 'AD':
case 'APD':
return 'ila.achDebit';
default:
return type;
}
};
const tableDoc = {
pageOrientation: 'landscape',
pageMargins: [25, 74, 25, 60],
footer: (currentPage, pageCount) => PDFFooter(pageName, pageCount, currentPage),
header: () => PDFHeader(fontMargin(fontLocale), pageName),
content: [
{
text: [{ text: messageTitle, bold: true, color: 'black' }, { text: messageBody }, '\n', { text: pdfNote }],
style: 'note'
},
{
style: 'tableStyle',
table: {
headerRows: 1,
dontBreakRows: true,
body: [arrOfHeader, ...getTableRows()],
widths:
arrOfHeader.length === 9
? ['13.3%', '11.3%', '11.3%', '13.3%', '12.3%', '9.1%', '9.1%', '8.6%', '11.6%']
: ['12.8%', '12.8%', '14.8%', '13.8%', '10.6%', '10.6%', '11.4%', '13.1%']
},
layout: {
vLineWidth: () => 1.2,
hLineWidth: hLineIndex => (hLineIndex === 1 ? 0 : 1.2),
hLineColor: () => '#E4E8EA',
// eslint-disable-next-line no-confusing-arrow
vLineColor: (i, node, rowIndex) =>
rowIndex === 0 || (i === 0 || i === arrOfHeader.length) ? 'white' : '#E4E8EA',
fillColor: rowIndex => (rowIndex === 0 ? '#E4E8EA' : null)
}
}
],
styles: {
tableStyle: {
margin: [0, fontMargin(fontLocale), 0, 20]
},
tableHeader: {
bold: true,
color: 'black',
margin: [5, fontMargin(fontLocale), 5, 5]
},
note: {
lineHeight: 2,
italics: false
}
},
defaultStyle: {
columnGap: 10,
font: fontLocale,
color: '#333333',
fontSize: 8
}
};
return tableDoc;
}
export default TablePDF;
const date = new Date();
const gmtString = date.toGMTString();
const finalDate =
JSON.stringify(gmtString).slice(6, 17) +
',' +
JSON.stringify(gmtString).slice(17, 23) +
JSON.stringify(gmtString).slice(26, 30);
const PDF_Footer = (pageName, totalPages, pageNumber, reference) => ({
margin: [30, 20, 30, 10],
stack: [
{
table: {
headerRows: 1,
widths: ['*'],
body: [[''], ['']]
},
layout: {
hLineWidth: i => (i === 0 ? 1 : 0),
vLineWidth: () => 0,
hLineColor: () => 'black'
}
},
{
columns: [
finalDate + ' | ' + pageName + (reference || ''),
{
text: pageNumber && totalPages && `Page ${pageNumber} of ${totalPages}`,
alignment: 'right'
}
]
}
],
color: '#333333',
bold: false
});
export default PDF_Footer;
export function getLanguageFont(text) {
const universal = /^[a-zA-Z0-9_\-/.!##$%^&*' 'wığüşöçĞÜŞÖÇİÀ-ÿ/]+$/;
const arabic = /[\u0600-\u06FF]/;
const hebrew = /[\u0590-\u05FF]/;
const greek = /[\u0370-\u03ff\u1f00-\u1fff]/;
const japanese = /[\u3000-\u303f\u3040-\u309f\u30a0-\u30ff\uff00-\uff9f\u4e00-\u9faf\u3400-\u4dbf]/;
const korean = /[\u3130-\u318F\uAC00-\uD7AF]/g;
const russian = /[\u0401\u0451\u0410-\u044f]/;
const vietnamese = /^[a-zA-ZÀÁÂÃÈÉÊÌÍÒÓÔÕÙÚĂĐĨŨƠàáâãèéêìíòóôõùúăđĩũơƯĂẠẢẤẦẨẪẬẮẰẲẴẶẸẺẼỀỀỂẾưăạảấầẩẫậắằẳẵặẹẻẽềềểếỄỆỈỊỌỎỐỒỔỖỘỚỜỞỠỢỤỦỨỪễệỉịọỏốồổỗộớờởỡợụủứừỬỮỰỲỴÝỶỸửữựỳỵỷỹ\s\W|_]+$/;
const chinese = /[\u3040-\u30ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff\uff66-\uff9f]/;
if (universal.test(text)) {
return 'UniverseNextHSBC';
} else if (arabic.test(text)) {
return 'arabicFont';
} else if (hebrew.test(text)) {
return 'hebrewFont';
} else if (greek.test(text)) {
return 'hebrewFont';
} else if (japanese.test(text)) {
return 'chineseSimple';
} else if (korean.test(text)) {
return 'korean';
} else if (vietnamese.test(text)) {
return 'vietnamese';
} else if (chinese.test(text)) {
return 'chineseSimple';
}
return 'UniverseNextHSBC';
}
pdfMake.createPdf(tableDoc, null, fontCategories).download(('file.pdf', () => setDownloading(false)));
I have a function called renderExercises which I call in my render function. renderExercises returns an array of ExercisesChoose components.
renderExercises() {
const {selectedType} = this.state;
const allExercises = this.props.exercises;
let exercisesToRender = [];
if (selectedType !== 'all') {
exercisesToRender = allExercises[selectedType];
} else {
exercisesToRender = Object.values(allExercises)
.reduce((array, subarray) => array.concat(subarray), [])
.sort();
}
return exercisesToRender.map((exercise) => {
return (
<ExercisesChoose
key={exercise}
name={exercise}
/>
)
})
}
So far this works. However I also want to filter based on search text if the user has entered this text.
This isn't working as filter can't be called on the existing array exercisesToRender.
if (typeof this.searchText !== 'undefined') {
const searchText = this.searchText.value;
// This is not working
exercisesToRender.filter(item => {
return item.includes(searchText);
});
}
What is the solution to this? Is there a sort method that allows for mutation? If so, is this advisable to use?
This is my current solution which works but is pretty ugly:
renderExercises() {
const {selectedType} = this.state;
const allExercises = this.props.exercises;
let exercisesToRender = [];
if (selectedType !== 'all') {
exercisesToRender = allExercises[selectedType];
} else {
// Combine all the different exercise groups into a single array
exercisesToRender = Object.values(allExercises)
.reduce((array, subarray) => array.concat(subarray), [])
.sort();
}
let render = [];
if (typeof this.searchText !== 'undefined') {
const searchText = this.searchText.value;
render = exercisesToRender.filter(item => {
return item.includes(searchText);
});
} else {
render = exercisesToRender;
}
return render.map((exercise) => {
return (
<ExercisesChoose
key={exercise}
name={exercise}
/>
)
})
}
This is what my exercises object looks like:
this.props.exercises = [
legs:["Squat", "Power squats", "Burpees"]
pull:["Pull up", "Chin up", "Dumbbell curl", "Horizontal row"]
push:["Push up", "Bench press", "Dumbbell bench press", "Mountain climbers"]
cardio: ["Running high knees", "Plank", "Crunches", "Skipping"]
]
My strategy for this case would be:
reduce to filter exercises by type
filter them by searchText
sort
map to render
Final result:
renderExercises() {
const { selectedType } = this.state
const { exercises: allExercises } = this.props
return Object
.keys(allExercises)
.reduce((result, key) => {
if (selectedType === 'all' || key === selectedType) {
return [
...result,
...allExercises[key],
]
}
return result
}, [])
.filter(exercise => searchText ? exercise.includes(searchText) : true)
.sort()
.map(exercise =>
<ExercisesChoose
key={exercise}
name={exercise}
/>
)
}
const exercises = {
legs:["Squat", "Power squats", "Burpees"],
pull:["Pull up", "Chin up", "Dumbbell curl", "Horizontal row"],
push:["Push up", "Bench press", "Dumbbell bench press", "Mountain climbers"],
cardio: ["Running high knees", "Plank", "Crunches", "Skipping"],
}
const filterExercises = (type, searchText) => {
return Object
.keys(exercises)
.reduce((result, key) => {
if (type === 'all' || key === type) {
return [
...result,
...exercises[key],
]
}
return result
}, [])
.filter(exercise => searchText ? exercise.includes(searchText) : true)
.sort()
.join(', ')
}
console.log('All exercises:', filterExercises('all', ''))
console.log('All (up):', filterExercises('all', 'up'))
console.log('Push:', filterExercises('push', ''))
console.log('Push (press):', filterExercises('push', 'press'))
Ive expanded slightly on mersocarlin's answer as I was getting some false results from searchText, but essentially his logic does work.
renderExercises() {
const {selectedType} = this.state;
const allExercises = this.props.exercises;
let searchText = false;
if (this.searchText && this.searchText.value.length > 0) {
searchText = this.searchText.value.toLowerCase();
}
return Object
.keys(allExercises)
.reduce((result, key) => {
if (selectedType === 'all' || key === selectedType) {
return [
...result,
...allExercises[key]
]
}
return result
}, [])
.filter(exercise => searchText ? exercise.toLowerCase().includes(searchText) : true)
.map((exercise) => {
let active = false;
if (this.props.chosenExercise === exercise) {
active = true;
}
return (
<ExercisesChoose
key={exercise}
name={exercise}
active={active}
setNumber={this.props.number}
updateValue={this.props.updateValue}
/>
)
})
}