Closed adburke closed 3 years ago
I did some further digging and noticed that all of this comes in from the c-cartesian-linear-axis data. It looks like only thing that might be missing is the tick styling for the callback function.
I found my answer but it could use some documentation and possibly a slight update to this method. BaseAxis already has the ticksCallback() created. Update to get this working is below.
Modification to baseAxis.js
set ticksCallback(v) { this._content.ticks = this._content.ticks || {}; this._content.ticks.callback = new Function("value", v); }
Example of html
<c-cartesian-linear-axis axis="y" ticks-stepsize="1" position="left" ticks-suggestedmin="0" ticks-callback="return value+ '%'" title-display="false" title-labelstring="Linear axis"></c-cartesian-linear-axis>
Hi @adburke ,
Yes, the idea behind LWCC is that it is a wrapper on top of Chart.js, so you work with component composition.
For the ticksCallback, you can simple use data binding: the same way you do it for other properties, you can also bind functions to the component template: https://developer.salesforce.com/docs/component-library/documentation/en/lwc/lwc.js_props_getter
Also, the good points of using data binding versus the solution you provided is that the code is going to be always more manageable and easier to control from a JS module than directly inside your markup, you can have potential problems when binding this
for the execution context... And the last point is that with your solution, you're totally skipping the two other parameters of the ticksCallback function: index
and values
.
I hope this is clear for you now, and thanks for using the library !
Makes sense. Thanks for the feedback.
Victor,
One other quick question. If I was to add a chartjs plugin or add an inline plugin function, is there a path to do that already in the library? Any guidance here would be appreciated.
@adburke that's a good question! There are some platform restrictions so unfortunately the plugins are not supported yet... It is actually our next goal and we hope to be able to include them soon in the future. Stay tunned ! 😄
Hey @victorgz ,
Thanks for working on such an awesome component. I've decided to integrate LWCC directly in my component code. The chart code examples were really helpful and it's looking good binding simple properties, but I'm currently out of my depths with how to use the callbacks.
Would it be possible to show the JS data binding example using one of the tooltip callbacks, like title or label?
Hey @anicedrop , here we go with a tooltip title callback example:
First, in your component you need to bind your function the same way you bind any other variable:
<c-cartesian-category-axis axis="x" ticks-callback={handleTicksCallback}></c-cartesian-category-axis>
And then in the JS side, you need to define a function passing it the arguments of the callback. For the ticksCallback, the argument is an array of Tooltip Items (see the Tooltip Item Interface section to find out the available properties for each item object). Just make any transformation you want and then return the value:
handleCallbackTitle(tooltipItem){
return `Custom title for ${tooltipItem[0].label} is here!`;
}
Note that some other callbacks might accept more than one argument. Like the ticksCallback for the axes that accepts three: value, index and values. The way of working with that is exactly the same:
handleTicksCallback(value, index, values){
return `${index} : ${value}`;
}
Hope this helps, and thanks for using the library ! 🚀
Thanks @victorgz . The tooltip callbackTitle() is working well on bar and line charts where the return item is listed in the Tooltip Item Interface. The difficulty I'm facing is with the bubble chart:
Here are the values available from the Tooltip Item Interface for the example above:
{ datasetIndex: 0, index: 0, label: "3", value: "7", x: 260.67265625, xLabel: 3, y: 34.400000000000006, yLabel: 7 }
I was actually hoping to return the data label "John" and also the radius value "10" in the title.
How do I accomplish this?
@anicedrop actually Chart.js is not exposing that information, so we don't have access to it. It would be nice to have the value of the third dimension but for now it is still not available.
But the good news is that you can still accomplish that by using the datasetIndex
and index
properties of the Tooltip Items to access directly the information of the dataset (actually it is how Chart.js says to access that information with var label = data.datasets[tooltipItem.datasetIndex].label || '';
). In your case it could be done like:
1) Extract your dataset values to a JS variable so you can easily access that info later:
dataItems = [
{label: 'John', backgroundcolor: 'rgba(82, 183, 216, 1)', detail: [{"x": 3,"y": 7,"r": 10}, {"x": 5,"y": 4,"r": 5}, {"x": 3,"y": 4,"r": 5}]},
{label: 'Paul', backgroundcolor: 'rgba(225, 96, 50, 1)', detail: [{"x": 2,"y": 2,"r": 2}, {"x": 6,"y": 5,"r": 10}, {"x": 4,"y": 2,"r": 5}]},
{label: 'Ringo', backgroundcolor: 'rgba(255, 176, 59, 1)', detail: [{"x": 1,"y": 3,"r": 10}, {"x": 3,"y": 3,"r": 10}, {"x": 6,"y": 4,"r": 10}]}
]
2) Fill you chart data by using the variable above, instead of with harcoded values:
<c-dataset>
<template for:each={dataItems} for:item="item">
<c-data key={item.label}
label={item.label}
detail={item.detail}
backgroundcolor={item.backgroundcolor}
></c-data>
</template>
</c-dataset>
3) Inside your callback function, use the properties datasetIndex
and index
to access the correct value of the dataItem
specific detail. So you can get it with:
const value = this.dataItems[tooltipItem[0].datasetIndex].detail[tooltipItem[0].index].r;
4) At this point, you won't be able to access the dataItems
value. This is because the scope of this
will change when the callback is called, so dataItems
will be undefined. To fix this you can simply change the definition of the callback function from a function declaration to a function expression using arrow functions. By using the arrow functions, the callback will keep the this
value to the component context. More info here:
handleCallbackTitle = (tooltipItem) => {
const value = this.dataItems[tooltipItem[0].datasetIndex].detail[tooltipItem[0].index].r;
return `Custom title is here with value: ${value}`;
}
So with these simple steps, the result will be:
I hope this helps !
Excellent explanation, @victorgz ! I finally feel like I have a good understanding of the callbacks and the data structure. I was able to successfully create a Lightning web component with a bubble chart that displays live Salesforce data from a dev org.
Feel free to take a look here - https://padron-developer-edition.na150.force.com/portfolio/s/
Super helpful. Thanks again!
Hi - I am attempting to render a doughnut chart and have managed to prepare the data in the following way in accordance with @victorgz suggestions in the above comments:
[{"label":"TEST A","detail":2896215,"backgroundcolor":"rgb(110, 64, 170)"},
{"label":"TEST","detail":1440702,"backgroundcolor":"rgb(91, 92, 207)"},
{"label":"TEST C","detail":457358,"backgroundcolor":"rgb(63, 128, 225)"},
{"label":"TEST D","detail":49376,"backgroundcolor":"rgb(37, 168, 218)"},
{"label":"TEST F","detail":39024,"backgroundcolor":"rgb(25, 205, 188)"}]
I am attempting to render the html in the following way but nothing is being shown.
<template>
<div class="slds-card">
<c-chart type="doughnut" responsive="true">
  <c-dataset>
