Qt 地震剖面图(或者叫地震摆动图,波形变面积图)_地震剖面图怎么看-程序员宅基地

技术标签: QT随笔  Qt  QCustomPlot  地震剖面图  地震  地震图  

0: 项目需求

近期项目有了新的需求, 需要根据地震数据绘制出对应图表, 关于这种图的资料比较少, 翻了不少网站, 也没找到太多有用的数据, 而关于Qt的, 更是只有一篇论文. 不过搜这么多资料也不是一无所获, 最起码知道了这种图的名字. 如标题所示, 下文统一称其为地震剖面图.

1: 图形分析: 

上图是我查资料时找到的一张地震剖面图的图片, 可以看出,横轴代表通道, 纵轴代表时间, 图表中的折线按照则代表了震动的强度和方向(这一点说的可能不准确),  震动起来的部分,超出某个值的, 则将波峰染成黑色, 波谷则不做处理

2: Qt效果展示

在继续分析之前, 先看下用Qt实现的效果

 

3: 图形分解

如第一张图所示,   图中的折线, 坐标轴等, 可以用QCustomplot来实现. 关于波峰染色,  QCustomPlot在使用时, 如果设置了Brush笔刷, 那么其实就自带染色, 只不过效果可能与我们的需求有差异.

下面是一个简单的折线图, 然后加上笔刷 截图, 可以看到, 箭头指向的部分, 就是我们需要的染色效果. 但是这和我们的需求其实还是有些差距

 主要有2点

1)  这里的染色是以0为基础,  颜色值从0到波形的折线图中进行填充, 并不能控制 "振幅超出某个值以后, 进行填充的效果,  并且, 地震剖面图是有很多条曲线的, 不可能都以0为起点,必然要加上偏移量显示"

2) 图形是横着的, 地震剖面图一般都是竖着, 所以这一点也需要做点调整 

4: 首先尝试现有方案

QCustomPlot中, QGraph类有一个setChannelFillGraph函数, 可以将2个图层之间的部分进行填充, 下边进行测试

1) 先创建2个图层, 并设置数据看看

    QVector<double> x, y, y2;
    for(int i=0; i<11; i++)
    {
        x.push_back(i);

        if(i%3 == 0 || i%4 == 0 || i%8 == 0)
        {
            y.push_back(25);
        }
        else
        {
            y.push_back(10);
        }

        y2.push_back(20);

    }

    //设置交互属性, 可拖动,可缩放
    customPlot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom);

    //添加图层, 并设置画笔和笔刷颜色
    customPlot->addGraph();
    customPlot->graph(0)->setPen(QPen(Qt::black));
    customPlot->graph(0)->setBrush(QColor(255, 0, 0, 50));

    //设置数据进去
    customPlot->graph(0)->setData(x, y);


    //添加图层2, 并设置画笔和笔刷颜色
    customPlot->addGraph();
    customPlot->graph(1)->setPen(QPen(Qt::red));
    customPlot->graph(1)->setBrush(QColor(0, 0, 255, 50));

    //设置数据进去
    customPlot->graph(1)->setData(x, y2);

    //图层间填充
    customPlot->graph(0)->setChannelFillGraph(customPlot->graph(1));
    
    //设置坐标轴范围
    customPlot->xAxis->setRange(0, 10);
    customPlot->yAxis->setRange(-10, 30);

    //重绘
    customPlot->replot();

运行起来, 结果是这样的

 如图所示, 比起直接设置默认笔刷, 图层间填充确实可以实现以某条线为分界, 然后以该线为中心, 进行填充( 或许这样看和直接设置笔刷没啥区别, 但是入如果把红线的笔刷透明, 或者直接把设置图层1笔刷的代码注释掉的话, 就能看出来区别了 )

//    customPlot->graph(1)->setBrush(QColor(0, 0, 255, 50));

那么,  问题来了, 是不是按照这个思路下去, 把红线下边的填充去掉, 只把红线上方的部分进行填充. 就完成任务了?

我认为是不行的,  使用这种方式, 显示一条曲线, 需要2个图层, 并且数据量也是要翻倍的. 如果只显示一条线还好说, 显示的线条数量很多的话, 必然会导致卡顿.

 5: 源码分析

