Skip to main content
Glama

@arizeai/phoenix-mcp

Official
by Arize-ai
DimensionQuantilesTimeSeries.tsx9.05 kB
import { useState } from "react"; import { graphql, useLazyLoadQuery } from "react-relay"; import { format } from "d3-format"; import { Area, CartesianGrid, ComposedChart, Legend, LegendProps, Line, ResponsiveContainer, Tooltip, TooltipContentProps, XAxis, YAxis, } from "recharts"; import { Text } from "@phoenix/components"; import { ChartTooltip, ChartTooltipItem, defaultTimeXAxisProps, useSequentialChartColors, useTimeTickFormatter, } from "@phoenix/components/chart"; import { useTimeRange } from "@phoenix/contexts/TimeRangeContext"; import { fullTimeFormatter } from "@phoenix/utils/timeFormatUtils"; import { calculateGranularity } from "@phoenix/utils/timeSeriesUtils"; import { DimensionQuantilesTimeSeriesQuery } from "./__generated__/DimensionQuantilesTimeSeriesQuery.graphql"; import { timeSeriesChartMargins } from "./dimensionChartConstants"; /** * Quantiles are floats so we want to create a trimmed down version of the significant digits */ const yTickFormatter = format("~s"); enum Label { p99_p01 = "p99_p01", p75_p25 = "p75_p25", p50 = "p50", } /** * Track whether each label is hidden or not. boolean true means hidden. */ type ChartState = { [label in Label]: boolean }; type ChartDataItem = { timestamp: number; [Label.p99_p01]: [number | null, number | null]; [Label.p75_p25]: [number | null, number | null]; [Label.p50]: number | null; }; const numberFormatter = new Intl.NumberFormat([], { maximumFractionDigits: 2, }); function formatValue(value: number | null) { return typeof value === "number" ? numberFormatter.format(value) : "--"; } const useColors = () => { const colors = useSequentialChartColors(); return { outerColor: colors.grey500, innerColor: colors.grey300, lineColor: colors.default, }; }; function TooltipContent({ active, payload, label, }: TooltipContentProps<number | Array<number | string>, string>) { const { outerColor, innerColor, lineColor } = useColors(); if (active && payload && payload.length) { const data: ChartDataItem = payload[0].payload; return ( <ChartTooltip> {label && ( <Text weight="heavy" size="S">{`${fullTimeFormatter( new Date(label) )}`}</Text> )} <ChartTooltipItem color={outerColor} name="p99" value={formatValue(data[Label.p99_p01][0])} /> <ChartTooltipItem color={innerColor} name="p75" value={formatValue(data[Label.p75_p25][0])} /> <ChartTooltipItem color={lineColor} name="p50" value={formatValue(data.p50)} /> <ChartTooltipItem color={innerColor} name="p25" value={formatValue(data[Label.p75_p25][1])} /> <ChartTooltipItem color={outerColor} name="p01" value={formatValue(data[Label.p99_p01][1])} /> </ChartTooltip> ); } return null; } export function DimensionQuantilesTimeSeries({ dimensionId, }: { dimensionId: string; }) { const { timeRange } = useTimeRange(); const granularity = calculateGranularity(timeRange); const data = useLazyLoadQuery<DimensionQuantilesTimeSeriesQuery>( graphql` query DimensionQuantilesTimeSeriesQuery( $dimensionId: ID! $timeRange: TimeRange! $granularity: Granularity! ) { dimension: node(id: $dimensionId) { id ... on Dimension { p99TimeSeries: dataQualityTimeSeries( metric: p99 timeRange: $timeRange granularity: $granularity ) { data { timestamp value } } p75TimeSeries: dataQualityTimeSeries( metric: p75 timeRange: $timeRange granularity: $granularity ) { data { timestamp value } } p50TimeSeries: dataQualityTimeSeries( metric: p50 timeRange: $timeRange granularity: $granularity ) { data { timestamp value } } p25TimeSeries: dataQualityTimeSeries( metric: p25 timeRange: $timeRange granularity: $granularity ) { data { timestamp value } } p01TimeSeries: dataQualityTimeSeries( metric: p01 timeRange: $timeRange granularity: $granularity ) { data { timestamp value } } } } } `, { dimensionId, timeRange: { start: timeRange.start.toISOString(), end: timeRange.end.toISOString(), }, granularity, } ); const p99data = data.dimension.p99TimeSeries?.data.map((d) => d) || []; const p01data = data.dimension.p01TimeSeries?.data.map((d) => d) || []; const p75data = data.dimension.p75TimeSeries?.data.map((d) => d) || []; const p25data = data.dimension.p25TimeSeries?.data.map((d) => d) || []; const p50data = data.dimension.p50TimeSeries?.data.map((d) => d) || []; const chartData = p99data.map((d, i) => { return { timestamp: new Date(d.timestamp).valueOf(), p99_p01: [d.value, p01data[i].value], p75_p25: [p75data[i].value, p25data[i].value], p50: p50data[i].value, }; }); const timeTickFormatter = useTimeTickFormatter({ samplingIntervalMinutes: granularity.samplingIntervalMinutes, }); // Legend interactivity const [chartState, setChartState] = useState<ChartState>( Object.keys(Label).reduce((a, key) => { a[key as Label] = false; return a; }, {} as ChartState) ); const handleLegendMouseOver: LegendProps["onMouseOver"] = (e) => { if (!chartState[e.dataKey as Label]) { setChartState({ ...chartState }); } }; const handleLegendMouseOut: LegendProps["onMouseOut"] = () => { setChartState({ ...chartState }); }; const selectChartItem: LegendProps["onClick"] = (e) => { setChartState({ ...chartState, [String(e.dataKey)]: !chartState[e.dataKey as Label], }); }; const { outerColor, innerColor, lineColor } = useColors(); return ( <ResponsiveContainer width="100%" height="100%"> <ComposedChart data={chartData} margin={timeSeriesChartMargins} syncId={"dimensionDetails"} > <defs> <linearGradient id="p99_p01ColorUV" x1="0" y1="0" x2="0" y2="1"> <stop offset="10%" stopColor={outerColor} stopOpacity={0.7} /> <stop offset="50%" stopColor={outerColor} stopOpacity={0.3} /> <stop offset="90%" stopColor={outerColor} stopOpacity={0.7} /> </linearGradient> <linearGradient id="p75_p25ColorUV" x1="0" y1="0" x2="0" y2="1"> <stop offset="10%" stopColor={innerColor} stopOpacity={0.7} /> <stop offset="50%" stopColor={innerColor} stopOpacity={0.3} /> <stop offset="90%" stopColor={innerColor} stopOpacity={0.7} /> </linearGradient> </defs> <XAxis {...defaultTimeXAxisProps} tickFormatter={(x) => timeTickFormatter(new Date(x))} /> <YAxis stroke="var(--ac-global-color-grey-500)" label={{ value: "Value", angle: -90, position: "insideLeft", style: { textAnchor: "middle", fill: "var(--ac-global-text-color-900)", }, }} tickFormatter={(x) => yTickFormatter(x)} style={{ fill: "var(--ac-global-text-color-700)" }} /> <CartesianGrid strokeDasharray="4 4" stroke="var(--ac-global-color-grey-500)" strokeOpacity={0.5} /> <Tooltip content={TooltipContent} /> <Legend onClick={selectChartItem} onMouseOver={handleLegendMouseOver} onMouseOut={handleLegendMouseOut} /> <Area type="monotone" dataKey={Label.p99_p01} name="p99 - p01" fillOpacity={1} fill="url(#p99_p01ColorUV)" stroke={outerColor} hide={chartState[Label.p99_p01] === true} /> <Area type="monotone" dataKey={Label.p75_p25} name="p75 - p25" fillOpacity={1} stroke={innerColor} fill="url(#p75_p25ColorUV)" hide={chartState[Label.p75_p25] === true} /> <Line type="monotone" dataKey={Label.p50} stroke={lineColor} hide={chartState[Label.p50] === true} /> </ComposedChart> </ResponsiveContainer> ); }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Arize-ai/phoenix'

If you have feedback or need assistance with the MCP directory API, please join our Discord server