<template for:each={dataItems} for:item="item">
<c-data
key={item.label}
label={item.label}
detail={item.detail}
backgroundcolor={item.backgroundcolor}
borderwidth="3" >
</c-data>
</template>
  </c-dataset>
</c-chart>
</div>
</template>
What am I doing wrong?
Hey @dimikolovopoulos, html looks good. Just to get this out of the way, if you test with static data directly in the html, does it render for you? What does your current JS look like, how are you assigning values to dataItems array? Also, in your data, no need to use quotes around label, detail, or backgroundcolor since they're not reserved.
Hi @anicedrop - thank you so much for reaching out. JS is below - and have confirmed, static data directly in the html does not render for me either :(
JS class below - using a renderedCallBack
method to load the d3 library which I use later on to access the interpolateCool
function. This allows me to dynamically assign a themed colour palette to the chart sections. Once d3 has been initialised I call the method fecthContactData()
which calls the imperative apex function getProductSummaries
. After the label
and detail
values have been set in dataItems
I then populate each element in the array with the loadColours()
function.
import { LightningElement, api, wire, track } from 'lwc';
import D3 from '@salesforce/resourceUrl/d3';
import { loadScript, loadStyle } from 'lightning/platformResourceLoader';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import getProductSummaries from '@salesforce/apex/ContactProductSummaries.getProductSummaries';
export default class ContactSalesByProductWebChart extends LightningElement {
// Object name set in parameters for flow or holdings
@api objName;
@api recordId;
// Object api name (Contact or Account) set by SF on record page
@api objectApiName;
dataItems = [];
d3Initialized = false;
renderedCallback() {
if (this.d3Initialized) {
return;
}
Promise.all([
loadScript(this, D3 + '/d3.v5.min.js'),
loadStyle(this, D3 + '/style.css')
])
.then(() => {
this.d3Initialized = true;
this.fecthContactData();
})
.catch((error) => {
this.dispatchEvent(
new ShowToastEvent({
title: 'Error loading D3',
message: error.message,
variant: 'error'
})
);
});
}
fecthContactData() {
getProductSummaries({recordId: this.recordId , objName: 'Shareholding__c', objApiName: this.objectApiName})
.then((result) => {
// Some data returned
var dataParsed = JSON.parse(JSON.stringify(result).split('children').join('_children'));
dataParsed.forEach(function (arrayItem) {
contactData.push({
"label":arrayItem["xxName"],
"detail": arrayItem["ValuationTotal"],
"backgroundcolor": ""
});
});
this.dataItems = contactData;
this.loadColours();
})
.catch((error) => {
this.error = error;
this.dataItems = undefined;
});
}
loadColours(){
/* Create color array */
var colorScale = d3.interpolateCool;
var colorRangeInfo = {
colorStart: 0,
colorEnd: 0.65,
useEndAsStart: false,
};
var COLORS = this.interpolateColors(this.dataItems.length, colorScale, colorRangeInfo);
this.contactDataColors = COLORS;
this.dataItems.forEach((dataItem, index) => {
const color = COLORS[index];
this.dataItems[index]["backgroundcolor"] = color;
});
}
/*
* Automatically Generate Chart Colors with Chart.js & D3's Color Scales
* DOCUMENTATION: https://codenebula.io/javascript/frontend/dataviz/2019/04/18/automatically-generate-chart-colors-with-chart-js-d3s-color-scales/
*/
calculatePoint(i, intervalSize, colorRangeInfo) {
var { colorStart, colorEnd, useEndAsStart } = colorRangeInfo;
return (useEndAsStart
? (colorEnd - (i * intervalSize))
: (colorStart + (i * intervalSize)));
}
/* Must use an interpolated color scale, which has a range of [0, 1] */
interpolateColors(dataLength, colorScale, colorRangeInfo) {
var { colorStart, colorEnd } = colorRangeInfo;
var colorRange = colorEnd - colorStart;
var intervalSize = colorRange / dataLength;
var i, colorPoint;
var colorArray = [];
for (i = 0; i < dataLength; i++) {
colorPoint = this.calculatePoint(i, intervalSize, colorRangeInfo);
colorArray.push(colorScale(colorPoint));
}
return colorArray;
}
}
Hey guys,
I didn't see any documentation on using the option object from the standard chart.js library. It looks like Chart.drawChart() (chart.js) uses "options: this._configService.getConfig()" on line 386. ConfigService being a refrence to ChartConfigService. I see ChartConfigService has the necessary object setup in the constructor so what is the intended way of setting this. My use case would be to show percentages on the y-axis. Sample below of chart.js setup.
Full example: https://jsfiddle.net/r71no58a/4/
options: { scales: { yAxes: [{ ticks: { min: 0, max: 25, callback: function(value){return value+ "%"} },
scaleLabel: { display: true, labelString: "Percentage" } }] } }