现有代码无法满足需求, 那么就只能对QCustomPlot进行二次开发了. 想要解决这个问题的第一步, 就是要找到, 笔刷填充部分的绘制逻辑

进入到QCustomplot.cpp源码中, 查找QCPGraph的 draw函数

void QCPGraph::draw(QCPPainter *painter)
{
    if (!mKeyAxis || !mValueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; }
    if (mKeyAxis.data()->range().size() <= 0 || mDataContainer->isEmpty()) return;
    if (mLineStyle == lsNone && mScatterStyle.isNone()) return;

    QVector<QPointF> lines, scatters; // line and (if necessary) scatter pixel coordinates will be stored here while iterating over segments

    // loop over and draw segments of unselected/selected data:
    QList<QCPDataRange> selectedSegments, unselectedSegments, allSegments;
    getDataSegments(selectedSegments, unselectedSegments);
    allSegments << unselectedSegments << selectedSegments;
    for (int i=0; i<allSegments.size(); ++i)
    {
        bool isSelectedSegment = i >= unselectedSegments.size();
        // get line pixel points appropriate to line style:
        QCPDataRange lineDataRange = isSelectedSegment ? allSegments.at(i) : allSegments.at(i).adjusted(-1, 1); // unselected segments extend lines to bordering selected data point (safe to exceed total data bounds in first/last segment, getLines takes care)
        getLines(&lines, lineDataRange);

        // check data validity if flag set:
#ifdef QCUSTOMPLOT_CHECK_DATA
        QCPGraphDataContainer::const_iterator it;
        for (it = mDataContainer->constBegin(); it != mDataContainer->constEnd(); ++it)
        {
            if (QCP::isInvalidData(it->key, it->value))
                qDebug() << Q_FUNC_INFO << "Data point at" << it->key << "invalid." << "Plottable name:" << name();
        }
#endif
        //注释的还是比较清除的, 在这里进行了图形填充的绘制
        // draw fill of graph:
        if (isSelectedSegment && mSelectionDecorator)
            mSelectionDecorator->applyBrush(painter);
        else
            painter->setBrush(mBrush);
        painter->setPen(Qt::NoPen);
        drawFill(painter, &lines);

        // draw line:
        if (mLineStyle != lsNone)
        {
            if (isSelectedSegment && mSelectionDecorator)
                mSelectionDecorator->applyPen(painter);
            else
                painter->setPen(mPen);
            painter->setBrush(Qt::NoBrush);
            if (mLineStyle == lsImpulse)
                drawImpulsePlot(painter, lines);
            else
                drawLinePlot(painter, lines); // also step plots can be drawn as a line plot
        }

        // draw scatters:
        QCPScatterStyle finalScatterStyle = mScatterStyle;
        if (isSelectedSegment && mSelectionDecorator)
            finalScatterStyle = mSelectionDecorator->getFinalScatterStyle(mScatterStyle);
        if (!finalScatterStyle.isNone())
        {
            getScatters(&scatters, allSegments.at(i));
            drawScatterPlot(painter, scatters, finalScatterStyle);
        }
    }

    // draw other selection decoration that isn't just line/scatter pens and brushes:
    if (mSelectionDecorator)
        mSelectionDecorator->drawDecoration(painter, selection());
}

上边的代码段是QCPGraph的draw函数内容, 如注释所示, 绘制填充的地方, 在drawFill函数中

那么我们进入到drawFill函数中看一下 

