plotly / react-plotly.js

A plotly.js React component from Plotly 📈
MIT License
1.01k stars 135 forks source link

Plotly toImage option - how to save Plotly without title, axis, coloraxis, buttons #295

Open StasSpawn opened 1 year ago

StasSpawn commented 1 year ago

So, how to make an image ONLY with plot graph. Here is demonstation:

This is default saving newplot

This is nedeed result 2022-10-07_015848

JohnCos247 commented 1 year ago

@StasSpawn I am not sure if you can update the layout when using the default export button but you can download the graph directly using plotly functionality.

https://plotly.com/javascript/plotlyjs-function-reference/#plotlydownloadimage

You can override the original graph with whatever layout / configurations you want. Here is an example snippet.

import Plotly from 'plotly.js/lib/core';

Plotly.downloadImage(
    {   
        data: plotlyPlot.data,
        layout: {
            ...plotlyPlot.layout, 
            ...{ Override Options Here }
        },
        config: plotlyPlot.config,
    }, {
        format: 'png',
        filename: 'filename',
        width: 1000,
        height: 500,
    }
);
StasSpawn commented 1 year ago

@JohnCos247 Thank you for the answer. I'm using React. This is supposed to be in config then? 2022-10-19_183017

JohnCos247 commented 1 year ago

I'm using React. This is supposed to be in config then?

@StasSpawn pretty much. You want to pass a reference to that plots element. Here is a quick example of how it could be done. I have done something similar to this before.

import React, { useState } from 'react';
import Plotly from 'plotly.js/lib/core';
import createPlotlyComponent from 'react-plotly.js/factory';
import Heatmap from 'plotly.js/lib/heatmap';

Plotly.register([Heatmap]);
const Plot = createPlotlyComponent(Plotly);

const triggerDownload = (plotlyPlot) => {

    // Ensure that the plot is instantiated
    if (plotlyPlot) {

        // Turn off the colorscale
        const scaleOffData = plotlyPlot.data.map((trace) => {
            ...trace,
            trace.showScale: false
        });

        // Fire the download.
        Plotly.downloadImage(
            {   
                data: scaleOffData,
                layout: {
                    ...plotlyPlot.layout, 
                    ...{ Override Options Here }
                },
                config: plotlyPlot.config,
            }, {
                format: 'png',
                filename: 'your_filename',
                width: 1000,
                height: 500,
            }
        );
    }
};

const yourRenderFunction = () => {
    ...Some variables and stuff

    // this will be the plots ref.
    const [plotRef, setPlotRef] = useState(null);

    // This will be the dom element
    const plotDomElement = plotRef?.el;

    return (
        <>
            <div onClick={() => triggerDownload(plotDomElement)}></div>
            <Plot
                ref={setPlotRef}
                data={json.data}
                layout={json.layout}
                frames={json.config}
                revision={revision}
                onUpdate={onPlotUpdate}
                onButtonClicked={plotlyButtonClicked}
                onSliderEnd={onSliderEnd}
                onRelayout={onPlotRelayout}
            />
        </>
    );
};

You can also use the plotlyPlot to set the width and height to be the same as you see it on screen with a bit more code.

Hassan560 commented 1 year ago

Hi @JohnCos247,

Your solution for enabling image downloads from Plotly charts was incredibly helpful! I implemented it successfully in a React project, but I'm encountering a "self is not defined" error in a Next.js project.

I understand this might be related to Next.js dynamic imports. I've tried using the dynamic function as follows:

` 'use client'

import React from "react"; import dynamic from 'next/dynamic'; const Plotly = dynamic(() => import('plotly.js/lib/core'), { ssr: false });

const createPlotlyComponent = dynamic(() => import('react-plotly.js/factory'), { ssr: false });

const Scatter = dynamic(() => import('plotly.js/lib/scatter'), { ssr: false });

import { Typography, Divider, Card, Button, } from "@mui/material"; import { useState } from "react";

Plotly.register([Scatter]); const Plot = createPlotlyComponent(Plotly);

const triggerDownload = (plotlyPlot) => {

if (plotlyPlot) {

    Plotly.downloadImage(
        {
            data: plotlyPlot.data,
            layout: {
                ...plotlyPlot.layout,
            },
            config: plotlyPlot.config,
        }, {
        format: 'png',
        filename: 'your_filename',
        width: 1000,
        height: 500,
    }
    );
}

};

const Graph = () => {

const [plotRef, setPlotRef] = useState(null)
return (
    <Card
        sx={{
            boxShadow: "1px 1px 8px #80808085",
            height: "598px",
            borderRadius: '9px',

        }}
    >
        <Typography sx={{ padding: 2, color: '#0A5F59' }}>Graph</Typography>
        <Divider />
        <Button onClick={() => triggerDownload(plotRef.el)}>
            Download Image
        </Button>

        <Plot
            ref={setPlotRef}
            style={{ Width: "100%" }}
            data={[
                {
                    x: [1, 2, 3, 4, 5, 6, 7, 8],
                    y: [1, 5, 6, 54, 12, 14, 45, 90],
                    marker: { color: "red" },
                    name: "Seasonality",
                    type: "scatter",
                },
                {
                    x: [1, 2, 3, 4, 5, 6, 7, 8],
                    y: [1, 2, 36, 45, 5, 64, 7, 45],
                    marker: { color: "blue" },
                    name: "Demand",
                    type: "scatter",
                },
                {
                    x: [1, 2, 3, 4, 5, 6, 7, 8],
                    y: [1, 29, 36, 43, 5, 46, 67, 18],
                    marker: { color: "Purple" },
                    name: "Residual",
                    type: "scatter",
                },
                {
                    x: [1, 2, 3, 4, 5, 6, 7, 8],
                    y: [1, 25, 37, 44, 65, 46, 37, 81],
                    marker: { color: "green" },
                    name: "Trend",
                    type: "scatter",
                },
            ]}
            config={{ responsive: true }}
        />
    </Card>
);

};

export default Graph;`

i got this error image

Hassan560 commented 1 year ago

Hello everyone,

I encountered an issue in my React project involving a component called TimeSeriousPlot that uses Plotly charts. Specifically, I wanted to implement a feature to download images of these charts when a certain condition is met.

After some investigation and debugging, I managed to find a solution to the problem, and I wanted to share it with the community in case others face a similar issue.

Here's the updated code for the TimeSeriousPlot component with the solution:


import { forwardRef, useEffect, useState, useCallback } from 'react';
import Plotly from "plotly.js/lib/core";
import createPlotlyComponent from 'react-plotly.js/factory';
import Scatter from 'plotly.js/lib/scatter';

Plotly.register([Scatter]);
const Plot = createPlotlyComponent(Plotly);

const TimeSeriousPlot = forwardRef((props, ref) => {
    const { customRef, screenShot, setScreenShot } = props;

    const [plotRef, setPlotRef] = useState(null);

    const triggerDownload = useCallback(() => {
        if (screenShot) {
            Plotly.downloadImage(
                {
                    data: plotRef?.el?.data,
                    layout: {
                        ...plotRef?.el?.layout,
                    },
                    config: plotRef?.el?.config,
                }, {
                format: 'png',
                filename: 'your_filename',
                width: 1000,
                height: 500,
            );
        }
        setScreenShot(false);
    }, [screenShot]);

    triggerDownload();

    useEffect(() => {
        if (plotRef !== null) {
            if (!!customRef) customRef(plotRef);
        }
    }, [plotRef]);

    return (
        <Plot ref={setPlotRef} {...props} />
    );
});

export default TimeSeriousPlot;
than i just dynamically import in the main page