Can anyone help me how to implement mixed chart (Responsive Bar & Line) chart
using nivo chart nivo docs, I've tried to follow some of example like this codesandbox, but the line won't show.
Here is the structure of my data:
"data": [
{
"id": "2023-02-01",
"conversion_rate": 6.9616,
"capture_rate": 19.0435,
"traffic": 8071,
"footfall": 1537,
"conversion": 107
},
{
"id": "2023-02-02",
"conversion_rate": 5.9096,
"capture_rate": 10.3813,
"traffic": 8313,
"footfall": 863,
"conversion": 51
},
{
"id": "2023-02-03",
"conversion_rate": 6.8789,
"capture_rate": 7.035,
"traffic": 13845,
"footfall": 974,
"conversion": 67
},
{
"id": "2023-02-04",
"conversion_rate": 7.9602,
"capture_rate": 9.9642,
"traffic": 18155,
"footfall": 1809,
"conversion": 144
},
{
"id": "2023-02-05",
"conversion_rate": 5.6901,
"capture_rate": 12.8901,
"traffic": 19224,
"footfall": 2478,
"conversion": 141
},
{
"id": "2023-02-06",
"conversion_rate": 7.3981,
"capture_rate": 11.7409,
"traffic": 13585,
"footfall": 1595,
"conversion": 118
},
{
"id": "2023-02-07",
"conversion_rate": 4.9834,
"capture_rate": 10.7847,
"traffic": 8373,
"footfall": 903,
"conversion": 45
},
{
"id": "2023-02-08",
"conversion_rate": 6.3354,
"capture_rate": 9.7352,
"traffic": 8269,
"footfall": 805,
"conversion": 51
},
{
"id": "2023-02-09",
"conversion_rate": 5.6029,
"capture_rate": 8.9278,
"traffic": 9196,
"footfall": 821,
"conversion": 46
},
{
"id": "2023-02-10",
"conversion_rate": 7.3901,
"capture_rate": 8.1822,
"traffic": 13065,
"footfall": 1069,
"conversion": 79
},
{
"id": "2023-02-11",
"conversion_rate": 7.2671,
"capture_rate": 10.2961,
"traffic": 15637,
"footfall": 1610,
"conversion": 117
},
{
"id": "2023-02-12",
"conversion_rate": 7.7323,
"capture_rate": 14.9319,
"traffic": 11606,
"footfall": 1733,
"conversion": 134
},
{
"id": "2023-02-13",
"conversion_rate": 5.4198,
"capture_rate": 15.3507,
"traffic": 6130,
"footfall": 941,
"conversion": 51
},
{
"id": "2023-02-14",
"conversion_rate": 4.6314,
"capture_rate": 16.2869,
"traffic": 6496,
"footfall": 1058,
"conversion": 49
},
{
"id": "2023-02-15",
"conversion_rate": 4.8889,
"capture_rate": 12.0757,
"traffic": 7453,
"footfall": 900,
"conversion": 44
},
{
"id": "2023-02-16",
"conversion_rate": 3.9301,
"capture_rate": 12.8941,
"traffic": 7104,
"footfall": 916,
"conversion": 36
},
{
"id": "2023-02-17",
"conversion_rate": 6.1553,
"capture_rate": 13.4454,
"traffic": 7854,
"footfall": 1056,
"conversion": 65
},
{
"id": "2023-02-18",
"conversion_rate": 8.0982,
"capture_rate": 16.5583,
"traffic": 9844,
"footfall": 1630,
"conversion": 132
},
{
"id": "2023-02-19",
"conversion_rate": 7.6063,
"capture_rate": 22.4454,
"traffic": 7966,
"footfall": 1788,
"conversion": 136
},
{
"id": "2023-02-20",
"conversion_rate": 2.8269,
"capture_rate": 14.9277,
"traffic": 9479,
"footfall": 1415,
"conversion": 40
},
{
"id": "2023-02-21",
"conversion_rate": 3.4237,
"capture_rate": 12.5246,
"traffic": 11194,
"footfall": 1402,
"conversion": 48
},
{
"id": "2023-02-22",
"conversion_rate": 3.7143,
"capture_rate": 15.9399,
"traffic": 8783,
"footfall": 1400,
"conversion": 52
},
{
"id": "2023-02-23",
"conversion_rate": 12.8159,
"capture_rate": 9.5765,
"traffic": 5785,
"footfall": 554,
"conversion": 71
},
{
"id": "2023-02-24",
"conversion_rate": 13.2128,
"capture_rate": 7.3994,
"traffic": 9717,
"footfall": 719,
"conversion": 95
},
{
"id": "2023-02-25",
"conversion_rate": 15.743,
"capture_rate": 9.514,
"traffic": 13086,
"footfall": 1245,
"conversion": 196
},
{
"id": "2023-02-26",
"conversion_rate": 18.1818,
"capture_rate": 10.4057,
"traffic": 9514,
"footfall": 990,
"conversion": 180
},
{
"id": "2023-02-27",
"conversion_rate": 10.1266,
"capture_rate": 5.7907,
"traffic": 10914,
"footfall": 632,
"conversion": 64
},
{
"id": "2023-02-28",
"conversion_rate": 14.1831,
"capture_rate": 11.12,
"traffic": 5009,
"footfall": 557,
"conversion": 79
},
{
"id": "2023-02-29",
"conversion_rate": 0,
"capture_rate": 0,
"traffic": 0,
"footfall": 0,
"conversion": 0
},
{
"id": "2023-02-30",
"conversion_rate": 0,
"capture_rate": 0,
"traffic": 0,
"footfall": 0,
"conversion": 0
},
{
"id": "2023-02-31",
"conversion_rate": 0,
"capture_rate": 0,
"traffic": 0,
"footfall": 0,
"conversion": 0
}
]
Here is how I implement it in code
import React, { useEffect, useState } from 'react';
import { ResponsiveBar } from '@nivo/bar';
import { line } from 'd3-shape';
import { Select } from 'flowbite-react';
import { useRouter } from 'next/router';
import { useDispatch, useSelector } from 'react-redux';
import BrowsingInterestSkeleton from '../skeleton/BrowsingInterest';
import { getStoreMetricGraph } from '../../services/insightV2/storeMetric';
import { setStoreMetricGraph } from '../../features/InsightV2/storeMetricGraphSlice';
const LineConversionRate = ({ bars, xScale, yScale }) => {
const lineGenerator = line()
.x((d) => xScale(d.data.index) + d.width / 2)
.y((d) => yScale(d.data.data.conversion_rate));
return (
<path d={lineGenerator(bars)} fill='none' stroke='hsla(24, 100%, 47%, 1)' />
);
};
const LineCaptureRate = ({ bars, xScale, yScale }) => {
const lineGenerator = line()
.x((d) => xScale(d.data.index) + d.width / 2)
.y((d) => yScale(d.data.data.capture_rate));
return (
<path d={lineGenerator(bars)} fill='none' stroke='hsla(46, 100%, 47%, 1)' />
);
};
export const StoreInsightSummaryChart = () => {
const days = [
{ title: 'Daily', value: 'daily' },
{ title: 'Weekly', value: 'weekly' },
{ title: 'Monthly', value: 'monthly' },
];
const { query } = useRouter();
const dispatch = useDispatch();
const { store, filterReport, accountMetric, storeMetricGraph } = useSelector(
(store) => store,
);
const { filter, custom_date } = filterReport;
const { store_metric_chart } = storeMetricGraph;
const [loading, setLoading] = useState(false);
const [storeDetail, setStoreDetail] = useState({});
// console.log('custom_date <<<<<<<<', custom_date);
// console.log('<<<<<<', store_metric_chart);
const storeDetails = () => {
const splitted =
query['store-insight']?.length && query['store-insight']?.split('&');
setStoreDetail((prev) => {
return {
...prev,
id: splitted[0],
storeName: splitted[1],
storeLocation: splitted[2],
storeLogo: splitted[3],
};
});
};
const fetchData = async () => {
try {
setLoading(true);
if (filter === 'custom') {
// console.log('masuk sini <<<<<<');
const { data } = await getStoreMetricGraph({
days: filter,
storeId: storeDetail?.id,
customStart: custom_date?.custom_start,
customeEnd: custom_date?.custom_end,
});
if (data) {
dispatch(setStoreMetricGraph(data));
}
} else {
const { data } = await getStoreMetricGraph({
days: filter,
storeId: storeDetail?.id,
});
if (data) {
dispatch(setStoreMetricGraph(data));
}
}
} catch (error) {
console.log('store metric chart log <<<<', error);
} finally {
setLoading(false);
}
};
useEffect(() => {
if (query['store-insight'].length > 0) storeDetails();
}, [query]);
useEffect(() => {
if (storeDetail?.id) fetchData();
}, [storeDetail?.id, filter, custom_date]);
return (
<div className='h-[500px] p-4 flex flex-col border rounded-lg bg-white mt-4'>
<div className='flex flex-1 flex-row mb-4'>
<div>
<label className='text-sm font-bold place-self-center'>
Overview
</label>
</div>
<div className='flex flex-1 justify-end'>
<Select
// onChange={(e) => dispatch(setTimePeriodGraph(e.target.value))}
>
{days?.map((v, i) => (
<option key={v.value} value={v.value}>
{v.title}
</option>
))}
</Select>
</div>
</div>
{loading ? (
<BrowsingInterestSkeleton />
) : (
<ResponsiveBar
data={store_metric_chart?.data}
keys={['traffic', 'footfall', 'conversion']}
indexBy='id'
groupMode='grouped'
margin={{ top: 50, right: 130, bottom: 50, left: 60 }}
padding={0.3}
valueScale={{ type: 'linear' }}
indexScale={{ type: 'band', round: true }}
colors={{ scheme: 'nivo' }}
defs={[
{
id: 'dots',
type: 'patternDots',
background: 'inherit',
color: '#38bcb2',
size: 4,
padding: 1,
stagger: true,
},
{
id: 'lines',
type: 'patternLines',
background: 'inherit',
color: '#eed312',
rotation: -45,
lineWidth: 6,
spacing: 10,
},
]}
layers={[
'grid',
'axes',
'bars',
'markers',
'legends',
'annotations',
LineCaptureRate,
LineConversionRate,
]}
// fill={[
// {
// match: {
// id: 'fries',
// },
// id: 'dots',
// },
// {
// match: {
// id: 'sandwich',
// },
// id: 'lines',
// },
// ]}
borderColor={{
from: 'color',
modifiers: [['darker', 1.6]],
}}
axisTop={null}
axisRight={null}
axisBottom={{
tickSize: 5,
tickPadding: 5,
tickRotation: 20,
legend: '',
legendPosition: 'middle',
legendOffset: 32,
}}
axisLeft={{
tickSize: 5,
tickPadding: 5,
tickRotation: 0,
legend: '',
legendPosition: 'middle',
legendOffset: -40,
}}
labelSkipWidth={12}
labelSkipHeight={12}
labelTextColor={{
from: 'color',
modifiers: [['darker', 1.6]],
}}
legends={[
{
dataFrom: 'keys',
anchor: 'top-right',
direction: 'column',
justify: false,
translateX: 120,
translateY: 0,
itemsSpacing: 2,
itemWidth: 100,
itemHeight: 20,
itemDirection: 'left-to-right',
itemOpacity: 0.85,
symbolSize: 20,
effects: [
{
on: 'hover',
style: {
itemOpacity: 1,
},
},
],
},
]}
role='application'
ariaLabel='Nivo bar chart demo'
// barAriaLabel={function (e) {
// return (
// e.id + ': ' + e.formattedValue + ' in country: ' + e.indexValue
// );
// }}
/>
)}
</div>
);
};
Here is the result (Line won't show)!
I'm not sure where did I do wrong...
Try to use indexValue, not index
const LineConversionRate = ({ bars, xScale, yScale }) => {
const lineGenerator = line()
.x((d) => xScale(d.data.indexValue) + d.width / 2)
.y((d) => yScale(d.data.data.conversion_rate))
return <path d={lineGenerator(bars)} fill="none" stroke="hsla(24, 100%, 47%, 1)" />
}