void QCPGraph::drawFill(QCPPainter *painter, QVector<QPointF> *lines) const
{
    if (mLineStyle == lsImpulse) return; // fill doesn't make sense for impulse plot
    if (painter->brush().style() == Qt::NoBrush || painter->brush().color().alpha() == 0) return;

    applyFillAntialiasingHint(painter);
    const QVector<QCPDataRange> segments = getNonNanSegments(lines, keyAxis()->orientation());
    //如果没有设置与目标图层绘图的话, 就绘制一个从曲线到坐标轴的0位置的闭合图形
    if (!mChannelFillGraph)
    {
        // draw base fill under graph, fill goes all the way to the zero-value-line:
        foreach (QCPDataRange segment, segments)
            painter->drawPolygon(getFillPolygon(lines, segment));

    }
    else         //如果设置了目标图层填充, 那就绘制从当前图层到目标图层填充的闭合图形
    {
        // draw fill between this graph and mChannelFillGraph:
        QVector<QPointF> otherLines;
        mChannelFillGraph->getLines(&otherLines, QCPDataRange(0, mChannelFillGraph->dataCount()));
        if (!otherLines.isEmpty())
        {
            QVector<QCPDataRange> otherSegments = getNonNanSegments(&otherLines, mChannelFillGraph->keyAxis()->orientation());
            QVector<QPair<QCPDataRange, QCPDataRange> > segmentPairs = getOverlappingSegments(segments, lines, otherSegments, &otherLines);
            for (int i=0; i<segmentPairs.size(); ++i)
                painter->drawPolygon(getChannelFillPolygon(lines, segmentPairs.at(i).first, &otherLines, segmentPairs.at(i).second));
        }
    }
}

上边的代码段我加了注释

mChannelFillGraph 变量是从哪来的, 我们可以看一下

void QCPGraph::setChannelFillGraph(QCPGraph *targetGraph)
{
    // prevent setting channel target to this graph itself:
    if (targetGraph == this)
    {
        qDebug() << Q_FUNC_INFO << "targetGraph is this graph itself";
        mChannelFillGraph = nullptr;
        return;
    }
    // prevent setting channel target to a graph not in the plot:
    if (targetGraph && targetGraph->mParentPlot != mParentPlot)
    {
        qDebug() << Q_FUNC_INFO << "targetGraph not in same plot";
        mChannelFillGraph = nullptr;
        return;
    }
    
    /* 
        没错, 我们之前测试的时候, 进行图层间填充, 调用了 setChannelFillGraph 函数, 而这个函数
        最终会修改 mChannelFillGraph 的值
    */ 
    mChannelFillGraph = targetGraph;
}

如图所示, 如果我们调用了图层间填充的设置函数,  drawFill函数, 就会进入到 else 选项中, 否则就是绘制从曲线到坐标轴0线的填充, painter->drawPolygon(), 这个函数是QPainter对象的绘制多边形的函数, 在这里我们暂且跳过, 我们需要关注的 是 getFillPolygon, 从名字上可以看出, 这个函数返回一个将要被填充的多边形范围

那么我们进入到 getFillPolygon 函数看看

//此函数返回一个形状, 依靠这个形状进行绘图
const QPolygonF QCPGraph::getFillPolygon(const QVector<QPointF> *lineData, QCPDataRange segment) const
{
    if (segment.size() < 2)
        return QPolygonF();
      QPolygonF result(segment.size()+2);
      result[0] = getFillBasePoint(lineData->at(segment.begin()));
      std::copy(lineData->constBegin()+segment.begin(), lineData->constBegin()+segment.end(), result.begin()+1);
      result.push_back(getFillBasePoint(lineData->at(segment.end()-1)));
      result[result.size()-1] = getFillBasePoint(lineData->at(segment.end()-1));
      return result;
}

getFillPolygon 函数, 通过传递进来的折线图的顶点列表, 再结合 getFillBasePoint 函数获取基线坐标(基线坐标指的是 X轴为水平坐标轴的情况下,  坐标轴 Y轴为0, X轴最左边和X轴最右边, 获取到的2个坐标). 这也就解释了为什么图形填充为什么总是填充到0

那么我们需要动的地方, 就在这里了, 有折线图的顶点坐标列表, 我们就能根据自己的需求, 实现一个我们想要的填充多边形

为了验证我们的猜想, 这里先进行一下测试, 直接返回一个固定形状, 看下是否会绘制出来

注意, 需要把 之前设置的图层间填充的代码屏蔽掉

// customPlot->graph(0)->setChannelFillGraph(customPlot->graph(1));

修改getFillPolygon函数如下

