Back to Gallery

Stacked Polar Bars

const directions = {
  0: "E", 45: "NE", 90: "N", 135: "NW",
  180: "W", 225: "SW", 270: "S", 315: "SE"
};
const orange = { base: "gold", highlight: "darkOrange" };
const red = { base: "tomato", highlight: "orangeRed" };
const innerRadius = 30;

function CompassCenter(props) {
  const { origin } = props;
  const circleStyle = {
    stroke: red.base, strokeWidth: 2, fill: orange.base
  };

  return (
    <g>
      <circle
        cx={origin.x} cy={origin.y} r={innerRadius} style={circleStyle}
      />
    </g>
  );
}

function CenterLabel(props) {
  const { datum, active, color } = props;
  const text = [ `${directions[datum._x]}`, `${Math.round(datum._y1)} mph` ];
  const baseStyle = { fill: color.highlight, textAnchor: "middle" };
  const style = [
    { ...baseStyle, fontSize: 18, fontWeight: "bold" },
    { ...baseStyle, fontSize: 12 }
  ];

  return active ?
    (
      <VictoryLabel
        text={text} style={style} x={175} y={175} renderInPortal
      />
    ) : null;
}

function App() {
  const [state, setState] = React.useState({ wind: getWindData() });

  React.useState(() => {
    const setStateInterval = window.setInterval(() => {
      setState({ wind: getWindData() });
    }, 4000);

    return () => {
      window.clearInterval(setStateInterval);
    }
  }, []);

  return (
    <VictoryChart
      polar
      animate={{ duration: 500, onLoad: { duration: 500 } }}
      theme={VictoryTheme.material}
      innerRadius={innerRadius}
      domainPadding={{ y: 10 }}
      events={[{
        childName: "all",
        target: "data",
        eventHandlers: {
          onMouseOver: () => {
            return [
              { target: "labels", mutation: () => ({ active: true }) },
              { target: "data", mutation: () => ({ active: true }) }
            ];
          },
          onMouseOut: () => {
            return [
              { target: "labels", mutation: () => ({ active: false }) },
              { target: "data", mutation: () => ({ active: false }) }
            ];
          }
        }
      }]}
    >
      <VictoryPolarAxis
        dependentAxis
        labelPlacement="vertical"
        style={{ axis: { stroke: "none" } }}
        tickFormat={() => ""}
      />
      <VictoryPolarAxis
        labelPlacement="parallel"
        tickValues={_.keys(directions).map((k) => +k)}
        tickFormat={_.values(directions)}
      />
      <VictoryStack>
        <VictoryBar
          style={{ data: {
            fill: ({ active }) => active ? orange.highlight : orange.base,
            width: 40
          } }}
          data={state.wind}
          x="windBearing"
          y="windSpeed"
          labels={() => ""}
          labelComponent={<CenterLabel color={orange}/>}
        />
        <VictoryBar
          style={{ data: {
            fill: (d, a) => a ? red.highlight : red.base,
            width: 40
          } }}
          data={state.wind}
          x="windBearing"
          y={(d) => d.windGust - d.windSpeed}
          labels={() => ""}
          labelComponent={<CenterLabel color={red}/>}
        />
      </VictoryStack>
      <CompassCenter/>
    </VictoryChart>
  );
}

function getWindData() {
  return _.keys(directions).map((d) => {
    const speed = Math.floor(_.random() * 17) + 4;
    return {
      windSpeed: speed,
      windGust: speed + _.random(2, 10),
      windBearing: +d
    };
  });
}

render(<App/>);