Skip to content

Commit

Permalink
Migrating react-vis to recharts for SearchResults
Browse files Browse the repository at this point in the history
Signed-off-by: Navaneeth Rao <[email protected]>
  • Loading branch information
navaneeth-dev committed Jan 6, 2025
1 parent 9d439db commit ae0cbf8
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,28 +15,34 @@
import React, { useRef, useState, useLayoutEffect } from 'react';
import dayjs from 'dayjs';
import PropTypes from 'prop-types';
import { XYPlot, XAxis, YAxis, MarkSeries, Hint } from 'react-vis';
import { ScatterChart, XAxis, YAxis, ZAxis, Scatter, Tooltip, ResponsiveContainer, Cell } from 'recharts';

import { FALLBACK_TRACE_NAME } from '../../../constants';
import { ONE_MILLISECOND, formatDuration } from '../../../utils/date';

import 'react-vis/dist/style.css';
import './ScatterPlot.css';

export const CustomTooltip = ({ overValue }) => {
if (overValue) {
return <h4 className="scatter-plot-hint">{overValue.current?.name || FALLBACK_TRACE_NAME}</h4>;
}
return null;
};

export default function ScatterPlot(props) {
const { data, onValueClick, calculateContainerWidth } = props;

const containerRef = useRef(null);
const [containerWidth, setContainerWidth] = useState(0);

const [overValue, setValueOver] = useState(null);
const overValue = useRef();

const onValueOver = value => {
setValueOver(value);
overValue.current = value;
};

const onValueOut = () => {
setValueOver(null);
overValue.current = null;
};

useLayoutEffect(() => {
Expand All @@ -57,34 +63,41 @@ export default function ScatterPlot(props) {
return (
<div className="TraceResultsScatterPlot" ref={containerRef}>
{containerWidth && (
<XYPlot
margin={{
left: 70,
}}
width={containerWidth}
colorType="literal"
height={200}
>
<XAxis
title="Time"
tickTotal={4}
tickFormat={t => dayjs(t / ONE_MILLISECOND).format('hh:mm:ss a')}
/>
<YAxis title="Duration" tickTotal={3} tickFormat={t => formatDuration(t)} />
<MarkSeries
sizeRange={[3, 10]}
opacity={0.5}
onValueClick={onValueClick}
onValueMouseOver={onValueOver}
onValueMouseOut={onValueOut}
data={data}
/>
{overValue && (
<Hint value={overValue}>
<h4 className="scatter-plot-hint">{overValue.name || FALLBACK_TRACE_NAME}</h4>
</Hint>
)}
</XYPlot>
<ResponsiveContainer width={containerWidth} height={200}>
<ScatterChart>
<XAxis
label={{ value: 'Time', position: 'insideRight' }}
dataKey="x"
reversed
minTickGap={150}
tickFormatter={t => dayjs(t / ONE_MILLISECOND).format('hh:mm:ss a')}
/>
<YAxis
label={{ value: 'Duration', angle: -90, position: 'insideLeft' }}
dataKey="y"
type="number"
tickCount={3}
tickFormatter={t => formatDuration(t)}
/>
<ZAxis dataKey="size" type="number" range={[90, 300]} />
<Scatter
data={data}
onClick={onValueClick}
onMouseOver={onValueOver}
onMouseOut={onValueOut}
fillOpacity={0.5}
>
{data.map(entry => (
<Cell key={`cell-${entry.traceID}`} fill={entry.color} />
))}
</Scatter>
<Tooltip
isAnimationActive={false}
cursor={false}
content={<CustomTooltip overValue={overValue} />}
/>
</ScatterChart>
</ResponsiveContainer>
)}
</div>
);
Expand All @@ -96,6 +109,7 @@ const valueShape = PropTypes.shape({
traceID: PropTypes.string,
size: PropTypes.number,
name: PropTypes.string,
color: PropTypes.string,
});

ScatterPlot.propTypes = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@

import React from 'react';
import { mount, shallow } from 'enzyme';
import { XAxis, XYPlot, YAxis, MarkSeries, Hint } from 'react-vis';
import { Scatter, ScatterChart, Tooltip, XAxis, YAxis } from 'recharts';

import ScatterPlot from './ScatterPlot';
import ScatterPlot, { CustomTooltip } from './ScatterPlot';
import { ONE_MILLISECOND } from '../../../utils/date';

const generateTimestamp = (hours, minutes, seconds) => {
Expand Down Expand Up @@ -99,9 +99,9 @@ it('<ScatterPlot /> should set fixed container width on initial render', () => {
<ScatterPlot calculateContainerWidth={() => 1200} data={sampleData} onValueClick={() => null} />
);

const xyPlot = wrapper.find(XYPlot).getDOMNode();
const xyPlot = wrapper.find(ScatterChart);

expect(xyPlot.style.width).toBe('1200px');
expect(xyPlot.prop('width')).toBe(1200);
});

it('<ScatterPlot /> should update container width on window resize', () => {
Expand All @@ -118,28 +118,28 @@ it('<ScatterPlot /> should update container width on window resize', () => {

window.dispatchEvent(new Event('resize'));

const xyPlot = wrapper.find(XYPlot).getDOMNode();
const xyPlot = wrapper.find(ScatterChart);

expect(xyPlot.style.width).toBe('700px');
expect(xyPlot.prop('width')).toBe(700);
});

it('<ScatterPlot /> should render Hint correctly', () => {
it('<ScatterPlot /> should render Hint correctly', async () => {
const wrapper = mount(
<ScatterPlot calculateContainerWidth={() => 1200} data={sampleData} onValueClick={() => null} />
);
expect(wrapper).toBeDefined();

const markSeries = wrapper.find(MarkSeries);
const markSeries = wrapper.find(Scatter);
expect(markSeries.length).toEqual(1);

const circle = wrapper.find('.rv-xy-plot__series--mark circle:first-child');
const circle = wrapper.find(Tooltip);
expect(circle.length).toEqual(1);

circle.simulate('mouseOver');
const hint = wrapper.find(Hint);
const hint = wrapper.find(Tooltip);
expect(hint.length).toEqual(1);

circle.simulate('mouseOut');
const noHint = wrapper.find(Hint);
const noHint = wrapper.find(CustomTooltip);
expect(noHint.length).toEqual(0);
});
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ export class UnconnectedSearchResults extends React.PureComponent<SearchResultsP
x: t.startTime,
y: t.duration,
traceID: t.traceID,
size: t.spans.length,
size: t.spans.length * 30,
name: t.traceName,
color: t.spans.some(sp => sp.tags.some(isErrorTag)) ? 'red' : '#12939A',
}))}
Expand Down

0 comments on commit ae0cbf8

Please sign in to comment.