//此函数返回一个形状, 依靠这个形状进行绘图
const QPolygonF QCPGraph::getFillPolygon(const QVector<QPointF> *lineData, QCPDataRange segment) const
{
    //        if (segment.size() < 2)
//            return QPolygonF();
//        QPolygonF result(segment.size()+2);
//        result[0] = getFillBasePoint(lineData->at(segment.begin()));


//        std::copy(lineData->constBegin()+segment.begin(), lineData->constBegin()+segment.end(), result.begin()+1);


//        result.push_back(getFillBasePoint(lineData->at(segment.end()-1)));
//        result[result.size()-1] = getFillBasePoint(lineData->at(segment.end()-1));
//        return result;

        //这里我们直接返回一个三角形
        QPolygonF result;
        result.append(QPointF(150, 50));
        result.append(QPointF(50, 150));
        result.append(QPointF(250, 150));
        return result;


}

然后编译运行, 结果如下所示

 可以看到, 和我们预想的一致, 确实绘制出来了一个三角形.

既然这样, 那我们完全可以按照顶点数据, 重新组组装出来一个多边形结构, 让它将超出我们设置的阈值的数据部分进行填充, 低于该值的则不填充.  

理论可行, 接下来进行修改

再次回到源码部分, 

//此函数返回一个形状, 依靠这个形状进行绘图
const QPolygonF QCPGraph::getFillPolygon(const QVector<QPointF> *lineData, QCPDataRange segment) const
{
    if (segment.size() < 2)
        return QPolygonF();
      QPolygonF result(segment.size()+2);
      result[0] = getFillBasePoint(lineData->at(segment.begin()));
      
      //这里把顶点坐标复制到了多边形顶点坐标中, 这里我们做点处理
      std::copy(lineData->constBegin()+segment.begin(), lineData->constBegin()+segment.end(), result.begin()+1);
      
result.push_back(getFillBasePoint(lineData->at(segment.end()-1)));
      result[result.size()-1] = getFillBasePoint(lineData->at(segment.end()-1));
      return result;
}

代码中的std::copy那一句, 把折线图的顶点坐标复制到了result容器中, result容器,正是保存填充色的容器, 我们就在复制这一步, 做点东西.

既然是超出阈值的才进行染色, 那么不难想到, 我们把数据低于阈值的, 全部设置成和阈值相等,  然后把基线也提升到和阈值相等,  是不是就行了? 

事实上,  如果只这么操作的话,  是会有点问题的, 先看下效果

//此函数返回一个形状, 依靠这个形状进行绘图
const QPolygonF QCPGraph::getFillPolygon(const QVector<QPointF> *lineData, QCPDataRange segment) const
{
    if (segment.size() < 2)
            return QPolygonF();
        QPolygonF result(segment.size()+2);
//        result[0] = getFillBasePoint(lineData->at(segment.begin()));


//        std::copy(lineData->constBegin()+segment.begin(), lineData->constBegin()+segment.end(), result.begin()+1);

        //计算出染色基准线, 这里把阈值设置为20, 也就是说, Y轴数据超出20的部分进行染色
        double divding = 20;
        QPointF start((*lineData)[0].x(), mValueAxis->coordToPixel(divding));
        QPointF end((*lineData).last().x(), mValueAxis->coordToPixel(divding));

        //基线起点
        result[0] = start;

        double divdingPix = mValueAxis->coordToPixel(divding);
        for(int i=0; i<lineData->size(); i++)
        {
            if((*lineData)[i].y() <= divdingPix)            //注意, lineData里边保存的是折线图的图像坐标, 左上角是0,0, 而不是我们图形中的左下角是 0,0
            {
                result.push_back((*lineData)[i]);
            }
            else
            {
                //所有低于阈值的, 都设置成阈值
                result.push_back(QPointF((*lineData)[i].x(), divdingPix));
            }
        }

        //基线终点
        result[result.size()-1] = end;



//        result.push_back(getFillBasePoint(lineData->at(segment.end()-1)));
//        result[result.size()-1] = getFillBasePoint(lineData->at(segment.end()-1));
        return result;
}

将基线位置, 和多边形生成逻辑进行修改, 最终效果如下图

