Here, I am using highcharts library for creating drilldown pie chart, I have used click event for filtering and drillup for revert filtering but it is not working for me anybody check it for me..?
Here have shared some of my code :
Create Chart Function
createChart(chartData) {
this.chartData = chartData;
let currencySign = this.currencySign;
let options = {
chart: {
plotBackgroundColor: null,
plotBorderWidth: null,
plotShadow: false,
type: 'pie',
// backgroundColor: bgColor
},
title: {
text: '',
},
plotOptions: {
pie: {
series: {
borderWidth: 0,
dataLabels: {
enabled: true
}
},
cursor: 'pointer',
events: {}
}
},
series: [{
name: "Plant Types",
id: "plant_types",
data: chartData.series
}],
tooltip: {
formatter: function() {
let title = this.series.name;
if(title == 'Products') {
title += " ("+ this.point['rate_unit'] + ")";
}
let pointcolor = this.point.color;
let pointName = this.point.name;
let pointY = numeral(this.point.y).format('0,0');
let pointPercentage = numeral(this.point.percentage).format('0,0.00');
let pointTotal = numeral(this.point.total).format('0,0');
return '<span style="font-size:11px">'+title+'</span><br><span style="color:'+pointcolor+'">'+pointName+':</span> '+currencySign+'' + pointY +'<b>('+pointPercentage+'%)</b><br/>Total: '+currencySign+''+pointTotal;
}
},
drilldown: {
series: []
}
};
options['plotOptions']['pie']['events'] = this.chartClickHandler();
// set theme options if dark theme enabled.
if(this._localStorageService.getIsDarkTheme()) {
Highcharts['theme'] = this._chartThemeOptions.getSearchScreenDarkThemeOptions();
Highcharts.setOptions(Highcharts['theme']);
}
this.chart = new Chart(options);
}
Event Handler Method
chartClickHandler() {
let clickObj = {
click: (e) => {
let userOptions = e.point.series.userOptions;
let key = "";
if(userOptions['id'] == "plant_types") {
this.chart.ref.showLoading('Loading Technologies...');
this.loadTechnologyByPlantType(e.point);
key = "plant_plant_type";
} else if (userOptions['id'] == "technologies") {
this.chart.ref.showLoading('Loading Prime Movers...');
this.loadPrimeMoversByTechnologies(e.point);
key = "generator_technology";
} else if (userOptions['id'] == 'prime_movers') {
let nameArr = (e.point.name).split(' - ');
let primeMoverCode = nameArr[0];
key = "prime_mover";
}
let filterData = {
type: key,
selectedValues: e.point.name
};
this.onSelectFilter.emit(filterData);
},
drillUp: (e) => {
console.log("revert event");
}
};
return clickObj;
}
if drillup event is not using for revert then which event is used for revert any suggestion?
Related
I want to change languageId value without the page reloading. When the languageId value changes the title of products is changed.
export const getServerSideProps = async ({ req, res, query }) => {
try {
const languageId = getCookie('appLanguageId', { req, res })
const countryid = getCookie('countryId', { req, res })
const appLanguage = getCookie('appLanguage', { req, res })
const { id, page, brandId } = query
var fields = [{ condition: 'contains', value: id, dataField: 'categoryPath' }];
// FILTERS
let flag = true;
if (query.brandIds) {
flag = false;
fields.push({ condition: "equal", value: query.brandIds, dataField: 'brandId' });
}
if (query.priceStart) {
flag = false;
fields.push({ condition: ">=", value: query.priceStart, dataField: 'listPrice' });
}
if (query.priceEnd) {
flag = false;
fields.push({ condition: "<=", value: query.priceEnd, dataField: 'listPrice' });
}
const response = await axios.post(urlHelper + '/wapi/v1/product/listSearch',
{
filter: {
fields,
page: { number: page || 1, size: 20, },
}
},
{
headers: { languageId, countryid }
}
);
//
const products = response?.data;
const filterOptions = (await axios.get(`${urlHelper}/wapi/v1/category/filterOptions/${id}`)).data
return {
props: {
products: products?.data || [],
totalCount: products?.totalCount || 0,
filterOptions: filterOptions,
filterObj: {
brandIds: query.brandIds ? query.brandIds : null,
priceStart: query.priceStart ? query.priceStart : null,
priceEnd: query.priceEnd ? query.priceEnd : null,
isAllNull: flag
},
loader: false
},
};
};
I use getServerSideProps. How can I fix it? I have to access product titles correctly when the language change.
I get some data from back-end to show. When I inspect element with React Developer Tools, I can see that data is there but not shown in production. ChartJS version is 3.8, not react-chartjs
I was having the same problem in development, too, but solved it by setting a unique key with key={Math.random()}. In development build, it works just fine. Problem occurs in production. I deploy my app on Firebase.
I wait for data before rendering:
{isAnyFetching ? "Loading..." : <BarChart01 data={chartData} width={595} height={248} key={Math.random()} />}
I tried giving an array of zeroes until data is loaded to be sure chartData changed to trigger re-render by changing the state of the chart component. I also tried giving an extraKey prop and change it with useEffect to re-render again.
The whole chart component is:
function BarChart01({
data,
width,
height
}) {
const canvas = useRef(null);
const legend = useRef(null);
useEffect(() => {
const ctx = canvas.current;
// eslint-disable-next-line no-unused-vars
const chart = new Chart(ctx, {
type: 'bar',
data: data,
options: {
layout: {
padding: {
top: 12,
bottom: 16,
left: 20,
right: 20,
},
},
scales: {
y: {
grid: {
drawBorder: false,
},
ticks: {
maxTicksLimit: 6,
callback: (value) => formatValue(value),
},
},
x: {
type: 'time',
time: {
parser: 'MM-DD-YYYY',
unit: 'month',
displayFormats: {
month: 'MMM YY',
},
},
grid: {
display: false,
drawBorder: false,
},
},
},
plugins: {
legend: {
display: true,
},
tooltip: {
callbacks: {
title: () => false, // Disable tooltip title
label: (context) => formatValue(context.parsed.y),
},
},
},
interaction: {
intersect: false,
mode: 'nearest',
},
animation: {
duration: 500,
},
maintainAspectRatio: false,
resizeDelay: 200,
},
plugins: [{
id: 'htmlLegend',
afterUpdate(c, args, options) {
const ul = legend.current;
if (!ul) return;
// Remove old legend items
while (ul.firstChild) {
ul.firstChild.remove();
}
// Reuse the built-in legendItems generator
const items = c.options.plugins.legend.labels.generateLabels(c);
items.forEach((item) => {
const li = document.createElement('li');
li.style.marginRight = tailwindConfig().theme.margin[4];
// Button element
const button = document.createElement('button');
button.style.display = 'inline-flex';
button.style.alignItems = 'center';
button.style.opacity = item.hidden ? '.3' : '';
button.onclick = () => {
c.setDatasetVisibility(item.datasetIndex, !c.isDatasetVisible(item.datasetIndex));
c.update();
};
// Color box
const box = document.createElement('span');
box.style.display = 'block';
box.style.width = tailwindConfig().theme.width[3];
box.style.height = tailwindConfig().theme.height[3];
box.style.borderRadius = tailwindConfig().theme.borderRadius.full;
box.style.marginRight = tailwindConfig().theme.margin[2];
box.style.borderWidth = '3px';
box.style.borderColor = item.fillStyle;
box.style.pointerEvents = 'none';
// Label
const labelContainer = document.createElement('span');
labelContainer.style.display = 'flex';
labelContainer.style.alignItems = 'center';
const value = document.createElement('span');
value.style.color = tailwindConfig().theme.colors.slate[800];
value.style.fontSize = tailwindConfig().theme.fontSize['3xl'][0];
value.style.lineHeight = tailwindConfig().theme.fontSize['3xl'][1].lineHeight;
value.style.fontWeight = tailwindConfig().theme.fontWeight.bold;
value.style.marginRight = tailwindConfig().theme.margin[2];
value.style.pointerEvents = 'none';
const label = document.createElement('span');
label.style.color = tailwindConfig().theme.colors.slate[500];
label.style.fontSize = tailwindConfig().theme.fontSize.sm[0];
label.style.lineHeight = tailwindConfig().theme.fontSize.sm[1].lineHeight;
const theValue = c.data.datasets[item.datasetIndex].data.reduce((a, b) => a + b, 0);
const valueText = document.createTextNode(formatValue(theValue));
const labelText = document.createTextNode(item.text);
value.appendChild(valueText);
label.appendChild(labelText);
li.appendChild(button);
button.appendChild(box);
button.appendChild(labelContainer);
labelContainer.appendChild(value);
labelContainer.appendChild(label);
ul.appendChild(li);
});
},
}],
});
chart.update();
return () => chart.destroy();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [data]);
return (
<>
<div className="px-5 py-3">
<ul ref={legend} className="flex flex-wrap"></ul>
</div>
<div className="grow">
<canvas ref={canvas} width={width} height={height}></canvas>
</div>
</>
);
}
According to the code, it seems like
button.onclick = () => {
c.setDatasetVisibility(item.datasetIndex,!c.isDatasetVisible(item.datasetIndex));
c.update();
};
part in forEach loop is responsible of this update operation when I click on a label. So it somehow doesn't call update function in production as it should as useEffect listens to data prop.
I am using react quill as rich text editor and I have used quill mention for adding hashtags and people mention in editor. I have went through the docs of quill mention but there is no example for adding links to inserted "hashtag" or "mention".
There is prop, "linkTarget" for adding link but there is no example for addition of link to hashtag and mention.
Hashvalues and atvalues from database:
hashvalues:[{
id:1,
value:"newHashtag"
}]
atvalues:[{
id:1,
value:"Jhon"
}]
So my expected output is:
for hashtag:
<a href:`/#/hashtags/${id}`>#{value}</a>
for people mention:
<a href:`/#/people/${id}`>#{value}</a>
Here's my code for text editor and mention module:
import React, { useEffect, useState } from "react";
import ReactQuill, { Quill } from "react-quill";
import * as Emoji from "quill-emoji";
import "react-quill/dist/quill.snow.css";
import "quill-emoji/dist/quill-emoji.css";
import "quill-mention/dist/quill.mention.css";
import "quill-mention";
//Add https to link if https is not present
const Link = Quill.import("formats/link");
Link.sanitize = function (url) {
// quill by default creates relative links if scheme is missing.
if (!url.startsWith("http://") && !url.startsWith("https://")) {
return `http://${url}`;
}
return url;
};
Quill.register(Link, true);
Quill.register("modules/emoji", Emoji);
// Add sizes to whitelist and register them
const Size = Quill.import("formats/size");
Size.whitelist = ["extra-small", "small", "medium", "large"];
Quill.register(Size, true);
// Add fonts to whitelist and register them
const Font = Quill.import("formats/font");
Font.whitelist = [
"arial",
"comic-sans",
"courier-new",
"georgia",
"helvetica",
"lucida",
];
Quill.register(Font, true);
let atValues = [];
let hashValues = [];
const mention = {
allowedChars: /^[A-Za-z\sÅÄÖåäö]*$/,
mentionDenotationChars: ["#", "#"],
linkTarget:"https://www.google.com",
source: function (searchTerm, renderList, mentionChar, ) {
let values;
if (mentionChar === "#") {
values = atValues;
} else {
values = hashValues;
}
if (searchTerm.length === 0) {
renderList(values, searchTerm);
} else {
const matches = [];
for (let i = 0; i < values.length; i++)
if (~values[i].value.toLowerCase().indexOf(searchTerm.toLowerCase()))
matches.push(values[i]);
renderList(matches, searchTerm);
}
},
};
function Editor(props) {
const [editorHtml, setEditorHtml] = useState("");
const handleChange = (html) => {
setEditorHtml(html);
props.changeHandler(html);
};
useEffect(() => {
if (props.value) {
setEditorHtml(props.value);
} else {
setEditorHtml("");
}
if(props.values){
let hash=props.values
hash.map((v) => {
v["value"] = v["display"]
})
hashValues=hash
}
if(props.people){
let peoples = props.people
peoples.map((v) => {
v["value"] = v["display"]
})
atValues=peoples
}
}, [props.value]);
return (
<div>
<ReactQuill
onChange={handleChange}
value={editorHtml}
modules={modules}
formats={formats}
bounds={".app"}
placeholder={props.placeholder}
/>
</div>
);
}
const modules = {
toolbar: [
[{ header: [1, 2, 3, 4, 5, 6, false] }],
[{ list: "ordered" }, { list: "bullet" }],
["bold", "italic", "underline"],
[{ color: [] }, { background: [] }],
// [{ script: 'sub' }, { script: 'super' }],
[{ align: [] }],
["link", "blockquote", "emoji"],
["clean"],
],
clipboard: {
// toggle to add extra line breaks when pasting HTML:
matchVisual: false,
},
mention,
"emoji-toolbar": true,
"emoji-textarea": false,
"emoji-shortname": true,
};
const formats = [
"header",
"font",
"size",
"bold",
"italic",
"underline",
"strike",
"blockquote",
"list",
"bullet",
"indent",
"link",
"mention",
"emoji",
];
export default function EMTextArea({
placeHolder,
name,
value,
changeHandler,
hash,
peopleMention
}) {
return (
<div className="custom-toolbar-example">
<Editor
placeholder={placeHolder}
name={name}
value={value}
changeHandler={changeHandler}
values={hash}
people={peopleMention}
/>
</div>
);
}
How can I achieve this?
Thank You!
I solved it, I had to add "link" key in atvalues and hashvalues array of objects.
New hashvalues:
hashvalues:[{
id:1,
value:"hashtag",
link:"/#/users/hashtags/1"}]
And in mention module:
const mention = {
allowedChars: /^[A-Za-z\sÅÄÖåäö]*$/,
mentionDenotationChars: ["#", "#"],
linkTarget: '_self',
source: function (searchTerm, renderList, mentionChar, ) {
let values;
if (mentionChar === "#") {
values = atValues;
} else {
values = hashValues;
}
if (searchTerm.length === 0) {
renderList(values, searchTerm);
} else {
const matches = [];
for (let i = 0; i < values.length; i++)
if (~values[i].value.toLowerCase().indexOf(searchTerm.toLowerCase()))
matches.push(values[i]);
renderList(matches, searchTerm);
}
},
};
Thanks, anyway.
I'm using the tinyMCE editor in my React project. I need a custom button based on number of additional users. If it has 3 additional users, I add 3 additional buttons in my dropdown.
import { Editor } from '#tinymce/tinymce-react';
...
const [ totalAdditionalUsers, setTotalAdditionalUsers] = useState(0);
// I get this data from NodeJS backend and set the value inside my useEffect
// I'll simplify the code here
useEffect(() => {
setTotalAdditionalUsers(myVariable); // The value here is 3, for example
});
console.log(totalAdditionalUsers); // it shows 3
return (
<>
<Editor
apiKey={TINYMCEKEY}
value={editorContent}
init={{
height: 600,
menubar: false,
branding: false,
plugins: [
"print"
],
setup: function (editor) {
editor.ui.registry.addMenuButton('addAllSignatures', {
text: "Users Signature",
fetch: function (callback) {
var items = [
{
type: 'menuitem',
text: 'Primary User Signature',
onAction: function () {
editor.insertContent(' <strong>#userSignature#</strong> ');
}
}, {
type: 'menuitem',
text: 'Primary User Signature Date',
onAction: function () {
editor.insertContent(' <strong>#userSignatureDate#</strong> ');
}
}
];
console.log(totalAdditionalUsers); // It is showing 0. Why??
for(let i=1; i<=totalAdditionalUsers; i++) {
let s = 'th';
if(i === 1) s = 'nd';
else if(i === 2) s = 'th';
const objSign = {
type: 'menuitem',
text: `${(i+1)}${s}User Signature`,
onAction: function () {
editor.insertContent(` <strong>#addUser${i}#</strong> `);
}
};
const objDate = {
type: 'menuitem',
text: `${(i+1)}${s}User Signature Date`,
onAction: function () {
editor.insertContent(` <strong>#addUser${i}SignatureDate#</strong> `);
}
};
items.push(objSign);
items.push(objDate);
}
callback(items);
}
})
},
toolbar1: "print | addAllSignatures"
}}
onEditorChange={handleEditorChange}
/>
</>
);
My issue, it that inside the TinyMCE editor, the totalAdditionalUsers is always 0. Looks like it is not updating.
Am I setting in wrong?
Thanks
I want to check an Array until it's filled and show up a loading dialog but it always tells me
this.events[0] is undefined
ngOnInit() {
this.initMethod();
if(this.events[0].start == this.books[0].date_from_og) {
this.dialog.closeAll();
}
}
But events cant be undefined because it contains event of a calendar which get displayed.
initMethod() {
this.service
.getEmployees()
.subscribe(
(listBooks) => {
this.books = listBooks;
this.events = this.books.map((book) => {
return {
start: new Date(book.date_from_og),
end: new Date(book.date_to_og),
type: ""+book.type,
title: "" + book.device + "",
color: colors.blue,
actions: this.actions,
resizable: {
beforeStart: false,
afterEnd: false
},
draggable: false
}
});
},
(err) => console.log(err)
);
}
}
And Constructor:
constructor(private modal: NgbModal, private service: BookingService, private dialog: MatDialog) {
this.initMethod();
this.dialog.open(DialogLaedt, {
width: '650px'
});
Your issue is that you initMethod() retrieves the result asynchronously.
So when you reach the line with if(this.events[0].start == ... there is no guarantee that the event data has been retrieved from the service yet.
The fix is to move your check inside the subscribe part of your init method (which executes as soon as the observable emits it's value), or let the init method return an observable that you can subscribe to, and perform your check inside that subscription.
Solution 1 - Moving your check inside subscription
ngOnInit() {
this.initMethod();
}
initMethod() {
this.service
.getEmployees()
.subscribe(
(listBooks) => {
this.books = listBooks;
this.events = this.books.map((book) => {
return {
start: new Date(book.date_from_og),
end: new Date(book.date_to_og),
type: ""+book.type,
title: "" + book.device + "",
color: colors.blue,
actions: this.actions,
resizable: {
beforeStart: false,
afterEnd: false
},
draggable: false
}
if(this.events[0].start == this.books[0].date_from_og) {
this.dialog.closeAll();
}
});
},
(err) => console.log(err)
);
}
Solution 2 - Letting your initMethod return an Observable
ngOnInit() {
this.initMethod().subscribe(() => {
if(this.events[0].start == this.books[0].date_from_og) {
this.dialog.closeAll();
}
});
}
initMethod() {
return this.service
.getEmployees()
.pipe(tap(
(listBooks) => {
this.books = listBooks;
this.events = this.books.map((book) => {
return {
start: new Date(book.date_from_og),
end: new Date(book.date_to_og),
type: ""+book.type,
title: "" + book.device + "",
color: colors.blue,
actions: this.actions,
resizable: {
beforeStart: false,
afterEnd: false
},
draggable: false
}
});
}))
}
I have noticed that you are calling the initMethod() twice. Once in the constructor, and once in the ngOninit method.