Hello Friends! In this post, I will show you how to build a candlestick charts in the Lightning web component using the D3.js charts library.
What is D3.js?
D3Js is the data visualization library in Javascript. With the help of that, we can build different types of charts based on the data. D3js uses HTML, CSS, SVG, and Javascript to display the data visualizations. Learn more about D3.js.
For this exercise, we will see how to build candlestick charts using D3JS. We are going to display stock prices on this chart. If you don't know what is candlestick chart then follow this link.
Prerequisites
- Create one custom object to store the stock prices.
-
Object Name: Stock Data (
Stock_Data__c
) - Fields:
- Close Price (
Close_Price__c
) - Currency - Date (
Date__c
) - Date/Time - High Price (
High_Price__c
) - Currency - Low Price (
Low_Price__c
) - Currency - Open Price (
Open_Price__c
) - Currency - Symbol (
Symbol__c
) - Text - Download the D3js code zip file from here.
-
Upload the downloaded
D3.zip
file in static resource with NameD3Js
. - Upload the sample data.
Implementation
Create an apex class with auraEnabled
method to query the stock
price data.
CandleStickChartController.cls
public class CandleStickChartController { @AuraEnabled(cacheable=true) public static List<Stock_Data__c> getStocksData() { return [ SELECT Close_Price__c, Date__c, High_Price__c, Low_Price__c, Open_Price__c FROM Stock_Data__c ORDER BY Date__c LIMIT 50 ]; } }
candleStickChartD3js
and copy-paste the following code in its respective files.
candleStickChartD3js.js
import { LightningElement, wire, api } from "lwc"; import { ShowToastEvent } from "lightning/platformShowToastEvent"; import { loadScript, loadStyle } from "lightning/platformResourceLoader"; // import d3js from static resource import D3Js from "@salesforce/resourceUrl/D3Js"; import getStocksData from "@salesforce/apex/CandleStickChartController.getStocksData"; export default class CandleStickChartD3js extends LightningElement { @api svgWidth = 1000; @api svgHeight = 400; d3Initialized = false; stocksData; renderedCallback() { if (this.d3Initialized) { return; } Promise.all([loadScript(this, D3Js + "/d3.min.js")]) .then(() => { this.d3Initialized = true; this.initializeD3(); }) .catch((error) => { this.dispatchEvent( new ShowToastEvent({ title: "Error loading D3", message: error.message, variant: "error" }) ); }); } @wire(getStocksData) wiredGetStocksData({ error, data }) { if (error) { console.log( "error while getting stocks data", JSON.stringify(error) ); } else if (data) { this.stocksData = data.map((item) => { let newItem = { low: item.Low_Price__c, open: item.Open_Price__c, symbol: item.Symbol__c, close: item.Close_Price__c, high: item.High_Price__c, date: new Date(item.Date__c) }; return newItem; }); this.initializeD3(); } } initializeD3() { if (!this.d3Initialized || !this.stocksData) { return; } let height = this.svgHeight; let width = this.svgWidth; let data = this.stocksData; let margin = { top: 20, right: 30, bottom: 30, left: 40 }; // X scale let x = d3 .scaleBand() .domain( d3.utcDay.range(data[0].date, +data[data.length - 1].date + 1) ) .range([margin.left, width - margin.right]) .padding(0.2); // y scale let y = d3 .scaleLog() .domain([d3.min(data, (d) => d.low), d3.max(data, (d) => d.high)]) .rangeRound([height - margin.bottom, margin.top]); // x axis let xAxis = (g) => g .attr("transform", `translate(0,${height - margin.bottom})`) .style("font-size", "0.8rem") .call( d3 .axisBottom(x) .tickValues( d3.utcMonday .every(width > 720 ? 1 : 2) .range(data[0].date, data[data.length - 1].date) ) .tickFormat(d3.utcFormat("%-m/%-d/%Y")) ) .call((g) => g.select(".domain").remove()); // y axis let yAxis = (g) => g .attr("transform", `translate(${margin.left},0)`) .style("font-size", "0.8rem") .call( d3 .axisLeft(y) .tickFormat(d3.format("$~f")) .tickValues(d3.scaleLinear().domain(y.domain()).ticks()) ) .call((g) => g .selectAll(".tick line") .clone() .attr("stroke-opacity", 0.2) .attr("x2", width - margin.left - margin.right) ) .call((g) => g.select(".domain").remove()); // format date let formatDate = d3.utcFormat("%B %-d, %Y"); function formatChange() { const f = d3.format("+.2%"); return (y0, y1) => f((y1 - y0) / y0); } const svg = d3.select(this.template.querySelector("svg.d3")); svg.attr("viewBox", [0, 0, width, height]); svg.append("g").call(xAxis); svg.append("g").call(yAxis); const g = svg .append("g") .attr("stroke-linecap", "round") .attr("stroke", "black") .selectAll("g") .data(data) .join("g") .attr("transform", (d) => { return `translate(${x(d.date)},0)`; }); g.append("line") .attr("y1", (d) => y(d.low)) .attr("y2", (d) => y(d.high)); g.append("line") .attr("y1", (d) => y(d.open)) .attr("y2", (d) => y(d.close)) .attr("stroke-width", x.bandwidth()) .attr("stroke", (d) => d.open > d.close ? d3.schemeSet1[0] : d.close > d.open ? d3.schemeSet1[2] : d3.schemeSet1[8] ); g.append("title").text( (d) => `${formatDate(d.date)} \n` + `Open: ${d.open}\n` + `Close: ${d.close} (${formatChange()(d.open, d.close)})\n` + `Low: ${d.low}\n` + `High: ${d.high}` ); } }
candleStickChartD3js.html
<template> <lightning-card title="Candlestick Chart Using D3Js" icon-name="custom:custom19"> <div class="slds-m-around_medium slds-align_absolute-center"> <svg class="d3" width={svgWidth} height={svgHeight} lwc:dom="manual"></svg> </div> </lightning-card> </template>
candleStickChartD3js.js-meta.xml
<?xml version="1.0" encoding="UTF-8"?> <LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata"> <apiVersion>50.0</apiVersion> <isExposed>true</isExposed> <targets> <target>lightning__Tab</target> </targets> </LightningComponentBundle>
The Lightning Component tab
Final Notes
- if the static resource is loaded and the script is loaded successfully.
- if the data is loaded correctly and it does not contain any null values.
- if any API method from D3 is no working try putting version
v6.3.1
of d3 in the static resource. - if the data types are correct.
- option to query and display specific data, for example from specific dates.
- zoom and pan features
- click and expand to show more details from records.
gosh, I do agree that you code would work but I want to understand why you have called all those methods and why you have set specifically those variables of d3js, so that I could build my own d3JS, please make some tutorial video or explain here why u have used those specific methods and how will I learn to use them ??
ReplyDeleteHi Arth, thank you for visiting, it really great idea about to make a video tutorial. Till then I can give you little insights on the above code. First thing you should note that D3Js is not just a charting library you can do anything that you can do with SVG and JS to draw and animate images (many sites use d3js for animating the views or drawing abstract svg images), that is also a scalable. If you even want to create your own representation of the data you can do that definitely. So in my opinion D3Js gives us endless possibilities for data visualization.
Deletevariable x - defines the x axis scale based on the data. Which is the smallest and the biggest date in the input data. to define the start and end of x axis.
variable y - defines the y axis scale based on the data. Which is the smallest and the biggest share value in the input data. to define the min and max of y axis.
xAxis - defines the tick values of the x axis based on the zoom level using transform. basically the distance between two marker points on the x axis. like 0, 2, ...n.
yAxis - defines the linear scale of the axis basically the distance between two marker points of the x-axis this can range from days, months, hours based on the zoom values.
then there is some code to draw the lines to show the stock values. some date formatting and displaying titles etc. Now if you back to the code you will know what is happening there. Hope this helps! Thanks for visiting!