可以看到, 低于阈值的数据确实没了,  但是图好像塌下来了, 其实这个也好理解, 我们把低于阈值的数据全部设置的和阈值相等,  所以只有Y轴被提上去了,  但是折线从阈值线上跨切过去的点并没有被添加到多边形顶点坐标中

所以在这之前, 还需要有一步, 就是把X轴的位置找出来, 也就是从阈值线上跨切过去的坐标

分析出来了原因, 那就继续往下走,  把这些跨切坐标找到, 并添加到多边形顶点坐标中

修改之后的代码如下所示

//此函数返回一个形状, 依靠这个形状进行绘图
const QPolygonF QCPGraph::getFillPolygon(const QVector<QPointF> *lineData, QCPDataRange segment) const
{
    //闭合区间第一个点: QPointF(40.2451,542)

            //使用交点检测方式插入数据
            if (segment.size() < 1)
                return QPolygonF();
            QPolygonF result(1);

            //计算出染色基准线
            double  divding = 20;     
            QPointF start((*lineData)[0].x(), mValueAxis->coordToPixel(divding));
            QPointF end((*lineData).last().x(), mValueAxis->coordToPixel(divding));
                   

            //闭合区间第一个点
            result[0] = QPointF(start.x(), mValueAxis->coordToPixel(divding));

            //阈值线
            QLineF line1(start, end);
            QPointF po;
            int pointCount = 0;

            //生成一个带有跨切点的顶点坐标列表
            QVector<QPointF> out;
            for(int i=0; i<lineData->size()-1; i++)
            {
                //使用QPointF类的判断线段交点的函数寻找交点
                QLineF line2((*lineData)[i], (*lineData)[i+1]);

                out.push_back((*lineData)[i]);

                if(line2.intersects(line1, &po))
                {
                    out.push_back(po);
                    ++pointCount;
                }
            }
            out.push_back(lineData->last());          //把缺失的最后一个点补上


            //这里遍历带有跨切点的像素坐标列表, 同时对数据大于限定值的数据进行限制
            for(int i=0; i<out.size(); i++)
            {
                //这里比较的是像素, 因此坐标轴上小的值,在这里反而会比较大
                if(out[i].y() > start.y())
                {
                    //如果数据值小于阈值线, 那就设置其和阈值线相等
                    result.push_back(QPointF(out[i].x(), start.y()));
                }
                else
                {
                    result.push_back(out[i]);
                }

            }
                
            //打印一下顶点数量和交点数量
            qDebug() << u8"总的点数:" << result.size() << u8"交点数量:" << pointCount;


            //闭合区间最后一个点
            result.push_back(end);
            return result;
}

上边的代码片中, 使用QPointF类的 intersects 函数, 找到了线段的交点, 然后将这个交点也加入到临时顶点坐标中. 然后对临时顶点坐标进行遍历, 并将小于阈值的数据修正到和阈值相等.

进行完了这一步之后,  输出就比较接近我们的需求了

调试输出打印:

总的点数: 19 交点数量: 7

 不考虑调试输出的话, 效果似乎还不错, 但是, 调试输出显示, 顶点数量有19个, 但我们的折线图中, 其实是没这么多顶点的,  而导致顶点数增加的原因, 就在于小于阈值的点, 我们把它移动到了阈值的Y轴位置,  但这一步其实可以省略. 因为有了最左侧和最右侧的蓝色圈位置的顶点, 就已经可以决定填充色多边形的走向了. 因此, 可以把上边代码中的小于阈值部分, 移动到阈值部分的代码注释掉, 直接丢弃这个顶点坐标

//这里比较的是像素, 因此坐标轴上小的值,在这里反而会比较大
if(out[i].y() > start.y())
{
     //数据值小于阈值线, 那就完全可以丢弃了, 这里就不往顶点数据里边加了, 直接注释掉
     //result.push_back(QPointF(out[i].x(), start.y()));
}
else
{
    result.push_back(out[i]);
}

再次运行一下代码,  显示的结果一样, 但是顶点数量就少了

调试输出打印

总的点数: 14 交点数量: 7

可以看到, 相比原来的19个顶点, 现在变成了14个顶点,  这少掉的5个顶点, 其实就是小于阈值的点

 这5个顶点, 从填充多边形的顶点中删除掉了, 但是由于跨切交点的存在, 所以对图形展现并没有影响, 等到数据量多起来的时候, 这一操作可以有效的省略掉大量的点.

比如我们把点数增加到100看看

总的点数: 168 交点数量: 66
总的点数: 118 交点数量: 66

仅仅100个数据点的情况下, 就少掉了50个点, 如果每一条线的点数达到几十K的时候, 这些点数对速度和内存的影响就会大起来.

写到这里, 后边其实也就没什么好说的了

把图形竖起来的话,  只需要把X轴设置成Y轴, Y轴设置成X轴就行了

customPlot->graph(0)->setKeyAxis(ui->customPlot->yAxis);
customPlot->graph(0)->setValueAxis(ui->customPlot->xAxis);

然后再加个阈值变量, 基本上就完事了

6: 效果展示

最后, 在前边的代码基础上, 添加几个图层, 加一些数据看下效果

 

 

 

 改个颜色, 就得到了标题图

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/zh15237774494/article/details/118753006

智能推荐

从零开始搭建Hadoop_创建一个hadoop项目-程序员宅基地

文章浏览阅读331次。第一部分:准备工作1 安装虚拟机2 安装centos73 安装JDK以上三步是准备工作,至此已经完成一台已安装JDK的主机第二部分:准备3台虚拟机以下所有工作最好都在root权限下操作1 克隆上面已经有一台虚拟机了,现在对master进行克隆,克隆出另外2台子机;1.1 进行克隆21.2 下一步1.3 下一步1.4 下一步1.5 根据子机需要,命名和安装路径1.6 ..._创建一个hadoop项目

心脏滴血漏洞HeartBleed CVE-2014-0160深入代码层面的分析_heartbleed代码分析-程序员宅基地

文章浏览阅读1.7k次。心脏滴血漏洞HeartBleed CVE-2014-0160 是由heartbeat功能引入的,本文从深入码层面的分析该漏洞产生的原因_heartbleed代码分析

java读取ofd文档内容_ofd电子文档内容分析工具(分析文档、签章和证书)-程序员宅基地

文章浏览阅读1.4k次。前言ofd是国家文档标准,其对标的文档格式是pdf。ofd文档是容器格式文件,ofd其实就是压缩包。将ofd文件后缀改为.zip,解压后可看到文件包含的内容。ofd文件分析工具下载:点我下载。ofd文件解压后,可以看到如下内容: 对于xml文件,可以用文本工具查看。但是对于印章文件(Seal.esl)、签名文件(SignedValue.dat)就无法查看其内容了。本人开发一款ofd内容查看器,..._signedvalue.dat

基于FPGA的数据采集系统(一)_基于fpga的信息采集-程序员宅基地

文章浏览阅读1.8w次,点赞29次,收藏313次。整体系统设计本设计主要是对ADC和DAC的使用,主要实现功能流程为:首先通过串口向FPGA发送控制信号,控制DAC芯片tlv5618进行DA装换,转换的数据存在ROM中,转换开始时读取ROM中数据进行读取转换。其次用按键控制adc128s052进行模数转换100次,模数转换数据存储到FIFO中,再从FIFO中读取数据通过串口输出显示在pc上。其整体系统框图如下:图1:FPGA数据采集系统框图从图中可以看出,该系统主要包括9个模块:串口接收模块、按键消抖模块、按键控制模块、ROM模块、D.._基于fpga的信息采集

微服务 spring cloud zuul com.netflix.zuul.exception.ZuulException GENERAL-程序员宅基地

文章浏览阅读2.5w次。1.背景错误信息:-- [http-nio-9904-exec-5] o.s.c.n.z.filters.post.SendErrorFilter : Error during filteringcom.netflix.zuul.exception.ZuulException: Forwarding error at org.springframework.cloud..._com.netflix.zuul.exception.zuulexception

邻接矩阵-建立图-程序员宅基地

文章浏览阅读358次。1.介绍图的相关概念  图是由顶点的有穷非空集和一个描述顶点之间关系-边(或者弧)的集合组成。通常,图中的数据元素被称为顶点,顶点间的关系用边表示,图通常用字母G表示,图的顶点通常用字母V表示,所以图可以定义为:  G=(V,E)其中,V(G)是图中顶点的有穷非空集合,E(G)是V(G)中顶点的边的有穷集合1.1 无向图:图中任意两个顶点构成的边是没有方向的1.2 有向图:图中..._给定一个邻接矩阵未必能够造出一个图

随便推点

MDT2012部署系列之11 WDS安装与配置-程序员宅基地

文章浏览阅读321次。(十二)、WDS服务器安装通过前面的测试我们会发现,每次安装的时候需要加域光盘映像,这是一个比较麻烦的事情,试想一个上万个的公司,你天天带着一个光盘与光驱去给别人装系统,这将是一个多么痛苦的事情啊,有什么方法可以解决这个问题了?答案是肯定的,下面我们就来简单说一下。WDS服务器,它是Windows自带的一个免费的基于系统本身角色的一个功能,它主要提供一种简单、安全的通过网络快速、远程将Window..._doc server2012上通过wds+mdt无人值守部署win11系统.doc

python--xlrd/xlwt/xlutils_xlutils模块可以读xlsx吗-程序员宅基地

文章浏览阅读219次。python–xlrd/xlwt/xlutilsxlrd只能读取,不能改,支持 xlsx和xls 格式xlwt只能改,不能读xlwt只能保存为.xls格式xlutils能将xlrd.Book转为xlwt.Workbook,从而得以在现有xls的基础上修改数据,并创建一个新的xls,实现修改xlrd打开文件import xlrdexcel=xlrd.open_workbook('E:/test.xlsx') 返回值为xlrd.book.Book对象,不能修改获取sheett_xlutils模块可以读xlsx吗

关于新版本selenium定位元素报错:‘WebDriver‘ object has no attribute ‘find_element_by_id‘等问题_unresolved attribute reference 'find_element_by_id-程序员宅基地

文章浏览阅读8.2w次,点赞267次,收藏656次。运行Selenium出现'WebDriver' object has no attribute 'find_element_by_id'或AttributeError: 'WebDriver' object has no attribute 'find_element_by_xpath'等定位元素代码错误,是因为selenium更新到了新的版本,以前的一些语法经过改动。..............._unresolved attribute reference 'find_element_by_id' for class 'webdriver

DOM对象转换成jQuery对象转换与子页面获取父页面DOM对象-程序员宅基地

文章浏览阅读198次。一:模态窗口//父页面JSwindow.showModalDialog(ifrmehref, window, 'dialogWidth:550px;dialogHeight:150px;help:no;resizable:no;status:no');//子页面获取父页面DOM对象//window.showModalDialog的DOM对象var v=parentWin..._jquery获取父window下的dom对象

什么是算法?-程序员宅基地

文章浏览阅读1.7w次,点赞15次,收藏129次。算法(algorithm)是解决一系列问题的清晰指令,也就是,能对一定规范的输入,在有限的时间内获得所要求的输出。 简单来说,算法就是解决一个问题的具体方法和步骤。算法是程序的灵 魂。二、算法的特征1.可行性 算法中执行的任何计算步骤都可以分解为基本可执行的操作步,即每个计算步都可以在有限时间里完成(也称之为有效性) 算法的每一步都要有确切的意义,不能有二义性。例如“增加x的值”,并没有说增加多少,计算机就无法执行明确的运算。 _算法

【网络安全】网络安全的标准和规范_网络安全标准规范-程序员宅基地

文章浏览阅读1.5k次,点赞18次,收藏26次。网络安全的标准和规范是网络安全领域的重要组成部分。它们为网络安全提供了技术依据,规定了网络安全的技术要求和操作方式,帮助我们构建安全的网络环境。下面,我们将详细介绍一些主要的网络安全标准和规范,以及它们在实际操作中的应用。_网络安全标准规范

推荐文章

热门文章

相关标签